代理设计模式

代理设计模式

传送门java

  • 模拟计算面试时间
  • 建立一个接口
//面试
public interface InterView {
    void chatting();
}
复制代码
  • 真实对象实现接口
public class Persion implements InterView{
    @Override
    public void chatting() {
        System.out.println("is chatting ...");
        try {
            //模拟面试时间
            Thread.sleep(new Random().nextInt(1000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
复制代码
  • 用Thread.sleep()模拟面试的时间,那么我若是想知道这个时间怎么作
public class Persion implements InterView{
    @Override
    public void chatting() {
        Long start = System.currentTimeMillis();
        System.out.println("is chatting ...");
        try {
            //模拟面试时间
            Thread.sleep(new Random().nextInt(1000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Long end = System.currentTimeMillis();
        System.out.println("chatting time: " + (end - start) );
    }
}
复制代码
  • 很简单,在方法的先后捕捉当前时间,相减就出来了
  • 若是这个方法来自三方库没有改动源码的权限,怎么办

  • 咱们可使用继承,重写他的方法
public class Persion2 extends Persion {
    @Override
    public void chatting() {
        long start = System.currentTimeMillis();
        super.chatting();
        long end = System.currentTimeMillis();
        System.out.println("chatting time : " + (end - start));
    }
}
复制代码
  • 咱们也可使用聚合(注入),同时也实现面试的接口
public class Person3 implements InterView {

    private Person person;

    public Person3(Person person){
        this.person = person;
    }

    @Override
    public void chatting() {
        long start = System.currentTimeMillis();
        person.chatting();
        long end = System.currentTimeMillis();
        System.out.println("chatting time : " + (end - start));
    }
}
复制代码
  • 这两种方法均可以实现计算面试时间,哪一种好点呢
  • 咱们继续增长需求,若是我还要在面试先后作记录(打印日志)呢,很简单,继承person2,并在先后添加打印日志便可
  • 若是我要改变执行顺序呢,先获取时间,再打印日志呢,能够再用一个继承类,重写方法,这样会致使无限扩增
  • 那前面提到的聚合(注入)不会有这样的问题吗,只要稍微改一下就能够,把注入的Person修改成InterView接口
public class Person3 implements InterView {

    private InterView interView;
    
    public Person3(InterView interView){
        this.interView = interView;
    }

    @Override
    public void chatting() {
        long start = System.currentTimeMillis();
        interView.chatting();
        long end = System.currentTimeMillis();
        System.out.println("chatting time : " + (end - start));
    }
}
复制代码
  • 为了看的更清楚,将P3改成PersonProxy,用于获取方法执行时间代理的意思,同时新建PersonLogProxy代理类用于打印日志
public class PersonLogProxy implements Interview {
    private Interview interview;

    public PersonLogProxy(Interview interview) {
        this.interview = interview;
    }

    @Override
    public void chatting() {
        System.out.println("chatting start...");
        interview.chatting();
        System.out.println("chatting end...");
    }
}
复制代码
  • 若是要先记录日志,再获取面试时间,能够这样作
public class Test {
    public static void main(String[] args) {
        Person person = new Person();
        PersonLogProxy p1 = new PersonLogProxy(person);
        PersonProxy p2 = new PersonProxy(p1);
        p2.chatting();
    }

}
复制代码
  • 若是需求变动,反过来,能够这样作
public class Test {
    public static void main(String[] args) {
        PersonProxy p3 = new PersonProxy(person);
        PersonLogProxy p4 = new PersonLogProxy(p3);
        p4.chatting();

    }
}
复制代码

这里会出现一个问题,从表现来看,聚合能够实现灵活的执行顺序,而继承不能够,为何 面试

image

  • 继承实际上是一种包裹关系,若是要改变顺序,则须要建立新的模板来实现
  • 因为聚合对象和代理对象哦都是先了interView接口,相似继承的包裹关系,能够经过传入不一样的interview来改变
  • 这是利用了java多态特性,其实全部的设计模式都或多或少的用到多态

静态代理

在上面的PersonTimeProxy的chatting方法,咱们直接调用了interview().chatting,换而言之,PersonProxy代理传入的interview对象,这就是典型的静态代理实现设计模式

  • 问题: 若是须要对全部方法都执行这样的操做,一样的代码至少须要重复多少次,同时也多少次建立代理类
    • 若是溶蚀代理多各种,依然致使类无限扩展
    • 若是类中有多个方法,须要反复实现
  • 是否能够用同一个代理类来代理任意对象呢,甚至代理的逻辑也能够自定义

动态代理

jdk动态代理

  • 能够传入任意对象,即动态生成代理对象
  • 能够传入任意方法

image

  • 其实就是经过jdk自带的Proxy.newProxyInstance()方法动态生成代理类,动态编译,在经过反射建立对象并加载到内存中bash

  • 能够查看一下Proxy->newProxyInstance()的源码,代理类的动态建立经过InvocationHandler自定义dom

  • 咱们能够看一下源码中的InvocationHandler接口jvm

import java.lang.reflect.Method;

/**
 * proxy:指定动态生成的代理类
 * method:接口中传入的全部方法对象
 * args:当前传入方法的参数
 */
public interface InvocationHandler {
    void invoke(Object proxy, Method method,Object[] args);
}

复制代码

使用:ide

  • 实现InvocationHandler接口
public class MyInvocationHandler implements InvocationHandler {

    //被代理对象,object类型
    private Object target;

    public MyInvocationHandler(Object target){
        this.target = target;
    }

    /**
    *proxy:指定动态生成的代理类,
    *method:接口中传入的对象的全部方法
    *当前传入方法的参数
    */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("chatting start ...");
        Object returnValue = method.invoke(target, args);
        System.out.println("chatting end");
        return returnValue;
    }

}
复制代码
  • 测试
public class DynamicProxyTest {

    public static void main(String[] args) {
        Person person = new Person();
        MyInvocationHandler handler = new MyInvocationHandler(person);
        /**
         * 第一个参数指定代理类的类加载器,这里传入当前测试类加载器
         * 第二个参数是代理类须要实现的接口的实例类
         * 第三个参数是invocation handler,用来处理方法的调用,这里传入本身实现的handler
         */
        InterView proxyObject = (InterView) Proxy.newProxyInstance(DynamicProxyTest.class.getClassLoader()
                , person.getClass().getInterfaces(),
                handler);

        proxyObject.chatting();
    }
}
复制代码

此时,整个方法的调用栈变成了这样 测试

image

  • 简而言之就是:ui

    • 真实的业务类须要实现业务接口,代理类经过反射获取真实业务对象的类加载器,从而在内存中建立一个真实的代理对象(静态代理手动扩展那个),代理类须要实现invocationhandler接口,重写invoke方法,invoke方法三个参数,分别是代理的接口,代理的方法,和方法参数,在该方法里对真实的方法进行包装,实现动态代理
  • 和静态代理的区别:静态代理是本身去建立代理类对象,动态代理本质是在内存中根据传入的参数,动态的生成一个代理类对象,经过生成的class文件反编译能够看到this


cglib 动态代理

cglib是针对类来实现代理的,原理是针对指定的业务类生成一个子类,并覆盖其中的业务方法,实现代理,由于用的是继承,因此不能代理final修饰的类

  • 实现MethodInterceptor接口,建立代理类
public class PersonCglib implements MethodInterceptor {

    //业务对象
    private Object target;
    public Object getInstance(Object target){
        this.target = target;
        //建立加强器
        Enhancer enhancer = new Enhancer();
        //指定要代理的类(指定生成子类的父类)
        enhancer.setSuperclass(this.target.getClass());
        //设置回调,对于代理类上全部方法的调用,都会调用callback,因此callback则须要实现intercept()方法进行拦截
        enhancer.setCallback(this);
        //包含三个步骤
        //生成源代码
        //编译成class文件
        //加载到jvm中,建立实例并返回
        return enhancer.create();
    }

    //实现方法回调
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("chatting start");
        //这个obj对象是cglib给咱们new出来的
        //cglib new出来的对象是被代理对象的子类(继承了被代理类),继承至关于间接持有父类的引用
        methodProxy.invokeSuper(o,objects);
        System.out.println("chatting end");
        return null;
    }
}
复制代码
  • 使用
public static void main(String[] args) {
        Person person = new Person();
        PersonCglib cglib = new PersonCglib();
        InterView interView = (InterView) cglib.getInstance(person);

        interView.chatting();
    }
复制代码
  • 基于字节码,动态的建立须要代理的子类
  • 建立一个本来的业务对象,建立一个代理类对象,在代理类对象中实现MethodInterceptor接口,经过传入本来的业务对象,设置要继承的父类对象,并设置回调,返回一个加强对象,代理类对象实现intercept方法,传入参数是被代理对象,拦截的方法,拦截的参数,原有业务类指向的代理对象,每次调用业务方法都会回调intercept方法,实现动态代理