在我们使用Springboot时,如果不想用Tomcat,想用Jetty,应该怎么办呢?
其实很简单,我们只需要把Tomcat的依赖排除掉,然后引入Jetty即可.
那Springboot底层究竟是怎样处理的呢?
首先要明确,Springboot的自动配置其实就是帮我们自动配置注入了很多bean, 比如原来的Spring需要整合Tomcat,一定需要配置一些Tomcat的bean, 需要整合Jetty,就一定需要配置一些Jetty相关的bean.
Springboot能够启动Jetty,能够启动tomcat,那它在自己内部工程一定是同时引入这两个依赖的.
那为什么使用者在切换的时候还需要额外引入Jetty的依赖呢?
其实这里利用了依赖的传递性,在Springboot内部工程pom中,其实对于可选择的依赖加上了
,加上该配置后,表示该依赖并不会传递,而tomcat依赖是没有加该配置的,会进行依赖的传递.
如果不理解这部分的maven的知识,可以看一下这个文章Maven常见知识、冲突解决.
所以,使用者默认是不需要额外引入Tomcat依赖的,而进行Jetty的切换时,需要额外引入Jetty的依赖.
那为什么要这么做呢?为什么不也直接让使用者也都引入这些依赖呢?
对于使用者而言,比如说Servlet容器,其实一般最多都只用一个的,大部分都是Tomcat,那我就不需要引入其他的多余的依赖,那样会使项目变得更加庞大, 还会极大增加依赖冲突的可能性.
知道了怎么进行切换,那现在就应该进行自动配置bean, 那怎么知道应该注入哪个bean呢?是Tomcat的,还是Jetty的?
其实这里也非常简单,就是通过判断能不能加载到某一个类进行处理的.
比如说使用Tomcat,那我是一定有这个依赖,能够加载到这个对象的.然后才把这个对象注入到Spring容器中.
也就是说配置这个bean是需要满足一定的条件的.
具体代码如下:
以下代码就表示当加载到对应的Tomcat,或 Jetty对象时, 才将对应的bean注入到Spring容器中.
@BlingConditionOnClass注解是模仿Springboot中的@ConditionOnClass注解自己定义的,他俩起到的效果是一样的.都是加载到某一个类才生效.
@BlingConditionOnClass 这个注解其实底层最关键的其实是运用了Spring的原生注解@Conditional
Conditional注解需要传入一个实现Condition接口的实现类, 实现matches方法.
笔者这里是传入了一个OnClassCondition类.在这个matches方法中就是做了一件事情,
就是判断注解传入的类路径能不能被正常加载.
public class OnClassCondition implements Condition {
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//获取BlingConditionOnClass注解中的所有属性
MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(BlingConditionOnClass.class.getName());
//获取其中的value属性对应的值
List<Object> classPathList = attributes.get("value");
if(classPathList == null || classPathList.isEmpty()){
return false;
}
try {
//加载传入的类路径
Objects.requireNonNull(context.getClassLoader()).loadClass((String) classPathList.get(0));
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
}
总体思路是这样的:
根据以上流程就可以达到引入不同maven依赖而自动注入不同bean的效果.
现在看起来好像是已经能达到我们刚开始想要的效果了, 通过引入不同的maven依赖向Spring容器注入不同的bean,但是还有一个很关键的点.
就是当使用者引入该项目,他怎么能够扫描加载到项目中WebServerAutoConfigration这个自动配置类呢?
我们知道Spring会默认扫描传入启动类所在的包路径.比如说下面这个工程,传入的是TestApplication, 这个类所在的包路径是com.bling.test,那么Spring就会扫描加载这个路径下所有的类.
而我们自己的写的Springboot项目需要加载的自动配置类,在com.bling.springboot路径下.默认当然是不会扫描加载到的.
此时,最常见的有两种方法,可以从使用者的角度去加载的这个自动配置类 :
真实Spring在进行处理的时候会在@SpringBootApplication注解上Import一个类,AutoConfigurationImportSelector, 见名知义,也就是自动配置导入选择器. 这里也模仿Springboot这样处理.
这个类实现了ImportSelector接口,重写了selectImports方法,这个方法要返回什么呢?
其实就是返回需要被Spring扫描管理的类的全类名.返回的类将会被Spring容器加载管理.
那在这个方法中又应该怎样获取到所有自动配置类的全类名呢?
这里Spring利用的是SPI机制,SPI (Service Provider Interface)本身是一个概念,有很多的框架会去实现自己的SPI.比如Spring,它自己实现加载的spring.factories, 就是SPI机制, 当然JDK本身也有实现的一套SPI的机制.
我们这里就不去实现Spring的这一套SPI,比较麻烦,我们这里直接使用JDK的SPI进行演示处理.核心理解为什么要在这里用到SPI就行.
使用JDK SPI机制步骤:
首先明确JDK SPI是针对接口进行处理的,就是当你传入一个接口, 它可以获取这个接口对应的所有配置的实现类.
在resources目录下新建文件夹META-INF,在该文件夹下再新建文件夹services.在services下新建一个文件,文件名就是接口全类名.
在该文件中配置好对应的实现类.
使用JDK中的ServiceLoader类进行加载.
/**
* @ClassName:
* @Description: 该类标识在启动类中,使用Import注解导入,所以Spring会对该类进行加载
* @author:
* @date:
*
*/
public class AutoConfigurationImportSelector implements ImportSelector {
/**
* @param importingClassMetadata
* @return 返回需要加载到Spring容器的全类名
*/
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
//这里暂时不引入Spring中的SPI,比较复杂
//直接使用Java中的SPI
ArrayList<String> result = new ArrayList<>();
ServiceLoader<AutoConfigration> load = ServiceLoader.load(AutoConfigration.class);
for (AutoConfigration autoConfigration : load) {
result.add(autoConfigration.getClass().getName());
}
return result.toArray(new String[0]);
}
}
至此,就完完全全的能够达到我们在最开始提到的想法,想切换不同的Servlet容器,直接切换maven依赖就可以.
想看该项目完整代码的可以到Github上下载:手写自己的springboot
如果有网络不通的小伙伴也可以到CSDN中下载CSDN地址下载.
我们还是将该项目进行打包, 额外新建一个非常简单的maven项目,引入该项目依赖,如下:
可以正常启动tomcat…
然后,排除Tomcat依赖,加入Jetty依赖.
现在就可以启动Jetty.
整个流程下来,其实主要有三点:
今天的分享就到这里了,有问题可以在评论区留言,均会及时回复呀.
我是bling,未来不会太差,只要我们不要太懒就行, 咱们下期见.