当前位置:首页 > 科技  > 软件

Spring 冷知识:一个提前 AOP 的机会

来源: 责编: 时间:2023-10-30 09:06:44 170观看
导读今天再来聊一个 Spring 中的冷门知识:Bean 的处理不走正常流程,而是提前进行 AOP。1. Bean 创建流程在 Bean 创建的过程中,会先给 BeanPostProcessor 一个返回代理对象的机会:@Overrideprotected Object createBean(Strin

今天再来聊一个 Spring 中的冷门知识:Bean 的处理不走正常流程,而是提前进行 AOP。qlC28资讯网——每日最新资讯28at.com

1. Bean 创建流程

在 Bean 创建的过程中,会先给 BeanPostProcessor 一个返回代理对象的机会:qlC28资讯网——每日最新资讯28at.com

@Overrideprotected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)  throws BeanCreationException { //省略。。。 try {  // Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.  Object bean = resolveBeforeInstantiation(beanName, mbdToUse);  if (bean != null) {   return bean;  } } catch (Throwable ex) {  throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName,    "BeanPostProcessor before instantiation of bean failed", ex); } try {  Object beanInstance = doCreateBean(beanName, mbdToUse, args);  if (logger.isTraceEnabled()) {   logger.trace("Finished creating instance of bean '" + beanName + "'");  }  return beanInstance; }    //省略。。。}

小伙伴们看,这里的 resolveBeforeInstantiation 方法就是给 BeanPostProcessor 一个返回代理对象的机会,在这个方法中,最终就会触发到 InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation 方法,而在 postProcessBeforeInstantiation 方法中,会先判断当前 bean 是否是 AOP 相关类等:qlC28资讯网——每日最新资讯28at.com

@Overridepublic Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) { Object cacheKey = getCacheKey(beanClass, beanName); if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {  if (this.advisedBeans.containsKey(cacheKey)) {   return null;  }  if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {   this.advisedBeans.put(cacheKey, Boolean.FALSE);   return null;  } }  TargetSource targetSource = getCustomTargetSource(beanClass, beanName); if (targetSource != null) {  if (StringUtils.hasLength(beanName)) {   this.targetSourcedBeans.add(beanName);  }  Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);  Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);  this.proxyTypes.put(cacheKey, proxy.getClass());  return proxy; } return null;}

这里主要来说说 getCustomTargetSource 中的逻辑。qlC28资讯网——每日最新资讯28at.com

先来说什么情况下会走到 getCustomTargetSource 方法:当前 Bean 不是代理对象,也不是 AOP 相关的类,就是一个普普通通的常规类,那么就会走到 getCustomTargetSource 方法这里来,这里失去查找到一个 TargetSource 对象,然后根据该对象创建当前 bean 的代理对象并返回,如果返回了代理对象,那么后续的 bean 创建流程就不执行了。qlC28资讯网——每日最新资讯28at.com

我们来看下这个方法的源码:qlC28资讯网——每日最新资讯28at.com

@Nullableprotected TargetSource getCustomTargetSource(Class<?> beanClass, String beanName) { // We can't create fancy target sources for directly registered singletons. if (this.customTargetSourceCreators != null &&   this.beanFactory != null && this.beanFactory.containsBean(beanName)) {  for (TargetSourceCreator tsc : this.customTargetSourceCreators) {   TargetSource ts = tsc.getTargetSource(beanClass, beanName);   if (ts != null) {    return ts;   }  } } // No custom TargetSource found. return null;}

可以看到,这里就是当前类 AbstractAutoProxyCreator 中有一个 customTargetSourceCreators 变量,现在就是遍历该变量,通过这个集合中保存的 TargetSourceCreator 来创建 TargetSource 对象。qlC28资讯网——每日最新资讯28at.com

TargetSourceCreator 是一个接口,这个接口只有一个抽象类 AbstractBeanFactoryBasedTargetSourceCreator,我们来看下 AbstractBeanFactoryBasedTargetSourceCreator 中的 getTargetSource 方法是怎么执行的:qlC28资讯网——每日最新资讯28at.com

@Override@Nullablepublic final TargetSource getTargetSource(Class<?> beanClass, String beanName) { AbstractBeanFactoryBasedTargetSource targetSource =   createBeanFactoryBasedTargetSource(beanClass, beanName); if (targetSource == null) {  return null; } DefaultListableBeanFactory internalBeanFactory = getInternalBeanFactoryForBean(beanName); // We need to override just this bean definition, as it may reference other beans // and we're happy to take the parent's definition for those. // Always use prototype scope if demanded. BeanDefinition bd = getConfigurableBeanFactory().getMergedBeanDefinition(beanName); GenericBeanDefinition bdCopy = new GenericBeanDefinition(bd); if (isPrototypeBased()) {  bdCopy.setScope(BeanDefinition.SCOPE_PROTOTYPE); } internalBeanFactory.registerBeanDefinition(beanName, bdCopy); // Complete configuring the PrototypeTargetSource. targetSource.setTargetBeanName(beanName); targetSource.setBeanFactory(internalBeanFactory); return targetSource;}

首先,TargetSource 对象是通过 createBeanFactoryBasedTargetSource 方法来创建的,这个方法是一个抽象方法,将来在子类中被实现。qlC28资讯网——每日最新资讯28at.com

接下来会调用 getInternalBeanFactoryForBean 方法创建一个新的内部容器 internalBeanFactory,本质上这个 internalBeanFactory 其实是一个子容器,现有的容器将作为这个子容器的父容器。qlC28资讯网——每日最新资讯28at.com

接下来就是获取到当前 beanName 所对应的 BeanDefinition,然后进行属性配置,并注册到内部容器中,最后返回 targetSource 对象。qlC28资讯网——每日最新资讯28at.com

我们来看下这里的 getInternalBeanFactoryForBean 方法:qlC28资讯网——每日最新资讯28at.com

protected DefaultListableBeanFactory getInternalBeanFactoryForBean(String beanName) { synchronized (this.internalBeanFactories) {  return this.internalBeanFactories.computeIfAbsent(beanName,    name -> buildInternalBeanFactory(getConfigurableBeanFactory())); }}protected DefaultListableBeanFactory buildInternalBeanFactory(ConfigurableBeanFactory containingFactory) { // Set parent so that references (up container hierarchies) are correctly resolved. DefaultListableBeanFactory internalBeanFactory = new DefaultListableBeanFactory(containingFactory); // Required so that all BeanPostProcessors, Scopes, etc become available. internalBeanFactory.copyConfigurationFrom(containingFactory); // Filter out BeanPostProcessors that are part of the AOP infrastructure, // since those are only meant to apply to beans defined in the original factory. internalBeanFactory.getBeanPostProcessors().removeIf(beanPostProcessor ->   beanPostProcessor instanceof AopInfrastructureBean); return internalBeanFactory;}

这个其实就是正常的容器创建,倒也没啥好说的,但是有几个需要注意的点:qlC28资讯网——每日最新资讯28at.com

  1. 在调用 buildInternalBeanFactory 方法构建容器的时候,会先调用 getConfigurableBeanFactory 方法获取到当前容器作为父容器,如果当前容器不存在,那么就会抛出异常。这就意味着,当我们自己提供 TargetSourceCreator 实例的时候,一定要指定一个容器。
  2. 在创建了内部容器之后,会从内部容器中移除所有 AopInfrastructureBean 类型的 BeanPostProcessor,也就是内部容器将来创建出来的 bean,不再走 AopInfrastructureBean 类型后置处理器,因为这种类型的后置处理器主要是用来处理 AOP 的,现在,AOP 代理当场就生成了,就不再需要这些后置处理器了。

好了,这就是大致的 AOP 提前生成原理,接下来松哥写一个案例我们一起来看下。qlC28资讯网——每日最新资讯28at.com

2. 实践

首先,我们先来自定义一个 TargetSource:qlC28资讯网——每日最新资讯28at.com

public class UserServiceTargetSource extends AbstractBeanFactoryBasedTargetSource {    @Override    public Object getTarget() throws Exception {        return getBeanFactory().getBean(getTargetBeanName());    }    @Override    public boolean isStatic() {        return true;    }}

关于 TargetSource 本身,松哥在之前的 Spring 源码视频中已经和大家介绍过很多了,这里我就不再啰嗦了。qlC28资讯网——每日最新资讯28at.com

接下来自定义 TargetSourceCreator:qlC28资讯网——每日最新资讯28at.com

public class CustomTargetSourceCreator extends AbstractBeanFactoryBasedTargetSourceCreator {    @Override    protected AbstractBeanFactoryBasedTargetSource createBeanFactoryBasedTargetSource(Class<?> beanClass, String beanName) {        if (getBeanFactory() instanceof ConfigurableListableBeanFactory) {            if (beanClass.isAssignableFrom(UserService.class)) {                return new UserServiceTargetSource();            }        }        return null;    }}

如果要创建的 bean 是 UserService 的话,那么就给返回一个 UserServiceTargetSource 对象。qlC28资讯网——每日最新资讯28at.com

最后,也是最关键的一步,根据前面的分析,TargetSourceCreator 是存在于 AnnotationAwareAspectJAutoProxyCreator 这样一个 InstantiationAwareBeanPostProcessor 类型的后置处理器中的,因此,我们要想办法把自定义的 TargetSourceCreator 设置给 AnnotationAwareAspectJAutoProxyCreator,如下:qlC28资讯网——每日最新资讯28at.com

@Componentpublic class SetCustomTargetSourceCreator implements BeanPostProcessor, PriorityOrdered, BeanFactoryAware {    private BeanFactory beanFactory;    @Override    public int getOrder() {        return Integer.MIN_VALUE;    }    @Override    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {        if(bean instanceof AnnotationAwareAspectJAutoProxyCreator) {            AnnotationAwareAspectJAutoProxyCreator annotationAwareAspectJAutoProxyCreator = (AnnotationAwareAspectJAutoProxyCreator)bean;            CustomTargetSourceCreator customTargetSourceCreator = new CustomTargetSourceCreator();            customTargetSourceCreator.setBeanFactory(beanFactory);            annotationAwareAspectJAutoProxyCreator.setCustomTargetSourceCreators(customTargetSourceCreator);        }        return bean;    }    @Override    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {        this.beanFactory = beanFactory;    }}

AnnotationAwareAspectJAutoProxyCreator 本身就是一个 BeanPostProcessor,我们现在要做的就是修改这个 BeanPostProcessor,BeanPostProcessor 是在 Spring 容器启动时候的 refresh 方法中去初始化的。qlC28资讯网——每日最新资讯28at.com

BeanPostProcessor 初始化的时候,先初始化实现了 PriorityOrdered 接口的,再初始化实现了 Ordered 接口的,最后再去初始化那些没有实现任何排序接口的 BeanPostProcessor。qlC28资讯网——每日最新资讯28at.com

而我们这里 SetCustomTargetSourceCreator 一定要赶在 AnnotationAwareAspectJAutoProxyCreator 之前进行初始化,这样,当 AnnotationAwareAspectJAutoProxyCreator 进行初始化的时候,就会用到 SetCustomTargetSourceCreator 这样一个后置处理器,进而在该处理器中修改 AnnotationAwareAspectJAutoProxyCreator 的属性。qlC28资讯网——每日最新资讯28at.com

AnnotationAwareAspectJAutoProxyCreator 类间接实现了 Ordered 接口,默认优先级是最低,但是在 Spring 容器启动时,在处理 BeanFactoryPostProcessor 时(具体是 ConfigurationClassPostProcessor),将其优先级设置为最高。qlC28资讯网——每日最新资讯28at.com

所以,我们如果想要让自定义的 SetCustomTargetSourceCreator 抢在 AnnotationAwareAspectJAutoProxyCreator 之前执行,那么就只能让 SetCustomTargetSourceCreator 去实现 PriorityOrdered 接口了,实现 PriorityOrdered 接口之后,重写 getOrder 方法,这个方法返回值是什么无所谓,反正都会在实现了 Ordered 接口的 BeanPostProcessor 之前执行。qlC28资讯网——每日最新资讯28at.com

最后,我们再在启动类上开启自动代理即可:qlC28资讯网——每日最新资讯28at.com

@Configuration@ComponentScan@EnableAspectJAutoProxypublic class JavaConfig {}

大功告成。qlC28资讯网——每日最新资讯28at.com

这样,当 Spring 容器创建一个 Bean 的时候,就会提前被 BeanPostProcessor 拦截,然后给出一个 TargetSource,进而据此创建代理对象,这样就不需要后续常规的 Bean 创建流程了。好啦,感兴趣的小伙伴可以自己去试一试哦~qlC28资讯网——每日最新资讯28at.com

本文链接:http://www.28at.com/showinfo-26-15742-0.htmlSpring 冷知识:一个提前 AOP 的机会

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: 给 Go 提问题?充分了解 Go 提案流程

下一篇: 基于 DDD 的互联网“赞&amp;踩”系统

标签:
  • 热门焦点
  • 摸鱼心法第一章——和配置文件说拜拜

    摸鱼心法第一章——和配置文件说拜拜

    为了能摸鱼我们团队做了容器化,但是带来的问题是服务配置文件很麻烦,然后大家在群里进行了“亲切友好”的沟通图片图片图片图片对比就对比,简单对比下独立配置中心和k8s作为配
  • 如何正确使用:Has和:Nth-Last-Child

    如何正确使用:Has和:Nth-Last-Child

    我们可以用CSS检查,以了解一组元素的数量是否小于或等于一个数字。例如,一个拥有三个或更多子项的grid。你可能会想,为什么需要这样做呢?在某些情况下,一个组件或一个布局可能会
  • 使用AIGC工具提升安全工作效率

    使用AIGC工具提升安全工作效率

    在日常工作中,安全人员可能会涉及各种各样的安全任务,包括但不限于:开发某些安全工具的插件,满足自己特定的安全需求;自定义github搜索工具,快速查找所需的安全资料、漏洞poc、exp
  • 拼多多APP上线本地生活入口,群雄逐鹿万亿市场

    拼多多APP上线本地生活入口,群雄逐鹿万亿市场

    Tech星球(微信ID:tech618)文 | 陈桥辉 Tech星球独家获悉,拼多多在其APP内上线了&ldquo;本地生活&rdquo;入口,位置较深,位于首页的&ldquo;充值中心&rdquo;内,目前主要售卖美食相关的
  • 大厂卷向扁平化

    大厂卷向扁平化

    来源:新熵作者丨南枝 编辑丨月见大厂职级不香了。俗话说,兵无常势,水无常形,互联网企业调整职级体系并不稀奇。7月13日,淘宝天猫集团启动了近年来最大的人力制度改革,目前已形成一
  • ESG的面子与里子

    ESG的面子与里子

    来源 | 光子星球撰文 | 吴坤谚编辑 | 吴先之三伏大幕拉起,各地高温预警不绝,但处于厄尔尼诺大&ldquo;烤&rdquo;之下的除了众生,还有各大企业发布的ESG报告。ESG是&ldquo;环境保
  • 信通院:小米、华为等11家应用商店基本完成APP签名及验签工作

    信通院:小米、华为等11家应用商店基本完成APP签名及验签工作

    中国信通院表示,目前,小米、华为、OPPO、vivo、360手机助手、百度手机助手、应用宝、豌豆荚和努比亚等9家应用商店,以及抖音和快手2家新型应用分发平
  • iQOO Neo8系列今日官宣:首发天玑9200+ 全球安卓最强芯!

    iQOO Neo8系列今日官宣:首发天玑9200+ 全球安卓最强芯!

    在昨日举行的的联发科新一代旗舰芯片天玑9200+的发布会上,iQOO官方也正式宣布,全新的iQOO Neo8系列新品将全球首发搭载这款当前性能最强大的移动平台
  • 三翼鸟智能家居亮相电博会,让用户体验更真实

    三翼鸟智能家居亮相电博会,让用户体验更真实

    2021电博会在青岛国际会展中心开幕中,三翼鸟直接把“家”搬到了现场,成为了展会的一大看点。这也是三翼鸟继9月9日发布了行业首个一站式定制智慧家平台后的
Top