SpringAOP设计原理及应用场景

1.SpringAOP应用示例

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

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

AOP的相关概念

  • 切面(Aspect): 官方的抽象定义为"一个关注点的模块化,这个关注点可能会横切多个对象"."切面"在ApplicationContext<aop:aspect>来配置.
  • 连接点(Joinpoint): 程序执行过程中的某一行为,例如,MemberService.get的调用或者MemberService.delete抛出异常等行为.
  • 通知(Advice): "切面"对于某个"连接点"所产生的动作.其中,一个"切面"可以包含多个"Advice".
  • 切入点(Pointcut): 匹配连接点的断言,在AOP中通知和一个切入点表达式关联.切面中的所有通知所关注的连接点,都由切入点表达式来决定.
  • 目标对象(TargetObject): 被一个或者多个切面所通知的对象.例如,AServcieImplBServiceImpl,当然在实际运行时,Spring AOP采用代理实现,实际AOP操作的是TargetObject的代理对象.
  • AOP代理(AOPProxy): 在Spring AOP中有两种代理方式,JDK动态代理和CGLIB代理.默认情况下,TargetObject实现了接口时,则采用JDK动态代理;反之,采用CGLIB代理.强制使用CGLIB代理需要将<aop:config>proxy-target-class属性设为true.
  • 通知(Advice)类型: 前置通知(Beforeadvice): 在某连接点(JoinPoint)之前执行的通知,但这个通知不能阻止连接点前的执行.ApplicationContext中在<aop:aspect>里面使用<aop:before>元素进行声明.
  • 后置通知(Afteradvice): 当某连接点退出的时候执行的通知(不论是正常返回还是异常退出).ApplicationContext中在<aop:aspect>里面使用<aop:after>元素进行声明.
  • 返回后通知(Afterreturnadvice): 在某连接点正常完成后执行的通知,不包括抛出异常的情况.ApplicationContext中在<aop:aspect>里面使用<after-returning>元素进行声明.
  • 环绕通知(Aroundadvice): 包围一个连接点的通知,类似Web中Servlet规范中的FilterdoFilter方法.可以在方法的调用前后完成自定义的行为,也可以选择不执行.ApplicationContext中在<aop:aspect>里面使用<aop:around>元素进行声明.
  • 抛出异常后通知(Afterthrowingadvice): 在方法抛出异常退出时执行的通知.ApplicationContext中在<aop:aspect>里面使用<aop:after-throwing>元素进行声明.

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

使用Spring AOP可以基于两种方式,一种是比较方便和强大的注解方式,另一种则是中规中矩的xml配置方式.

切入点表达式的配置规则 通常情况下,表达式中使用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-patternname-pattern之外,其他都是可选的. eg: execution(*com.spring.service.*.*(..)) and args(msg,..)表示com.spring.service包下,返回值为任意类型;方法名任意;参数不作限制的所有方法.

可以通过args来绑定参数,这样就可以在通知(Advice)中访问具体参数了.上面的代码args(msg,..)是指将切入点方法上的第一个String类型参数添加到参数名为msg的通知的入参上,这样就可以直接使用该参数啦.

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

2.SpringAOP设计原理及源码分析

Spring中主要的AOP组件 AOP组件

AOP类图

Spring提供了两种方式来生成代理对象:JDKProxyCglib,具体使用哪种方式生成由AopProxyFactory根据AdvisedSupport对象的配置来决定. 默认的策略是如果目标类是接口,则使用JDK动态代理技术,否则使用Cglib来生成代理.

2.1 生成代理对象

下面我们来研究一下Spring如何使用JDK来生成代理对象,具体的生成代码放在JdkDynamicAopProxy这个类中,直接上相关代码:

final class JdkDynamicAopProxy implements AopProxy, InvocationHandler /* 重要 */, Serializable {
    /**
     * 获取代理类要实现的接口, 除了Advised对象中配置的, 还会加上SpringProxy, Advised(opaque=false)
     * 检查上面得到的接口中有没有定义equals或者hashcode的接口,调用Proxy.newProxyInstance创建代理对象
     * @return
     */
    @Override
    public Object getProxy(@Nullable ClassLoader classLoader) {
        if (logger.isTraceEnabled()) {
            logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());
        }
        // 获得代理接口的数组
        Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
        // 判断,是否已经包含 equals 和 hashCode 方法
        findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
        // 创建 Proxy 对象
        return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
    }
}

2.2 切面织入

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

final class JdkDynamicAopProxy implements AopProxy, InvocationHandler /* 重要 */, Serializable {
    @Override
    @Nullable
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        MethodInvocation invocation;
        Object oldProxy = null;
        boolean setProxyContext = false;

        TargetSource targetSource = this.advised.targetSource;
        Object target = null;

        try {
            // 如果被代理的类,未实现 equals 方法,则调用当前类的 equals 方法
            if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
                // The target does not implement the equals(Object) method itself.
                return equals(args[0]);
            // 如果被代理的类,未实现 hashCode 方法,则调用当前类的 hashCode 方法
            } else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
                // The target does not implement the hashCode() method itself.
                return hashCode();
            // 如果是 DecoratingProxy#getDecoratedClass() 方法,通过 advised 这个代理配置,获得被代理的类
            } else if (method.getDeclaringClass() == DecoratingProxy.class) {
                // There is only getDecoratedClass() declared -> dispatch to proxy config.
                return AopProxyUtils.ultimateTargetClass(this.advised);
            // 调用 advised 这个代理配置的方法
            } else 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) {
                // AopContext 的实现原理是,基于 ThreadLocal
                // Make invocation available if necessary.
                // 获得原代理对象
                oldProxy = AopContext.setCurrentProxy(proxy);
                // 标记,设置了代理
                setProxyContext = true;
            }

            // Get as late as possible to minimize the time we "own" the target,
            // in case it comes from a pool.
            // 活动目标对象的类
            target = targetSource.getTarget();
            Class<?> targetClass = (target != null ? target.getClass() : null);

            // Get the interception chain for this method.
            // 获得当前方法拦截的拦截器链
            List<Object> 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.
                Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
                retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
            } else {
                // 创建 ReflectiveMethodInvocation 对象
                // 将拦截器链封装到该对象,以便使其 proceed 方法执行时,进行拦截处理
                // We need to create a method invocation...
                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 != Object.class && 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;
            // 返回类型为基本类型,但是返回 null ,不匹配,所以抛出 AopInvocationException 异常
            } 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);
            }
            // 设置 AopContext 回老代理
            if (setProxyContext) {
                // Restore old proxy.
                AopContext.setCurrentProxy(oldProxy);
            }
        }
    }
}

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

2.3 通知链获取执行

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

/**
* 获得指定方法拦截的拦截器链
*/
public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, @Nullable Class<?> targetClass) {
    MethodCacheKey cacheKey = new MethodCacheKey(method);
    // 优先从缓存中获取
    List<Object> cached = this.methodCache.get(cacheKey);
    // 获取不到,则进行构建
    if (cached == null) {
        cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
                this, method, targetClass);
        this.methodCache.put(cacheKey, cached);
    }
    return cached;
}

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

public class DefaultAdvisorChainFactory implements AdvisorChainFactory, Serializable {

    /**
     * 从提供的配置实例config中获取advisor列表,遍历处理这些advisor.
     * 如果是IntroductionAdvisor,则判断此Advisor能否应用到目标类targetClass上.
     * 如果是PointcutAdvisor,则判断此Advisor能否应用到目标方法Methods上
     * 将满足条件的Advisor通过AdvisorAdaptor转化为Interceptor列表返回
     * 
     * @param config the AOP configuration in the form of an Advised object
     * @param method the proxied method
     * @param targetClass the target class (may be {@code null} to indicate a proxy without
     * target object, in which case the method's declaring class is the next best option)
     * @return
     */
    @Override
    public List<Object> getInterceptorsAndDynamicInterceptionAdvice(
            Advised config, Method method, @Nullable Class<?> targetClass) {

        // This is somewhat tricky... We have to process introductions first,
        // but we need to preserve order in the ultimate list.
        // 获得 DefaultAdvisorAdapterRegistry 对象
        AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();
        // 该类可筛选的 Advisor 集合,需要进过下面逻辑的筛选,再创建成对应的拦截器,添加到 interceptorList 中.
        Advisor[] advisors = config.getAdvisors();
        // 拦截器的结果集合
        List<Object> interceptorList = new ArrayList<>(advisors.length);
        // 真实的类
        Class<?> actualClass = (targetClass != null ? targetClass : method.getDeclaringClass());
        // 是否 advisors 中有 IntroductionAdvisor 对象.
        Boolean hasIntroductions = null;

        // 遍历 Advisor 集合
        for (Advisor advisor : advisors) {
            if (advisor instanceof PointcutAdvisor) {
                // Add it conditionally.
                PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
                // 判断通知器是否匹配实际对象
                if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) {
                    MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();
                    // 判断是否匹配
                    boolean match;
                    if (mm instanceof IntroductionAwareMethodMatcher) {
                        if (hasIntroductions == null) {
                            hasIntroductions = hasMatchingIntroductions(advisors, actualClass);
                        }
                        match = ((IntroductionAwareMethodMatcher) mm).matches(method, actualClass, hasIntroductions);
                    } else {
                        match = mm.matches(method, actualClass);
                    }
                    if (match) {
                        MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
                        // 运行时,封装成对应的 InterceptorAndDynamicMethodMatcher 拦截器对象.TODO 芋艿,暂时没详细调试
                        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(actualClass)) {
                    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. 接下来我们再看下得到的拦截器链是怎么起作用的.

// 如果没有可以应用到此方法的通知(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.
    Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
    retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
} else {
    // 创建 ReflectiveMethodInvocation 对象
    // 将拦截器链封装到该对象,以便使其 proceed 方法执行时,进行拦截处理
    // We need to create a method invocation...
    invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
    // Proceed to the joinpoint through the interceptor chain.
    // 执行拦截器和方法
    retVal = invocation.proceed();
}

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


/**
 * 基于反射的方式,代理方法调用实现类
 *
 * Spring's implementation of the AOP Alliance
 * {@link org.aopalliance.intercept.MethodInvocation} interface,
 * implementing the extended
 * {@link org.springframework.aop.ProxyMethodInvocation} interface.
 *
 * <p>Invokes the target object using reflection. Subclasses can override the
 * {@link #invokeJoinpoint()} method to change this behavior, so this is also
 * a useful base class for more specialized MethodInvocation implementations.
 *
 * <p>It is possible to clone an invocation, to invoke {@link #proceed()}
 * repeatedly (once per clone), using the {@link #invocableClone()} method.
 * It is also possible to attach custom attributes to the invocation,
 * using the {@link #setUserAttribute} / {@link #getUserAttribute} methods.
 *
 * <p><b>NOTE:</b> This class is considered internal and should not be
 * directly accessed. The sole reason for it being public is compatibility
 * with existing framework integrations (e.g. Pitchfork). For any other
 * purposes, use the {@link ProxyMethodInvocation} interface instead.
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 * @author Adrian Colyer
 * @see #invokeJoinpoint
 * @see #proceed
 * @see #invocableClone
 * @see #setUserAttribute
 * @see #getUserAttribute
 */
public class ReflectiveMethodInvocation implements ProxyMethodInvocation, Cloneable {
    /**
     * 执行.
     * 基于递归的方式
     *
     * @return
     * @throws Throwable
     */
    @Override
    @Nullable
    public Object proceed() throws Throwable {
        //    We start with an index of -1 and increment early.
        // 执行完所有拦截器后,执行切面方法
        if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
            return invokeJoinpoint();
        }

        // 获得下一个拦截器
        Object interceptorOrInterceptionAdvice =
                this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
        if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
            // Evaluate dynamic method matcher here: static part will already have
            // been evaluated and found to match.
            // 如果要动态匹配joinPoint
            InterceptorAndDynamicMethodMatcher dm =
                    (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
            Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
            // 动态匹配: 运行时参数是否满足条件
            if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {
                // 匹配,则执行拦截器
                return dm.interceptor.invoke(this);
            } else {
                // 不匹配,略过当前Interceptor,调用下一个Interceptor,递归执行
                // Dynamic matching failed.
                // Skip this interceptor and invoke the next in the chain.
                return proceed();
            }
        } else {
            // It's an interceptor, so we just invoke it: The pointcut will have
            // been evaluated statically before this object was constructed.
            // 将 this 作为参数传递,以保证当前实例中调用链的执行
            // MethodInterceptor 基于不同的切面类型,有不同的实现.
            //      例如 @Before 对应 MethodBeforeAdviceInterceptor
            //      例如 @After  对应 AfterReturningAdviceInterceptor
            return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
        }
    }

    /**
     * 基于反射的方式,调用切点方法
     *
     * Invoke the joinpoint using reflection.
     * Subclasses can override this to use custom invocation.
     * @return the return value of the joinpoint
     * @throws Throwable if invoking the joinpoint resulted in an exception
     */
    @Nullable
    protected Object invokeJoinpoint() throws Throwable {
        return AopUtils.invokeJoinpointUsingReflection(this.target, this.method, this.arguments);
    }
}

2.4 标对象方法的调用

来看看invokeJoinpointUsingReflection(),即上面所说的目标对象方法的调用,其实是通过AopUtils的方法调用,使用反射机制来对目标对象的方法进行的:

public abstract class AopUtils {
    @Nullable
    public static Object invokeJoinpointUsingReflection(@Nullable Object target, Method method, Object[] args)
            throws Throwable {

        // Use reflection to invoke the method.
        // 通过反射机制来获得相应的方法,并调用invoke
        try {
            ReflectionUtils.makeAccessible(method);
            return method.invoke(target, args);
        }
        catch (InvocationTargetException ex) {
            // Invoked method threw a checked exception.
            // We must rethrow it. The client won't see the interceptor.
            throw ex.getTargetException();
        }
        catch (IllegalArgumentException ex) {
            throw new AopInvocationException("AOP configuration seems to be invalid: tried calling method [" +
                    method + "] on target [" + target + "]", ex);
        }
        catch (IllegalAccessException ex) {
            throw new AopInvocationException("Could not access method [" + method + "]", ex);
        }
    }
}

results matching ""

    No results matching ""