一提到Spring,大家最先想到的是啥?是AOP和IOC的兩大特性?是Spring中Bean的初始化流程?還是基於Spring的Spring Cloud全家桶呢? 今天我們就從Spring的IOC特性入手,聊一聊Spring中把Bean註入Spring容器的幾種方式。 我們先來簡單瞭解下IOC的概念 ...
一提到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
源碼的你來說熟不熟悉。如果不熟悉的話請往下看,要是熟悉的話就再看一遍吧