spring常用扩展点小记

前言

最近看spring cloud netflix和spring cloud alibaba的一些代码,发现自己有个不太清楚的地方是关于spring的一个常用扩展点的认识和体系。这个扩展点体现在如何能够在spring容器中动态注册、修改和增强bean。在我看了一些代码和文档之后总结了一些常用的组件和基本的用法与场景,给自己做个记录,也给大家一个参考。
本文内容大致可以参考下图:


spring扩展点.

扩展点一览

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有AutowiredAnnotationBeanPostProcessorAbstractAdvisorAutoProxyCreator。为了说明一些简单地用法,我们也可以用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

用代码描述了一下各自的功能与相关的使用场景,欢迎有问题的同学留言交流。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容