当前位置:首页>热门 > >正文

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

  • 2023-03-29 15:32:32来源:腾讯云

之前分析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给我们提供了大量的扩展点,基于这些扩展点我们就可以实现很多灵活的功能定制需

今日热闻!星战人物图集:曼达洛超级突击队

曼达洛超级突击队(MandalorianSuperCommando)领导者:达斯摩尔GarSaxon曼达洛超级突击队,也被称为曼达洛传统主义者,是在克隆人战争期间为

高新一中幼-每日热门

1、高新一淄博市高新区现在要加快发展,积极转型,淄博高新区“三区一窗口的”意思是指,高新区要成为高新技术产业和新经济集聚发展的主要...

吉林省长春市妇女法律援助工作站成立

日前,长春市司法局、长春市妇联联合成立长春市妇女法律援助工作站,该工作站的成立标志着长春市法律援助工作在维护妇女合法权益、促进社会和

分层分类兜住困难群众民生底线 我省健全完善社会救助体系

原标题:分层分类兜住困难群众民生底线我省健全完善社会救助体系记者从近日召开的云南省社会救助联席会议获悉,自2020年12月我省印发《关于改

无钴电池6月量产 蜂巢能源无钴正极材料下线|最新消息

,蜂巢能源无钴电池所需的核心材料——无钴正极材料开始在常州材料工厂正式批量下线。同时,根据此前该公司的发展规划,蜂巢能源

当前滚动:从消费品到城市文化载体,青岛啤酒打开城市文旅跃级的新窗口!

在“中国啤酒之都”,你的一天要怎样度过?不妨从一杯青岛啤酒中探寻答案。

碧蓝档案生盐诺亚美图推荐 当前聚焦

画师:几乎U糖投稿时间:2022年10月26日11:59作品lD:102243151画师ID:18593130作品尺寸:2000px*3044pxhttps: www pixiv net users 18593130http

甘油三酯高吃什么食物降得快_甘油三酯高吃什么食物

1、甘油三酯高的饮食不仅要少油少糖,还要多运动,还要知道经常吃哪些食物有助于降低甘油三酯。富含鱼油制剂或-3脂肪酸的食物

“手握”两家券商!这家A股巨亏近4亿_焦点速讯

3月28日晚间,广东锦龙发展股份(下称“锦龙股份”)披露了2022年年度报告,包括控股子公司中山证券和参股子公司东莞证券2022年业绩。综合来...

中医怎么认识高血脂|每日观察

现代医学认为,高血脂:血浆当中的血脂超过正常范围,也称之为脂质高,也叫做高脂血症。中医无高血脂之称叫。中医是怎么认识“高血脂”的,...

好孩子国际(01086.HK)年度收益82.92亿港元 同比减少14.4%

格隆汇3月28日丨好孩子国际(01086 HK)公告,截至2022年12月31日止年度,公司收益82 92亿港元,同比减少14 4%;母公司拥有人应占年内溢利3

23077期福彩3d分享:当获得小惊喜之后,不会发生好运连连的现象|世界热门

获得小惊喜是一件令人愉悦的事情,它可以给我们带来一瞬间的快乐和满足感。然而,有些人会把这种小惊喜和好运连连联系起来,认为一次小小的好

王牌对王牌中游戏规则(王牌对王牌中的游戏有哪些) 前沿热点

1、王牌对王牌的游戏名称为:开火车、拍七令、瞎子背瘸子、踩气球、传声筒游戏。2、开火车游戏规则:在开始之前,每个人说出一个地名,代表自

惠焕章编剧导演纪录片《走近易经》被多所大学列为课堂教学片_环球快讯

据多方面消息获悉,今年以来,有河北大学、西南大学等多个大学,将纪录片《走近易经》列为课堂教学片。其中,河北大学戏剧影视专

北京门头沟异地直接结算定点药店名单(附地址)

☞北京医保中洋大药房有限公司新桥分店地址:北京市门头沟区惠民家园二区26号楼底商104-2号☞北京同仁堂善和医药有限公司地址:北京市门头沟区

美国私立小学枪击事件现场视频

警方刚刚公布了枪手开车进入校园并持枪进入教学楼多处地点的监控视频

每日速看!宇通新能源轻卡T系列产品全球首发 越级而致开启中国新能源轻卡3.0时

宇通新能源轻卡T系列产品全球首发越级而致开启中国新能源轻卡3 0时今天的关注度非常高,直接上了热搜榜,那么具体的是什么

iPhone折旧率仅为安卓一半 世界即时

iPhone折旧率仅为安卓一半

环球速递!海南1人登上全国“见义勇为勇士榜”

商报全媒体讯(椰网 海拔新闻记者李兴民通讯员冯加起摄影报道)3月27日,中央政法委发布2022年第四季度和2023年第一季度“见义勇为勇士榜”,

每日热文:河南考古又一重大发现!

墓志铭的发现为年代判定提供了准确信息宋国故城是历史上两周时期的宋国都城遗址,也是最后一个被考古发掘找到的春秋五霸的诸侯国都城

林丹携爱妻为新球馆剪彩!40岁谢杏芳精心打扮,和老公大秀恩爱_每日视讯

对谢杏芳而言,林丹确实是他生命中非常重要的男人,虽然林丹曾经做过对不起她的事情,但谢杏芳并没有在意,谢杏芳也展现了自己作为女人的大度

河北首张“个转企”营业执照在衡水颁发

衡水餐饮店老板张淑勤高兴地换上刚刚取得的新营业执照。河北日报记者焦磊摄“拿到个人独资企业营业执照后,我开办第二家分店的目标很快就能...

电脑直播怎么弄_电脑上怎么看淘宝直播

1、打开淘宝官网。2、2在官网右边的区域找到“好店直播”。3、3进入后,点击喜欢的主播进入,就可以观看了。本文到此分享完

lol更新慢怎么解决_lol更新太慢怎么办

1、玩家现家网速基本普及4M每更新LOL候怜几十KB面教家使用百百奏效让载速度满速跑首先要安装360或者QQ管家QQ管家

铀235是什么意思_铀235-世界新资讯

1、(235,92)U(235写在左上方,92写在左下方,下同)=>(89,36)Kr+(144,56)Ba+2(1,0

山西省2022年国家统一法律职业资格考试圆满收官 全球快看

山西省2022年国家统一法律职业资格考试圆满收官,主流媒体,山西门户。山西新闻网是经国务院新闻办审核批准,由山西日报报业集团主管、主办的山

快讯2023-03-27 19:17:35

3月27日电,标普将欧元区2023年增长预期从零上调至0 3%,将2024年GDP增长预期从1 4%下调至1 0%。

运营商服务密码几位数_运营商服务密码_世界信息

1、服务密码是中国移动的客户身份识别代码,由一组6位阿拉伯数字组成,无固定数值。2、建议用户拨打移动客服咨询修改用户的服

长沙一大厦发生火灾两女子攀爬防盗窗逃生,消防:已灭火,1人受轻伤 环球热议

3月27日上午10时许,长沙市区一大厦发生火灾,两女孩通过翻窗爬绳惊险逃生,同楼栋工作人员称火势不大,很快就被扑灭,没有人员伤亡。视频显示

猜您喜欢

Copyright ©  2015-2022 大众服装网版权所有  备案号:豫ICP备20014643号-14   联系邮箱: 905 14 41 07@qq.com