cover_image

聊聊我们身边的spring扩展点

加里克 缦图coder
2024年01月31日 06:12

图片

点击关注上方蓝字,阅读更多干货~


图片



1 前言

看到这个标题,想必大家就都明白我们本文章所分享的主要内容了,下面我们就一起去看看所使用的spring框架中的一些扩展点。


spring本身是一套流程化、标准化的代码,它为了使自己更易扩展或方便使用者有更多的机会去整合,以接口这种易扩展的方式留出口子,让以spring框架为基础的家族更易扩展,功能更加强大,使用起来也更加方便。通过实现接口的形式,让我们开发者能与spring的bean过程和容器的生命周期更加贴合,而我们的关注力便可以更多的放在具体的业务实现上。因此,spring在设计的时候提供了很多扩展点


那么spring扩展点是什么?有哪些呢?我们接下来就详细聊聊吧。



2 spring中的扩展点

2.1 spring扩展点是什么?

我们已经知道spring中的主要功能是对bean对象的管理,也就是spring容器替我们管理了bean对象的一生。所以,spring在对bean的管理过程中提供了一些特定的接口这些接口会在spring管理bean或者容器的过程中被调用,让使用者可以参与或扩展对bean的管理。那么,我们来简单回顾下bean的一生吧。

图片

图1 bean的生命周期回顾

从上图可以看出,spring对bean的管理从bean定义开始,会从创建bean的统一定义对象BeanDefinition,经过实例化、初始化创建出完整的bean对象,而在使用后会经过销毁的过程对bean进行销毁。

  • bean的定义阶段

在bean的定义阶段,通过留出的BeanFactoryPostProcessor接口提供了对beanDefinition的扩展接口

    • 初始化前
    通过@PostContruct、Aware接口以及BeanPostProcessor的postProcessBefore-Initialization方法来实现对bean实例化后初始化前的扩展
    • 初始化

    bean的初始化过程中通过InitializationBean和配置init-method方法来扩展初始化阶段

    • 初始化后

    通过BeanPostProcessor的postProcessAfterInitialization方法来实现对bean初始化后的扩展

  • bean的销毁阶段

在bean的销毁过程中,又提供了DestructionAwareBeanPostProcessor的postProcess-BeforeDestruction方法、DisposableBean以及destory-method方法来进行扩展

  • 在整个过程中spring还提供了事件机制以及对spring容器生命周期的回调接口Lifecycle


2.1 spring中扩展点有哪些?

这部分内容我们将从对IOC相关的扩展、对aop相关的扩展、对spring容器相关的扩展、springboot中新增的扩展、spring的SPI机制这五个方面来展开。


2.1.1 对IOC相关的扩展

下面我们先来从对BeanFactory的扩展,以及对bean的扩展的来具体看看IOC的拓展内容,通过阅读下文的相信大家将对IOC的拓展将有更深的认知。


2.1.1.1 对BeanFactory的扩展

2.1.1.1.1 BeanFactoryPostProcessor

BeanFactoryPostProcessor是在spring容器加载了bean的定义文件之后,在bean实例化之前执行的,可以根据自己的需求修改beanDefinition。

/** * 定义一个bean。默认为singleton */@Component("beanDemo")    public class BeanDemo {}
/** * 自定义BeanFactoryPostProcessor */@Componentpublic class BeanFactoryPostProcessorDemo implements BeanFactoryPostProcessor { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException { BeanDefinition beanDefinition = configurableListableBeanFactory.getBeanDefinition("beanDemo"); // 将BeanDemo改为原型 beanDefinition.setScope("prototype"); }}
/** * 读取bean */ @Slf4j@SpringBootApplicationpublic class Application { public static void main(String[] args) { ConfigurableApplicationContext applicationContext = SpringApplication.run(Application.class, args); BeanDemo beanDemo = (BeanDemo)applicationContext.getBean("beanDemo"); log.info("beanDemo== {}", beanDemo.toString()); BeanDemo beanDemo1 = (BeanDemo)applicationContext.getBean("beanDemo"); log.info("beanDemo1== {}", beanDemo1.toString()); log.info("beanDemo == beanDemo1 : {}", beanDemo.equals(beanDemo1)); }}
// 结果// beanDemo== com.mainto.demo.spring.BeanDemo@4b6e1c0// beanDemo1== com.mainto.demo.spring.BeanDemo@561b61ed// beanDemo == beanDemo1 : false// 通过这个演示我们已经在BeanFactoryPostProcessor中将bean的类型改为原型模式,所以每次获取的bean才不是相同的

2.1.1.1.2 BeanDefinitionRegistryPostProcessor

这个接口是BeanFactoryPostProcessor的一个子接口,可以添加自定义beanDefinition。


BeanFactoryPostProcessor是在spring容器加载了bean的定义文件之后,在bean实例化之前执行的,但会在调用BeanFactoryPostProcessor前调用。
/** * 注意哦,这里没有生成Bean的注解 */public class BeanDefinitionDemo {}
/** * 实现BeanDefinitionRegistryPostProcessor将BeanDefinitionDemo注册到BeanDefinitionRegistry中 */ @Componentpublic class BeanDefinitionRegistryPostProcessorDemo implements BeanDefinitionRegistryPostProcessor { @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException { // 注册BeanDefinitionDemo RootBeanDefinition beanDefinition = new RootBeanDefinition(); beanDefinition.setBeanClass(BeanDefinitionDemo.class); beanDefinitionRegistry.registerBeanDefinition("beanDefinitionDemo", beanDefinition); }
@Override public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
}}@Slf4j@SpringBootApplicationpublic class Application {
public static void main(String[] args) { ConfigurableApplicationContext applicationContext = SpringApplication.run(Application.class, args); Object beanDefinitionDemo = applicationContext.getBean("beanDefinitionDemo"); log.info("beanDefinitionDemo == {}", beanDefinitionDemo); }}
// 结果// beanDefinitionDemo == com.mainto.demo.spring.BeanDefinitionDemo@3dffc764// 结果生成了bean哦


2.1.1.1.3 @Import注解

ConfigurationClassParser#processImports方法会读取@Import中属性的值,最后生成bean。@Import注解属性有三种写法,导入普通类、导入ImportBeanDefinitionRegistrar和导入ImportSelector。

  • 导入普通类

普通类:spring会将该类加载到spring容器中
@Slf4jpublic class ImportNormalDemo {    public void demo() {        log.info("this is importNormalDemo");    }}@Slf4j@SpringBootApplication@Import(ImportNormalDemo.class)public class Application {
public static void main(String[] args) { ConfigurableApplicationContext applicationContext = SpringApplication.run(Application.class, args); ImportNormalDemo bean = applicationContext.getBean(ImportNormalDemo.class); bean.demo(); }}
// 结果// com.mainto.demo.spring.ImportNormalDemo : this is importNormalDemo// 结果获取到了这个ImportNormalDemo 的bean

  • 导入实现了ImportBeanDefinitionRegistrar接口的类
导入实现了ImportBeanDefinitionRegistrar接口后重写registerBeanDefinitions方法后,可以添加自定义的BeanDefinition
@Slf4jpublic class ImportBeanDefinitionRegistryClassDemo {
public void demo() { log.info("this is ImportBeanDefinitionRegistryClassDemo"); }}/** * 实现了ImportBeanDefinitionRegistrar接口 */ public class ImportBeanDefinitionRegistryDemo implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) { RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(); rootBeanDefinition.setBeanClass(ImportBeanDefinitionRegistryClassDemo.class); registry.registerBeanDefinition("importBeanDefinitionRegistryClassDemo", rootBeanDefinition); }}@Slf4j@SpringBootApplication@Import(ImportBeanDefinitionRegistryDemo.class)public class Application { public static void main(String[] args) { ConfigurableApplicationContext applicationContext = SpringApplication.run(Application.class, args); ImportBeanDefinitionRegistryClassDemo bean = applicationContext.getBean(ImportBeanDefinitionRegistryClassDemo.class); bean.demo(); }}
// 结果// .s.ImportBeanDefinitionRegistryClassDemo : this is ImportBeanDefinitionRegistryClassDemo


  • 导入实现了ImportSelector接口的类
导入实现了ImportSelector重写selectImports方法后,可以导入该方法中返回的String[]中的类到BeanDefinitionMap中
@Slf4jpublic class ImportSelectClassDemo {    public void demo() {        log.info("this is ImportSelectClassDemo");    }}public class ImportSelectorDemo implements ImportSelector {    @Override    public String[] selectImports(AnnotationMetadata annotationMetadata) {        return new String[]{"com.mainto.demo.spring.bean.ImportSelectClassDemo"};    }}@Slf4j@SpringBootApplication@Import(ImportSelectorDemo.class)public class Application {
public static void main(String[] args) { ConfigurableApplicationContext applicationContext = SpringApplication.run(Application.class, args); ImportSelectClassDemo bean = applicationContext.getBean(ImportSelectClassDemo.class); bean.demo(); }}// 结果// c.m.d.spring.bean.ImportSelectClassDemo : this is ImportSelectClassDemo

2.2.1.2 对bean的扩展

2.2.1.2.1 Aware类接口

这类接口可以用于给bean注入一些bean或者容器相关的信息,常见接口有:

  • BeanFactoryAware注入BeanFactory容器

  • ApplicationContextAware注入ApplicationContext

  • BeanNameAware注入bean的名称

  • EnvironmentAware注入能获取到的Environment对象,进而可以获取各种系统变量信息

@Slf4j@Componentpublic class AwareDemo implements BeanFactoryAware, ApplicationContextAware, BeanNameAware, EnvironmentAware {    private BeanFactory beanFactory;    private ApplicationContext applicationContext;    private String beanName;    private Environment environment;
@Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { this.beanFactory = beanFactory; }
@Override public void setBeanName(String s) { this.beanName = s; log.info("beanName : {}", s); }
@Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; }
@Override public void setEnvironment(Environment environment) { this.environment = environment; }}


2.2.1.2.2 @PostConstruct

InitDestroyAnnotationBeanPostProcessor会在初始化前这个步骤中执行@PostConstruct的方法

@Slf4j@Componentpublic class PostConstructDemo {
private String name;
public PostConstructDemo() { log.info(" 对象构造时 name:{}", name); }
@PostConstruct public void init() { name = "123"; log.info("执行过postConstruct后 name:{}", name); }
public void demo() { log.info("执行过postConstruct后 name:{}", name); }}@Slf4j@SpringBootApplicationpublic class Application {
public static void main(String[] args) { ConfigurableApplicationContext applicationContext = SpringApplication.run(Application.class, args); PostConstructDemo bean = applicationContext.getBean(PostConstructDemo.class); bean.demo(); }}
// 结果// c.m.demo.spring.bean.PostConstructDemo : 对象构造时 name:null// c.m.demo.spring.bean.PostConstructDemo : 执行过postConstruct...// c.m.demo.spring.bean.PostConstructDemo : 执行过postConstruct后 name:123

2.2.1.2.3 InitializingBean

这个接口中afterPropertiesSet会在bean对象实例化后,且在所有属性注入完成后执行,可以处理一些实例化后一些初始业务逻辑。

@Slf4j@Componentpublic class InitializingBeanDemo implements InitializingBean {    public InitializingBeanDemo() {        log.info("this is 构造方法");    }
@Override public void afterPropertiesSet() throws Excc.m.d.spring.bean.InitializingBeanDemo : this is 构造方法eption { log.info("this is InitializingBean afterPropertiesSet方法"); }}//结果// c.m.d.spring.bean.InitializingBeanDemo : this is 构造方法// c.m.d.spring.bean.InitializingBeanDemo : this is InitializingBean afterPropertiesSet方法


2.2.1.2.4 SmartInitializingSingleton

通过这个接口我们可以对容器中的bean对象进行定制处理。当所有的非懒加载的bean初始化后会回调该接口的afterSingletonsInstantiated方法。

@Slf4j@Component("beanDemo")public class BeanDemo {
private String name;
public BeanDemo() { log.info("BeanDemo 构造实例时 name : {}", name); }
public void custom(String name) { this.name = name; }
public void print() { log.info("BeanDemo custom方法执行后 name : {}", name); }}
@Componentpublic class SmartInitializingSingletonDemo implements SmartInitializingSingleton, ApplicationContextAware {
private ApplicationContext applicationContext;
@Override public void afterSingletonsInstantiated() { BeanDemo bean = applicationContext.getBean(BeanDemo.class); bean.custom("test_SmartInitializingSingletonDemo"); }
@Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; }}
@Slf4j@SpringBootApplicationpublic class Application {
public static void main(String[] args) { ConfigurableApplicationContext applicationContext = SpringApplication.run(Application.class, args); BeanDemo bean = applicationContext.getBean(BeanDemo.class); bean.print(); }}
// 结果// com.mainto.demo.spring.bean.BeanDemo : BeanDemo 构造实例时 name : null// com.mainto.demo.spring.bean.BeanDemo : BeanDemo custom方法执行后 name : test_SmartInitializingSingletonDemo

2.2.1.2.5 @PreDestroy   

在Bean的初始化之后,InitDestroyAnnotationBeanPostProcessor
#buildLifecycleMetadata方法中会把标记有@PreDestroy的方法加入到destroyMethods,可以添加一些bean销毁时的处理逻辑。
@Slf4j@Component("beanDemo")public class BeanDemo implements DisposableBean {
private String name;
public BeanDemo() { log.info("BeanDemo 构造实例时 name : {}", name); }
public void custom(String name) { this.name = name; }
public void print() { log.info("BeanDemo custom方法执行后 name : {}", name); }
@PreDestroy public void preDestroy() { log.info("preDestroy... "); }
@Override public void destroy() throws Exception { log.info("destroy... "); }}


2.2.1.2.6 DisposableBean
这个接口可以自定义销毁bean前的一些逻辑或动作,会在bean被销毁前执行。
@Slf4j@Component("beanDemo")public class BeanDemo implements DisposableBean {
private String name;
public BeanDemo() { log.info("BeanDemo 构造实例时 name : {}", name); }
public void custom(String name) { this.name = name; }
public void print() { log.info("BeanDemo custom方法执行后 name : {}", name); }
@PreDestroy public void preDestroy() { log.info("preDestroy... "); }
@Override public void destroy() throws Exception { log.info("destroy... "); }}
2.2.1.2.7 BeanPostProcessor

这个接口主要提供了两个回调方法,分别在bean初始化前后被调用。

  • postProcessorBeforeInitialization方法会在bean初始化前调用

  • postProcessorAfterInitialization方法会在bean初始化后调用,Spring中的Aop就是基于初始化后实现的

通过这个接口我们可以对spring管理的bean进行再次加工,比如可以修改bean的属性,给bean生成一个动态代理等等。

public interface TestService {    void print();}
@Slf4j@Servicepublic class TestServiceImpl1 implements TestService{
@Override public void print() { log.info("this is testServiceImpl1"); }}
@Slf4j@Servicepublic class TestServiceImpl2 implements TestService{ @Override public void print() { log.info("this is TestServiceImpl2"); }}
@Slf4j@Component("beanDemo")public class BeanDemo implements DisposableBean {
private String name;
@Autowired @Qualifier("testServiceImpl1") private TestService testService;
public BeanDemo() { log.info("BeanDemo 构造实例时 name : {}", name); }
public void custom(String name) { this.name = name; }
public void print() { log.info("BeanDemo custom方法执行后 name : {}", name); testService.print(); }
@PreDestroy public void preDestroy() { log.info("preDestroy... "); }
@Override public void destroy() throws Exception { log.info("destroy... "); }}
@Slf4j@Componentpublic class BeanPostProcessorDemo implements BeanPostProcessor, ApplicationContextAware {
private ApplicationContext applicationContext;
@Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if("beanDemo".equals(beanName)) { log.info("BeforeInitialization beanDemo : {}", beanName); } return bean; }
@SneakyThrows @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if("beanDemo".equals(beanName)) { log.info("AfterInitialization beanDemo : {}", beanName); Class<?> aClass = bean.getClass(); Field[] declaredFields = aClass.getDeclaredFields(); for(Field field : declaredFields) { field.setAccessible(true); if(field.getType().isInterface() && field.getType().getName().equals("com.mainto.demo.spring.service.TestService")) { String value = field.getAnnotation(Qualifier.class).value(); field.set(bean, applicationContext.getBean(value, TestService.class)); } } } return bean; }
@Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; }}
@Slf4j@SpringBootApplicationpublic class Application {
public static void main(String[] args) { ConfigurableApplicationContext applicationContext = SpringApplication.run(Application.class, args); BeanDemo bean = applicationContext.getBean(BeanDemo.class); bean.print(); }}
// 结果// c.m.demo.spring.BeanPostProcessorDemo : BeforeInitialization beanDemo : beanDemo// c.m.demo.spring.BeanPostProcessorDemo : AfterInitialization beanDemo : beanDemo// c.m.d.spring.service.TestServiceImpl1 : this is testServiceImpl1

2.2.1.2.8 FactoryBean

如果我们想创建一个完全由我们控制的bean,那么就可以使用这个接口,但这种方式创建出来的bean是不会经过完整生命周期的,只会经过初始化后。

public class DemoBean {}
@Componentpublic class DemoBeanFactoryBean implements FactoryBean {
@Override public Object getObject() throws Exception { return new DemoBean(); }
@Override public Class<?> getObjectType() { return DemoBean.class; }
/** * 工厂管理的的bean对象是否为单例的 * 如果该方法返回true, 那么通过getObject()方法返回的对象都是同一个对象 * @return */ @Override public boolean isSingleton() { return false; }}
@Slf4j@SpringBootApplicationpublic class Application {
public static void main(String[] args) throws Exception { ConfigurableApplicationContext applicationContext = SpringApplication.run(Application.class, args); Object bean = applicationContext.getBean("demoBeanFactoryBean"); System.out.println(bean); Object bean1 = applicationContext.getBean("demoBeanFactoryBean"); System.out.println(bean1); System.out.println(bean.equals(bean1)); // 如果想获取DemoBeanFactoryBean这个bean 获取的时候需要添加& DemoBeanFactoryBean demoBeanFactoryBean = (DemoBeanFactoryBean)applicationContext.getBean("&demoBeanFactoryBean"); System.out.println(demoBeanFactoryBean); Object object = demoBeanFactoryBean.getObject(); System.out.println(object); System.out.println(bean.equals(object)); }}
// 结果// 因isSingleton方法返回的是false// com.mainto.demo.spring.bean.DemoBean@7fe82967// com.mainto.demo.spring.bean.DemoBean@50850539// false// com.mainto.demo.spring.DemoBeanFactoryBean@65e21ce3// com.mainto.demo.spring.bean.DemoBean@6c3659be// false


2.2.2 对aop相关的扩展

2.2.2.1 MethodInterceptor

实现这个接口可以用于对需要增强的方法,在调用之前、调用过程中以及调用之后进行增强。

@Slf4jpublic class MethodInterceptorDemo implements MethodInterceptor {
@Override public Object invoke(MethodInvocation methodInvocation) throws Throwable { String name = methodInvocation.getMethod().getName(); log.info("method name:{}", name); return methodInvocation.proceed(); }}
@Configurationpublic class InterceptorConfig {
public static final String traceExecution = "execution(* com.mainto.demo.spring.service..*.*(..))"; @Bean public DefaultPointcutAdvisor defaultPointcutAdvisor2() { MethodInterceptorDemo interceptor = new MethodInterceptorDemo(); AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut(); pointcut.setExpression(traceExecution);
// 配置增强类advisor DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(); advisor.setPointcut(pointcut); advisor.setAdvice(interceptor); return advisor; }}
/** * @author jlk * @Description * @Title: BeanDemo * @Package: com.mainto.demo * @CreateTime: */@Slf4j@Component("beanDemo")public class BeanDemo implements DisposableBean {
private String name;
@Autowired @Qualifier("testServiceImpl1") private TestService testService;
public BeanDemo() { log.info("BeanDemo 构造实例时 name : {}", name); }
public void custom(String name) { this.name = name; }
public void print() { log.info("BeanDemo custom方法执行后 name : {}", name); testService.print(); }
@PreDestroy public void preDestroy() { log.info("preDestroy... "); }
@Override public void destroy() throws Exception { log.info("destroy... "); }}
public interface TestService { void print();}
@Slf4j@Servicepublic class TestServiceImpl1 implements TestService{
@Override public void print() { log.info("this is testServiceImpl1"); }}
@Slf4j@SpringBootApplicationpublic class Application {
public static void main(String[] args) throws Exception { ConfigurableApplicationContext applicationContext = SpringApplication.run(Application.class, args); BeanDemo bean = applicationContext.getBean(BeanDemo.class); bean.print(); }}
// 结果// c.m.demo.spring.MethodInterceptorDemo : method name:print// c.m.d.spring.service.TestServiceImpl1 : this is testServiceImpl1

2.2.3 对spring容器相关的扩展

我们主要从对容器动作的监听和对容器生命周期的拓展两个方面展开介绍对spring容器的拓展。


2.2.3.1 对容器动作的监听

2.2.3.1.1 ApplicationListener

通过实现这个接口可以订阅容器的发布的事件,比如ContextRefreshedEvent,来处理一些特定的义务逻辑 。

@Slf4j@Componentpublic class ApplicationListenerDemo implements ApplicationListener {    @Override    public void onApplicationEvent(ApplicationEvent applicationEvent) {        log.info("applicationEvent : {}", applicationEvent.getClass());    }}// 结果// c.m.demo.spring.ApplicationListenerDemo  : applicationEvent : class org.springframework.boot.web.servlet.context.ServletWebServerInitializedEvent// c.m.demo.spring.ApplicationListenerDemo  : applicationEvent : class org.springframework.context.event.ContextRefreshedEvent// com.mainto.demo.Application              : Started Application in 3.671 seconds (JVM running for 4.51)// c.m.demo.spring.ApplicationListenerDemo  : applicationEvent : class org.springframework.boot.context.event.ApplicationStartedEvent// c.m.demo.spring.ApplicationListenerDemo  : applicationEvent : class org.springframework.boot.availability.AvailabilityChangeEvent// c.m.demo.spring.ApplicationListenerDemo  : applicationEvent : class org.springframework.boot.context.event.ApplicationReadyEvent// c.m.demo.spring.ApplicationListenerDemo  : applicationEvent : class org.springframework.boot.availability.AvailabilityChangeEvent

2.2.3.2 对容器生命周期的扩展

2.2.3.2.1 Lifecycle
这个接口用于对spring容器本身的生命周期的增强。我们主要使用的Lifecycle的扩展接口是SmartLifecycle,SmartLifecycle比Lifecycle有着更丰富的功能,如果有多个SmartLifecycle通过getPhase方法可以控制顺序。

Lifecycle可以用来处理一些具有生命周期的义务,比如启动时注册一个客户端,然后为了避免资源位置占用,在容器关闭时需要停止客户端的注册。

@Slf4j@Componentpublic class LifecycleDemo implements SmartLifecycle {
private final AtomicBoolean isRunning = new AtomicBoolean(false);
@Override public void start() { log.info("LifecycleDemo start ..."); isRunning.compareAndSet(false, true); }
@Override public void stop() { log.info("LifecycleDemo stop ..."); isRunning.compareAndSet(true, false); }
@Override public boolean isRunning() { return isRunning.get(); }}// 结果// com.mainto.demo.spring.LifecycleDemo : LifecycleDemo start ...// 关闭进程时:com.mainto.demo.spring.LifecycleDemo : LifecycleDemo stop ...


2.2.4 springboot中新增的扩展

2.2.4.1 ApplicationRunner

在spring容器启动好后,发布ApplicationStartedEvent事件后会被调用。如果需要做一些在容器启动之后处理的一些逻辑,可以实现这个接口。
@Slf4j@Componentpublic class ApplicationRunnerDemo implements ApplicationRunner {    @Override    public void run(ApplicationArguments args) throws Exception {        log.info("args : {}", args.getOptionNames());    }}


2.2.4.2 CommandLineRunner

在spring容器启动好后、发布ApplicationStartedEvent事件后会被调用。如果需要做一些需要在容器启动之后处理一些逻辑,可以实现这个接口。
@Slf4j@Componentpublic class CommandLineRunnerDemo implements CommandLineRunner {    @Override    public void run(String... args) throws Exception {        log.info("args:{}", args);    }}

这两个接口是在同一个方法内被调用,SpringApplication#callRunners方法统一被触发,这两者没什么很大的区别,只是在接收方法入参上有些区别。

  • CommandLineRunner方法入参为可变参数,接收的是java-jar启动jar包时的可变参数,没做过处理

  • ApplicationRunner方法入参是ApplicationArguments,接收的也是java-jar启动jar包时的可变参数,只不过会把‘--’开始的参数处理成optionName->optionValue的格式

2.2.5 spring的SPI机制

在Spring中提供了SPI机制,我们只需要在META-INF/spring.factories中配置接口实现类名,即可通过服务发现机制,在运行时加载接口的实现类:
//# Initializersorg.springframework.context.ApplicationContextInitializer=\org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

配置好spring.factories文件就可以使用SpringFactoriesLoader.loadFactoryNames找到对应的类,在springboot的自动配置类中就是这样去处理的。

// 在工程目录下新建包 ext// 在ext包下创建配置类 TestConfiguration
@Configurationpublic class TestConfiguration {}
// 在META-INF/spring.factories文件中增加内容org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ ext.TestConfiguration
@Slf4j@SpringBootApplicationpublic class Application {
public static void main(String[] args) throws Exception { ConfigurableApplicationContext applicationContext = SpringApplication.run(Application.class, args); TestConfiguration bean = applicationContext.getBean(TestConfiguration.class); System.out.println(bean); }}
// 结果// ext.TestConfiguration$$EnhancerBySpringCGLIB$$c2f80267@63d5874f
聊了这么久的spring中扩展点,那么这些在我们的代码中是如何使用的呢?


3 spring扩展点的应用示例

接下来用我们现在经常使用springcloud中的几个关键组件来说明在其他框架中是如何与spring进行整合的。


3.1 在nacos与spring的整合中的使用

3.1.1 对ApplicationListener的使用

当我们接入nacos客户端后,我们只是引入了nacos-discovery的jar包,配置了一下nacos的基础数据,当我们的服务启动后就可以使用nacos的服务注册与发现功能了。大家有没有奇怪过这是为什么?


在spring-cloud-starter-alibaba-nacos-discovery这个jar包中有一个NacosAutoServiceRegistration这个非常关键的类。那么我们先来看一下这个类的继承关系:

图片

图2 NacosAutoServiceRegistration类的继承关系
从上面这个类的关系中可以清晰的看出NacosAutoServiceRegistration这个类通过继承AbstractAutoServiceRegistration这个父类,间接的继承了上文提到的ApplicationListener的其中一个扩展点。
    /**   * 这个方法是实现ApplicationListener需要重写的方法,看到这个方法入参就可以看出,     * 这个AbstractAutoServiceRegistration监听的事件是 WebServerInitializedEvent   * WebServerInitializedEvent在springboot中有两个子类,咱们常用的是ServletWebServerInitializedEvent     * ServletWebServerInitializedEvent会在webServer启动的方法里发布   */    @Override  @SuppressWarnings("deprecation")  public void onApplicationEvent(WebServerInitializedEvent event) {    bind(event);  }
/** * 重写ApplicationListener的bind方法处理监听的事件触发的逻辑 */ @Deprecated public void bind(WebServerInitializedEvent event) { ApplicationContext context = event.getApplicationContext(); if (context instanceof ConfigurableWebServerApplicationContext) { if ("management".equals(((ConfigurableWebServerApplicationContext) context) .getServerNamespace())) { return; } } // 使用CAS设置端口号 this.port.compareAndSet(0, event.getWebServer().getPort()); // 开始启动 this.start(); } public void start() { if (!isEnabled()) { if (logger.isDebugEnabled()) { logger.debug("Discovery Lifecycle disabled. Not starting"); } return; }
// only initialize if nonSecurePort is greater than 0 and it isn't already running // because of containerPortInitializer below // 判断是否正在运行,如果没有运行进行启动逻辑 if (!this.running.get()) { // 发布InstancePreRegistreredEvent事件 this.context.publishEvent( new InstancePreRegisteredEvent(this, getRegistration())); // 这就开始注册服务喽 register(); // 发布InstanceRegisteredEvent 事件 this.context.publishEvent( new InstanceRegisteredEvent<>(this, getConfiguration())); // 使用CAS设置运行状态为true this.running.compareAndSet(false, true); } }
看到上面的代码,大家应该明白了,nacos服务注册是通过ApplicationListener这个事件监听扩展来实现的。那么nacos服务注册上了后,它是怎样开启watch的呢?

3.1.2 对Lifecycle的使用

在nacos的客户端jar包中有个NacosWatch的类,我们来看下其继承关系:

图片图3 NacosWatch类的继承关系

从上图可以看出nacos实现了SmartLifecyle这个接口,重写了start和stop这两个接口。

    @Override  public void start() {        // 使用CAS设置运行状态为true    if (this.running.compareAndSet(false, true)) {            // 设置时间监听      EventListener eventListener = listenerMap.computeIfAbsent(buildKey(),          event -> new EventListener() {            @Override            public void onEvent(Event event) {              if (event instanceof NamingEvent) {                List<Instance> instances = ((NamingEvent) event)                    .getInstances();                                // 根据ip和端口号选择instance                Optional<Instance> instanceOptional = selectCurrentInstance(                    instances);                                // 如果存在重设元数据信息                instanceOptional.ifPresent(currentInstance -> {                  resetIfNeeded(currentInstance);                });              }            }          });            // 根据nacos配置获取NamingService      NamingService namingService = nacosServiceManager          .getNamingService(properties.getNacosProperties());      try {                // 订阅配置中的服务        namingService.subscribe(properties.getService(), properties.getGroup(),            Arrays.asList(properties.getClusterName()), eventListener);      }      catch (Exception e) {        log.error("namingService subscribe failed, properties:{}", properties, e);      }            // 给watchFuture赋值定时线程      this.watchFuture = this.taskScheduler.scheduleWithFixedDelay(          this::nacosServicesWatch, this.properties.getWatchDelay());    }  }
@Override public void stop() { // 当容器停止时,nacos也需要执行停止逻辑 // 使用CAS将运行状态改为false if (this.running.compareAndSet(true, false)) { // 判断观测任务是否为null,如果不为null,则停止任务 if (this.watchFuture != null) { // shutdown current user-thread, // then the other daemon-threads will terminate automatic. this.taskScheduler.shutdown(); this.watchFuture.cancel(true); }
EventListener eventListener = listenerMap.get(buildKey()); try { NamingService namingService = nacosServiceManager .getNamingService(properties.getNacosProperties()); // 解除订阅 namingService.unsubscribe(properties.getService(), properties.getGroup(), Arrays.asList(properties.getClusterName()), eventListener); } catch (Exception e) { log.error("namingService unsubscribe failed, properties:{}", properties, e); } } }

3.2 在Loadbalancer与spring整合中的使用

从开始接触请求同名(同域名或者服务名)多实例开始就有一个概念一直贯穿其中,那就是负载,那么在springcloud中是如何负载的呢?没错,那就是使用我们的ribbon或者说是LoadBalancer,那么LoadBalancer又是如何和spring进行整合的呢?


3.2.1 对SmartInitializingSingleton的使用

在spring cloud common中有一个类LoadBalancerAutoConfiguration:
    /**     * 这里是用SamrtInitializingSingleton      * 当所有懒加载的bean初始化后,spring容器会回调afterSingletonsInstantiated方法     * 也就是遍历所有的自定义RestTemplate,给RestTemplate添加拦截器实现负载拦截     */  @Bean  public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(      final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {    return () -> restTemplateCustomizers.ifAvailable(customizers -> {      for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {        for (RestTemplateCustomizer customizer : customizers) {          customizer.customize(restTemplate);        }      }    });  }
@Configuration(proxyBeanMethods = false) /** * 条件注入 */ @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate") static class LoadBalancerInterceptorConfig {
/** * 注入LoadBalanceInterceptor Bean */ @Bean public LoadBalancerInterceptor loadBalancerInterceptor( LoadBalancerClient loadBalancerClient, LoadBalancerRequestFactory requestFactory) { return new LoadBalancerInterceptor(loadBalancerClient, requestFactory); }
/** * 注入RestTemplateCustomizer Bean * 并重写customize方法 */ @Bean // 条件注入 @ConditionalOnMissingBean public RestTemplateCustomizer restTemplateCustomizer( final LoadBalancerInterceptor loadBalancerInterceptor) { return restTemplate -> { List<ClientHttpRequestInterceptor> list = new ArrayList<>( restTemplate.getInterceptors()); // 给RestTemplate 添加负载拦截 list.add(loadBalancerInterceptor); restTemplate.setInterceptors(list); }; } }

通过上述代码,我们知道了LoadBalancer是如何与Spring整合的,接下来我们一起看看spring cloud openFeign是怎么跟spring整合的。


3.3 在feign与spring整合中的使用

同上文一样,我们还是先找出关键类。我们在使用feign的时候,除了要引入相关依赖包,是否还要在启动类上添加注解@EnableFeignClients?大家有没有尝试点进去看过呢?

@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)@Documented@Import(FeignClientsRegistrar.class)public @interface EnableFeignClients


这里我们看到了一个关键类FeignClientsRegistrarFeignClientRegistrar这个类的工作主要是扫描加了@FeignClient注解的类。根据feign定义的工厂bean FeignClientFactoryBean获取beanDefinition,然后获取@FeignClien注解信息封装到beanDefinition中,后续会生成代理对象然后获取http client api,根据@FeignClien注解信息完成rpc调用。那么我们先来看下这个类的继承关系:

图片

图4 FeignClientsRegistrar类的继承关系

这个类里我们能看到ImportBeanDefinitionRegistrar这个接口,这个接口可以让我们添加一些自定义的BeanDefinition。

  /**   * 重写 ImportBeanDefinitionRegistrar#registerBeanDefinitions方法   * ImportBeanDefinitionRegisrar是spring其中一个扩展点,这个接口的主要功能是提供开发者   * 可以扩展注册自定义的BeanDefinition   * AnnotationMetadata 是注解元数据   * BeanDefinitionRegistry可以理解成BeanDefinition的操作接口类,都会存储到beanDefinitionMap中   */  @Override  public void registerBeanDefinitions(AnnotationMetadata metadata,      BeanDefinitionRegistry registry) {        // 注册默认配置    registerDefaultConfiguration(metadata, registry);        // 注册feignClient    registerFeignClients(metadata, registry);  }
/** * 注册feignClient */ public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 存放解析@EnableFeignClients注解中的内容,找到所有的FeignClient LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>(); Map<String, Object> attrs = metadata .getAnnotationAttributes(EnableFeignClients.class.getName()); final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients"); if (clients == null || clients.length == 0) { ClassPathScanningCandidateComponentProvider scanner = getScanner(); scanner.setResourceLoader(this.resourceLoader); scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class)); Set<String> basePackages = getBasePackages(metadata); for (String basePackage : basePackages) { candidateComponents.addAll(scanner.findCandidateComponents(basePackage)); } } else { for (Class<?> clazz : clients) { candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz)); } }
// 遍历扫描到的FeignClient BeanDefinition 注册bean for (BeanDefinition candidateComponent : candidateComponents) { if (candidateComponent instanceof AnnotatedBeanDefinition) { // verify annotated class is an interface // 校验被FeignClient标注的类是一个接口 AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent; AnnotationMetadata annotationMetadata = beanDefinition.getMetadata(); Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface");
Map<String, Object> attributes = annotationMetadata .getAnnotationAttributes(FeignClient.class.getCanonicalName()); // 获取client名称 String name = getClientName(attributes); // 注册clientConfiguration registerClientConfiguration(registry, name, attributes.get("configuration")); // 注册feignClient registerFeignClient(registry, annotationMetadata, attributes); } } /** * 注册FeignClient * 1.根据注解信息获取类名拿到class对象 * 2.生成FeignClientFactoryBean 对象 * 3.将FeignClientFactoryBean封装成beanDefinition对象并注册 */ private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) { String className = annotationMetadata.getClassName(); Class clazz = ClassUtils.resolveClassName(className, null); ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory ? (ConfigurableBeanFactory) registry : null; String contextId = getContextId(beanFactory, attributes); String name = getName(attributes); // 这里体现了使用的另一个扩展点 FactoryBean // 如果需要定义一个逻辑相对复杂,且需要注册成bean, 但不好用@Bean注解来生成bean可以使用FactoryBean FeignClientFactoryBean factoryBean = new FeignClientFactoryBean(); factoryBean.setBeanFactory(beanFactory); factoryBean.setName(name); factoryBean.setContextId(contextId); factoryBean.setType(clazz); // 定义BeanDefinition BeanDefinitionBuilder definition = BeanDefinitionBuilder .genericBeanDefinition(clazz, () -> { factoryBean.setUrl(getUrl(beanFactory, attributes)); factoryBean.setPath(getPath(beanFactory, attributes)); factoryBean.setDecode404(Boolean .parseBoolean(String.valueOf(attributes.get("decode404")))); Object fallback = attributes.get("fallback"); if (fallback != null) { factoryBean.setFallback(fallback instanceof Class ? (Class<?>) fallback : ClassUtils.resolveClassName(fallback.toString(), null)); } Object fallbackFactory = attributes.get("fallbackFactory"); if (fallbackFactory != null) { factoryBean.setFallbackFactory(fallbackFactory instanceof Class ? (Class<?>) fallbackFactory : ClassUtils.resolveClassName(fallbackFactory.toString(), null)); } return factoryBean.getObject(); }); definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); definition.setLazyInit(true); validate(attributes);
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition(); beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className); beanDefinition.setAttribute("feignClientsRegistrarFactoryBean", factoryBean);
// has a default, won't be null boolean primary = (Boolean) attributes.get("primary");
beanDefinition.setPrimary(primary);
String[] qualifiers = getQualifiers(attributes); if (ObjectUtils.isEmpty(qualifiers)) { qualifiers = new String[] { contextId + "FeignClient" }; }
// beanDefinition包装类 BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers); // 注册beanDefinition // 这些注册好的beanDefinition会在后面spring生成bean的时候创建出bean对象 // 具体的逻辑是: // 当AbstractApplicationContext refresh时在执行finishBeanFactoryInitialization(beanFactory); // 最后调用DefaultListableBeanFactory#preInstantiateSingletons方法生成bean对象 BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry); }


3.4 参考样例

目的:整合Rocketmq客户端
背景:需要能根据配置注入多个订阅关系不一致的订阅者
接入思路:使用自定义容器的方式,一种订阅关系一个容器,容器本身实现SmartLifycycle跟随spring容器的生命周期。因容器本身需要在其他bean初始化好后执行,所以使用了SmartInitializingSingleton这个bean的特性。
/** * 自动装配类 */   @Slf4j@Configuration@ConditionalOnClass(ConsumerBean.class)@EnableConfigurationProperties(value = {RocketmqProperties.class, RocketmqNormalMsgConsumerProperties.class})@ConditionalOnProperty(value = "rocketmq.normal.consumer.enabled", havingValue = "true")public class EnableRocketmqNormalMsgConsumerAutoConfig implements ApplicationContextAware, SmartInitializingSingleton {    @Override    public void afterSingletonsInstantiated() {        // 读取配置获取所有的订阅关系        List<RocketmqNormalDTO> rocketmqNormalDTOList = this.initSubscriptionTable();        if(CollectionUtils.isEmpty(rocketmqNormalDTOList)){            return;        }        // 遍历订阅关系开始构建容器,生成beanDefiniation 注册bean        for(RocketmqNormalDTO rocketmqNormalDTO : rocketmqNormalDTOList) {            String containerName = concatContainerName(rocketmqNormalDTO.getGroupId());            GenericApplicationContext genericApplicationContext = (GenericApplicationContext) this.applicationContext;            genericApplicationContext.registerBean(containerName, RocketmqNormalMsgConsumerContainer.class, () -> {                return this.createContainer(rocketmqNormalDTO.getSubscriptionTable(), rocketmqNormalDTO.getProperties(), containerName);            }, new BeanDefinitionCustomizer[0]);
// 获取containerBean RocketmqNormalMsgConsumerContainer consumerContainer = genericApplicationContext.getBean(containerName, RocketmqNormalMsgConsumerContainer.class); if(!consumerContainer.getHasRun().get()){ try{ consumerContainer.start(); } catch (Exception e) { throw new RocketmqException("container start error", e); } } log.info("容器启动完成,consumerContainerName : {}", containerName); } }
/** * 创建容器 * @param subscriptionTable * @return */ private RocketmqNormalMsgConsumerContainer createContainer(Map<Subscription, MessageListener> subscriptionTable, Properties properties, String containerName){ RocketmqNormalMsgConsumerContainer consumerContainer = new RocketmqNormalMsgConsumerContainer(); consumerContainer.setSubscriptionTable(subscriptionTable); consumerContainer.setProperties(properties); consumerContainer.setContainerName(containerName);
return consumerContainer; }}
@Data@Slf4jpublic class RocketmqNormalMsgConsumerContainer implements InitializingBean, SmartLifecycle, ApplicationContextAware {
private ApplicationContext applicationContext; private ConsumerBean consumerBean; private Map<Subscription, MessageListener> subscriptionTable; private Properties properties; private AtomicBoolean hasRun = new AtomicBoolean(false); private String containerName;
/** * 重写InitializingBean的afterPropertiesSet方法,这个方法是在bean实例化好了后调用 */ @Override public void afterPropertiesSet() throws Exception { // 实例化consumerBean this.consumerBean = initConsumerBean(); }
private ConsumerBean initConsumerBean(){ ConsumerBean consumerBean = new ConsumerBean(); consumerBean.setProperties(this.properties); consumerBean.setSubscriptionTable(this.subscriptionTable); return consumerBean; }
@Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; }
/** * 设置自动启动 */ @Override public boolean isAutoStartup() { return true; }
/** * 重写SmartLifecycle的stop方法 * 这个方法在容器关闭的时候被调用 * 这里用来关闭consumerBean */ @Override public void stop(Runnable callback) { this.stop(); callback.run(); }
/** * 重写SmartLifecycle的start方法 * 这个方法在容器开始启动的时候被调用 * 这里用来开启consumerBean */ @Override public void start() { if(this.hasRun.get()){ throw new RocketmqException("this container has run"); } if(this.hasRun.compareAndSet(false, true)){ this.consumerBean.start(); log.info("容器已经启动:{}", this.getContainerStartDesc()); } }
private String getContainerStartDesc(){ StringBuffer descBuffer = new StringBuffer(); descBuffer.append("name:").append(this.containerName).append(",") .append("properties:").append(JSON.toJSONString(this.properties)).append(",") .append("subscriptionTable:").append(JSON.toJSONString(this.subscriptionTable)); return descBuffer.toString(); }
/** * 重写Lifecycle的stop方法 * 这个方法在容器开始关闭的时候被调用 * 这里用来关闭consumerBean */ @Override public void stop() { if(this.hasRun.get()){ if(this.hasRun.compareAndSet(true, false)){ log.info("容器:{} 已经stop", this.getContainerName()); if(Objects.nonNull(this.consumerBean)){ this.consumerBean.shutdown(); log.info("consumerBean has shutdown"); } } } }
@Override public boolean isRunning() { return this.hasRun.get(); }
@Override public int getPhase() { return Integer.MAX_VALUE; }}


4 spring扩展点的作用与应用场景

上面聊了spring的扩展点,也聊了一些在我们熟悉的springcloud的一些使用,那么下面我们一起看看这些扩展点的具体应用场景。


4.1 spring扩展点的作用

如果你对spring的扩展点、扩展机制有所了解的话,那么对你理解其他框架是如何整合spring时有非常大的帮助,比如上面介绍过的nacos的整合,上面只是写出了关键类,那么这个关键类是怎么找到的呢?


首先,根据上文之前的习惯看提供的客户端jar包中是否有spring.factories,看里面内容中使用了哪几个接口的扩展机制,按照starter的老习惯可能会注意org.springframework.boot.autoconfigure.EnableAutoConfiguration这个里面有哪些类


其次,观察每个类,看一下是否有使用spring扩展点的这种关键字眼,当看到NacosAutoServiceRegistration这个类里面有start和stop,那么这时候我们可以联想到:这应该与Lifecycle或者ApplicationListener存在关系,点开父类可以发现它果然实现了ApplicationListener。


最后,你根据找到的关键类,就能大致理解nacos客户端是怎样与spring整合的。


4.2 spring扩展点的应用场景

  • 使用spring扩展点整合封装自己的业务jar包时,将有些公共业务或者功能逻辑抽象出来封装成jar包的形式,通过引包的方式再提供使用,帮助大家提升开发效率。

  • 根据扩展点不同的特性进行特定业务逻辑的处理

    • 如果在初始化RocketmqNormalMsgConsumerContainer这个容器中,因可能会使用到其他的业务bean,那么就是用了SmartInitializingSingleton这个扩展点的特性

    • 如果需要对实现了某一类接口的bean的各个生命期间进行收集,或对某个类型的bean进行统一的设值等等,那就可以使用BeanPostProcessor的扩展子接口InstantiationAwareBeanPostProcessor
    • 如果需要跟随spring容器的生命周期做一些处理,那其实可以配置ApplicationListener或者实现SmartLifecyle

4.3 使用spring扩展点时的注意事项

现在看起来spring扩展点是不是非常好用?但也要注意使用spring扩展点的前后逻辑要保持一致,时刻谨记spring生命周期的线性逻辑,确保不会逻辑冲突。


比如,你使用BeanFactoryPostProcessor把一个bean的定义对象beanDefinition改成了原形,但是在后面又使用SmartInitializingSingleton对这个bean进行了特定处理,然后再使用的时候你可能会以为你做到了特定处理,但之后拿来用的时候,你可能会感到不可思议,因为你已经改成了原形模式。所以,要时刻谨记扩展逻辑的前后一致与连贯性,确保不会自相冲突。



5 总结

在上面聊了很多spring扩展点相关的内容,有spring扩展点的定义理解;有举例一些常见的spring扩展点;有spring扩展点在springcloud中的一些实际使用示例;也有自己使用的一个整合rocketmq客户端的例子;还有spring扩展点的使用场景和注意事项。


看完了这些后相信大家对spring扩展点会有一个比较熟悉的认知,在今后的实际工作中可以考虑使用spring扩展点来提高代码的整洁度与复用度,利用spring本身提供的扩展机制来更好的处理自己实际业务的需求实现,借鉴spring的扩展思想来提升自己的代码在实现的过程中,可以在保证本身功能实现的内聚沉淀抽象的基础上留出更好的易扩展性和可复用性。


本文作者


加里克,来自缦图互联网中心中台团队。



--------END--------



也许你还想看

  | 浅谈Spring源码之BeanDefinition
  | 走近java并发同步器AQS
  | 分布式锁选型和源码分析


继续滑动看下一个
缦图coder
向上滑动看下一个