2018-05-19

spring源码分析(五)



目录

五、源码分析
--5.6、Spring AOP 设计原理及具体实践
----5.6.1、SpringAOP 应用示例
------AOP 的相关概念
------通知(Advice)类型
------使用 SpringAOP 两种方式
--------表达式中使用”execution“
--------访问当前的连接点
--------通知参数
----5.6.2、SpringAOP 设计原理及源码分析



五、源码分析

5.6、Spring AOP 设计原理及具体实践
5.6.1、SpringAOP 应用示例

AOP 是 OOP 的延续,是 AspectOrientedProgramming 的缩写,意思是面向切面编程。可以通过预编译方式和运行期动态代 理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。 AOP 设计模式孜孜不倦追求的是调用者和被调用者之 间的解耦,AOP 可以说也是这种目标的一种实现。

我们现在做的一些非业务,如:日志、事务、安全等都会写在业务代码中(也即是说,这些非业务类横切于业务类),但这些 代码往往是重复,复制——粘贴式的代码会给程序的维护带来不便,AOP 就实现了把这些业务需求与系统需求分开来做。这 种解决的方式也称代理机制。

AOP 的相关概念

  • 切面(Aspect):官方的抽象定义为“一个关注点的模块化,这个关注点可能会横切多个对象”。“切面”在 ApplicationContext 中来配置。

  • 连接点(Joinpoint):程序执行过程中的某一行为,例如,MemberService.get 的调用或者 MemberService.delete 抛出异常等行为。  通知(Advice) :“切面”对于某个“连接点”所产生的动作。其中,一个“切面”可以包含多个“Advice”。

  • 切入点(Pointcut) :匹配连接点的断言,在 AOP 中通知和一个切入点表达式关联。切面中的所有通知所关注的连 接点,都由切入点表达式来决定。

  • 目标对象(TargetObject) :被一个或者多个切面所通知的对象。例如,AServcieImpl 和 BServiceImpl,当然在实 际运行时,SpringAOP 采用代理实现,实际 AOP 操作的是 TargetObject 的代理对象。

  • AOP 代理(AOPProxy):在 SpringAOP 中有两种代理方式, JDK 动态代理和 CGLIB 代理。默认情况下, TargetObject 实现了接口时,则采用 JDK 动态代理,例如,AServiceImpl;反之,采用 CGLIB 代理,例如,BServiceImpl。强制 使用 CGLIB 代理需要将 的 proxy-target-class 属性设为 true。

通知(Advice)类型:

  • 前置通知(Beforeadvice):在某连接点(JoinPoint)之前执行的通知,但这个通知不能阻止连接点前的执行。 ApplicationContext 中在里面使用元素进行声明。例如,TestAspect 中的 doBefore 方法。

  • 后置通知(Afteradvice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。ApplicationContext 中在里面使用元素进行声明。例如,ServiceAspect 中的 returnAfter 方法,所以 Teser 中调用 UserService.delete 抛出异常时,returnAfter 方法仍然执行。

  • 返回后通知(Afterreturnadvice):在某连接点正常完成后执行的通知,不包括抛出异常的情况。ApplicationContext 中在里面使用元素进行声明。

  • 环绕通知(Aroundadvice):包围一个连接点的通知,类似 Web 中 Servlet 规范中的 Filter 的 doFilter 方法。可以在 方法的调用前后完成自定义的行为,也可以选择不执行。ApplicationContext 中在里面使用 元素进行声明。例如,ServiceAspect 中的 around 方法。

  • 抛出异常后通知(Afterthrowingadvice):在方法抛出异常退出时执行的通知。ApplicationContext 中在 里面使用元素进行声明。例如,ServiceAspect 中的 returnThrow 方法。

注:可以将多个通知应用到一个目标对象上,即可以将多个切面织入到同一目标对象。

使用 SpringAOP 可以基于两种方式:

  • 一种是比较方便和强大的注解方式
  • 另一种则是中规中矩的 xml 配置方式。

注解,使用注解配置 SpringAOP 总体分为两步:

第一步是在 xml 文件中声明激活自动扫描组件功能,同时激活自动代 理功能(来测试 AOP 的注解功能):



    


    

第二步是为 Aspect 切面类添加注解:

//声明这是一个组件
@Component
//声明这是一个切面Bean
@Aspect
public class AnnotaionAspect {
    private final static Logger log = Logger.getLogger(AnnotaionAspect.class);
    //配置切入点,该方法无方法体,主要为方便同类中其他方法使用此处配置的切入点
    @Pointcut("execution(* com.gupaoedu.aop.service..*(..))")
    public void aspect(){ }
    /*
    * 配置前置通知,使用在方法aspect()上注册的切入点
    * 同时接受JoinPoint切入点对象,可以没有该参数
    */
    @Before("aspect()")
    public void before(JoinPoint joinPoint){
        log.info("before " + joinPoint);
    }
    //配置后置通知,使用在方法aspect()上注册的切入点
    @After("aspect()")
    public void after(JoinPoint joinPoint){
        log.info("after " + joinPoint);
    }
    //配置环绕通知,使用在方法aspect()上注册的切入点
    @Around("aspect()")
    public void around(JoinPoint joinPoint){
        long start = System.currentTimeMillis();
        try {
            ((ProceedingJoinPoint) joinPoint).proceed();
            long end = System.currentTimeMillis();
            log.info("around " + joinPoint + "\tUse time : " + (end - start) + " ms!");
        } catch (Throwable e) {
            long end = System.currentTimeMillis();
            log.info("around " + joinPoint + "\tUse time : " + (end - start) + " ms with exception : " + e.getMessage());
        }
    }
    //配置后置返回通知,使用在方法aspect()上注册的切入点
    @AfterReturning("aspect()")
    public void afterReturn(JoinPoint joinPoint){
        log.info("afterReturn " + joinPoint);
    }
    //配置抛出异常后通知,使用在方法aspect()上注册的切入点
    @AfterThrowing(pointcut="aspect()", throwing="ex")
    public void afterThrow(JoinPoint joinPoint, Exception ex){
        log.info("afterThrow " + joinPoint + "\t" + ex.getMessage());
    }
}

测试代码

@ContextConfiguration(locations = {"classpath*:application-context.xml"})
@RunWith(SpringJUnit4ClassRunner.class)
public class AnnotationTester {
    @Autowired MemberService annotationService;
    @Autowired ApplicationContext app;
    @Test
    // @Ignore
    public void test(){
        System.out.println("=====这是一条华丽的分割线======");
        AnnotaionAspect aspect = app.getBean(AnnotaionAspect.class);
        System.out.println(aspect);
        annotationService.save(new Member());
        System.out.println("=====这是一条华丽的分割线======");
        try {
            annotationService.delete(1L);
        } catch (Exception e) {
            //e.printStackTrace();
        }
    }
}

控制台输出如下:

=====这是一条华丽的分割线======
com.gupaoedu.aop.aspect.AnnotaionAspect@6ef714a0
[INFO ] [13:04:46] com.gupaoedu.aop.aspect.AnnotaionAspect - before execution(void
com.gupaoedu.aop.service.MemberService.save(Member))
[INFO ] [13:04:46] com.gupaoedu.aop.aspect.ArgsAspect - beforeArgUser execution(void
com.gupaoedu.aop.service.MemberService.save(Member))
[INFO ] [13:04:46] com.gupaoedu.aop.aspect.AnnotaionAspect - save member method . . . [INFO ] [13:04:46] com.gupaoedu.aop.aspect.AnnotaionAspect - around execution(void
com.gupaoedu.aop.service.MemberService.save(Member)) Use time : 38 ms!
[INFO ] [13:04:46] com.gupaoedu.aop.aspect.AnnotaionAspect - after execution(void
com.gupaoedu.aop.service.MemberService.save(Member))
[INFO ] [13:04:46] com.gupaoedu.aop.aspect.AnnotaionAspect - afterReturn execution(void
com.gupaoedu.aop.service.MemberService.save(Member)) =====这是一条华丽的分割线======
[INFO ] [13:04:46] com.gupaoedu.aop.aspect.AnnotaionAspect - before execution(boolean
com.gupaoedu.aop.service.MemberService.delete(long))
[INFO ] [13:04:46] com.gupaoedu.aop.aspect.ArgsAspect - beforeArgId execution(boolean
com.gupaoedu.aop.service.MemberService.delete(long)) ID:1
[INFO ] [13:04:46] com.gupaoedu.aop.aspect.AnnotaionAspect - delete method . . . [INFO ] [13:04:46] com.gupaoedu.aop.aspect.AnnotaionAspect - around execution(boolean
com.gupaoedu.aop.service.MemberService.delete(long)) Use time : 3 ms with exception : spring aop
ThrowAdvice演示
[INFO ] [13:04:46] com.gupaoedu.aop.aspect.AnnotaionAspect - after execution(boolean
com.gupaoedu.aop.service.MemberService.delete(long))
[INFO ] [13:04:46] com.gupaoedu.aop.aspect.AnnotaionAspect - afterReturn execution(boolean
com.gupaoedu.aop.service.MemberService.delete(long))

可以看到,正如我们预期的那样,虽然我们并没有对 MemberService 类包括其调用方式做任何改变,但是 Spring 仍然拦截到了其中方法的调用,或许这正是 AOP 的魔力所在。

再简单说一下 xml 配置方式,其实也一样简单:


    
    

    

    
    
        
        
            
            
            
            
            
            
            
        
    

个人觉得不如注解灵活和强大,你可以不同意这个观点,但是不知道如下的代码会不会让你的想法有所改善:

//配置切入点,该方法无方法体,主要为方便同类中其他方法使用此处配置的切入点
@Pointcut("execution(* com.gupaoedu.aop.service..*(..))")
public void aspect(){ }

//配置前置通知,拦截返回值为cn.ysh.studio.spring.mvc.bean.User的方法
@Before("execution(com.gupaoedu.model.Member com.gupaoedu.aop.service..*(..))")
public void beforeReturnUser(JoinPoint joinPoint){
    log.info("beforeReturnUser " + joinPoint);
}

//配置前置通知,拦截参数为cn.ysh.studio.spring.mvc.bean.User的方法
@Before("execution(* com.gupaoedu.aop.service..*(com.gupaoedu.model.Member))")
public void beforeArgUser(JoinPoint joinPoint){
    log.info("beforeArgUser " + joinPoint);
}

//配置前置通知,拦截含有long类型参数的方法,并将参数值注入到当前方法的形参id中
@Before("aspect()&&args(id)")
public void beforeArgId(JoinPoint joinPoint, long id){
    log.info("beforeArgId " + joinPoint + "\tID:" + id);
}

以下是 MemberService 的代码:

@Service
public class MemberService {
    private final static Logger log = Logger.getLogger(AnnotaionAspect.class);
    public Member get(long id){
        log.info("getMemberById method . . .");
        return new Member();
    }
    public Member get(){
        log.info("getMember method . . .");
        return new Member();
    }
    public void save(Member member){
        log.info("save member method . . .");
    }
    public boolean delete(long id) throws Exception{
        log.info("delete method . . .");
        throw new Exception("spring aop ThrowAdvice演示");
    }
}

应该说学习 Spring AOP 有两个难点,第一点在于理解 AOP 的理念和相关概念,第二点在于灵活掌握和使用切入点表达式。

概念的理解通常不在一朝一夕,慢慢浸泡的时间长了,自然就明白了,下面我们简单地介绍一下切入点表达式的配置规则吧。

通常情况下,表达式中使用”execution“就可以满足大部分的要求。表达式格式如下:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?
  • modifiers-pattern:方法的操作权限
  • ret-type-pattern:返回值
  • declaring-type-pattern:方法所在的包
  • name-pattern:方法名
  • parm-pattern:参数名
  • throws-pattern:异常

其中,除 ret-type-pattern 和 name-pattern 之外,其他都是可选的。上例中,execution(com.spring.service..*(..))表示 com.spring.service 包下,返回值为任意类型;方法名任意;参数不作限制的所有方法。

通知参数

可以通过 args 来绑定参数,这样就可以在通知(Advice)中访问具体参数了。例如,配置如下:


    
        
        
    

上面的代码 args(msg,..)是指将切入点方法上的第一个 String 类型参数添加到参数名为 msg 的通知的入参上,这样就可以直接 使用该参数啦。

访问当前的连接点

在上面的 Aspect 切面 Bean 中已经看到了,每个通知方法第一个参数都是 JoinPoint。其实,在 Spring 中,任何通知(Advice) 方法都可以将第一个参数定义为 org.aspectj.lang.JoinPoint 类型用以接受当前连接点对象。JoinPoint 接口提供了一系列有用 的方法, 比如 getArgs() (返回方法参数)、getThis() (返回代理对象)、getTarget() (返回目标)、getSignature() (返回 正在被通知的方法相关信息)和 toString() (打印出正在被通知的方法的有用信息)。

5.6.2、SpringAOP 设计原理及源码分析

开始之前先上图,看看 Spring 中主要的 AOP 组件


2018-05-19_第1张图片
AOP组件.PNG

Spring 提供了两种方式来生成代理对象: JDKProxy 和 Cglib,具体使用哪种方式生成由 AopProxyFactory 根据 AdvisedSupport 对象的配置来决定。默认的策略是如果目标类是接口,则使用 JDK 动态代理技术,否则使用 Cglib 来生成代 理。下面我们来研究一下 Spring 如何使用 JDK 来生成代理对象,具体的生成代码放在 JdkDynamicAopProxy 这个类中,直接 上相关代码:

/**
* 
    *
  1. 获取代理类要实现的接口,除了 Advised 对象中配置的,还会加上 SpringProxy, Advised(opaque=false) *
  2. 检查上面得到的接口中有没有定义 equals 或者 hashcode 的接口 *
  3. 调用 Proxy.newProxyInstance 创建代理对象 *
*/ public Object getProxy(ClassLoader classLoader) { if (logger.isDebugEnabled()) { logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource()); } Class[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised); findDefinedEqualsAndHashCodeMethods(proxiedInterfaces); return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this); }

那这个其实很明了,注释上我也已经写清楚了,不再赘述。

下面的问题是,代理对象生成了,那切面是如何织入的

我们知道 InvocationHandler 是 JDK 动态代理的核心,生成的代理对象的方法调用都会委托到 InvocationHandler.invoke() 方法。而通过 JdkDynamicAopProxy 的签名我们可以看到这个类其实也实现了 InvocationHandler,下面我们就通过分析这个 类中实现的 invoke()方法来具体看下 Spring AOP 是如何织入切面的。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    MethodInvocation invocation;
    Object oldProxy = null;
    boolean setProxyContext = false;
    
    TargetSource targetSource = this.advised.targetSource;
    Class targetClass = null;
    Object target = null;
    
    try {
        //eqauls()方法,具目标对象未实现此方法
        if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
            // The target does not implement the equals(Object) method itself.
            return equals(args[0]);
        }
        //hashCode()方法,具目标对象未实现此方法
        if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
            // The target does not implement the hashCode() method itself.
            return hashCode();
        }
        //Advised 接口或者其父接口中定义的方法,直接反射调用,不应用通知
        if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
            method.getDeclaringClass().isAssignableFrom(Advised.class)) {
            // Service invocations on ProxyConfig with the proxy config...
            return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
        }
        Object retVal;
        if (this.advised.exposeProxy) {
            // Make invocation available if necessary.
            oldProxy = AopContext.setCurrentProxy(proxy);
            setProxyContext = true;
        }
        // May be null. Get as late as possible to minimize the time we "own" the target,
        // in case it comes from a pool.
        //获得目标对象的类
        target = targetSource.getTarget();
        if (target != null) {
            targetClass = target.getClass();
        }
        // Get the interception chain for this method.
        //获取可以应用到此方法上的 Interceptor 列表
        List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method,
        targetClass);
        
        // Check whether we have any advice. If we don t, we can fallback on direct
        // reflective invocation of the target, and avoid creating a MethodInvocation.
        //如果没有可以应用到此方法的通知(Interceptor),此直接反射调用 method.invoke(target, args)
        
        if (chain.isEmpty()) {
            // We can skip creating a MethodInvocation: just invoke the target directly
            // Note that the final invoker must be an InvokerInterceptor so we know it does
            // nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
            retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);
        }else {
            // We need to create a method invocation...
            //创建 MethodInvocation
            invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass,
            chain);
            // Proceed to the joinpoint through the interceptor chain.
            retVal = invocation.proceed();
        }
        // Massage return value if necessary.
        Class returnType = method.getReturnType();
        if (retVal != null && retVal == target && returnType.isInstance(proxy) &&
        !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
            // Special case: it returned "this" and the return type of the method
            // is type-compatible. Note that we can t help if the target sets
            // a reference to itself in another returned object.
            retVal = proxy;
        } else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
            throw new AopInvocationException("Null return value from advice does not match
            primitive return type for: " + method);
        }
        return retVal;
    }finally {
        if (target != null && !targetSource.isStatic()) {
            // Must have come from TargetSource.
            targetSource.releaseTarget(target);
        }
        if (setProxyContext) {
            // Restore old proxy.
            AopContext.setCurrentProxy(oldProxy);
        }
    }
}

主流程可以简述为:获取可以应用到此方法上的通知链(Interceptor Chain),如果有,则应用通知,并执行 joinpoint; 如 果没有,则直接反射执行 joinpoint。而这里的关键是通知链是如何获取的以及它又是如何执行的,下面逐一分析下。

首先,从上面的代码可以看到,通知链是通过Advised.getInterceptorsAndDynamicInterceptionAdvice()这个方法来获取 的,我们来看下这个方法的实现:

public List getInterceptorsAndDynamicInterceptionAdvice(Method method, Class targetClass){
    MethodCacheKey cacheKey = new MethodCacheKey(method);
    List cached = this.methodCache.get(cacheKey);
    if (cached == null) {
        cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
        this, method, targetClass);
        this.methodCache.put(cacheKey, cached);
    }
    return cached;
}

可以看到实际的获取工作其实是由 AdvisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice()这个方法来 完成的,获取到的结果会被缓存。

下面来分析下这个方法的实现:

/**
* 从提供的配置实例 config 中获取 advisor 列表,遍历处理这些 advisor.如果是 IntroductionAdvisor,
* 则判断此 Advisor 能否应用到目标类 targetClass 上.如果是 PointcutAdvisor,则判断
* 此 Advisor 能否应用到目标方法 method 上.将满足条件的 Advisor 通过 AdvisorAdaptor 转化成 Interceptor
列表返回.
*/
public List getInterceptorsAndDynamicInterceptionAdvice(
Advised config, Method method, Class targetClass) {
    // This is somewhat tricky... we have to process introductions first,
    // but we need to preserve order in the ultimate list.
    List interceptorList = new ArrayList(config.getAdvisors().length);
    //查看是否包含 IntroductionAdvisor
    boolean hasIntroductions = hasMatchingIntroductions(config, targetClass);
    //这里实际上注册一系列 AdvisorAdapter,用于将 Advisor 转化成 MethodInterceptor
    AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();
    for (Advisor advisor : config.getAdvisors()) {
        if (advisor instanceof PointcutAdvisor) {
            // Add it conditionally.
            PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
            if (config.isPreFiltered() ||
            pointcutAdvisor.getPointcut().getClassFilter().matches(targetClass)) {
                //这个地方这两个方法的位置可以互换下
                //将 Advisor 转化成 Interceptor
                MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
                //检查当前 advisor 的 pointcut 是否可以匹配当前方法
                MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();
                if (MethodMatchers.matches(mm, method, targetClass, hasIntroductions)) {
                    if (mm.isRuntime()) {
                        // Creating a new object instance in the getInterceptors() method
                        // isn't a problem as we normally cache created chains.
                        for (MethodInterceptor interceptor : interceptors) {
                            interceptorList.add(new
                            InterceptorAndDynamicMethodMatcher(interceptor, mm));
                        }
                    }else {
                        interceptorList.addAll(Arrays.asList(interceptors));
                    }
                }
            }
        }else if (advisor instanceof IntroductionAdvisor) {
            IntroductionAdvisor ia = (IntroductionAdvisor) advisor;
            if (config.isPreFiltered() || ia.getClassFilter().matches(targetClass)) {
            Interceptor[] interceptors = registry.getInterceptors(advisor);
                interceptorList.addAll(Arrays.asList(interceptors));
            }
        }else {
            Interceptor[] interceptors = registry.getInterceptors(advisor);
            interceptorList.addAll(Arrays.asList(interceptors));
        }
    }
    return interceptorList;
}

这个方法执行完成后,Advised 中配置能够应用到连接点或者目标类的 Advisor 全部被转化成了 MethodInterceptor. 接下来我们再看下得到的拦截器链是怎么起作用的。

......
// Check whether we have any advice. If we don't, we can fallback on direct
// reflective invocation of the target, and avoid creating a MethodInvocation.
//如果没有可以应用到此方法的通知(Interceptor),此直接反射调用 method.invoke(target, args)
if (chain.isEmpty()) {
    // We can skip creating a MethodInvocation: just invoke the target directly
    // Note that the final invoker must be an InvokerInterceptor so we know it does
    // nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
    retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);
}
else {
    // We need to create a method invocation...
    //创建 MethodInvocation
    invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
    // Proceed to the joinpoint through the interceptor chain.
    retVal = invocation.proceed();
}
......

从这段代码可以看出,如果得到的拦截器链为空,则直接反射调用目标方法,否则创建 MethodInvocation,调用其 proceed方法,触发拦截器链的执行,来看下具体代码

public Object proceed() throws Throwable {
    // We start with an index of -1 and increment early.
    //如果 Interceptor 执行完了,则执行 joinPoint
    if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
        return invokeJoinpoint();
    }
    Object interceptorOrInterceptionAdvice =
    this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
    //如果要动态匹配 joinPoint
    if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
        // Evaluate dynamic method matcher here: static part will already have
        // been evaluated and found to match.
        InterceptorAndDynamicMethodMatcher dm =
        (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
        //动态匹配:运行时参数是否满足匹配条件
        if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
            //执行当前 Intercetpor
            return dm.interceptor.invoke(this);
        }else {
            // Dynamic matching failed.
            // Skip this interceptor and invoke the next in the chain.
            //动态匹配失败时,略过当前 Intercetpor,调用下一个 Interceptor
            return proceed();
        }
    }else {
        // It's an interceptor, so we just invoke it: The pointcut will have
        // been evaluated statically before this object was constructed.
        //执行当前 Intercetpor
        return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
    }
}

你可能感兴趣的:(2018-05-19)