MiniSpring框架学习笔记-拦截器 :如何在方法前后进行拦截?
- [18. 拦截器:如何在方法前后进行拦截?](#18. 拦截器:如何在方法前后进行拦截?)
-
- 一、先看最终使用方式
- [二、把增强统一成 MethodInterceptor](#二、把增强统一成 MethodInterceptor)
- [三、用 MethodInvocation 表示一次方法调用](#三、用 MethodInvocation 表示一次方法调用)
- 四、把具体增强逻辑搬出代理类
- 五、把前置、后置通知适配成统一拦截器
- [六、用 Advisor 保存代理需要的拦截器](#六、用 Advisor 保存代理需要的拦截器)
- [七、ProxyFactoryBean 组装代理](#七、ProxyFactoryBean 组装代理)
- [八、JDK 动态代理执行拦截器](#八、JDK 动态代理执行拦截器)
- 九、容器为什么返回代理对象
- 十、运行结果
- 小结
教程: https://github.com/YaleGuo/minis
极客时间: 手把手带你写一个 MiniSpring
前言:MiniSpring 将 Java 面向对象的设计思想体现得非常充分:需要提供某种能力,就先抽象成接口;需要创建某类对象,就通过工厂负责创建。整体结构不断分层细化,上层工厂负责组织流程和组装组件,下层工厂负责创建具体实现,从而降低模块之间的耦合。以动态代理为例,ProxyFactoryBean 负责准备 target 和 Advisor,AopProxyFactory 负责选择代理实现,JdkDynamicAopProxy 最终调用 JDK API 创建代理对象。每一层只关注自己的职责,既便于扩展,也便于替换具体实现。
18. 拦截器:如何在方法前后进行拦截?
上一节我们已经用 JDK 动态代理给 doAction() 加上了日志。不过,增强逻辑还是直接写在代理类里:
java
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("doAction".equals(method.getName())) {
System.out.println("-----before call real object, dynamic proxy........");
}
return method.invoke(this.target, args);
}
这段代码能工作,但代理类同时负责了两件事:
- 创建并调用代理对象;
- 编写日志、事务等具体增强逻辑。
以后每增加一种增强,都要继续修改代理类。为了解耦,本节把增强逻辑抽成独立的 Advice 和 MethodInterceptor,代理类只负责在合适的位置调用它们。
一、先看最终使用方式
业务代码仍然只从 IoC 容器中获取 IAction:
java
IAction action = (IAction) context.getBean("action");
action.doAction();
调用方看不到代理创建过程。它只知道自己拿到了一个 IAction,但这个对象实际上是 ProxyFactoryBean 创建的 JDK 动态代理。
项目当前的 beans.xml 配置如下:
xml
<bean id="realaction" class="com.chenhai.test.Action1"/>
<bean id="myBeforeAdvice" class="com.chenhai.test.MyBeforeAdvice"/>
<bean id="myAfterAdvice" class="com.chenhai.test.MyAfterAdvice"/>
<bean id="tracingInterceptor" class="com.chenhai.test.TracingInterceptor"/>
<bean id="action" class="com.chenhai.aop.ProxyFactoryBean">
<property type="java.lang.Object" name="target" ref="realaction"/>
<property type="String" name="interceptorName" value="myAfterAdvice"/>
</bean>
这里有三个关键对象:
realaction:真实业务对象;myAfterAdvice:需要插入的增强逻辑;action:代理工厂,对外返回代理对象。
整个过程可以先记成一条主线:
text
context.getBean("action")
↓
容器发现 action 是 ProxyFactoryBean
↓
ProxyFactoryBean 找到 target 和 interceptorName
↓
创建 JdkDynamicAopProxy,再生成 JDK 代理对象
↓
调用 action.doAction()
↓
MethodInterceptor.invoke(...)
↓
MethodInvocation.proceed()
↓
反射调用 realaction.doAction()
下面再从上往下拆开这条流程。
二、把增强统一成 MethodInterceptor
先定义三个接口:
java
package com.chenhai.aop;
/**
* AOP 通知的顶层标记接口。
*/
public interface Advice {
}
java
package com.chenhai.aop;
/**
* 拦截器类型的通知。
*/
public interface Interceptor extends Advice {
}
java
package com.chenhai.aop;
/**
* 环绕一次方法调用的拦截器。
*/
public interface MethodInterceptor extends Interceptor {
Object invoke(MethodInvocation invocation) throws Throwable;
}
它们的关系很简单:
text
Advice
└─ Interceptor
└─ MethodInterceptor
Advice 表示"这是一段通知",本身不定义执行方式;Interceptor 表示这段通知会参与调用过程;MethodInterceptor 进一步约定,方法增强统一从 invoke() 进入。
MethodInterceptor 不仅能在真实方法执行前后加逻辑,还能决定是否调用 proceed()。如果不调用,真实方法就不会继续执行。因此它表达的是一种环绕调用能力。
三、用 MethodInvocation 表示一次方法调用
拦截器需要知道当前调用了哪个方法、参数是什么、目标对象是谁,还需要一种方式继续执行真实方法。于是我们把这些信息封装进 MethodInvocation:
java
package com.chenhai.aop;
import java.lang.reflect.Method;
/**
* 一次被代理方法调用的上下文。
*/
public interface MethodInvocation {
Method getMethod();
Object[] getArguments();
Object getThis();
Object proceed() throws Throwable;
}
四个方法分别表示:
| 方法 | 含义 |
|---|---|
getMethod() |
当前被调用的方法 |
getArguments() |
调用参数 |
getThis() |
当前项目中返回真实目标对象 target |
proceed() |
继续执行调用,最终进入真实方法 |
当前教学版使用反射实现 proceed()。构造时还会把无参方法可能传入的 null 参数统一成空数组:
java
protected ReflectiveMethodInvocation(Object proxy, Object target, Method method,
Object[] arguments, Class<?> targetClass) {
this.proxy = proxy;
this.target = target;
this.method = method;
this.arguments = arguments == null ? new Object[0] : arguments;
this.targetClass = targetClass;
}
@Override
public Object proceed() throws Throwable {
try {
return this.method.invoke(this.target, this.arguments);
} catch (InvocationTargetException e) {
// 反射会包一层 InvocationTargetException,这里还原真实业务异常。
throw e.getTargetException();
}
}
其余 getter 只是返回构造时保存的方法、参数和目标对象。当前版本只有一个拦截器,所以 proceed() 会直接反射调用目标方法。以后扩展成多个拦截器时,proceed() 还可以表示"继续调用下一个拦截器"。
四、把具体增强逻辑搬出代理类
有了统一接口,日志逻辑就可以写成独立的 TracingInterceptor:
java
package com.chenhai.test;
import com.chenhai.aop.MethodInterceptor;
import com.chenhai.aop.MethodInvocation;
import java.util.Arrays;
/**
* 环绕通知示例:在真实方法前后都插入日志。
*/
public class TracingInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("method " + invocation.getMethod() + " is called on "
+ invocation.getThis() + " with args "
+ Arrays.toString(invocation.getArguments()));
// 继续执行真实方法。
Object ret = invocation.proceed();
System.out.println("method " + invocation.getMethod() + " returns " + ret);
return ret;
}
}
proceed() 的位置决定了增强逻辑的执行位置:
text
proceed() 前面的代码
↓
真实业务方法
↓
proceed() 后面的代码
这里没有用 finally,因此真实方法抛出异常时,proceed() 后面的日志不会执行。这种写法表达的是"正常返回后增强",不是"无论成功失败都执行"。
五、把前置、后置通知适配成统一拦截器
除了直接实现 MethodInterceptor,项目还提供了更专门的前置通知和后置返回通知:
java
// MethodBeforeAdvice.java
public interface MethodBeforeAdvice extends BeforeAdvice {
void before(Method method, Object[] args, Object target) throws Throwable;
}
java
// AfterReturningAdvice.java
public interface AfterReturningAdvice extends AfterAdvice {
void afterReturning(Object returnValue, Method method,
Object[] args, Object target) throws Throwable;
}
这两个接口的调用方式不同,代理类如果分别处理它们,就又会出现很多分支。因此项目使用适配器,把它们统一转换成 MethodInterceptor:
java
// MethodBeforeAdviceInterceptor:先执行前置通知,再继续真实调用。
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
return mi.proceed();
}
java
// AfterReturningAdviceInterceptor:真实方法正常返回后,再执行后置通知。
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
Object retVal = mi.proceed();
this.advice.afterReturning(
retVal, mi.getMethod(), mi.getArguments(), mi.getThis());
return retVal;
}
适配之后,动态代理只需要认识 MethodInterceptor,不用关心配置的是前置通知、后置通知还是环绕拦截器。
六、用 Advisor 保存代理需要的拦截器
Advisor 负责把代理配置和通知连接起来。当前教学版只保存一个 MethodInterceptor:
java
package com.chenhai.aop;
public interface Advisor {
MethodInterceptor getMethodInterceptor();
void setMethodInterceptor(MethodInterceptor methodInterceptor);
}
DefaultAdvisor 是它的最小实现,内部只有一个 MethodInterceptor 字段和对应的 getter、setter。在完整 Spring 中,能够按方法匹配的 Advisor(例如 PointcutAdvisor)还会关联切点,用来判断哪些类、哪些方法需要增强。当前项目还没有实现 Pointcut,所以方法匹配暂时放在 JdkDynamicAopProxy 中。
七、ProxyFactoryBean 组装代理
XML 里只配置了 interceptorName,ProxyFactoryBean 还要回到容器中找到对应的通知 Bean。为此,它实现了 BeanFactoryAware:
BeanFactoryAware 只定义一个 setBeanFactory(BeanFactory beanFactory) 方法。容器完成普通属性注入后,会把自己回调给实现了该接口的 Bean:
java
private void invokeAwareMethods(Object bean) throws BeansException {
if (bean instanceof BeanFactoryAware) {
((BeanFactoryAware) bean).setBeanFactory(this);
}
}
于是 ProxyFactoryBean 可以根据名称取得通知,并把它适配成统一的 MethodInterceptor:
java
private synchronized void initializeAdvisor() {
if (this.advisor != null
|| this.interceptorName == null
|| this.interceptorName.trim().isEmpty()) {
return;
}
if (this.beanFactory == null) {
throw new IllegalStateException(
"BeanFactory is required to resolve interceptorName");
}
Object advice;
try {
advice = this.beanFactory.getBean(this.interceptorName);
} catch (BeansException e) {
throw new IllegalStateException(
"Resolve interceptor failed: " + this.interceptorName, e);
}
MethodInterceptor methodInterceptor = adaptAdvice(advice);
DefaultAdvisor defaultAdvisor = new DefaultAdvisor();
defaultAdvisor.setMethodInterceptor(methodInterceptor);
this.advisor = defaultAdvisor;
}
private MethodInterceptor adaptAdvice(Object advice) {
if (advice instanceof MethodBeforeAdvice) {
return new MethodBeforeAdviceInterceptor((MethodBeforeAdvice) advice);
}
if (advice instanceof AfterReturningAdvice) {
return new AfterReturningAdviceInterceptor((AfterReturningAdvice) advice);
}
if (advice instanceof MethodInterceptor) {
return (MethodInterceptor) advice;
}
throw new IllegalArgumentException("Unsupported advice type: " + advice);
}
准备好 Advisor 之后,再把目标对象和 Advisor 一起交给代理工厂:
java
protected AopProxy createAopProxy() {
if (this.target == null) {
throw new IllegalStateException("Property 'target' is required");
}
initializeAdvisor();
return getAopProxyFactory().createAopProxy(this.target, this.advisor);
}
这里几层对象的职责不要混在一起:
| 对象 | 职责 |
|---|---|
ProxyFactoryBean |
从容器取配置、适配通知、组织代理创建 |
AopProxyFactory |
选择并创建一种 AopProxy 实现 |
JdkDynamicAopProxy |
使用 JDK API 生成代理对象并处理方法调用 |
当前 DefaultAopProxyFactory 只选择 JDK 动态代理:
java
package com.chenhai.aop;
public class DefaultAopProxyFactory implements AopProxyFactory {
@Override
public AopProxy createAopProxy(Object target, Advisor advisor) {
return new JdkDynamicAopProxy(target, advisor);
}
}
八、JDK 动态代理执行拦截器
JdkDynamicAopProxy.getProxy() 负责生成代理对象。目标类必须实现接口,这是 JDK 动态代理的限制:
java
@Override
public Object getProxy() {
Class<?>[] interfaces = this.target.getClass().getInterfaces();
if (interfaces.length == 0) {
throw new IllegalStateException(
"JDK dynamic proxy requires target to implement an interface");
}
return Proxy.newProxyInstance(
JdkDynamicAopProxy.class.getClassLoader(),
interfaces,
this);
}
最后,代理对象的方法调用会进入 InvocationHandler.invoke():
java
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
MethodInterceptor interceptor = getMethodInterceptor();
if ("doAction".equals(method.getName()) && interceptor != null) {
Class<?> targetClass =
this.target == null ? null : this.target.getClass();
MethodInvocation invocation =
new ReflectiveMethodInvocation(
proxy, this.target, method, args, targetClass);
return interceptor.invoke(invocation);
}
// 本章只增强 doAction,其他方法仍然转发给真实对象。
return invokeTarget(method, args);
}
private MethodInterceptor getMethodInterceptor() {
return this.advisor == null
? null
: this.advisor.getMethodInterceptor();
}
private Object invokeTarget(Method method, Object[] args) throws Throwable {
try {
return method.invoke(this.target, args);
} catch (InvocationTargetException e) {
throw e.getTargetException();
}
}
这里不能让其他方法直接返回 null。代理对象上的 toString()、equals() 等调用同样会进入 invoke(),不需要增强的方法也应该正常转发给真实对象。
目前用方法名硬编码 "doAction" 只是为了聚焦本节主题。完整做法应该由切点统一判断目标类和目标方法,不能一直把匹配规则写在动态代理里。
九、容器为什么返回代理对象
XML 中注册的 action 实际上是 ProxyFactoryBean。容器取 Bean 时,会先判断它是不是 FactoryBean:
java
protected Object getObjectForBeanInstance(
Object beanInstance, String beanName) {
if (!(beanInstance instanceof FactoryBean)) {
return beanInstance;
}
FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
return getObjectFromFactoryBean(factory, beanName);
}
如果是,就调用它的 getObject()。ProxyFactoryBean 在这里创建并缓存代理对象:
java
@Override
public Object getObject() {
return getSingletonInstance();
}
private synchronized Object getSingletonInstance() {
if (this.singletonInstance == null) {
this.singletonInstance = getProxy(createAopProxy());
}
return this.singletonInstance;
}
protected Object getProxy(AopProxy aopProxy) {
return aopProxy.getProxy();
}
因此,容器内部管理的是 ProxyFactoryBean,业务代码通过 getBean("action") 拿到的却是它生产的代理对象。这就是代理创建过程可以对业务代码隐藏起来的原因。
十、运行结果
项目当前配置的是:
xml
<property type="String" name="interceptorName" value="myAfterAdvice"/>
所以调用 action.doAction() 时,真实方法先执行,后置返回通知再执行:
text
real action doAction........
----------my interceptor after method call----------
也可以切换 interceptorName 来观察不同执行顺序:
| 配置值 | 执行顺序 |
|---|---|
myBeforeAdvice |
前置通知 → 真实方法 |
myAfterAdvice |
真实方法 → 后置返回通知 |
tracingInterceptor |
调用日志 → 真实方法 → 返回日志 |
当前版本一次只能配置一个通知,还没有形成多拦截器调用链。
项目中的 AopAdviceTest 验证的是前置通知。如果 beans.xml 仍配置为 myAfterAdvice,这个测试会因为找不到前置通知输出而失败;运行该测试前,需要把配置值切换为 myBeforeAdvice,或者把测试目标改成验证后置通知。
小结
这一节完成的核心变化,是把具体增强逻辑从动态代理中抽了出来:
text
动态代理负责拦截方法
↓
Advisor 提供 MethodInterceptor
↓
MethodInterceptor 负责增强
↓
MethodInvocation.proceed() 调用真实方法
这样一来,代理创建逻辑保持稳定,日志、前置通知和后置通知可以独立编写。业务代码仍然只面向 IAction,代理对象则由 IoC 容器通过 ProxyFactoryBean 自动创建。