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


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

相关文章
|
15天前
|
Java API 数据库
构建RESTful API已经成为现代Web开发的标准做法之一。Spring Boot框架因其简洁的配置、快速的启动特性及丰富的功能集而备受开发者青睐。
【10月更文挑战第11天】本文介绍如何使用Spring Boot构建在线图书管理系统的RESTful API。通过创建Spring Boot项目,定义`Book`实体类、`BookRepository`接口和`BookService`服务类,最后实现`BookController`控制器来处理HTTP请求,展示了从基础环境搭建到API测试的完整过程。
31 4
|
12天前
|
Java API 数据库
Spring Boot框架因其简洁的配置、快速的启动特性及丰富的功能集而备受开发者青睐
本文通过在线图书管理系统案例,详细介绍如何使用Spring Boot构建RESTful API。从项目基础环境搭建、实体类与数据访问层定义,到业务逻辑实现和控制器编写,逐步展示了Spring Boot的简洁配置和强大功能。最后,通过Postman测试API,并介绍了如何添加安全性和异常处理,确保API的稳定性和安全性。
27 0
|
5天前
|
Java API Spring
在 Spring 配置文件中配置 Filter 的步骤
【10月更文挑战第21天】在 Spring 配置文件中配置 Filter 是实现请求过滤的重要手段。通过合理的配置,可以灵活地对请求进行处理,满足各种应用需求。还可以根据具体的项目要求和实际情况,进一步深入研究和优化 Filter 的配置,以提高应用的性能和安全性。
|
13天前
|
Java BI 调度
Java Spring的定时任务的配置和使用
遵循上述步骤,你就可以在Spring应用中轻松地配置和使用定时任务,满足各种定时处理需求。
94 1
|
2月前
|
XML Java 数据格式
Spring IOC—基于XML配置Bean的更多内容和细节(通俗易懂)
Spring 第二节内容补充 关于Bean配置的更多内容和细节 万字详解!
183 18
Spring IOC—基于XML配置Bean的更多内容和细节(通俗易懂)
|
2月前
|
前端开发 Java Spring
关于spring mvc 的 addPathPatterns 拦截配置常见问题
关于spring mvc 的 addPathPatterns 拦截配置常见问题
178 1
|
19天前
|
XML Java 数据格式
手动开发-简单的Spring基于注解配置的程序--源码解析
手动开发-简单的Spring基于注解配置的程序--源码解析
35 0
|
19天前
|
XML Java 数据格式
手动开发-简单的Spring基于XML配置的程序--源码解析
手动开发-简单的Spring基于XML配置的程序--源码解析
61 0
|
25天前
|
负载均衡 Java API
【Spring Cloud生态】Spring Cloud Gateway基本配置
【Spring Cloud生态】Spring Cloud Gateway基本配置
31 0
|
2月前
|
Java 数据库连接 Maven
Spring基础1——Spring(配置开发版),IOC和DI
spring介绍、入门案例、控制反转IOC、IOC容器、Bean、依赖注入DI
Spring基础1——Spring(配置开发版),IOC和DI