代理模式

  在开发过程中,尤其现在注解开发过程中代理其实运用的非常广泛,它可以让我们可以更关注业务,其他的比如日志,事务,以及aop这些公共的东西交给代理类去做。本文就来介绍代理设计模式。

一、代理模式概念

  代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。(来自百度百科的介绍)
  我觉得,代理就是让我们可以单纯的去关注业务,而把公共的方法交给代理类去处理,如果业务发生变化,而像一些公共的方法是不需要发生变化,我们只需要去修改业务方法就可以。

二、代理模式的优缺点

  • 优点
    (1) .责任清晰,真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件完成事务,附带的结果就是编程简洁清晰。
    (2) .代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了中介的作用和保护了目标对象的作用。
    (3) .高扩展性
  • 缺点
    (1) .静态代理,如果代理对象多的话,则会增加代理类,加重了开发人员的工作量。
    (2) .静态代理由于需要和目标对象实现相同的接口,一旦更改了接口,那么目标对象和代理对象都要同时做出调整,不方便管理。

三、代理模式的组成

  • 抽象角色:通过接口或抽象类声明真实角色实现的业务方法。
  • 代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。
  • 真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。

 

四、静态代理

下面写一个静态代理的例子:

  • 首先需要一个抽象角色(用于定义真实角色的动作)
/**
 * 
 * @escription:此接口为代理模式的"抽象角色", 其中定义了真实角色与代理角色共同需要做的事情。
 * 此接口也可以定义为抽象类。
 * @author: Herrt灬凌夜
 * @date: 2018年11月24日 下午12:24:24
 */
public interface AbstractRole {
    /**
     * 
     * @Title: businessMethod   
     * @Description: 此方法为真实角色和代理角色需要实现的业务方法
     * @param:       
     * @return: void      
     * @throws
     */
    public void businessMethod();
}
  • 然后是真实角色(来实现抽象角色的动作)
/**
 * @escription:(真实角色)
 * @author: Herrt灬凌夜
 * @date: 2018年11月24日 下午12:47:07 
 */
public class RealRole implements AbstractRole {
    /**   
     * <p>Title: businessMethod</p>   
     * <p>Description: 真实角色所以执行的方法,只需要注重业务</p>      
     * @see proxy.AbstractRole#businessMethod()   
     */  
    public void businessMethod() {
        System.out.println("业务方法!");
    }
}
  • 接下来是代理角色(来代理真实角色的动作,并且加上自己的一些动作)
/**
 * @escription:(代理角色)
 * @author: Herrt灬凌夜
 * @date: 2018年11月24日 下午12:46:35 
 */
public class ProxyRole implements AbstractRole{
    
    private RealRole realRole;
    /**
     * 
     * @Title:ProxyRole   
     * @Description:通过构造器初始化真实角色 
     * @param:@param realRole  
     * @throws
     */
    public ProxyRole(RealRole realRole) {
        super();
        this.realRole = realRole;
    }

    /**   
     * <p>Title: businessMethod</p>   
     * <p>Description: 代理角色执行的方法,在这里处理非业务,并且公共的东西,比如日志,事务等等。</p>      
     * @see proxy.AbstractRole#businessMethod()   
     */  
    public void businessMethod() {
        prior();
        realRole.businessMethod();
        after();
    }

    /**
     * 
     * @Title: prior   
     * @Description: 在businessMethod方法执行之前执行
     * @param:       
     * @return: void      
     * @throws
     */
    private void prior() {
        System.out.println("在真实角色执行方法之前执行");
    }
    
    /**
     * 
     * @Title: after   
     * @Description: 在businessMethod方法执行之后执行
     * @param:       
     * @return: void      
     * @throws
     */
    private void after() {
        System.out.println("在真实角色执行方法之后执行");
    }
}
  • 最后是使用者
/**
 * @escription:使用者,调用真实角色的方法
 * @author: Herrt灬凌夜
 * @date: 2018年11月24日 下午12:48:16 
 */
public class Client {
    public static void main(String[] args) {
        RealRole realRole = new RealRole();
        ProxyRole proxyRole = new ProxyRole(realRole);
        proxyRole.businessMethod();
    }
}

  执行结果如下:

五、基于接口动态代理(jdk动态代理)

  在java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过这个类和这个接口可以生成JDK动态代理类和动态代理对象。

  • 原理:
  1. 拿到被代理类的引用,并且获取它的所有的接口(反射获取)。
  2. JDK Proxy类重新生成一个新的类,实现了被代理类所有的方法。
  3. 动态生成java代码,把增强逻辑加入到新生成代码中。
  4. 编译生成新的java代码的class文件。
  5. 加载并运行新的class文件,得到的类就是全新的类。  

 

 

  • 首先还是创建抽象角色和真实角色,此处使用的还是上例中的AbstractRole和RealRole
  • 创建动态代理类
 1 /**
 2  * @escription:log的动态代理类
 3  * @author: Herrt灬凌夜
 4  * @date: 2018年11月24日 下午2:44:26 
 5  */
 6 public class LogDynamicProxy implements InvocationHandler{
 7 
 8     private Object target;
 9 
10     /**   
11      * @Title:LogDynamicProxy   
12      * @Description: 将代理对象通过构造器传入
13      * @param:@param target  
14      * @throws   
15      */  
16     public LogDynamicProxy(Object target) {
17         super();
18         this.target = target;
19     }
20 
21 
22     /**
23      * 获取代理类
24      * @return the target
25      */
26     public Object getTarget() {
27         return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
28     }
29 
30     /**   
31      * <p>Title: invoke</p>   
32      * <p>Description: </p>   
33      * @param proxy : 代理类
34      * @param method : 代理类的 调用处理程序的方法对象
35      * @param args : 参数
36      * @return
37      * @throws Throwable   
38      * @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[])   
39      */  
40     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
41         log(method.getName());
42         return method.invoke(target, args);
43     }
44 
45     private void log (String method) {
46         System.out.println("执行" + method + "方法");
47     }
48 
49 }
  • 创建使用者
 1 /**
 2  * @escription:使用者,调用真实角色的方法
 3  * @author: Herrt灬凌夜
 4  * @date: 2018年11月24日 下午12:48:16 
 5  */
 6 public class Client {
 7 
 8     public static void main(String[] args) {
 9         RealRole realRole = new RealRole();
10         LogDynamicProxy logProxy = new LogDynamicProxy(realRole);
11         AbstractRole abstractRole = (AbstractRole)logProxy.getTarget();
12         abstractRole.businessMethod();
13     }
14 }

 

  下面对上述代码进行简单分析,首相我们在创建LogDynamicProxy时,调用其构造器,将真实角色传入在LogDynamicProxy的get方法中调用了Proxy类的newProxyInstance方法,这个方法是用于动态创建代理类的,其中有三个参数,第一个参数是代理类的类加载器ClassLoader,第二个是真实角色的接口数组,第三参数是代理类。我们简单看下其源码,就可以很容易的看到使用反射去创建代理类。

 1 @CallerSensitive
 2 public static Object newProxyInstance(ClassLoader loader,
 3                                           Class<?>[] interfaces,
 4                                           InvocationHandler h)throws IllegalArgumentException
 5     {
 6         //判断代理类是否为null
 7         Objects.requireNonNull(h);
 8         //克隆接口
 9         final Class<?>[] intfs = interfaces.clone();
10         //权限检查
11         final SecurityManager sm = System.getSecurityManager();
12         if (sm != null) {
13             checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
14         }
15 
16         /*
17          * Look up or generate the designated proxy class.
18          * 生成代理类,如果有直接从缓存中取,如果没有则生成
19          */
20         Class<?> cl = getProxyClass0(loader, intfs);
21 
22         /*
23          * Invoke its constructor with the designated invocation handler.
24          */
25         try {
26             if (sm != null) {
27                 checkNewProxyPermission(Reflection.getCallerClass(), cl);
28             }
29             //动态获取构造
30             final Constructor<?> cons = cl.getConstructor(constructorParams);
31             final InvocationHandler ih = h;
32             if (!Modifier.isPublic(cl.getModifiers())) {
33                 AccessController.doPrivileged(new PrivilegedAction<Void>() {
34                     public Void run() {
35                         cons.setAccessible(true);
36                         return null;
37                     }
38                 });
39             }
40             //返回代理对象
41             return cons.newInstance(new Object[]{h});
42         } catch (IllegalAccessException|InstantiationException e) {
43             throw new InternalError(e.toString(), e);
44         } catch (InvocationTargetException e) {
45             Throwable t = e.getCause();
46             if (t instanceof RuntimeException) {
47                 throw (RuntimeException) t;
48             } else {
49                 throw new InternalError(t.toString(), t);
50             }
51         } catch (NoSuchMethodException e) {
52             throw new InternalError(e.toString(), e);
53         }
54     }

  最后使用代理类去调用真实角色的方法,在调用时其实是调用的代理类的invoke方法,而这个invoke方法是重写了InvocationHandler接口的方法,这个方法有3个参数,第一个为代理类,第二个则为方法,第三为参数,在这个方法中我们动态的调用真实角色的方法,并且在其中加入一些log。
  所以说每个动态代理类,都可以去代理一类的业务,这样可以减轻程序员的代码量,而且可以更好的管理代理类。

六、基于类动态代理(cglib动态代理)

  上面的jdk动态代理是实现接口的方式去实现动态代理,而接下来说的则是继承类去实现动态代理。
  要想使用cglib的动态代理,首先,我们需要去导一个cglib-nodep的jar包,下面是maven的坐标

<!-- https://mvnrepository.com/artifact/cglib/cglib-nodep -->
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib-nodep</artifactId>
    <version>2.1_3</version>
</dependency> 
  • 首先需要创建真实角色,这个还是使用上例中的RealRole类
  • 创建cglib代理类
 1 /**
 2  * @escription:cglig代理类
 3  * @author: Herrt灬凌夜
 4  * @date: 2018年11月25日 下午12:29:14 
 5  */
 6 public class LogCglibProxy  implements MethodInterceptor {
 7 
 8     /**   
 9      * <p>Title: intercept</p>   
10      * <p>Description: 在Client中调用通过动态代理获取到的真实对象调用方法时,其实调用的是此方法</p>   
11      * @param obj
12      * @param method
13      * @param args
14      * @param proxy
15      * @return
16      * @throws Throwable   
17      * @see net.sf.cglib.proxy.MethodInterceptor#intercept(java.lang.Object, java.lang.reflect.Method, java.lang.Object[], net.sf.cglib.proxy.MethodProxy)   
18      */  
19     public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
20         log(method.getName());
21         return proxy.invokeSuper(obj, args); //动态调用真实角色的方法
22     }
23 
24     /**
25      * @Title: getInstance   
26      * @Description: 动态获取代理对象
27      * @param: @param target 真实对象
28      * @return 代理对象
29      * @throws
30      */
31     public Object getInstance (Object target) {
32         Enhancer enhancer = new Enhancer(); //创建加强器,用来创建动态代理类
33         enhancer.setSuperclass(target.getClass()); //给加强器指定父类,即真实角色
34         enhancer.setCallback(this); //设置回调,调用上面的 intercept 方法
35         return enhancer.create();//动态创建代理类,并返回
36     }
37 
38     private void log (String method) {
39         System.out.println("执行" + method + "方法");
40     }
41
  • 创建使用者
 1 /**
 2  * @escription:使用者,调用真实角色的方法
 3  * @author: Herrt灬凌夜
 4  * @date: 2018年11月24日 下午12:48:16 
 5  */
 6 public class Client {
 7 
 8     public static void main(String[] args) {
 9         RealRole realRole = new RealRole();
10         LogCglibProxy cglibProxy = new LogCglibProxy();
11         RealRole cglibRealRole = (RealRole)cglibProxy.getInstance(realRole);
12         cglibRealRole.businessMethod();
13     }
14

对上面代码进行简单分析,首先创建真实角色,代理类,调用其getInstance方法,动态创建代理类,其中代码的含义已经在代码中备注,所以在此不再赘述。而在使用代理类调用真实角色方法时,其实在调用intercept方法,这个方法是重写了MethodInterceptor 接口的方法,可以在其中加入log的一些方法。

 

 

-------------------- END ---------------------

 


 

最后附上作者的微信公众号地址和博客地址 

 


 

公众号:wuyouxin_gzh

 


 

 


 

 

 


 

Herrt灬凌夜:https://www.cnblogs.com/wuyx/

 

posted on 2018-11-25 14:18  Herrt灬凌夜  阅读(236)  评论(0编辑  收藏  举报

导航