static關(guān)鍵字有何魔法?竟讓Spring Boot搞出那么多靜態(tài)內(nèi)部類(lèi)(推薦)
生命太短暫,不要去做一些根本沒(méi)有人想要的東西。本文已被 https://www.yourbatman.cn 收錄,里面一并有Spring技術(shù)棧、MyBatis、JVM、中間件等小而美的專(zhuān)欄供以免費(fèi)學(xué)習(xí)。
前言
各位小伙伴大家好,我是A哥。上篇文章了解了static關(guān)鍵字 + @Bean方法的使用,知曉了它能夠提升Bean的優(yōu)先級(jí),在@Bean方法前標(biāo)注static關(guān)鍵字,特定情況下可以避免一些煩人的“警告”日志的輸出,排除隱患讓工程變得更加安全。我們知道static關(guān)鍵字它不僅可使用在方法上,那么本文將繼續(xù)挖掘static在Spring環(huán)境下的用處。
根據(jù)所學(xué)的JavaSE基礎(chǔ),static關(guān)鍵字除了能夠修飾方法外,還能使用在這兩個(gè)地方:
修飾類(lèi)。確切的說(shuō),應(yīng)該叫修飾內(nèi)部類(lèi),所以它叫靜態(tài)內(nèi)部類(lèi)修飾成員變量
其實(shí)static還可以修飾代碼塊、static靜態(tài)導(dǎo)包等,但很明顯,這些與本文無(wú)關(guān)
接下來(lái)就以這為兩條主線(xiàn),分別研究static在對(duì)應(yīng)場(chǎng)景下的作用,本文將聚焦在靜態(tài)內(nèi)部類(lèi)上。
版本約定
本文內(nèi)容若沒(méi)做特殊說(shuō)明,均基于以下版本:
JDK:1.8 Spring Framework:5.2.2.RELEASE正文
說(shuō)到Java里的static關(guān)鍵字,這當(dāng)屬最基礎(chǔ)的入門(mén)知識(shí),是Java中常用的關(guān)鍵字之一。你平時(shí)用它來(lái)修飾變量和方法了,但是對(duì)它的了解,即使放在JavaSE情景下知道這些還是不夠的,問(wèn)題雖小但這往往反映了你對(duì)Java基礎(chǔ)的了解程度。
當(dāng)然嘍,本文并不討論它在JavaSE下使用,畢竟咱們還是有一定逼格的專(zhuān)欄,需要進(jìn)階一把,玩玩它在Spring環(huán)境下到底能夠迸出怎么樣的火花呢?比如靜態(tài)內(nèi)部類(lèi)~
Spring下的靜態(tài)內(nèi)部類(lèi)
static修飾類(lèi)只有一種情況:那就是這個(gè)類(lèi)屬于內(nèi)部類(lèi),這就是我們津津樂(lè)道的靜態(tài)內(nèi)部類(lèi),形如這樣:
public class Outer { private String name; private static Integer age; // 靜態(tài)內(nèi)部類(lèi) private static class Inner { private String innerName; private static Integer innerAge; public void fun1() { // 無(wú)法訪問(wèn)外部類(lèi)的成員變量 //System.out.println(name); System.out.println(age); System.out.println(innerName); System.out.println(innerAge); } } public static void main(String[] args) { // 靜態(tài)內(nèi)部類(lèi)的實(shí)例化并不需要依賴(lài)于外部類(lèi)的實(shí)例 Inner inner = new Inner(); }}
在實(shí)際開(kāi)發(fā)中,靜態(tài)內(nèi)部類(lèi)的使用場(chǎng)景是非常之多的。
認(rèn)識(shí)靜態(tài)/普通內(nèi)部類(lèi)
由于一些小伙伴對(duì)普通內(nèi)部類(lèi) vs 靜態(tài)內(nèi)部類(lèi)傻傻分不清,為了方便后續(xù)講解,本處把關(guān)鍵要素做簡(jiǎn)要對(duì)比說(shuō)明:
靜態(tài)內(nèi)部類(lèi)可以聲明靜態(tài)or實(shí)例成員(屬性和方法);而普通內(nèi)部類(lèi)則不可以聲明靜態(tài)成員(屬性和方法) 靜態(tài)內(nèi)部類(lèi)實(shí)例的創(chuàng)建不依賴(lài)于外部類(lèi);而普通外部類(lèi)實(shí)例創(chuàng)建必須先有外部類(lèi)實(shí)例才行(綁定關(guān)系拿捏得死死的,不信你問(wèn)鄭凱) 靜態(tài)內(nèi)部類(lèi)不能訪問(wèn)外部類(lèi)的實(shí)例成員;而普通內(nèi)部類(lèi)可以隨意訪問(wèn)(不管靜態(tài)or非靜態(tài)) --> 我理解這是普通內(nèi)部類(lèi)能 “存活” 下來(lái)的最大理由了吧😄總之,普通內(nèi)部類(lèi)和外部類(lèi)的關(guān)系屬于強(qiáng)綁定,而靜態(tài)內(nèi)部類(lèi)幾乎不會(huì)受到外部類(lèi)的限制,可以游離單獨(dú)使用。既然如此,那為何還需要static靜態(tài)內(nèi)部類(lèi)呢,直接單獨(dú)寫(xiě)個(gè)Class類(lèi)豈不就好了嗎?存在即合理,這么使用的原因我個(gè)人覺(jué)得有如下兩方面思考,供以你參考:
靜態(tài)內(nèi)部類(lèi)是弱關(guān)系并不是沒(méi)關(guān)系,比如它還是可以訪問(wèn)外部類(lèi)的static的變量的不是(即便它是private的) 高內(nèi)聚的體現(xiàn)在傳統(tǒng)Spirng Framework的配置類(lèi)場(chǎng)景下,你可能鮮有接觸到static關(guān)鍵字使用在類(lèi)上的場(chǎng)景,但這在Spring Boot下使用非常頻繁,比如屬性配置類(lèi)的典型應(yīng)用:
@ConfigurationProperties(prefix = 'server', ignoreUnknownFields = true)public class ServerProperties {// server.port = xxx // server.address = xxxprivate Integer port;private InetAddress address;...// tomcat配置public static class Tomcat {// server.tomcat.protocol-header = xxxprivate String protocolHeader;...// tomcat內(nèi)的log配置public static class Accesslog {// server.tomcat.accesslog.enabled = xxxprivate boolean enabled = false;...}}}
這種嵌套case使得代碼(配置)的key 內(nèi)聚性非常強(qiáng),使用起來(lái)更加方便。試想一下,如果你不使用靜態(tài)內(nèi)部類(lèi)去集中管理這些配置,每個(gè)配置都單獨(dú)書(shū)寫(xiě)的話(huà),像這樣:
@ConfigurationProperties(prefix = 'server', ignoreUnknownFields = true)public class ServerProperties {}@ConfigurationProperties(prefix = 'server.tomcat', ignoreUnknownFields = true)public class TomcatProperties {}@ConfigurationProperties(prefix = 'server.tomcat.accesslog', ignoreUnknownFields = true)public class AccesslogProperties {}
這代碼,就問(wèn)你,如果是你同事寫(xiě)的,你罵不罵吧!用臃腫來(lái)形容還是個(gè)中意詞,層次結(jié)構(gòu)體現(xiàn)得也非常的不直觀嘛。因此,對(duì)于這種屬性類(lèi)里使用靜態(tài)內(nèi)部類(lèi)是非常適合,內(nèi)聚性一下子高很多~
除了在內(nèi)聚性上的作用,在Spring Boot中的@Configuration配置類(lèi)下(特別常見(jiàn)于自動(dòng)配置類(lèi))也能經(jīng)常看到它的身影:
@Configuration(proxyBeanMethods = false)public class WebMvcAutoConfiguration {// web MVC個(gè)性化定制配置@Configuration(proxyBeanMethods = false)@Import(EnableWebMvcConfiguration.class)@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })@Order(0)public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {...}@Configuration(proxyBeanMethods = false)public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {...}}
利用靜態(tài)內(nèi)部類(lèi)把相似配置類(lèi)歸并在一個(gè) .java文件 內(nèi),這樣多個(gè)static類(lèi)還可公用外部類(lèi)的屬性、方法,也是一種高內(nèi)聚的體現(xiàn)。同時(shí)static關(guān)鍵字提升了初始化的優(yōu)先級(jí),比如本例中的EnableWebMvcConfiguration它會(huì)優(yōu)先于外部類(lèi)加載~
關(guān)于static靜態(tài)內(nèi)部類(lèi)優(yōu)先級(jí)相關(guān)是重點(diǎn),靜態(tài)內(nèi)部類(lèi)的優(yōu)先級(jí)會(huì)更高嗎?使用普通內(nèi)部能達(dá)到同樣效果嗎?拍腦袋直接回答是沒(méi)用的,帶著這兩個(gè)問(wèn)題,接下來(lái)A哥舉例領(lǐng)你一探究竟...
static靜態(tài)配置類(lèi)提升配置優(yōu)先級(jí)
自己先構(gòu)造一個(gè)Demo,場(chǎng)景如下:
@Configurationclass OuterConfig { OuterConfig() { System.out.println('OuterConfig init...'); } @Bean static Parent parent() { return new Parent(); } @Configuration private static class InnerConfig { InnerConfig() { System.out.println('InnerConfig init...'); } @Bean Daughter daughter() { return new Daughter(); } }}
測(cè)試程序:
@ComponentScanpublic class TestSpring { public static void main(String[] args) { new AnnotationConfigApplicationContext(TestSpring.class); }}
啟動(dòng)程序,結(jié)果輸出:
InnerConfig init...OuterConfig init...Daughter init...Parent init...
結(jié)果細(xì)節(jié):似乎都是按照字母表的順序來(lái)執(zhí)行的。I在前O在后;D在前P在后;
看到這個(gè)結(jié)果,如果你就過(guò)早的得出結(jié)論:靜態(tài)內(nèi)部類(lèi)優(yōu)先級(jí)高于外部類(lèi),那么就太隨意了,圖樣圖森破啊。大膽猜想,小心求證 應(yīng)該是程序員應(yīng)有的態(tài)度,那么繼續(xù)往下看,在此基礎(chǔ)上我新增加一個(gè)靜態(tài)內(nèi)部類(lèi):
@Configurationclass OuterConfig { OuterConfig() { System.out.println('OuterConfig init...'); } @Bean static Parent parent() { return new Parent(); } @Configuration private static class PInnerConfig { PInnerConfig() { System.out.println('PInnerConfig init...'); } @Bean Son son() { return new Son(); } } @Configuration private static class InnerConfig { InnerConfig() { System.out.println('InnerConfig init...'); } @Bean Daughter daughter() { return new Daughter(); } }}
我先解釋下我這么做的意圖:
增加一個(gè)字母P開(kāi)頭的內(nèi)部類(lèi),自然順序P在O(外部類(lèi))后面,消除影響 P開(kāi)頭的內(nèi)部類(lèi)在源碼擺放順序上故意放在了I開(kāi)頭的內(nèi)部類(lèi)的上面,同樣為了消除字母表順序帶來(lái)的影響 目的:看看是按照字節(jié)碼順序,還是字母表順序呢? PInnerConfig里面的@Bean實(shí)例為Son,字母表順序是三者中最為靠后的,但字節(jié)碼卻在中間,這樣也能夠消除影響運(yùn)行程序,結(jié)果輸出:
InnerConfig init...PInnerConfig init...OuterConfig init...Daughter init...son init...Parent init...
結(jié)果細(xì)節(jié):外部類(lèi)貌似總是滯后于內(nèi)部類(lèi)初始化;同一類(lèi)的多個(gè)內(nèi)部類(lèi)之間順序是按照字母表順序(自然排序)初始化而非字節(jié)碼順序;@Bean方法的順序依照了類(lèi)的順序
請(qǐng)留意本結(jié)果和上面結(jié)果是否有區(qū)別,你應(yīng)該若有所思。
這是單.java文件的case(所有static類(lèi)都在同一個(gè).java文件內(nèi)),接下來(lái)我在同目錄下增加 2個(gè).java文件(請(qǐng)自行留意類(lèi)名第一個(gè)字母,我將不再贅述我的設(shè)計(jì)意圖):
// 文件一:@Configurationclass A_OuterConfig { A_OuterConfig() { System.out.println('A_OuterConfig init...'); } @Bean String a_o_bean(){ System.out.println('A_OuterConfig a_o_bean init...'); return new String(); } @Configuration private static class PInnerConfig { PInnerConfig() { System.out.println('A_OuterConfig PInnerConfig init...'); } @Bean String a_p_bean(){ System.out.println('A_OuterConfig a_p_bean init...'); return new String(); } } @Configuration private static class InnerConfig { InnerConfig() { System.out.println('A_OuterConfig InnerConfig init...'); } @Bean String a_i_bean(){ System.out.println('A_OuterConfig a_i_bean init...'); return new String(); } }}// 文件二:@Configurationclass Z_OuterConfig { Z_OuterConfig() { System.out.println('Z_OuterConfig init...'); } @Bean String z_o_bean(){ System.out.println('Z_OuterConfig z_o_bean init...'); return new String(); } @Configuration private static class PInnerConfig { PInnerConfig() { System.out.println('Z_OuterConfig PInnerConfig init...'); } @Bean String z_p_bean(){ System.out.println('Z_OuterConfig z_p_bean init...'); return new String(); } } @Configuration private static class InnerConfig { InnerConfig() { System.out.println('Z_OuterConfig InnerConfig init...'); } @Bean String z_i_bean(){ System.out.println('Z_OuterConfig z_i_bean init...'); return new String(); } }}
運(yùn)行程序,結(jié)果輸出:
A_OuterConfig InnerConfig init...A_OuterConfig PInnerConfig init...A_OuterConfig init...InnerConfig init...PInnerConfig init...OuterConfig init...Z_OuterConfig InnerConfig init...Z_OuterConfig PInnerConfig init...Z_OuterConfig init...
A_OuterConfig a_i_bean init...A_OuterConfig a_p_bean init...A_OuterConfig a_o_bean init...Daughter init...son init...Parent init...Z_OuterConfig z_i_bean init...Z_OuterConfig z_p_bean init...Z_OuterConfig z_o_bean init...
這個(gè)結(jié)果大而全,是有說(shuō)服力的,通過(guò)這幾個(gè)示例可以總結(jié)出如下結(jié)論:
垮.java文件 (垮配置類(lèi))之間的順序,是由自然順序來(lái)保證的(字母表順序)如上:下加載A打頭的配置類(lèi)(含靜態(tài)內(nèi)部類(lèi)),再是O打頭的,再是Z打頭的
同一.java文件內(nèi)部,static靜態(tài)內(nèi)部類(lèi)優(yōu)先于外部類(lèi)初始化。若有多個(gè)靜態(tài)內(nèi)部類(lèi),那么按照類(lèi)名自然排序初始化(并非按照定義順序哦,請(qǐng)務(wù)必注意)說(shuō)明:一般內(nèi)部類(lèi)只可能與外部類(lèi)“發(fā)生關(guān)系”,與兄弟之間不建議有任何聯(lián)系,否則順序控制上你就得當(dāng)心了。畢竟靠自然順序去保證是一種弱保證,容錯(cuò)性太低
同一.java文件內(nèi),不同類(lèi)內(nèi)的@Bean方法之間的執(zhí)行順序,保持同2一致(也就說(shuō)你的@Bean所在的@Configuration配置類(lèi)先加載,那你就優(yōu)先被初始化嘍)同一Class內(nèi)多個(gè)@Bean方法的執(zhí)行順序,上篇文章static關(guān)鍵字真能提高Bean的優(yōu)先級(jí)嗎?答:真能 就已經(jīng)說(shuō)過(guò)了哈,請(qǐng)移步參見(jiàn)
總的來(lái)說(shuō),當(dāng)static標(biāo)注在class類(lèi)上時(shí),在同.java文件內(nèi)它是能夠提升優(yōu)先級(jí)的,這對(duì)于Spring Boot的自動(dòng)配置非常有意義,主要體現(xiàn)在如下兩個(gè)方法:
static靜態(tài)內(nèi)部類(lèi)配置優(yōu)先于外部類(lèi)加載,從而靜態(tài)內(nèi)部類(lèi)里面的@Bean也優(yōu)先于外部類(lèi)的@Bean先加載 既然這樣,那么Spring Boot自動(dòng)配置就可以結(jié)合此特性,就可以進(jìn)行具有優(yōu)先級(jí)的@Conditional條件判斷了。這里我舉個(gè)官方的例子,你便能感受到它的魅力所在:
@Configurationpublic class FeignClientsConfiguration {...@Bean@Scope('prototype')@ConditionalOnMissingBeanpublic Feign.Builder feignBuilder(Retryer retryer) {return Feign.builder().retryer(retryer);}@Configuration@ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })protected static class HystrixFeignConfiguration {@Bean@Scope('prototype')@ConditionalOnMissingBean@ConditionalOnProperty(name = 'feign.hystrix.enabled')public Feign.Builder feignHystrixBuilder() {return HystrixFeign.builder();}}}
因?yàn)镠ystrixFeign.builder()它屬于靜態(tài)內(nèi)部類(lèi),所以這個(gè)@Bean肯定是優(yōu)先于外部的Feign.builder()先加載的。所以這段邏輯可解釋為:優(yōu)先使用HystrixFeign.builder()(若條件滿(mǎn)足),否則使用Feign.builder().retryer(retryer)作為兜底。通過(guò)此例你應(yīng)該再一次感受到Bean的加載順序之于Spring應(yīng)用的重要性,特別在Spring Boot/Cloud下此特性尤為凸顯。
你以為記住這幾個(gè)結(jié)論就完事了?不,這明顯不符合A哥的逼格嘛,下面我們就來(lái)繼續(xù)挖一挖吧。
源碼分析
關(guān)于@Configuration配置類(lèi)的順序問(wèn)題,事前需強(qiáng)調(diào)兩點(diǎn):
不同 .java文件 之間的加載順序是不重要的,Spring官方也強(qiáng)烈建議使用者不要去依賴(lài)這種順序因?yàn)闊o(wú)狀態(tài)性,因此你在使用過(guò)程中可以認(rèn)為垮@Configuration文件之前的初始化順序是不確定的 同一.javaw文件內(nèi)也可能存在多個(gè)@Configuration配置類(lèi)(比如靜態(tài)內(nèi)部類(lèi)、普通內(nèi)部類(lèi)等),它們之間的順序是我們需要關(guān)心的,并且需要強(qiáng)依賴(lài)于這個(gè)順序編程(比如Spring Boot)@Configuration配置類(lèi)只有是被@ComponentScan掃描進(jìn)來(lái)(或者被Spring Boot自動(dòng)配置加載進(jìn)來(lái))才需要討論順序(倘若是構(gòu)建上下文時(shí)自己手動(dòng)指好的,那順序就已經(jīng)定死了嘛),實(shí)際開(kāi)發(fā)中的配置類(lèi)也確實(shí)是醬紫的,一般都是通過(guò)掃描被加載。接下來(lái)我們看看@ComponentScan是如何掃描的,把此注解的解析步驟(偽代碼)展示如下:
說(shuō)明:本文并不會(huì)著重分析@ComponentScan它的解析原理,只關(guān)注本文“感興趣”部分
1、解析配置類(lèi)上的@ComponentScan注解(們):本例中TestSpring作為掃描入口,會(huì)掃描到A_OuterConfig/OuterConfig等配置類(lèi)們
ConfigurationClassParser#doProcessConfigurationClass:// **最先判斷** 該配置類(lèi)是否有成員類(lèi)(普通內(nèi)部類(lèi))// 若存在普通內(nèi)部類(lèi),最先把普通內(nèi)部類(lèi)給解析嘍(注意,不是靜態(tài)內(nèi)部類(lèi))if (configClass.getMetadata().isAnnotated(Component.class.getName())) {processMemberClasses(configClass, sourceClass);}...// 遍歷該配置類(lèi)上所有的@ComponentScan注解// 使用ComponentScanAnnotationParser一個(gè)個(gè)解析for (AnnotationAttributes componentScan : componentScans) {Set<BeanDefinitionHolder> scannedBeanDefinitions = this.componentScanParser.parse(componentScan,...);// 繼續(xù)判斷掃描到的bd是否是配置類(lèi),遞歸調(diào)用... }
細(xì)節(jié)說(shuō)明:關(guān)于最先解析內(nèi)部類(lèi)時(shí)需要特別注意,Spring通過(guò)sourceClass.getMemberClasses()來(lái)獲取內(nèi)部類(lèi)們:只有普通內(nèi)部類(lèi)屬于這個(gè),static靜態(tài)內(nèi)部類(lèi)并不屬于它,這點(diǎn)很重要哦
2、解析該注解上的basePackages/basePackageClasses等屬性值得到一些掃描的基包,委托給ClassPathBeanDefinitionScanner去完成掃描
ComponentScanAnnotationParser#parse// 使用ClassPathBeanDefinitionScanner掃描,基于類(lèi)路徑哦scanner.doScan(StringUtils.toStringArray(basePackages));
3、遍歷每個(gè)基包,從文件系統(tǒng)中定位到資源,把符合條件的Spring組件(強(qiáng)調(diào):這里只指外部@Configuration配置類(lèi),還沒(méi)涉及到里面的@Bean這些)注冊(cè)到BeanDefinitionRegistry注冊(cè)中心
ComponentScanAnnotationParser#doScanfor (String basePackage : basePackages) {// 這個(gè)方法是本文最需要關(guān)注的方法Set<BeanDefinition> candidates = findCandidateComponents(basePackage);for (BeanDefinition candidate : candidates) {...// 把該配置**類(lèi)**(并非@Bean方法)注冊(cè)到注冊(cè)中心registerBeanDefinition(definitionHolder, this.registry);}}
到這一步就完成了Bean定義的注冊(cè),此處可以驗(yàn)證一個(gè)結(jié)論:多個(gè)配置類(lèi)之間,誰(shuí)先被掃描到,就先注冊(cè)誰(shuí),對(duì)應(yīng)的就是誰(shuí)最先被初始化。那么這個(gè)順序到底是咋樣界定的呢?那么就要來(lái)到這中間最為重要(本文最關(guān)心)的一步嘍:findCandidateComponents(basePackage)。
說(shuō)明:Spring 5.0開(kāi)始增加了@Indexed注解為云原生做了準(zhǔn)備,可以讓scan掃描動(dòng)作在編譯期就完成,但這項(xiàng)技術(shù)還不成熟,暫時(shí)幾乎無(wú)人使用,因此本文仍舊只關(guān)注經(jīng)典模式的實(shí)現(xiàn)
ClassPathScanningCandidateComponentProvider#scanCandidateComponents// 最終返回的候選組件們Set<BeanDefinition> candidates = new LinkedHashSet<>();// 得到文件系統(tǒng)的路徑,比如本例為classpath*:com/yourbatman/**/*.classString packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +resolveBasePackage(basePackage) + ’/’ + this.resourcePattern;// 從文件系統(tǒng)去加載Resource資源文件進(jìn)來(lái)// 這里Resource代表的是一個(gè)本地資源:存在你硬盤(pán)上的.class文件Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);for (Resource resource : resources) {if (isCandidateComponent(metadataReader)) {if (isCandidateComponent(sbd)) {candidates.add(sbd);}}}
這段代碼的信息量是很大的,分解為如下兩大步:
1.通過(guò)ResourcePatternResolver從磁盤(pán)里加載到所有的 .class資源Resource[]。這里面順序信息就出現(xiàn)了,加載磁盤(pán)Resource資源的過(guò)程很復(fù)雜,總而言之它依賴(lài)于你os文件系統(tǒng)。所以關(guān)于資源的順序可簡(jiǎn)單理解為:你磁盤(pán)文件里是啥順序它就按啥順序加載進(jìn)來(lái)
注意:不是看.java源代碼順序,也不是看你target目錄下的文件順序(該目錄是經(jīng)過(guò)了IDEA反編譯的結(jié)果,無(wú)法反應(yīng)真實(shí)順序),而是編譯后看你的磁盤(pán)上的.class文件的文件順序
2.遍歷每一個(gè)Resource資源,并不是每個(gè)資源都會(huì)成為candidates候選,它有個(gè)雙重過(guò)濾(對(duì)應(yīng)兩個(gè)isCandidateComponent()方法):
過(guò)濾一:使用TypeFilter執(zhí)行過(guò)濾,看看是否被排除;再看看是否滿(mǎn)足@Conditional條件
過(guò)濾二:它有兩種case能滿(mǎn)足條件(任意滿(mǎn)足一個(gè)case即可)
isIndependent()是獨(dú)立類(lèi)(top-level類(lèi) or 靜態(tài)內(nèi)部類(lèi)屬于獨(dú)立類(lèi)) 并且 isConcrete()是具體的(非接口非抽象類(lèi)) isAbstract()是抽象類(lèi) 并且 類(lèi)內(nèi)存在標(biāo)注有@Lookup注解的方法基于以上例子,磁盤(pán)中的.class文件情況如下:
看著這個(gè)順序,再結(jié)合上面的打印結(jié)果,是不是感覺(jué)得到了解釋呢?既然@Configuration類(lèi)(外部類(lèi)和內(nèi)部類(lèi))的順序確定了,那么@Bean就跟著定了嘍,因?yàn)楫吘古渲妙?lèi)也得遍歷一個(gè)一個(gè)去執(zhí)行嘛(有依賴(lài)關(guān)系的case除外)。
特別說(shuō)明:理論上不同的操作系統(tǒng)(如windows和Linux)它們的文件系統(tǒng)是有差異的,對(duì)文件存放的順序是可能不同的(比如$xxx內(nèi)部類(lèi)可能放在后面),但現(xiàn)實(shí)狀況它們是一樣的,因此各位同學(xué)對(duì)此無(wú)需擔(dān)心跨平臺(tái)問(wèn)題哈,這由JVM底層來(lái)給你保證。
什么,關(guān)于此解析步驟你想要張流程圖?好吧,你知道的,這個(gè)A哥會(huì)放到本專(zhuān)欄的總結(jié)篇里統(tǒng)一供以你白嫖,關(guān)注我公眾號(hào)吧~
靜態(tài)內(nèi)部類(lèi)在容器內(nèi)的beanName是什么?
看到這個(gè)截圖你就懂了:在不同.java文件內(nèi),靜態(tài)內(nèi)部類(lèi)是不用擔(dān)心重名問(wèn)題的,這不也就是內(nèi)聚性的一種體現(xiàn)麼。
說(shuō)明:beanName的生成其實(shí)和你注冊(cè)Bean的方式有關(guān),比如@Import、Scan方式是不一樣的,這里就不展開(kāi)討論了,知道有這個(gè)差異就成。
進(jìn)階:Spring下普通內(nèi)部類(lèi)表現(xiàn)如何?
我們知道,從內(nèi)聚性上來(lái)說(shuō),普通內(nèi)部類(lèi)似乎也可以達(dá)到目的。但是相較于靜態(tài)內(nèi)部類(lèi)在Spring容器內(nèi)對(duì)優(yōu)先級(jí)的問(wèn)題,它的表現(xiàn)可就沒(méi)這么好嘍。基于以上例子,把所有的static關(guān)鍵字去掉,就是本處需要的case。
reRun測(cè)試程序,結(jié)果輸出:
A_OuterConfig init...OuterConfig init...Z_OuterConfig init...
A_OuterConfig InnerConfig init...A_OuterConfig a_i_bean init...A_OuterConfig PInnerConfig init...A_OuterConfig a_p_bean init...A_OuterConfig a_o_bean init...
InnerConfig init...Daughter init...PInnerConfig init...son init...Parent init...
Z_OuterConfig InnerConfig init...Z_OuterConfig z_i_bean init...Z_OuterConfig PInnerConfig init...Z_OuterConfig z_p_bean init...Z_OuterConfig z_o_bean init...
對(duì)于這個(gè)結(jié)果A哥不用再做詳盡分析了,看似比較復(fù)雜其實(shí)有了上面的分析還是比較容易理解的。主要有如下兩點(diǎn)需要注意:
普通內(nèi)部類(lèi)它不是一個(gè)獨(dú)立的類(lèi)(也就是說(shuō)isIndependent() = false),所以它并不能像靜態(tài)內(nèi)部類(lèi)那樣預(yù)先就被掃描進(jìn)去,如圖結(jié)果展示:
普通內(nèi)部類(lèi)初始化之前,一定得先初始化外部類(lèi),所以類(lèi)本身的優(yōu)先級(jí)是低于外部類(lèi)的(不包含@Bean方法哦)普通內(nèi)部類(lèi)屬于外部類(lèi)的memberClasses,因此它會(huì)在解析當(dāng)前外部類(lèi)的第一步processMemberClasses()時(shí)被解析普通內(nèi)部類(lèi)的beanName和靜態(tài)內(nèi)部類(lèi)是有差異的,如下截圖:
思考題:
請(qǐng)思考:為何使用普通內(nèi)部類(lèi)得到的是這個(gè)結(jié)果呢?建議copy我的demo,自行走一遍流程,多動(dòng)手總是好的
總結(jié)
本文一如既往的很干哈。寫(xiě)本文的原動(dòng)力是因?yàn)檎娴奶嘈』锇樵诳碨pring Boot自動(dòng)配置類(lèi)的時(shí)候,無(wú)法理解為毛它有些@Bean配置要單獨(dú)寫(xiě)在一個(gè)static靜態(tài)類(lèi)里面,感覺(jué)挺費(fèi)事;方法前直接價(jià)格static不香嗎?通過(guò)這篇文章 + 上篇文章的解讀,相信A哥已經(jīng)給了你答案了。
到此這篇關(guān)于static關(guān)鍵字有何魔法?竟讓Spring Boot搞出那么多靜態(tài)內(nèi)部類(lèi)(推薦)的文章就介紹到這了,更多相關(guān)Spring Boot靜態(tài)內(nèi)部類(lèi)內(nèi)容請(qǐng)搜索好吧啦網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持好吧啦網(wǎng)!
相關(guān)文章:
1. Python如何實(shí)現(xiàn)感知器的邏輯電路2. JS實(shí)現(xiàn)表單中點(diǎn)擊小眼睛顯示隱藏密碼框中的密碼3. JS錯(cuò)誤處理與調(diào)試操作實(shí)例分析4. asp讀取xml文件和記數(shù)5. python基于scrapy爬取京東筆記本電腦數(shù)據(jù)并進(jìn)行簡(jiǎn)單處理和分析6. 原生js實(shí)現(xiàn)的觀察者和訂閱者模式簡(jiǎn)單示例7. Python ellipsis 的用法詳解8. 在終端啟動(dòng)Python時(shí)報(bào)錯(cuò)的解決方案9. vue實(shí)現(xiàn)移動(dòng)端返回頂部10. xml中的空格之完全解說(shuō)
