zoukankan      html  css  js  c++  java
  • TDDL与Spring Boot集成Version报错——跟踪与解决

    先说背景:公司采用diamond+tddl,这套技术来做web管理。本人处于好奇率先体验了下spring-boot,于是就有了spring-boot+tddl的组合。但是jar包上线后,屡屡发现一条error日志不痛不痒的出现在日志文件中,处于程序员的本能,怎么能允许error日志出现在我的系统中呢!

    于是,展开了一段tddl与spring-boot的爱恨之旅...

    挣扎期

    首先看错误提示:

    2017-09-27 11:15:58,428 [main] ERROR com.taobao.tddl.common.utils.version.Version:74 -  [TDDL] check guava version is recommend <= 15.0(the minimum version), please upgrade guava jar version, tddl version: recommend
    java.lang.IllegalStateException: check guava version is recommend <= 15.0(the minimum version), please upgrade guava jar version
    	at com.taobao.tddl.common.utils.version.Version.validVersion(Version.java:122)
    	at com.taobao.tddl.common.utils.version.Version.<clinit>(Version.java:36)
    	at com.taobao.tddl.monitor.logger.LoggerInit.initTddlLog(LoggerInit.java:42)
    	at com.taobao.tddl.monitor.logger.LoggerInit.<clinit>(LoggerInit.java:32)
    	at com.taobao.tddl.group.jdbc.TGroupDataSource.doInit(TGroupDataSource.java:117)
    	at com.taobao.tddl.common.model.lifecycle.AbstractLifecycle.init(AbstractLifecycle.java:21)
    	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    	at java.lang.reflect.Method.invoke(Method.java:498)
    

    太明显了!jar包版本不对嘛!——so easy!

    于是取mvn里面,调查版本,发现没啥问题呀!guava(21.0);druid(1.1.2)。尝试了maven的diagram图和mvn dependency:tree,各种分析都没啥问题!

    <!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
    <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>20.0</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.2</version>
    </dependency>
    

    于是,我迷茫了!!!!!!!

    突破期

    作为一个合格的程序员,不放弃,不抛弃是我们的原则!我想起来实习的时候,公司一位技术大牛教过我,怎么在不下载完整源码的情况下,直接调试第三方jar。

    首先需要一点背景知识,就是java的类加载机制!

    类加载

    最基本的知识就是类加载的模型和父类委托机制:

    Bootstrap ClassLoader:加载JAVA_HOMElib下的jar
            |
    Extension ClassLoader:加载JAVA_HOMEextlib下的jar
            |
    Application ClassLoader:加载用户路径上指定的类
    

    父类委托机制,其实就是每次加载的时候,会优先把加载的任务交给上一层,上一层再交给上一层。如果上一层找到,就使用这个jar;如果找不到再由下一层寻找。也就是说,如果同样的两个jar,放在JAVA_HOMElib里面的会优先使用。

    有些人可能会有疑问,这根本地调试有什么关系?别着急,在开发环境或者应用部署环境中,应用类的加载也是有一定的顺序的。比如会先读取classes下面的文件,找不到的话,在读取lib中的文件。

    了解这个classes和lib的优先级,就足够我们调试用了!

    spring-boot jar目录结构

    另外一个背景知识,就是需要了解spring-boot jar的目录结构。

    这样我们编译出的class会进入到classes目录下,而其他的第三方的jar会进入lib目录。

    开始源码跟踪

    根据error日志的跟踪,可以发现最终出错的位置是在Version的validVersion方法。所以我们直接看一下源码,并且根据源码直接拷贝出来放入我们自己的工程:

    public final class Version {
        ...
        // 这里是检查常用jar包版本的地方,可以看到tddl强烈要求druid、daimond、guava的版本
        static {
            ...
            // 检查下经常性冲突的两个包
            Version.checkDuplicate("com/alibaba/druid/pool/DruidDataSource.class", false);
            validVersion("druid", "com/alibaba/druid/pool/DruidDataSource.class", "1.0.6");
            Version.checkDuplicate("com/taobao/diamond/client/Diamond.class", false);
            validVersion("diamond", "com/taobao/diamond/client/Diamond.class", "3.6.8");
            Version.checkDuplicate("com/google/common/collect/MapMaker.class", false);
            validVersion("guava", "com/google/common/collect/MapMaker.class", "15.0");
        }
    
        // 这里是检查版本的地方
        public static boolean validVersion(String name, String path, String minVersion) {
            try {
                if (minVersion == null) {
                    return true;
                }
    
                Long minv = convertVersion(minVersion);
                Enumeration<URL> urls = Version.class.getClassLoader().getResources(path);
                while (urls.hasMoreElements()) {
                    URL url = urls.nextElement();
                    if (url != null) {
                        String file = url.getFile();
                        if (file != null && file.length() > 0) {
                            // 主要的点是这里!根据file的名字获取版本
                            String version = getVerionByPath(file);
                            // 为了方便运行期调试,直接输出关键的信息 System.out.println("------------------------------------");
                            System.out.println("完整的路径为:"+file);
                            System.out.println("检测到version为:"+version);
                            System.out.println("------------------------------------");
                            if (checkVersionNecessary(version)) {
                                Long ver = convertVersion(version);
                                if (ver < minv) {
                                    throw new IllegalStateException("check " + name + " version is " + version + " <= "
                                            + minVersion + "(the minimum version), please upgrade "
                                            + name + " jar version");
                                }
                            }
                        }
                    }
                }
            } catch (Throwable e) { // 防御性容错
                logger.error(e.getMessage(), e);
            }
    
            return true;
        }
        
    }
    

    目录结构如下:

    运行后就发现,得到下面的输出:

    ------------------------------------
    完整的路径为:file:/Users/xingoo/IdeaProjects/test-recommend/target/recommend.jar!/BOOT-INF/lib/guava-20.0.jar!/com/google/common/collect/MapMaker.class
    检测到version为:recommend
    ------------------------------------
    

    呵呵哒!原来error日志已经提示了版本为recommend,如果有经验的话,应该直接就能发现版本是错的。不过对于新手来说,真是看不出来啊。

    那么接下来就可以分析一下为什么版本会出错了!

    talk is cheap, show me your code!

    public static String getVerionByPath(String file) {
        if (file != null && file.length() > 0 && StringUtils.contains(file, ".jar")) {
            int index = StringUtils.indexOf(file, ".jar");
            file = file.substring(0, index);
            int i = file.lastIndexOf('/');
            if (i >= 0) {
                file = file.substring(i + 1);
            }
            i = file.indexOf("-");
            if (i >= 0) {
                file = file.substring(i + 1);
            }
            while (file.length() > 0 && !Character.isDigit(file.charAt(0))) {
                i = file.indexOf("-");
                if (i >= 0) {
                    file = file.substring(i + 1);
                } else {
                    break;
                }
            }
            return file;
        } else {
            return null;
        }
    }
    

    这里用的StringUtils.indexOf获得第一个jar前面的部分,由于我的spring-boot打出来的jar包,是嵌套jar的。所以获取第一个就出错了!

    修改方法, 把indexOf替换成lastIndexOf就行了:

    ...
    int index = StringUtils.lastIndexOf(file, ".jar");
    ...
    

    最终解决办法

    想要一条error日志也没有,可以参考下面的方法:

    • 把下面的代码考入工程,记住不要修改包名,直接放在com.tddl.common.utils.version下!
    • 并且关闭tddl的各种版本检查,

    不过因为两个error报错,就把整个tddl的版本检测给摘掉好像太暴力。所以最好还是等官网来修复吧!:

    public final class Version {
    
        private static final Logger logger = LoggerFactory.getLogger(Version.class);
        private static final Package myPackage = VersionAnnotation.class.getPackage();
        private static final VersionAnnotation va = myPackage.getAnnotation(VersionAnnotation.class);
        private static final String VERSION = getVersion(Version.class, "5.1.7");
    
        static {
            // 检查是否存在重复的jar包
            // Version为tddl5.x之后的版本检测类
            // 如果Version检测没有发现重复,再和tddl3.x系列进行检查,ThreadLocalMap兼容了tddl3.x的类路径
    //        if (!Version.checkDuplicate(Version.class)) {
    //            Version.checkDuplicate(ThreadLocalMap.class);
    //        }
    
            // 检查下经常性冲突的两个包
    //        Version.checkDuplicate("com/alibaba/druid/pool/DruidDataSource.class", false);
    //        validVersion("druid", "com/alibaba/druid/pool/DruidDataSource.class", "1.0.6");
    //        Version.checkDuplicate("com/taobao/diamond/client/Diamond.class", false);
    //        validVersion("diamond", "com/taobao/diamond/client/Diamond.class", "3.6.8");
    //        Version.checkDuplicate("com/google/common/collect/MapMaker.class", false);
    //        validVersion("guava", "com/google/common/collect/MapMaker.class", "15.0");
        }
    
        private Version() {
        }
        ...
    }
    
  • 相关阅读:
    C++类构造函数初始化列表(转)
    Linux可重入函数和线程安全的区别与联系(转)
    Splinter自动登录
    VBA添加下拉菜单
    批处理自动备份并生成日志文件
    VBA 格式化excel数据表 (数据分列)
    Python 调用outlook发送邮件(转 )
    Node.js 文件操作
    Node.js express模块 http服务
    将ejs模板文件的后缀换成html
  • 原文地址:https://www.cnblogs.com/xing901022/p/7601344.html
Copyright © 2011-2022 走看看