您当前的位置:首页 > 电脑百科 > 程序开发 > 框架

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

时间:2023-10-30 14:22:32  来源:微信公众号  作者:江南一点雨

今天再来聊一个 Spring 中的冷门知识:Bean 的处理不走正常流程,而是提前进行 AOP。

本文算是前面文章(Spring Bean 名称暗藏玄机,这样取名就不会被代理)内容的一个补充,如果还没阅读前文,建议先阅读,这样有利于更好的理解本文。

1. Bean 创建流程

前面文章中,松哥和大家梳理了,在 Bean 创建的过程中,会先给 BeanPostProcessor 一个返回代理对象的机会:

@Override
protected 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 相关类等:

@Override
public 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;
}

前面 if 分支中的内容,松哥在前面的文章中已经和大家分析过了,这里就不再赘述。

这里主要来说说 getCustomTargetSource 中的逻辑。

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

我们来看下这个方法的源码:

@Nullable
protected 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 对象。

TargetSourceCreator 是一个接口,这个接口只有一个抽象类 AbstractBeanFactoryBasedTargetSourceCreator,我们来看下 AbstractBeanFactoryBasedTargetSourceCreator 中的 getTargetSource 方法是怎么执行的:

@Override
@Nullable
public 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 方法来创建的,这个方法是一个抽象方法,将来在子类中被实现。

接下来会调用 getInternalBeanFactoryForBean 方法创建一个新的内部容器 internalBeanFactory,本质上这个 internalBeanFactory 其实是一个子容器,现有的容器将作为这个子容器的父容器。

接下来就是获取到当前 beanName 所对应的 BeanDefinition,然后进行属性配置,并注册到内部容器中,最后返回 targetSource 对象。

我们来看下这里的 getInternalBeanFactoryForBean 方法:

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;
}

这个其实就是正常的容器创建,倒也没啥好说的,但是有几个需要注意的点:

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

好了,这就是大致的 AOP 提前生成原理,接下来松哥写一个案例我们一起来看下。

2. 实践

首先,我们先来自定义一个 TargetSource:

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

    @Override
    public boolean isStatic() {
        return true;
    }
}

关于 TargetSource 本身,松哥在之前的 Spring 源码视频中已经和大家介绍过很多了,这里我就不再啰嗦了。

接下来自定义 TargetSourceCreator:

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 对象。

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

@Component
public 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 方法中去初始化的,整个的初始化过程松哥在之前的BeanPostProcessor 是在何时介入 Bean 创建的?一文中已经详细介绍过了。

BeanPostProcessor 初始化的时候,先初始化实现了 PriorityOrdered 接口的,再初始化实现了 Ordered 接口的,最后再去初始化那些没有实现任何排序接口的 BeanPostProcessor。

而我们这里 SetCustomTargetSourceCreator 一定要赶在 AnnotationAwareAspectJAutoProxyCreator 之前进行初始化,这样,当 AnnotationAwareAspectJAutoProxyCreator 进行初始化的时候,就会用到 SetCustomTargetSourceCreator 这样一个后置处理器,进而在该处理器中修改 AnnotationAwareAspectJAutoProxyCreator 的属性。

AnnotationAwareAspectJAutoProxyCreator 类间接实现了 Ordered 接口,默认优先级是最低,但是在 Spring 容器启动时,在处理 BeanFactoryPostProcessor 时(具体是 ConfigurationClassPostProcessor),将其优先级设置为最高。

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

最后,我们再在启动类上开启自动代理即可:

@Configuration
@ComponentScan
@EnableAspectJAutoProxy
public class JAVAConfig {
}

大功告成。

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



Tags:Spring   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
Spring Security:保障应用安全的利器
SpringSecurity作为一个功能强大的安全框架,为Java应用程序提供了全面的安全保障,包括认证、授权、防护和集成等方面。本文将介绍SpringSecurity在这些方面的特性和优势,以及它...【详细内容】
2024-02-27  Search: Spring  点击:(52)  评论:(0)  加入收藏
Spring Security权限控制框架使用指南
在常用的后台管理系统中,通常都会有访问权限控制的需求,用于限制不同人员对于接口的访问能力,如果用户不具备指定的权限,则不能访问某些接口。本文将用 waynboot-mall 项目举例...【详细内容】
2024-02-19  Search: Spring  点击:(39)  评论:(0)  加入收藏
详解基于SpringBoot的WebSocket应用开发
在现代Web应用中,实时交互和数据推送的需求日益增长。WebSocket协议作为一种全双工通信协议,允许服务端与客户端之间建立持久性的连接,实现实时、双向的数据传输,极大地提升了用...【详细内容】
2024-01-30  Search: Spring  点击:(8)  评论:(0)  加入收藏
Spring实现Kafka重试Topic,真的太香了
概述Kafka的强大功能之一是每个分区都有一个Consumer的偏移值。该偏移值是消费者将读取的下一条消息的值。可以自动或手动增加该值。如果我们由于错误而无法处理消息并想重...【详细内容】
2024-01-26  Search: Spring  点击:(84)  评论:(0)  加入收藏
SpringBoot如何实现缓存预热?
缓存预热是指在 Spring Boot 项目启动时,预先将数据加载到缓存系统(如 Redis)中的一种机制。那么问题来了,在 Spring Boot 项目启动之后,在什么时候?在哪里可以将数据加载到缓存系...【详细内容】
2024-01-19  Search: Spring  点击:(86)  评论:(0)  加入收藏
Spring Boot2.0深度实践 核心原理拆解+源码分析
Spring Boot2.0深度实践:核心原理拆解与源码分析一、引言Spring Boot是一个基于Java的轻量级框架,它简化了Spring应用程序的创建过程,使得开发者能够快速搭建一个可运行的应用...【详细内容】
2024-01-15  Search: Spring  点击:(93)  评论:(0)  加入收藏
SpringBoot3+Vue3 开发高并发秒杀抢购系统
开发高并发秒杀抢购系统:使用SpringBoot3+Vue3的实践之旅随着互联网技术的发展,电商行业对秒杀抢购系统的需求越来越高。为了满足这种高并发、高流量的场景,我们决定使用Spring...【详细内容】
2024-01-14  Search: Spring  点击:(90)  评论:(0)  加入收藏
Spring Boot 3.0是什么?
Spring Boot 3.0是一款基于Java的开源框架,用于简化Spring应用程序的构建和开发过程。与之前的版本相比,Spring Boot 3.0在多个方面进行了改进和增强,使其更加易用、高效和灵活...【详细内容】
2024-01-11  Search: Spring  点击:(131)  评论:(0)  加入收藏
GraalVM与Spring Boot 3.0:加速应用性能的完美融合
在2023年,SpringBoot3.0的发布标志着Spring框架对GraalVM的全面支持,这一支持是对Spring技术栈的重要补充。GraalVM是一个高性能的多语言虚拟机,它提供了Ahead-of-Time(AOT)编...【详细内容】
2024-01-11  Search: Spring  点击:(124)  评论:(0)  加入收藏
Spring Boot虚拟线程的性能还不如Webflux?
早上看到一篇关于Spring Boot虚拟线程和Webflux性能对比的文章,觉得还不错。内容较长,抓重点给大家介绍一下这篇文章的核心内容,方便大家快速阅读。测试场景作者采用了一个尽可...【详细内容】
2024-01-10  Search: Spring  点击:(115)  评论:(0)  加入收藏
▌简易百科推荐
Web Components实践:如何搭建一个框架无关的AI组件库
一、让人又爱又恨的Web ComponentsWeb Components是一种用于构建可重用的Web元素的技术。它允许开发者创建自定义的HTML元素,这些元素可以在不同的Web应用程序中重复使用,并且...【详细内容】
2024-04-03  京东云开发者    Tags:Web Components   点击:(7)  评论:(0)  加入收藏
Kubernetes 集群 CPU 使用率只有 13% :这下大家该知道如何省钱了
作者 | THE STACK译者 | 刘雅梦策划 | Tina根据 CAST AI 对 4000 个 Kubernetes 集群的分析,Kubernetes 集群通常只使用 13% 的 CPU 和平均 20% 的内存,这表明存在严重的过度...【详细内容】
2024-03-08  InfoQ    Tags:Kubernetes   点击:(12)  评论:(0)  加入收藏
Spring Security:保障应用安全的利器
SpringSecurity作为一个功能强大的安全框架,为Java应用程序提供了全面的安全保障,包括认证、授权、防护和集成等方面。本文将介绍SpringSecurity在这些方面的特性和优势,以及它...【详细内容】
2024-02-27  风舞凋零叶    Tags:Spring Security   点击:(52)  评论:(0)  加入收藏
五大跨平台桌面应用开发框架:Electron、Tauri、Flutter等
一、什么是跨平台桌面应用开发框架跨平台桌面应用开发框架是一种工具或框架,它允许开发者使用一种统一的代码库或语言来创建能够在多个操作系统上运行的桌面应用程序。传统上...【详细内容】
2024-02-26  贝格前端工场    Tags:框架   点击:(46)  评论:(0)  加入收藏
Spring Security权限控制框架使用指南
在常用的后台管理系统中,通常都会有访问权限控制的需求,用于限制不同人员对于接口的访问能力,如果用户不具备指定的权限,则不能访问某些接口。本文将用 waynboot-mall 项目举例...【详细内容】
2024-02-19  程序员wayn  微信公众号  Tags:Spring   点击:(39)  评论:(0)  加入收藏
开发者的Kubernetes懒人指南
你可以将本文作为开发者快速了解 Kubernetes 的指南。从基础知识到更高级的主题,如 Helm Chart,以及所有这些如何影响你作为开发者。译自Kubernetes for Lazy Developers。作...【详细内容】
2024-02-01  云云众生s  微信公众号  Tags:Kubernetes   点击:(50)  评论:(0)  加入收藏
链世界:一种简单而有效的人类行为Agent模型强化学习框架
强化学习是一种机器学习的方法,它通过让智能体(Agent)与环境交互,从而学习如何选择最优的行动来最大化累积的奖励。强化学习在许多领域都有广泛的应用,例如游戏、机器人、自动驾...【详细内容】
2024-01-30  大噬元兽  微信公众号  Tags:框架   点击:(67)  评论:(0)  加入收藏
Spring实现Kafka重试Topic,真的太香了
概述Kafka的强大功能之一是每个分区都有一个Consumer的偏移值。该偏移值是消费者将读取的下一条消息的值。可以自动或手动增加该值。如果我们由于错误而无法处理消息并想重...【详细内容】
2024-01-26  HELLO程序员  微信公众号  Tags:Spring   点击:(84)  评论:(0)  加入收藏
SpringBoot如何实现缓存预热?
缓存预热是指在 Spring Boot 项目启动时,预先将数据加载到缓存系统(如 Redis)中的一种机制。那么问题来了,在 Spring Boot 项目启动之后,在什么时候?在哪里可以将数据加载到缓存系...【详细内容】
2024-01-19   Java中文社群  微信公众号  Tags:SpringBoot   点击:(86)  评论:(0)  加入收藏
花 15 分钟把 Express.js 搞明白,全栈没有那么难
Express 是老牌的 Node.js 框架,以简单和轻量著称,几行代码就可以启动一个 HTTP 服务器。市面上主流的 Node.js 框架,如 Egg.js、Nest.js 等都与 Express 息息相关。Express 框...【详细内容】
2024-01-16  程序员成功  微信公众号  Tags:Express.js   点击:(85)  评论:(0)  加入收藏
站内最新
站内热门
站内头条