设计模式之–代理模式


代理可以简单理解为,B类托管A类的功能,并根据需求,对A类的访问作控制,这里的控制可以理解为对A类方法执行的流程的影响,包括但不限于:

在方法执行之前,先做其他事(前置通知)
在方法执行之后,再做某事(后置通知)
决定方法是否执行(环绕通知)

java中代理的主要应用体现在权限控制,日志管理,事务控制等方面

设计模式之--代理模式
Java 代理模式简单图解

静态代理

静态代理的作用,可扩展性,可维护性相对较差。典型的静态代理可以通过继承包装器两种方式来实现,包装器方式比起继承方式稍方便。

继承方式的静态代理

/**
 * @author:wangy
 * @date: 2018/9/21 / 15:15
 * @description: 通过继承的方式实现java静态代理, 主要在于理解所谓"代理"的概念
 */
public interface CellPhone {
    void sendMessage();
}

/**
 * 模拟被代理类对象
 */
public class Iphone5 implements CellPhone {

    @Override
    public void sendMessage() {
        System.out.println("the sending message is in the air.");
    }
}

/**
 * 模拟代理类对象
 */
public class Iphone5S extends Iphone5 {
    @Override
    public void sendMessage() {
        System.out.println("you need to unlock your phone first.");
        super.sendMessage();
        // 模拟短信发送
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("message sent successful.");
    }
}

/**
 *客户端
 */
public class Client {
    public static void main(String[] args) {
        CellPhone phone = new Iphone5S();
        phone.sendMessage();
    }
}

///:~
you need to unlock your phone first.
the sending message is in the air.
message sent successful.

上例中,Iphone5S 类继承了Iphone5,并对5的功能“加了点椰果”,这就是最简单的代理模式。但是,如果我还想“加点奶油”,那么就需要再创建一个类,给5“加奶油”,如果有很多手机,要加很多功能,只能通过硬编码 来完成,这样显然是不合适的。

包装器方式的静态代理

/**
 * @author:wangy
 * @date: 2018/9/21 / 15:15
 * @description: 通过包装器的方式实现java静态代理
 */
 public interface IceCream {
    void iceCreamMaker();
}

/**
 * 模拟被代理类对象
 */
 public class BerryCream implements IceCream {
    @Override
    public void iceCreamMaker() {
        System.out.println("this is a IceCream with blueberry juice.");
    }
}

/**
 * 模拟代理类对象
 */
 public class Jelly implements IceCream {
   // 代理对象中封装了被代理对象
   private BerryCream berryCream;

   public Jelly(BerryCream berryCream) {
       this.berryCream = berryCream;
   }

   @Override
   public void iceCreamMaker() {
       System.out.println("add Grass jelly into Berry IceCream");
       berryCream.iceCreamMaker();
       System.out.println("now it's a Grass-jelly-Berry-IceCream");
   }
}
 
/**
 * 模拟客户端
 */
   public class Client {
   public static void main(String[] args) {

       IceCream iceCream = new Jelly(new BerryCream());
       iceCream.iceCreamMaker();
   }
}

///:~
add Grass jelly into Berry IceCream
this is a IceCream with blueberry juice.
now it's a Grass-jelly-Berry-IceCream

在包装器模型中,被代理类对象被封装在代理类对象中。通过这种形式,如果我需要在“莓派冰激凌”上加上“烧仙草”和“珍珠果”,只需要将代码作一些改动,并且,其相对于继承模式的优势在于:继承是类似于一个链式的叠加,并且对于功能的改动比较麻烦(比如想先加珍珠果再加烧仙草,就需要改动继承关系)。而包装器设计就相对比较灵活了,只需要作简单改动:

// 将代理类对象1的封装类直接改为接口
public class Jelly implements IceCream {
    private  IceCream iceCream;

    public Jelly(IceCream iceCream) {
        this.iceCream = iceCream;
    }

    @Override
    public void iceCreamMaker() {
        System.out.println("add Grass jelly into Berry IceCream");
        iceCream.iceCreamMaker();
        System.out.println("now it's a Grass-jelly-Berry-IceCream");
    }
}

// 添加代理对象2
public class Pearl implements IceCream {
    private  IceCream iceCream;

    public Pearl(IceCream iceCream) {
        this.iceCream = iceCream;
    }

    @Override
    public void iceCreamMaker() {
        System.out.println("add pearl fruit into Berry Ice Cream.");
        iceCream.iceCreamMaker();
        System.out.println("now it's a what a icecream!");
    }
}

// 测试类
public class Client {
    public static void main(String[] args) {
        // 如果想改变“加料”的顺序,只需要改变对象初始化的顺序就ok
        IceCream iceCream = new BerryCream();
        Pearl pearl = new Pearl(iceCream);
        Jelly jelly = new Jelly(pearl);
        jelly.iceCreamMaker();
    }
}

///:~
add Grass jelly into Berry IceCream
add pearl fruit into Berry Ice Cream.
this is a IceCream with blueberry juice.
now it's a what a icecream!
now it's a Grass-jelly-Berry-IceCream

动态代理

相较于静态代理,动态代理带来的好处是:一个代理工具(或者叫代理处理程序),能够有效地实现对不同委托类的代理,这样使代码更加灵活。代理模式一定程度上可以看作“功能增强”,而“功能增强”的需求本质上就是相对灵活的,这和动态代理的初衷相契合。

JDK 动态代理

JDK 动态代理是比较常见的代理模式。其实现方法可大致总结为:

  • 1.创建一个实现接口InvocationHandler的类,它必须实现invoke方法
  • 2.创建被代理的类以及接口
  • 3.通过Proxy的静态方法 newProxyInstance(ClassLoaderloader, Class[] interfaces, InvocationHandler h)创建一个代理类实例
  • 4.通过代理类实例调用委托类方法
1. JDK动态代理需要一个接口

这个接口供所有需要被代理的类实现

public interface Subject {
    
    void sayHallo();
}
2. 和一个简单的实现类
public class RealSubject implements Subject {
    @Override
    public void sayHallo() {
        System.out.println("大家好!");
    }
}
3. 自定义代理处理程序,须实现 InvocationHandler 接口
public class MyProxyHandler implements InvocationHandler {
    private Subject subject;

    MyProxyHandler() {
    }

    MyProxyHandler(Subject subject) {
        this.subject = subject;
    }

    /**
     * 此方法用于生成一个指定接口的代理类实例, 该接口可以将方法调用指派到 [指定的调用处理程序],
     * 这个所谓的 [调用处理程序] 就是 InvocationHandler的invoke()方法
     *
     * @param subject 需要被代理的类的实例对象(被代理接口的实现类)
     * @return 一个指定接口的代理类实例
     * 这里, 指定接口就是说的是 Subject 接口, 常说的JDK动态代理需要有一个接口,就是这个原因
     */
     Object bind(Subject subject) {
        this.subject = subject;
        return Proxy.newProxyInstance(subject.getClass().getClassLoader(), subject.getClass().getInterfaces(), this);
        // 此Proxy类的静态方法等价于
        /*try {
            // 获取实现指定接口的被代理类实例对象
            Class<?> proxyClass = Proxy.getProxyClass(Subject.class.getClassLoader(), Subject.class.getInterfaces());
            // 获取指定的 [调用处理程序对象] 的构造器
            Constructor<?> proxyClassConstructor = proxyClass.getConstructor(MyProxyHandler.class);
            // 通过指定的InvocationHandler实例创建实现指定接口的代理类实例对象
            return proxyClassConstructor.newInstance(new MyProxyHandler(subject));
        }catch(Exception e){
            e.printStackTrace();
        }*/
    }

    /*  ********************************************
     *  java.lang.reflect.Proxy [extends Object implements Serializable] 类                                            
     *      -- 该类提供用于创建动态代理类和实例的静态方法, 它是由这些静态方法创建的动态代理类的超类
     *      -- 动态代理类(以下称为代理类)是一个实现 [在创建类时在运行时指定的接口列表] 的类                             
     *      -- 代理接口(Subject)是代理类实现的一个接口                                                                  
     *      -- 代理实例是代理类的一个实例, 每个代理实例都有一个关联的 [调用处理程序] 对象, 其实现接口 InvocationHandler    
     *      -- 代理实例调用方法(sayHallo())时, 会被指派到 [调用处理程序] 对象的 invoke() 方法                           
     *  ********************************************/

    /**
     * @param proxy  代理类对象
     * @param method 被代理类的方法实例
     * @param args   被代理类对象(subject)的方法实例method的参数
     * @return null
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("before...");
        method.invoke(subject, args);
        System.out.println("after...");
        return null;
    }
}
4. 客户端生成代理类对象并且调用委托类的方法

这个client介绍了3种创建动态代理类的方法

public class Client {
    /**
     * 被代理类对象(接口)
     */
    private static Subject subject = new RealSubject();
    /**
     * 代理实例关联的调用处理程序对象实例
     */
    private static InvocationHandler handler = new MyProxyHandler(subject);
    /**
     * 代理实例关联的调用处理程序对象, 一般是自定义的InvocationHandler类的实现类
     */
    private static MyProxyHandler my_proxy = new MyProxyHandler();

    public static void main(String[] args) {
        m1();
        m2();
        m3();
    }

    /**
     * 通过 [代理类实例] 调用 [被代理类] 实例的指定方法时, 会被[自定义代理类处理程序]指派给 [调用处理程序](即invoke()方法),并且执行invoke方法的增强代码
     */
    private static void m3() {
        Subject proxySubject = (Subject) my_proxy.bind(subject);
        proxySubject.sayHallo();
    }

    private static void m2() {
        Subject proxy_subject = (Subject) Proxy.newProxyInstance(subject.getClass().getClassLoader(), subject.getClass().getInterfaces(), handler);
        proxy_subject.sayHallo();
    }

    private static void m1() {
        Subject proxySubject = null;
        try {
            Class<?> proxyClass = Proxy.getProxyClass(Subject.class.getClassLoader(), Subject.class);
            Constructor<?> constructor = proxyClass.getConstructor(InvocationHandler.class);
            proxySubject = (Subject) constructor.newInstance(handler);
        } catch (Exception e) {
            e.printStackTrace();
        }
        proxySubject.sayHallo();
    }

cglib 动态代理

与JDK动态代理不同的是,cglib动态代理不需要委托类实现某个接口,其生成的代理类是委托类的子类。当然,cglib也有其局限:

  1. final 类不能通过cglib代理
  2. final 修饰的方法不能通过cglib作增强处理

cglib实现动态代理可简单地归纳为:

  • 1.创建委托类对象
  • 2.创建[自定义代理类生成程序]类,该类须实现MethodInterceptor
  • 3.通过Enhancer来创建代理类实例
  • 4.通过代理类实例调用委托类方法

1.创建委托类

public class Flower {
    public void bloom(){
        System.out.println("the flower will bloom...");
    }
}

2. 自定义代理类生成程序

public class ProxyInterceptor implements MethodInterceptor {
    private Enhancer enhancer = new Enhancer();

    Object getProxyClass(Class clazz){
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        return enhancer.create();
    }
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("after you fertilized...");
        methodProxy.invokeSuper(o, objects);
        System.out.println("and you will have a harvest.");
        return null;
    }
}

3. 客户端

public class Client {
    private static ProxyInterceptor proxyInterceptor =  new ProxyInterceptor();
    public static void main(String[] args) {
        Flower proxyClass = (Flower) proxyInterceptor.getProxyClass(Flower.class);
        proxyClass.bloom();
    }
}

JDK动态代理和cglib动态代理的区别

代理方式 优点 缺点 特点
JDK 代理类与委托类实现同一接口,主要是通过代理类实现InvocationHandler并重写invoke方法来进行动态代理的,在invoke方法中将对方法进行增强处理 不需要硬编码接口,代码复用率高 只能够代理实现了接口的委托类
cglib 代理类将委托类作为自己的父类并为其中的非final委托方法创建两个方法,一个是与委托方法签名相同的方法,它在方法中会通过super调用委托方法;另一个是代理类独有的方法。在代理方法中,它会判断是否存在实现了MethodInterceptor接口的对象,若存在则将调用intercept方法对委托方法进行代理 可以在运行时对类或者是接口进行增强操作,且委托类无需实现接口 不能对final类以及final方法进行代理
JDK和cglib动态代理的简易区别,引自[email protected]

解析JDK动态代理

to be finished…

References:

  • to be finished…

相关文章: