mini-spring-course

18|拦截器 :如何在方法前后进行拦截?

你好,我是郭屹,今天我们继续手写MiniSpring。

前面,我们用JDK动态代理技术实现了AOP,并且进行了解耦,采用IoC容器来管理代理对象,实现了非侵入式编程。我们现在能在不影响业务代码的前提下,进行逻辑的增强工作,比如打印日志、事务处理、统计接口耗时等等,将这些例行性逻辑作为一种增强放在代理中,运行时动态插入(编织)进去。

有了这个雏形,我们自然就会进一步考虑,在这个代理结构的基础上,将动态添加逻辑这件事情做得更加结构化一点,而不是全部简单地堆在invoke()方法里。

引入三个概念

我们先来看看invoke()这个方法的代码在结构方面有什么问题。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
	if (method.getName().equals("doAction")) {
		 System.out.println("-----before call real object, dynamic proxy........");
		 return method.invoke(target, args);
	}
	return null;
}

我们看到,在实际调用某个方法的时候,是用的反射直接调用method,对应在代码里也就是 method.invoke(target,args); 这一句。而增强的例行性代码是直接写在method.invoke()这个方法前面的,也就是上面代码里的 System.out.println())。这么做当然没有错,不过扩展性不好。这里我们还是使用那个老办法, 不同的功能由不同的部件来做,所以这个增强逻辑我们可以考虑抽取出一个专门的部件来做,实际业务方法的调用也可以包装一下。

所以这节课,我们引入以下几个概念。

通过这几个概念,我们就可以把例行性逻辑单独剥离出来了。现在我们要做一个切面,只需要实现某个Interceptor就可以了。

对应地,我们定义一下Advice、Interceptor、MethodInterceptor这几个接口。

package com.minis.aop;
public interface Advice {
}

package com.minis.aop;
public interface Interceptor extends Advice{
}

package com.minis.aop;
public interface MethodInterceptor extends Interceptor{
    Object invoke(MethodInvocation invocation) throws Throwable;
}

MethodInterceptor就是方法上的拦截器,对外就是一个invoke()方法。拦截器不仅仅会增强逻辑,它内部也会调用业务逻辑方法。因此,对外部程序而言,只需要使用这个MethodInterceptor就可以了。

它需要传入一个MethodInvocation,然后调用method invocation的proceed()方法,MethodInvocation实际上就是以前通过反射方法调用业务逻辑的那一段代码的包装。。

public interface MethodInvocation {
   Method getMethod();
   Object[] getArguments();
   Object getThis();
   Object proceed() throws Throwable;
}

我们再来看一下应用程序员的工作,为了插入切面,需要在invoke()中实现自己的业务增强代码。

public class TracingInterceptor implements MethodInterceptor {
	public Object invoke(MethodInvocation i) throws Throwable {
		System.out.println("method "+i.getMethod()+" is called on "+
	                        i.getThis()+" with args "+i.getArguments());
		Object ret=i.proceed();
		System.out.println("method "+i.getMethod()+" returns "+ret);
		return ret;
   }
}

中间的i.proceed()才是真正的目标对象的方法调用。

public Object proceed() throws Throwable {
	return this.method.invoke(this.target, this.arguments);
}

改造代理类

有了上面准备好的这些部件,我们在动态代理中如何使用它们呢?这里我们再引入一个Advisor接口。

	public interface Advisor {
		MethodInterceptor getMethodInterceptor();
		void setMethodInterceptor(MethodInterceptor methodInterceptor);
	}

在代理类ProxyFactoryBean里增加Advisor属性和拦截器。

    private String interceptorName;
    private Advisor advisor;

这样,我们的代理类里就有跟拦截器关联的点了。

接下来,为了在目标对象调用前进行拦截,我们就需要调整这个ProxyFactoryBean,并设置其Advisor属性,同时定义这个initializeAdvisor方法来进行关联。

package com.minis.aop;
public class ProxyFactoryBean implements FactoryBean<Object> {
    private BeanFactory beanFactory;
    private String interceptorName;
    private Advisor advisor;

    private synchronized void initializeAdvisor() {
        Object advice = null;
        MethodInterceptor mi = null;
        try {
            advice = (MethodInterceptor) this.beanFactory.getBean(this.interceptorName);
        } catch (BeansException e) {
            e.printStackTrace();
        }
        advisor = new DefaultAdvisor();
        advisor.setMethodInterceptor((MethodInterceptor)advice);
    }
}

通过ProxyFactoryBean代码实现可以看出,里面新增了initializeAdvisor处理,将应用程序自定义的拦截器获取到Advisor里。并且,可以在IoC容器中配置这个Interceptor名字。

在initializeAdvisor里,我们把Advisor初始化工作交给了DefaultAdvisor。

package com.minis.aop;
public class DefaultAdvisor implements Advisor{
    private MethodInterceptor methodInterceptor;
    public DefaultAdvisor() {
    }
    public void setMethodInterceptor(MethodInterceptor methodInterceptor) {
        this.methodInterceptor = methodInterceptor;
    }
    public MethodInterceptor getMethodInterceptor() {
        return this.methodInterceptor;
    }
}

随后,我们修改AopProxyFactory中createAopProxy接口的方法签名,新增Advisor参数。

package com.minis.aop;
public interface AopProxyFactory {
    AopProxy createAopProxy(Object target, Advisor advisor);
}

修改接口后,我们需要相应地修改其实现方法。在ProxyFactoryBean中,唯一的实现方法就是createAopProxy()。

protected AopProxy createAopProxy() {
    return getAopProxyFactory().createAopProxy(target);
}

在这个方法中,我们对前面引入的Advisor进行了赋值。修改之后,代码变成了这样。

protected AopProxy createAopProxy() {
    return getAopProxyFactory().createAopProxy(targetthis.advisor);
}

默认实现是DefaultAopProxyFactory与JdkDynamicAopProxy,这里要一并修改。

package com.minis.aop;
public class DefaultAopProxyFactory implements AopProxyFactory{
    @Override
    public AopProxy createAopProxy(Object target, Advisor advisor) {
        return new JdkDynamicAopProxy(target, advisor);
    }
}

package com.minis.aop;
public class JdkDynamicAopProxy implements AopProxy, InvocationHandler {
    Object target;
    Advisor advisor;
    public JdkDynamicAopProxy(Object target, Advisor advisor) {
        this.target = target;
        this.advisor = advisor;
    }
    @Override
    public Object getProxy() {
        Object obj = Proxy.newProxyInstance(JdkDynamicAopProxy.class.getClassLoader(), target.getClass().getInterfaces(), this);
        return obj;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().equals("doAction")) {
            Class<?> targetClass = (target != null ? target.getClass() : null);
            MethodInterceptor interceptor = this.advisor.getMethodInterceptor();
            MethodInvocation invocation =
                    new ReflectiveMethodInvocation(proxy, target, method, args, targetClass);
            return interceptor.invoke(invocation);
        }
        return null;
    }
}

在JdkDynamicAopProxy里我们发现,invoke方法和之前相比有了不小的变化,在调用某个方法的时候,不再是直接用反射调用方法了,而是先拿到Advisor里面的Interceptor,然后把正常的method调用包装成ReflectiveMethodInvocation,最后调用interceptor.invoke(invocation),对需要调用的方法进行了增强处理。

你把这一段和之前的invoke()进行比对,可以看出,通过Interceptor这个概念,我们就把增强逻辑单独剥离出来了。

你可以看一下实际的ReflectiveMethodInvocation类,其实就是对反射调用方法进行了一次包装。

package com.minis.aop;
public class ReflectiveMethodInvocation implements MethodInvocation{
    protected final Object proxy;
    protected final Object target;
    protected final Method method;
    protected Object[] arguments;
    private Class<?> targetClass;
    protected ReflectiveMethodInvocation(
            Object proxy,  Object target, Method method,  Object[] arguments,
            Class<?> targetClass) {
        this.proxy = proxy;
        this.target = target;
        this.targetClass = targetClass;
        this.method = method;
        this.arguments = arguments;
    }

    //省略getter/setter

    public Object proceed() throws Throwable {
        return this.method.invoke(this.target, this.arguments);
    }
}

测试

我们现在可以来编写一下测试代码,定义TracingInterceptor类模拟业务拦截代码。

package com.test.service;
import com.minis.aop.MethodInterceptor;
import com.minis.aop.MethodInvocation;
public class TracingInterceptor implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation i) throws Throwable {
        System.out.println("method "+i.getMethod()+" is called on "+
                i.getThis()+" with args "+i.getArguments());
        Object ret=i.proceed();
        System.out.println("method "+i.getMethod()+" returns "+ret);
        return ret;
    }
}

applicationContext.xml配置文件:

   <bean id="myInterceptor" class="com.test.service.TracingInterceptor" />
   <bean id="realaction" class="com.test.service.Action1" />
   <bean id="action" class="com.minis.aop.ProxyFactoryBean" >
      <property type="java.lang.Object" name="target" ref="realaction"/>
      <property type="String" name="interceptorName" value="myInterceptor"/>
   </bean>

配置文件里,除了原有的target,我们还增加了一个interceptorName属性,让程序员指定需要启用什么样的增强。

到这里,我们就实现了MethodInterceptor。

在方法前后拦截

我们现在实现的方法拦截,允许程序员自行编写invoke()方法,进行任意操作。但是在许多场景下,调用方式实际上是比较固定的,即在某个方法调用之前或之后,允许程序员插入业务上需要的增强。为了满足这种情况,我们可以提供特定的方法拦截,并允许程序员在这些拦截点之前和之后进行业务增强的操作。这种方式就大大简化了程序员的工作。

所以这里我们新增两种advice:MethodBeforeAdvice和AfterReturningAdvice。根据名字也可以看出来,它们分别对应方法调用前处理和返回后的处理。你可以看一下它们的定义。

package com.minis.aop;
public interface BeforeAdvice extends Advice{
}

package com.minis.aop;
public interface AfterAdvice extends Advice{
}

package com.minis.aop;
import java.lang.reflect.Method;
public interface MethodBeforeAdvice extends BeforeAdvice {
    void before(Method method, Object[] args, Object target) throws Throwable;
}

package com.minis.aop;
import java.lang.reflect.Method;
public interface AfterReturningAdvice extends AfterAdvice{
    void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable;
}

首先我们定义通用接口BeforeAdvice与AfterAdvice,随后定义核心的MethodBeforeAdvice与AfterReturningAdvice接口,它们分别内置了before方法和afterReturning方法。由方法签名可以看出,这两者的区别在于afterReturning它内部传入了返回参数,说明是目标方法执行返回后,再调用该方法,在方法里面可以拿到返回的参数。

有了新的Advice的定义,我们就可以实现新的Interceptor了。你可以看下实现的代码。

package com.minis.aop;
public class MethodBeforeAdviceInterceptor implements MethodInterceptor, BeforeAdvice {
    private final MethodBeforeAdvice advice;
    public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) {
        this.advice = advice;
    }
    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
        this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
        return mi.proceed();
    }
}

在这个Interceptor里,invoke()方法的实现实际上就是限制性地使用advice.before()方法,然后执行目标方法的调用,也意味着这是在方法调用之前插入的逻辑。由于这是针对before这种行为的特定Interceptor,因此上层应用程序员无需自己再进行实现,而是可以直接使用这个Interceptor。

package com.minis.aop;
public class AfterReturningAdviceInterceptor implements MethodInterceptor, AfterAdvice{
    private final AfterReturningAdvice advice;
    public AfterReturningAdviceInterceptor(AfterReturningAdvice advice) {
        this.advice = advice;
    }
    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
        Object retVal = mi.proceed();
        this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());
        return retVal;
    }
}

同样,由AfterReturningAdviceInterceptor类中对invoke方法的实现可以看出,是先调用mi.proceed()方法获取到了返回值retVal,再调用afterReturning方法,实现的是方法调用之后的逻辑增强,这个时序也是固定的。所以注意了,在advice.afterReturing()方法中,是可以拿到目标方法的返回值的。

在拦截器的使用中,存在一个有意思的问题,同时也是一个有着广泛争议的话题:拦截器是否应该影响业务程序的流程?比如,在before()拦截器中加入一个返回标志(true/false),当其为false时,我们就中止业务流程并且不再调用目标方法。

不同的开发者对于这个问题有着不同的主张。一方面,这种机制使得开发者能够根据需要对业务逻辑进行精细控制;另一方面,过度使用这种机制也可能会导致代码难度增加、可维护性降低等问题。因此,在使用拦截器的时候,需要在开发效率和程序可维护性之间做出一个平衡,并根据实际情况做出相应的选择。

现在我们手上有三种Advice类型了,普通的MethodInterceptor,还有特定的MethodBeforeAdviceInterceptor和AfterReturningAdviceInterceptor,自然在ProxyFactoryBean中也要对这个initializeAdvisor方法进行改造,分别支持三种不同类型的Advice。

package com.minis.aop;
public class ProxyFactoryBean implements FactoryBean<Object>, BeanFactoryAware {
    private synchronized void initializeAdvisor() {
        Object advice = null;
        MethodInterceptor mi = null;
        try {
            advice = this.beanFactory.getBean(this.interceptorName);
        } catch (BeansException e) {
            e.printStackTrace();
        }
        if (advice instanceof BeforeAdvice) {
            mi = new MethodBeforeAdviceInterceptor((MethodBeforeAdvice)advice);
        }
        else if (advice instanceof AfterAdvice) {
            mi = new AfterReturningAdviceInterceptor((AfterReturningAdvice)advice);
        }
        else if (advice instanceof MethodInterceptor) {
            mi = (MethodInterceptor)advice;
        }
        advisor = new DefaultAdvisor();
        advisor.setMethodInterceptor(mi);
    }
}

上述实现比较简单,根据不同的Advice类型进行判断,最后统一用MethodInterceptor来封装。

测试

在这一步改造完毕后,我们测试一下,这里我们提供的是比较简单的实现,实际开发过程中你可以跟据自己的需求定制开发。

我们先提供两个Advice。

package com.test.service;
public class MyAfterAdvice implements AfterReturningAdvice {
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("----------my interceptor after method call----------");
    }
}

package com.test.service;
public class MyBeforeAdvice implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("----------my interceptor before method call----------");
    }
}

上述的测试代码都很简单,在此不多赘述。相应的applicationContext.xml这个配置文件里面的内容也要发生变化。

<?xml version="1.0" encoding="UTF-8"?>
<beans>
   <bean id="myBeforeAdvice" class="com.test.service.MyBeforeAdvice" />
   <bean id="realaction" class="com.test.service.Action1" />
   <bean id="action" class="com.minis.aop.ProxyFactoryBean" >
      <property type="java.lang.Object" name="target" ref="realaction"/>
      <property type="String" name="interceptorName" value="myBeforeAdvice"/>
   </bean>
</beans>

将beforeAdvice或者afterAdvice放在配置文件里,除了注册的Bean类名有一些修改,其配置是没有发生任何别的变化的,但经过这样一番改造,我们就能使用上述三类Advice,来对我们的业务代码进行拦截增强处理了。

小结

这节课我们在简单动态代理结构的基础上, 将动态添加的逻辑设计得更加结构化一点,而不是全部简单地堆在invoke()一个方法中。为此,我们提出了Advice的概念,表示这是一个增强操作。然后提出Interceptor拦截器的概念,它实现了真正的增强逻辑并包装了目标方法的调用,应用程序中实际使用的就是这个Interceptor。我们实际实现的是MethodInterceptor,它表示的是调用方法上的拦截器。

我们注意到大部分拦截的行为都是比较固定的,或者在方法调用之前,或者在之后,为了方便处理这些常见的场景,我们进一步分离出了beforeAdvice和afterAdvice。通过这些工作,用户希望插入的例行性逻辑现在都单独抽取成一个部件了,应用程序员只要简单地实现MethodBeforeAdvice和AfterReturningAdvice即可。整个软件结构化很好,完全解耦。

完整源代码参见 https://github.com/YaleGuo/minis

课后题

学完这节课的内容,我也给你留一道思考题。如果我们希望beforeAdvice能在某种情况下阻止目标方法的调用,应该从哪里下手改造?欢迎你在留言区与我交流讨论,也欢迎你把这节课分享给需要的朋友。我们下节课见!