你自我介绍说很懂Spring配置类,那你怎么解释这个现象?(中)

简介: 你自我介绍说很懂Spring配置类,那你怎么解释这个现象?(中)

postProcessBeanFactory()


此方法的作用用一句话可概括为:为Full模式的Bean使用CGLIB做字节码提升,确保最终生成的是代理类实例放进容器内


ConfigurationClassPostProcessor:
  @Override
  public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
    int factoryId = System.identityHashCode(beanFactory);
    // 防止重复处理
    if (this.factoriesPostProcessed.contains(factoryId)) {
      throw new IllegalStateException("postProcessBeanFactory already called on this post-processor against " + beanFactory);
    }
    this.factoriesPostProcessed.add(factoryId);
    // 在执行postProcessBeanDefinitionRegistry方法的时就已经将
    // 这个id添加到registriesPostProcessed集合中了
    // 所以到这里就不会再重复执行配置类的解析了(解析@Import、@Bean等)
    if (!this.registriesPostProcessed.contains(factoryId)) {
      processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
    }
    // 从名字上看,这个方法应该就是为配置类创建代理用的喽
    enhanceConfigurationClasses(beanFactory);
    // 添加了一个后置处理器 它是个SmartInstantiationAwareBeanPostProcessor
    // 它不是本文重点,略
    beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
  }


达到这一步之前,已经完成了bd的收集和标记(见上一步)。对bd进行实例化之前,针对于Full模式的配置类这步骤里会做增强处理,那就是enhanceConfigurationClasses(beanFactory)这个方法。


enhanceConfigurationClasses(beanFactory)


对一个BeanFactory进行增强,先查找配置类BeanDefinition,再根据Bean定义信息(元数据信息)来决定配置类是否应该被ConfigurationClassEnhancer增强。具体处理代码如下:


ConfigurationClassPostProcessor:
  public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
    // 最终需要做增强的Bean定义们
    Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>();
    for (String beanName : beanFactory.getBeanDefinitionNames()) {
      BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
      Object configClassAttr = beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE);
      ... // 省略其它情况以及异常情况的处理代码
      // 如果是Full模式,才会放进来
      if (ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(configClassAttr)) {
        configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
      }
    }
    if (configBeanDefs.isEmpty()) {
      // nothing to enhance -> return immediately
      return;
    }
    // ConfigurationClassEnhancer就是对配置类做增强操作的核心类,下面详解
    ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
    // 对每个Full模式的配置类,一个个做enhance()增强处理
    for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
      AbstractBeanDefinition beanDef = entry.getValue();
      // 如果代理了@Configuration类,则始终代理目标类
      // 该属性和自动代理时是相关的,具体参见Spring的自动代理章节描述
      beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
      // CGLIB是给父类生成子类对象的方式实现代理,所以这里指定“父类”类型
      Class<?> configClass = beanDef.getBeanClass();
      // 做增强处理,返回enhancedClass就是一个增强过的子类
      Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
      // 不相等,证明代理成功,那就把实际类型设置进去
      // 这样后面实例化配置类的实例时,实际实例化的就是增强子类喽
      if (configClass != enhancedClass) {
        beanDef.setBeanClass(enhancedClass);
      }
    }
  }


值得注意的是,虽然此方法被设计为public的,但是只被一处使用到。Spring这么做是为了给提供钩子,方便容器开发者做扩展时使用


步骤总结:


  1. 从BeanFactory拿出所有的bd信息,一个个判断
  2. 如果是配置类并且是Full模式,就先存储起来,后面会对它做字节码提升。最终如果一个Full模式的配置类都木有,那直接return,此方法结束。否则继续
  3. 对收集到的每一个 Full模式的配置类,使用ConfigurationClassEnhancer增强器进行字节码提升,生成一个CGLIB子类型
  4. 小细节:此处显示标注了AOP自动代理为:始终代理目标类
  5. 把CGLIB生成的子类型设置到元数据里去:beanDef.setBeanClass(enhancedClass)。这样Spring在最后实例化Bean时,实际生成的是该代理类型的实例,从而达到代理/增强的目的


该方法执行完成后,执行“结果”我截了张图,供以参考:


image.png



这段代码是不难的,理解起来十分简单。但是,我们仍旧还只知道结果,并不清楚原因。凭它还无法解释上文中两个case的现象,所以我们应该端正态度继续深入,看看ConfigurationClassEnhancer增强器到底做了什么事。


在介绍ConfigurationClassEnhancer之前,希望你对CGLIB的使用有那么一定的了解,这样会轻松很多。当然不必过于深究(否则容易怀疑人生),但应该能知道如何使用Enhancer增强器去增强/代理目标类,如何写拦截器等。


因为之前文章介绍过了CGLIB的基本使用,限于篇幅,此处就不再啰嗦。


ConfigurationClassEnhancer源码分析


得打起精神了,因为接下来才是本文之精华,让你出彩的地方。

@since 3.0。通过生成一个CGLIB子类来增强@Configuration类与Spring容器进行交互,每个这样的@Bean方法都会被生成的子类所复写。这样子当遇到方法调用时,才有可能通过拦截从而把方法调用引回容器,通过名称获得相应的Bean。


建立在对CGLIB的使用有一定了解的基础上,再来阅读本文会变得轻松许多。该类有且仅有一个 public方法,如下所示:


image.png


ConfigurationClassEnhancer:
  public Class<?> enhance(Class<?> configClass, @Nullable ClassLoader classLoader) {
    // 如果已经实现了该接口,证明已经被代理过了,直接返回呗~
    if (EnhancedConfiguration.class.isAssignableFrom(configClass)) {
      return configClass;
    }
    // 若没被代理过。就先调用newEnhancer()方法创建一个增强器Enhancer
    // 然后在使用这个增强器,生成代理类字节码Class对象
    Class<?> enhancedClass = createClass(newEnhancer(configClass, classLoader));
    return enhancedClass;
  }


巨简单有没有。


为何Spring的源码是开源软件的范本?因为它各种封装、设计模式用得都非常好,甚至对初学者都是友好的,所以说Spring易学难精。

该public方法的核心,在下面这两个个私有方法上。



newEnhancer()和createClass()

创建一个新的CGLIB Enhancer实例,并且做好相应配置。


ConfigurationClassEnhancer:
  private Enhancer newEnhancer(Class<?> configSuperClass, @Nullable ClassLoader classLoader) {
    Enhancer enhancer = new Enhancer();
    // 目标类型:会以这个作为父类型来生成字节码子类
    enhancer.setSuperclass(configSuperClass);
    // 让代理类实现EnhancedConfiguration接口,这个接口继承了BeanFactoryAware接口
    enhancer.setInterfaces(new Class<?>[] {EnhancedConfiguration.class});
    // 设置生成的代理类不实现org.springframework.cglib.proxy.Factory接口
    enhancer.setUseFactory(false);
    // 设置代理类名称的生成策略:Spring定义的一个生成策略
    // 你名称中会有“BySpringCGLIB”字样
    enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
    enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));
    // 设置拦截器/过滤器
    enhancer.setCallbackFilter(CALLBACK_FILTER);
    enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes());
    return enhancer;
  }
  // 使用增强器生,成代理类的字节码对象
  private Class<?> createClass(Enhancer enhancer) {
    Class<?> subclass = enhancer.createClass();
    Enhancer.registerStaticCallbacks(subclass, CALLBACKS);
    return subclass;
  }


Enhancer是CGLIB的最核心API,通过方法名应该基本清楚了每一步都有什么作用吧。


Enhancer属于CGLIB的核心API,但你发现它的包名是xxx.springframework.xxx 。这是因为CGLIB在Spring内太常用了(强依赖),因此Spring索性就自己fork了一份代码过来~

相关文章
|
27天前
|
Java Spring
【Spring】方法注解@Bean,配置类扫描路径
@Bean方法注解,如何在同一个类下面定义多个Bean对象,配置扫描路径
170 73
|
1天前
|
监控 Java 数据库连接
Spring c3p0配置详解
在Spring项目中配置C3P0数据源,可以显著提高数据库连接的效率和应用程序的性能。通过合理的配置和优化,可以充分发挥C3P0的优势,满足不同应用场景的需求。希望本文的详解和示例代码能为开发者提供清晰的指导,帮助实现高效的数据库连接管理。
25 10
|
27天前
|
Java Spring
【Spring配置相关】启动类为Current File,如何更改
问题场景:当我们切换类的界面的时候,重新启动的按钮是灰色的,不能使用,并且只有一个Current File 项目,下面介绍两种方法来解决这个问题。
|
27天前
|
Java Spring
【Spring配置】idea编码格式导致注解汉字无法保存
问题一:对于同一个项目,我们在使用idea的过程中,使用汉字注解完后,再打开该项目,汉字变成乱码问题二:本来a项目中,汉字注解调试好了,没有乱码了,但是创建出来的新的项目,写的注解又成乱码了。
|
27天前
|
Java Spring
【Spring配置】创建yml文件和properties或yml文件没有绿叶
本文主要针对,一个项目中怎么创建yml和properties两种不同文件,进行配置,和启动类没有绿叶标识进行解决。
|
1月前
|
XML Java 数据格式
Spring容器Bean之XML配置方式
通过对以上内容的掌握,开发人员可以灵活地使用Spring的XML配置方式来管理应用程序的Bean,提高代码的模块化和可维护性。
66 6
|
8月前
|
消息中间件 SpringCloudAlibaba Java
【Springcloud Alibaba微服务分布式架构 | Spring Cloud】之学习笔记(八)Config服务配置+bus消息总线+stream消息驱动+Sleuth链路追踪
【Springcloud Alibaba微服务分布式架构 | Spring Cloud】之学习笔记(八)Config服务配置+bus消息总线+stream消息驱动+Sleuth链路追踪
1068 0
|
XML Java 数据库连接
【Spring学习笔记 五】Spring注解及Java类配置开发
【Spring学习笔记 五】Spring注解及Java类配置开发
106 0
|
XML Java 数据格式
Spring学习笔记:02 spring配置
Spring学习笔记:02 spring配置
|
Java 数据库连接 Spring
spring学习笔记(22)声明式事务配置,readOnly无效写无异常
<div class="markdown_views"> <p>在上一节内容中,我们使用了编程式方法来配置事务,这样的优点是我们对每个方法的控制性很强,比如我需要用到什么事务,在什么位置如果出现异常需要回滚等,可以进行非常细粒度的配置。但在实际开发中,我们可能并不需要这样细粒度的配置。另一方面,如果我们的项目很大,service层方法很多,单独为每个方法配置事务也是一件很繁琐的
1690 0