java源文件编译成jar
最近,我花了一些时间来研究有效java ,该方法正在GitHub上达到300星(可以免费帮助实现目标:D)。
Effectivejava是在您的Java代码上运行查询的工具。 它基于我参与的另一个项目javaparser 。 Javaparser将Java源代码作为输入,并生成一个抽象语法树(AST)。 我们可以直接在AST上执行简单的分析。 例如,我们可以找出哪些方法采用超过5个参数(您可能希望对其进行重构……)。 但是,更复杂的分析要求解析符号 。
在这篇文章中,我将介绍在考虑源代码和JAR文件的情况下如何实现符号解析。 在第一篇文章中,我们将在源代码和JAR文件上建立同质视图,在下一篇文章中,我们将探索这些模型来解决这些符号。
代码可在GitHub上的有效Java的分支symbolsolver中获得。
解析符号
出于什么原因,我们需要解析符号?
给出以下代码:
foo.method(a, b, c);
我们需要弄清楚foo , method , a , b , c是什么。 它们是否引用局部变量? 给当前方法的参数? 到在类中声明的字段? 要从超类继承的字段? 他们有什么类型? 为了回答这个问题,我们需要能够解析符号。
为了解决符号,我们可以浏览AST并应用作用域规则。 例如,我们可以查看某个符号是否对应于局部变量。 如果没有,我们可以在该方法的参数中查找。 如果仍然找不到对应关系,则需要在类声明的字段中查找,如果仍然不走运,则可能必须在此类继承的字段中走运。
现在,作用域规则比我刚刚描述的一小步要复杂得多。 由于重载,解决方法特别复杂。 但是,一个关键点是,要解决符号,我们通常需要在导入的类,扩展的类和外部类中进行查找,这些类可能是项目的一部分,也可以作为依赖项导入。
因此,要解决符号,我们需要寻找相应的声明:
- 根据我们正在检查的项目类别的AST
- 在用作依赖项的JAR文件中包含的类中
Javaparser为我们提供了第一点所需的AST,对于第二点,我们将使用Javassist在JAR文件中构建类的模型。
建立JAR文件中包含的类的模型
我们的符号求解器应按顺序在条目列表(我们的类路径条目)中查找,并查看是否可以在其中找到某个类。 为此,我们需要打开JAR文件并在其内容中查找。 出于性能原因,我们可能希望构建给定JAR中包含的元素的缓存。
(ns app.jarloading
(:use [app.javaparser])
(:use [app.operations])
(:use [app.utils])
(:import [app.operations Operation]))
(import java.net.URLDecoder)
(import java.util.jar.JarEntry)
(import java.util.jar.JarFile)
(import javassist.ClassPool)
(import javassist.CtClass)
; An element on the classpath (a single class, interface, enum or resource file)
(defrecord ClasspathElement [resource path contentAsStreamThunk])
(defn- jarEntryToClasspathElement [jarFile jarEntry]
(let [name (.getName jarEntry)
content (fn [] (.getInputStream jarFile jarEntry))]
(ClasspathElement. jarFile name content)))
(defn getElementsEntriesInJar
"Return a set of ClasspathElements"
[pathToJarFile]
(let [url (URLDecoder/decode pathToJarFile "UTF-8")
jarfile (new JarFile url)
entries (enumeration-seq (.entries jarfile))
entries' (filter (fn [e] (not (.isDirectory e))) entries )]
(map (partial jarEntryToClasspathElement jarfile) entries')))
(defn getClassesEntriesInJar
"Return a set of ClasspathElements"
[pathToJarFile]
(filter (fn [e] (.endsWith (.path e) ".class")) (getElementsEntriesInJar pathToJarFile)))
(defn pathToTypeName [path]
(if (.endsWith path ".class")
(let [path' (.substring path 0 (- (.length path) 6))
path'' (clojure.string/replace path' #"/" ".")
path''' (clojure.string/replace path'' "$" ".")]
path''')
(throw (IllegalArgumentException. "Path not ending with .class"))))
(defn findEntry
"return the ClasspathElement corresponding to the given name, or nil"
[typeName classEntries]
(first (filter (fn [e] (= typeName (pathToTypeName (.path e)))) classEntries)))
(defn findType
"return the CtClass corresponding to the given name, or nil"
[typeName classEntries]
(let [entry (findEntry typeName classEntries)
classPool (ClassPool/getDefault)]
(if entry
(.makeClass classPool ((.contentAsStreamThunk entry)))
nil)))
我们如何开始? 首先,我们阅读jar中列出的条目( getElementEntriesInJar )。 这样,我们得到了ClasspathElements的列表。 然后,我们仅关注.class文件( getClassesEntriesInJar )。 每个jar应调用一次此方法,并且应将结果缓存。 给定ClasspathElement列表,然后我们可以搜索与给定名称对应的元素(例如com.github.javaparser.ASTParser )。 为此,我们可以使用方法findEntry 。 或者,我们也可以使用Javassist加载该类: findType方法执行的操作,返回CtClass的实例。
为什么不仅仅使用反射?
有人可能会认为,仅在有效java的类路径中添加依赖项,然后使用常规的类加载器和反射来获取所需的信息会更容易。 虽然这会更容易,但是存在一些缺点:
- 当加载一个类时,将执行静态初始化程序,这可能不是我们想要的
- 它可能与有效Java的实际依赖项冲突。
- 最后,并非所有字节码中可用的信息都可以通过反射API轻松检索到
解决符号:结合异构模型
现在,要解决符号问题,我们将必须实现作用域规则,并浏览从Javaparser获得的AST和从Javassist获得的CtClass 。 我们将在以后的博客文章中看到详细信息,但是我们需要首先考虑另一个方面。 考虑以下代码:
package me.tomassetti;
import com.github.someproject.ClassInJar;
public class MyClass extends ClassInJar {
private int myDeclaredField;
public int foo(){
return myDeclaredField + myInheritedField;
}
}
在这种情况下,我们假设有一个包含类com.github.someproject.ClassInJar的JAR,该类声明了字段myInheritedField 。 当我们求解符号时,将具有以下映射:
- myDeclaredField将被解析为com.github.javaparser.ast.body.VariableDeclarator的一个实例(在JavaParser类我们有映射到结构,如私人INT A,B,C型FieldDeclaration的节点; VariableDeclarators代替点到单个字段例如a , b或c )
- myInheritedField将解析为javassist.CtField的实例
问题在于我们希望能够以同质的方式对待它们:我们应该能够使用相同的函数来对待每个字段,而不管它们的起源(JAR文件还是Java源文件)。 为此,我们将使用clojure 协议构建通用视图。 我倾向于将clojure的协议视为与Java 接口等效。
(defprotocol FieldDecl
(fieldName [this]))
(extend-protocol FieldDecl
com.github.javaparser.ast.body.VariableDeclarator
(fieldName [this]
(.getName (.getId this))))
(extend-protocol FieldDecl
javassist.CtField
(fieldName [this]
(.getName this)))
在Java中,我们必须构建适配器,实现新的接口( FieldDecl )并将现有的类( VariableDeclarator , CtField )包装在Clojure中,我们只能说这些类扩展了协议,我们已经完成了。
现在我们可以将每个字段都视为fieldDecl ,并且可以在每个字段上调用fieldName 。 我们仍然需要弄清楚如何解决字段类型 。 为此,我们需要研究符号解析,尤其是类型解析,这是我们的下一步。
结论
Java代码的构建模型使我着迷了一段时间。 作为我的硕士论文的一部分,我写了一个与现有Java代码交互的DSL(我也有编辑器,写为Eclipse插件和代码生成器:这很酷)。 在DSL中,可以使用源代码和JAR文件指定对Java类的引用。 我使用的是EMF,并且可能在该项目中采用了JaMoPP和Javassist。
后来,我建立了CodeModels库,以分析几种语言(Java,JavaScript,Ruby,Html等)的AST。
我认为构建用于操作代码的工具是元编程的一种非常有趣的形式,并且应该在每个开发人员的工具箱中。 我计划花更多的时间来使用有效的java。 有趣的时刻来了。
随时分享评论和建议!
翻译自: https://www.javacodegeeks.com/2015/08/building-models-of-java-code-from-source-and-jar-files.html
java源文件编译成jar