你好,我是郭屹,今天我们继续手写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(target,this.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能在某种情况下阻止目标方法的调用,应该从哪里下手改造?欢迎你在留言区与我交流讨论,也欢迎你把这节课分享给需要的朋友。我们下节课见!