之前在写mybatis拦截器的时候,因为不懂原理,琢磨了很久,不知道怎么写,在网上找了很多资料,才知道mybatis的拦截器主要还是通过代理实现的,而且我在之前的博文中刚好学习了代理模式。更精细的是,在mybatis对代理的应用上,不管是封装易用性,减少代码耦合度上,都可以让我之前写的代理模式demo进一步改进,也让我加深了对代理模式的理解。
静态代理需要自己写代理类,比较麻烦,代理的东西一多就很不方便,动态代理只要简单的实现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;}@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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 {@Overridepublic void method() {System.out.println("the original method!");}@Overridepublic 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 {@Overridepublic Object intercept(Invocation invocation) throws Throwable {Intercepts annotation = this.getClass().getAnnotation(Intercepts.class);if (Objects.nonNull(annotation)) {List<String> 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也运用的很经典,自己以后还要多多学习