当大潮退去,才知道谁在裸泳。关注公众号【 BAT的乌托邦 】开启专栏式学习,拒绝浅尝辄止。本文 https://www.yourbatman.cn 已收录,里面一并有Spring技术栈、MyBatis、中间件等小而美的专栏供以学习哦。
目录
各位小伙伴大家好,我是A哥。这是一篇“插队”进来的文章,源于我公众号下面的这句评论:
官方管这两种模式分别叫: Full @Configuration
和 lite @Bean mode
,口语上我习惯把它称为Spring配置的Full模式和Lite模式更易沟通。
的确,我很简单的“调研”了一下,知晓Spring配置中 Lite模式
和 Full模式
的几乎没有(或者说真的很少吧)。按照我之前的理论,大多人都不知道的技术(知识点)那肯定是不流行的。但是: 不流行不代表不重要,不流行不代表不值钱,毕竟高薪往往只有少数人才能拥有。
什么OPP、OOP、AOP编程,其实我最喜欢的和推崇的是 面向工资编程 。当然前提是够硬(收回你邪恶的笑容),没有金刚钻,不揽瓷器活。
听我这么一忽悠,是不是对这块内容还饶有兴味了,这不它来了嘛。
本文内容若没做特殊说明,均基于以下版本:
1.8 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源码才让此难题迎刃而解,当时那种豁然开朗的感觉真好呀。
关于配置类的核心概念,在这里先予以解释。
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模式均是针对于Spring配置类而言的,和xml配置文件无关。值得注意的是:判断是Full模式 or Lite模式的前提是,首先你得是个容器组件。至于一个实例是如何“晋升”成为容器组件的,可以用注解也可以没有注解,本文就不展开讨论了,这属于Spring的基础知识。
当 @Bean
方法在没有使用 @Configuration
注释的类中声明时,它们被称为 在Lite模式下处理 。它包括:在 @Component
中声明的 @Bean
方法,甚至只是在一个非常普通的类中声明的Bean方法,都被认为是Lite版的配置类。 @Bean
方法是一种通用的工厂方法( factory-method
)机制。
和Full模式的 @Configuration
不同,Lite模式的 @Bean
方法 不能声明Bean之间的依赖关系 。因此,这样的 @Bean
方法 不应该调用其他@Bean方法 。每个这样的方法实际上 只是一个特定Bean引用的工厂方法(factory-method) ,没有任何特殊的运行时语义。
官方定义为:在没有标注 @Configuration
的类里面有 @Bean
方法就称为Lite模式的配置。透过源码再看这个定义是不完全正确的,而应该是有如下case均认为是Lite模式的配置类:
@Component
注解 @ComponentScan
注解 @Import
注解 @ImportResource
注解 以上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继续做准备。
主配置类:
@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} ------------------------
private/final
等进行修饰(static自然也是阔仪的) 在常见的场景中, @Bean
方法都会在标注有 @Configuration
的类中声明,以确保总是使用“Full模式”,这么一来,交叉方法引用会被重定向到容器的生命周期管理,所以就可以更方便的管理Bean依赖。
标注有 @Configuration
注解的类被称为full模式的配置类。自Spring5.2后这句话改为下面这样我觉得更为精确些:
@Configuration
或者 @Configuration(proxyBeanMethods = true)
的类被称为Full模式的配置类 proxyBeanMethods
属性的最直接原因) 主配置:
@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} ------------------------
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哥给出使用建议,仅供参考:
通过 new AnnotationConfigApplicationContext(AppConfig.class)
直接放进去的类,它会成为一个IoC的组件吗?若会,那么它是Full模式 or Lite模式呢?是个固定的结果还是也和其标注的注解有关呢?
本思考题不难,自己试验一把便知,建议多动手~
本文结合代码示例阐述了Spring配置中Full模式和Lite模式,以及各自的定义和优缺点。对于一般的小伙伴,掌握本文就够用了,并且足够你面试中吹x。但A哥系列文章一般不止于“表面”嘛, 下篇文章 将从原理层面告诉你Spring是如何来巧妙的处理这两种模式的,特别是会结合 Spring 5.2.0
新特性,以及对比 Spring 5.2.0
的实现和 之前版本 有何不同,你课订阅我的公众号保持关注。