java设计模式之动态代理

前言

为了更好的理解代理模式,首先根据生活中实际场景进行模拟,让我们在生活中去体验设计思想的美妙。

场景描述

“病从口入”这句成语告诉我们注意饮食健康,小六同学想吃苹果,在吃苹果之前需要清洗一下苹果和洗一下手,吃完苹果后,需要洗一下手保持个人卫生;十分钟后。。。小六同学又想吃一个大鸭梨,清洗鸭梨--洗手--吃鸭梨--吃完洗手。

代码模拟

苹果和鸭梨都属于食物,创建一个食物接口

public interface Foods {
    void eatApple();
    void eatpear();
}
复制代码

小六同学吃苹果和鸭梨的动作,相当于实现类

public class People implements Foods {
    @Override
    public void eatApple() {
        System.out.println("eat apple");
    }
    @Override
    public void eatpear() {
        System.out.println("eat pear");
    }
}
复制代码

小六同学谨记“病从口入”这句成语,所以在吃食物之前需要清洗食物洗手,吃完食物后需要洗手。so easy~~直接在People实现类上加上这两个动作就可以,但是小五同学说我吃苹果和鸭梨之前只洗手不洗食物,为了实现小五这个动作需要重新写接口,重写实现类。那可不可以在不改变实现类的前提下实现呢,答案是肯定的,那就用到静态代理来实现。

 public static void main(String []args){
        People people = new Perople();
        System.out.println("吃前:洗食物洗手");
        people.eatApple();
        System.out.println("吃后:洗手");
        System.out.println("吃前:洗食物洗手");
        people.eatpear();
        System.out.println("吃后:洗手");
    }
复制代码

小六变胖了

小六同学最近变胖了,原因是越来越能吃了,一天需要吃苹果、鸭梨、香蕉、樱桃、橘子、橙子。。。等一百种水果才能吃饱!虽然饮食控制不住,但小六同学还是每次吃食物之前都洗食物洗手,吃完食物后洗手的好习惯,随之食量的增大,每次都需要洗食物洗手,费力费时间,小六心生一计,不如干脆找个管家,每次想吃东西时只需喊一声,管家帮忙洗食物洗手,自己只负责吃,棒极了。

动态代理的两种实现方式

Java 实现动态代理有两种方式,一种是 Java 自带的 JDK 动态代理,还有一种是使用字节码增强技术实现的 CGLIB 库动态代理。

两种方法同时存在,各有优劣。jdk动态代理是由Java内部的反射机制来实现的,cglib动态代理底层则是借助asm来实现的。总的来说,反射机制在生成类的过程中比较高效,而asm在生成类之后的相关执行过程中比较高效(可以通过将asm生成的类进行缓存,这样解决asm生成类过程低效问题)。还有一点必须注意:jdk动态代理的应用前提,必须是目标类基于统一的接口。如果没有上述前提,jdk动态代理不能应用。由此可以看出,jdk动态代理有一定的局限性,cglib这种第三方类库实现的动态代理应用更加广泛,且在效率上更有优势。

JDK动态代理

小六委托管家来代理洗食物和洗手,小六属于委托对象,管家属于代理对象。

JDK动态代理主要两个相关类:

  • Proxy(java.lang.reflect包下的),主要负责管理和创建代理类的工作。
  • InvocationHandler 接口,只拥有一个invoke方法,主要负责方法调用部分,是动态代理中我们需要实现的方法

每一个代理实例都必须要实现InvocationHandler这个接口,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用,所以要想对方法(吃食物)加强(洗食物洗手)就需要在invoke方法中实现。

public class FoodsHandler implements InvocationHandler{
    private Object object;//委托对象(小六同学)
    public FoodsHandler(Object object){
        this.object = object;
    }
    /*invoke方法的三个参数:
    proxy:  指代我们所代理的那个真实对象
    method:  指代的是我们所要调用真实对象的某个方法的Method对象
    args:  指代的是调用真实对象某个方法时接受的参数
    */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
        System.out.println("吃前:洗食物洗手");
      	//当代理对象调用真实对象的方法时,会自动跳转到代理对象关联的handler对象的invoke方法来进行调用
        Object result = method.invoke(object, args);
        System.out.println("吃后:洗手");
        return result;
    }
}
复制代码
public static void main(String []args){
    	//委托对象(小六同学)
        People people = new People();
  		//我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的
        FoodsHandler inter = new FoodsHandler(people);
        //加上这句将会产生一个$Proxy0.class文件,这个文件即为动态生成的代理类文件
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
        //获取代理类实例foods
        Foods foods = (Foods)(Proxy.newProxyInstance(Foods.class.getClassLoader(), new 				Class[] {Foods.class}, fh));

        //通过代理类对象调用代理类方法,实际上会转到invoke方法调用
        foods.eatApple();
        foods.eatpear();
    }

/*newProxyInstance方法三个参数的解释如下
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException 
loader:一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载
interfaces:一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
h:一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上
*/
复制代码

Cglib动态代理

上面的 JDK 动态代理需要定义一个接口,实现类实现接口中的方法,如果实现类不能实现接口时,我们就需要 CGLIB 动态代理。

使用 CGLIB 动态代理之前需要导入相关 jar 包,可以单独导入 cglib-.jar 包和 asm-.jar 包,也可以只导入一个 cglib-nodep-.jar 包(包含了 asm)。下载链接

public class People {
    public void eatApple() {
        System.out.println("eat apple");
    }

    public void eatpear() {
        System.out.println("eat pear");
    }
}
复制代码
public class PeopleCglib implements MethodInterceptor {
    @Override
    // object 代表要增强的对象,method代表要拦截的方法,objects 代表方法中的参数,methodProxy 代表对方法的代理
    public Object intercept(Object object, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable{
        System.out.println("吃前:洗食物洗手");
        methodProxy.invokeSuper(object,objects);
        System.out.println("吃后:洗手");
        return object;
    }
}
复制代码
public class Main {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer(); // 增强类对象
        enhancer.setSuperclass(People.class); // 设置要增强的类(People)
        PeopleCglib peopleCglib = new PeopleCglib();
        enhancer.setCallback(peopleCglib); // 设置要增强的方法(peopleCglib)
        People people = (People) enhancer.create(); // 生成增强过的子类对象
        people.eatApple(); // 调用方法实际为增强过的方法
        people.eatApple();
    }
}
复制代码

输出结果

吃前:洗食物洗手
eat apple
吃后:洗手
吃前:洗食物洗手
eat apple
吃后:洗手
复制代码

转载于:https://juejin.im/post/5d295463f265da1ba328f348

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值