原創(chuàng):微信公眾號(hào) 【阿Q說(shuō)代碼】凝赛,歡迎分享,轉(zhuǎn)載請(qǐng)保留出處坛缕。
一提到Spring
墓猎,大家最先想到的是啥?是AOP
和IOC
的兩大特性赚楚?是Spring
中Bean
的初始化流程毙沾?還是基于Spring
的Spring Cloud
全家桶呢?
今天我們就從Spring
的IOC
特性入手宠页,聊一聊Spring
中把Bean
注入Spring
容器的幾種方式左胞。
我們先來(lái)簡(jiǎn)單了解下IOC
的概念:IOC
即控制反轉(zhuǎn),也稱為依賴注入举户,是指將對(duì)象的創(chuàng)建或者依賴關(guān)系的引用從具體的對(duì)象控制轉(zhuǎn)為框架或者IOC
容器來(lái)完成烤宙,也就是依賴對(duì)象的獲得被反轉(zhuǎn)了。
可以簡(jiǎn)單理解為原來(lái)由我們來(lái)創(chuàng)建對(duì)象俭嘁,現(xiàn)在由
Spring
來(lái)創(chuàng)建并控制對(duì)象躺枕。
xml 方式
依稀記得最早接觸Spring
的時(shí)候,用的還是SSH
框架,不知道大家對(duì)這個(gè)還有印象嗎屯远?所有的bean
的注入得依靠xml
文件來(lái)完成蔓姚。
它的注入方式分為:set
方法注入、構(gòu)造方法注入慨丐、字段注入坡脐,而注入類型分為值類型注入(8種基本數(shù)據(jù)類型)和引用類型注入(將依賴對(duì)象注入)。
以下是set
方法注入的簡(jiǎn)單樣例
<bean name="teacher" class="org.springframework.demo.model.Teacher">
<property name="name" value="阿Q"></property>
</bean>
對(duì)應(yīng)的實(shí)體類代碼
public class Teacher {
private String name;
public void setName(String name) {
this.name = name;
}
}
xml方式存在的缺點(diǎn)如下:
-
xml
文件配置起來(lái)比較麻煩房揭,既要維護(hù)代碼又要維護(hù)配置文件备闲,開(kāi)發(fā)效率低; - 項(xiàng)目中配置文件過(guò)多捅暴,維護(hù)起來(lái)比較困難恬砂;
- 程序編譯期間無(wú)法對(duì)配置項(xiàng)的正確性進(jìn)行驗(yàn)證,只能在運(yùn)行期發(fā)現(xiàn)并且出錯(cuò)之后不易排查蓬痒;
- 解析
xml
時(shí)泻骤,無(wú)論是將xml
一次性裝進(jìn)內(nèi)存,還是一行一行解析梧奢,都會(huì)占用內(nèi)存資源狱掂,影響性能。
注解方式
隨著Spring
的發(fā)展亲轨,Spring 2.5
開(kāi)始出現(xiàn)了一系列注解趋惨,除了我們經(jīng)常使用的@Controller、@Service惦蚊、@Repository器虾、@Component 之外,還有一些比較常用的方式蹦锋,接下來(lái)我們簡(jiǎn)單了解下兆沙。
@Configuration + @Bean
當(dāng)我們需要引入第三方的jar
包時(shí),可以用@Bean
注解來(lái)標(biāo)注莉掂,同時(shí)需要搭配@Configuration
來(lái)使用葛圃。
@Configuration
用來(lái)聲明一個(gè)配置類,可以理解為xml
的<beans>
標(biāo)簽@Bean
用來(lái)聲明一個(gè)bean
巫湘,將其加入到Spring
容器中,可以理解為xml
的<bean>
標(biāo)簽
簡(jiǎn)單樣例:將 RedisTemplate 注入 Spring
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
......
return redisTemplate;
}
}
@Import
我們?cè)诜?code>Spring源碼的過(guò)程中昏鹃,經(jīng)常會(huì)看到@Import
注解尚氛,它也可以用來(lái)將第三方jar
包注入Spring
,但是它只可以作用在類上洞渤。
例如在注解EnableSpringConfigured
上就包含了@Import
注解阅嘶,用于將SpringConfiguredConfiguration
配置文件加載進(jìn)Spring
容器。
@Import(SpringConfiguredConfiguration.class)
public @interface EnableSpringConfigured {}
@Import
的value
值是一個(gè)數(shù)組,一個(gè)一個(gè)注入比較繁瑣讯柔,因此我們可以搭配ImportSelector
接口來(lái)使用抡蛙,用法如下:
@Configuration
@Import(MyImportSelector.class)
public class MyConfig {}
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
return new String[]{"org.springframework.demo.model.Teacher","org.springframework.demo.model.Student"};
}
}
其中selectImports
方法返回的數(shù)組就會(huì)通過(guò)@Import
注解注入到Spring
容器中。
無(wú)獨(dú)有偶魂迄,ImportBeanDefinitionRegistrar
接口也為我們提供了注入bean
的方法粗截。
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
......
}
我們點(diǎn)擊AspectJAutoProxyRegistrar
類,發(fā)現(xiàn)它實(shí)現(xiàn)了ImportBeanDefinitionRegistrar
接口捣炬,它的registerBeanDefinitions
方法便是注入bean
的過(guò)程熊昌,可以參考下。
如果覺(jué)得源代碼比較難懂湿酸,可以看一下我們自定義的類
@Configuration
@Import(value = {MyImportBeanDefinitionRegistrar.class})
public class MyConfig {}
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
RootBeanDefinition tDefinition = new RootBeanDefinition(Teacher.class);
// 注冊(cè) Bean婿屹,并指定bean的名稱和類型
registry.registerBeanDefinition("teacher", tDefinition);
}
}
}
這樣我們就把Teacher
類注入到Spring
容器中了。
FactoryBean
提到FactoryBean
推溃,就不得不與BeanFactory
比較一番昂利。
-
BeanFactory
: 是Factory
,IOC
容器或者對(duì)象工廠铁坎,所有的Bean
都由它進(jìn)行管理 -
FactoryBean
: 是Bean
蜂奸,是一個(gè)能產(chǎn)生或者修飾對(duì)象生成的工廠Bean
,實(shí)現(xiàn)與工廠模式和修飾器模式類似
那么FactoryBean
是如何實(shí)現(xiàn)bean
注入的呢厢呵?
先定義實(shí)現(xiàn)了FactoryBean
接口的類
public class TeacherFactoryBean implements FactoryBean<Teacher> {
/**
* 返回此工廠管理的對(duì)象實(shí)例
**/
@Override
public Teacher getObject() throws Exception {
return new Teacher();
}
/**
* 返回此 FactoryBean 創(chuàng)建的對(duì)象的類型
**/
@Override
public Class<?> getObjectType() {
return Teacher.class;
}
}
然后通過(guò) @Configuration + @Bean的方式將TeacherFactoryBean
加入到容器中
@Configuration
public class MyConfig {
@Bean
public TeacherFactoryBean teacherFactoryBean(){
return new TeacherFactoryBean();
}
}
注意:我們沒(méi)有向容器中注入Teacher
, 而是直接注入的TeacherFactoryBean
窝撵,然后從容器中拿Teacher
這個(gè)類型的bean
,成功運(yùn)行襟铭。
BDRegistryPostProcessor
看到這個(gè)接口碌奉,不知道對(duì)于翻看過(guò)Spring
源碼的你來(lái)說(shuō)熟不熟悉。如果不熟悉的話請(qǐng)往下看寒砖,要是熟悉的話就再看一遍吧??赐劣。
源碼
public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
// 注冊(cè)bean到spring容器中
void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;
}
@FunctionalInterface
public interface BeanFactoryPostProcessor {
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}
BeanFactoryPostProcessor
接口是BeanFactory
的后置處理器,方法postProcessBeanFactory
對(duì)bean
的定義進(jìn)行控制哩都。今天我們重點(diǎn)來(lái)看看postProcessBeanDefinitionRegistry
方法:它的參數(shù)是BeanDefinitionRegistry
魁兼,顧名思義就是與BeanDefinition
注冊(cè)相關(guān)的。
通過(guò)觀察該類漠嵌,我們發(fā)現(xiàn)它里邊包含了registerBeanDefinition
方法咐汞,這個(gè)不就是我們想要的嗎?為了能更好的使用該接口來(lái)達(dá)到注入bean
的目的儒鹿,我們先來(lái)看看Spring
是如何操作此接口的化撕。
看下invokeBeanFactoryPostProcessors
方法,會(huì)發(fā)現(xiàn)沒(méi)有實(shí)現(xiàn)PriorityOrdered
和Ordered
的bean
(這種跟我們自定義的實(shí)現(xiàn)類有關(guān))會(huì)執(zhí)行以下代碼约炎。
while (reiterate) {
......
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
......
}
進(jìn)入該方法
private static void invokeBeanDefinitionRegistryPostProcessors(
Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors,
BeanDefinitionRegistry registry) {
for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessBeanDefinitionRegistry(registry);
}
}
會(huì)發(fā)現(xiàn)實(shí)現(xiàn)了BeanDefinitionRegistryPostProcessor
接口的bean
植阴,其postProcessBeanDefinitionRegistry
方法會(huì)被調(diào)用蟹瘾,也就是說(shuō)如果我們自定義接口實(shí)現(xiàn)該接口,它的postProcessBeanDefinitionRegistry
方法也會(huì)被執(zhí)行掠手。
實(shí)戰(zhàn)
話不多說(shuō)憾朴,直接上代碼。自定義接口實(shí)現(xiàn)類
public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
/**
* 初始化過(guò)程中先執(zhí)行
**/
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(Teacher.class);
//Teacher 的定義注冊(cè)到spring容器中
registry.registerBeanDefinition("teacher", rootBeanDefinition);
}
/**
* 初始化過(guò)程中后執(zhí)行
**/
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {}
}
啟動(dòng)類代碼
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
MyBeanDefinitionRegistryPostProcessor postProcessor = new MyBeanDefinitionRegistryPostProcessor();
//將自定義實(shí)現(xiàn)類加入 Spring 容器
context.addBeanFactoryPostProcessor(postProcessor);
context.refresh();
Teacher bean = context.getBean(Teacher.class);
System.out.println(bean);
}
啟動(dòng)并打印結(jié)果
org.springframework.demo.model.Teacher@2473d930
發(fā)現(xiàn)已經(jīng)注入到Spring
容器中了喷鸽。
以上就是我們總結(jié)的幾種將bean
注入Spring
容器的方式众雷,趕快行動(dòng)起來(lái)實(shí)戰(zhàn)演練一下吧!
阿Q將持續(xù)更新java實(shí)戰(zhàn)方面的文章魁衙,感興趣的可以關(guān)注下报腔,也可以來(lái)技術(shù)群討論問(wèn)題呦!