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 Cloud Gateway 全解析:路由配置、断言规则与过滤器实战指南
本文详细介绍了 Spring Cloud Gateway 的核心功能与实践配置。首先讲解了网关模块的创建流程,包括依赖引入(gateway、nacos 服务发现、负载均衡)、端口与服务发现配置,以及路由规则的设置(需注意路径前缀重复与优先级 order)。接着深入解析路由断言,涵盖 After、Before、Path 等 12 种内置断言的参数、作用及配置示例,并说明了自定义断言的实现方法。随后重点阐述过滤器机制,区分路由过滤器(如 AddRequestHeader、RewritePath、RequestRateLimiter 等)与全局过滤器的作用范围与配置方式,提
Spring Cloud Gateway 全解析:路由配置、断言规则与过滤器实战指南
|
2月前
|
Java 关系型数据库 MySQL
Spring Boot自动配置:魔法背后的秘密
Spring Boot 自动配置揭秘:只需简单配置即可启动项目,背后依赖“约定大于配置”与条件化装配。核心在于 `@EnableAutoConfiguration` 注解与 `@Conditional` 系列条件判断,通过 `spring.factories` 或 `AutoConfiguration.imports` 加载配置类,实现按需自动装配 Bean。
|
2月前
|
人工智能 Java 开发者
【Spring】原理解析:Spring Boot 自动配置
Spring Boot通过“约定优于配置”的设计理念,自动检测项目依赖并根据这些依赖自动装配相应的Bean,从而解放开发者从繁琐的配置工作中解脱出来,专注于业务逻辑实现。
|
4月前
|
Java Spring
Spring Boot配置的优先级?
在Spring Boot项目中,配置可通过配置文件和外部配置实现。支持的配置文件包括application.properties、application.yml和application.yaml,优先级依次降低。外部配置常用方式有Java系统属性(如-Dserver.port=9001)和命令行参数(如--server.port=10010),其中命令行参数优先级高于系统属性。整体优先级顺序为:命令行参数 > Java系统属性 > application.properties > application.yml > application.yaml。
926 0
|
1月前
|
前端开发 Java 应用服务中间件
《深入理解Spring》 Spring Boot——约定优于配置的革命者
Spring Boot基于“约定优于配置”理念,通过自动配置、起步依赖、嵌入式容器和Actuator四大特性,简化Spring应用的开发与部署,提升效率,降低门槛,成为现代Java开发的事实标准。
|
2月前
|
缓存 Java 应用服务中间件
Spring Boot配置优化:Tomcat+数据库+缓存+日志,全场景教程
本文详解Spring Boot十大核心配置优化技巧,涵盖Tomcat连接池、数据库连接池、Jackson时区、日志管理、缓存策略、异步线程池等关键配置,结合代码示例与通俗解释,助你轻松掌握高并发场景下的性能调优方法,适用于实际项目落地。
517 5
|
2月前
|
传感器 Java 数据库
探索Spring Boot的@Conditional注解的上下文配置
Spring Boot 的 `@Conditional` 注解可根据不同条件动态控制 Bean 的加载,提升应用的灵活性与可配置性。本文深入解析其用法与优势,并结合实例展示如何通过自定义条件类实现环境适配的智能配置。
170 0
探索Spring Boot的@Conditional注解的上下文配置
|
3月前
|
安全 算法 Java
在Spring Boot中应用Jasypt以加密配置信息。
通过以上步骤,可以在Spring Boot应用中有效地利用Jasypt对配置信息进行加密,这样即使配置文件被泄露,其中的敏感信息也不会直接暴露给攻击者。这是一种在不牺牲操作复杂度的情况下提升应用安全性的简便方法。
960 10
|
4月前
|
人工智能 安全 Java
Spring Boot yml 配置敏感信息加密
本文介绍了如何在 Spring Boot 项目中使用 Jasypt 实现配置文件加密,包含添加依赖、配置密钥、生成加密值、在配置中使用加密值及验证步骤,并提供了注意事项,确保敏感信息的安全管理。
1103 1
|
4月前
|
SQL XML Java
配置Spring框架以连接SQL Server数据库
最后,需要集成Spring配置到应用中,这通常在 `main`方法或者Spring Boot的应用配置类中通过加载XML配置或使用注解来实现。
425 0