当前位置: 首页>前端>正文

springboot中中间件的作用 springboot web中间件

一. 导读

每一个框架都有客户端和服务端,一般客户端只需要引入一个包,然后通过注解自动开启客户端,这是如何做到的?
有的框架,比如 Seata 没有引入 spring ,就无法使用 spring 的注解来定义bean,那么它是如何管控实例的?
框架代码肯定不全是写死的,那运行时加载是如何做到的?

二. Spring boot 如何加载管理实例

Spring boot 主要是通过 ApplicationContext 来作bean的容器,通过注解来识别需要被管理的bean。
Spring Cloud 中大量的用 starter包代替传统的包,主要就是因为使用 starter 可以不用在启动类上加注解,通过约定的默认路径 resources/META-INF 来加载自定义配置类。下面是 sb 启动时的加载:

SpringApplication构造函数 --> setInitializers(getSpringFactoriesInstances(ApplicationContextInitializer.class)  
  -->SpringFactoriesLoader.loadFactoryNames --> loadSpringFactories(ClassLoader) --> classLoader.getResources(resource/META-INF)

使用类加载器来加载默认路径下的文件,加载格式:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=自定义配置类

在自定义类中,可以通过注解,进行类的扫描和一些自定义bean的引入,非常方便。

sb 引入传统的包,需要在启动类上加注解,通过注解来扫描包中需要被spring管理的bean。类似:

@EnableInterceptor
@EnableDatabase
@Import(SupportAutoConfiguration.class)
public @interface EnableSupport {
}

这个注解加载启类上,启动类识别 @import ,引入 SupportAutoConfiguration ,进行类的加载。

不管传统的方式还是starter,都需要一个入口,来引入需要被管理的bean。

三. Seata如何加载和管理实例

首先看一下 Seata common包中 EnhancedServiceLoader 加载器的用法:

EnhancedServiceLoader.load(class,ClassLoader) 
  --> loadFile(Class,String,Classloader,Class[],Object[])
  --> findAllExtensionClass(service, activateName, loader)
  --> initInstance(service, extension, argTypes, args)
  --> Class.newInstance()

主要就是根据 class的全名称 ,查找指定目录下 (META-INF/seata) 是否有这个类全名称命名的文件,按行读取文件中配置的 class ,通过 Class.forName() 加载class,缓存到 providers 中,最后通过 newInstance 实例化返回。
使用 CurrentHashMap 缓存class (不是实例) :

private static Map<Class, List<Class>> providers = new ConcurrentHashMap<Class, List<Class>>();

一个 class 对应的是一个列表,因为一个抽象类可以有多个实现,通过注解 @LoadLevel 来决定如何加载,注解中有name和order参数,name 优先。

最后通过获取到的实例,就可以调用对应的方法。

这里主要用的就是SPI机制来加载文件,通过double-check-lock来防止并发问题。通过SPI,可以不用在启动时就加载文件,而是在扩展方法被调用时,通过运行时参数加载文件。这也是为什么 map 中缓存的是类而不是实例。

四. 总结

想要在运行时根据参数加载类,就一定要有一个入口,可以是约定好的,然后通过运行时加载进行框架的扩展,避免纯写死在代码中。实例的缓存一般就是使用 ConcurrentHashMap 进行缓存,要注意读写并发问题和可能多次实例化问题。


https://www.xamrdz.com/web/25z1942986.html

相关文章: