Spring AOP源码分析(二):AOP的三种配置方式与内部解析实现

版权声明:非商业目的可自由转载,转载请标明出处 https://blog.csdn.net/u010013573/article/details/88324682

AOP配置

  • 在应用代码中,可以通过在spring的XML配置文件applicationContext.xml或者基于注解方式来配置AOP。
  • AOP配置的核心元素为:pointcut,advisor,aspect,pointcut用于定义需要该辅助功能的类或方法集合;advisor则是将advice和pointcut结合起来,在spring的IOC容器启动时,为pointcut匹配的类生成代理对象,使用拦截器拦截对应的方法的执行,将辅助功能advice添加进去;aspect表示一个完整切面,即在aspect对应的类中定义辅助方法advice,然后在aspect中组装到pointcut拦截的方法集合中。

一、XML配置方式

  • 在applicationContext.xml配置文件中配置AOP,所有配置都在<aop:config >命名空间内,其中可以通过aspect或者advisor两种方式来配置,二者是等价的。
基于aspect配置
  • 增强配置类AnnotationAOPXmlConfig定义:

    package com.yzxie.demo.aop;
    
    /*
     * 切面支持类
     */
    public class AnnotationAOPXmlConfig {
    
        // 切点的方法执行前执行
        public void beforeAdivce(){
            System.out.println("注解类型前置增强");
        }
        
        // 切点的方法执行后执行
        public void afterAdivce(){
            System.out.println("注解类型后置增强");
        }
        
        // 切点的方法返回后执行
        public void afterReturningAdivce(){
            System.out.println("方法返回后执行");
        }
        
        // 切点的方法执行抛异常时执行
        public void afterThrowingAdivce(){
            System.out.println("方法执行抛异常");
        }
        
        // 切点的方法执行前后均执行
        public void aroundAdvice(ProceedingJoinPoint pjp) throws Throwable{
            System.out.println("注解类型环绕增强,方法执行前");
            
            //执行实际的目标方法
            pjp.proceed();
            
            System.out.println("注解类型环绕增强,方法执行后");
        }
    }
    
  • xml配置:基于aspect配置

    <!-- 辅助方法定义类,在内部包含beforeAdivce,afterAdivce等方法定义 -->
    <bean id="annotationAOPXmlConfig"
        class="com.yzxie.demo.aop.AnnotationAOPXmlConfig" />
    
    <aop:config>
    
        <!-- 切点配置 -->
        <aop:pointcut id="targetMethod" 
            expression="execution(* *.testAOP(..))" />
        
        <!-- 基于aspect配置一个完整切面 -->
        <aop:aspect ref="annotationAOPXmlConfig">
            <!-- 声明前置增强 (在切点方法被执行前调用)-->
            <aop:before method="beforeAdivce"   
                pointcut-ref="targetMethod"/>
            <!-- 声明后置增强 (在切点方法被执行后调用)-->
            <aop:after method="afterAdivce"
                pointcut-ref="targetMethod"/>
            <!-- 环绕增强 -->
            <aop:around method="afterAdivce"
                pointcut-ref="embark"/>
            <!-- 方法返回增强 -->
            <aop:returning method="afterReturningAdivce"
                pointcut-ref="targetMethod"/>
            <!-- 方法抛异常增强 -->
            <aop:throwing method="afterThrowingAdivce"
                pointcut-ref="targetMethod"/>
        </aop:aspect>
    </aop:config>
    
基于advisor配置
  • 与aspect不同的是,需要对每个advice都定义一个类,然后使用advisor组装到pointcut拦截的方法。

  • 辅助方法对应的类定义:其中一个环绕增强AroundAdvice定义。

    package com.yzxie.demo.aop.advice;
    
    import java.lang.reflect.Method;
    import org.aopalliance.intercept.MethodInterceptor;
    import org.aopalliance.intercept.MethodInvocation;
    
    // 切点的方法执行前后均执行
    public class AroundAdvice implements MethodInterceptor {
    
        public Object invoke(MethodInvocation arg0) throws Throwable {
            System.out.println("注解类型环绕增强,方法执行前");
                
            //执行实际的目标方法
            pjp.proceed();
            
            System.out.println("注解类型环绕增强,方法执行后");
        }
    }
    
  • xml配置:

    <bean id="aroundAdvice" class="com.yzxie.demo.aop.advice.AroundAdvice" />
        
    <!-- 配置切面 -->
    <aop:config>
        <!-- 切点配置 -->
        <aop:pointcut id="targetMethod" 
            expression="execution(* *.testAOP(..))" />
        
        <!-- 配置环绕增强advisor -->       
        <aop:advisor advice-ref="aroundAdvice"
            pointcut-ref="targetMethod"/>
        
        <!-- 配置其他增强advisor -->
    </aop:config>
    

二、基于注解的配置方式

  • 为了在spring中启动对@AspectJ注解支持,需要在类加载路径下新增两个AspectJ库:aspectjweaver.jar和aspectjrt.jar。除此之外,Spring AOP还需要依赖一个aopalliance.jar包

  • 基于注解的配置通常也需要先在spring的XML配置文件中配置来开启基于注解的配置开关。如下:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:context="http://www.springframework.org/schema/context"
        xmlns:aop="http://www.springframework.org/schema/aop"
        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
            
        <!-- 开启注解扫描 -->
        <context:component-scan base-package="com.yzxie.demo.aop"/>
        
        <!-- 开启aop注解方式,此步骤不能少,这样java类中的aop注解才会生效 -->
        <aop:aspectj-autoproxy/>
    </beans>
    
  • 基于以上配置,在com.yzxie.demo.aop包下定义一个AOP配置类,其中需要同时使用@Component和@Aspect注解,如下:

    package com.yzxie.demo.aop;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.After;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.annotation.Pointcut;
    import org.springframework.stereotype.Component;
    
    @Component
    @Aspect
    public class AnnotationAOPConfig {
    
        //定义切点,即拦截所有方法名为testAOP的方法的执行
        @Pointcut("execution(* *.testAOP(..))")
        public void targetMethod(){}
        
        // 切点的方法执行前执行
        @Before("targetMethod()")
        public void beforeAdivce(){
            System.out.println("注解类型前置增强");
        }
        
        // 切点的方法执行后执行
        @After("targetMethod()")
        public void afterAdivce(){
            System.out.println("注解类型后置增强");
        }
        
        // 切点的方法返回后执行
        @AfterRunning("targetMethod()")
        public void afterReturningAdivce(){
            System.out.println("方法返回后执行");
        }
        
        // 切点的方法执行抛异常时执行
        @AfterThrowing("targetMethod()")
        public void afterThrowingAdivce(){
            System.out.println("方法执行抛异常");
        }
        
        // 切点的方法执行前后均执行
        @Around("targetMethod()")
        public void aroundAdvice(ProceedingJoinPoint pjp) throws Throwable{
            System.out.println("注解类型环绕增强,方法执行前");
            
            //执行实际的目标方法
            pjp.proceed();
            
            System.out.println("注解类型环绕增强,方法执行后");
        }
    }
    

内部源码解析

  • 在内部源码实现当中,主要是在spring容器启动,加载解析applicationContext.xml时,解析对应的标签来完成AOP相关组件的加载。

  • 与spring的其他标签解析规则一样,在spring-aop源码包的META-INF目录的spring.handlers文件中定义aop命令空间解析器:

    http\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler
    
  • AopNamespaceHandler在spring-aop的config包内定义,定义如下:

    public class AopNamespaceHandler extends NamespaceHandlerSupport {
    
    	/**
    	 * Register the {@link BeanDefinitionParser BeanDefinitionParsers} for the
    	 * '{@code config}', '{@code spring-configured}', '{@code aspectj-autoproxy}'
    	 * and '{@code scoped-proxy}' tags.
    	 */
    	@Override
    	public void init() {
    		// In 2.0 XSD as well as in 2.1 XSD.
    		// aop:config
    		registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
    		
    		// aop:aspectj-autoproxy
    		registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
    		
    		// aop:scoped-proxy
    		registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
    
    		// Only in 2.0 XSD: moved to context namespace as of 2.1
    		// aop:spring-configured
    		registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
    	}
    }
    

aop:config标签解析

  • aop:config的解析器为ConfigBeanDefinitionParser,主要用于解析aop:config内部各标签并生成对应的bean对象,注册到spring的IOC容器中,具体逻辑在parse方法定义:

    public BeanDefinition parse(Element element, ParserContext parserContext) {
    	CompositeComponentDefinition compositeDef =
    			new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element));
    	parserContext.pushContainingComponent(compositeDef);
    
    	// 配置用于将pointcut匹配的bean生成对应的Proxy代理对象的BeanPostProcessor,
    	// 该BeanPostProcessor具体为AspectJAwareAdvisorAutoProxyCreator
    	configureAutoProxyCreator(parserContext, element);
    
    	List<Element> childElts = DomUtils.getChildElements(element);
    	for (Element elt: childElts) {
    		String localName = parserContext.getDelegate().getLocalName(elt);
    		// 处理pointcut
    		if (POINTCUT.equals(localName)) {
    			parsePointcut(elt, parserContext);
    		}
    		// 处理advisor
    		else if (ADVISOR.equals(localName)) {
    			parseAdvisor(elt, parserContext);
    		}
    		// 处理aspect
    		else if (ASPECT.equals(localName)) {
    			parseAspect(elt, parserContext);
    		}
    	}
    
    	parserContext.popAndRegisterContainingComponent();
    	return null;
    }
    
  • 核心逻辑为:首先创建一个BeanPostProcessor接口的实现类AspectJAwareAdvisorAutoProxyCreator的对象注册到spring中,具体为BeanPostProcessor的子接口InstantiationAwareBeanPostProcessor接口的实现类。

  • AspectJAwareAdvisorAutoProxyCreator的作用是在spring创建每个bean对象实例时,都检查一下是否在pointcut的拦截范围内,如果存在则需要结合advisor获取辅助方法为该bean对象创建对应的代理对象来注册到spring的IOC容器,即实际注册到spring的IOC容器的不是bean对象自身而是该代理对象,AspectJAwareAdvisorAutoProxyCreator的核心方法为postProcessBeforeInstantiation:

    // 在bean对象实例的创建过程中,在创建bean对象实例之前,先调用这个方法,看是否需要创建一个AOP代理对象直接返回
    @Override
    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {
    	Object cacheKey = getCacheKey(beanClass, beanName);
    	
    	// 返回null,则表示不是AOP的目标对象,不需要创建代理对象
    	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;
    		}
    	}
    
    	// Create proxy here if we have a custom TargetSource.
    	// Suppresses unnecessary default instantiation of the target bean:
    	// The TargetSource will handle target instances in a custom fashion.
    	TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
    	if (targetSource != null) {
    		if (StringUtils.hasLength(beanName)) {
    			this.targetSourcedBeans.add(beanName);
    		}
    
    		// specificInterceptors类型为Advisor[],是当前bean需要的辅助功能列表
    		// 因为Advisor集成了pointcut和advice,故可以知道当前bean是否在pointcut拦截范围内,
    		// 如果在获取配置对应的advice列表,该列表作为代理对象的interceptor方法拦截器
    		// getAdvicesAndAdvisorsForBean由子类实现
    		Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
    
    		// 基于以上辅助功能列表,创建该bean对应的代理对象proxy
    		Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);
    		this.proxyTypes.put(cacheKey, proxy.getClass());
    
    		// 只有创建了proxy,才不返回null
    		return proxy;
    	}
    
    	return null;
    }
    
  • 其次是解析aop:config内部的aop:pointcut,aop:advisor,aop:aspect子标签,生成的advisors注册到spring的IOC容器中。advisors是在上面AspectJAwareAdvisorAutoProxyCreator的postProcessBeforeInstantiation方法中创建目标bean对象的代理对象时使用。

aop:aspect-autoproxy标签解析和@AspectJ注解处理

  • aop:aspect-autoproxy标签解析器为AspectJAutoProxyBeanDefinitionParser,AspectJAutoProxyBeanDefinitionParser也是通过注册一个BeanPostProcess接口的实现类AnnotationAwareAspectJAutoProxyCreator到spring容器。其中AnnotationAwareAspectJAutoProxyCreator继承于AspectJAwareAdvisorAutoProxyCreator,由上面分析可知AspectJAwareAdvisorAutoProxyCreator是aop:config的解析器。

  • 由于继承于AspectJAwareAdvisorAutoProxyCreator,所以也是在postProcessBeforeInstantiation方法中判断是否需要为给定的bean对象创建对应的代理对象注册到spring的IOC容器。

  • 由上面的分析可知,基于aop:config配置的方式,由于aop:pointcut, aop:advisor, aop:aspect都已在applicationContext.xml配置文件中配置好了,所以直接是在parser,即ConfigBeanDefinitionParser中解析生成对应的advisors。

  • 而基于@AspectJ注解方式配置pointcut, advisor时,是使用懒加载的方式,即在spring的IOC容器创建bean对象时,通过AnnotationAwareAspectJAutoProxyCreator来获取@AspectJ注解的类并获取其内部的poitcut,advice等配置,即PointCut,@Before,@After等注解的处理,创建对应的advisors集合并缓存起来实现只懒加载一次,之后这些advisors可以直接使用。具体在findCandidateAdvisors方法定义,如下,然后在创建代理对象时使用。

    protected List<Advisor> findCandidateAdvisors() {
    	// Add all the Spring advisors found according to superclass rules.
    	List<Advisor> advisors = super.findCandidateAdvisors();
    	// Build Advisors for all AspectJ aspects in the bean factory.
    	if (this.aspectJAdvisorsBuilder != null) {
    	
    	    // 检查@Aspect注解的类并解析其内部使用了@Around, @Before,
    	    // @After, @AfterReturning, @AfterThrowing注解的方法作为advisors
    		advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
    	}
    	return advisors;
    }
    
  • this.aspectJAdvisorsBuilder.buildAspectJAdvisors()的方法核心实现:从spring的IOC容器中获取使用了@Aspect注解的bean,然后解析该bean对象内部使用了@Around, @Before, @After, @AfterReturning, @AfterThrowing注解的方法并生成对应的advisors。

    // 遍历spring的IOC容器的所有bean对象,如使用了@Component注解的类就在其中
    for (String beanName : beanNames) {
    
    	...
    
    	// 检查给定的bean对象所在类是否使用了@Aspect注解
    	if (this.advisorFactory.isAspect(beanType)) {
    		aspectNames.add(beanName);
    		AspectMetadata amd = new AspectMetadata(beanType, beanName);
    		if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {
    			MetadataAwareAspectInstanceFactory factory =
    					new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
    
    			// 获取所有使用了@Around, @Before, @After, @AfterReturning, @AfterThrowing注解的方法
    			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 {
    		
    			...
    			
    			MetadataAwareAspectInstanceFactory factory =
    					new PrototypeAspectInstanceFactory(this.beanFactory, beanName);
    			this.aspectFactoryCache.put(beanName, factory);
    
    			// 获取所有使用了@Around, @Before, @After, @AfterReturning, @AfterThrowing注解的方法
    			advisors.addAll(this.advisorFactory.getAdvisors(factory));
    		}
    	}
    }
    

猜你喜欢

转载自blog.csdn.net/u010013573/article/details/88324682
今日推荐