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

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

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

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

1. Bean 创建流程

在 Bean 创建的过程中,会先给 BeanPostProcessor 一个返回代理对象的机会:2re28资讯网——每日最新资讯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 相关类等:2re28资讯网——每日最新资讯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 中的逻辑。2re28资讯网——每日最新资讯28at.com

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

我们来看下这个方法的源码:2re28资讯网——每日最新资讯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 对象。2re28资讯网——每日最新资讯28at.com

TargetSourceCreator 是一个接口,这个接口只有一个抽象类 AbstractBeanFactoryBasedTargetSourceCreator,我们来看下 AbstractBeanFactoryBasedTargetSourceCreator 中的 getTargetSource 方法是怎么执行的:2re28资讯网——每日最新资讯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 方法来创建的,这个方法是一个抽象方法,将来在子类中被实现。2re28资讯网——每日最新资讯28at.com

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

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

我们来看下这里的 getInternalBeanFactoryForBean 方法:2re28资讯网——每日最新资讯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;}

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

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

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

2. 实践

首先,我们先来自定义一个 TargetSource:2re28资讯网——每日最新资讯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 源码视频中已经和大家介绍过很多了,这里我就不再啰嗦了。2re28资讯网——每日最新资讯28at.com

接下来自定义 TargetSourceCreator:2re28资讯网——每日最新资讯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 对象。2re28资讯网——每日最新资讯28at.com

最后,也是最关键的一步,根据前面的分析,TargetSourceCreator 是存在于 AnnotationAwareAspectJAutoProxyCreator 这样一个 InstantiationAwareBeanPostProcessor 类型的后置处理器中的,因此,我们要想办法把自定义的 TargetSourceCreator 设置给 AnnotationAwareAspectJAutoProxyCreator,如下:2re28资讯网——每日最新资讯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 方法中去初始化的。2re28资讯网——每日最新资讯28at.com

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

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

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

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

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

@Configuration@ComponentScan@EnableAspectJAutoProxypublic class JavaConfig {}

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

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

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

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

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

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

标签:
  • 热门焦点
  • 掘力计划第 20 期:Flutter 混合开发的混乱之治

    在掘力计划系列活动第20场,《Flutter 开发实战详解》作者,掘金优秀作者,Github GSY 系列目负责人恋猫的小郭分享了Flutter 混合开发的混乱之治。Flutter 基于自研的 Skia 引擎
  • 三分钟白话RocketMQ系列—— 如何发送消息

    我们知道RocketMQ主要分为消息 生产、存储(消息堆积)、消费 三大块领域。那接下来,我们白话一下,RocketMQ是如何发送消息的,揭秘消息生产全过程。注意,如果白话中不小心提到相关代
  • 为什么你不应该使用Div作为可点击元素

    按钮是为任何网络应用程序提供交互性的最常见方式。但我们经常倾向于使用其他HTML元素,如 div span 等作为 clickable 元素。但通过这样做,我们错过了许多内置浏览器的功能。
  • 腾讯盖楼,字节拆墙

    来源 | 光子星球撰文 | 吴坤谚编辑 | 吴先之&ldquo;想重温暴刷深渊、30+技能搭配暴搓到爽的游戏体验吗?一起上晶核,即刻暴打!&rdquo;曾凭借直播腾讯旗下代理格斗游戏《DNF》一
  • “又被陈思诚骗了”

    作者|张思齐 出品|众面(ID:ZhongMian_ZM)如今的国产悬疑电影,成了陈思诚的天下。最近大爆电影《消失的她》票房突破30亿断层夺魁暑期档,陈思诚再度风头无两。你可以说陈思诚的
  • 品牌洞察丨服务本地,美团直播成效几何?

    来源:17PR7月11日,美团App首页推荐位出现&ldquo;美团直播&rdquo;的固定入口。在直播聚合页面,外卖&ldquo;神枪手&rdquo;直播间、美团旅行直播间、美团买菜直播间等均已上线,同时
  • 微博大门常打开,迎接海外画师漂洋东渡

    作者:互联网那些事&ldquo;起猛了,我能看得懂日语了&rdquo;。&ldquo;为什么日本人说话我能听懂?&rdquo;&ldquo;中文不像中文,日语不像日语,但是我竟然看懂了&rdquo;&hellip;&hell
  • 小米MIX Fold 3配置细节曝光:搭载领先版骁龙8 Gen2+罕见5倍长焦

    这段时间以来,包括三星、一加、荣耀等等有不少品牌旗下的最新折叠屏旗舰都得到了不少爆料,而小米新一代折叠屏旗舰——小米MIX Fold 3此前也屡屡被传
  • 三星显示已开始为AR设备研发硅基LED微显示屏

    7月18日消息,据外媒报道,随着苹果首款头显产品Vision Pro在6月份正式推出,AR/VR/MR等头显产品也就将成为各大公司下一个重要的竞争领域,对显示屏这一关
Top