之前在写mybatis拦截器的时候,因为不懂原理,琢磨了很久,不知道怎么写,在网上找了很多资料,才知道mybatis的拦截器主要还是通过代理实现的,而且我在之前的博文中刚好学习了代理模式。更精细的是,在mybatis对代理的应用上,不管是封装易用性,减少代码耦合度上,都可以让我之前写的代理模式demo进一步改进,也让我加深了对代理模式的理解。
之前代理模式博文地址:http://blog.csdn.net/lovejj1994/article/details/74932311,上一篇博文中,我们讨论了静态代理和动态代理的区别,静态代理需要自己写代理类,比较麻烦,代理的东西一多就很不方便,动态代理只要简单的实现InvocationHandler接口,让jvm自己在运行时生成所需的代理类.
但是第一个问题就是逻辑代码在InvocationHandler的invoke方法里被写死了,不同的被代理类可以有不同的逻辑,逻辑代码被写死就无法保证代码的可拓展性。所以我们可以定义一个Interceptor接口,把需要的逻辑放在接口的intercept方法中。
public interface Interceptor {
Object intercept() throws Throwable;
}
随后我们将Interceptor 放到代理类中,让Interceptor 的intercept方法在invoke里执行。
/**
* 动态代理,新增Interceptor接口,让拦截逻辑分离出来
* Created by panqian on 2017/7/31.
*/
public class DynamicProxy implements InvocationHandler {
private Interceptor interceptor;
private DynamicProxy(Object object, Interceptor interceptor) {
super();
this.interceptor = interceptor;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object intercept = interceptor.intercept();
System.out.println("我收到了:" + intercept);
return intercept;
}
}
编写测试类,查看效果:
public class DynamicProxyTest {
public static void main(String[] args) {
Sourceable source = new Source();
//lambda表达式直接实现,返回 [0,9] 之间的数
Interceptor interceptor = () -> new Double(Math.random() * 10).intValue();
DynamicProxy dynamicProxy = new DynamicProxy(source, interceptor);
Sourceable sourceable = (Sourceable) Proxy.newProxyInstance(Source.class.getClassLoader(), Source.class.getInterfaces(), dynamicProxy);
sourceable.method();
System.out.println("=========");
sourceable.method1();
}
}
我收到了:9
=========
我收到了:4
通过上面对逻辑代码的封装,作为客户端程序员就可以随意改变代码逻辑,不需要直接在InvocationHandler接口上定义代码逻辑。
现在引出第二个问题,Interceptor 的引入对我们操作代理类还不是很自由,因为在invoke方法中,还有三个参数,除了第一个proxy对象用的很少,其余两个对于代理操作的灵活性非常重要,method可以返回当前的方法对象,args则是方法的参数数组。那这些参数是否也可以封装进Interceptor接口供 客户端程序员使用呢?
invoke(Object proxy, Method method, Object[] args)
先新建一个Invocation类,里面有method和args。
public class Invocation {
private Method method;
private Object[] args;
public Invocation(Method method, Object[] args) {
this.method = method;
this.args = args;
}
public Method getMethod() {
return method;
}
public void setMethod(Method method) {
this.method = method;
}
public Object[] getArgs() {
return args;
}
public void setArgs(Object[] args) {
this.args = args;
}
@Override
public String toString() {
return "Invocation{" +
"method=" + method +
", args=" + Arrays.toString(args) +
'}';
}
}
Interceptor 接口做个改造,intercept方法放入Invocation对象。
public interface Interceptor {
Object intercept(Invocation invocation) throws Throwable;
}
最后在代理类将Invocation 对象封装好并传入intercept方法,这样客户端程序员就对这个拦截器操作有更大的灵活性。
public class DynamicProxy implements InvocationHandler {
private Interceptor interceptor;
private DynamicProxy(Object object, Interceptor interceptor) {
super();
this.interceptor = interceptor;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//method和args通过intercept()方法传给了客户端程序员
Object intercept = interceptor.intercept(new Invocation(method, args));
System.out.println("我收到了:" + intercept);
return intercept;
}
}
最后的测试类,这里为了打印方法参数,将被代理类的method1方法多加了一个参数,用来演示效果。
public static void main(String[] args) {
Sourceable source = new Source();
//lambda表达式直接实现,返回 [0,9] 之间的数
Interceptor interceptor = (invocation) -> {
System.out.println("invocation :" + invocation.toString());
return new Double(Math.random() * 10).intValue();
};
DynamicProxy dynamicProxy = new DynamicProxy(source, interceptor);
Sourceable sourceable = (Sourceable) Proxy.newProxyInstance(Source.class.getClassLoader(), Source.class.getInterfaces(), dynamicProxy);
sourceable.method();
System.out.println("=========");
sourceable.method1(666);
}
invocation :Invocation{method=public abstract void designmode.代理模式.evolution02.Sourceable.method(), args=null}
我收到了:2
=========
invocation :Invocation{method=public abstract void designmode.代理模式.evolution02.Sourceable.method1(int), args=[666]}
我收到了:0
这时候我们从客户端程序员的角度开始看生成代理对象的代码,我们只需要提供被代理对象(source)和代理逻辑(interceptor),但是我们还写了生成代理对象的逻辑代码(Proxy.newProxyInstance),这不是我们客户端程序员需要干的事,所以我们决定把这部分代码继续提取出来,封装到DynamicProxy代理类中。因为在mybatis拦截器中,Plugin类对应我们的DynamicProxy类,我们也在逐步靠近mybatis拦截器的写法,所以下面直接把DynamicProxy更名为Plugin,便于理解。
对Plugin新增一个静态方法wrap,通过此方法生成代理对象,客户端不需要写过多跟自己业务无关的代码,生成代理对象的任务全部转移到Plugin类。
public class Plugin implements InvocationHandler {
private Interceptor interceptor;
private Plugin(Object object, Interceptor interceptor) {
super();
this.interceptor = interceptor;
}
//wrap方法生成代理对象
public static Object wrap(Object target, Interceptor interceptor) {
Class> type = target.getClass();
Class>[] interfaces = target.getClass().getInterfaces();
return interfaces.length > 0 ? Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor)) : target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object intercept = interceptor.intercept(new Invocation(method, args));
System.out.println("我收到了:" + intercept);
return intercept;
}
}
测试类部分代码,sourceable 代理对象 依靠Plugin.wrap生成 :
Sourceable sourceable = (Sourceable)Plugin.wrap(source, interceptor);
等等!我们似乎忘记了一个很严重的问题,我们似乎忘了执行 被代理类本身的method代码。。。虽然这也很好解决,只要在Plugin 的invoke方法中执行以下方法即可:
method.invoke(this.target, this.args);
但是现在已经用了Interceptor ,这个也干脆剥离出来 给客户端程序员 自由发挥吧,Invocation做个小小的改造,新增了target被代理对象 和 proceed方法,proceed方法用来执行原方法的逻辑:
public class Invocation {
private Method method;
private Object[] args;
private Object target;
public Invocation(Method method, Object[] args,Object target) {
this.method = method;
this.args = args;
this.target = target;
}
....
public Object proceed() throws InvocationTargetException, IllegalAccessException {
return this.method.invoke(this.target, this.args);
}
}
Plugin 也改动一下,新增object被代理对象。
public class Plugin implements InvocationHandler {
private Object object;
private Interceptor interceptor;
private Plugin(Object object, Interceptor interceptor) {
super();
this.object = object;
this.interceptor = interceptor;
}
public static Object wrap(Object target, Interceptor interceptor) {
Class> type = target.getClass();
Class>[] interfaces = target.getClass().getInterfaces();
return interfaces.length > 0 ? Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor)) : target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object intercept = interceptor.intercept(new Invocation(method, args, object));
System.out.println("我收到了:" + intercept);
return intercept;
}
}
被代理类也新增一些逻辑代码:
public class Source implements Sourceable {
@Override
public void method() {
System.out.println("the original method!");
}
@Override
public void method1(int i) {
System.out.println("the original method1!");
}
}
最后测试类测试一下:
public static void main(String[] args) {
Sourceable source = new Source();
//lambda表达式直接实现,返回 [0,9] 之间的数
Interceptor interceptor = (invocation) -> {
System.out.println("原方法开始执行");
//执行原方法的逻辑
invocation.proceed();
System.out.println("原方法执行完毕");
return new Double(Math.random() * 10).intValue();
};
Sourceable sourceable = (Sourceable) Plugin.wrap(source, interceptor);
sourceable.method();
System.out.println("=========");
sourceable.method1(666);
}
测试成功~!
原方法开始执行
the original method!
原方法执行完毕
我收到了:4
=========
原方法开始执行
the original method1!
原方法执行完毕
我收到了:9
到现在为止mybatis拦截器的原理也讲了十之六七,在拦截器里,并不是所有的方法都需要拦截,你可以在拦截逻辑里判断method的方法名,这是一个方法,mybatis用了注解解决这个问题。
先新建一个注解,methods加入要拦截的方法名:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface Intercepts {
String[] methods();
}
新建一个Interceptor实现类,加上注解@Intercepts。
@Intercepts(methods = {"method1"})
public class MyInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
Intercepts annotation = this.getClass().getAnnotation(Intercepts.class);
if (Objects.nonNull(annotation)) {
List methods = Arrays.asList(annotation.methods());
if (methods.contains(invocation.getMethod().getName())) {
System.out.println(invocation.getMethod().getName() + " :该方法可以执行");
} else {
System.out.println(invocation.getMethod().getName() + " :该方法不能执行");
}
}
return null;
}
}
测试类:
public static void main(String[] args) {
Sourceable source = new Source();
//lambda表达式貌似不能加注解,所以换成传统实现类
Interceptor interceptor = new MyInterceptor();
Sourceable sourceable = (Sourceable) Plugin.wrap(source, interceptor);
sourceable.method();
System.out.println("=========");
sourceable.method1(666);
}
结果,可以看出拦截起到了效果:
method :该方法不能执行
=========
method1 :该方法可以执行
总结:
这是一篇以mybatis拦截器实现原理为例子的 讲解代理模式运用 的文章,单纯的学习代理模式不用于实战是理解不了设计模式的核心和用法。 在设计模式中,代理模式算是运用的很广泛了,比如spring的aop也运用的很经典,自己以后还要多多学习