背景

大一点的公司,可能有一些组,专门做中间件的;假设,某中间件小组,给你提供了一个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

总结

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

曹工说面试:当应用依赖jar包的A版本,中间件jar包依赖B版本,两个版本不兼容,这还怎么玩?的更多相关文章

  1. 曹工说Spring Boot源码(22)-- 你说我Spring Aop依赖AspectJ,我依赖它什么了

    写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...

  2. 曹工说Spring Boot源码(25)-- Spring注解扫描的瑞士军刀,ASM + Java Instrumentation,顺便提提Jar包破解

    写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...

  3. 曹工说Spring Boot源码(29)-- Spring 解决循环依赖为什么使用三级缓存,而不是二级缓存

    写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...

  4. 曹工说Spring Boot源码(27)-- Spring的component-scan,光是include-filter属性的各种配置方式,就够玩半天了.md

    写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...

  5. 曹工说Spring Boot源码系列开讲了(1)-- Bean Definition到底是什么,附spring思维导图分享

    写在前面的话&&About me 网上写spring的文章多如牛毛,为什么还要写呢,因为,很简单,那是人家写的:网上都鼓励你不要造轮子,为什么你还要造呢,因为,那不是你造的. 我不是要 ...

  6. 曹工说Spring Boot源码(26)-- 学习字节码也太难了,实在不能忍受了,写了个小小的字节码执行引擎

    曹工说Spring Boot源码(26)-- 学习字节码也太难了,实在不能忍受了,写了个小小的字节码执行引擎 写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean De ...

  7. 曹工说Spring Boot源码(28)-- Spring的component-scan机制,让你自己来进行简单实现,怎么办

    写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...

  8. 曹工说Spring Boot源码(30)-- ConfigurationClassPostProcessor 实在太硬核了,为了了解它,我可能debug了快一天

    写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...

  9. 曹工说mini-dubbo(1)--为了实践动态代理,我写了个简单的rpc框架

    相关背景及资源: 之前本来一直在写spring源码解析这块,如下,aop部分刚好写完.以前零散看过一些文章,知道rpc调用基本就是使用动态代理,比如rmi,dubbo,feign调用等.自己也就想着试 ...

随机推荐

  1. Spring基本介绍

    一:Spring是什么 Spring是分层的 Java SE/EE应用 full-stack 轻量级开源框架,它以IOC控制反转和AOP面向切面编程为核心,提供了展现层 Spring MVC 和持久层 ...

  2. vue + elementUI开发,使用el-tabs,导致浏览器卡死问题。

    第一次自己建项目,用过el-tabs,当时是正常使用的. 贴下版本信息: "element-ui": "^2.13.0", "js-md5" ...

  3. AttributeError: 'PyQt5.QtCore.pyqtSignal' object has no attribute 'connect'

    pyqt5信号要定义为类属性 #!/usr/bin/python3 # -*- coding: utf-8 -*- from PyQt5.Qt import * import sys class Wi ...

  4. jQuery - Ajax ajax方法详解

    $.ajax()方法详解 jquery中的ajax方法参数总是记不住,这里记录一下. 1.url: 要求为String类型的参数,(默认为当前页地址)发送请求的地址. 2.type: 要求为Strin ...

  5. [转] Git caret(^) and tilde(~)

    点击阅读原文 I spent a little bit of time playing with Git today, specifically the way that the ^ (caret) ...

  6. Bumped!【迪杰斯特拉消边、堆优化】

    Bumped! 题目链接(点击) Peter returned from the recently held ACM ICPC World Finals only to find that his r ...

  7. Windows 程序设计(4) MFC-01前置知识

    1. Windows编程简介 1.0 开发环境 操作系统 Win10 IDE: VS2017 1.1 Windows程序简介 Windows程序呢也主要分那么几种,例如:exe的可执行程序,dll的动 ...

  8. Java中在数字前自动补零方法

    /** * 数字前面自动补零 * @param number 数字 * @return */ public static String geFourNumber(int number){ Number ...

  9. 容器技术之Docker Machine

    前文我们聊了下docker容器的资源限制,回顾请参考https://www.cnblogs.com/qiuhom-1874/p/13138725.html:今天我们来聊一聊docker machine ...

  10. 【K8s学习笔记】K8s是如何部署应用的?

    本文内容 本文致力于介绍K8s一些基础概念与串联部署应用的主体流程,使用Minikube实操 基础架构概念回顾 温故而知新,上一节[K8S学习笔记]初识K8S 及架构组件 我们学习了K8s的发展历史. ...