Spring奇技淫巧之扩展点的应用!

标签: spring 扩展 应用 | 发表时间:2022-04-19 13:49 | 作者:程序员段飞
出处:https://juejin.im/backend?sort=monthly_hottest

前言

文章首发在公众号(月伴飞鱼),之后同步到个人网站: xiaoflyfish.cn/

觉得有收获,希望帮忙点赞,转发下哈,谢谢,谢谢

最近在看公司项目和中间件的时候,看到一些Spring扩展点的使用,写篇文章学习下,对大家之后看源码都有帮助

「首先先介绍下Bean的生命周期」

我们知道Bean的生命周期分为几个主干流程

  • Bean(单例非懒加载)的实例化阶段
  • Bean的属性注入阶段
  • Bean的初始化阶段
  • Bean的销毁阶段

下面是整个Spring容器的启动流程,可以看到除了上述几个主干流程外,Spring还提供了很多扩展点

下面详细介绍下Spring的常见的扩展点

Spring常见扩展点

「BeanFactoryPostProcessor#postProcessBeanFactory」

有时候整个项目工程中bean的数量有上百个,而大部分单测依赖都是整个工程的xml,导致单测执行时需要很长时间(大部分时间耗费在xml中数百个单例非懒加载的bean的实例化及初始化过程)

解决方法:利用Spring提供的扩展点将xml中的bean设置为懒加载模式,省去了Bean的实例化与初始化时间

  public class LazyBeanFactoryProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        DefaultListableBeanFactory fac = (DefaultListableBeanFactory) beanFactory;
        Map<String, AbstractBeanDefinition> map = (Map<String, AbstractBeanDefinition>) ReflectionTestUtils.getField(fac, "beanDefinitionMap");
        for (Map.Entry<String, AbstractBeanDefinition> entry : map.entrySet()) {
            //设置为懒加载
            entry.getValue().setLazyInit(true);
        }
    }
}

「InstantiationAwareBeanPostProcessor#postProcessPropertyValues」

非常规的配置项比如

  <context:component-scan base-package="com.zhou" />

Spring提供了与之对应的特殊解析器

正是通过这些特殊的解析器才使得对应的配置项能够生效

而针对这个特殊配置的解析器为 ComponentScanBeanDefinitionParser

在这个解析器的解析方法中,注册了很多特殊的Bean

  public BeanDefinition parse(Element element, ParserContext parserContext) {
  //...
  registerComponents(parserContext.getReaderContext(), beanDefinitions, element);
    //...
  return null;
}
public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
   BeanDefinitionRegistry registry, Object source) {

  Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<BeanDefinitionHolder>(4);
  //...
    //@Autowire
  if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
   RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);
   def.setSource(source);
   beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
  }

  // Check for JSR-250 support, and if present add the CommonAnnotationBeanPostProcessor.
   //@Resource
  if (jsr250Present && !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) {
      //特殊的Bean
   RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class);
   def.setSource(source);
   beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
  }
  //...
  return beanDefs;
 }

以@Resource为例,看看这个特殊的bean做了什么

  public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBeanPostProcessor
  implements InstantiationAwareBeanPostProcessor, BeanFactoryAware, Serializable {
     
      public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, 
      Object bean, String beanName) throws BeansException {
          InjectionMetadata metadata = findResourceMetadata(beanName, bean.getClass());
          try {
            //属性注入
            metadata.inject(bean, beanName, pvs);
          }
          catch (Throwable ex) {
            throw new BeanCreationException(beanName, "Injection of resource dependencies failed", ex);
          }
          return pvs;
    }
    
}

我们看到在postProcessPropertyValues方法中,进行了属性注入

「invokeAware」

实现BeanFactoryAware接口的类,会由容器执行setBeanFactory方法将当前的容器BeanFactory注入到类中

  @Bean
class BeanFactoryHolder implements BeanFactoryAware{
   
    private static BeanFactory beanFactory;
    
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }
}

「BeanPostProcessor#postProcessBeforeInitialization」

实现ApplicationContextAware接口的类,会由容器执行setApplicationContext方法将当前的容器applicationContext注入到类中

  @Bean
class ApplicationContextAwareProcessor implements BeanPostProcessor {

    private final ConfigurableApplicationContext applicationContext;

    public ApplicationContextAwareProcessor(ConfigurableApplicationContext applicationContext) {
      this.applicationContext = applicationContext;
    }

    @Override
    public Object postProcessBeforeInitialization(final Object bean, String beanName) throws BeansException {
      //...
      invokeAwareInterfaces(bean);
      return bean;
    }

    private void invokeAwareInterfaces(Object bean) {
        if (bean instanceof ApplicationContextAware) {
          ((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
        }
    }
}

我们看到是在BeanPostProcessor的postProcessBeforeInitialization中进行了setApplicationContext方法的调用

  class ApplicationContextHolder implements ApplicationContextAware{
   
    private static ApplicationContext applicationContext;
    
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

「afterPropertySet()和init-method」

目前很多Java中间件都是基本Spring Framework搭建的,而这些中间件经常把入口放到afterPropertySet或者自定义的init中

「BeanPostProcessor#postProcessAfterInitialization」

熟悉aop的同学应该知道,aop底层是通过动态代理实现的

当配置了 <aop:aspectj-autoproxy/>时候,默认开启aop功能,相应地调用方需要被aop织入的对象也需要替换为动态代理对象

不知道大家有没有思考过动态代理是如何**「在调用方无感知情况下替换原始对象」**的?

根据上文的讲解,我们知道:

  <aop:aspectj-autoproxy/>

Spring也提供了特殊的解析器,和其他的解析器类似,在核心的parse方法中注册了特殊的bean

这里是一个BeanPostProcessor类型的bean

  class AspectJAutoProxyBeanDefinitionParser implements BeanDefinitionParser {
 @Override
 public BeanDefinition parse(Element element, ParserContext parserContext) {
    //注册特殊的bean
  AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);
  extendBeanDefinition(element, parserContext);
  return null;
    }
}

将于当前bean对应的动态代理对象返回即可,该过程对调用方全部透明

  public class AnnotationAwareAspectJAutoProxyCreator extends AspectJAwareAdvisorAutoProxyCreator {
  public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean != null) {
          Object cacheKey = getCacheKey(bean.getClass(), beanName);
          if (!this.earlyProxyReferences.containsKey(cacheKey)) {
            //如果该类需要被代理,返回动态代理对象;反之,返回原对象
            return wrapIfNecessary(bean, beanName, cacheKey);
          }
        }
        return bean;
 }
}

正是利用Spring的这个扩展点实现了动态代理对象的替换

「destroy()和destroy-method」

bean生命周期的最后一个扩展点,该方法用于执行一些bean销毁前的准备工作,比如将当前bean持有的一些资源释放掉

最后

「写文章画图不易,喜欢的话,希望帮忙点赞,转发下哈,谢谢」

微信搜索:月伴飞鱼,交个朋友

参考书籍:

  • Spring技术内幕
  • Spring源码深度解析

相关 [spring 扩展 应用] 推荐:

Spring奇技淫巧之扩展点的应用!

- - 掘金后端本月最热
文章首发在公众号(月伴飞鱼),之后同步到个人网站: xiaoflyfish.cn/. 觉得有收获,希望帮忙点赞,转发下哈,谢谢,谢谢. 最近在看公司项目和中间件的时候,看到一些Spring扩展点的使用,写篇文章学习下,对大家之后看源码都有帮助. 「首先先介绍下Bean的生命周期」. 我们知道Bean的生命周期分为几个主干流程.

使用spring-boot快速开发spring应用

- - 企业架构 - ITeye博客
spring多年以来一直都是java平台开发web应用的主流技术,在标准的J2EE架构之外提供了一个轻量级的解决方案. 虽然spring提供了很多功能,简化了java平台的企业应用开发,降低了开发工作量,但相比较其它语言的一些框架(例如ruby on rails,python Django)来说,基于spring 的开发仍然比较复杂,尤其是新建一个项目时,需要进行各种配置,重复的工作量较大.

Spring AOP 实现原理与 CGLIB 应用

- - 博客 - 伯乐在线
来源: IBM Developerworks. 简介: AOP(Aspect Orient Programming),也就是面向方面编程,作为面向对象编程的一种补充,专门用于处理系统中分布于各个模块(不同方法)中的交叉关注点的问题,在 Java EE 应用中,常常通过 AOP 来处理一些具有横切性质的系统级服务,如事务管理、安全检查、缓存、对象池管理等.

spring+hibernate多数据源的应用

- - CSDN博客推荐文章
我有两个数据库test,和test1,两个库里都有一张表TEST_ONE. applicationContext.xml的配置如下. //数据库test1配置.   //整合两个数据源,指定数据源管理类.    //数据库test.    //数据库test1. //这个类是用来管理数据源的,配置文件中.

使用Spring跟踪应用异常(1)

- - ImportNew
几周以前,一个同事请我帮他一周的忙. 他想要享受一个难得的假期,但是找不到其他人帮他. 由于我刚完成一个特别复杂的编码工作,需要来点自我调节,所以就答应了他的请求. 那份工作的一部分是监控一些非常关键的后台运行进程,观察它们是否运行正常. 开发者花了很多时间和精力在应用中加入日志输出,以此表明应用正常运行.

Spring/Hibernate应用性能调优

- - ImportNew
对于大多数典型的Spring/Hibernate 企业应用来说,应用程序的性能几乎完全取决于它的持久层的性能. 这篇文章将会对如何确认在“数据库约束”的应用前,使用7种“快速见效”的技巧来帮助我们提升应用性能. 如何确认一个应用受到“数据库约束”. 为了验证一个应用程序是否受到“数据库约束”,首先在一些开发环境中做一些普遍的行为,即使用 VisualVM来监控.

Spring / Hibernate应用性能调优

- - ImportNew
对大部分典型的Spring/Hibernate企业应用来说,应用的性能大部分由持久层的性能决定. 这篇文章会重温一下怎么去确认我们的应用是否是”数据库依赖(data-bound)”( 译者注:即非常依赖数据库,大量时间花在数据库操作上),然后会大概过一下7个常用的提升应用性能的速效方案. 怎么确定应用是否是“数据库依赖”.

spring boot应用启动原理分析

- - ImportNew
在spring boot里,很吸引人的一个特性是可以直接把应用打包成为一个jar/war,然后这个jar/war是可以直接启动的,不需要另外配置一个Web Server. 如果之前没有使用过spring boot可以通过下面的demo来感受下. 下面以这个工程为例,演示如何启动Spring boot项目:.

Spring MVC+JQuery+Google Map打造IP位置查找应用(1)

- - 企业架构 - ITeye博客
All examples are simple, easy to read, and full source code available, and of course well tested in our development environment.. 在本文中,读者将学习到如何使用Spring MVC框架和jQuery及Google Map,制作一个简单的根据IP位置查找应用.

在应用层通过spring解决数据库读写分离

- - CSDN博客推荐文章
如何配置mysql数据库的主从. 单机配置mysql主从: http://my.oschina.net/god/blog/496. 常见的解决数据库读写分离有两种方案. http://neoremind.net/2011/06/spring实现数据库读写分离. 目前的一些解决方案需要在程序中手动指定数据源,比较麻烦,后边我会通过AOP思想来解决这个问题.