@aspect注解背后的奥秘--下(代码片段)

热爱编程的大忽悠 热爱编程的大忽悠     2023-02-24     653

关键词:

@Aspect注解背后的奥秘--下


前言

上一篇文章我们减少了aop原始时代中通过ProxyFactory和AspectJProxyFactory手动创建代理对象的过程,本文我们来探究一下新生代中aop自动化是如何实现的。


手动化进行到自动化靠的是什么

Spring设计了一套完善的bean生命周期流程,并在流程中各个阶段都预留了相关的生命周期回调接口,如果检测到有回调接口实现类,便会在创建每个bean时都在各个生命周期阶段回调这些实现类相应的生命周期回调接口。

如果我们想对放入容器中的每个bean都尝试进行修改,那么就需要提供实现相应的生命周期接口,然后将实现类注入到容器中即可。

这里生命周期回调接口在spring中具体指的是BeanPostProcessor这个接口体系:

因此,如果要实现自动化判断bean是否需要被代理,并在需要代理时,进行代理,我们只需要提供一个对应的bean后置处理器即可,该后置处理器只需要关心postProcessAfterInitialization和getEarlyReference这两个回调接口即可。

postProcessAfterInitialization方法中创建代理对象,getEarlyReference方法确保在存在循环依赖时,依然返回的是代理对象。

getEarlyReference回调接口的作用,参考下面这篇文章,后面我就不再提了:

spring三级缓存


自动代理创建器

spring已经为我们提供了一个这样的bean后置处理器,由于它能自动帮助我们判断某个bean是否需要进行代理,并在需要是进行代理,所有这个bean后置处理器又被称为自动代理创建器。


不考虑循环依赖导致提前暴露的问题的话,AbstractAutoProxyCreator只会在postProcessAfterInitialization回调接口中对bean尝试进行代理,也就是bean的初始化相关方法被调用后:

	@Override
	public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) 
		if (bean != null) 
			Object cacheKey = getCacheKey(bean.getClass(), beanName);
			//如果产生了循环依赖,那么提前暴露的bean已经在getEarlyReference方法中尝试进行过代理了,此时跳过会bean的代理尝试
			if (this.earlyProxyReferences.remove(cacheKey) != bean) 
			//我们不考虑循环依赖情况,普通情况下,bean会在此处被判断是否需要进行代理
				return wrapIfNecessary(bean, beanName, cacheKey);
			
		
		return bean;
	

去掉一些判断逻辑:

	protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) 
		 ...
		//如果寻找到能够切入当前bean的增强器链,那么就进行代理
		Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
		if (specificInterceptors != DO_NOT_PROXY) 
			...
			//进行代理
			Object proxy = createProxy(
					bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
			...
			return proxy;
		
		...
		//如果不存在advisor能够应用到当前bean上,那么跳过代理,返回原对象
		return bean;
	

上面这段流程的关键在于getAdvicesAndAdvisorsForBean方法,即自动代理创建器如何筛选得到能够应用到当前bean上的advisors呢?


如何搜寻并对增强器集合进行过滤

AbstractAutoCreator自动代理创建器的顶层抽象类规定好了自动代理创建器的核心工作流程,但是将获取advisors并筛选获取能应用到当前bean上的advisors的方法交给子类进行实现:

BeanNameAutoProxyCreator可以通过设置beanName匹配列表,如: user*,那么会将beanName以user开头的bean的自动创建代理,而所应用的拦截器是AbstractAutoCreator提供的通用拦截器,即会应用到所有bean上的拦截器,如果不手动设置,默认是空。

我们这里更关注的是AbstractAdvisorAutoProxyCreator:

AbstractAdvisorAutoProxyCreator通过搜寻beanFactory中所有类型为Advisors的bean完成对增强器的查找过程:

	protected Object[] getAdvicesAndAdvisorsForBean(
			Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) 
        //搜索所有能够应用到当前bean上的增强器
		List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
		if (advisors.isEmpty()) 
			return DO_NOT_PROXY;
		
		return advisors.toArray();
	
	protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) 
	    //寻找所有可用的候选advisor
		List<Advisor> candidateAdvisors = findCandidateAdvisors();
		//对候选增强器进行过滤,只挑选出那些能应用在当前bean上的增强器
		List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
		//钩子函数,留给子类扩展可用增强器
		extendAdvisors(eligibleAdvisors);
		//为增强器进行排序
		if (!eligibleAdvisors.isEmpty()) 
			eligibleAdvisors = sortAdvisors(eligibleAdvisors);
		
		return eligibleAdvisors;
	

1.寻找所有可用的候选advisor

	private BeanFactoryAdvisorRetrievalHelper advisorRetrievalHelper;


	@Override
	public void setBeanFactory(BeanFactory beanFactory) 
		//设置并检查beanFactory
		...
		initBeanFactory((ConfigurableListableBeanFactory) beanFactory);
	

	protected void initBeanFactory(ConfigurableListableBeanFactory beanFactory) 
		this.advisorRetrievalHelper = new BeanFactoryAdvisorRetrievalHelperAdapter(beanFactory);
	

核心是通过BeanFactoryAdvisorRetrievalHelper完成的,该类会从BeanFactory进行搜寻:

	public List<Advisor> findAdvisorBeans() 
		String[] advisorNames = this.cachedAdvisorBeanNames;
		if (advisorNames == null) 
			//从IOC容器中获得所有类型为Advisor的bean
			advisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
					this.beanFactory, Advisor.class, true, false);
			this.cachedAdvisorBeanNames = advisorNames;
		
		if (advisorNames.length == 0) 
			return new ArrayList<>();
		
        
		List<Advisor> advisors = new ArrayList<>();
		//对每个advisor进行过滤判断
		for (String name : advisorNames) 
		    //当前advisor是否有效--BeanFactoryAdvisorRetrievalHelper的isEligibleBean默认返回true
		    //但是子类进行了覆写,实际调用的是AbstractAdvisorAutoProxyCreator的isEligibleAdvisorBean方法
		    //该方法主要有两个分支: 通常情况下返回true, 或者只对spring基础设施bean进行代理
			if (isEligibleBean(name)) 
			   //跳过正在创建的advisor
				if (this.beanFactory.isCurrentlyInCreation(name)) 
					if (logger.isTraceEnabled()) 
						logger.trace("Skipping currently created advisor '" + name + "'");
					
				
				else 
					try 
					//其他的advisor加入增强器集合,最后返回
						advisors.add(this.beanFactory.getBean(name, Advisor.class));
					
					catch (BeanCreationException ex) 
						....
					
				
			
		
		return advisors;
	
    
    protected boolean isEligibleBean(String beanName) 
		return true;
	
    
    //BeanFactoryAdvisorRetrievalHelper子类覆写了isEligibleBean方法
    //实际调用的是AbstractAdvisorAutoProxyCreator的isEligibleAdvisorBean方法进行的判断
	private class BeanFactoryAdvisorRetrievalHelperAdapter extends BeanFactoryAdvisorRetrievalHelper 
	     ...
		@Override
		protected boolean isEligibleBean(String beanName) 
			return AbstractAdvisorAutoProxyCreator.this.isEligibleAdvisorBean(beanName);
		
	        

1.1 isEligibleBean两种分支情况

AbstractAdvisorAutoProxyCreator类的isEligibleAdvisorBean方法负责在搜寻可用advisor阶段,对advisor进行一次预先过滤,该方法默认返回true,但是其中两个子类对其进行了覆写:

DefaultAdvisorAutoProxyCreator:

DefaultAdvisorAutoProxyCreator的前缀匹配默认是不开启,一般情况下也不会开启,所以可以认为默认返回true。

InfrastructureAdvisorAutoProxyCreator会提前过滤掉不是spring基础设施bean类型的增强器:

InfrastructureAdvisorAutoProxyCreator最典型的使用案例就是Spring声明式事务,Spring声明式事务开启后会向容器中注入一个InfrastructureAdvisorAutoProxyCreator类型的自动代理创建器,同时向容器中注入一个用于处理事务的增强器:

该增强器被标识为了Spring基础设施bean,因此会被InfrastructureAdvisorAutoProxyCreator搜寻到,而其他非基础设施类型的增强器会被InfrastructureAdvisorAutoProxyCreator过滤掉。

spring事务模块源码解析


2.过滤候选增强器

过滤候选增强器集合,筛选出能够切入当前bean的增强器过程由AopUtils工具类完成:

	protected List<Advisor> findAdvisorsThatCanApply(
			List<Advisor> candidateAdvisors, Class<?> beanClass, String beanName) 
        ...
			return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);
		...
	

这个工具类的findAdvisorsThatCanApply方法在上一篇文章中也带领大家阅读过,不清楚可以回看:

@Aspect注解背后的奥秘–上

核心过滤思路: 取出advisor内部的pointcut,先通过classFilter进行过滤,如果通过了,再判断目标对象包括其继承的所有接口是否有任何一个方法能够被methodMatcher切入,如果有当前advisor才会预留下来。


3.扩展增强器

AbstractAdvisorAutoProxyCreator提供的扩展增强器钩子方法默认是空实现,存在一个子实例类对该钩子方法进行了覆写:

AspectJAwareAdvisorAutoProxyCreator重写了该钩子方法:

	@Override
	protected void extendAdvisors(List<Advisor> candidateAdvisors) 
		AspectJProxyUtils.makeAdvisorChainAspectJCapableIfNecessary(candidateAdvisors);
	

AspectJProxyUtils调用makeAdvisorChainAspectJCapableIfNecessary添加一个ExposeInvocationInterceptor到增强器链头部, 当然添加的前提是当前增强器集合中,存在由相关增强方法转换过来的advisor。

为啥要添加这样一个拦截器,上一篇文章也进行了解释,不清楚回看上一篇文章。

那么这里又产生了一个问题,AbstractAdvisorAutoProxyCreator提供的findCandidateAdvisors方法默认是去IOC容器中寻找所有类型为advisor的bean,并进行一波预先过滤,那么标注了@AspectJ注解的切面类难道就不管了吗?

肯定是要管的,那么又是在何时对切面类进行搜寻并完成解析转换工作的呢?

这个过程,待会分析。


4.对增强器进行排序

Spring中要对某个组件集合进行排序,都是借助的AnnotationAwareOrderComparator完成的:

	protected List<Advisor> sortAdvisors(List<Advisor> advisors) 
		AnnotationAwareOrderComparator.sort(advisors);
		return advisors;
	

核心排序逻辑在AnnotationAwareOrderComparator的getOrder方法中体现:

	@Override
	@Nullable
	protected Integer findOrder(Object obj) 
	    //先找是否实现了Order接口,如果实现了调用getOrder方法顺序值
		Integer order = super.findOrder(obj);
		if (order != null) 
			return order;
		
		//如果没有再查询是否标注了@Order注解,根据@Order注解提供的值作为顺序值
		return findOrderFromAnnotation(obj);
	
	
	//父类的getOrder方法实现 
	@Nullable
	protected Integer findOrder(Object obj) 
		return (obj instanceof Ordered ? ((Ordered) obj).getOrder() : null);
	 

compare方法是JDK提供的比较器接口,我们可以简单看一下OrderComparator对其实现:

	@Override
	public int compare(@Nullable Object o1, @Nullable Object o2) 
		return doCompare(o1, o2, null);
	

	private int doCompare(@Nullable Object o1, @Nullable Object o2, @Nullable OrderSourceProvider sourceProvider) 
	   //实现了PriorityOrdered接口的优先级会更高
		boolean p1 = (o1 instanceof PriorityOrdered);
		boolean p2 = (o2 instanceof PriorityOrdered);
		if (p1 && !p2) 
			return -1;
		
		else if (p2 && !p1) 
			return 1;
		
       //否则正常获取每个组件的优先级,比较进行排序
		int i1 = getOrder(o1, sourceProvider);
		int i2 = getOrder(o2, sourceProvider);
		return Integer.compare(i1, i2);
	

	private int getOrder(@Nullable Object obj, @Nullable OrderSourceProvider sourceProvider) 
		Integer order = null;
		//暂时先忽略OrderSourceProvider的作用,看最简单的排序过程实现
		...
		return (order != null ? order : getOrder(obj));
	

	protected int getOrder(@Nullable Object obj) 
		if (obj != null) 
			Integer order = findOrder(obj);
			if (order != null) 
				return order;
			
		
		//如果没有找到@Order注解或者实现Order接口,那么返回最低优先级
		return Ordered.LOWEST_PRECEDENCE;
	

如果我们想要指定增强器的优先级,可以通过自定义增强器,然后让增强器实现Order接口,或者在增强器类上标注@Order注解完成。

但是注意,上一篇文章中我们分析过,由切面类中的增强方法转换得到的advisor而言,他们的优先级和所属切面类优先级一致,无法手动指定。


搜寻所有切面类并完成解析转换过程

AnnotationAwareAspectJAutoProxyCreator覆写了AbstractAdvisorAutoProxyCreator的findCandidateAdvisors方法:

	@Override
	protected List<Advisor> findCandidateAdvisors() 
		// 调用父类的方法,去IOC容器中寻找所有可用的advisor类型bean
		List<Advisor> advisors = super.findCandidateAdvisors();
		// BeanFactoryAspectJAdvisorsBuilder负责完成从容器中获取所有切面类并进行解析转换得到一批advisor的任务
		if (this.aspectJAdvisorsBuilder != null) 
			advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
		
		return advisors;
	

BeanFactoryAspectJAdvisorsBuilder承担了搜索切面类并完成解析转换的核心工作:

public List<Advisor> buildAspectJAdvisors() 
       //从容器中寻找出所有切面类,寻找过程比较耗时,通过aspectBeanNames缓存上一次找到的切面类beanName
       //不直接缓存切面类对应的bean实例,是考虑到某些切面类不是单例类型,每次获取需要重新创建
		List<String> aspectNames = this.aspectBeanNames;
        //双重锁确保并发安全  
		if (aspectNames == null) 
			synchronized (this) 
				aspectNames = this.aspectBeanNames;
				if (aspectNames == null) 
                    //存放结果的集合
					List<Advisor> advisors = new ArrayList<>();
					aspectNames = new ArrayList<>();
					//从容器中取出所有bean
                    String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
							this.beanFactory, Object.class, true, false);
                    //依次对每个bean进行panda
					for (String beanName : beanNames) 
						//套路和BeanFactoryAdvisorRetrievalHelper一样
                        //最终调用的是AnnotationAwareAspectJAutoProxyCreator的isEligibleAspectBean
                        //最终调用的方法会判断是否设置了AspectBeanNamePattern
                        //如果设置了就利用这个pattern进行预先过滤,过滤掉不符合命名规范的bean
                        //默认是没有设置的,所以可以认为该方法始终返回true
                        if (!isEligibleBean(beanName)) 
							continue;
						
						// We must be careful not to instantiate beans eagerly as in this case they
						// would be cached by the Spring container but would not have been weaved.
						Class<?> beanType = this.beanFactory.getType(beanName, false);
						if (beanType == null) 
							continue;
						
                        //判断当前bean是否是切面类
						if (this.advisorFactory.isAspect(beanType)) 
							aspectNames.add(beanName);
							//构建切面元数据类
                            AspectMetadata amd = new AspectMetadata(beanType, beanName);
							//切面类类型为singleton的情况--这里不是spring中单例意思
                            if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) 
                                //构建拥有切面元数据和切面实例的工厂
								MetadataAwareAspectInstanceFactory factory =
										new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
                                //advisorFactory利用MetadataAwareAspectInstanceFactory
                                //提供的切面元数据信息解析得到一批advisor
								List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
                               	//如果切面类是spring单例bean,那么直接缓存当前切面类解析得到的一批
                                //增强器集合
								if (this.beanFactory.isSingleton(beanName)) 
									this.advisorsCache.put(beanName, classAdvisors);
								
								else 
                                    //如果切面类不是单例的,那么只会缓存MetadataAwareAspectInstanceFactory工厂对象,每次都需要利用该工厂对象获取一个新的切面实例
                                    //再利用advisorFactory解析获取一批advisor
									this.aspectFactoryCache.put(beanName, factory);
								
								advisors.addAll(classAdvisors);
							
							else 
                                //大部分情况下@AspectJ切面都是sigleton类型,其他情况可以不考虑
								...
						
					
					this.aspectBeanNames = aspectNames;
					return advisors;
				
			
		
        //存在缓存的情况下--满足下面这个条件,说明容器中不存在切面类
		if (aspectNames.isEmpty()) 
			return Collections.emptyList();
		
		List<Advisor>“idea无法解析@aspect注解”的解决方法(代码片段)

...没有引入以下两个jar包:解决方法:1.在官网下载AspectJ相关jar包链接:https://www.eclipse.org/aspectj/downloads.php.2.安装jar包后,将文章开头两个jar包复制到项目的lib中3.在专业版IDEA中开发AspectJ,需要确保下述插件被激活&#... 查看详情

“idea无法解析@aspect注解”的解决方法(代码片段)

...没有引入以下两个jar包:解决方法:1.在官网下载AspectJ相关jar包链接:https://www.eclipse.org/aspectj/downloads.php.2.安装jar包后,将文章开头两个jar包复制到项目的lib中3.在专业版IDEA中开发AspectJ,需要确保下述插件被激活&#... 查看详情

xml方式配置切面(代码片段)

...,通知作用到连接点需要有切入点表达式。 除了使用AspectJ注解声明切面,Spring也支持在bean配置文件中声明切面。这种声明是通过aop名称空间中的XML元素完成的。 正常情况下,基于注解的声明要优先于基于XML的声明。通... 查看详情

java使用注解加aspect实现拦截方法打印方法日志(代码片段)

    为了熟悉AOP中的ASPECTJ的使用,为了方便我们在写数据搬运型代码时候观测的方便,自己学习并定义了一组切面方法和注解,来实现这个功能,啥都不说了,先上代码:首先是注解定义:importjava.la... 查看详情

SwiftUI 中某些环境自动更新背后的奥秘

】SwiftUI中某些环境自动更新背后的奥秘【英文标题】:TheMysterybehindautoupdateofsomeEnvironmentinSwiftUI【发布时间】:2021-03-2917:23:50【问题描述】:我可以使用环境修改器轻松更改视图级别中名为backgroundColor的自定义环境!像这样下来... 查看详情

注解+aspect省时省力的管理好接口日志(代码片段)

背景无论是对外提供的RPC接口,还是项目内的普通方法,我们都会有需要打印方法入参、出参的需求,方便在遇到问题时通过查看日志快速定位,我们也会需要对方法的执行时间进行打印方便分析和调优。比较笨... 查看详情

aop面向切面编程androidstudio使用aspectj监控方法运行(定义连接点注解|定义aspect切面|定义切入点|逐个处理切入点的各个连接点)(代码片段)

文章目录一、定义JoinPoint连接点注解二、定义Aspect切面1、定义Aspect切面2、定义Aspect切面3、逐个处理切入点的各个连接点4、完整Aspect切面代码三、使用AspectJ埋点并监控方法性能一、定义JoinPoint连接点注解要监控哪些方法,首先要... 查看详情

注解+aspect省时省力的管理好接口日志(代码片段)

...etentionPolicy.RUNTIME)@Documentedpublic@interfaceRunTimeAnnotationAspect我们使用Aspect进行切面开发首先引用对应的pom依赖<dependency><groupId>org.aspectj</groupId><artifactId 查看详情

手把手教你解决循环依赖,一步一步地来窥探出三级缓存的奥秘(代码片段)

先不去管Spring中的循环依赖,我们先实现一个自定义注解,来模拟@Autowired的功能。一、自定义注解模拟@Autowired自定义Load注解,被该注解标识的字段,将会进行自动注入/***@authorqcy*@create2021/10/0213:31:20*/... 查看详情

springaop中@aspect拦截介绍(二)

参考技术A本章介绍的@Aspect拦截用的注解方式,对于想要实现多数据源切换,在指定类或方法上只需加上一个注解便可以实现入参出参日志打印的小伙伴,要认真看那一大堆有关SpringAOP支持的AspectJ切入点指示符的注释了。使用@Po... 查看详情

java使用注解加aspect实现拦截方法打印方法日志(代码片段)

    为了熟悉AOP中的ASPECTJ的使用,为了方便我们在写数据搬运型代码时候观测的方便,自己学习并定义了一组切面方法和注解,来实现这个功能,啥都不说了,先上代码:首先是注解定义:importjava.la... 查看详情

aspectj——基于注解的开发方式(代码片段)

基于注解的开发方式AspectJ5版本支持了基于注解的开发方式,当然其仍然需要AspectJ自己的编译器。要使用基于注解的开发方式,需要为项目引入aspectjweaver.jar包,该Jar包也在AspectJ安装目录下的lib目录中。aspectjweaver.jar... 查看详情

springaop官方文档学习笔记之基于注解的springaop(代码片段)

1.@Aspect注解(1)@Aspect注解用于声明一个切面类,我们可在该类中来自定义切面,早在Spring之前,AspectJ框架中就已经存在了这么一个注解,而Spring为了提供统一的注解风格,因此采用了和AspectJ框架相同的注解方式,这便是@Aspect注... 查看详情

工作中实现记录接口调用的注解例子(代码片段)

...义好了,我们就需要编写一个能处理上面注解的处理类@Aspect@ComponentpublicclassRequestLogAspectpublicfinalstaticLoggerlogger=LoggerFactory.getLogger(RequestLogAspect.class);@Pointcut("@annotation(com.***.***.***.service.aspect.RequestLogAnnotation)")//上面注解的包路径pub... 查看详情

reentrantlock源码探究探究公平锁与非公平锁背后的奥秘(代码片段)

本文目录前言ReentrantLock定义锁的可重入性什么是AQS公平、非公平锁区别一(lock方法)核心AQS解读AQS(tryAcquire)尝试去竞争锁AQS(addWaiter)维护双向链表AQS(acquireQueued)休眠第二个节点后的所有节... 查看详情

aop记录日志(代码片段)

...public@interfaceRptLogStringvalue()default"";2.aop相关的配置类@Slf4j@Aspect@ComponentpublicclassRptOperateLogAspect@Autowired@SuppressWarnings("all")privateRptOperateLogMapperrptOperateLogMapper;@AutowiredprivateUserServiceuserService;//定义切点@Pointcut//在注解的位置切入代码@Poi... 查看详情

图解|从武侠角度探究stl排序算法的奥秘(代码片段)

...悍的性能和效率是STL的更让人惊艳的地方。STL极致表现的背后是大牛们炉火纯青的编程技艺和追求极 查看详情

关于数据库时区,这么多奥秘你都知道么?(代码片段)

...介绍。本文分享自华为云社区《人人都知道的时区,背后竟然有这么多奥秘》,原文作者:leapdb。背景介绍时区同其它工业标准一样,标准化过程也是一个复杂而漫长的过程。GaussDB(DWS)作为一款面向全球用户的高... 查看详情