前言
最近看spring cloud netflix和spring cloud alibaba的一些代碼,發(fā)現(xiàn)自己有個不太清楚的地方是關(guān)于spring的一個常用擴(kuò)展點的認(rèn)識和體系。這個擴(kuò)展點體現(xiàn)在如何能夠在spring容器中動態(tài)注冊、修改和增強(qiáng)bean。在我看了一些代碼和文檔之后總結(jié)了一些常用的組件和基本的用法與場景,給自己做個記錄,也給大家一個參考。
本文內(nèi)容大致可以參考下圖:
擴(kuò)展點一覽
ImportBeanDefinitionRegistrar
這個類需要與@Import
和@Configuration
共同配合使用。
一般來說@Import
可以導(dǎo)入三種bean
- 普通的bean class
- ImportSelector 這個類可以通過自定義一些條件來控制classpath中需要導(dǎo)入的class
- ImportBeanDefinitionRegistrar 這個類可以通過代碼來動態(tài)加載bean,這些bean可以是普通的定義好的class也可以是動態(tài)代理。
通過查看代碼我們可以知道,spring cloud中的一些常用的注解,包括@EnableFeignClients
,@EnableDubboConfig
等都是通過ImportBeanDefinitionRegistrar來動態(tài)注入的服務(wù)調(diào)用類到spring容器里面。因此,我們就明確了這個類算是一個比較重要的spring擴(kuò)展點。
為了搞清楚它的用法,我們就模擬@EnableFeignClients
來做一個動態(tài)注入的例子(在本文中實際上是個偽動態(tài),只是說明原理)。
準(zhǔn)備工作
我們先定義一個注解來作為注入標(biāo)識,類似于@FeignClient
:
package com.roger.springtest.annotation;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TestUtil {
}
然后我們定義一個接口,并用之前我們寫的@TestUtil
注解裝飾,模擬一個客戶端調(diào)用聲明:
package com.roger.springtest.api;
import com.roger.springtest.annotation.TestUtil;
/**
* TestInferface
*
* @author Yuanqing Luo
* @since 2019/3/19
*/
@TestUtil
public interface TestInferface {
String hello();
}
然后我們實現(xiàn)一個TestInferface(單詞拼錯了大家無視)當(dāng)做我們客戶端調(diào)用的實現(xiàn)(模擬動態(tài)代理生成FeignClient調(diào)用):
package com.roger.springtest.impl;
import com.roger.springtest.api.TestInferface;
import org.springframework.beans.factory.InitializingBean;
/**
* TestImpl
*
* @author Yuanqing Luo
* @since 2019/3/19
*/
public class TestImpl implements TestInferface, InitializingBean {
private String hello = "hello";
@Override
public String hello() {
System.out.println("invoke hello");
return hello;
}
public String getHello() {
return hello;
}
public void setHello(String hello) {
this.hello = hello;
}
public TestImpl(){
System.out.println("hello in contructor:" + hello);
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("init method invoked, hello:" + hello);
}
}
然后我們實現(xiàn)一個自己的ImportBeanDefinitionRegistrar :
package com.roger.springtest.configuration;
import com.roger.springtest.annotation.TestUtil;
import com.roger.springtest.api.TestInferface;
import com.roger.springtest.impl.TestImpl;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.util.ClassUtils;
/**
* MyImportRegistrator
*
* @author Yuanqing Luo
* @since 2019/3/19
*/
public class MyImportRegistrator implements ImportBeanDefinitionRegistrar, EnvironmentAware {
private Environment environment;
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
// 創(chuàng)建一個classpath的scanner
ClassPathScanningCandidateComponentProvider scanner = getScanner();
// 添加一個掃描的攔截器,只讓被TestUtil注解裝飾的class過
scanner.addIncludeFilter(new AnnotationTypeFilter(TestUtil.class));
for(BeanDefinition beanDefinition : scanner.findCandidateComponents(ClassUtils.getPackageName(annotationMetadata.getClassName()))){
// 對于掃描出來的BeanDefinition,如果class是TestInferface
if(beanDefinition.getBeanClassName().equals(TestInferface.class.getCanonicalName())){
// 就將實現(xiàn)類TestImpl當(dāng)做bean class 添加到beanDefinitionRegistry
// 方便后面容器啟動創(chuàng)建bean的時候創(chuàng)建出來
beanDefinition.setBeanClassName(TestImpl.class.getCanonicalName());
beanDefinitionRegistry.registerBeanDefinition(ClassUtils.getShortName(TestInferface.class), beanDefinition);
}
}
/*
GenericBeanDefinition beanPostFactoryPostProcessor = new GenericBeanDefinition();
beanPostFactoryPostProcessor.setBeanClass(MyBeanFactoryPostProcessor.class);
beanDefinitionRegistry.registerBeanDefinition("myBeanPostFactoryPostProcessor", beanPostFactoryPostProcessor);
GenericBeanDefinition beanPostProcessor = new GenericBeanDefinition();
beanPostProcessor.setBeanClass(MyBeanPostProcessor.class);
beanDefinitionRegistry.registerBeanDefinition("myBeanPostProcessor", beanPostProcessor);
*/
}
private ClassPathScanningCandidateComponentProvider getScanner(){
// 創(chuàng)建一個class path scanner
return new ClassPathScanningCandidateComponentProvider(false, environment){
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
// 只要候選的class是個interface就讓他過
return beanDefinition.getMetadata().isInterface();
}
};
}
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
}
然后我們自己創(chuàng)建一個controller,并把我們的客戶端調(diào)用通過@Autowired
注入:
package com.roger.springtest.controller;
import com.roger.springtest.api.TestInferface;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* TestController
*
* @author Yuanqing Luo
* @since 2019/3/19
*/
@RestController
public class TestController {
@Autowired
TestInferface testInferface;
@GetMapping("/test")
public String get(){
return testInferface.hello();
}
}
這個controller的目的是為了測試基于ImportBeanDefinitionRegistrar的動態(tài)注入是否成功。
最后我們來看啟動類:
package com.roger.springtest;
import com.roger.springtest.configuration.MyImportRegistrator;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Import;
/**
* Application
*
* @author Yuanqing Luo
* @since 2019/3/19
*/
@SpringBootApplication
@Import(MyImportRegistrator.class)
public class Application {
public static void main(String[] args){
SpringApplication.run(Application.class, args);
}
}
這里就把我們的ImportBeanDefinitionRegistrar導(dǎo)入進(jìn)去。最終我們啟動,測試:
bingo,說明我們的注入成功了,返回了我們TestImpl實現(xiàn)類的hello字符串。
BeanFactoryPostProcessor
按spring core文檔的描述
BeanFactoryPostProcessor operates on the bean configuration metadata. That is, the Spring IoC container lets a BeanFactoryPostProcessor read the configuration metadata and potentially change it before the container instantiates any beans other than BeanFactoryPostProcessor instances.
這個描述比較清楚了,BeanFactoryPostProcessor可以在容器初始化創(chuàng)建bean之前讀他們的元數(shù)據(jù)信息并能夠修改它。在spring framework中,一個比較典型的例子就是PropertySourcesPlaceholderConfigurer
,它能通過閱讀bean的元信息并結(jié)合配置屬性源來修改bean definition來完成配置屬性注入的功能。我們這里也簡單地模擬來做一個類似的:
package com.roger.springtest.configuration;
import com.roger.springtest.api.TestInferface;
import org.springframework.beans.BeansException;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.util.ClassUtils;
/**
* MyBeanFactoryPostProcessor
*
* @author Yuanqing Luo
* @since 2019/3/20
*/
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
BeanDefinition beanDefinition = configurableListableBeanFactory.getBeanDefinition(ClassUtils.getShortName(TestInferface.class));
MutablePropertyValues mpv = beanDefinition.getPropertyValues();
mpv.add("hello", "hello world");
}
}
這里我們實際上就是獲取了TestImpl的元數(shù)據(jù),讀取并修改了屬性,將hello屬性的值改為hello world,這樣就讓我們再通過http請求訪問調(diào)用hello方法的時候就能看到我們修改后的值了。我們再通過我們的ImportBeanDefinitionRegistrar來把我們的MyBeanFactoryPostProcessor注冊上去:
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
// 創(chuàng)建一個classpath的scanner
ClassPathScanningCandidateComponentProvider scanner = getScanner();
// 添加一個掃描的攔截器,只讓被TestUtil注解裝飾的class過
scanner.addIncludeFilter(new AnnotationTypeFilter(TestUtil.class));
for(BeanDefinition beanDefinition : scanner.findCandidateComponents(ClassUtils.getPackageName(annotationMetadata.getClassName()))){
// 對于掃描出來的BeanDefinition,如果class是TestInferface
if(beanDefinition.getBeanClassName().equals(TestInferface.class.getCanonicalName())){
// 就將實現(xiàn)類TestImpl當(dāng)做bean class 添加到beanDefinitionRegistry
// 方便后面容器啟動創(chuàng)建bean的時候創(chuàng)建出來
beanDefinition.setBeanClassName(TestImpl.class.getCanonicalName());
beanDefinitionRegistry.registerBeanDefinition(ClassUtils.getShortName(TestInferface.class), beanDefinition);
}
}
// 注入beanFactoryPostProcessor
GenericBeanDefinition beanPostFactoryPostProcessor = new GenericBeanDefinition();
beanPostFactoryPostProcessor.setBeanClass(MyBeanFactoryPostProcessor.class);
beanDefinitionRegistry.registerBeanDefinition("myBeanPostFactoryPostProcessor", beanPostFactoryPostProcessor);
/*
GenericBeanDefinition beanPostProcessor = new GenericBeanDefinition();
beanPostProcessor.setBeanClass(MyBeanPostProcessor.class);
beanDefinitionRegistry.registerBeanDefinition("myBeanPostProcessor", beanPostProcessor);
*/
}
走一個:
完美的毫無懸念。
BeanPostProcessor
現(xiàn)在到最后一個組件BeanPostProcessor,我們先來看看文檔描述:
The BeanPostProcessor interface defines callback methods that you can implement to provide your own (or override the container’s default) instantiation logic, dependency resolution logic, and so forth. If you want to implement some custom logic after the Spring container finishes instantiating, configuring, and initializing a bean, you can plug in one or more custom BeanPostProcessor implementations.
我們可以看出來,BeanPostProcessor是在容器實例化bean之后調(diào)用的,通過它可以完成自定義的解析實例化邏輯。在spring framework中,比較知名的BeanPostProcessor有AutowiredAnnotationBeanPostProcessor
和AbstractAdvisorAutoProxyCreator
。為了說明一些簡單地用法,我們也可以用BeanPostProcessor做個類似AOP的應(yīng)用,先看代碼:
package com.roger.springtest.configuration;
import com.roger.springtest.api.TestInferface;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.config.BeanPostProcessor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* MyBeanPostProcessor
*
* @author Yuanqing Luo
* @since 2019/3/20
*/
public class MyBeanPostProcessor implements BeanPostProcessor, BeanClassLoaderAware {
private ClassLoader classLoader;
@Override
public Object postProcessBeforeInitialization(Object bean, String name) throws BeansException {
if(bean instanceof TestInferface){
System.out.println("invoke before initialization");
}
return bean;
}
@Override
public Object postProcessAfterInitialization(final Object bean, String name) throws BeansException {
if(bean instanceof TestInferface){
System.out.println("invoke after initialization");
TestInferface newProxy = (TestInferface) Proxy.newProxyInstance(classLoader, new Class[]{TestInferface.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before invoke");
Object result = method.invoke(bean, args);
if(method.getName().equals("hello")){
result = result.toString() + " from proxy";
}
System.out.println("after invoke");
return result;
}
});
return newProxy;
}
return bean;
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
}
然后我們再把這個BeanPostProcessor也注冊上去
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
// 創(chuàng)建一個classpath的scanner
ClassPathScanningCandidateComponentProvider scanner = getScanner();
// 添加一個掃描的攔截器,只讓被TestUtil注解裝飾的class過
scanner.addIncludeFilter(new AnnotationTypeFilter(TestUtil.class));
for(BeanDefinition beanDefinition : scanner.findCandidateComponents(ClassUtils.getPackageName(annotationMetadata.getClassName()))){
// 對于掃描出來的BeanDefinition,如果class是TestInferface
if(beanDefinition.getBeanClassName().equals(TestInferface.class.getCanonicalName())){
// 就將實現(xiàn)類TestImpl當(dāng)做bean class 添加到beanDefinitionRegistry
// 方便后面容器啟動創(chuàng)建bean的時候創(chuàng)建出來
beanDefinition.setBeanClassName(TestImpl.class.getCanonicalName());
beanDefinitionRegistry.registerBeanDefinition(ClassUtils.getShortName(TestInferface.class), beanDefinition);
}
}
// 注入beanFactoryPostProcessor
GenericBeanDefinition beanPostFactoryPostProcessor = new GenericBeanDefinition();
beanPostFactoryPostProcessor.setBeanClass(MyBeanFactoryPostProcessor.class);
beanDefinitionRegistry.registerBeanDefinition("myBeanPostFactoryPostProcessor", beanPostFactoryPostProcessor);
// 注入beanPostProcessor
GenericBeanDefinition beanPostProcessor = new GenericBeanDefinition();
beanPostProcessor.setBeanClass(MyBeanPostProcessor.class);
beanDefinitionRegistry.registerBeanDefinition("myBeanPostProcessor", beanPostProcessor);
}
最終我們再來測試一下:
,這時候我們再來看看我們控制臺里面打出來的兩段日志信息:
hello in contructor:hello
invoke before initialization
init method invoked, hello:hello world
invoke after initialization
這一段標(biāo)識BeanPostProcessor的兩個方法分別在bean的實例化之后的初始化方法前后執(zhí)行。
接著我們再來看看執(zhí)行的時候的日志信息:
before invoke
invoke hello
after invoke
這個也應(yīng)印證了動態(tài)代理的執(zhí)行。
后記
本文主要探討和記錄了spring中比較常用的擴(kuò)展點:
- ImportBeanDefinitionRegistrar
- BeanFactoryPostProcessor
- BeanPostProcessor
用代碼描述了一下各自的功能與相關(guān)的使用場景,歡迎有問題的同學(xué)留言交流。