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方法还是普通方法,都会“参与”此判断逻辑哦


相关文章
|
6天前
|
存储 Java 数据安全/隐私保护
|
6天前
|
安全 Java 开发者
深入理解Spring Boot配置绑定及其实战应用
【4月更文挑战第10天】本文详细探讨了Spring Boot中配置绑定的核心概念,并结合实战示例,展示了如何在项目中有效地使用这些技术来管理和绑定配置属性。
16 1
|
6天前
|
XML Java 数据格式
Spring高手之路18——从XML配置角度理解Spring AOP
本文是全面解析面向切面编程的实践指南。通过深入讲解切面、连接点、通知等关键概念,以及通过XML配置实现Spring AOP的步骤。
23 6
Spring高手之路18——从XML配置角度理解Spring AOP
|
6天前
|
消息中间件 开发框架 Java
什么是Spring Boot 自动配置?
Spring Boot 是一个流行的 Java 开发框架,它提供了许多便利的功能和工具,帮助开发者快速构建应用程序。其中一个最引人注目的特性是其强大的自动配置功能。
11 0
|
6天前
|
Java Spring
Spring文件配置以及获取
Spring文件配置以及获取
14 0
|
6天前
|
Java 微服务 Spring
Spring Boot中获取配置参数的几种方法
Spring Boot中获取配置参数的几种方法
22 2
|
6天前
|
消息中间件 安全 Java
在Spring Bean中,如何通过Java配置类定义Bean?
【4月更文挑战第30天】在Spring Bean中,如何通过Java配置类定义Bean?
22 1
|
6天前
|
Java 开发者 Spring
Spring Boot中的资源文件属性配置
【4月更文挑战第28天】在Spring Boot应用程序中,配置文件是管理应用程序行为的重要组成部分。资源文件属性配置允许开发者在不重新编译代码的情况下,对应用程序进行灵活地配置和调整。本篇博客将介绍Spring Boot中资源文件属性配置的基本概念,并通过实际示例展示如何利用这一功能。
27 1
|
6天前
|
安全 Java 测试技术
Spring Boot 自动化单元测试类的编写过程
企业开发不仅要保障业务层与数据层的功能安全有效,也要保障表现层的功能正常。但是我们一般对表现层的测试都是通过postman手工测试的,并没有在打包过程中代码体现表现层功能被测试通过。那么能否在测试用例中对表现层进行功能测试呢?答案是可以的,我们可以使用MockMvc来实现它。
52 0
|
6天前
|
Java Spring 容器
如何用基于 Java 配置的方式配置 Spring?
如何用基于 Java 配置的方式配置 Spring?