521我发誓读完本文,再也不会担心Spring配置类问题了(中)

简介: 521我发誓读完本文,再也不会担心Spring配置类问题了(中)

做了何事


这里是具体拦截逻辑,会比第一个拦截器复杂很多。源码不算非常的多,但牵扯到的东西还真不少,比如AOP、比如Scope、比如Bean的创建等等,理解起来还蛮费劲的

本处以拦截到parent()方法的执行为例,结合源码进行跟踪讲解:



BeanMethodInterceptor:
// enhancedConfigInstance:被拦截的对象实例,也是代理对象
// beanMethod:parent()方法
// beanMethodArgs:空
// cglibMethodProxy:代理。用于调用其invoke/invokeSuper()来执行对应的方法
@Override
@Nullable
public Object intercept(Object enhancedConfigInstance, 
  Method beanMethod, Object[] beanMethodArgs, MethodProxy cglibMethodProxy) throws Throwable {
  // 通过反射,获取到Bean工厂。也就是$$beanFactory这个属性的值~
  ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance);
  // 拿到Bean的名称
  String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod);
  // 判断这个方法是否是Scoped代理对象 很明显本利里是没有标注的 暂先略过
  // 简答的说:parent()方法头上是否标注有@Scoped注解~~~
  if (BeanAnnotationHelper.isScopedProxy(beanMethod)) {
    String scopedBeanName = ScopedProxyCreator.getTargetBeanName(beanName);
    if (beanFactory.isCurrentlyInCreation(scopedBeanName)) {
      beanName = scopedBeanName;
    }
  }
  // ========下面要处理bean间方法引用的情况了========
  // 首先:检查所请求的Bean是否是FactoryBean。也就是bean名称为`&parent`的Bean是否存在
  // 如果是的话,就创建一个代理子类,拦截它的getObject()方法以返回容器里的实例
  // 这样做保证了方法返回一个FactoryBean和@Bean的语义是效果一样的,确保了不会重复创建多个Bean
  if (factoryContainsBean(beanFactory, BeanFactory.FACTORY_BEAN_PREFIX + beanName) &&
      factoryContainsBean(beanFactory, beanName)) {
    // 先得到这个工厂Bean
    Object factoryBean = beanFactory.getBean(BeanFactory.FACTORY_BEAN_PREFIX + beanName);
    if (factoryBean instanceof ScopedProxyFactoryBean) {
      // Scoped proxy factory beans are a special case and should not be further proxied
      // 如果工厂Bean已经是一个Scope代理Bean,则不需要再增强
      // 因为它已经能够满足FactoryBean延迟初始化Bean了~
    }
    // 继续增强
    else {
      return enhanceFactoryBean(factoryBean, beanMethod.getReturnType(), beanFactory, beanName);
    }
  }
  // 检查给定的方法是否与当前调用的容器相对应工厂方法。
  // 比较方法名称和参数列表来确定是否是同一个方法
  // 怎么理解这句话,参照下面详解吧
  if (isCurrentlyInvokedFactoryMethod(beanMethod)) {
    // 这是个小细节:若你@Bean返回的是BeanFactoryPostProcessor类型
    // 请你使用static静态方法,否则会打印这句日志的~~~~
    // 因为如果是非静态方法,部分后置处理失效处理不到你,可能对你程序有影像
    // 当然也可能没影响,所以官方也只是建议而已~~~
    if (logger.isInfoEnabled() &&
        BeanFactoryPostProcessor.class.isAssignableFrom(beanMethod.getReturnType())) {
      ... // 输出info日志
    }
    // 这表示:当前parent()方法,就是这个被拦截的方法,那就没啥好说的 
    // 相当于在代理代理类里执行了super(xxx);
    // 但是,但是,但是,此时的this依旧是代理类
    return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
  }
  // parent()方法里调用的son()方法会交给这里来执行
  return resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName);
}


步骤总结:


1.拿到当前BeanFactory工厂对象。该工厂对象通过第一个拦截器BeanFactoryAwareMethodInterceptor已经完成了设值


2.确定Bean名称。默认是方法名,若通过@Bean指定了以指定的为准,若指定了多个值以第一个值为准,后面的值当作Bean的alias别名


3.判断当前方法(以parent()方法为例)是否是个Scope域代理。也就是方法上是否标注有@Scope注解

  1. 若是域代理类,那旧以它的方式来处理喽。beanName的变化变化为scopedTarget.parent
  2. 判断scopedTarget.parent这个Bean是否正在创建中…若是的,那就把当前beanName替换为scopedTarget.parent,以后就关注这个名称的Bean了~
  3. 试想一下,如果不来这个判断的话,那最终可能的结果是:容器内一个名为parent的Bean,一个名字为scopedTarget.parent的Bean,那岂不又出问题了麽~


4.判断请求的Bean是否是个FactoryBean工厂Bean。

  1. 若是工厂Bean,那么就需要enhance增强这个Bean,以拦截它的getObject()方法
  2. 拦截getObject()的做法是:当执行getObject()方法时转为 -> getBean()方法
  3. 为什么需要这么做:是为了确保FactoryBean产生的实例是通过getBean()容器去获取的,而非又自己创建一个出来了
  4. 这种case先打个❓,下面会结合代码示例加以说明


5.判断这个beanMethod是否是当前正在被调用的工厂方法。

  1. 若是正在创建的方法,那就好说了,直接super(xxx)执行父类方法体完事~
  2. 若不是正在创建的方法,那就需要代理喽,以确保实际调用的仍旧是实际调用getBean方法而保证是同一个Bean
  3. 这种case先打个❓,下面会结合代码示例加以说明。因为这个case是最常见的主线case,所以先把它搞定


这是该拦截器的执行步骤,留下两个打❓下面我来一一解释(按照倒序)。


多次调用@Bean方法为何不会产生新实例?


这是最为常见的case。示例代码:


@Configuration
public class AppConfig {
    @Bean
    public Son son() {
        Son son = new Son();
        System.out.println("son created..." + son.hashCode());
        return son;
    }
    @Bean
    public Parent parent() {
        notBeanMethod();
        Son son = son();
        System.out.println("parent created...持有的Son是:" + son.hashCode());
        return new Parent(son);
    }
    public void notBeanMethod(){
        System.out.println("notBeanMethod invoked by 【" + this + "】");
    }
}


本配置类一共有三个方法:


  • son():标注有@Bean。


image.png


因此它最终交给cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);方法直接执行父类(也就是目标类)的方法体:


image.png


值得注意的是:此时所处的对象仍旧是代理对象内,这个方法体只是通过代理类调用了super(xxx)方法进来的而已嘛~


  • parent():标注有@Bean。它内部会还会调用notBeanMethod()和son()两个方法


同上,会走到目标类的方法体里,开始调用 notBeanMethod()和son() 这两个方法,这个时候处理的方式就不一样了:


  1. 调用notBeanMethod()方法,因为它没有标注@Bean注解,所以不会被拦截 -> 直接执行方法体
  2. 调用son()方法,因为它标注有@Bean注解,所以会继续进入到拦截器里。但请注意和上面 直接调用 son()方法不一样的是:此时当前正在被invoked的方法是parent()方法,而并非son()方法,所以他会被交给resolveBeanReference()方法来处理:
BeanMethodInterceptor:
private Object resolveBeanReference(Method beanMethod, Object[] beanMethodArgs,
    ConfigurableBeanFactory beanFactory, String beanName) {
  // 当前bean(son这个Bean)是否正在创建中... 本处为false嘛
  // 这个判断主要是为了防止后面getBean报错~~~
  boolean alreadyInCreation = beanFactory.isCurrentlyInCreation(beanName);
  try {
    // 如果该Bean确实正在创建中,先把它标记下,放置后面getBean报错~
    if (alreadyInCreation) {
      beanFactory.setCurrentlyInCreation(beanName, false);
    }
    // 更具该方法的入参,决定后面使用getBean(beanName)还是getBean(beanName,args)
    // 基本原则是:但凡只要有一个入参为null,就调用getBean(beanName)
    boolean useArgs = !ObjectUtils.isEmpty(beanMethodArgs);
    if (useArgs && beanFactory.isSingleton(beanName)) {
      for (Object arg : beanMethodArgs) {
        if (arg == null) {
          useArgs = false;
          break;
        }
      }
    }
    // 通过getBean从容器中拿到这个实例  本处拿出的就是Son实例喽
    Object beanInstance = (useArgs ? beanFactory.getBean(beanName, beanMethodArgs) : beanFactory.getBean(beanName));
    // 方法返回类型和Bean实际类型做个比较,因为有可能类型不一样
    // 什么时候会出现类型不一样呢?当BeanDefinition定义信息类型被覆盖的时候,就可能出现此现象
    if (!ClassUtils.isAssignableValue(beanMethod.getReturnType(), beanInstance)) {
      if (beanInstance.equals(null)) {
        beanInstance = null;
      } else {
        ...
        throw new IllegalStateException(msg);
      }
    }
    // 当前被调用的方法,是parent()方法
    Method currentlyInvoked = SimpleInstantiationStrategy.getCurrentlyInvokedFactoryMethod();
    if (currentlyInvoked != null) {
      String outerBeanName = BeanAnnotationHelper.determineBeanNameFor(currentlyInvoked);
      // 这一步是注册依赖关系,告诉容器:
      // parent实例的初始化依赖于son实例
      beanFactory.registerDependentBean(beanName, outerBeanName);
    }
    // 返回实例
    return beanInstance;
  }
  // 归还标记:笔记实际确实还在创建中嘛~~~~
  finally {
    if (alreadyInCreation) {
      beanFactory.setCurrentlyInCreation(beanName, true);
    }
  }
}


这么一来,执行完parent()方法体里的son()方法后,实际得到的是容器内的实例,从而保证了我们这么写是不会有问题的。


  • notBeanMethod():因为没有标注@Bean,所以它并不会被容器调用,而只能是被上面的parent()方法调用到,并且也不会被拦截(值得注意的是:因为此方法不需要被代理,所以此方法可以是private final的哦~)


以上程序的运行结果是:


son created...347978868
notBeanMethod invoked by 【com.yourbatman.fullliteconfig.config.AppConfig$$EnhancerBySpringCGLIB$$ec611337@12591ac8】
parent created...持有的Son是:347978868
com.yourbatman.fullliteconfig.config.AppConfig$$EnhancerBySpringCGLIB$$ec611337@12591ac8
容器内的Son实例:347978868
容器内Person持有的Son实例:347978868
true

可以看到,Son自始至终都只存在一个实例,这是符合我们的预期的。


Lite模式下表现如何?

同样的代码,在Lite模式下(去掉@Configuration注解即可),不存在“如此复杂”的代理逻辑,所以上例的运行结果是:


son created...624271064
notBeanMethod invoked by 【com.yourbatman.fullliteconfig.config.AppConfig@21a947fe】
son created...90205195
parent created...持有的Son是:90205195
com.yourbatman.fullliteconfig.config.AppConfig@21a947fe
容器内的Son实例:624271064
容器内Person持有的Son实例:90205195
false


这个结果很好理解,这里我就不再啰嗦了。总之就不能这么用就对了~

相关文章
|
2天前
|
Java Spring
【Spring】方法注解@Bean,配置类扫描路径
@Bean方法注解,如何在同一个类下面定义多个Bean对象,配置扫描路径
110 73
|
1月前
|
Java 开发者 微服务
手写模拟Spring Boot自动配置功能
【11月更文挑战第19天】随着微服务架构的兴起,Spring Boot作为一种快速开发框架,因其简化了Spring应用的初始搭建和开发过程,受到了广大开发者的青睐。自动配置作为Spring Boot的核心特性之一,大大减少了手动配置的工作量,提高了开发效率。
61 0
|
2天前
|
Java Spring
【Spring配置相关】启动类为Current File,如何更改
问题场景:当我们切换类的界面的时候,重新启动的按钮是灰色的,不能使用,并且只有一个Current File 项目,下面介绍两种方法来解决这个问题。
|
2天前
|
Java Spring
【Spring配置】idea编码格式导致注解汉字无法保存
问题一:对于同一个项目,我们在使用idea的过程中,使用汉字注解完后,再打开该项目,汉字变成乱码问题二:本来a项目中,汉字注解调试好了,没有乱码了,但是创建出来的新的项目,写的注解又成乱码了。
|
2天前
|
Java Spring
【Spring配置】创建yml文件和properties或yml文件没有绿叶
本文主要针对,一个项目中怎么创建yml和properties两种不同文件,进行配置,和启动类没有绿叶标识进行解决。
|
10天前
|
NoSQL Java Redis
Spring Boot 自动配置机制:从原理到自定义
Spring Boot 的自动配置机制通过 `spring.factories` 文件和 `@EnableAutoConfiguration` 注解,根据类路径中的依赖和条件注解自动配置所需的 Bean,大大简化了开发过程。本文深入探讨了自动配置的原理、条件化配置、自定义自动配置以及实际应用案例,帮助开发者更好地理解和利用这一强大特性。
55 14
|
8天前
|
XML Java 数据格式
Spring容器Bean之XML配置方式
通过对以上内容的掌握,开发人员可以灵活地使用Spring的XML配置方式来管理应用程序的Bean,提高代码的模块化和可维护性。
40 6
|
10天前
|
XML Java 数据格式
🌱 深入Spring的心脏:Bean配置的艺术与实践 🌟
本文深入探讨了Spring框架中Bean配置的奥秘,从基本概念到XML配置文件的使用,再到静态工厂方式实例化Bean的详细步骤,通过实际代码示例帮助读者更好地理解和应用Spring的Bean配置。希望对你的Spring开发之旅有所助益。
59 3
|
1月前
|
Java Spring
[Spring]aop的配置与使用
本文介绍了AOP(面向切面编程)的基本概念和核心思想。AOP是Spring框架的核心功能之一,通过动态代理在不修改原代码的情况下注入新功能。文章详细解释了连接点、切入点、通知、切面等关键概念,并列举了前置通知、后置通知、最终通知、异常通知和环绕通知五种通知类型。
40 1
|
7月前
|
消息中间件 SpringCloudAlibaba Java
【Springcloud Alibaba微服务分布式架构 | Spring Cloud】之学习笔记(八)Config服务配置+bus消息总线+stream消息驱动+Sleuth链路追踪
【Springcloud Alibaba微服务分布式架构 | Spring Cloud】之学习笔记(八)Config服务配置+bus消息总线+stream消息驱动+Sleuth链路追踪
1038 0