一文搞懂 Java 动态代理

不论是在开源框架中还是日常开发中,都少不了 代理的出现。那么代理到底是什么呢?本文将详细介绍代理相关知识,彻底搞懂代理。

1. 什么是代理,它有什么特点?

大家都知道微商代理,简单地说就是代替厂家卖商品,厂家“委托”代理为其销售商品。关于微商代理,首先我们从他们那里买东西时通常不知道背后的厂家究竟是谁,也就是说,“委托者”对我们来说是不可见的;其次,微商代理主要以朋友圈的人为目标客户,这就相当于为厂家做了一次对客户群体的“过滤”。把微商代理和厂家进一步抽象,前者可抽象为代理类,后者可抽象为委托类(被代理类)。

通过使用代理,通常有两个优点,并且能够分别与我们提到的微商代理的两个特点对应起来:

优点一:可以隐藏委托类的实现;

优点二:可以实现客户与委托类间的解耦,提高扩展性。


2. 代理实现方式

           本章节将详细介绍代理底层的原理,并结合设计原理,总结在使用过程中应当注意的事项。

           我们一般常见的代理如下图所示:

   

 

下面围绕 计算器 这个场景为基础来讲解各种代理的使用及原理。

/**
 * 计算器接口及基础实现类
 * @author jackcheng (jackcheng1117@163.com)
 */
public class TestProxy {

    public interface Calculate {
        /**
         * 计算合
         */
        void add(int i, int j);
    }

    public class CalculateImpl implements Calculate{

        @Override
        public void add(int i, int j) {
            System.out.println("result = " + (i + j));
        }
    }
}

 

2.1 JDK 静态代理

   假设现在有一个需求:需要在Calculate接口中所有方法执行前后打印一条日志

  如何可以在不修改原有代码的逻辑下解决呢?

  我们试着使用静态代理解决:

/**
 * 使用静态代理解决日志打印需求
 * @author jackcheng (jackcheng1117@163.com)
 */
public class CalculateByStaticProxy implements Calculate {

    private Calculate calculate;

    public CalculateByStaticProxy(Calculate calculate) {
        this.calculate = calculate;
    }

    @Override
    public void add(int i, int j) {
        System.out.println("====== Before() ======");
        calculate.add(i, j);
        System.out.println("====== After () ======");
    }

    /**
     * 测试结果
     */
    static class Test {
        public static void main(String[] args) {
            CalculateByStaticProxy proxy = new CalculateByStaticProxy(new TestProxy().new CalculateImpl());
            proxy.add(10, 20);
        }
    }
}

 通过静态代理在不修改原有逻辑的前提下,好像可以很好的完成添加日志的需求。

 静态代理原理:

     通过上面的案例,我们并没有添加新的东西,只是使用了组合的方式对原有逻辑进行了包装。也就是说:

     代理对象 = 增强代码 + 目标对象(原对象)

     需要注意的是:在程序运行之前代理类就已经存在了,所以这种代理方式就成为 静态代理。

   

    

代理模式看似很好的解决了需求,但是要注意的是,现在我们只有1个接口,如果有数百上千个接口就需要添加相同数量的代理类,我们努力的方向是:如何不写类或者少些代理类完成需求。

 

2.2 JDK 动态代理

  使用静态代理完成增加日志需求,若接口很多的话,则要很大的工作量,那么我们试着使用动态代理解决一下:

 

/**
 * 使用动态代理解决日志打印需求
 * @author jackcheng (jackcheng1117@163.com)
 */
public class CalculateByDynamicProxy implements InvocationHandler{

    private Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("====== Before() ======");
        method.invoke(target, args);
        System.out.println("====== After () ======");
        return null;
    }


    public static class Test {
        public static void main(String[] args) {
            Calculate proxy = (Calculate) Proxy.newProxyInstance(Calculate.class.getClassLoader(), new Class[]{Calculate.class}, new CalculateByDynamicProxy(new TestProxy().new CalculateImpl()));
            proxy.add(10, 20);
        }
    }
}

   观察上方动态代理实现案例,本质上动态代理也是:

   代理对象 = 增强代码 + 目标对象(原对象)

   与静态代理不同的是,动态代理的实现更加通用,不需要为每一个类都单独创建一个代理类,

   只需要传入被代理类的元信息即可创建代理类。排除反射性能消耗,编写一个动态代理类即可为多个接口创建代理对象。

   实现动态代理关键步骤:

    通过Proxy.newInstance静态方法创建一个代理对象

Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

    loader参数 : 被代理类(接口)的classLoader

    interfaces参数: 被代理类(接口)的类数组

    h参数: 自己创建一个实现InvocationHandler接口的类,该类实际上是一个 “中介类” ,用来写 增强代码

 

 

使用动态代理的好处就是不需要单独为每个接口都创建一个代理类,只需要创建一个实现InvocationHandler接口的实现类即可。

然后通过Java反射机制使用JVM中已知Class生成新的Class对象(代理对象),并对其方法包装执行。

关于JDK动态代理需要注意:
jdk中的动态代理只能对实现接口的类进行动态代理,并不能针对一个具体的实现类进行动态代理。
若现在有一个接口,并且接口中有A,B两个方法,我们创建一个类实现该接口,并且实现A,B方法,但是如果再在接口实现中创建一个C方法,但是接口中没有声明C方法,那么C方法时不能被代理的

 

2.3 Cglib (Code Generation Library) 动态代理

JDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能实现JDK的动态代理,cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。
cglib实现动态代理的方法和JDK动态代理类似

//创建一个普通类
public class SayHello {
    public void say(String name) {
        System.out.println("您好," + name);
    }
}
//CGLIB动态代理类
public class CglibProxy implements MethodInterceptor {
    
    /**
     * 生成CGLIB代理对象
     * @param cls -Class类 需要被代理的真实对象
     * @return
     */
    public Object getProxy(Class cls) {
        //1.CGLIB enhancer增强类对象
        Enhancer en = new Enhancer();
        //2.设置增强类型
        en.setSuperclass(cls);
        //3.定义代理逻辑对象为当前对象,要求当前对象实现 MethodInterceptor 接口
        en.setCallback(this);
        //生成代理对象并返回
        Object proxy = en.create();
        return proxy;
    }
    /**
     * 代理逻辑方法
     * 1.proxy 代理对象
     * 2.method 方法
     * 3.args 方法参数
     * 4.methodProxy 方法代理
     */
    @Override
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("调用代理对象之前的逻辑~");
        Object result = methodProxy.invokeSuper(proxy, args);
        System.out.println("调用代理对象之后的逻辑~");
        return result;
    }
}

 

//测试代码
public class TestCglibProxy {
    public static void main(String[] args) {
        CglibProxy cglib = new CglibProxy();
        SayHello proxy = (SayHello) cglib.getProxy(SayHello.class);
        proxy.say("James");
    }
}

 

3. 代理总结

优点:
        1.代理模式可以将代理对象与真实被调用的目标对象分离
        2.在一定程度上降低了系统的耦合,提高扩展性
        3.保护目标对象     

缺点:
        1.代理模式会造成系统设计中类的数目增加
        2.在客户端和目标对象通过反射增加一个代理对象,会造成请求处理速度变慢

性能测试:(万份数据测试)

    反射  <  Cglib  <  jdk8动态代理
  

 

以上关于代理的论述,仅代表个人观点,作者水平有限,如有错误,欢迎批评指正。

@author : jackcheng1117@163.com

  • 3
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值