首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >SpringAop源码分析(基于注解)二:筛选通知器

SpringAop源码分析(基于注解)二:筛选通知器

作者头像
周同学
发布于 2019-10-24 10:54:34
发布于 2019-10-24 10:54:34
1.1K00
代码可运行
举报
文章被收录于专栏:一块自留地一块自留地
运行总次数:0
代码可运行

一、入口

我们已经知道BeanPostProcessors是在Bean实例化前后起作用的,如果看过前面的文章Spring Ioc源码分析 之 Bean的加载(八):初始化,应该知道Spring是在AbstractAutowireCapableBeanFactory#doCreateBean() 方法中有一个初始化Bean的方法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
exposedObject = initializeBean(beanName, exposedObject, mbd)

继续深入:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {
	//JDK的安全机制验证权限
	if (System.getSecurityManager() != null) {
		// <1> 激活 Aware 方法,对特殊的 bean 处理:Aware、BeanClassLoaderAware、BeanFactoryAware
		AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
			invokeAwareMethods(beanName, bean);
			return null;
		}, getAccessControlContext());
	}
	else {
		// <1> 激活 Aware 方法,对特殊的 bean 处理:Aware、BeanClassLoaderAware、BeanFactoryAware
		invokeAwareMethods(beanName, bean);
	}

	Object wrappedBean = bean;
	// <2> 后置处理器,before
	if (mbd == null || !mbd.isSynthetic()) {
		wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
	}

	// <3> 激活用户自定义的 init 方法
	try {
		invokeInitMethods(beanName, wrappedBean, mbd);
	}
	catch (Throwable ex) {
		throw new BeanCreationException(
				(mbd != null ? mbd.getResourceDescription() : null),
				beanName, "Invocation of init method failed", ex);
	}
	// <2> 后置处理器,after
	if (mbd == null || !mbd.isSynthetic()) {
	        // 我们关注的重点是这里!!!
		wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
	}

	return wrappedBean;
}

其中第<2>步就是触发我们BeanPostProcessors的地方。

我们再回过头来看AnnotationAwareAspectJAutoProxyCreator有一个上层父类AbstractAutoProxyCreator,它实现了SmartInstantiationAwareBeanPostProcessor接口,来看下它的主要方法。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//AbstractAutoProxyCreator.java

//在Bean初始化之前回调
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
	return bean;
}

/**
 * Create a proxy with the configured interceptors if the bean is
 * identified as one to proxy by the subclass.
 * @see #getAdvicesAndAdvisorsForBean
 */
//在Bean初始化之后回调
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) throws BeansException {
	if (bean != null) {
		Object cacheKey = getCacheKey(bean.getClass(), beanName);
		//判断缓存中是否有
		if (!this.earlyProxyReferences.contains(cacheKey)) {
		        // 没有,为 bean 生成代理对象
			return wrapIfNecessary(bean, beanName, cacheKey);
		}
	}
	return bean;
}

可以看到AbstractAutoProxyCreator类里实现了postProcessAfterInitialization()方法,该方法将在Bean初始化之后调用。 接着看wrapIfNecessary方法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//AbstractAutoProxyCreator.java

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
	if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
		return bean;
	}
	if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
		return bean;
	}

	/*
	 * 如果是基础设施类(Pointcut、Advice、Advisor 等接口的实现类),或是应该跳过的类,
	 * 则不应该生成代理,此时直接返回 bean
	 */
	if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
		this.advisedBeans.put(cacheKey, Boolean.FALSE);
		return bean;
	}

	// Create proxy if we have advice.
	// 返回匹配当前 bean 的所有的通知器  advisor、advice、interceptor
	Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
	if (specificInterceptors != DO_NOT_PROXY) {
		this.advisedBeans.put(cacheKey, Boolean.TRUE);
		// 核心!创建代理对象
		Object proxy = createProxy(
				bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
		this.proxyTypes.put(cacheKey, proxy.getClass());
		return proxy;
	}

	this.advisedBeans.put(cacheKey, Boolean.FALSE);
	return bean;
	}

这里看起来逻辑不复杂:

  • 找到匹配该Bean的所有通知器
  • 创建代理对象

但是这两步具体细节就很复杂了,我们一个一个来看,先看第一步。

二、找到匹配该Bean的所有通知器

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//AbstractAdvisorAutoProxyCreator

protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {
        //获取匹配的通知器
	List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
	if (advisors.isEmpty()) {
		return DO_NOT_PROXY;
	}
	return advisors.toArray();
	}

继续深入:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//AbstractAdvisorAutoProxyCreator.java

protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
        //获取所有的通知器
	List<Advisor> candidateAdvisors = findCandidateAdvisors();
	 
        //筛选可应用在 beanClass 上的 Advisor,通过 ClassFilter 和 MethodMatcher
        //对目标类和方法进行匹配
	List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
	//
	extendAdvisors(eligibleAdvisors);
	if (!eligibleAdvisors.isEmpty()) {
	        //设置排序,方便后面拦截器链执行
		eligibleAdvisors = sortAdvisors(eligibleAdvisors);
	}
	return eligibleAdvisors;
	}

2.1、获取有所通知器

接上面的代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//AnnotationAwareAspectJAutoProxyCreator.java

protected List<Advisor> findCandidateAdvisors() {
	// 调用父类方法从容器中获取所有的通知器
	List<Advisor> advisors = super.findCandidateAdvisors();
	// 解析 @Aspect 注解,并构建通知器
	if (this.aspectJAdvisorsBuilder != null) {
		advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
	}
	return advisors;
	}

2.1.1、调用父类方法从容器中获取所有的通知器

先看一下调用父类的方法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//AbstractAdvisorAutoProxyCreator.java

protected List<Advisor> findCandidateAdvisors() {
	Assert.state(this.advisorRetrievalHelper != null, "No BeanFactoryAdvisorRetrievalHelper available");
	return this.advisorRetrievalHelper.findAdvisorBeans();
	}

继续深入:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//BeanFactoryAdvisorRetrievalHelper.java

public List<Advisor> findAdvisorBeans() {
    String[] advisorNames = null;
    synchronized (this) {
        // cachedAdvisorBeanNames 是 advisor 名称的缓存
        advisorNames = this.cachedAdvisorBeanNames;
        
        //如果缓存为空,到容器中查找,
        //并设置缓存,后续直接使用缓存即可
        if (advisorNames == null) {
            // 从容器中查找 Advisor 类型 bean 的名称
            advisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
                    this.beanFactory, Advisor.class, true, false);
            // 设置缓存
            this.cachedAdvisorBeanNames = advisorNames;
        }
    }
    if (advisorNames.length == 0) {
        return new LinkedList<Advisor>();
    }

    List<Advisor> advisors = new LinkedList<Advisor>();
    // 遍历 advisorNames
    for (String name : advisorNames) {
        if (isEligibleBean(name)) {
            // 忽略正在创建中的 advisor bean
            if (this.beanFactory.isCurrentlyInCreation(name)) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Skipping currently created advisor '" + name + "'");
                }
            }
            else {
                try {
                    
                    //调用 getBean 方法从容器中获取名称为 name 的 bean,
                    //并将 bean 添加到 advisors 中
                    advisors.add(this.beanFactory.getBean(name, Advisor.class));
                }
                catch (BeanCreationException ex) {
                    Throwable rootCause = ex.getMostSpecificCause();
                    if (rootCause instanceof BeanCurrentlyInCreationException) {
                        BeanCreationException bce = (BeanCreationException) rootCause;
                        if (this.beanFactory.isCurrentlyInCreation(bce.getBeanName())) {
                            if (logger.isDebugEnabled()) {
                                logger.debug("Skipping advisor '" + name +
                                        "' with dependency on currently created bean: " + ex.getMessage());
                            }
                            continue;
                        }
                    }
                    throw ex;
                }
            }
        }
    }

    return advisors;
}

这段代码虽然很长,但并不复杂:

  • 先从缓存中获取,获取不到就从IOC容器中获取类型为Advisor的BeanName
  • 遍历获取到的BeanName,调用getBean()方法获取实例,并加入到通知器集合中

2.1.2、解析 @Aspect 注解,并构建通知器

代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//BeanFactoryAspectJAdvisorsBuilder.java

public List<Advisor> buildAspectJAdvisors() {
	List<String> aspectNames = this.aspectBeanNames;

	if (aspectNames == null) {
	synchronized (this) {
		aspectNames = this.aspectBeanNames;
		if (aspectNames == null) {
			List<Advisor> advisors = new LinkedList<>();
			aspectNames = new LinkedList<>();
			// 从容器中获取所有 bean 的名称
			String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
					this.beanFactory, Object.class, true, false);
			for (String beanName : beanNames) {
				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.
				// 根据 beanName 获取 bean 的类型
				Class<?> beanType = this.beanFactory.getType(beanName);
				if (beanType == null) {
					continue;
				}
				// 检测 beanType 是否包含 Aspect 注解
				if (this.advisorFactory.isAspect(beanType)) {
					aspectNames.add(beanName);
					//创建Aspect元数据
					AspectMetadata amd = new AspectMetadata(beanType, beanName);
					if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {
						//创建元数据aop实例化工厂
						MetadataAwareAspectInstanceFactory factory =
								new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
						// 从工厂中获取通知器
						List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
						if (this.beanFactory.isSingleton(beanName)) {
							this.advisorsCache.put(beanName, classAdvisors);
						}
						else {
							this.aspectFactoryCache.put(beanName, factory);
						}
						advisors.addAll(classAdvisors);
					}
					else {
						// Per target or per this.
						if (this.beanFactory.isSingleton(beanName)) {
							throw new IllegalArgumentException("Bean with name '" + beanName +
									"' is a singleton, but aspect instantiation model is not singleton");
						}
						MetadataAwareAspectInstanceFactory factory =
								new PrototypeAspectInstanceFactory(this.beanFactory, beanName);
						this.aspectFactoryCache.put(beanName, factory);
						advisors.addAll(this.advisorFactory.getAdvisors(factory));
					}
				}
			}
			this.aspectBeanNames = aspectNames;
			return advisors;
		}
	}
}

if (aspectNames.isEmpty()) {
	return Collections.emptyList();
}
List<Advisor> advisors = new LinkedList<>();
for (String aspectName : aspectNames) {
	List<Advisor> cachedAdvisors = this.advisorsCache.get(aspectName);
	if (cachedAdvisors != null) {
		advisors.addAll(cachedAdvisors);
	}
	else {
		MetadataAwareAspectInstanceFactory factory = this.aspectFactoryCache.get(aspectName);
		advisors.addAll(this.advisorFactory.getAdvisors(factory));
	}
	}
	return advisors;
	}

代码很长,但我们在只需要关注关键步骤即可:

  • 从容器中获取所有 bean 的名称
  • 遍历,根据 beanName 获取 bean 的类型
  • 检测 beanType 是否包含 Aspect 注解
  • 从工厂中获取通知器

这里也可以和我们前面的demo对应起来,我们之前定义了一个LogAspect的类,然后用注解@Component@Aspect声明了。 上面这段代码的逻辑就是:找到这个标注@Aspect的类,并找到里面定义的通知器,如@Before@After等。 同时这也回答了上篇文章的一个问题:Spring是怎么找到我们定义的切面的?

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Aspect
@Component
@EnableAspectJAutoProxy
public class LogAspect {

	@Before("execution(* com.mydemo.work.StudentController.getName(..))")
	public void doBefore() {
		System.out.println("========before");
	}

	@After("execution(* com.mydemo.work.StudentController.getName(..))")
	public void doAfter() {
		System.out.println("========after");
	}
}

接着看从工厂获取通知器的方法this.advisorFactory.getAdvisors(factory)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//ReflectiveAspectJAdvisorFactory.java

public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) {
	Class<?> aspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();
	String aspectName = aspectInstanceFactory.getAspectMetadata().getAspectName();
	validate(aspectClass);

	// We need to wrap the MetadataAwareAspectInstanceFactory with a decorator
	// so that it will only instantiate once.
	MetadataAwareAspectInstanceFactory lazySingletonAspectInstanceFactory =
			new LazySingletonAspectInstanceFactoryDecorator(aspectInstanceFactory);

	List<Advisor> advisors = new LinkedList<>();
<1>	//获取该切面的所有方法,排除@Pointcut修饰的
	for (Method method : getAdvisorMethods(aspectClass)) {
		//遍历,获取被 通知注解 修饰的方法,并封装成Advisor
<2>		Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, advisors.size(), aspectName);
		if (advisor != null) {
			advisors.add(advisor);
		}
	}

	// If it's a per target aspect, emit the dummy instantiating aspect.
	if (!advisors.isEmpty() && lazySingletonAspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {
		Advisor instantiationAdvisor = new SyntheticInstantiationAdvisor(lazySingletonAspectInstanceFactory);
		advisors.add(0, instantiationAdvisor);
	}

	// Find introduction fields.
	for (Field field : aspectClass.getDeclaredFields()) {
		Advisor advisor = getDeclareParentsAdvisor(field);
		if (advisor != null) {
			advisors.add(advisor);
		}
	}

	return advisors;
	}

接着追踪getAdvisor()方法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory,
		int declarationOrderInAspect, String aspectName) {

	validate(aspectInstanceFactory.getAspectMetadata().getAspectClass());
	//获取切点Pointcut
<3>	AspectJExpressionPointcut expressionPointcut = getPointcut(
			candidateAdviceMethod, aspectInstanceFactory.getAspectMetadata().getAspectClass());
	if (expressionPointcut == null) {
		return null;
	}

	// 创建 Advisor 实现类,封装切点表达式、通知名称、方法名称等
<6>	return new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod,
			this, aspectInstanceFactory, declarationOrderInAspect, aspectName);
}

@Nullable
private AspectJExpressionPointcut getPointcut(Method candidateAdviceMethod, Class<?> candidateAspectClass) {
	// 获取方法上的 AspectJ 相关注解,包括 @Before,@After、@Around、@Pointcut 等
	//因为这些注解上都可以设置切点
<4>	AspectJAnnotation<?> aspectJAnnotation =
			AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
	if (aspectJAnnotation == null) {
		return null;
	}

	AspectJExpressionPointcut ajexp =
			new AspectJExpressionPointcut(candidateAspectClass, new String[0], new Class<?>[0]);
<5>	//设置切点匹配表达式
	ajexp.setExpression(aspectJAnnotation.getPointcutExpression());
	if (this.beanFactory != null) {
		ajexp.setBeanFactory(this.beanFactory);
	}
	return ajexp;
}

这里的逻辑其实也不复杂,。

  • <1>,获取切面中的所有方法,排除@Pointcut修饰的方法
  • <2>,遍历所有方法
  • <3>,获取该方法的切点
  • <4>,根据AspectJ相关注解,包括 @Before,@After、@Pointcut等获取切点
  • <5>,设置切点表达式到AspectJExpressionPointcut 封装结果如下:

  • <6>,创建Advisor,封装切点表达式、通知名称、方法名称等 封装结果如下:

this.advisorFactory.getAdvisors(factory)这段代码的最终目的,就是获取该切面所有的通知方法、它们的切点,并把它们都封装成一个个Advisor

但其实每个Advisor里的Advice都是不同的,我们来看下创建Advisor的过程,即第<6>步:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//InstantiationModelAwarePointcutAdvisorImpl.java

public InstantiationModelAwarePointcutAdvisorImpl(AspectJExpressionPointcut declaredPointcut,
		Method aspectJAdviceMethod, AspectJAdvisorFactory aspectJAdvisorFactory,
		MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) {

	this.declaredPointcut = declaredPointcut;
	this.declaringClass = aspectJAdviceMethod.getDeclaringClass();
	this.methodName = aspectJAdviceMethod.getName();
	this.parameterTypes = aspectJAdviceMethod.getParameterTypes();
	this.aspectJAdviceMethod = aspectJAdviceMethod;
	this.aspectJAdvisorFactory = aspectJAdvisorFactory;
	this.aspectInstanceFactory = aspectInstanceFactory;
	this.declarationOrder = declarationOrder;
	this.aspectName = aspectName;

	if (aspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {
		// Static part of the pointcut is a lazy type.
		Pointcut preInstantiationPointcut = Pointcuts.union(
				aspectInstanceFactory.getAspectMetadata().getPerClausePointcut(), this.declaredPointcut);

		// Make it dynamic: must mutate from pre-instantiation to post-instantiation state.
		// If it's not a dynamic pointcut, it may be optimized out
		// by the Spring AOP infrastructure after the first evaluation.
		this.pointcut = new PerTargetInstantiationModelPointcut(
				this.declaredPointcut, preInstantiationPointcut, aspectInstanceFactory);
		this.lazy = true;
	}
	else {
		// A singleton aspect.
		this.pointcut = this.declaredPointcut;
		this.lazy = false;
		// 按照注解解析 Advice
		this.instantiatedAdvice = instantiateAdvice(this.declaredPointcut);
	}
	}

上面是 InstantiationModelAwarePointcutAdvisorImpl 的构造方法,不过我们无需太关心这个方法中的一些初始化逻辑。我们把目光移到构造方法的最后一行代码中,即 instantiateAdvice(this.declaredPointcut),这个方法用于创建通知 Advice。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private Advice instantiateAdvice(AspectJExpressionPointcut pcut) {
    return this.aspectJAdvisorFactory.getAdvice(this.aspectJAdviceMethod, pcut,
            this.aspectInstanceFactory, this.declarationOrder, this.aspectName);
}

public Advice getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut expressionPointcut,
        MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) {

    Class<?> candidateAspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();
    validate(candidateAspectClass);

    // 获取 Advice 注解
    AspectJAnnotation<?> aspectJAnnotation =
            AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
    if (aspectJAnnotation == null) {
        return null;
    }

    if (!isAspect(candidateAspectClass)) {
        throw new AopConfigException("Advice must be declared inside an aspect type: " +
                "Offending method '" + candidateAdviceMethod + "' in class [" +
                candidateAspectClass.getName() + "]");
    }

    if (logger.isDebugEnabled()) {
        logger.debug("Found AspectJ method: " + candidateAdviceMethod);
    }

    AbstractAspectJAdvice springAdvice;

    // 按照注解类型生成相应的 Advice 实现类
    switch (aspectJAnnotation.getAnnotationType()) {
        case AtBefore:    // @Before -> AspectJMethodBeforeAdvice
            springAdvice = new AspectJMethodBeforeAdvice(
                    candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
            break;

        case AtAfter:    // @After -> AspectJAfterAdvice
            springAdvice = new AspectJAfterAdvice(
                    candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
            break;

        case AtAfterReturning:    // @AfterReturning -> AspectJAfterAdvice
            springAdvice = new AspectJAfterReturningAdvice(
                    candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
            AfterReturning afterReturningAnnotation = (AfterReturning) aspectJAnnotation.getAnnotation();
            if (StringUtils.hasText(afterReturningAnnotation.returning())) {
                springAdvice.setReturningName(afterReturningAnnotation.returning());
            }
            break;

        case AtAfterThrowing:    // @AfterThrowing -> AspectJAfterThrowingAdvice
            springAdvice = new AspectJAfterThrowingAdvice(
                    candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
            AfterThrowing afterThrowingAnnotation = (AfterThrowing) aspectJAnnotation.getAnnotation();
            if (StringUtils.hasText(afterThrowingAnnotation.throwing())) {
                springAdvice.setThrowingName(afterThrowingAnnotation.throwing());
            }
            break;

        case AtAround:    // @Around -> AspectJAroundAdvice
            springAdvice = new AspectJAroundAdvice(
                    candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
            break;

        
         //什么都不做,直接返回 null。
        case AtPointcut:    
            if (logger.isDebugEnabled()) {
                logger.debug("Processing pointcut '" + candidateAdviceMethod.getName() + "'");
            }
            return null;
            
        default:
            throw new UnsupportedOperationException(
                    "Unsupported advice type on method: " + candidateAdviceMethod);
    }

    springAdvice.setAspectName(aspectName);
    springAdvice.setDeclarationOrder(declarationOrder);
    
    //获取方法的参数列表名称,比如方法 int sum(int numX, int numY), 
    //getParameterNames(sum) 得到 argNames = [numX, numY]
    String[] argNames = this.parameterNameDiscoverer.getParameterNames(candidateAdviceMethod);
    if (argNames != null) {
        // 设置参数名
        springAdvice.setArgumentNamesFromStringArray(argNames);
    }
    springAdvice.calculateArgumentBindings();
    return springAdvice;
}

可见,根据注解的不同,创建不同的Advice,并封装到Advisor中。

2.2、筛选合适的通知器

现在我们已经拿到了所有通知器,接下来就要筛选出匹配当前Bean的通知器。 代码List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);中:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//AbstractAdvisorAutoProxyCreator.java

protected List<Advisor> findAdvisorsThatCanApply(
		List<Advisor> candidateAdvisors, Class<?> beanClass, String beanName) {

	ProxyCreationContext.setCurrentProxiedBeanName(beanName);
	try {
	        //筛选匹配的通知器
		return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);
	}
	finally {
		ProxyCreationContext.setCurrentProxiedBeanName(null);
	}
	}

继续深入:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//AbstractAdvisorAutoProxyCreator.java

protected List<Advisor> findAdvisorsThatCanApply(
		List<Advisor> candidateAdvisors, Class<?> beanClass, String beanName) {

	ProxyCreationContext.setCurrentProxiedBeanName(beanName);
	try {
		//筛选出匹配当前Bean的通知器
		return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);
	}
	finally {
		ProxyCreationContext.setCurrentProxiedBeanName(null);
	}
	}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//AopUtils.java

public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {
	if (candidateAdvisors.isEmpty()) {
		return candidateAdvisors;
	}
	List<Advisor> eligibleAdvisors = new LinkedList<>();
	// 筛选 IntroductionAdvisor 类型的通知器
	for (Advisor candidate : candidateAdvisors) {
		if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) {
			eligibleAdvisors.add(candidate);
		}
	}
	boolean hasIntroductions = !eligibleAdvisors.isEmpty();
	// 筛选普通类型的通知器
	for (Advisor candidate : candidateAdvisors) {
		if (candidate instanceof IntroductionAdvisor) {
			// already processed
			continue;
		}
		if (canApply(candidate, clazz, hasIntroductions)) {
			eligibleAdvisors.add(candidate);
		}
	}
	return eligibleAdvisors;
	}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//AopUtils.java

public static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) {
	//ClassFilter直接匹配
	if (advisor instanceof IntroductionAdvisor) {
		return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass);
	}
	else if (advisor instanceof PointcutAdvisor) {
		PointcutAdvisor pca = (PointcutAdvisor) advisor;
		//继续调用重载方法
		return canApply(pca.getPointcut(), targetClass, hasIntroductions);
	}
	else {
		// It doesn't have a pointcut so we assume it applies.
		return true;
	}
	}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//AopUtils.java

public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {
	Assert.notNull(pc, "Pointcut must not be null");
	if (!pc.getClassFilter().matches(targetClass)) {
		return false;
	}

	MethodMatcher methodMatcher = pc.getMethodMatcher();
	if (methodMatcher == MethodMatcher.TRUE) {
		// No need to iterate the methods if we're matching any method anyway...
		return true;
	}

	IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;
	if (methodMatcher instanceof IntroductionAwareMethodMatcher) {
		introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher;
	}

	//查找当前类及其父类(以及父类的父类等等)所实现的接口,由于接口中的方法是 public,
	//所以当前类可以继承其父类,和父类的父类中所有的接口方法
	Set<Class<?>> classes = new LinkedHashSet<>(ClassUtils.getAllInterfacesForClassAsSet(targetClass));
	classes.add(targetClass);
	for (Class<?> clazz : classes) {
		// 获取当前类的方法列表,包括从父类中继承的方法
		Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
		for (Method method : methods) {
			// 使用 methodMatcher 匹配方法,匹配成功即可立即返回
			if ((introductionAwareMethodMatcher != null &&
					introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions)) ||
					methodMatcher.matches(method, targetClass)) {
				return true;
			}
		}
	}

	return false;
	}

上面就是筛选通知器的过程,筛选的工作主要由 ClassFilter 和 MethodMatcher 来完成。关于 ClassFilter 和 MethodMatcher,在 AOP 中,切点 Pointcut 是用来匹配连接点的,以 AspectJExpressionPointcut 类型的切点为例。该类型切点实现了ClassFilter 和 MethodMatcher 接口,匹配的工作则是由 AspectJ 表达式解析器负责。除了使用 AspectJ 表达式进行匹配,Spring 还提供了基于正则表达式的切点类,以及更简单的根据方法名进行匹配的切点类。大家有兴趣的话,可以自己去了解一下,这里就不多说了。

现在,我们知道了通知是怎么创建和筛选的。那下篇文章,我们一起来分析一下AOP是怎么创建代理对象的。

总结

这篇文章花了比较大的功夫,受个人能力限制,很遗憾没有对里面的源码作非常详细的分析,只理解了主流程,希望朋友们发现文章中的错误或不妥之处,还请指出,互相交流~

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019年10月21日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
由一次渗透测试引发的HTTP请求走私思考
前几天朋友发了一个朋友圈说他的网站刚建好没有多久就被别人给脱库了,里面有一些客户的资料,有点难受。便向他询问了一些事情,溯源无果后便和他商量了一下帮助他将再次新建的网站进行了一次友情渗透测试。而HTTP请求走私漏洞也是在其中发现的一个可以小事化大,大事化危的一个漏洞。遂将其发现过程记录下来
谢公子
2022/01/20
9790
由一次渗透测试引发的HTTP请求走私思考
请求走私利用实践(上)
在上次的"Websocket通信安全概览"一文中对WebSocket的请求走私做了一个简单的介绍后总觉得对请求走私这一部分知识内容缺乏一个完整性的梳理,后面经过几次断断续续的补充以及时间的拼凑最终有了这一篇较为完整的关于请求走私的介绍文章和利用实践文章,而这也算是填补了自己之前遗留的一个坑吧
Al1ex
2024/01/26
4750
请求走私利用实践(上)
请求走私利用实践(下)
假设应用程序使用前端服务器来实现访问控制限制,仅当用户被授权访问所请求的URL时才转发请求,然后后端服务器接受每个请求,而不做进一步的检查,在这种情况下可以利用HTTP请求走私漏洞通过请求走私访问受限制的URL从而绕过访问控制,假如允许当前用户访问/home,但不允许访问/admin,他们可以使用以下请求走私攻击绕过这一限制:
Al1ex
2024/01/26
2640
请求走私利用实践(下)
HTTP2请求走私(上)
HTTP/2是HTTP协议自1999年HTTP 1.1发布后的首个更新,它由互联网工程任务组(IETF)的Hypertext Transfer Protocol Bis(httpbis)工作小组进行开发,该组织于2014年12月将HTTP/2标准提议递交至IESG进行讨论并于2015年2月17日被批准,目前多数主流浏览器已经在2015年底支持了该协议,此外根据W3Techs的统计数据表示自2017年5月,在排名前一千万的网站中有13.7%支持了HTTP/2,本篇文章我们将主要对HTTP/2协议的新特性以及HTTP/2中的请求走私进行详细介绍
Al1ex
2024/02/01
3680
HTTP2请求走私(上)
HTTP2请求走私(下)
网站即使采取措施阻止基本H2.CL或H2.TE攻击(例如:验证content-length或剥离任何transfer-encoding头),我们也可以通过利用HTTP/2的二进制格式中允许的一些方法来绕过这些前端措施,在HTTP/1中我们有时可以利用服务器处理独立换行符(\n)方式之间的差异来走私被禁止的头
Al1ex
2024/02/01
4890
HTTP2请求走私(下)
[ffffffff0x] 浅析 HTTP Smuggling 攻击
由于各种各样的原因,各网站通常使用多级代理模式对外开放Web服务,如CDN、Nginx代理等。HTTP/1.1 版本倾向于使用keep-alive长连接进行通信,提高前后端之间的通讯效率。也就是说多个人的流量可能会在前后端之间的同一个tcp会话中传输,另外前后端对于Content-Length和Transfer-Encoding的解析处理方法不同,有可能造成请求污染的情况,直接导致HTTP Smuggling攻击的出现。
r0fus0d
2020/12/27
1.1K0
[ffffffff0x] 浅析 HTTP Smuggling 攻击
协议层的攻击——HTTP请求走私
最近在学习研究BlackHat的议题,其中有一篇议题——"HTTP Desync Attacks: Smashing into the Cell Next Door"引起了我极大地兴趣,在其中,作者讲述了HTTP走私攻击这一攻击手段,并且分享了他的一些攻击案例。我之前从未听说过这一攻击方式,决定对这一攻击方式进行一个完整的学习梳理,于是就有了这一篇文章。
知道创宇云安全
2019/10/11
2K0
协议层的攻击——HTTP请求走私
非侵入式入侵 —— Web缓存污染与请求走私
本文介绍了两种攻击者无需直接接触服务端即可攻击和影响用户行为的安全漏洞 —— Web缓存污染与请求走私。Web缓存污染旨在通过攻击者向缓存服务器投递恶意缓存内容,使得用户返回响应结果而触发安全风险。HTTP请求走私旨在基于前置服务器(CDN、反向代理等)与后置服务器对用户请求体的长度判断标准不一致的特性,构造能够被同一TCP连接中其它用户夹带部分恶意内容的攻击请求,从而篡改了受害者的请求与响应行为。两种漏洞均需要通过针对中间件的合理配置与业务接口的合理设计进行排查和防御。
2020labs小助手
2023/03/14
6220
请求走私利用扩展
在之前我们介绍了HTTP/1和HTTP/2的请求走私原理以及利用方法,本篇文章将对此进行进一步扩展介绍一些特殊的场景下的请求走私的检测以及利用方式和思路,对请求走私进行一个扩展补充,例如:CL.0请求走私、H2.0走私、去同步化攻击等,同时本篇文章也是对请求走私系列的最后一个收官,至此请求之前留下的请求走私的坑也算是被填完了
Al1ex
2024/02/22
4110
请求走私利用扩展
初探HTTP请求走私
文章首发于跳跳糖社区https://tttang.com/archive/1808/
用户9691112
2023/05/18
2K0
初探HTTP请求走私
Web Security 之 CSRF
在本节中,我们将解释什么是跨站请求伪造,并描述一些常见的 CSRF 漏洞示例,同时说明如何防御 CSRF 攻击。
凌虚
2021/03/19
2.6K0
Web Security 之 CSRF
Web Security 之 HTTP Host header attacks
在本节中,我们将讨论错误的配置和有缺陷的业务逻辑如何通过 HTTP Host 头使网站遭受各种攻击。我们将概述识别易受 HTTP Host 头攻击的网站的高级方法,并演示如何利用此方法。最后,我们将提供一些有关如何保护自己网站的一般建议。
凌虚
2021/03/19
6.5K0
Web Security 之 HTTP Host header attacks
基于select IO复用的HTTP服务器(十一)
没想到距离上篇文章才过去仅仅半个多月就发生了这么多的事情,其之沉、其之重、其之殇,如氤氲般笼罩环绕在这片古老的大地上。钟南山眼中的泪水让我没有丝毫的心情再在文章中随手写段子,白衣天使们脸上的疲倦让我没有了任何像以往那种调侃方式写文章的感觉。可能你们不太会适应失去了段子的本公号,但是只要哪天钟佬说“ 可以了 ”,我立马就恢复如初。
老李秀
2020/02/19
1.3K0
CTF中的请求走私
HTTP请求走私是一种干扰网站处理从一个或多个用户接收的HTTP请求序列方式的技术,它允许攻击者绕过安全控制获得对敏感数据的未经授权的访问并直接危害其他应用程序用户,请求走私大多发生于前端服务器和后端服务器对客户端传入的数据理解不一致的情况,主要是因为HTTP规范提供了两种不同的方法来指定请求的结束位置,即Content-Length和Transfer-Encoding标头,请求走私主要与HTTP/1请求相关,但是支持HTTP/2的网站可能容易受到攻击,具体取决于其后端架构,本篇文章我们主要介绍一些CTF中常见的请求走私题目并对请求走私的利用实现一个强化效果
Al1ex
2024/01/22
3430
CTF中的请求走私
rfc7230 Message Syntax and Routing
HTTP使用.来标识协议版本。本规范定义的版本为"1.1"。版本号表示发送端遵从对应HTTP版本的实现需求。
charlieroro
2020/03/24
1.1K0
Responses 部分
(3)HTTP/1.1: URI(Uniform Resource Identifier,统一资源标识符)及其版本
翎野君
2023/05/12
3110
Responses 部分
Smuggler:一款功能强大的HTTP请求走私和去同步安全测试工具
Smuggler是一款功能强大的HTTP请求走私和去同步安全测试工具,该工具基于纯Python 3开发,可以帮助广大研究人员针对应用程序的HTTP协议执行安全分析和测试。
FB客服
2024/03/01
3560
Smuggler:一款功能强大的HTTP请求走私和去同步安全测试工具
Web Security 之 CORS
在本节中,我们将解释什么是跨域资源共享(CORS),并描述一些基于 CORS 的常见攻击示例,以及讨论如何防御这些攻击。
凌虚
2021/03/03
1.5K0
从项目实际问题引发的思考
现在有一个 Web 项目,前端是使用 Vue.js 开发的,整个前端需要部署到 K8S 上,后端和前端分开,同样也需要部署到 K8S 上,因此二者需要打包为 Docker 镜像。
崔庆才
2019/12/11
5930
netty对http协议解析原理解析
本文主要介绍netty对http协议解析原理,着重讲解keep-alive,gzip,truncked等机制,详细描述了netty如何实现对http解析的高性能。 1 http协议 1.1 描述 标示
xiangzhihong
2018/02/05
3.4K0
netty对http协议解析原理解析
相关推荐
由一次渗透测试引发的HTTP请求走私思考
更多 >
LV.1
这个人很懒,什么都没有留下~
目录
  • 一、入口
  • 二、找到匹配该Bean的所有通知器
    • 2.1、获取有所通知器
    • 2.1.1、调用父类方法从容器中获取所有的通知器
    • 2.1.2、解析 @Aspect 注解,并构建通知器
    • 2.2、筛选合适的通知器
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档