真懂Spring的@Configuration配置类?你可能自我感觉太良好
当大潮退去,才知道谁在裸泳。关注公众号【BAT的乌托邦】开启专栏式学习,拒绝浅尝辄止。本文 https://www.yourbatman.cn 已收录,里面一并有Spring技术栈、MyBatis、中间件等小而美的专栏供以学习哦。
前言
各位小伙伴大家好,我是A哥。这是一篇“插队”进来的文章,源于我公众号下面的这句评论:
官方管这两种模式分别叫:Full @Configuration
和lite @Bean mode
,口语上我习惯把它称为Spring配置的Full模式和Lite模式更易沟通。
的确,我很简单的“调研”了一下,知晓Spring配置中Lite模式
和Full模式
的几乎没有(或者说真的很少吧)。按照我之前的理论,大多人都不知道的技术(知识点)那肯定是不流行的。但是:不流行不代表不重要,不流行不代表不值钱,毕竟高薪往往只有少数人才能拥有。
什么OPP、OOP、AOP编程,其实我最喜欢的和推崇的是面向工资编程。当然前提是够硬(收回你邪恶的笑容),没有金刚钻,不揽瓷器活。
听我这么一忽悠,是不是对这块内容还饶有兴味了,这不它来了嘛。
版本约定
本文内容若没做特殊说明,均基于以下版本:
- JDK:
1.8
- Spring Framework:
5.2.2.RELEASE
正文
最初的Spring只支持xml方式配置Bean,从Spring 3.0
起支持了一种更优的方式:基于Java类的配置方式,这一下子让我们Javaer可以从标签语法里解放了出来。毕竟作为Java程序员,我们擅长的是写Java类,而非用标签语言去写xml文件。
我对Spring配置的Full/Lite模式的关注和记忆深刻,源自于一个小小故事:某一年我在看公司的项目时发现,数据源配置类里有如下一段配置代码:
@Configuration
public class DataSourceConfig {
...
@Bean
public DataSource dataSource() {
...
return dataSource;
}
@Bean(name = "transactionManager")
public DataSourceTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
...
}
作为当时还是Java萌新的我,非常的费解。自然的对此段代码产生了较大的好奇(其实是质疑):在准备DataSourceTransactionManager
这个Bean时调用了dataSource()
方法,根据我“非常扎实”的JavaSE基础知识,它肯定会重新走一遍dataSource()
方法,从而产生一个新的数据源实例,那么你的事务管理器管理的不就是一个“全新数据源”麽?谈何事务呢?
为了验证我的猜想,我把断点打到dataSource()
方法内部开始调试,但让我“失望”的是:此方法并没有执行两次。这在当时是震惊了我的,甚至一度怀疑自己引以为豪的Java基础了。所以我四处询问,希望得到一个“解释”,但奈何,问了好几圈,那会没有一人能给我一个合理的说法,只知道那么用是没有问题的。
很明显,现在再回头来看当时的这个质疑是显得有些“无知”的,这个“难题”困扰了我很久,直到我前2年开始深度研究Spring源码才让此难题迎刃而解,当时那种豁然开朗的感觉真好呀。
基本概念
关于配置类的核心概念,在这里先予以解释。
@Configuration和@Bean
Spring新的配置体系中最为重要的构件是:@Configuration
标注的类,@Bean
标注的方法。
// @since 3.0
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
@AliasFor(annotation = Component.class)
String value() default "";
// @since 5.2
boolean proxyBeanMethods() default true;
}
用@Configuration
注解标注的类表明其主要目的是作为bean定义的源。此外,@Configuration
类允许通过调用同一类中的其他@Bean
method方法来定义bean之间的依赖关系(下有详解)。
// @since 3.0
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Bean {
@AliasFor("name")
String[] value() default {};
@AliasFor("value")
String[] name() default {};
@Deprecated
Autowire autowire() default Autowire.NO;
// @since 5.1
boolean autowireCandidate() default true;
String initMethod() default "";
String destroyMethod() default AbstractBeanDefinition.INFER_METHOD;
}
@Bean
注解标注在方法上,用于指示方法实例化、配置和初始化要由Spring IoC容器管理的新对象。对于熟悉Spring的<beans/>
XML配置的人来说,@Bean
注解的作用与<bean/>
元素相同。您可以对任何Spring的@Component组件使用@Bean
注释的方法代替(注意:这是理论上,实际上比如使用@Controller标注的组件就不能直接使用它代替)。
需要注意的是,通常来说,我们均会把@Bean
标注的方法写在@Configuration
标注的类里面来配合使用。
简单粗暴理解:
@Configuration
标注的类等同于一个xml文件,@Bean
标注的方法等同于xml文件里的一个<bean/>
标签
使用举例
@Configuration
public class AppConfig {
@Bean
public User user(){
User user = new User();
user.setName("A哥");
user.setAge(18);
return user;
}
}
public class Application {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
User user = context.getBean(User.class);
System.out.println(user.getClass());
System.out.println(user);
}
}
输出:
class com.yourbatman.fullliteconfig.User
User{name='A哥', age=18}
Full模式和Lite模式
Full模式和Lite模式均是针对于Spring配置类而言的,和xml配置文件无关。值得注意的是:判断是Full模式 or Lite模式的前提是,首先你得是个容器组件。至于一个实例是如何“晋升”成为容器组件的,可以用注解也可以没有注解,本文就不展开讨论了,这属于Spring的基础知识。
Lite模式
当@Bean
方法在没有使用@Configuration
注释的类中声明时,它们被称为在Lite模式下处理。它包括:在@Component
中声明的@Bean
方法,甚至只是在一个非常普通的类中声明的Bean方法,都被认为是Lite版的配置类。@Bean
方法是一种通用的工厂方法(factory-method
)机制。
和Full模式的@Configuration
不同,Lite模式的@Bean
方法不能声明Bean之间的依赖关系。因此,这样的@Bean
方法不应该调用其他@Bean方法。每个这样的方法实际上只是一个特定Bean引用的工厂方法(factory-method),没有任何特殊的运行时语义。
何时为Lite模式
官方定义为:在没有标注@Configuration
的类里面有@Bean
方法就称为Lite模式的配置。透过源码再看这个定义是不完全正确的,而应该是有如下case均认为是Lite模式的配置类:
- 类上标注有
@Component
注解 - 类上标注有
@ComponentScan
注解 - 类上标注有
@Import
注解 - 类上标注有
@ImportResource
注解 - 若类上没有任何注解,但类内存在@Bean方法
以上case的前提均是类上没有被标注@Configuration
,在Spring 5.2之后新增了一种case也算作Lite模式:
- 标注有
@Configuration(proxyBeanMethods = false)
,注意:此值默认是true哦,需要显示改为false才算是Lite模式
细心的你会发现,自Spring5.2(对应Spring Boot 2.2.0)开始,内置的几乎所有的@Configuration
配置类都被修改为了@Configuration(proxyBeanMethods = false)
,目的何为?答:以此来降低启动时间,为Cloud Native继续做准备。
优缺点
优点:
- 运行时不再需要给对应类生成CGLIB子类,提高了运行性能,降低了启动时间
- 可以该配置类当作一个普通类使用喽:也就是说@Bean方法 可以是private、可以是final
缺点:
- 不能声明@Bean之间的依赖,也就是说不能通过方法调用来依赖其它Bean
- (其实这个缺点还好,很容易用其它方式“弥补”,比如:把依赖Bean放进方法入参里即可)
代码示例
主配置类:
@ComponentScan("com.yourbatman.fullliteconfig.liteconfig")
@Configuration
public class AppConfig {
}
准备一个Lite模式的配置:
@Component
// @Configuration(proxyBeanMethods = false) // 这样也是Lite模式
public class LiteConfig {
@Bean
public User user() {
User user = new User();
user.setName("A哥-lite");
user.setAge(18);
return user;
}
@Bean
private final User user2() {
User user = new User();
user.setName("A哥-lite2");
user.setAge(18);
// 模拟依赖于user实例 看看是否是同一实例
System.out.println(System.identityHashCode(user()));
System.out.println(System.identityHashCode(user()));
return user;
}
public static class InnerConfig {
@Bean
// private final User userInner() { // 只在lite模式下才好使
public User userInner() {
User user = new User();
user.setName("A哥-lite-inner");
user.setAge(18);
return user;
}
}
}
测试用例:
public class Application {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
// 配置类情况
System.out.println(context.getBean(LiteConfig.class).getClass());
System.out.println(context.getBean(LiteConfig.InnerConfig.class).getClass());
String[] beanNames = context.getBeanNamesForType(User.class);
for (String beanName : beanNames) {
User user = context.getBean(beanName, User.class);
System.out.println("beanName:" + beanName);
System.out.println(user.getClass());
System.out.println(user);
System.out.println("------------------------");
}
}
}
结果输出:
1100767002
313540687
class com.yourbatman.fullliteconfig.liteconfig.LiteConfig
class com.yourbatman.fullliteconfig.liteconfig.LiteConfig$InnerConfig
beanName:userInner
class com.yourbatman.fullliteconfig.User
User{name='A哥-lite-inner', age=18}
------------------------
beanName:user
class com.yourbatman.fullliteconfig.User
User{name='A哥-lite', age=18}
------------------------
beanName:user2
class com.yourbatman.fullliteconfig.User
User{name='A哥-lite2', age=18}
------------------------
小总结
- 该模式下,配置类本身不会被CGLIB增强,放进IoC容器内的就是本尊
- 该模式下,对于内部类是没有限制的:可以是Full模式或者Lite模式
- 该模式下,配置类内部不能通过方法调用来处理依赖,否则每次生成的都是一个新实例而并非IoC容器内的单例
- 该模式下,配置类就是一普通类嘛,所以@Bean方法可以使用
private/final
等进行修饰(static自然也是阔仪的)
Full模式
在常见的场景中,@Bean
方法都会在标注有@Configuration
的类中声明,以确保总是使用“Full模式”,这么一来,交叉方法引用会被重定向到容器的生命周期管理,所以就可以更方便的管理Bean依赖。
何时为Full模式
标注有@Configuration
注解的类被称为full模式的配置类。自Spring5.2后这句话改为下面这样我觉得更为精确些:
- 标注有
@Configuration
或者@Configuration(proxyBeanMethods = true)
的类被称为Full模式的配置类 - (当然喽,proxyBeanMethods属性的默认值是true,所以一般需要Full模式我们只需要标个注解即可)
优缺点
优点:
- 可以支持通过常规Java调用相同类的@Bean方法而保证是容器内的Bean,这有效规避了在“Lite模式”下操作时难以跟踪的细微错误。特别对于萌新程序员,这个特点很有意义
缺点:
- 运行时会给该类生成一个CGLIB子类放进容器,有一定的性能、时间开销(这个开销在Spring Boot这种拥有大量配置类的情况下是不容忽视的,这也是为何Spring 5.2新增了
proxyBeanMethods
属性的最直接原因) - 正因为被代理了,所以@Bean方法 不可以是private、不可以是final
代码示例
主配置:
@ComponentScan("com.yourbatman.fullliteconfig.fullconfig")
@Configuration
public class AppConfig {
}
准备一个Full模式的配置:
@Configuration
public class FullConfig {
@Bean
public User user() {
User user = new User();
user.setName("A哥-lite");
user.setAge(18);
return user;
}
@Bean
protected User user2() {
User user = new User();
user.setName("A哥-lite2");
user.setAge(18);
// 模拟依赖于user实例 看看是否是同一实例
System.out.println(System.identityHashCode(user()));
System.out.println(System.identityHashCode(user()));
return user;
}
public static class InnerConfig {
@Bean
// private final User userInner() { // 只在lite模式下才好使
public User userInner() {
User user = new User();
user.setName("A哥-lite-inner");
user.setAge(18);
return user;
}
}
}
测试用例:
public class Application {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
// 配置类情况
System.out.println(context.getBean(FullConfig.class).getClass());
System.out.println(context.getBean(FullConfig.InnerConfig.class).getClass());
String[] beanNames = context.getBeanNamesForType(User.class);
for (String beanName : beanNames) {
User user = context.getBean(beanName, User.class);
System.out.println("beanName:" + beanName);
System.out.println(user.getClass());
System.out.println(user);
System.out.println("------------------------");
}
}
}
结果输出:
550668305
550668305
class com.yourbatman.fullliteconfig.fullconfig.FullConfig$$EnhancerBySpringCGLIB$$70a94a63
class com.yourbatman.fullliteconfig.fullconfig.FullConfig$InnerConfig
beanName:userInner
class com.yourbatman.fullliteconfig.User
User{name='A哥-lite-inner', age=18}
------------------------
beanName:user
class com.yourbatman.fullliteconfig.User
User{name='A哥-lite', age=18}
------------------------
beanName:user2
class com.yourbatman.fullliteconfig.User
User{name='A哥-lite2', age=18}
------------------------
小总结
- 该模式下,配置类会被CGLIB增强(生成代理对象),放进IoC容器内的是代理
- 该模式下,对于内部类是没有限制的:可以是Full模式或者Lite模式
- 该模式下,配置类内部可以通过方法调用来处理依赖,并且能够保证是同一个实例,都指向IoC内的那个单例
- 该模式下,@Bean方法不能被
private/final
等进行修饰(很简单,因为方法需要被复写嘛,所以不能私有和final。defualt/protected/public都可以哦),否则启动报错(其实IDEA编译器在编译器就提示可以提示你了):
Exception in thread "main" org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: @Bean method 'user2' must not be private or final; change the method's modifiers to continue
Offending resource: class path resource [com/yourbatman/fullliteconfig/fullconfig/FullConfig.class]
at org.springframework.beans.factory.parsing.FailFastProblemReporter.error(FailFastProblemReporter.java:72)
at org.springframework.context.annotation.BeanMethod.validate(BeanMethod.java:50)
at org.springframework.context.annotation.ConfigurationClass.validate(ConfigurationClass.java:220)
at org.springframework.context.annotation.ConfigurationClassParser.validate(ConfigurationClassParser.java:211)
at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:326)
at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:242)
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:275)
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:95)
at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:706)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:532)
at org.springframework.context.annotation.AnnotationConfigApplicationContext.<init>(AnnotationConfigApplicationContext.java:89)
at com.yourbatman.fullliteconfig.Application.main(Application.java:11)
使用建议
了解了Spring配置类的Full模式和Lite模式,那么在工作中我该如何使用呢?这里A哥给出使用建议,仅供参考:
- 如果是在公司的业务功能/服务上做开发,使用Full模式
- 如果你是个容器开发者,或者你在开发中间件、通用组件等,那么使用Lite模式是一种更被推荐的方式,它对Cloud Native更为友好
思考题?
通过new AnnotationConfigApplicationContext(AppConfig.class)
直接放进去的类,它会成为一个IoC的组件吗?若会,那么它是Full模式 or Lite模式呢?是个固定的结果还是也和其标注的注解有关呢?
本思考题不难,自己试验一把便知,建议多动手~
总结
本文结合代码示例阐述了Spring配置中Full模式和Lite模式,以及各自的定义和优缺点。对于一般的小伙伴,掌握本文就够用了,并且足够你面试中吹x。但A哥系列文章一般不止于“表面”嘛,下篇文章将从原理层面告诉你Spring是如何来巧妙的处理这两种模式的,特别是会结合Spring 5.2.0
新特性,以及对比Spring 5.2.0
的实现和之前版本有何不同,你课订阅我的公众号保持关注。
真懂Spring的@Configuration配置类?你可能自我感觉太良好的更多相关文章
- Spring源码解析 – @Configuration配置类及注解Bean的解析
在分析Spring 容器创建过程时,我们知道容器默认会加载一些后置处理器PostPRocessor,以AnnotationConfigApplicationContext为例,在构造函数中初始化rea ...
- 【Spring】简述@Configuration配置类注册BeanDefinition到Spring容器的过程
概述 本文以SpringBoot应用为基础,尝试分析基于注解@Configuration的配置类是如何向Spring容器注册BeanDefinition的过程 其中主要分析了 Configuratio ...
- spring5 源码深度解析----- 被面试官给虐懵了,竟然是因为我不懂@Configuration配置类及@Bean的原理
@Configuration注解提供了全新的bean创建方式.最初spring通过xml配置文件初始化bean并完成依赖注入工作.从spring3.0开始,在spring framework模块中提供 ...
- Spring之@Configuration配置解析
1.简单的示例: @Configuration @EnableConfigurationProperties({DemoProperties.class}) public class DemoConf ...
- Spring Boot自动配置类
http://docs.spring.io/spring-boot/docs/current/api/overview-summary.html http://docs.spring.io/sprin ...
- Spring boot添加配置类@Configuration并初始化@Bean,@Resource和@Autowired都为null
大写加黑,找了好久@Resource和@Autowired都依赖不到创建的bean的原因:@Bean的方法名即是创建的Bean名称 import org.activiti.engine.Process ...
- springboot @Configuration配置类里面使用@Value获取不到.yml配置文件属性的值
之前一个项目里面分为很多子工程的那种结构,要求让我改成一个项目的结构.我这边手动将代码合并之后出现下面的这种问题,然后尝试进行用各种方式解决 Error creating bean with name ...
- spring 自己创建配置类
- 配置类需要标注@Configuration却不知原因?那这次就不能给你涨薪喽
专注Java领域分享.成长,拒绝浅尝辄止.关注公众号[BAT的乌托邦]开启专栏式学习,拒绝浅尝辄止.本文 https://www.yourbatman.cn 已收录,里面一并有Spring技术栈.My ...
随机推荐
- 实战记录之SQL server报错手工注入
前言 最近测试了一个站点,这个站点挺有意思,发现没有关闭错误提示,初步猜测是SQL server数据库,后来验证确实是.在这里记录一下实战过程,并详细讲解一下用到的知识点. SQL server报错注 ...
- PAT1040 Longest Symmetric String (25分) 中心扩展法+动态规划
题目 Given a string, you are supposed to output the length of the longest symmetric sub-string. For ex ...
- 结合中断上下文切换和进程上下文切换分析Linux内核的一般执行过程
结合中断上下文切换和进程上下文切换分析Linux内核的一般执行过程 目录 结合中断上下文切换和进程上下文切换分析Linux内核的一般执行过程 一. 实验准备 二. 实验过程 I 分析中断上下文的切换 ...
- Iterable对象
''' 我们已经知道,可以直接作用于for循环的数据类型有以下几种: 一类是集合数据类型,如list.tuple.dict.set.str等: 一类是generator,包括生成器和带yield的ge ...
- mysql字符串类型(set类型)
集合 set 不定想项选 类似于 enum枚举,在定义时,也需要指定其已有值! 与字符串相比,优势是: 1, 也是采用 整数进行管理的!采用位运算,从第一位开始为1,逐一x2! 2, 每个集合类型8 ...
- VM virtualBox中文版 v6.0.12.133076官方版(64/-虚拟机
virtualBox中文版 v6.0.12.133076官方版(64/-虚拟机 http://www.onlinedown.net/soft/82464.htm http://8dx.pc6.com/ ...
- 8.实战交付一套dubbo微服务到k8s集群(1)之Zookeeper部署
1.基础架构 主机名 角色 ip HDSS7-11.host.com K8S代理节点1,zk1 10.4.7.11 HDSS7-12.host.com K8S代理节点2,zk2 10.4.7.12 H ...
- 学习ASP.NET Core(11)-解决跨域问题与程序部署
上一篇我们介绍了系统日志与测试相关的内容并添加了相关的功能:本章我们将介绍跨域与程序部署相关的内容 一.跨域 1.跨域的概念 1.什么是跨域? 一个请求的URL由协议,域名,端口号组成,以百度的htt ...
- 登录CentOS用户很慢/usr/bin/xauth: timeout in locking authority file /home/***/.Xauthority
当使用非root用户登录CentOS时,发现很慢,而且弹出以下信息: /usr/bin/xauth: timeout in locking authority file /home/***/.Xau ...
- Redis 持久化-AOF自动重写配置
AOF自动重写 auto-aof-rewrite-min-size size auto-aof-rewrite-percentage percent 在 redis.conf 自动重写的默认配置: a ...