Spring拓展點的使用
開篇
- 本篇文章是分析Spring源碼基礎的第二篇文章章,第一篇文章請看 Spring源碼解析之BeanDefinition
- 這篇文章主要講解Spring在創建工廠和實例化Bean時用到的相關的拓展點,以便在後邊分析Spring源碼時大家知道那些拓展點都有什麼功能
閱讀本篇文章你可以獲得什麼
- Spring中提供了哪幾種拓展點
- Spring中提供的拓展點的使用
爲什麼要學習Spring拓展點
- 一些高級的面試提會出現
- 項目中我們基於擴展點可以做開發
- 只有明白各種拓展點才能真正明白Spring源碼
常用的拓展點的分類
- Bean工廠的後置處理器:實現了BeanFactoryPostProcessor及其子類的實現類
- Bean的後置處理器:實現了BeanPostProcessor的實現類
- Import相關:可以是一個類或者實現了某些接口的類
Bean工廠的後置處理器
Bean工廠的後置處理器:實現了BeanFactoryPostProcessor及其子類的實現類
什麼是Bean工廠的後置處理器?
- 允許程序員自定義修改應用程序上下文的bean定義信息,調整上下文的底層bean工廠的bean屬性值。
- 應用程序上下文在初始化時可以在它們的bean定義中自動檢測BeanFactoryPostProcessor類型的bean,並在創建任何其他bean之前先應用它們。
注意:
BeanFactoryPostProcessor只可以與bean定義進行交互並對其進行修改,但不能與bean實例進行交互。
上邊是說了Bean工廠後置處理器的定義與執行時機,下邊來做一下實際的使用
實現了BeanFactoryPostProcessor的實現類
上篇文章在講解BeanDefinition時,講解到了一個屬性AutowireCandidate,如果我們有這樣一個需求,就是要把工廠中所有的BeanDefinition中的AutowireCandidate改爲false。我們應該怎麼做到呢?通過實現BeanFactoryPostProcessor就能很簡單的達成這個效果。代碼如下:
@Test
public void testBeanFactoryPostProcessorModifyBD() {
//初始化
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.registerBeanDefinition("tb1", new RootBeanDefinition(TestBean.class));
context.registerBeanDefinition("my", new RootBeanDefinition(MyBeanFactoryPostProcessor.class));
//刷新 執行後置處理器
context.refresh();
//獲取修改後的定義信息
BeanDefinition beanDefinition = context.getBeanDefinition("tb1");
//執行完之後不會報錯
// assertFalse(beanDefinition.isAutowireCandidate());
//執行完之後會報錯 因爲值已經修改成來fase
assertTrue(beanDefinition.isAutowireCandidate());
}
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
//取到容器中所有的Bean定義的名稱
String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames();
BeanDefinition beanDefinition = null;
for (int i = 0; i < beanDefinitionNames.length; i++) {
//根據名稱獲取BeanDefinition
beanDefinition = beanFactory.getBeanDefinition(beanDefinitionNames[i]);
beanDefinition.setAutowireCandidate(false);
}
}
}
上邊的場景可能不是很常見,但是如果你讀過Mybatis-Spring的源碼你應該也能看到這個類。這個類完成了修改BeanClass類型和自動注入的類型,這個源碼我在別的文章中提過,感興趣可以看一下 spring和mybatis整合爲什麼只定義了接口?爲什麼設置自動裝配模型爲BY_TYPE這邊文章。來更好的體會一下BeanFactoryPostProcessor這個接口的作用。
實現了BeanDefinitionRegistryPostProcessor的實現類
它和BeanFactoryPostProcessor有什麼不同?
BeanDefinitionRegistryPostProcessor繼承了BeanFactoryPostProcessor接口,並增加了自己的接口方法。在使用上來說,BeanDefinitionRegistryPostProcessor不只能修改Bean的定義信息,還能動態的向容器中添加定義信息。
現在有這麼一個場景,如果程序中存在類A的定義信息,則把類B添加到容器中。那要怎麼做呢?暫時不考慮使用@ConditionOnClass的解決方案,代碼如下:
@Test
public void testBeanDefinitionRegistryPostProcessorAddBD() {
//初始化
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.registerBeanDefinition("my", new RootBeanDefinition(MyBeanDefinitionRegistryPostProcessor.class));
context.registerBeanDefinition("a", new RootBeanDefinition(TestBean.class));
//刷新 執行後置處理器
context.refresh();
//獲取修改後的定義信息
assertNotNull(context.getBeanDefinition("b"));
// 把 context.registerBeanDefinition("a", new RootBeanDefinition(TestBean.class)); 注視後 驗證爲null
// assertNull(context.getBeanDefinition("b"));
}
public static class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
if (registry.containsBeanDefinition("a")) {
registry.registerBeanDefinition("b", new RootBeanDefinition(ListeningBean.class));
}
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
//這個方法是父類方法 如果要修改bean的定義信息 可以重寫
}
}
上邊就是Bean工廠的後置處理器的一個簡單使用流程。以後在分析源碼的時候會發現Spring底層是怎麼使用這種後置處理器來進行工廠的初始化的。尤其主要的是ConfigurationClassPostProcessor
這個明星類的處理邏輯,後邊會有專門的章節進行表述。
Bean的後置處理器
上邊介紹Bean工廠的後置處理器時,特意強調了一下BeanFactoryPostProcessor只能作用域BD而不能作用在一個Bean上,那如果我們要對Bean進行特殊的處理呢?那就引入了我們現在要將的接口BeanPostProcessor
什麼是Bean的後置處理器?
- 工廠鉤子,允許自定義新的bean實例,例如使用代理包裝它們。
- ApplicationContexts可以在它們的bean定義中自動檢測BeanPostProcessor類型的bean,並將它們應用於隨後創建的任何bean。
實現了BeanPostProcessor接口的後置處理器
BeanPostProcessor接口中有兩個方法:
public interface BeanPostProcessor {
@Nullable
default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Nullable
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
-
一般來說,如果你有屬性需要填充,或者bean實例化完成之後需要做些什麼可以重寫
postProcessBeforeInitialization
方法,最典型的應用場景是我們實現了ApplicationContextAware
接口,就可以注入給我們一個一個context對象。 -
如果你要對某個類進行包裝,一般是重寫
postProcessAfterInitialization
這個方法,在我們實際工作中最典型的例子是AOP的使用,最後返回代理對象時就是藉助於這個方法。
沒有想到太好的例子就以Spring中ApplicationContextAwareProcessor爲例進行講解,代碼如下:
class ApplicationContextAwareProcessor implements BeanPostProcessor {
private final ConfigurableApplicationContext applicationContext;
private final StringValueResolver embeddedValueResolver;
/**
* Create a new ApplicationContextAwareProcessor for the given context.
*/
public ApplicationContextAwareProcessor(ConfigurableApplicationContext applicationContext) {
this.applicationContext = applicationContext;
this.embeddedValueResolver = new EmbeddedValueResolver(applicationContext.getBeanFactory());
}
@Override
@Nullable
public Object postProcessBeforeInitialization(final Object bean, String beanName) throws BeansException {
AccessControlContext acc = null;
if (System.getSecurityManager() != null &&
(bean instanceof EnvironmentAware || bean instanceof EmbeddedValueResolverAware ||
bean instanceof ResourceLoaderAware || bean instanceof ApplicationEventPublisherAware ||
bean instanceof MessageSourceAware || bean instanceof ApplicationContextAware)) {
acc = this.applicationContext.getBeanFactory().getAccessControlContext();
}
if (acc != null) {
AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
invokeAwareInterfaces(bean);
return null;
}, acc);
}
else {
invokeAwareInterfaces(bean);
}
return bean;
}
private void invokeAwareInterfaces(Object bean) {
if (bean instanceof Aware) {
if (bean instanceof EnvironmentAware) {
((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());
}
if (bean instanceof EmbeddedValueResolverAware) {
((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver);
}
if (bean instanceof ResourceLoaderAware) {
((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);
}
if (bean instanceof ApplicationEventPublisherAware) {
((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);
}
if (bean instanceof MessageSourceAware) {
((MessageSourceAware) bean).setMessageSource(this.applicationContext);
}
if (bean instanceof ApplicationContextAware) {
((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
}
}
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
return bean;
}
}
看代碼我們可以發現 它主要重寫了postProcessBeforeInitialization這個方法。
在方法中判斷有沒有實現了aware接口,如果實現了則進行調用,把環境變量,或者上下文通過方法注入。
這個方法的執行時機是當對象被初始化完成且調用完屬性填充後,便會執行這個方法。
我們自己實現一個重寫了postProcessAfterInitialization
方法的類,對我們產生的類進行代理,在方法調用前後進行日誌打印。這裏我們只看方法的使用,先忽略這種業務場景合不合理,代碼如下:
public class BeanPostProcessorTests {
@Test
public void logTest() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(TestDoImpl.class);
applicationContext.register(MyBeanPostProcessor.class);
applicationContext.refresh();
TestDo testDo = applicationContext.getBean(TestDo.class);
testDo.doSome();
}
}
interface TestDo {
void doSome();
}
class TestDoImpl implements TestDo {
public void doSome() {
System.out.println("TestDo:doSome");
}
}
class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof TestDo) {
return Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{TestDo.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("先執行日誌打印log");
return method.invoke(bean, args);
}
});
} else {
return bean;
}
}
}
執行結果:
先執行日誌打印log
TestDo:doSome
可以看到現在工廠中存在的就是我們自己產生的代理對象了,實際上aop也是這麼做的,只是沒有像我們把代碼寫的這麼死。
@Import導入
這部分我們來說一下Spring常用的最後一類拓展點通過Import導入,相信大家對這個註解並不陌生。
我們向容器中添加一個BeanDefinition有很多種方式
- 通過xml配置
- 通過api形式顯示註冊
- 通過包掃描的方式掃描指定路徑下的Bean
- 還有就是通過@Import方式進行導入
所以可以說@Import是用來向容器中添加BeanDefinition最後會被實例化成爲Bean
來看一下@Import的代碼:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
/**
* {@link Configuration @Configuration}, {@link ImportSelector},
* {@link ImportBeanDefinitionRegistrar}, or regular component classes to import.
*/
Class<?>[] value();
}
可以看到通過value可以是三種類型的值
- 普通類
- ImportSelector類型的類
- ImportBeanDefinitionRegistrar類型的類
Import一個普通類
直接針對當前的class創建BD,放入到BeanDefinitionMap中後期用來實例化當前類型的Bean。
@Test
public void testImportAnnotation() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(ConfigurationWithImportAnnotation.class);
context.refresh();
////說明 產生了對象
assertNotNull(context.getBean(TestBean1.class));
}
@Import(TestBean1.class)
static class ConfigurationWithImportAnnotation {
}
static class TestBean1{
}
Import一個實現了ImportSelector接口的類
實現了ImportSelector的類需要重寫selectImports方法。在Spring內部會通過反射實例化對象調用這個方法獲取返回值,然後通過解析器把我們傳的全限定名根據類加載加載出來。也是先變成beanDefinition最後編程bean。具體用法如下
@Test
public void testImportAnnotation() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(ConfigurationWithImportAnnotation.class);
context.refresh();
////說明 產生了對象
assertNotNull(context.getBean(TestBean2.class));
}
@Import(TestBeanImportSelector.class)
static class ConfigurationWithImportAnnotation {
}
static class TestBeanImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"org.springframework.context.annotation.configuration.ImportTests.TestBean2"};
}
}
static class TestBean2 {
}
當然我們還可以獲取Import的類上的註解元數據。來做一些判斷進行加不加如特定的類。
Import一個實現了ImportBeanDefinitionRegistrar接口的類
這個接口可以讓我們直接獲取到BeanDefinitionRegistry
註冊器,可以根據其他條件動態的想容器中注入BeanDefinition。示例代碼如下:
@Test
public void testImportAnnotation() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(ConfigurationWithImportAnnotation.class);
context.refresh();
////說明 產生了對象
assertNotNull(context.getBean(TestBean2.class));
}
@Import(TestImportBeanDefinitionRegistrar.class)
static class ConfigurationWithImportAnnotation {
}
static class TestImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
registry.registerBeanDefinition("testBean2", new RootBeanDefinition(TestBean2.class));
}
}
static class TestBean2 {
}
當然可以進階使用,先判斷有沒有某個定義信息,有的話則不添加,僞代碼如下:
static class TestImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
if (!registry.containsBeanDefinition("a")) {
registry.registerBeanDefinition("testBean2", new RootBeanDefinition(TestBean2.class));
}
}
}
文章到現在爲止,已經說完了Spring常用的拓展點的使用。寫這篇文章的目的主要是爲了後邊介紹Spring源碼時知道這些有什麼作用,具體他們是怎麼實現的,會在源碼中講解