原創:微信公衆號 【阿Q說代碼】,歡迎分享,轉載請保留出處。
一提到Spring
,大家最先想到的是啥?是AOP
和IOC
的兩大特性?是Spring
中Bean
的初始化流程?還是基於Spring
的Spring Cloud
全家桶呢?
今天我們就從Spring
的IOC
特性入手,聊一聊Spring
中把Bean
注入Spring
容器的幾種方式。
我們先來簡單瞭解下IOC
的概念:IOC
即控制反轉,也稱爲依賴注入,是指將對象的創建或者依賴關係的引用從具體的對象控制轉爲框架或者IOC
容器來完成,也就是依賴對象的獲得被反轉了。
可以簡單理解爲原來由我們來創建對象,現在由
Spring
來創建並控制對象。
xml 方式
依稀記得最早接觸Spring
的時候,用的還是SSH
框架,不知道大家對這個還有印象嗎?所有的bean
的注入得依靠xml
文件來完成。
它的注入方式分爲:set
方法注入、構造方法注入、字段注入,而注入類型分爲值類型注入(8種基本數據類型)和引用類型注入(將依賴對象注入)。
以下是set
方法注入的簡單樣例
<bean name="teacher" class="org.springframework.demo.model.Teacher">
<property name="name" value="阿Q"></property>
</bean>
對應的實體類代碼
public class Teacher {
private String name;
public void setName(String name) {
this.name = name;
}
}
xml方式存在的缺點如下:
-
xml
文件配置起來比較麻煩,既要維護代碼又要維護配置文件,開發效率低; - 項目中配置文件過多,維護起來比較困難;
- 程序編譯期間無法對配置項的正確性進行驗證,只能在運行期發現並且出錯之後不易排查;
- 解析
xml
時,無論是將xml
一次性裝進內存,還是一行一行解析,都會佔用內存資源,影響性能。
註解方式
隨着Spring
的發展,Spring 2.5
開始出現了一系列註解,除了我們經常使用的@Controller、@Service、@Repository、@Component 之外,還有一些比較常用的方式,接下來我們簡單瞭解下。
@Configuration + @Bean
當我們需要引入第三方的jar
包時,可以用@Bean
註解來標註,同時需要搭配@Configuration
來使用。
@Configuration
用來聲明一個配置類,可以理解爲xml
的<beans>
標籤@Bean
用來聲明一個bean
,將其加入到Spring
容器中,可以理解爲xml
的<bean>
標籤
簡單樣例:將 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
我們在翻看Spring
源碼的過程中,經常會看到@Import
註解,它也可以用來將第三方jar
包注入Spring
,但是它只可以作用在類上。
例如在註解EnableSpringConfigured
上就包含了@Import
註解,用於將SpringConfiguredConfiguration
配置文件加載進Spring
容器。
@Import(SpringConfiguredConfiguration.class)
public @interface EnableSpringConfigured {}
@Import
的value
值是一個數組,一個一個注入比較繁瑣,因此我們可以搭配ImportSelector
接口來使用,用法如下:
@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
方法返回的數組就會通過@Import
註解注入到Spring
容器中。
無獨有偶,ImportBeanDefinitionRegistrar
接口也爲我們提供了注入bean
的方法。
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
......
}
我們點擊AspectJAutoProxyRegistrar
類,發現它實現了ImportBeanDefinitionRegistrar
接口,它的registerBeanDefinitions
方法便是注入bean
的過程,可以參考下。
如果覺得源代碼比較難懂,可以看一下我們自定義的類
@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);
// 註冊 Bean,並指定bean的名稱和類型
registry.registerBeanDefinition("teacher", tDefinition);
}
}
}
這樣我們就把Teacher
類注入到Spring
容器中了。
FactoryBean
提到FactoryBean
,就不得不與BeanFactory
比較一番。
-
BeanFactory
: 是Factory
,IOC
容器或者對象工廠,所有的Bean
都由它進行管理 -
FactoryBean
: 是Bean
,是一個能產生或者修飾對象生成的工廠Bean
,實現與工廠模式和修飾器模式類似
那麼FactoryBean
是如何實現bean
注入的呢?
先定義實現了FactoryBean
接口的類
public class TeacherFactoryBean implements FactoryBean<Teacher> {
/**
* 返回此工廠管理的對象實例
**/
@Override
public Teacher getObject() throws Exception {
return new Teacher();
}
/**
* 返回此 FactoryBean 創建的對象的類型
**/
@Override
public Class<?> getObjectType() {
return Teacher.class;
}
}
然後通過 @Configuration + @Bean的方式將TeacherFactoryBean
加入到容器中
@Configuration
public class MyConfig {
@Bean
public TeacherFactoryBean teacherFactoryBean(){
return new TeacherFactoryBean();
}
}
注意:我們沒有向容器中注入Teacher
, 而是直接注入的TeacherFactoryBean
,然後從容器中拿Teacher
這個類型的bean
,成功運行。
BDRegistryPostProcessor
看到這個接口,不知道對於翻看過Spring
源碼的你來說熟不熟悉。如果不熟悉的話請往下看,要是熟悉的話就再看一遍吧😃。
源碼
public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
// 註冊bean到spring容器中
void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;
}
@FunctionalInterface
public interface BeanFactoryPostProcessor {
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}
BeanFactoryPostProcessor
接口是BeanFactory
的後置處理器,方法postProcessBeanFactory
對bean
的定義進行控制。今天我們重點來看看postProcessBeanDefinitionRegistry
方法:它的參數是BeanDefinitionRegistry
,顧名思義就是與BeanDefinition
註冊相關的。
通過觀察該類,我們發現它裏邊包含了registerBeanDefinition
方法,這個不就是我們想要的嗎?爲了能更好的使用該接口來達到注入bean
的目的,我們先來看看Spring
是如何操作此接口的。
看下invokeBeanFactoryPostProcessors
方法,會發現沒有實現PriorityOrdered
和Ordered
的bean
(這種跟我們自定義的實現類有關)會執行以下代碼。
while (reiterate) {
......
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
......
}
進入該方法
private static void invokeBeanDefinitionRegistryPostProcessors(
Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors,
BeanDefinitionRegistry registry) {
for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessBeanDefinitionRegistry(registry);
}
}
會發現實現了BeanDefinitionRegistryPostProcessor
接口的bean
,其postProcessBeanDefinitionRegistry
方法會被調用,也就是說如果我們自定義接口實現該接口,它的postProcessBeanDefinitionRegistry
方法也會被執行。
實戰
話不多說,直接上代碼。自定義接口實現類
public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
/**
* 初始化過程中先執行
**/
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(Teacher.class);
//Teacher 的定義註冊到spring容器中
registry.registerBeanDefinition("teacher", rootBeanDefinition);
}
/**
* 初始化過程中後執行
**/
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {}
}
啓動類代碼
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
MyBeanDefinitionRegistryPostProcessor postProcessor = new MyBeanDefinitionRegistryPostProcessor();
//將自定義實現類加入 Spring 容器
context.addBeanFactoryPostProcessor(postProcessor);
context.refresh();
Teacher bean = context.getBean(Teacher.class);
System.out.println(bean);
}
啓動並打印結果
org.springframework.demo.model.Teacher@2473d930
發現已經注入到Spring
容器中了。
以上就是我們總結的幾種將bean
注入Spring
容器的方式,趕快行動起來實戰演練一下吧!
阿Q將持續更新java實戰方面的文章,感興趣的可以關注下,也可以來技術羣討論問題呦!