你自我介绍说很懂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了一份代码过来~

相关文章
|
5天前
|
存储 Java 数据安全/隐私保护
|
2天前
|
Java 开发者 Spring
Spring Boot中的资源文件属性配置
【4月更文挑战第28天】在Spring Boot应用程序中,配置文件是管理应用程序行为的重要组成部分。资源文件属性配置允许开发者在不重新编译代码的情况下,对应用程序进行灵活地配置和调整。本篇博客将介绍Spring Boot中资源文件属性配置的基本概念,并通过实际示例展示如何利用这一功能。
10 1
|
11天前
|
存储 安全 Java
第2章 Spring Security 的环境设置与基础配置(2024 最新版)(下)
第2章 Spring Security 的环境设置与基础配置(2024 最新版)(下)
18 0
|
11天前
|
安全 Java 数据库
第2章 Spring Security 的环境设置与基础配置(2024 最新版)(上)
第2章 Spring Security 的环境设置与基础配置(2024 最新版)
35 0
|
12天前
|
XML Java 数据库
在Spring框架中,XML配置事务
在Spring框架中,XML配置事务
|
12天前
|
Java Windows Spring
Spring Boot 3.x 全新的热部署配置方式(IntelliJ IDEA 2023.1)
Spring Boot 3.x 全新的热部署配置方式(IntelliJ IDEA 2023.1)
15 1
|
12天前
|
安全 Java Spring
Spring Security 5.7 最新配置细节(直接就能用),WebSecurityConfigurerAdapter 已废弃
Spring Security 5.7 最新配置细节(直接就能用),WebSecurityConfigurerAdapter 已废弃
30 0
|
XML 前端开发 Java
Spring 最常用的 7 大类注解,史上最强整理!
随着技术的更新迭代,Java5.0开始支持注解。而作为java中的领军框架spring,自从更新了2.5版本之后也开始慢慢舍弃xml配置,更多使用注解来控制spring框架。
|
2月前
|
Java 应用服务中间件 Maven
SpringBoot 项目瘦身指南
SpringBoot 项目瘦身指南
46 0
|
2月前
|
缓存 安全 Java
Spring Boot 面试题及答案整理,最新面试题
Spring Boot 面试题及答案整理,最新面试题
121 0