6. My自動裝配
看到這里又自然會產(chǎn)生疑問:不會吧,上面可都是自動裝配啊,我在配置文件或者使用注解都配置了變量的值,然后加個@Autowired注解就OK了,spring也是幫我自動去裝配。
再高端一點話,我就把XML文件寫成JavaConfig配置類,然后使用@Configuration注解,這樣也能自動裝配,這不是很nice了嗎?
6.1 自動裝配之再思考
我的理解,上面的自動裝配,我們至少要寫一個配置文件,無論是什么形式,我們都至少需要一個文件把它全部寫下來,就算這個文件的內(nèi)容是固定的,但是為了裝配這個對象,我們不得不寫。
我們甚至都可以做成模板了,比如我在學習spring框架整合時,把經(jīng)常寫的都搞成了模板:
有了這些模板,我們只需要點點點,再進行修改,就能用了。
這樣做確實很好,可是對于越來越成型的項目體系,我們每次都搞一些重復動作,是會厭煩的。而且面對這么多xml配置文件,我太難了。
于是我有了一個想說但不敢說的問題:
我一個配置文件都不想寫,程序還能照樣跑,我只關心有我需要的組件就可以了,我只需要關注我的目標就可以了, 我想打開一個工程之后可以1秒進入開發(fā)狀態(tài),而不是花3小時寫完配置文件(2.5小時找bug) 希望有個東西幫我把開始之前的準備工作全做了,即那些套路化的配置,這樣在我接完水之后回來就可以直接進行開發(fā)。
說到這里,想必大家都懂了:SpringBoot
6.2 一個例子
讓我們在偷懶的道路上繼續(xù)前進。
來看下面這個例子:
仍然是A類和B類,其中A類仍然引用了B類,我們給A類組件起id=“a”,B類組件起id=“b”
@Component("a")
public class A {
@Value("我是AAA")
private String name;
@Autowired
private B b;
}
@Component("b")
public class B {
@Value("我是BBB")
private String name;
}
可以看到我們使用了@Autowired注解來自動注入b,測試類如下:
@Test
public void test(){
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MyAutoConfig.class);
A aaa = ac.getBean("a", A.class);
System.out.println(aaa);
}
細心的同學已經(jīng)發(fā)現(xiàn)了:我們這里使用了AnnotationConfigApplicationContext這個JavaConfig配置類會使用到的加載類,于是我們順利成章地點開它所加載的MyAutoConfig類文件
文件內(nèi)容如下:
@Configuration
@MyEnableAutoConfig
public class MyAutoConfig {
// bean 都去哪了 ???
}
what? 我要聲明的Bean對象都去哪了(注意:這里的applicationContext.xml是空的)?
讓我們運行test:
A(name=我是AAA, b=B(name=我是BBB))
竟然運行成功了,這究竟是為什么?(元芳,你怎么看?)
細心的同學已經(jīng)發(fā)現(xiàn)了:@MyEnableAutoConfig是什么注解?我怎么沒有這個注解
讓我們點進@MyEnableAutoConfig一探究竟:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(MyImportSelector.class) // 導入bean定義
public @interface MyEnableAutoConfig {
}
原來如此!你是用了@Import注解導入了Bean定義對吧,注釋都寫著呢!
可是客官,@Import導入bean定義是沒錯,但是它導入的是MyImportSelector這個bean,不是A也不是B啊…
6.3 @Import注解
@Import的功能就是獲取某個類的bean對象,他的使用形式大致如下:
@Import(A.class)
@Import(MyImportBeanDefinitionRegister.class)
@Import(MyImportSelector.class)
6.3.1 @Import(A.class)
第一種形式@Import(A.class),是最簡單易懂的形式
我們需要哪個Bean定義,直接Import他的class即可
6.3.2 @Import(MyImportBeanDefinitionRegister.class)
第二種形式@Import(MyImportBeanDefinitionRegister.class)
傳遞了一個bean定義注冊器,這個注冊器的具體內(nèi)容如下:
public class MyImportBeanDefinitionRegister implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
RootBeanDefinition aDef = new RootBeanDefinition(A.class);
registry.registerBeanDefinition("a", aDef);
}
}
這個注冊器實現(xiàn)了ImportBeanDefinitionRegistrar接口,并且重寫了里面的registerBeanDefinitions方法
看他做了什么事:創(chuàng)建了一個新的bean定義,他的類型就是A,然后把這個bean定義注冊到BeanDefinitionMap(還記得吧?。├锩?,key值我們可以人為設置,這里就設置成"a"
這樣在傳遞一個注冊器的時候,我們就可以把注冊器中新增的bean定義注冊進來使用
6.3.3 @Import(MyImportSelector.class)
可以看到,這種使用方式就是我們剛才的注解中使用的方式
他傳遞了一個叫MyImportSelector的類,這個類依然是我們自己定義的,具體內(nèi)容如下:
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
// 導入配置類
return new String[]{"config.MyConfig"};
}
}
這個類實現(xiàn)了ImportSelector接口,并且重寫了selectImports方法,返回一個字符串數(shù)組
我們可以看到,返回的字符串數(shù)組中是我們要導入類的全類名
這個Importer返回的類如果是組件bean對象,就會被加載進來使用;如果是一個配置類,就會加載這個配置類
第三種和第二種的區(qū)別是第三種可以一次性寫很多類,而且比較簡潔,只需要清楚類的全包名即可。而第二種方式需要自己清楚包類名,手動創(chuàng)建bean定義,然后手動加入BeanDefinitionMap。
6.4 例子的研究
我們打開MyImportSelector,發(fā)現(xiàn)里面赫然寫著幾個大字:
return new String[]{"config.MyConfig"};
然后我們找到config.MyConfig類,發(fā)現(xiàn)這個類竟然就是我們剛才寫的JavaConfig版本的配置文件:
@Configuration
public class MyConfig {
@Bean
public A a(){
return new A();
}
@Bean
public B b(){
return new B();
}
}
加載這個MyConfig配置類,就相當于加載了A和B兩個Bean定義
喂!你是不是搞我!繞了一大圈,怎么還是加載這個配置文件?。∵@個配置文件明明就是我自己寫的。
總結一下,我們這個例子大概繞了這些過程:
6.5 將偷懶進行到底
"沒有會偷懶的人解決不掉的問題“ —— 魯迅
上面的例子也沒有多大優(yōu)化啊,我怎么覺得更加麻煩了?不但繞了一大圈,定義了許多新東西,到最后還是加載了我寫好的JavaConfig類,說到底我不是還在寫javaConfig類嗎…
但是你注意到?jīng)]有:有了上面的機制,我只需要把JavaConfig類寫一次,然后放在某個地方,在MyImportSelector中加入這個地方的全包名路徑,下次用的時候直接導入最頂層的MyAutoConfig類,所有有關這個部件我需要的東西,就全部自動整理好了,甚至比鼠標點點點添加代碼模板還要快!
我突然有了個很棒的想法,不知道你有了沒有 。
如果你開始有點感覺了,就會自然提出另一個問題:我這樣做確實可以提高效率,但是一段代碼里寫入我自己定制的內(nèi)容,每次更改起來不是太費勁了嗎?
想到這里,我就不禁回想起使用JDBC的時候,在代碼里改SQL語句的痛苦了,那真是生不如死…這種情況就構成了硬編碼的行為,是不好的。
我們自然會想到:要是我創(chuàng)建一個配置文件properties來專門保存我這個需求所使用的bean對象,然后使用的時候在MyImportSelector中讀取配置文件并且返回全包名,不就更加nice了嗎?
于是MyImportSelector中的代碼又改成了下面這樣:
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
Properties properties = MyPropertyReader.readPropertyForMe("/MyProperty.properties");
String strings = (String) properties.get(MyEnableAutoConfig.class.getName());
return new String[]{strings};
}
}
其中MyPropertyReader是我們自己新創(chuàng)建的用于讀取properties文件的工具類
之所以要自己再定義這樣一個工具類,是為了以后在其中可以做一些其他操作(比如:去重、預檢查)
public class MyPropertyReader {
public static Properties readPropertyForMe(String path){
Properties properties = new Properties();
try(InputStream sin = MyPropertyReader.class.getResourceAsStream(path)){
properties.load(sin);
}catch (IOException e){
e.printStackTrace();
System.out.println("讀取異常...");
}
return properties;
}
}
我們的配置文件里面這么寫:
anno.MyEnableAutoConfig=config.MyConfig
可以看到,key是注解@MyEnableAutoConfig的類名,也就是根據(jù)這個注解,就會導入后面的MyConfig這個Bean,這個Bean就是我們的配置文件
如此一來我們讀取這個配置文件,然后加載跟這個注解名稱相符的value(即JavaConfig配置文件),就相當于我們在代碼里手寫的"config.MyConfig",只不過現(xiàn)在的形式已經(jīng)發(fā)生了巨大的變化:我們添加或者刪除一個配件,完全只需要修改MyProperty.properties這個配置文件就行了!
至此,無論是添加或者刪除組件,無非是在配置文件中加上或者刪除一行的問題了。
讓我們在更新之后運行程序,可以看到成功拿到了配置文件的全類名
程序的運行當然也是沒問題的:
A(name=我是AAA, b=B(name=我是BBB))
到此,我仿佛又領悟了一些東西。。。
我的配置文件好像活了,在我需要的時候他會出現(xiàn),在我不需要的時候只需要在配置文件里面給他”打個叉“,他自己就跑開了
-
spring
+關注
關注
0文章
340瀏覽量
14344 -
源碼分析
+關注
關注
0文章
5瀏覽量
5552 -
自動裝配
+關注
關注
0文章
7瀏覽量
653
發(fā)布評論請先 登錄
相關推薦
評論