浅谈java代理 静态代理动态代理 JDK动态代理和CGLIB动态代理

2 篇文章 0 订阅

1. 介绍

  • 什么是代理模式
    代理的思想在生活中很常见,比如:
    • 你的对象喜欢海淘买化妆品,把钱交给代购或者海外店铺去采购,他们会帮你买好物品,邮寄到你的地址;
    • 你想查询一些技术问题或者其他合法操作时,你会使用梯子代理访问目标网站;
    • 你想听周杰伦的演唱会了,可是永远都抢不到票,你可能就得找黄牛把钱给他,他帮你抢票

可见生活中,代理的思想应用无处不在。在Java中,代理是一种设计模式,可以在不修改原有类的基础上,对类的方法进行拦截和增强。Java代理主要有两种实现方式:静态代理和动态代理,动态代理里面又分JDK动态代理和CGLIB动态代理。在实际开发中我们常常不会直接去写代理的代码,而是写如AOP,或者使用JPA,事务等注解时,这些底层的实现均是由代理实现,所以首先得熟悉代理,才能真正搞动上层的东西。看不明白没关系,咱慢慢往下看。

  • 代理模式的应用场景
    Java代理主要的应用场景有:日志记录、性能监控、事务管理、安全控制鉴权等。

2. 静态代理

  • 静态代理的概念与实现
    静态代理是指在编译期间手动创建代理类。代理类和目标类通常会实现同一个接口,以便在代理类中可以调用目标类的方法。代理类中还可以实现横切关注点,例如日志记录、性能监控等。
  • 使用静态代理实现一个简单的功能扩展
    假设我们有一个接口表示打印机:
public interface Printer {
    void print(String message);
}

实现该接口的具体类:

public class RealPrinter implements Printer {
    @Override
    public void print(String message) {
        System.out.println("RealPrinter: " + message);
    }
}

接下来我们创建一个静态代理类,实现相同的接口,并持有RealPrinter的实例:

public class StaticPrinterProxy implements Printer {
    private RealPrinter realPrinter;

    public StaticPrinterProxy(RealPrinter realPrinter) {
        this.realPrinter = realPrinter;
    }

    @Override
    public void print(String message) {
        System.out.println("StaticPrinterProxy: Before print");
        realPrinter.print(message);
        System.out.println("StaticPrinterProxy: After print");
    }
}

在客户端代码中使用代理:

public class Client {
    public static void main(String[] args) {
        RealPrinter realPrinter = new RealPrinter();
        Printer proxy = new StaticPrinterProxy(realPrinter);
        proxy.print("Hello, World!");
    }
}

运行结果:

StaticPrinterProxy: Before print
RealPrinter: Hello, World!
StaticPrinterProxy: After print

由此可见,静态代理需要手动创建代理类,对目标类进行代理。可你应该就会想到,如果我有很多类需要代理呢,难道我每个都要写对应的代理类吗?有没有自动生成代理类的方式呢?还真有,偷懒是人类进步的阶梯,下面我们谈谈动态代理!

3. 动态代理

动态代理是指在运行期间动态生成代理类的字节码,并加载到内存中。和静态代理一样,动态代理生成的代理类也会实现目标类所实现的接口。这使得代理对象可以在运行时处理方法调用,并在实际调用目标对象之前或之后执行额外的逻辑。

3.1 JDK 动态代理

示例:使用 JDK 动态代理实现一个简单的功能扩展。假设我们有一个接口表示打印机:

public interface Printer {
    void print(String message);
}

实现该接口的具体类:

public class RealPrinter implements Printer {
    @Override
    public void print(String message) {
        System.out.println("RealPrinter: " + message);
    }
}

如果想对print方法进行增强,在打印前后执行一些逻辑,则可以使用JDK动态代理实现。下面我们创建一个实现InvocationHandler接口的类,用于处理代理方法调用:

public class PrinterInvocationHandler implements InvocationHandler {
    private Object target;

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

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

测试main方法编写如下:

public class Client {
    public static void main(String[] args) {
        RealPrinter realPrinter = new RealPrinter();
        PrinterInvocationHandler handler = new PrinterInvocationHandler(realPrinter);
        // 获取动态代理类
        Printer proxy = (Printer) Proxy.newProxyInstance(
            realPrinter.getClass().getClassLoader(),
            realPrinter.getClass().getInterfaces(),
            handler
        );
        // 调用代理类的打印方法
        proxy.print("Hello, World!");
    }
}

执行结果打印如下:

Before printing
RealPrinter: Hello, World!
After printing

可以看到,在不修改RealPrinter类的情况下,JDK动态代理实现了在打印方法前后添加额外逻辑。可能你也在好奇,他是如何实现的呢?此处简述下原理,本篇章不做详细讲解,可以关注后续博文(此处埋个坑,我将写一篇介绍Proxy类核心源码即动态代理的实现逻辑的源码阅读笔记,敬请期待!):

  • 获取动态代理类的对象(生成的代理类中通过反射的方式加载了目标类的方法)
  • 执行代理类方法方法,会加载自定义的PrinterInvocationHandler中的invoke方法,从而加载增强逻辑
  • method.invoke(target, args); 则通过反射加载真正目标类的具体方法

Proxy类是jdk的核心类,我们看下方法的入参

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 
  • ClassLoader参数为类加载器
  • interfaces参数为实现的接口,可以传数组
  • InvocationHandler参数为InvocationHandler的实现类,这个实现类的构造函数入参需要包含目标实现的接口类

从方法入参interfaces可知,需要有具体实现类,才可以使用jdk的动态代理,这个要记住,重点中的重点,必考题,不信的话王老师就给你赌个面包机。哈哈!这样好像还是有点麻烦,我有些类就是没有实现类,岂不是不能代理了?当然可以!下面我们来了解下CGLIB动态代理!

3.1 CGLIB动态代理

CGLIB:即英文Code Generation Library 缩写,意为代码生成库。下面介绍下如何使用CGLIB动态代理:

首先使用maven或者手动引入cglib依赖

 <dependency>
     <groupId>cglib</groupId>
     <artifactId>cglib</artifactId>
     <version>3.3.0</version>
 </dependency>

假如有个计算工具类,需要对add方法进行增强

public class CalculateUtil {

    public int add(int a, int b) {
        System.out.println("方法执行中...");
        return a + b;
    }
}

首先定义一个MethodInterceptor

public class MyMethodInterceptor<T> implements MethodInterceptor {

    private T target;

    public MyMethodInterceptor(T target) {
        this.target = target;
    }


    @Override
    public Object intercept(Object target, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("方法执行前...." + method.getName());
        Object res = methodProxy.invokeSuper(target, args);
        System.out.println("方法执行后...." + res);
        return res;
    }

}

CGLIB动态代理类测试


   public static void main(String[] args) {
        CalculateUtil calculateUtil = new CalculateUtil();
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(calculateUtil.getClass());
        MyMethodInterceptor<CalculateUtil> calculateUtilMyMethodInterceptor = new MyMethodInterceptor<CalculateUtil>(calculateUtil);
        enhancer.setCallback(calculateUtilMyMethodInterceptor);
        CalculateUtil proxy = (CalculateUtil) enhancer.create();
        proxy.add(2,3);
    }

执行结果打印如下:

方法执行前....add
方法执行中...
方法执行后....5

简述下动态代理的原理,Enhancer会生成动态代理类(底层使用ASM字节码生成框架生成代理类),这个生成的代理类是继承目标类的,即生成的是目标类的子类,重写了目标类的所有方法。需要额外注意的是,通过继承来实现的代理方法的调用,就不能对final类和final方法进行代理了。

4. 如何选择合适的代理方式

经过上文的介绍,大概也能总结出什么时候使用静态代理,什么时候使用动态代理。静态代理需要手写代理类代码,来一个写一个,造成代码量庞大且冗余,不推荐使用;动态代理中,如果目标类有实现类,即使用JDK动态代理,如果没有实现类则使用CGLIB动态代理。

性能方面,静态代理的肯定是最好的,因为代码写出来了直接编译运行。动态代理都是在运行时动态生成的,两者性能上来说差异不太明显,非要说的话,CGLIB性能应该是优于JDK动态代理(因为JDK代理是基于反射实现,反射比较耗性能),但JDK动态代理毕竟是JDK核心类下的Proxy类,在JDK版本升级时性能都有所提升。

5. 感谢

至此,打完收工!感谢您的浏览和支持!这篇文章写了有三四个星期,一直酝酿在草稿箱里,希望每篇文章不仅是对自己的知识的巩固提升,也更希望能帮助阅读的每位程序员工程师,加油!

如果您对技术有兴趣,友好交流,可以加v进技术群一起沟通,v:zzs1067632338,备注csdn即可

在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值