zoukankan      html  css  js  c++  java
  • 曹工说面试:当应用依赖jar包的A版本,中间件jar包依赖B版本,两个版本不兼容,这还怎么玩?

    背景

    大一点的公司,可能有一些组,专门做中间件的;假设,某中间件小组,给你提供了一个jar包,你需要集成到你的应用里。假设,它依赖了一个日期类,版本是v1;我们应用也依赖了同名的一个日期类,版本是v2.

    两个版本的日期类,方法逻辑的实现,有一些差异。

    举个例子,中间件提供的jar包中,依赖如下工具包:

    <dependency>
        <groupId>com.example</groupId>
        <artifactId>common-v1</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </dependency>
    
    

    该版本中,包含了com.example.date.util.CommonDateUtil这个类。

    package com.example.date.util;
    
    import lombok.extern.slf4j.Slf4j;
    
    @Slf4j
    public class CommonDateUtil {
    
        public static String format(String date) {
            // 1
            String s = date + "- v1";
            log.info("v1 result:{}", s);
            return s;
        }
    }
    

    应用中,依赖如下jar包:

    <dependency>
       <groupId>com.example</groupId>
       <artifactId>common-v2</artifactId>
       <version>0.0.1-SNAPSHOT</version>
    </dependency>
    

    该jar包中,包含同名的class,但里面的方法实现不一样:

    @Slf4j
    public class CommonDateUtil {
    
        public static String format(String date) {
            String s = date + "- v2";
            log.info("v2 result:{}", s);
            return s;
        }
    }
    

    ok,那假设我们是一个spring boot应用,当中间件小组的哥们找到你,让你集成,你可能就愉快地弄进去了;但是,有个问题时,你的jar包、和中间件小哥的jar包,都是放在fatjar的lib目录的(我这里解压了,方便查看):

    请问,最终加载的CommonDateUtil类,到底是common-v1的,还是commonv2中的呢?因为spring boot加载BOOT-INF/lib时,肯定都是同一个类加载器,同一个类加载器,对于一个包名和类名都相同的类,只会加载一次;那么,加载了v1,就不可能再加载V2;反之亦然。

    那这就出问题了。我们应用要用V2;中间件要用V1,水火不容啊,这可怎么办?

    分析

    首先,我们要重写spring boot的启动类,这是毋庸置疑的,启动类是哪个呢?

    为什么要重写这个?因为,我们问题分析里说了,当打成fat jar运行时,其结构如下:

    [root@mini2 temp]# tree 
    .
    ├── BOOT-INF
    │   ├── classes
    │   │   ├── application.properties
    │   │   ├── application.yml
    │   │   └── com
    │   │       └── example
    │   │           └── demo
    │   │               ├── CustomMiddleWareClassloader.class
    │   │               └── OrderServiceApplication.class
    │   └── lib
    │       ├── classmate-1.4.0.jar
    │       ├── common-v1-0.0.1-SNAPSHOT.jar
    │       ├── common-v2-0.0.1-SNAPSHOT.jar
    │       ├── hibernate-validator-6.0.17.Final.jar
    │       ├── jackson-annotations-2.9.0.jar
    │       ├── jackson-core-2.9.9.jar
    │       ├── jackson-databind-2.9.9.jar
    │       ├── jackson-datatype-jdk8-2.9.9.jar
    │       ├── jackson-datatype-jsr310-2.9.9.jar
    │       ├── jackson-module-parameter-names-2.9.9.jar
    │       ├── javax.annotation-api-1.3.2.jar
    │       ├── jboss-logging-3.3.2.Final.jar
    │       ├── jul-to-slf4j-1.7.26.jar
    │       ├── log4j-api-2.11.2.jar
    │       ├── log4j-to-slf4j-2.11.2.jar
    │       ├── logback-classic-1.2.3.jar
    │       ├── logback-core-1.2.3.jar
    │       ├── lombok-1.18.10.jar
    │       ├── middle-ware-0.0.1-SNAPSHOT.jar
    │       ├── middle-ware-api-0.0.1-SNAPSHOT.jar
    │       ├── slf4j-api-1.7.26.jar
    │       ├── snakeyaml-1.23.jar
    │       ├── spring-aop-5.1.9.RELEASE.jar
    │       ├── spring-beans-5.1.9.RELEASE.jar
    │       ├── spring-boot-2.1.7.RELEASE.jar
    │       ├── spring-boot-autoconfigure-2.1.7.RELEASE.jar
    │       ├── spring-boot-loader-2.1.7.RELEASE.jar
    │       ├── spring-boot-starter-2.1.7.RELEASE.jar
    │       ├── spring-boot-starter-json-2.1.7.RELEASE.jar
    │       ├── spring-boot-starter-logging-2.1.7.RELEASE.jar
    │       ├── spring-boot-starter-tomcat-2.1.7.RELEASE.jar
    │       ├── spring-boot-starter-web-2.1.7.RELEASE.jar
    │       ├── spring-context-5.1.9.RELEASE.jar
    │       ├── spring-core-5.1.9.RELEASE.jar
    │       ├── spring-expression-5.1.9.RELEASE.jar
    │       ├── spring-jcl-5.1.9.RELEASE.jar
    │       ├── spring-web-5.1.9.RELEASE.jar
    │       ├── spring-webmvc-5.1.9.RELEASE.jar
    │       ├── tomcat-embed-core-9.0.22.jar
    │       ├── tomcat-embed-el-9.0.22.jar
    │       ├── tomcat-embed-websocket-9.0.22.jar
    │       └── validation-api-2.0.1.Final.jar
    ├── META-INF
    │   ├── MANIFEST.MF
    │   └── maven
    │       └── com.example
    │           └── web-application
    │               ├── pom.properties
    │               └── pom.xml
    └── org
        └── springframework
            └── boot
                └── loader
                    ├── archive
                    │   ├── Archive.class
                    │   ├── Archive$Entry.class
                    │   ├── Archive$EntryFilter.class
                    │   ├── ExplodedArchive$1.class
                    │   ├── ExplodedArchive.class
                    │   ├── ExplodedArchive$FileEntry.class
                    │   ├── ExplodedArchive$FileEntryIterator.class
                    │   ├── ExplodedArchive$FileEntryIterator$EntryComparator.class
                    │   ├── JarFileArchive.class
                    │   ├── JarFileArchive$EntryIterator.class
                    │   └── JarFileArchive$JarFileEntry.class
                    ├── data
                    │   ├── RandomAccessData.class
                    │   ├── RandomAccessDataFile$1.class
                    │   ├── RandomAccessDataFile.class
                    │   ├── RandomAccessDataFile$DataInputStream.class
                    │   └── RandomAccessDataFile$FileAccess.class
                    ├── ExecutableArchiveLauncher.class
                    ├── jar
                    │   ├── AsciiBytes.class
                    │   ├── Bytes.class
                    │   ├── CentralDirectoryEndRecord.class
                    │   ├── CentralDirectoryFileHeader.class
                    │   ├── CentralDirectoryParser.class
                    │   ├── CentralDirectoryVisitor.class
                    │   ├── FileHeader.class
                    │   ├── Handler.class
                    │   ├── JarEntry.class
                    │   ├── JarEntryFilter.class
                    │   ├── JarFile$1.class
                    │   ├── JarFile$2.class
                    │   ├── JarFile.class
                    │   ├── JarFileEntries$1.class
                    │   ├── JarFileEntries.class
                    │   ├── JarFileEntries$EntryIterator.class
                    │   ├── JarFile$JarFileType.class
                    │   ├── JarURLConnection$1.class
                    │   ├── JarURLConnection.class
                    │   ├── JarURLConnection$JarEntryName.class
                    │   ├── StringSequence.class
                    │   └── ZipInflaterInputStream.class
                    ├── JarLauncher.class
                    ├── LaunchedURLClassLoader.class
                    ├── LaunchedURLClassLoader$UseFastConnectionExceptionsEnumeration.class
                    ├── Launcher.class
                    ├── MainMethodRunner.class
                    ├── PropertiesLauncher$1.class
                    ├── PropertiesLauncher$ArchiveEntryFilter.class
                    ├── PropertiesLauncher.class
                    ├── PropertiesLauncher$PrefixMatchingArchiveFilter.class
                    ├── util
                    │   └── SystemPropertyUtils.class
                    └── WarLauncher.class
    

    BOOT-INF/lib下,是由同一个类加载器去加载的,而我们的V1和V2的jar包,全部混在这个目录下。

    我们要想同时加载V1和V2的jar包,必须用两个类加载器来做隔离。

    即,应用类加载器,不能加载V1;而中间件类加载器,只管加载V2,其他一概不能加载。

    spring boot 的启动类

    前面我们提到,启动类是org.springframework.boot.loader.JarLauncher,这个是在BOOT-INF/MANIFEST中指定了的。

    这个类在哪里呢,一般在如下这个依赖中:

    <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-loader</artifactId>
    </dependency>
    

    该依赖,会在打包阶段,由maven插件,打到我们的fat jar中:

    	<build>
    		<plugins>
    			<plugin>
    				<groupId>org.springframework.boot</groupId>
    				<artifactId>spring-boot-maven-plugin</artifactId>
    			</plugin>
    		</plugins>
    	</build>
    
    

    这个jar包,打到哪里去了呢?实际是解压后,放到fat jar的如下路径了,可以再去上面看看那个fat jar结构:

    上面那个启动类,就是在这个里面。

    启动类简单解析

    先来看看uml:

    先看看JarLauncher:

    public class JarLauncher extends ExecutableArchiveLauncher {
    
       static final String BOOT_INF_CLASSES = "BOOT-INF/classes/";
    
       static final String BOOT_INF_LIB = "BOOT-INF/lib/";
    
       public JarLauncher() {
       }
    
       protected JarLauncher(Archive archive) {
          super(archive);
       }
    
       @Override
       protected boolean isNestedArchive(Archive.Entry entry) {
          if (entry.isDirectory()) {
             return entry.getName().equals(BOOT_INF_CLASSES);
          }
          return entry.getName().startsWith(BOOT_INF_LIB);
       }
    
       public static void main(String[] args) throws Exception {
          // 1
          new JarLauncher().launch(args);
       }
    
    }
    

    这里1处,new了自身,然后调用launch。

    public abstract class Launcher {
    
       /**
        * Launch the application. This method is the initial entry point that should be
        * called by a subclass {@code public static void main(String[] args)} method.
        * @param args the incoming arguments
        * @throws Exception if the application fails to launch
        */
       protected void launch(String[] args) throws Exception {
          JarFile.registerUrlProtocolHandler();
          // 1
          ClassLoader classLoader = createClassLoader(getClassPathArchives());
          // 2
          launch(args, getMainClass(), classLoader);
       }
    

    1处这里,就是创建类加载器了,期间,先调用了getClassPathArchives。

    我们看看:

    org.springframework.boot.loader.Launcher#getClassPathArchives
    protected abstract List<Archive> getClassPathArchives() throws Exception;
    

    这是个抽象方法,此处使用了模板方法设计模式,在如下类中实现了:

    org.springframework.boot.loader.ExecutableArchiveLauncher#getClassPathArchives
        
    @Override
    protected List<Archive> getClassPathArchives() throws Exception {
        // 1
       List<Archive> archives = new ArrayList<>(this.archive.getNestedArchives(this::isNestedArchive));
        
       postProcessClassPathArchives(archives);
       return archives;
    }
    

    此处的1处,不用深究,就是获取类加载器的classpath集合。我们这里打个断点,直接看下:

    这里面细节就先不看了,主要就是拿到BOOT-INF/lib下的每个jar包。

    然后我们继续之前的:

       protected void launch(String[] args) throws Exception {
          JarFile.registerUrlProtocolHandler();
          // 1
          ClassLoader classLoader = createClassLoader(getClassPathArchives());
          // 2
          launch(args, getMainClass(), classLoader);
       }
    

    现在getClassPathArchives已经ok了,接着就调用createClassLoader来创建类加载器了。

    	protected ClassLoader createClassLoader(List<Archive> archives) throws Exception {
    		List<URL> urls = new ArrayList<>(archives.size());
    		for (Archive archive : archives) {
    			urls.add(archive.getUrl());
    		}
            // 1
    		return createClassLoader(urls.toArray(new URL[0]));
    	}
    

    1处,继续调用内层函数,传入了url数组。

    protected ClassLoader createClassLoader(URL[] urls) throws Exception {
       return new LaunchedURLClassLoader(urls, getClass().getClassLoader());
    }
    

    这里new了一个LaunchedURLClassLoader,参数就是url数组。我们看看这个类:

    public class LaunchedURLClassLoader extends URLClassLoader {
    
       static {
          ClassLoader.registerAsParallelCapable();
       }
    
       /**
        * Create a new {@link LaunchedURLClassLoader} instance.
        * @param urls the URLs from which to load classes and resources
        * @param parent the parent class loader for delegation
        */
       public LaunchedURLClassLoader(URL[] urls, ClassLoader parent) {
          super(urls, parent);
       }
    

    这个类,继承了URLClassLoader,所以,大家如果对类加载器有一定了解,就知道,URLClassLoader就是接收一堆的url,然后loadClass的时候,遵从双亲委派,双亲加载不了,就交给它,它就去url数组里,去加载class。

    思路分析

    我的打算是,修改fat jar中的启动类,为我们自定义的启动类。

    Manifest-Version: 1.0
    Implementation-Title: web-application
    Implementation-Version: 0.0.1-SNAPSHOT
    Start-Class: com.example.demo.OrderServiceApplication
    Spring-Boot-Classes: BOOT-INF/classes/
    Spring-Boot-Lib: BOOT-INF/lib/
    Build-Jdk-Spec: 1.8
    Spring-Boot-Version: 2.1.7.RELEASE
    Created-By: Maven Archiver 3.4.0
    // 1
    Main-Class: com.example.demo.CustomJarLauncher
    

    1处,指定我们自定义的class,这个class,我们会在打好fat jar后,手动拷贝进去。

    然后,我们自定义启动类里面要干啥呢?

    public class CustomJarLauncher extends JarLauncher {
    
    
    
        public static void main(String[] args) throws Exception {
            new CustomJarLauncher().launch(args);
        }
    
    
        @Override
        protected void launch(String[] args) throws Exception {
            JarFile.registerUrlProtocolHandler();
            // 1
            List<Archive> classPathArchives = getClassPathArchives();
            /**
             * 2
             */
            List<URL> allURLs = classPathArchives.stream().map(entries -> {
                try {
                    return entries.getUrl();
                } catch (MalformedURLException e) {
                    return null;
                }
            }).filter(Objects::nonNull).collect(Collectors.toList());
    
    		// 3
            List<URL> middleWareClassPathArchives = new ArrayList<>();
            for (URL url : allURLs) {
                String urlPath = url.getPath();
                if (urlPath == null) {
                    continue;
                }
                boolean isMiddleWareJar = urlPath.contains("common-v1")
                        || urlPath.contains("middle-ware");
                if (isMiddleWareJar) {
                    if (urlPath.contains("middle-ware-api")) {
                        continue;
                    }
                    middleWareClassPathArchives.add(url);
                }
            }
    
    
            /**
             * 4 从全部的应用lib目录,移除中间件需要的jar包,但是,中间件的api不能移除
             */
            allURLs.removeAll(middleWareClassPathArchives);
    
    		// 5
            CustomLaunchedURLClassLoader loader =
                    new CustomLaunchedURLClassLoader(allURLs.toArray(new URL[0]),
                    getClass().getClassLoader());
            loader.setMiddleWareClassPathArchives(middleWareClassPathArchives);
    
            launch(args, getMainClass(), loader);
        }
    
    }
    
    • 1处,获取fat jar的lib目录下的全部包
    • 2处,将1处得到的集合,转为url集合
    • 3处,筛选出中间件的包,复制到单独的集合中,我这边有2个,直接写死了(毕竟是demo)
    • 4处,将原集合中,移除中间件的jar包
    • 5处,创建一个自定义的classloader,主要是方便我们存放中间件相关jar包集合

    5处自定义的classloader,这里可以看下:

    public class CustomLaunchedURLClassLoader extends LaunchedURLClassLoader {
    	// 中间件jar包
    	List<URL> middleWareClassPathArchives;
    
    	/**
    	 * Create a new {@link LaunchedURLClassLoader} instance.
    	 *
    	 * @param urls   the URLs from which to load classes and resources
    	 * @param parent the parent class loader for delegation
    	 */
    	public CustomLaunchedURLClassLoader(URL[] urls, ClassLoader parent) {
    		super(urls, parent);
    	}
    
    
    	public List<URL> getMiddleWareClassPathArchives() {
    		return middleWareClassPathArchives;
    	}
    
    	public void setMiddleWareClassPathArchives(List<URL> middleWareClassPathArchives) {
    		this.middleWareClassPathArchives = middleWareClassPathArchives;
    	}
    }
    

    最终,在我们的业务代码,要怎么去写呢?

    我们现在自定义了一个类加载器,那么,后续业务代码都会由这个类加载器去加载。

    我们再想想标题说的问题,我们是需要:加载中间件代码时,不能用这个类加载器去加载,因为这个类加载器中,已经排除了中间件相关jar包,是加载不到的。

    此时,我们需要自定义一个classloader,去如下类中的middleWareClassPathArchives这个地方加载:

    public class CustomLaunchedURLClassLoader extends LaunchedURLClassLoader {
    	// 中间件jar包
    	List<URL> middleWareClassPathArchives;
        ...
    }
    

    只有当它加载不到之后,才丢给应用类加载器去加载。

    代码如下:

    @SpringBootApplication
    @RestController
    @Slf4j
    public class OrderServiceApplication {
    	/**
    	 * 中间件使用的classloader
    	 */
    	static ClassLoader delegatingClassloader;
    
    	public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
            // 1
    		ClassLoader loader = Thread.currentThread().getContextClassLoader();
    		Method method = loader.getClass().getMethod("getMiddleWareClassPathArchives");
    		List<URL> middleWareUrls = (List<URL>) method.invoke(loader);
    		// 2
    		delegatingClassloader = new CustomMiddleWareClassloader(middleWareUrls.toArray(new URL[0]), loader);
    		// 3
    		SpringApplication.run(OrderServiceApplication.class, args);
    	}
    
    • 1,这里,我们要通过当前线程,拿到我们的类加载器,此时拿到的,肯定就是我们的自定义类加载器;然后通过反射方法,拿到中间件url集合,其实这里自己去拼这个url也可以,我们这里为了省事,所以就这么写;

    • 2处,创建一个类加载器,主要就是给中间件代码使用,进行类加载器隔离。

      注意,这里,我们把当前应用的类加载器,传给了这个中间件类加载器。

    @Data
    @Slf4j
    public class CustomMiddleWareClassloader extends URLClassLoader {
        ClassLoader classLoader;
    
        public CustomMiddleWareClassloader(URL[] urls, ClassLoader parent) {
            super(urls);
            classLoader = parent;
        }
    
        @Override
        public Class<?> loadClass(String name) throws ClassNotFoundException {
            /**
             * 先自己来加载中间件相关jar包,这里调用findClass,就会去中间件那几个jar包加载class
             */
            try {
                Class<?> clazz = findClass(name);
                if (clazz != null) {
                    return clazz;
                }
                throw new ClassNotFoundException(name);
            } catch (Exception e) {
                /**
                 * 在中间件自己的jar包里找不到,就交给自己的parent,此处即应用类加载器
                 */
                return classLoader.loadClass(name);
            }
        }
    
    
    }
    

    代码结构

    中间件整体模块概览

    在继续之前,有必要说下代码结构。

    中间件总共三个jar包:

    common-v1,middle-ware,middle-ware-api。

    其中,middle-ware的pom如下:

    
    

    中间件api模块

    该模块无任何依赖,就是个接口

    public interface IGisUtilInterface {
    
        String getFormattedDate(String date);
    
    }
    

    该模块是很有必要的,该api模块必须由应用的类加载器加载,没错,是应用类加载器。

    类似于servlet-api吧。

    大家可以暂时这么记着,至于原因,那就有点长了。

    可以参考:

    不吹不黑,关于 Java 类加载器的这一点,市面上没有任何一本图书讲到

    中间件实现模块

    实现模块的pom:

    <groupId>com.example</groupId>
    <artifactId>middle-ware</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>middle-ware</name>
        
    <dependencies>
       <dependency>
          <groupId>com.example</groupId>
          <artifactId>middle-ware-api</artifactId>
          <version>0.0.1-SNAPSHOT</version>
       </dependency>
       <dependency>
          <groupId>com.example</groupId>
          <artifactId>common-v1</artifactId>
          <version>0.0.1-SNAPSHOT</version>
       </dependency>
    
    </dependencies>
    

    里面只有一个类,就是实现api模块的接口。

    @Slf4j
    public class GisUtilImpl implements IGisUtilInterface{
    
        @Override
        public  String getFormattedDate(String date) {
            String v1 = CommonDateUtil.format(date);
            log.info("invoke common v1,get result:{}", v1);
    
            return v1;
        }
    
    }
    

    spring boot 的自定义loader模块

    这部分和业务关系不大,主要就是自定义我们前面的那个fat jar启动类。

    <groupId>com.example</groupId>
    <artifactId>custom-jar-launch</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>custom-jar-launch</name>
    <description>Demo project for Spring Boot</description>
    
    <properties>
       <java.version>1.8</java.version>
       <spring-cloud.version>Greenwich.SR3</spring-cloud.version>
    </properties>
    
    <dependencies>
    
       <dependency>
          <groupId>org.projectlombok</groupId>
          <artifactId>lombok</artifactId>
          <version>1.18.10</version>
       </dependency>
       <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-loader</artifactId>
       </dependency>
    
    </dependencies>
    

    这里有个特别的依赖:

    <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-loader</artifactId>
    </dependency>
    

    该模块,主要包含:

    com.example.demo.CustomJarLauncher

    com.example.demo.CustomLaunchedURLClassLoader

    应用程序

    <groupId>com.example</groupId>
    <artifactId>web-application</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>web-application</name>
    <description>Demo project for Spring Boot</description>
    
    
    <dependencies>
       <dependency>
          <groupId>com.example</groupId>
          <artifactId>common-v2</artifactId>
          <version>0.0.1-SNAPSHOT</version>
       </dependency>
    
       <dependency>
          <groupId>com.example</groupId>
          <artifactId>middle-ware-api</artifactId>
          <version>0.0.1-SNAPSHOT</version>
       </dependency>
       <dependency>
          <groupId>com.example</groupId>
          <artifactId>middle-ware</artifactId>
          <version>0.0.1-SNAPSHOT</version>
       </dependency>
    

    模拟jar包冲突场景,此时,我们已经同时依赖了v1和v2了。

    其测试代码如下:

    public class OrderServiceApplication {
    	/**
    	 * 中间件使用的classloader
    	 */
    	static ClassLoader delegatingClassloader;
        
    @RequestMapping("/")
    public  void test() throws ClassNotFoundException, IllegalAccessException {
       // 1
       Class<?> middleWareImplClass = delegatingClassloader.loadClass("com.example.demo.GisUtilImpl");
        // 2
       IGisUtilInterface iGisUtilInterface = (IGisUtilInterface) middleWareImplClass.newInstance();
        // 3
       String middleWareResult = iGisUtilInterface.getFormattedDate("version:");
    
       log.info("middle ware result:{}",middleWareResult);
    	
        // 4
       String result = CommonDateUtil.format("version:");
       log.info("application result:{}",result);
    
    }
    
    • 1处,类似于servlet,也是把servlet实现类写死在web.xml的,我们这里也一样,把中间件的实现类写死了。

      可能有更好的方式,暂时先这样。

    • 2处,将实现类(中间件类加载器加载),转换为接口类(应用类加载器加载)。之所以要定义接口,这里很关键。

      可以再仔细看看:

      不吹不黑,关于 Java 类加载器的这一点,市面上没有任何一本图书讲到

    • 3处,调用中间件代码

    • 4处,调用应用代码

    效果展示

    2020-05-22 06:37:13.481  INFO 6676 --- [nio-8082-exec-1] com.example.demo.GisUtilImpl             : invoke common v1,get result:version:- v1
    2020-05-22 06:37:13.481  INFO 6676 --- [nio-8082-exec-1] c.example.demo.OrderServiceApplication   : middle ware result:version:- v1
    2020-05-22 06:37:13.482  INFO 6676 --- [nio-8082-exec-1] com.example.date.util.CommonDateUtil     : v2 result:version:- v2
    2020-05-22 06:37:13.482  INFO 6676 --- [nio-8082-exec-1] c.example.demo.OrderServiceApplication   : application result:version:- v2
    

    可以发现,中间件那里,是v1;而调用应用的方法,则是v2。

    说明我们成功了。

    我这里用arthas分析了下这个类:

    这个类,还在下面出现:

    这个是中间件加载的。

    所以,大家平安无事地继续生活在了一起。

    源码

    https://gitee.com/ckl111/all-simple-demo-in-work-1/tree/master/jar-conflict

    该源码怎么使用?

    先正常打包应用为fat jar,然后将custom-jar-launch中的class,拷进fat jar,然后修改META-INF/MANIFEST文件的启动类。

    然后调用接口:

     curl  localhost:8082
    

    总结

    希望对大家有所启发,谢谢。

  • 相关阅读:
    依赖注入模式与反模式
    WPF异常——某个ItemsControl与它的项源不一致
    C# 3进化的数据访问之智能的编译器
    C# 2的重大改进之可空类型
    C# 1之外:构建于坚实基础上的新特性
    C# 1的核心基础之二——类型系统
    C# 1的核心基础之一——委托
    C#进化史
    单一职责原则
    HBase简介
  • 原文地址:https://www.cnblogs.com/grey-wolf/p/13253014.html
Copyright © 2011-2022 走看看