热议:【Spring源码】- 04 扩展点之BeanFactoryPostProcessor

2023-03-29 13:23:47 腾讯云

之前分析IoC容器的启动流程时,夹杂在启动流程中我们发现Spring给我们提供了大量的扩展点,基于这些扩展点我们就可以实现很多灵活的功能定制需求。这篇我们首先来看下BeanFactoryPostProcessor这个扩展点,它是非常重要的一个扩展点,面向IoC容器进行扩展。

类结构

BeanFactoryPostProcessorBeanFactory的后置处理器,针对BeanFactory实现各种功能扩展。BeanFactoryPostProcessor又分为两类:BeanFactoryPostProcessorBeanDefinitionRegistryPostProcessorBeanDefinitionRegistryPostProcessor继承了BeanFactoryPostProcessor接口,对其进行了扩展。


【资料图】

执行时机

前面分析IoC容器启动流程中,refresh()方法中invokeBeanFactoryPostProcessors(beanFactory)处触发BeanFactoryPostProcessor扩展类的执行。

执行逻辑总结:1、先执行BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry方法,其中BeanDefinitionRegistryPostProcessor执行优先级如下:a、addBeanFactoryPostProcessor()传入到优先级最高,因为不需要实例化,直接可以获取到对象进行执行; b、然后从IoC容器中获取PriorityOrdered接口的BeanDefinitionRegistryPostProcessor,实例化并排序后执行postProcessBeanDefinitionRegistry方法 c、然后从IoC容器中获取Ordered接口的BeanDefinitionRegistryPostProcessor,实例化并排序后执行postProcessBeanDefinitionRegistry方法 d、然后从IoC容器中获取剩余的BeanDefinitionRegistryPostProcessor,实例化后执行postProcessBeanDefinitionRegistry方法;注意这个处理步骤存在一个循环,主要是存在执行前面的BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry方法时,存在可能会向IoC容器中注册新的BeanDefinitionRegistryPostProcessor,通过循环保证都会被执行;2、然后执行BeanDefinitionRegistryPostProcessor#postProcessBeanFactory方法,执行顺序参照步骤1中执行顺序;3、最后才会执行BeanFactoryPostProcessor#postProcessBeanFactory,执行优先级和BeanDefinitionRegistryPostProcessor一致: a、addBeanFactoryPostProcessor()传入到优先级最高,因为不需要实例化,直接可以获取到对象进行执行; b、然后从IoC容器中获取PriorityOrdered接口的BeanFactoryPostProcessor,实例化并排序后执行postProcessBeanFactory方法 c、然后从IoC容器中获取Ordered接口的BeanFactoryPostProcessor,实例化并排序后执行postProcessBeanFactory方法 d、然后从IoC容器中获取剩余的BeanFactoryPostProcessor,实例化后执行postProcessBeanFactory方法

总结两句话:

1、不同方法执行优先级:`BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry` > `BeanDefinitionRegistryPostProcessor#postProcessBeanFactory` > `BeanFactoryPostProcessor#postProcessBeanFactory`;
2、同方法执行优先级:`addBeanFactoryPostProcessor` > `PriorityOrdered` > `Ordered` > 非排序

使用场景

这两个接口都比较简单,都只定义一个方法。首先看下BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry这个方法,主要使用场景是:向IoC容器中注册BeanDefinition。这里面有个非常重要的实现类:ConfigurationClassPostProcessor,完成各种将Bean解析成BeanDefinition注入到IoC容器功能。

BeanDefinitionRegistryPostProcessor

案例一

首先,我们来看个例子,自定义BeanDefinition方式注册到IoC容器中。

1、定义一个Bean

public class TestService02 { private TestService01 testService01; @Autowired private TestService03 testService03; private String name;     //省略setter、toString方法}

2、由于该Bean没有@Component等注解,所以我们可以定义一个BeanDefinitionRegistryPostProcessor扩展注册到IoC容器中:

@Componentpublic class MyBeanFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor { @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {  System.out.println("===MyBeanFactoryPostProcessor#postProcessBeanDefinitionRegistry===");  //创建一个BeanDefinition  RootBeanDefinition beanDefinition = new RootBeanDefinition(TestService02.class);  //获取MutablePropertyValues,用于添加依赖注入  MutablePropertyValues pvs = beanDefinition.getPropertyValues();  //为依赖注入指定固定值方式  pvs.addPropertyValue("name", "张三");  //为依赖注入指定beanName,自动将beanName指向IoC中的Bean注入进去  pvs.addPropertyValue("testService01", new RuntimeBeanReference("testService01"));  //将生成的BeanDefinition注册到IoC容器中  registry.registerBeanDefinition("testService02", beanDefinition); } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {  System.out.println("===MyBeanFactoryPostProcessor#postProcessBeanFactory==="); }}

3、输出结果:

myConfigtestService01testService03myBeanFactoryPostProcessortestService02===TestService02属性信息===TestService02{testService01=xxx.bean.TestService01@56ef9176, testService03=xxx.bean.TestService03@4566e5bd, name="张三"}

从输出结果可以看到,TestService02已被注册到IoC容器中,且其属性字段都已成功进行依赖注入。通过这种方式,我们就可以更加灵活的向IoC容器注册Bean,实现各种功能的扩展。

案例二

我们可以模拟@ComponentScan@Component注解方式实现:将指定路径下的带有指定注解的Bean注册到IoC容器中。

1、首先,模仿@ComponentScan@Component定义两个注解:

@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Indexedpublic @interface MyComponent { String value() default "";}
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)@Documentedpublic @interface MyComponentScan { @AliasFor("basePackages") String[] value() default {}; @AliasFor("value") String[] basePackages() default {}; Class[] basePackageClasses() default {};}

2、定义一个BeanDefinitionRegistryPostProcessor实现类:

@Componentpublic class MyBeanFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor { private List basePackageList = new ArrayList<>(); @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {  System.out.println("===MyBeanFactoryPostProcessor#postProcessBeanFactory==="); } @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {  Arrays.stream(registry.getBeanDefinitionNames()).forEach(x -> parseMyComponentScan(registry.getBeanDefinition(x)));  if(basePackageList.isEmpty()){//没有扫描路径情况   return;  }  //定义一个BeanDefinitionScanner,扫描指定路径下Bean  ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, false);  scanner.addIncludeFilter(new AnnotationTypeFilter(MyComponent.class));  scanner.setIncludeAnnotationConfig(false);  String[] basePackages = basePackageList.stream().toArray(String[]::new);  int beanCount = scanner.scan(basePackages);  System.out.println("注册成功:"+beanCount); } /**  * 遍历所有的BeanDefinition,然后解析出有@MyComponentScan注解信息,并提取basePackageClasses属性值,  * 解析成扫描路径存储到basePackageList集合中  * @param beanDefinition  */ private void parseMyComponentScan(BeanDefinition beanDefinition){  if(AnnotatedBeanDefinition.class.isAssignableFrom(beanDefinition.getClass())){   AnnotatedBeanDefinition bdf = (AnnotatedBeanDefinition) beanDefinition;   Class beanClass;   try {    beanClass = Class.forName(bdf.getBeanClassName());    AnnotationMetadata metadata = AnnotationMetadata.introspect(beanClass);    Map config = metadata.getAnnotationAttributes(MyComponentScan.class.getName());    if(config != null && config.containsKey("basePackageClasses")){     Object obj = config.get("basePackageClasses");     Class clz = ((Class[]) obj)[0];     basePackageList.add(clz.getPackage().getName());    }   } catch (Throwable e) {    e.printStackTrace();   }  } }}

3、测试:

@Configuration@ComponentScan(basePackageClasses = MyConfig.class)@MyComponentScan(basePackageClasses = MyConfig.class)public class MyConfig {}

@ComponentScan可以把常规的@Component注册到IoC容器中,@MyComponentScan可以把指定包路径下带有@MyComponent注解的Bean注册到IoC容器中。

BeanFactoryPostProcessor

上面我们利用BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry扩展方法实现了@MyComponentScan@MyComponent注解功能,实现将指定包路径下带有@MyComponent注解的Bean注册到IoC容器中。BeanDefinitionRegistryPostProcessor接口中还有一个继承父接口BeanFactoryPostProcessor中的方法:postProcessBeanFactory

上一个案例实现了@MyComponent注解的Bean注册到IoC功能,现在我们来实现对所有@MyComponent注解的类进行AOP增强。

1、定义一个增强类:

public class MyMethodInterceptor implements MethodInterceptor {    @Override    public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy)            throws Throwable {        try {            before(method);//前置通知            Object ret = methodProxy.invokeSuper(obj, args);//目标方法执行            after(method, ret);//后置通知            return ret;        } catch (Exception e) {            exception();//异常通知        } finally {            afterReturning();//方法返回通知        }        return null;    } //前置增强    private void before(Method method) {        System.out.printf("before execute:%s\r\n", method.getName());    } //后置增强    private void after(Method method, Object ret) {        System.out.printf("after execute:%s, ret:%s\r\n", method.getName(), ret);    } //异常增强    private void exception() {        System.out.println("execute failure");    } //after返回增强    private void afterReturning() {        System.out.println("execute finish");    }}

2、基于Spring Enhancer工具类,实现一个传入Class,创建一个带有增强逻辑的Class返回出来工具类:

public class EnhancerUtil  { //定义增强实例 private static final Callback[] CALLBACKS = new Callback[] {   new MyMethodInterceptor(),   NoOp.INSTANCE }; private static final Class[] CALLBACKTYPES = new Class[] {   new MyMethodInterceptor().getClass(),   NoOp.INSTANCE.getClass() }; public static Class createProxyClass(Class targetClz){  Enhancer enhancer =  new Enhancer();  //基于继承方式代理:设置代理类的父类,就是目标对象,创建出来的对象就是目标对象的子类  enhancer.setSuperclass(targetClz);  //设置回调过滤器,返回值是callbacks数组的下标  enhancer.setCallbackFilter(new CallbackFilter() {   @Override   public int accept(Method method) {    /**     * 屏蔽掉Object类中定义方法的拦截     */    if (method.getDeclaringClass() == targetClz) {     return 0;    }    return 1;   }  });  /**   * NoOp回调把对方法调用直接委派给这个方法在父类中的实现,即可理解为真实对象直接调用方法   */  enhancer.setCallbackTypes(CALLBACKTYPES);  //设置类加载器  enhancer.setClassLoader(targetClz.getClassLoader());  //创建代理对象  Class clz = enhancer.createClass();  Enhancer.registerStaticCallbacks(clz, CALLBACKS);  return clz; }}

3、下面我们来实现BeanFactoryPostProcessor#postProcessBeanFactory方法:

@Componentpublic class MyBeanFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor { private List basePackageList = new ArrayList<>(); /**  * 基于该扩展实现AOP增强  * @param beanFactory  * @throws BeansException  */ @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {        //遍历IoC中BeanDefinition  Arrays.stream(beanFactory.getBeanDefinitionNames())    .forEach(x -> enhancer(beanFactory.getBeanDefinition(x))); }    /**  * 判断beanDefinition是否有@MyComponent注解,如果有则调用EnhancerUtil.createProxyClass创建一个增强后的类,  * 并替换到BeanDefinition中的beanClass  * @param beanDefinition  */ private void enhancer(BeanDefinition beanDefinition){  if(ScannedGenericBeanDefinition.class.isAssignableFrom(beanDefinition.getClass())){   ScannedGenericBeanDefinition bdf = (ScannedGenericBeanDefinition) beanDefinition;   try {    String beanClassName = bdf.getBeanClassName();    Class beanClass = Class.forName(beanClassName);    if (beanClass != null){     AnnotationMetadata metadata = AnnotationMetadata.introspect(beanClass);     if(metadata.hasAnnotation(MyComponent.class.getName())){      Class enhancedClass = EnhancerUtil.createProxyClass(beanClass);      if(enhancedClass != beanClass){       bdf.setBeanClass(enhancedClass);      }     }    }   } catch (Throwable e) {    e.printStackTrace();   }  } }}

4、定义Bean

@MyComponentpublic class TestService02 { public String hi(String name){  System.out.println("===TestService02#hi===");  return "hello,"+name; }}

5、context.getBean(TestService02.class).testService02.hi("zhangsan")调用方法时,会发现方法前后都已被加入增强逻辑:

before execute:hi===TestService02#hi===after execute:hi, ret:hello,zhangsanexecute finish返回结果:hello,zhangsan

总结

通过使用场景案例分析,我们对BeanFactoryPostProcessor扩展点的使用大致情况应该有了一些了解:

BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry这个扩展点主要用于实现向IoC容器中注册Bean相关的功能扩展。比如上面案例中我们九利用该扩展点实现了自定义扫描包路径下自定义注解,并向IoC容器中注册BeanBeanFactoryPostProcessor#postProcessBeanFactory这个扩展点主要用于实现将注册到IoC容器中BeanDefinition进行相关操作。以为这个方法执行时机是在postProcessBeanDefinitionRegistry之后,这时Bean都已经以BeanDefinition样式被注册到IoC容器中,这时就可以对这些BeanDefinition进行操作。比如案例中我们获取到BeanDefinition中的beanClass属性,并使用Enhancer工具类创建AOP代理类并设置进去,就实现了Spring中的AOP功能。

标签:

相关文章

热议:【Spring源码】- 04 扩展点之BeanFactoryPostProcessor

之前分析IoC容器的启动流程时,夹杂在启动流程中我们发现Spring给我们提供了大量的扩展点,基于这些扩展...

2023-03-29

北京有人抢孩子?警方通报 环球快看点

据@平安北京丰台通报:针对网传“丰台区某小区附近遭遇陌生人抢孩子”情况,丰台警方经工作,已将嫌疑人...

2023-03-29

宣城:全面掀起“皖南川藏线”旅游新热潮 独家焦点

宣城:全面掀起“皖南川藏线”旅游新热潮

2023-03-29

上海拍牌技巧几秒出价(上海拍牌技巧)|世界焦点

1、众人代拍回答您:一、准备篇  器材准备:首先推荐用快而稳的网络,个人感觉还是一兆以上的ADSL比较...

2023-03-29

中牟县官渡镇中心中学开展防溺水活动启动仪式

随着天气的逐渐转热,暑期、汛期的即将到来,学生户外活动增多,防溺水再次面临严峻形势。为有效提高我...

2023-03-29

天天即时:“我要跳楼,你们会来救我吗?”“当然会!” 这通电话救了一位花季少女

我们可以帮助你。王柳阳马上冷静并耐心开导“是遇到什么问题吗。在接警员的耐心引导和询问下,小侯透露...

2023-03-29

世界快消息!南方基金管理股份有限公司关于旗下基金投资关联方承销可转换公司债券的关联交易公告

根据《公开募集证券投资基金运作管理办法》、《公开募集证券投资基金信息披露管理办法》及相关法律法规...

2023-03-29

扩容增类公募REITs资产拼图持续延展

扩容增类公募REITs资产拼图持续延展:据上证报,首批两只新能源REITs于3月28日上市,为REITs市场带来全...

2023-03-29

庆生文案短句_庆生-焦点热讯

1、是庆祝生日的意思。2、为某人庆生就是庆祝×××生日。本文分享完毕,希望对大家有所帮助。

2023-03-29

广联达加密锁盗版的能用吗?-zol问答_盗版广联达加密锁升级

1、这就需要看广联达此次升级的版本变动大不大,还有你所买的盗版狗技术含量如何,一般来说是要升级的。...

2023-03-28

财报速递:海南矿业2022年全年净利润6.15亿元,总体财务状况不佳

3月29日,A股上市公司海南矿业发布2022年全年业绩报告。其中,净利润6 15亿元,同比下降29 66%。根据...

2023-03-28

厦门保障房单身申请条件 动态

保障性商品房【保商房】申请购买保障性商品房的单身居民应当在本市无住房且符合以下基本条件之一:本市...

2023-03-28

钱塘江大潮时间和退潮时间查询_钱塘江大潮时间

1、每年的农历八月十八前后,是观潮的最佳时节。2、这期间,秋阳朗照,金风宜人,钱塘江口的海塘上,游...

2023-03-28

科创新源:在数据中心温控领域,公司已针对数据中心的散热需求进行了散热结构件的产品开发和技术储备,目前尚处于初期阶段

同花顺(300033)金融研究中心3月28日讯,有投资者向科创新源(300731)提问,请问贵公司有数据中心温控...

2023-03-28

全国中成药集采征求意见 报价降幅大于40%可获拟中选资格 环球视讯

全国中成药联合采购办公室发布《全国中成药采购联盟集中采购文件(征求意见稿)》。此次中成药集采覆盖复方

2023-03-28

民生银行济南天桥支行开展人民币宣传活动

民生银行济南天桥支行开展人民币宣传活动,天桥,央行,人民币,民生银行,金融知识

2023-03-28

日本财务大臣:日本银行几乎没有AT1债券被全额减记的风险

日本财务大臣铃木俊一表示,日本银行的AT1债券没有导致瑞信170亿美元AT1债券被全额减记的条款。

2023-03-28

上海“最美地铁口”迎最佳赏樱期,新增足球元素打造春日“独一份” 世界看点

三月的申城,春暖花开,市民纷纷外出游玩赏花。上海地铁8号线虹口足球场站6号出入口有着“最美地铁口”...

2023-03-28

世界热消息:卓越商企服务郭莹:积极谨慎推进收并购,补强战略目标区域

乐居财经王彦杰3月28日,卓越商企服务召开2022年度业绩会。总经理、执行董事郭莹表示,公司会以积极谨慎...

2023-03-28

琼粤闽男子排球邀请赛在儋州举行|当前速讯

近日,儋州市体育中心排球馆举行琼粤闽男子排球邀请赛。本次比赛采用九人制,先打循环赛,后按积分决出...

2023-03-28

云南省碳资产交易管理有限公司揭牌 世界快报

3月27日,云南省碳资产交易管理有限公司揭牌仪式暨数字双碳助力产业绿色发展论坛在曲靖市举行。云南省碳...

2023-03-28

深海鲈鱼制作方法_冷冻海鲈鱼的家常做法

1、材料:鲈鱼1条,葱20g,蒜20g,姜20g,香菜15g,盐20g,料酒100g,酱油20g,蚝油10g,糖10g,

2023-03-28

世界今头条!矿泉水桶(矿泉水桶尺寸是多大)

本文目录一览:1、矿泉水桶的尺寸是多少(矿泉水桶尺寸大小)2、矿泉水桶含塑化剂吗3、如何割矿泉水桶4

2023-03-28

“五大行动”推动工业企业数字化转型“全覆盖”

本报讯(记者柳姗姗彭冰)3月23日,记者从2023年长春市加快工业企业数字化转型升级大会暨春季高峰论坛获...

2023-03-28

原油,来到底部附近 全球今日讯

原油,来到底部附近,原油价格,美国页岩油,全球石油需求,石油输出国组织

2023-03-27

广元这个地方有点童话里的味道,清早的光线很巴适!-世界速看料

广元这个地方有点童话里的味道,清早的光线很巴适!

2023-03-27

今日精选:《寻光之旅》3月28日公测倒计时1天

〈裁判球广播台相关权限已解锁!〉〈读取指令激活……Loading……〉〈公测倒计时读取成功!〉亲爱的参赛...

2023-03-27

世界报道:钵施然主板IPO审核状态更新为“已问询”

机会报从上海证券交易所网站获悉,钵施然主板IPO审核状态更新为“已问询”,更新时间为2023年03月26日,...

2023-03-27

世界快播:美国联邦存款保险公司:第一公民银行将收购硅谷银行

上证报中国证券网讯当地时间3月26日,美国联邦存款保险公司(FDIC)发布公告称,第一公民银行达成收购硅...

2023-03-27

克尔维特C8Z06比HuracanEVO慢但在四分之一英里处更快

兰博基尼(跑车)诞生于不喜欢法拉利的拖拉机制造商费鲁乔和离合器平庸的昂贵汽车制造商恩佐的仇恨之子。...

2023-03-27

最新发布
精彩图文
精彩推送

Copyright @  2015-2018 世纪it网版权所有  备案号:京ICP备2021034106号-22   联系邮箱:55 16 53 8@qq.com