希望大牛加入,共同为项目智能化管理jar包而努力


想听听大家对于我这个想法的一些看法,喷也好,赞也罢,希望留下您宝贵的建议!
 
我一直认为java程序员不需要自己去管理项目中依赖的jar,甚至不需要知道jar包的存在。什么,你不知道jar?好吧,下面就来说一说jar包。
 
就是一种被广泛使用的文件格式,比如你开发了一个牛逼闪闪的小程序,你们公司的其它人也想使用这个小程序,怎么办?
一般的解决办法就是将这个程序打包成jar文件,发布到某个可供分享的地方(比如nexus),其它人如果使用maven或者gradle来管理
项目的话,只需要加个jar的依赖配置即可,类似maven与Gradle的写法如下:
 
Maven引入外部依赖写法:

<dependency>
   <groupId>org.hibernate</groupId>
   <artifactId>hibernate-core</artifactId>
   <version>3.6.7.Final</version>
</dependency>

Gradle引入外部依赖写法:

dependencies {
    compile group: 'org.hibernate', name: 'hibernate-core', version: '3.6.7.Final'
}


看到了吧,当你的程序需要使用第三方封装好的功能时,可以通过引入jar包来实现。
 
我为什么感觉到没有必要呢?因为我觉得完全可以依赖于某个智能化工具来处理这件事情,也就是智能化引包。想想下面的一些问题吧。
 
 
假如我要在项目中添加邮件发送功能并且想通过第三方jar包来实现时,不但要知道相关功能在哪个jar包(幸亏maven也帮助我们引入了三方包的依赖),并且需要准确写出jar包的相关信息,比如唯一名称,版本号等。
 
 
假如我使用了hibernate的3.6.7.Final这个版本,那么在新需求开发时,项目还需要用到spring相关的功能,应该怎么做呢?
搜索使用spring需要引入哪些依赖包吗?根据本人多年的经验,如果你不查一下与已有Hibernate相关包的兼容性,那起冲突的概率是非常大的。
 
PS:不要和我说Eclipse,IDEA不是有这个功能吗?我说的不是项目新建的时候,而是在项目开发过程中加入新的三方jar包。
 
 
你有遇到过ClassNotFoundException异常吗?我觉得大部分时候出现这种异常都是由于相关jar包引入不全,或者由于版本的问题,某个类并不存在而引起的。这种问题一般在运行项目时
才会发现,想想为什么?很简单,你的源代码中肯定没有直接用这个类,项目不报错你当然在编写源代码时发现不了。
 
 
以前工作时项目经理让我把两个老项目改造成maven项目,以方便对项目进行jar包管理,项目打包、编译和部署。我查找了这个项目依赖的每一个jar包(包多,非常痛苦),然后转换为maven的<dependency>,那时候想了想,不就是把以前依赖的包转换为maven的写法,让maven来引入不就行了吗?其实事情远没有想像的那么简单,尤其是你的项目依赖的包很多时,非常容易引起冲突。
 
举个例子,好多的三方jar包会依赖log4j或者logback进行日志记录,maven就会帮助你引入这些三方jar包所依赖的包,那么就有可能引入一些版本不同的日志记录包,也就是项目中出现了好多相同的类,还需要配合<dependency>节点下的<exclude>来排除某些包。
 
记得曾经struts2的核心包暴露了一个巨大的安全隐患,而你又不幸的在项目中使用了这一版本的包,那么肯定还需要修改依赖配置,引入最新发布的补丁包(你知道补丁包具体的版本号是多少吗)。对于知道的人是这样,对于不知道的人也就那样了。
 
 
如果你经历过这些事情,那么假如现在有一个智能管理jar包的工具(暂时命名为autort,意为auto import)为你管理类似下面这一坨的东西,你愿不愿意用上一用呢?

<dependencies> 
    <dependency> 
        <groupId>org.apache.geronimo.ext.openejb</groupId> 
        <artifactId>javaee-api</artifactId> 
        <version>5.0.3</version> 
    </dependency> 
    <dependency> 
      <groupId>javax.servlet</groupId> 
      <artifactId>jstl</artifactId> 
      <version>1.2</version> 
      <scope>provided</scope> 
    </dependency> 
    <dependency> 
      <groupId>javax.servlet.jsp</groupId> 
      <artifactId>jsp-api</artifactId> 
      <version>2.1</version> 
      <scope>provided</scope> 
    </dependency> 
    ...
</dependencies> 


使用时当你再想使用三方依赖的功能,如javamail的邮件发送功能时,可直接查找javamail api,在类中编写相关代码即可。如使用如下的类
HtmlEmail email = new HtmlEmail();
还需要手动键入import相关信息,如:
import org.apache.commons.mail.HtmlEmail;

运行autort后会引入与当前项目中其它jar包不冲突的,最新稳定版本的javamail相关包。添加上面的信息是为了告诉autort,我需要使用javamail相关的发送功能,如果你去掉了HtmlEmail类及相关引入,那么再次运行管理工具后,相关包也会去除。

 
不必担心在使用某个api接口时,当前javamail版本是否支持的问题。如果出现了找不到相关方法的提示时,只管调用这个方法,然后重新运行一下autort即可。
 
 
如果使用编程工具如Eclipse或IDEA的话项目可能显示红叉或者红色下划线,完全不必在意,需要保证的是你的项目没有语法错误,只是缺少了相关jar包而已。
 
 
听着还不错吧,不过要是实现起来可没那么容易。
 
首先能够想到的问题就一大堆,如何通过扫描用户的源代码准确retrieve出相关依赖信息(这个就够繁琐复杂的,已经快做了两个月了),即使找到了 各种依赖信息,又如何匹配出所有合适的jar包呢?肯定需要在服务端事先对jar包进行大规模的扫描,找出不同版本的不同依赖等等...............太多太多
 
 
不过经过2个月的思考,感觉越来越有点眉目了,最近准备好好出个文档,然后整理一下项目,多添加点注释,然后就 开源啦!希望感兴趣的朋友一起加入,共同为解放程序员的双手而努力!
 
最后来一点干货吧,也不枉对这个项目不感兴趣的朋友白来一趟。
 
Java在调用Get()方法时给其传递了一串字符串引用,如下:
  
String a = Get(cn.autort.core.Expression.a);

如何正确的分析这串字符串引用是一个关键。autort必须知道这串字符串引用代表的真正意思。可能你会毫不犹豫地说是在cn.autort.core包下的Expression类中的静态变量a。

如果用户编写代码时有良好的编码规范,也许你可以马上对这个字符串进行正确的分解。但是总有一些情况下,这个字符串代表的意思并不是这样。
 
它可以代表连续的变量引用,cn、autort、core、Expression与a全部为变量

它可以代表所有的类引用,就是内部类嵌套内部类的情况

它可以代表很多,但是有些规则还是需要知道的,那就是变量后面不可能直接跟类名和包名,包后不可能直接跟变量名。
 
那么如何在某些情况下正确分解出这个字符串代表的信息。我们还是需要基于已有的包信息和类信息进行分解。autort需要扫描所有的包和类,并进行有组织的存储,以便快速进行判断。在这里可以使用Trie树来解决。
 

如果你不了解什么是Trie树,可以看一看这篇文章。
 
我要做的只是稍加更改一下Trie树,让它适应需求即可。
 
class Node {
    String term;    // the character in the node
    boolean isEnd;  // whether the end of the words
    int count;      // the number of words sharing this character
    LinkedList<Node> childList; // the child list
     
    Map<String,Object> map = null; // store class name
 
    public Node(String term) {
        this.childList = new LinkedList<Node>();
        this.isEnd = false;
        this.term = term;
        this.count = 0;
    }
 
    public Node subNode(String term) {
        if (childList != null) {
            for (Node eachChild : childList) {
                if (eachChild.term == term) {
                    return eachChild;
                }
            }
        }
        return null;
    }
     
    public void put(String key,Object value){
        if(map==null){
            map = new HashMap<String,Object>();
        }
        map.put(key, value);
    }
     
    public Object get(String key){
        return map.get(key);
    }
     
     
}

这是Node结点,每个包名为一个节点。如cn、core等。需要指出的是map中存储的数据。key为类名,如果类中有类的话,以逗号隔开。例如A.B,值就是对应的具体的语法树了。

public class Trie {
    private Node root;
 
    public Trie() {
        root = new Node("root");  // 第一个节点的字符串为空
    }
     
    public Node getRoot(){
        return root;
    }
 
    public Node insert(String[] terms) {
        if (searchNode(terms)!=null)
            return searchNode(terms);
 
        Node current = root;
        for (int i = 0; i < terms.length; i++) {
            Node child = current.subNode(terms[i]);
            if (child != null) {
                current = child;
            } else {
                current.childList.add(new Node(terms[i]));
                current = current.subNode(terms[i]);
            }
            current.count++;
        }
        current.isEnd = true;
        return current;
    }
 
 
    public Node searchNode(String[] terms) {
        Node current = root;
        for (int i = 0; i < terms.length; i++) {
            if (current.subNode(terms[i]) == null){
                return null;
            }else{
                current = current.subNode(terms[i]);
            }
        }
        if(current.isEnd){
            return current;
        }
        return null;
    }
     
    public int[] searchParts(String[] terms) {
        int[] result = new int[]{-1,-1};
        int matchedIndex = -1;
        Node current = root;
        for (int i = 0; i < terms.length; i++) {
            if (current.subNode(terms[i]) == null){
                break;
            }else{
                current = current.subNode(terms[i]);
                if(current.isEnd){
                    matchedIndex = i;
                }
            }
        }
         
        result[0] = matchedIndex;
         
        StringBuffer buffer = new StringBuffer();
        for(int j=matchedIndex+1;j<terms.length;j++){
            buffer.append(terms[j]);
            Object obj = current.get(buffer.toString());
            if(obj!=null){
                result[1] = j;
            }
            if(j!=terms.length-1){
                buffer.append(".");
            }
        }
        return result;
    }
 
}

提供了插入和搜索的方法,并不复杂。不过需要兼容那些没有包路径的类。

提供了个小Demo测试一下。

建立Trie树并存储包和类的相关信息:

Trie trie = new Trie();
Node a1 = trie.insert(new String[]{"cn","autort","core"});
a1.put("Compilation", "ReferenceASTNode");
a1.put("Compilation.A", "ReferenceASTNode");  // 嵌套类Compilation.A
         
Node a2 = trie.insert(new String[]{"cn","autort","core","expression"});
a2.put("Assignment", "ReferenceASTNode");
a2.put("FieldAccess", "ReferenceASTNode");

int[] result1 = trie.searchParts(new String[]{"cn","autort","core","Compilation","A","B","a"});
System.out.println(result1[0]+"/"+result1[1]);
         
 
int[] result2 = trie.searchParts(new String[]{"cn","autort","core","expression","Compilation"});
System.out.println(result2[0]+"/"+result2[1]);
         
int[] result3 = trie.searchParts(new String[]{"ClassA"});
System.out.println(result3[0]+"/"+result3[1]);

运行的结果如下:  

2/4   // cn.autort.core为包名,Compilation.A为类名,剩下的B和a就是类内的变量名了
3/-1  // cn.autort.core.expression为包中,Compilation无论做为包名还是类名都不存在
-1/0  //  无包名,ClassA为类名


以后有时间的话就多码点字,讲一讲autort技术实现上的一些原理和细节。
 






发布了167 篇原创文章 · 获赞 321 · 访问量 58万+

猜你喜欢

转载自blog.csdn.net/mazhimazh/article/details/53541278