静态代理、动态代理

1  为何要用代理?

当用户但愿和某个对象打交道的时候,可是程序可能不但愿用户直接访问该对象,而是提供一个特殊的对象,这个特殊的对象就被称为当前要访问对象的代理,在程序中,让用户直接和这个代理对象打交道java

2  代理的特色是啥?

代理模式必需要让代理类和目标类实现相同的接口,客户端经过代理类来调用目标方法,代理类会将全部的方法调用分派到目标对象上反射执行,还能够在分派过程当中添加"前置通知"和后置处理(如在调用目标方法前校验权限,在调用完目标方法后打印日志等)等功能。特别要注意的是,代理只作效果加强,却不改变结果的类型,若是须要改变返回结果的类型,那么不推荐使用代理模式node

3  静态代理

所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就肯定了,以下代码,StaticProxy类直接就是Person的实现类缓存

public interface Person {
    public void eat(String food);

    public void sleep(String time);
}
public class StaticProxy implements Person {

    private Person person;

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

    @Override
    public void eat(String food) {
        System.out.println("静态代理提供的 吃" + food);
    }

    @Override
    public void sleep(String time) {
        System.out.println("静态代理提供的  睡" + time+"h");
    }

}
public class TestStatic {

    public static void main(String[] args){
        Person person =new PersonImpl();
        StaticProxy staticProxy=new StaticProxy(person);
        staticProxy.eat("食物");
        staticProxy.sleep("24");
    }
}

运行结果app

4  动态代理(JDK动态代理+Cglib动态代理)

为何要使用动态代理呢,由于静态代理已经不能知足咱们业务的须要了,若是我要代理20个接口怎么办?静态代理就要建20个代理类,可是对于动态代理,只要直接new它的代理实例就能够了,这就是动态代理的好处,在下面的代码中会体现出来ide

4-1 JDK动态代理

public interface Person {
    public void eat(String food);

    public void sleep(String time);
}
public class PersonImpl implements Person {

    @Override
    public void eat(String food) {
        System.out.println("JDK动态代理提供的  开始吃:" + food);
    }

    @Override
    public void sleep(String time) {
        System.out.println("JDK动态代理提供的  睡了:" + time + "h");
    }
}

建立代理类this

a   invoke方法在调用指定的具体方法时会自动调用,用于集中处理在动态代理类对象上的方法调用,好比下图中,调用到person.eat("食物")的时候,会调用到invoke(),这里能够对调用的接口作统一的处理,其实jdk的动态代理能够做为Aop的实现方式(固然Aop的实现方式有不少种spa

b   newProxyInstance()静态方法负责建立动态代理类的实例设计

public class JDKProxy implements InvocationHandler {

    private Person person;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //插入前置通知
        System.out.println("invoke method before------" + method + "------------------");

        //重点就是这里,关心的并非这个method.invoke(person,args),若是下面接口的实例作的是同一件事的话,就至关于横切
        //也就是Aop的实现,前面干什么,后面干什么
        Object result = method.invoke(person, args);

        //插入后置通知
        System.out.println("invoke method after------" + method + "------------------");
        return result;
    }

    /**
     * 建立代理
     */
    public void creatProxy() {
        //Person.class.getInterfaces();  获取某个类下面的接口
        //这里体现了动态代理的好处,能够代理Person下的全部接口
        Person person = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class[] {Person.class},
                new JDKProxy(new PersonImpl()));
     
        //若是要在代理一个接口怎么办?直接在这里实例化一个代理实例就行了
        //可是这里有个问题,新接口要作的是同一件事情,这就是Aop的一种实现方式
        person.eat("食物");
        person.sleep("24");
    }

}
参数
ClassLoader loader:类加载器
Class<?>[] interfaces:获得所有的接口
InvocationHandler h:获得InvocationHandler接口的子类实例

main方法的调用代理

public class TestJDK {
    public static void main(String[] args) {
        Person person = new PersonImpl();
        JDKProxy jdkProxy = new JDKProxy(person);
        jdkProxy.creatProxy();
    }
}

运行结果日志

理解JDK动态代理

看到这里,有的小伙伴就有点不明觉厉了,小编看到这里的时候,会有如下几个问题

1 这个代理对象究竟是怎么生成的呢?

http://rejoy.iteye.com/blog/1627405?page=2#comments

网上有篇写的很是好的博客,小编这篇博客里面重点给你们圈一下,你们看源码的时候,能够跟着这个顺序来看

Proxy.newProxyInstance(新增一个代理实例)--->
getProxyClass0(loader, intfs)(获取代理的class字节码)--->
ProxyClassFactory.apply(若是存在的话,从缓存中获取,不然从代理工厂中获取)--->
ProxyGenerator.generateProxyClass(这个才是整个代理最精华的部分,获取代理类的字节码文件)--->
var3.generateClassFile(将字节码文件写入内存)

2  这个InvocationHandler 的invoke方法究竟是谁在调用?

咱们把代理类反编译出来(主要就是用ProxyGenerator.generateProxyClass)

/***
 *
 * 获取代理的class文件,这个方法是为了印证,代理是如何实现的,与是否生成代理无关
 * 1 跟踪源码,获取代理的class------Proxy.newProxyInstance(跟进去看)
 * 2 用文件输出流输出,而后全看反编译以后的class
 */
public static void getProxyClass(Object proxyObject) {
    Class proxyClass = proxyObject.getClass();
    String proxyName=proxyClass.getName();

    // 生成代理类的字节码文件,这里是动态代理最精华的部分
    // 在源码中,生成字节码以后,会根据字节码去生成对应的代理类实例
    byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyClass.getName(), new Class[] {Person.class});

    //生成的class
    String relativeFilePath = "target/classes/com/proxy/"+proxyName+".class";

    FileOutputStream outputStream = null;

    try {
        outputStream = new FileOutputStream(relativeFilePath);
        outputStream.write(proxyClassFile);
        //java在使用流时,都会有一个缓冲区,按一种它认为比较高效的方法来发数据:把要发的数据先放到缓冲区,
        // 缓冲区放满之后再一次性发过去,而不是分开一次一次地发

        //而flush()表示强制将缓冲区中的数据发送出去,没必要等到缓冲区满.
        outputStream.flush();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            outputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

调用

public static void main(String[] args) {
    Person person=new PersonImpl();
    Person2 person2=new Person2Impl();
    getProxyClass(person);
    getProxyClass(person2);
}

在指定的路径下查看反编译以后的class文件

小编的路径是

String relativeFilePath = "target/classes/com/proxy/"+proxyName+".class";

你们点开反编译的class文件

public final void eat(String var1) throws  {
    try {
        //我的理解是,invoke方法是接口在调用,调用到你的接口时,接口调用invoke,如图,接口eat,调用了invoke
        super.h.invoke(this, m4, new Object[]{var1});
    } catch (RuntimeException | Error var3) {
        throw var3;
    } catch (Throwable var4) {
        throw new UndeclaredThrowableException(var4);
    }
}

 

3 为何jdk的动态代理只能代理接口呢?

网上看了不少的帖子,依然不是很明白,因此仍是动手敲一下吧

思路:既然说jdk的动态代理不能代理类,那么我就尝试下代理类看看报什么错误

package com.base;

/**
 * 用person4,来验证,为何jdk的动态代理只能代理接口
 */
public class Person4 {
    public void say(String word) {
        System.out.println("say:" + word);
    }
}
public class JDKProxy implements InvocationHandler {

    private Person4 person4;

    public JDKProxy(Person4 person4) {
        this.person4 = person4;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //插入前置通知
        System.out.println("invoke method before------" + method + "------------------");
        //执行相应的目标方法
        Object result = method.invoke(person4, args);
        //插入后置通知
        System.out.println("invoke method after------" + method + "------------------");
        return result;
    }

    public void creatProxy() {
        Person4 person4 = (Person4) Proxy.newProxyInstance(Person4.class.getClassLoader(), new Class[] {Person4.class}, this);
        person4.say("hallo word");
    }
}
public class TestJDK {

    public static void main(String[] args) {
        Person4  person4=new Person4();
        JDKProxy jdkProxy=new JDKProxy(person4);
        jdkProxy.creatProxy();
    }
}

而后运行结果,duang,果真错了

仍是咱们上面说的源码,找到

这里是生成代理类的class字节码文件,而后写入硬盘,这里清清楚楚的写明了,jdk的动态代理只能代理接口,自我理解下,在Java中,能够单个继承类,也能够实现多个接口,可是在jdk动态代理设计的时候,可能考量到单继承会有诸多的不便,因此在源码设计的时候,强行的设计成了接口

4-2  Cglib的动态代理

上文中咱们能够知道,jdk的动态代理只能用于接口代理,对于类的代理就力不从心了,那么咱们就能够用cglib的动态代理来实现类代理,cglib是怎么样实现动态代理的呢,首先经过字节码技术为类建立子类,子类中拦截全部父类的方法(也是AOP的实现),咱们依然以上面的person为例,话很少说,直接上代码

public class CglibProxy implements MethodInterceptor {

    private Enhancer enhancer = new Enhancer();

    public Object getProxy(Class clazz) {
        enhancer.setCallback(this);
        enhancer.setSuperclass(clazz);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("前置通知" + method + "----------");
        //这里要比较jdk的动态代理,一个是实现InvocationHandler
        //一个是实现了MethodInterceptor
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("后置通知" + method + "----------");
        return result;
    }
}
public class Person3 {
    public void eat(String food) {
        System.out.println("吃:" + food);
    }

    public void play(String basketball) {
        System.out.println("玩:" + basketball);
    }

}
public class TestCglib {

    public static void main(String[] args) {
        CglibProxy cglibProxy=new CglibProxy();
        Person3 person3=(Person3) cglibProxy.getProxy(Person3.class);
        person3.eat("cglibProxy提供的food");
        person3.play("cglibProxy提供的basketBall");
    }

}

运行结果

cglib的包问题:

这两个包都是须要的,若是cglib-nodep-2.2.jar这个包若是少了,那么就会报错

 

5  总结:动态代理与静态代理的区别

1 静态代理的字节码文件,在程序运行前就已经生成了(代理类和委托关系在程序运行前就肯定了) ,动态代理的字节码文件是在程序运行过程当中,经过Java的反射机制生成的(代理类和委托关系是在程序运 行中肯定的)

2  静态代理,要本身实现处理的结果,动态代理中全部的方法都被转移调用到了处理器的一个集中的地方处理(InvocationHandler.invoke),这样在接口数量较多的时候,能够灵活处理,而不须要像静态代理同样,对每个方法进行中转(这是动态代理了最大的优势)

对于静态代理来讲,若是要新代理一个接口的话,那么它就要新建一个代理类

可是对于动态代理,若是要代理一个新的接口,他只要从新new一个代理的实例就能够了,这样就大大减小的代码,避免了一些多余的代码

相关文章
相关标签/搜索