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

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

版本约定


本文内容若没做特殊说明,均基于以下版本:


  • JDK:1.8
  • Spring Framework:5.2.2.RELEASE



正文


上篇文章介绍了代理对象两个拦截器其中的前者,即BeanFactoryAwareMethodInterceptor,它会拦截setBeanFactory()方法从而完成给代理类指定属性赋值。通过第一个拦截器的讲解,你能够成功“忽悠”很多面试官了,但仍旧不能够解释我们最常使用中的这个疑惑:为何通过调用@Bean方法最终指向的仍旧是同一个Bean呢?


带着这个疑问,开始本文的陈诉。请系好安全带,准备发车了…


Spring配置类的使用误区


根据不同的配置方式,展示不同情况。从Lite模式的使用产生误区,到使用Full模式解决问题,最后引出解释为何有此效果的原因分析/源码解析。


Lite模式:错误姿势


配置类:


public class AppConfig {
    @Bean
    public Son son() {
        Son son = new Son();
        System.out.println("son created..." + son.hashCode());
        return son;
    }
    @Bean
    public Parent parent() {
        Son son = son();
        System.out.println("parent created...持有的Son是:" + son.hashCode());
        return new Parent(son);
    }
}

运行程序:


public static void main(String[] args) {
    ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
    AppConfig appConfig = context.getBean(AppConfig.class);
    System.out.println(appConfig);
    // bean情况
    Son son = context.getBean(Son.class);
    Parent parent = context.getBean(Parent.class);
    System.out.println("容器内的Son实例:" + son.hashCode());
    System.out.println("容器内Person持有的Son实例:" + parent.getSon().hashCode());
    System.out.println(parent.getSon() == son);
}


运行结果:

son created...624271064
son created...564742142
parent created...持有的Son是:564742142
com.yourbatman.fullliteconfig.config.AppConfig@1a38c59b
容器内的Son实例:624271064
容器内Person持有的Son实例:564742142
false


结果分析:


  • Son实例被创建了2次。很明显这两个不是同一个实例
  • 第一次是由Spring创建并放进容器里(624271064这个)
  • 第二次是由构造parent时创建,只放进了parent里,并没放进容器里(564742142这个)


这样的话,就出问题了。问题表现在这两个方面:


  1. Son对象被创建了两次,单例模式被打破
  2. 对Parent实例而言,它依赖的Son不再是IoC容器内的那个Bean,而是一个非常普通的POJO对象而已。所以这个Son对象将不会享有Spring带来的任何“好处”,这在实际场景中一般都是会有问题的


这种情况在生产上是一定需要避免,那怎么破呢?下面给出Lite模式下使用的正确姿势。


Lite模式:正确姿势

其实这个问题,现在这么智能的IDE(如IDEA)已经能教你怎么做了:


image.png


按照“指示”,可以使用依赖注入的方式代替从而避免这种问题,如下:


// @Bean
// public Parent parent() {
//     Son son = son();
//     System.out.println("parent created...持有的Son是:" + son.hashCode());
//     return new Parent(son);
// }
@Bean
public Parent parent(Son son){
    System.out.println("parent created...持有的Son是:" + son.hashCode());
    return new Parent(son);
}


再次运行程序,结果为:

son created...624271064
parent created...持有的Son是:624271064
com.yourbatman.fullliteconfig.config.AppConfig@667a738
容器内的Son实例:624271064
容器内Person持有的Son实例:624271064
true



Full模式:

Full模式是容错性最强的一种方式,你乱造都行,没啥顾虑。

当然喽,方法不能是private/final。但一般情况下谁会在配置里final掉一个方法呢?你说对吧~

@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() {
        Son son = son();
        System.out.println("parent created...持有的Son是:" + son.hashCode());
        return new Parent(son);
    }
}



运行程序,结果输出:


son created...1797712197
parent created...持有的Son是:1797712197
com.yourbatman.fullliteconfig.config.AppConfig$$EnhancerBySpringCGLIB$$8ef51461@be64738
容器内的Son实例:1797712197
容器内Person持有的Son实例:1797712197
true


结果是完美的。它能够保证你通过调用标注有@Bean的方法得到的是IoC容器里面的实例对象,而非重新创建一个。相比较于Lite模式,它还有另外一个区别:它会为配置类生成一个CGLIB的代理子类对象放进容器,而Lite模式放进容器的是原生对象。


凡事皆有代价,一切皆在取舍。原生的才是效率最高的,是对Cloud Native最为友好的方式。但在实际“推荐使用”上,业务端开发一般只会使用Full模式,毕竟业务开发的同学水平是残参差不齐的,容错性就显得至关重要了。


如果你是容器开发者、中间件开发者…推荐使用Lite模式配置,为容器化、Cloud Native做好准备嘛~


Full模式既然是面向使用侧为常用的方式,那么接下来就趴一趴Spring到底是施了什么“魔法”,让调用@Bean方法竟然可以不进入方法体内而指向同一个实例。

BeanMethodInterceptor拦截器


终于到了今天的主菜。关于前面的流程分析本文就一步跳过,单刀直入分析BeanMethodInterceptor这个拦截器,也也就是所谓的两个拦截器的后者。


温馨提示:亲务必确保已经了解过了上篇文章的流程分析哈,不然下面内容很容易造成你脑力不适的


相较于上个拦截器,这个拦截器不可为不复杂。官方解释它的作用为:拦截任何标注有@Bean注解的方法的调用,以确保正确处理Bean语义,例如作用域(请别忽略它)和AOP代理。


复杂归复杂,但没啥好怕的,一步一步来呗。同样的,我会按如下两步去了解它:执行时机 + 做了何事。


执行时机

废话不多说,直接结合源码解释。


BeanMethodInterceptor:
@Override
public boolean isMatch(Method candidateMethod) {
  return (candidateMethod.getDeclaringClass() != Object.class &&
      !BeanFactoryAwareMethodInterceptor.isSetBeanFactory(candidateMethod) &&
      BeanAnnotationHelper.isBeanAnnotated(candidateMethod));
}



三行代码,三个条件:


  1. 该方法不能是Object的方法(即使你Object的方法标注了@Bean,我也不认)
  2. 不能是setBeanFactory()方法。这很容易理解,它交给上个拦截器搞定即可
  3. 方法必须标注标注有@Bean注解


简而言之,标注有@Bean注解方法执行时会被拦截。


所以下面例子中的son()和parent()这两个,以及parent()里面调用的son()方法的执行它都会拦截(一共拦截3次)~


小细节:方法只要是个Method即可,无论是static方法还是普通方法,都会“参与”此判断逻辑哦


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