前言
最近看spring cloud netflix和spring cloud alibaba的一些代码,发现自己有个不太清楚的地方是关于spring的一个常用扩展点的认识和体系。这个扩展点体现在如何能够在spring容器中动态注册、修改和增强bean。在我看了一些代码和文档之后总结了一些常用的组件和基本的用法与场景,给自己做个记录,也给大家一个参考。
本文内容大致可以参考下图:
扩展点一览
ImportBeanDefinitionRegistrar
这个类需要与@Import
和@Configuration
共同配合使用。
一般来说@Import
可以导入三种bean
- 普通的bean class
- ImportSelector 这个类可以通过自定义一些条件来控制classpath中需要导入的class
- ImportBeanDefinitionRegistrar 这个类可以通过代码来动态加载bean,这些bean可以是普通的定义好的class也可以是动态代理。
通过查看代码我们可以知道,spring cloud中的一些常用的注解,包括@EnableFeignClients
,@EnableDubboConfig
等都是通过ImportBeanDefinitionRegistrar来动态注入的服务调用类到spring容器里面。因此,我们就明确了这个类算是一个比较重要的spring扩展点。
为了搞清楚它的用法,我们就模拟@EnableFeignClients
来做一个动态注入的例子(在本文中实际上是个伪动态,只是说明原理)。
准备工作
我们先定义一个注解来作为注入标识,类似于@FeignClient
:
package com.roger.springtest.annotation;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TestUtil {
}
然后我们定义一个接口,并用之前我们写的@TestUtil
注解装饰,模拟一个客户端调用声明:
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();
}
然后我们实现一个TestInferface(单词拼错了大家无视)当做我们客户端调用的实现(模拟动态代理生成FeignClient调用):
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);
}
}
然后我们实现一个自己的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) {
// 创建一个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())){
// 就将实现类TestImpl当做bean class 添加到beanDefinitionRegistry
// 方便后面容器启动创建bean的时候创建出来
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(){
// 创建一个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;
}
}
然后我们自己创建一个controller,并把我们的客户端调用通过@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的动态注入是否成功。
最后我们来看启动类:
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导入进去。最终我们启动,测试:
bingo,说明我们的注入成功了,返回了我们TestImpl实现类的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可以在容器初始化创建bean之前读他们的元数据信息并能够修改它。在spring framework中,一个比较典型的例子就是PropertySourcesPlaceholderConfigurer
,它能通过阅读bean的元信息并结合配置属性源来修改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的元数据,读取并修改了属性,将hello属性的值改为hello world,这样就让我们再通过http请求访问调用hello方法的时候就能看到我们修改后的值了。我们再通过我们的ImportBeanDefinitionRegistrar来把我们的MyBeanFactoryPostProcessor注册上去:
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
// 创建一个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())){
// 就将实现类TestImpl当做bean class 添加到beanDefinitionRegistry
// 方便后面容器启动创建bean的时候创建出来
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
现在到最后一个组件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之后调用的,通过它可以完成自定义的解析实例化逻辑。在spring framework中,比较知名的BeanPostProcessor有AutowiredAnnotationBeanPostProcessor
和AbstractAdvisorAutoProxyCreator
。为了说明一些简单地用法,我们也可以用BeanPostProcessor做个类似AOP的应用,先看代码:
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) {
// 创建一个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())){
// 就将实现类TestImpl当做bean class 添加到beanDefinitionRegistry
// 方便后面容器启动创建bean的时候创建出来
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
这一段标识BeanPostProcessor的两个方法分别在bean的实例化之后的初始化方法前后执行。
接着我们再来看看执行的时候的日志信息:
before invoke
invoke hello
after invoke
这个也应印证了动态代理的执行。
后记
本文主要探讨和记录了spring中比较常用的扩展点:
- ImportBeanDefinitionRegistrar
- BeanFactoryPostProcessor
- BeanPostProcessor
用代码描述了一下各自的功能与相关的使用场景,欢迎有问题的同学留言交流。