首页 > 其他分享 >Spring扩展点之SmartInitializingSingleton接口

Spring扩展点之SmartInitializingSingleton接口

时间:2022-11-21 10:36:40浏览次数:41  
标签:Spring beanName EventListener 接口 SmartInitializingSingleton 注解 final

执行时机

SmartInitializingSingleton主要用于在IoC容器基本启动完成时进行扩展,这时非Lazy的Singleton都已被初始化完成。所以,在该扩展点执行ListableBeanFactory#getBeansOfType()等方法不会出现因过早加载Bean出现副作用。这个扩展点Spring 4.1开始引入,其定义如下: 

public interface SmartInitializingSingleton {
    void afterSingletonsInstantiated();
} 

这个扩展点,可能和我们平时采用事件监听机制ApplicationListener<ContextRefreshedEvent>监听容器启动完成事件功能很类似。现在我们来看下该扩展点触发代码位置,是在DefaultListableBeanFactory#preInstantiateSingletons()方法中最后执行:

for (String beanName : beanNames) {
    Object singletonInstance = getSingleton(beanName);
    //如果实现SmartInitializingSingleton接口,则执行其afterSingletonsInstantiated方法
    if (singletonInstance instanceof SmartInitializingSingleton) {
        SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
        if (System.getSecurityManager() != null) {
            AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
                smartSingleton.afterSingletonsInstantiated();
                return null;
            }, getAccessControlContext());
        }
        else {
            smartSingleton.afterSingletonsInstantiated();
        }
    }
}

使用场景

Spring中有个SmartInitializingSingleton接口实现类: 

EventListenerMethodProcessor,主要用于完成@EventListener注解方式的事件监听。在Spring中需要监听某个事件常规方式是实现ApplicationListener接口,Spring IoC容器启动时自动会收集系统中所有ApplicationListener资料,并将其注册到Spring的事件广播器上,采用典型的订阅/发布模式。Spring 4.2引入了@EventListener注解方式,可以更加方便的对事件进行监听,使用方式如下如下,只需要在方法上使用@EventListener注解,并在方法参数上指定需要监听的事件类型即可:

@EventListener
public void onEvent(ContextRefreshedEvent event){
    System.out.println("===receive ContextRefreshedEvent===");
}

@EventListener注解的背后,Spring做了哪些工作以支持该注解功能呢?

1、首先,定义一个扩展类EventListenerMethodProcessor,继承SmartInitializingSingleton接口: 

public class EventListenerMethodProcessor
  implements SmartInitializingSingleton, ApplicationContextAware, BeanFactoryPostProcessor 

2、当IoC容器完成所有单实例Bean的初始化工作后,触发afterSingletonsInstantiated()方法执行:

public void afterSingletonsInstantiated() {
    ConfigurableListableBeanFactory beanFactory = this.beanFactory;
    Assert.state(this.beanFactory != null, "No ConfigurableListableBeanFactory set");
    String[] beanNames = beanFactory.getBeanNamesForType(Object.class);
    for (String beanName : beanNames) {
     if (!ScopedProxyUtils.isScopedTarget(beanName)) {
      ...
      processBean(beanName, type);
      ... 
     }
    }
}

该方法中,获取所有IoC容器中的实例,然后遍历使用processBean()方法进行处理,其核心代码见下面:

private void processBean(final String beanName, final Class<?> targetType) {
    if (!this.nonAnnotatedClasses.contains(targetType) &&
     AnnotationUtils.isCandidateClass(targetType, EventListener.class) &&
     !isSpringContainerClass(targetType)) {

    Map<Method, EventListener> annotatedMethods = null;

    //查找Class上所有被@EventListener注解的方法
    annotatedMethods = MethodIntrospector.selectMethods(targetType,
      (MethodIntrospector.MetadataLookup<EventListener>) method ->
        AnnotatedElementUtils.findMergedAnnotation(method, EventListener.class));

    ConfigurableApplicationContext context = this.applicationContext;
    Assert.state(context != null, "No ApplicationContext set");
    List<EventListenerFactory> factories = this.eventListenerFactories;
    Assert.state(factories != null, "EventListenerFactory List not initialized");
     for (Method method : annotatedMethods.keySet()) {//遍历所有被@EventListener注解的方法
      for (EventListenerFactory factory : factories) {
       if (factory.supportsMethod(method)) {//判断工厂类是否支持该方法
        Method methodToUse = AopUtils.selectInvocableMethod(method, context.getType(beanName));
                       //利用工厂生成一个ApplicationListener实例
        ApplicationListener<?> applicationListener =
          factory.createApplicationListener(beanName, targetType, methodToUse);
                       //将生成的ApplicationListener实例注册到Spring事件广播器上
        if (applicationListener instanceof ApplicationListenerMethodAdapter) {
         ((ApplicationListenerMethodAdapter) applicationListener).init(context, this.evaluator);
        }
        context.addApplicationListener(applicationListener);
        break;
       }
      }
     }
    }
}

大致逻辑就是:

  • 查找Class里有被@EventListener注解的方法,存储到Map<Method, EventListener> annotatedMethods中;
  • 然后遍历Map,对每个@EventListener注解方法,使用EventListenerFactory工厂模式创建一个ApplicationListener实例,默认这里是ApplicationListenerMethodAdapter类型;
  • 最后使用context.addApplicationListener向Spring注册事件监听器;

所以,当Spring中触发事件时,会调用ApplicationListenerMethodAdapter#onApplicationEvent()方法,而该方法内部通过反射最终可以调用到@EventListener注解方法,因为ApplicationListenerMethodAdapter内部持有@EventListener注解方法对应的Method,以及该方法所处Bean的name信息。这样,就间接实现了将@EventListener方法包装成了ApplicationListener对象。

还比如,Spring Cloud Ribbon组件中,LoadBalancerAutoConfiguration自动装配类中,就向Spring中注入了一个SmartInitializingSingleton实现类,利用该实现类,当IoC容器要启动完成时,将所有带有@LoadBalanced注解的RestTemplate对象利用RestTemplateCustomizer进行定制处理: 

@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();

@Autowired(required = false)
private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();

@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);
   }
  }
 });
}

 而常见的处理就是,给RestTemplate添加一个LoadBalancerInterceptor类型的拦截器: 

@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
  final LoadBalancerInterceptor loadBalancerInterceptor) {
 return restTemplate -> {
  List<ClientHttpRequestInterceptor> list = new ArrayList<>(
    restTemplate.getInterceptors());
  list.add(loadBalancerInterceptor);
  restTemplate.setInterceptors(list);
 };
}

该拦截器会拦截RestTemplate请求,从url地址中解析出需要请求的serviceId,当该serviceId对应多台主机时,Ribbon就会根据一定策略指定其中一台,这样就实现负载均衡功能。

public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
  final ClientHttpRequestExecution execution) throws IOException {
 final URI originalUri = request.getURI();
 String serviceName = originalUri.getHost();
 Assert.state(serviceName != null,
   "Request URI does not contain a valid hostname: " + originalUri);
 return this.loadBalancer.execute(serviceName,
   this.requestFactory.createRequest(request, body, execution));
}

 

标签:Spring,beanName,EventListener,接口,SmartInitializingSingleton,注解,final
From: https://www.cnblogs.com/xfeiyun/p/16910558.html

相关文章

  • postman接口测试工具汉化教程
    1.汉化包地址 https://github.com/hlmd/Postman-cn/releases?page=1下载对应版本下的app.zip解压后放到postman安装路径下的版本号目录下的resources文件夹中  2.......
  • Spring框架语言支持
    版本6.0.01.科特林Kotlin是一种面向JVM的静态类型语言。(和其他平台),允许编写简洁优雅的代码,同时提供与用Java编写的现有库具有非常好的互操作性。Spring框架为Kotl......
  • Spring整合Mybatis(注解版)
    Spring整合Mybatis入门(注解版)步骤一:创建maven项目在pom.xml文件中导入以下坐标<?xmlversion="1.0"encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/......
  • 面试官:谈谈 Spring Cloud 与 Dubbo 有什么区别?
    作者:IsToRestartblog.csdn.net/weixin_51291483/article/details/1092121371、SpringCloud与Dubbo的区别两者都是现在主流的微服务框架,但却存在不少差异:初始定位不同......
  • Spring Security(1)
    您好,我是湘王,这是我的博客园,欢迎您来,欢迎您再来~ 虽然说互联网是一个非常开发、几乎没有边界的信息大海,但说起来有点奇怪的是,每个稍微有点规模的互联网应用都有自己的权......
  • 第2-3-8章 分片上传和分片合并的接口开发-文件存储服务系统-nginx/fastDFS/minio/阿里
    目录5.10接口开发-分片上传5.10.1分片上传介绍5.10.2前端分片上传插件webuploader5.10.3后端代码实现5.10.3.1接口文档5.10.3.2代码开发5.10.3.3接口测试5.11接口......
  • Spring - AOP
    目前SpringAOP一共有三种配置方式,Spring做到了很好的向下兼容。Spring1.2基于接口的配置,最早的SpringAOP是完全基于几个接口的。Spring2.0schema-based配置:Spr......
  • 003.Spring案例
    1.创建实体类packagecom.imooc.entity;publicclassApple{privateStringtitle;privateStringcolor;privateStringorigin;publicApple(......
  • Java高手请进:关于接口的问题。
    《鲁提辖剃度》和尚要做什么呢,要吃斋(chiZai())、念经(nianJing())、打坐(daZuo())、撞钟(zhuangZhong())、习武(xiWu())等。如果设计一个和尚(Monk)接口,给出所有和尚都需要实现的方法,那么这个接......
  • Springboot整合Swagger(二)
    1、创建Springboot项目2、引入swagger依赖<!--SpringBoot-starter--><dependency><groupId>org.springframework.boot</groupId>......