javascript
Spring的@Configuration配置类-Full和Lite模式
前言
各位小伙伴大家好,我是A哥。這是一篇“插隊”進來的文章,源于我公眾號下面的這句評論:
官方管這兩種模式分別叫:Full @Configuration和lite @Bean mode,口語上我習慣把它稱為Spring配置的Full模式和Lite模式更易溝通。
的確,我很簡單的“調研”了一下,知曉Spring配置中Lite模式和Full模式的幾乎沒有(或者說真的很少吧)。按照我之前的理論,大多人都不知道的技術(知識點)那肯定是不流行的。但是:不流行不代表不重要,不流行不代表不值錢,畢竟高薪往往只有少數人才能擁有。
版本約定
本文內容若沒做特殊說明,均基于以下版本:
- 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 {...@Beanpublic 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.2boolean 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 {};@DeprecatedAutowire autowire() default Autowire.NO;// @since 5.1boolean 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 {@Beanpublic 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模式的配置類:
以上case的前提均是類上沒有被標注@Configuration,在Spring 5.2之后新增了一種case也算作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 {@Beanpublic User user() {User user = new User();user.setName("A哥-lite");user.setAge(18);return user;}@Beanprivate 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 {@Beanpublic User user() {User user = new User();user.setName("A哥-lite");user.setAge(18);return user;}@Beanprotected 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編譯器在編譯器就提示可以提示你了):
使用建議
了解了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的實現和之前版本有何不同,你課訂閱我的公眾號保持關注。
超強干貨來襲 云風專訪:近40年碼齡,通宵達旦的技術人生總結
以上是生活随笔為你收集整理的Spring的@Configuration配置类-Full和Lite模式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring Boot Transact
- 下一篇: Spring AOP源码解析(二)——