JDK和CGLib动态代理

一篇写的很好的解释动态代理原理的文章:

博客原地址:https://www.cnblogs.com/lifullmoon/p/14654836.html

代理

代理:在不改变原始代码的情况下修改对象的行为。代理可以以透明的方式为对象添加额外的功能。

言简意赅:方法增强

分类

静态代理

人为编写,编译时就存在

静态代理就是通过实现被代理对象所实现的接口,内部保存了被代理对象,在实现的方法中对处理逻辑进行增强,实际的方法执行调用了被代理对象的方法。

静态代理实现步骤:

  • 定义一个接口及其实现类;

  • 创建一个代理类同样实现这个接口

  • 将目标对象【实现类】注入进代理类,然后在代理类的对应方法调用目标类中的对应方法。

这样的话,我们就可以通过代理类屏蔽对目标对象的访问,并且可以在目标方法执行前后做一些自己想做的事情。

代码示例:

接口

public interface Email {
    void sendEmail();
}

实现类

public class EmailImpl implements Email{
    @Override
    public void sendEmail() {
        System.out.println("send email");
    }
}

代理类创建

public class EmailProxy implements Email {
    private EmailProxy() {
    }

    private Email email;

    public EmailProxy(Email email) {
        this.email = email;
    }

    @Override
    public void sendEmail() {
        System.out.println("方法开始执行");
        email.sendEmail();
        System.out.println("方法执行结束");
    }
}

测试

public class Test {
    public static void main(String[] args) {
        EmailProxy emailProxy = new EmailProxy(new EmailImpl());
        emailProxy.sendEmail();
    }
}

image-20240801084712166

缺点:

1、接口一旦新增方法,目标对象和代理对象都要进行修改

2、每个代理类只能为一个接口服务,不能为多个接口服务【针对每个目标类都单独创建一个代理类】

​ 假设还有一个Phone的接口,又要再写一个Phone的代理类,麻烦!

3、代理类必须实现接口

动态代理

jdk动态代理

运行时生成代理类,编译时不存在

解决了静态代理每个代理类只能代理一个目标的问题,但是目标对象必须还是要实现接口

代码示例:

代理类:Proxy.newProxyInstance()方法

​ 参数:类加载器、目标对象实现的接口、处理接口方法调用的InvocationHandler处理器

public class JDKProxyFactory {
    private JDKProxyFactory(){}
    public static Object getProxy(Object target){
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new MyMethodInterceptor(target));
    }
}

处理器

public class MyMethodInterceptor implements InvocationHandler {
    private final Object target;

    public MyMethodInterceptor(Object target){
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("方法开始执行");
        Object res = method.invoke(target, args);
        System.out.println("方法执行结束");
        return res;
    }
}

测试

public class Test {
    public static void main(String[] args) {
        Email email = (Email) JDKProxyFactory.getProxy(new EmailImpl());
        email.sendEmail();

        System.out.println("=================================");

        Phone phon = (Phone) JDKProxyFactory.getProxy(new PhoneImpl());
        phon.call();
    }
}

image-20240801090847107

缺点:只能代理实现了接口的类

为什么 JDK 动态代理只能基于接口代理,不能基于类代理?

在JDK动态代理的底层代码中,对于入参中的 interfaces 如果存在非接口,那么会抛出异常;且从生成的代理对象中看到会继承 Proxy 这个类,在 Java 中类只能是单继承关系,无法再继承一个代理类,所以只能基于接口代理。

CGLIB 动态代理

【天空一声巨响,cglib闪亮登场】

CGLIB 通过继承方式实现代理。

CGLIB 动态代理基于类代理(字节码提升),通过 ASM(Java 字节码的操作和分析框架)将被代理类的 class 文件加载进来,通过修改其字节码生成子类来处理。

很多知名的开源框架都使用到了CGLIB,例如 Spring 中的 AOP 模块中:

如果目标对象实现了接口或者是代理类的子类,则默认采用 JDK 动态代理,否则采用 CGLIB 动态代理。

image-20240801092715724

CGLIB基本介绍:

Code Generation Library代码生成类库,可以动态的给代理类生成一个子类,然后使用继承方式重写被代理方法

CGLIB基本使用

  • 创建增强器对象Enhancer

  • 给增强器对象设置要代理的父类以及拦截器回调

  • 创建方法拦截器类,实现MethodInterceptor接口

  • 创建代理对象,并调用方法

JDK 动态代理只能代理实现了接口的类或者直接代理接口,而 CGLIB 可以代理未实现任何接口的类。

代码示例:

public class Email1 {
    public void sendEmail()
    {
        System.out.println("发送邮件");
    }
}

创建代理类

public class CglibProxyFactory {
    private CglibProxyFactory(){}

    public static Object getProxy(Class<?> clazz)
    {
        Enhancer enhancer = new Enhancer();
        enhancer.setClassLoader(clazz.getClassLoader());
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(new MyCallback());
        return enhancer.create();
    }
}

拦截器回调Callback

因为 MethodInterceptor 继承了 Callback 回调接口,所以可以传入一个 MethodInterceptor 方法拦截器

注意,如果你想设置一个 Callback[] 数组去处理不同的方法,那么需要设置一个 CallbackFilter 筛选器,用于选择这个方法使用数组中的那个 Callback 去处理

public class MyCallback implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("before");
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("after");
        return result;
    }
}

测试

public class Test {
    public static void main(String[] args) {
        Email1 email = (Email1) CglibProxyFactory.getProxy(Email1.class);
        email.sendEmail();
    }
}

image-20240801093028672

CGLIB 动态代理在 JVM 运行时会新生成一个代理类,这个代理对象继承了目标类,也就是创建了一个目标类的子类,从而字节码得到提升。

posted @   kk小新  阅读(24)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
点击右上角即可分享
微信分享提示