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

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

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

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

1. Bean 创建流程

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

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

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

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

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

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

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

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

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

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

2. 实践

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

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

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

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

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

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

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

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

@Configuration@ComponentScan@EnableAspectJAutoProxypublic class JavaConfig {}

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

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

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

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

上一篇: 作为前端开发者,你没有必要学 Rust

下一篇: 作为前端开发者,你没有必要学 Rust

标签:
  • 热门焦点
  • 石头智能洗地机A10 Plus体验:双向自清洁治好了我的懒癌

    一、前言和介绍专为家庭请假懒人而生的石头科技在近日又带来了自己的全新旗舰新品,石头智能洗地机A10 Plus。从这个产品名上就不难看出,这次石头推出的并不是常见的扫地机器
  • JavaScript 混淆及反混淆代码工具

    介绍在我们开始学习反混淆之前,我们首先要了解一下代码混淆。如果不了解代码是如何混淆的,我们可能无法成功对代码进行反混淆,尤其是使用自定义混淆器对其进行混淆时。什么是混
  • 在线图片编辑器,支持PSD解析、AI抠图等

    自从我上次分享一个人开发仿造稿定设计的图片编辑器到现在,不知不觉已过去一年时间了,期间我经历了裁员失业、面试找工作碰壁,寒冬下一直没有很好地履行计划.....这些就放在日
  • WebRTC.Net库开发进阶,教你实现屏幕共享和多路复用!

    WebRTC.Net库:让你的应用更亲民友好,实现视频通话无痛接入! 除了基本用法外,还有一些进阶用法可以更好地利用该库。自定义 STUN/TURN 服务器配置WebRTC.Net 默认使用 Google 的
  • 每天一道面试题-CPU伪共享

    前言:了不起:又到了每天一到面试题的时候了!学弟,最近学习的怎么样啊 了不起学弟:最近学习的还不错,每天都在学习,每天都在进步! 了不起:那你最近学习的什么呢? 了不起学弟:最近在学习C
  • 使用AIGC工具提升安全工作效率

    在日常工作中,安全人员可能会涉及各种各样的安全任务,包括但不限于:开发某些安全工具的插件,满足自己特定的安全需求;自定义github搜索工具,快速查找所需的安全资料、漏洞poc、exp
  • 阿里瓴羊One推出背后,零售企业迎数字化新解

    作者:刘旷近年来随着数字经济的高速发展,各式各样的SaaS应用服务更是层出不穷,但本质上SaaS大多局限于单一业务流层面,对用户核心关切的增长问题等则没有提供更好的解法。在Saa
  • 华为发布HarmonyOS 4:更好玩、更流畅、更安全

    在8月4日的华为开发者大会2023(HDC.Together)大会上,HarmonyOS 4正式发布。自2019年发布以来,HarmonyOS一直以用户为中心,经历四年多的发展HarmonyOS已
  • 荣耀Magicbook V 14 2021曙光蓝版本正式开售,拥有触摸屏

    荣耀 Magicbook V 14 2021 曙光蓝版本正式开售,搭载 i7-11390H 处理器与 MX450 显卡,配备 16GB 内存与 512GB SSD,重 1.48kg,厚 14.5mm,具有 1.5mm 键盘键程、
Top