java知识点——动态代理

一、动态代理 

在使用 spring 进行开发时,经常会使用到动态代理。当我们要实现某个功能时,我们会编写一个 Service 接口,和一个(或者多个)实现该接口的 ServiceImpl 类。这么做的好处是可以使得避免耦合。我们再需要使用该方法是,并不会去显性的创建一个 ServiceImpl,我们会直接通过 Service.Method() 的方式来调用该方法,在普通的程序里,这样是会报错的,毕竟 Service 并没有真的完成该方法的实现。但是,我们只需要添加几个注解,就能完成 ServiceImpl 和 Service 的绑定,从而使用 ServiceImpl 里实现的方法。

这就是动态代理,Service 是 方法调用者和实现者(Impl)之间的代理商。

那么如何实现这样的功能?

二、JDK 动态代理

java 的 java.lang.reflect.* 给我们提供了一种动态代理的实现方案。它可以帮助我们完成接口和接口实现类之间的绑定。

首先,我们创建一个用于测试的 Service:

1 package SSMTest.JProxyTest;
2 
3 public interface ProxyService {
4     void HelloWorld();
5 
6 }

然后是它的实现:

 1 package SSMTest.JProxyTest;
 2 import static MyTools.PrintTools.*;
 3 public class ProxyServiceImpl implements ProxyService {
 4 
 5     @Override
 6     public void HelloWorld() {
 7         println("Hello World");
 8     }
 9 
10 }

如何将这两者绑定在一起,首先,jdk 提供的方法是:

Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), invocationHandler);

第一个参数是 Impl 类的加载器,第二个参数是 Impl 的接口也就是 Service,第三个参数是一个 InvocationHandler 接口,它的 invoke() 方法将提供方法的具体实现。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable

该方法的第一个参数是 Proxy 是代理对象,也就是 Proxy.newProxyInstance(...) 方法返回的对象,第二个是当前调度的方法,第三个是调度方法的参数。

 1 package SSMTest.JProxyTest;
 2 
 3 import java.lang.reflect.InvocationHandler;
 4 import java.lang.reflect.Method;
 5 import java.lang.reflect.Proxy;
 6 
 7 import static MyTools.PrintTools.*;
 8 
 9 public class ProxyJdkExample implements InvocationHandler{
10 
11     private Object target = null;
12     
13     public Object bind(Object target) {
14         this.target = target;
15         return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
16     }
17     @Override
18     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
19         println("proxy method");
20         println("before proxy");
21         Object obj = method.invoke(target, args);
22         println("after proxy");
23         return null;
24     }
25 
26 }

在 invoke 里,我们使用的 method.invoke() 调用了 Impl 的方法。

以下是测试用例:

 1 package SSMTest.JProxyTest;
 2 
 3 public class ProxyJdkTest {
 4 
 5     public static void main(String[] args) {
 6         ProxyJdkExample jdk = new ProxyJdkExample();
 7         ProxyService proxy = (ProxyService)jdk.bind(new ProxyServiceImpl());
 8         proxy.HelloWorld();
 9     }
10 
11 }
12 /*output
13 proxy method
14 before proxy
15 Hello World
16 after proxy*/

如何去理解 jdk 动态代理的实现?

我们假设,A 需要调用 某个方法,这个方法是 接口 B 的方法,而实现这个方法的类是 C?

在程序开始,A(也就是上述的 mian() 方法)定义了一个 B

ProxyService proxy = (ProxyService)jdk.bind(new ProxyServiceImpl());

单从这个语句来看,我们似乎有点多此一举,因为上面以及它背后的大串的代码完全等价于下面这段代码:

ProxyService proxy = new ProxyServiceImpl();

但是我们发现,我们使用动态代理后调用的方法除了实现了方法本身的输出外,还额外输出了我们再 invoke 里的输出,这说明,我们并不是直接调用了 Impl 里 helloWorld() 方法这么简单。而在后面,我们会看到,我们可以在 invoke() 里面多做点文章,然后实现一些特殊的功能。

接着上面的话题,A 首先找到了 B,这个时候我们将 C 和 B 通过 Proxy.newProxyInstance(...) 方法绑定在了一起,然后 B 开始调用它的方法 HelloWorld,但是调用方法的逻辑已经被 invoke() 代理了,也就是说实际上我们并没有真的去执行 ProxyServiceImpl().HelloWorld 这个方法,而是去执行了 invoke() 方法,如果我们愿意,甚至可以让他连 Hello World 都不输出。

这表明,我们甚至可以完全不用去管 Impl 到底是怎么实现 Service 里的方法的,我们可以在 invoke() 里做我们想做的任何事。

换句话来,当 A 在调用 B 的某个方法是,我们可以把这个请求拦截下来,然后进行我们自己的操作。

三、拦截器

既然我们可以将某个方法调用的请求拦截下来,我们不如就去创建一个拦截器。

首先是拦截器的接口:

 

 1 package SSMTest.JProxyTest;
 2 
 3 import java.lang.reflect.Method;
 4 
 5 public interface Interceptor {
 6     boolean brfore(Object proxy,Object target,Method method,Object[] args);
 7     boolean around(Object proxy,Object target,Method method,Object[] args);
 8     boolean after(Object proxy,Object target,Method method,Object[] args);
 9 
10 }

 

这个拦截器,在拦截下请求之后,会执行 3 个方法的操作。

然后是它的具体实现:

 1 package SSMTest.JProxyTest;
 2 
 3 import java.lang.reflect.Method;
 4 import static MyTools.PrintTools.*;
 5 
 6 public class MyInterceptor implements Interceptor {
 7 
 8     @Override
 9     public boolean brfore(Object proxy, Object target, Method method, Object[] args) {
10         println("=====================proxy before ============================");
11         return false;
12     }
13 
14     @Override
15     public void around(Object proxy, Object target, Method method, Object[] args) {
16         println("=====================proxy aroud ============================");
17     }
18 
19     @Override
20     public void after(Object proxy, Object target, Method method, Object[] args) {
21         println("=====================proxy after ============================");
22     }
23 
24 }

好,现在我们可以利用这个拦截器去拦截一次方法的请求。

 1 package SSMTest.JProxyTest;
 2 
 3 import java.lang.reflect.InvocationHandler;
 4 import java.lang.reflect.Method;
 5 import java.lang.reflect.Proxy;
 6 
 7 public class InterceptorJdkProxy implements InvocationHandler {
 8 
 9     private Object target;
10     private String interceptorClass = null;
11     
12     public InterceptorJdkProxy(Object target,String interceptorClass) {
13         this.target =  target;
14         this.interceptorClass = interceptorClass;
15     }
16     
17     public static Object bind(Object target,String interceptorClass) {
18         return Proxy.newProxyInstance(target.getClass().getClassLoader(), 
19                 target.getClass().getInterfaces(), 
20                 new InterceptorJdkProxy(target,interceptorClass));
21     }
22     @Override
23     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
24         if(interceptorClass == null) {
25             return method.invoke(target, args);
26         }
27         Object result = null;
28         Interceptor interceptor = (Interceptor)Class.forName(interceptorClass).newInstance();
29         if(interceptor.before(proxy,target,method,args)) {
30             result = method.invoke(target, args);
31         }else {
32             interceptor.around(proxy, args, method, args);
33         }
34         interceptor.after(proxy, args, method, args);
35         return result;
36     }
37 
38 }

InterceptorJdkProxy 和 ProxyJdkExample 的区别在于,我们会做一次判断来决定是继续执行原有的 Impl 的方法,还是执行拦截器自己的方法。另外,在执行方法前后都会有一个操作。

现在我们将原来 ProxyJdkTest 代码的 ProxyJdkExample  换成 InterceptorJdkProxy:

 1 package SSMTest.JProxyTest;
 2 
 3 public class ProxyJdkTest {
 4 
 5     public static void main(String[] args) {
 6         ProxyService proxy = (ProxyService) InterceptorJdkProxy.bind(new ProxyServiceImpl(),"SSMTest.JProxyTest.MyInterceptor");
 7         proxy.HelloWorld();
 8     }
 9 
10 }
11 /*output
12 =====================proxy before ============================
13 =====================proxy aroud ============================
14 =====================proxy after ============================
15 */

通过代理的方式,我们将原来的简单的 HelloWorld() 方法的调用拦截,并且按照我们的方式进行的新的操作。

 

posted @ 2018-09-28 19:48  crazy_runcheng  阅读(216)  评论(0编辑  收藏  举报