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

你好,我是郭屹,今天我们继续探讨手写MiniSpring的旅程。 在之前的章节中,我们使用JDK动态代理技术实现了AOP(面向切面编程),并结合了IoC容器来管理这些代理对象。这让我们能够以非侵入的方式,在业务代码运行时动态地添加诸如日志记录、事务管理或性能监控等横切关注点的功能。 然而,随着功能的增加,我们在invoke()方法中直接编写的逻辑会变得越来越臃肿和难以维护。为了改善这种情况,我们需要引入一种更为结构化的方式来组织这些增强逻辑。接下来,我们将引入三个关键概念来帮助我们实现这一点。

引入的概念

1. 拦截器(Interceptor)拦截器是在调用目标方法之前或之后执行特定任务的对象。它可以用来插入预处理和后处理逻辑,比如开启/关闭数据库连接、事务提交/回滚等。

2. 拦截器链(Interceptor Chain)拦截器链是一种模式,它允许多个拦截器按照一定的顺序依次执行。每个拦截器都可以决定是否将请求传递给下一个拦截器或是直接返回结果。

3. 拦截器适配器(Interceptor Adapter)拦截器适配器用于简化拦截器的实现。它可以提供默认的行为,而具体的拦截器只需要覆盖它们关心的方法。

通过这三个概念,我们可以更加清晰地分离关注点,并且使得invoke()方法变得更加简洁明了。接下来的工作将是设计和实现相应的接口与类,以及考虑如何配置和管理拦截器链。 通过这种方式,我们的AOP机制不仅能够更有效地支持各种横切关注点,而且还能保持代码的整洁性和可扩展性。 aaaaaaa

1
2
3
4
5
6
7
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.invoke(target, args);前添加增强代码(如System.out.println())会导致代码的扩展性变差。因此,我们引入了以下几个概念来改善这一点:

  • Advice:表示这是一个增强操作。

  • Interceptor:拦截器,实现真正的增强逻辑。

  • MethodInterceptor:方法上的拦截器,实现在某个方法上的增强。 通过这些概念,我们可以将例行性逻辑单独剥离出来,使得切面逻辑更加清晰和易于管理。

接口定义

为了实现上述概念,我们需要定义以下几个接口:

  1. Advice:定义增强操作的接口。

  2. Interceptor:定义拦截器的接口,实现增强逻辑。

  3. MethodInterceptor:定义方法上的拦截器接口,用于在特定方法上应用增强。

实现切面

现在,要实现一个切面,我们只需要实现相应的Interceptor接口即可。这样,我们就可以根据需要灵活地添加或修改增强逻辑,而不需要修改业务方法的实现代码。

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

MethodInterceptor 是一个方法上的拦截器,对外暴露的接口是 invoke() 方法。这个拦截器不仅会增强逻辑,还会在内部调用业务逻辑方法。因此,对于外部程序来说,只需要使用这个 MethodInterceptor 就可以了。 它需要传入一个 MethodInvocation 对象,然后通过调用 method invocation 的 proceed() 方法来执行实际的业务逻辑。MethodInvocation 实际上是对以前通过反射调用业务逻辑的那一段代码的封装。

1
2
3
4
5
6
public interface MethodInvocation {
   Method getMethod();
   Object[] getArguments();
   Object getThis();
   Object proceed() throws Throwable;
}

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

1
2
3
4
5
6
7
8
9
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()才是真正的目标对象的方法调用。

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

改造代理类

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

1
2
3
4
	public interface Advisor {
		MethodInterceptor getMethodInterceptor();
		void setMethodInterceptor(MethodInterceptor methodInterceptor);
	}

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

1
2
    private String interceptorName;
    private Advisor advisor;

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

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
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。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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参数。

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

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

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

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

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

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

1
2
3
4
5
6
7
package com.minis.aop;
public class DefaultAopProxyFactory implements AopProxyFactory{
    @Override
    public AopProxy createAopProxy(Object target, Advisor advisor) {
        return new JdkDynamicAopProxy(target, advisor);
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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;
    }
}

JdkDynamicAopProxyinvoke方法中,我们可以观察到与之前版本相比有了一些显著的变化。现在,在调用某个方法时,不再直接使用反射来调用目标方法。而是首先获取Advisor中的Interceptor,然后将常规的方法调用封装成一个ReflectiveMethodInvocation对象。最终,通过调用interceptor.invoke(invocation),对原方法进行了增强处理。

这种变化使得增强逻辑能够被单独剥离出来,通过Interceptor这个概念实现了更清晰的职责划分。ReflectiveMethodInvocation类实际上是对反射方法调用的一层包装,它允许我们在实际方法执行前后插入额外的行为或逻辑,从而实现AOP(面向切面编程)的功能。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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类模拟业务拦截代码。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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配置文件:

1
2
3
4
5
6
   <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。根据名字也可以看出来,它们分别对应方法调用前处理和返回后的处理。你可以看一下它们的定义。

1
2
3
package com.minis.aop;
public interface BeforeAdvice extends Advice{
}
1
2
3
package com.minis.aop;
public interface AfterAdvice extends Advice{
}
1
2
3
4
5
package com.minis.aop;
import java.lang.reflect.Method;
public interface MethodBeforeAdvice extends BeforeAdvice {
    void before(Method method, Object[] args, Object target) throws Throwable;
}
1
2
3
4
5
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了。你可以看下实现的代码。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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()方法来插入逻辑,然后继续执行目标方法。这表明,在实际的目标方法被调用之前,相关的拦截逻辑已经被处理。

由于这个Interceptor专门设计用于处理before类型的行为,它为上层的应用程序员提供了一个现成的解决方案,因此他们不需要自行编写额外的代码来实现相同的功能,可以直接利用这个Interceptor来满足需求。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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.afterReturning()方法中可以访问到目标方法的返回值。 拦截器的使用中存在一个有争议的话题:拦截器是否应该影响业务程序的流程。例如,在before()拦截器中加入一个返回标志(true/false),当其为false时,中止业务流程并且不再调用目标方法。不同的开发者对此有不同的看法:一方面,这种机制允许开发者根据需要对业务逻辑进行精细控制;另一方面,过度使用可能导致代码复杂度增加、可维护性降低等问题。因此,在使用拦截器时需要在开发效率和程序可维护性之间做出平衡,并根据实际情况做出选择。 目前我们有三种Advice类型:普通的MethodInterceptor,特定的MethodBeforeAdviceInterceptor和AfterReturningAdviceInterceptor。相应地,ProxyFactoryBean中的initializeAdvisor方法也需要改造,以支持这三种不同类型的Advice。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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);
    }
}

测试AOP实现

在完成了基于不同Advice类型的判断并最终用MethodInterceptor封装的简单实现后,下一步是进行测试。以下是关于如何进行这一过程的结构化说明。

准备工作

  • 确保你的开发环境已经配置好,并且可以运行AOP相关的代码。

  • 准备至少两个不同的Advice实现,这将帮助我们验证系统是否能够正确地区分和处理不同类型的Advice。

实现步骤

  1. 定义Advices
  • 创建第一个Advice:例如,一个简单的BeforeAdvice,它将在目标方法执行前打印一条消息。

  • 创建第二个Advice:比如,一个AfterReturningAdvice,用于在目标方法成功执行后记录结果。

  1. 配置拦截器
  • 根据上面创建的Advices,配置相应的MethodInterceptor来包装这些Advice逻辑。

  • 确保每个Advice都有对应的拦截器负责其行为的触发。

  1. 应用到目标对象
  • 选择或创建一个目标对象(即包含需要被拦截方法的对象)。

  • 使用代理模式或者框架特定的方法,将上述配置好的拦截器应用到这个目标对象上。

  1. 编写测试案例
  • 编写单元测试或集成测试,调用目标对象中的相关方法。

  • 观察输出,确认Before和AfterReturning等Advice按照预期工作。

  1. 运行测试
  • 执行测试案例,检查控制台输出或其他形式的日志信息,确保每个Advice都被正确地识别并按顺序执行。

  • 如果遇到问题,回顾你的配置和代码,必要时调整直至满足需求。 aaaaaaa通过以上步骤,你可以验证你的AOP实现是否正确处理了不同类型的Advice。这是基于一种较为简化的场景所做的示范;实际项目中可能还需要考虑更多细节,如异常处理、更复杂的条件判断等。

1
2
3
4
5
6
7
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----------");
    }
}
1
2
3
4
5
6
7
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这个配置文件里面的内容也要发生变化。

1
2
3
4
5
6
7
8
9
<?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>

在本次课程中,我们深入探讨了动态代理的高级应用,特别是如何通过结构化的方式增强业务代码。首先,我们引入了Advice的概念,表示这是一个增强操作,并进一步提出了Interceptor拦截器的概念,它不仅实现了增强逻辑,还包装了目标方法的调用。我们特别关注了MethodInterceptor,它是对方法调用进行拦截的具体实现。 为了应对常见的拦截场景,如方法调用前后的处理,我们进一步分离出了beforeAdvice和afterAdvice。这些组件允许开发者专注于实现特定的业务逻辑,而无需关心拦截器的复杂性。通过这种方式,应用程序的结构更加清晰,耦合度更低。 此外,我们还讨论了如何通过修改配置来使用这些Advice,从而使得整个系统的配置更加灵活和可维护。 最后,我们留了一个思考题:如果希望beforeAdvice能在特定情况下阻止目标方法的调用,应该如何改造?欢迎在留言区与我交流讨论。 完整源代码参见 https://github.com/YaleGuo/minis