【Java代理】静态代理、动态代理、cglib代理

本文详细介绍了Java中的代理模式,包括静态代理和动态代理的实现步骤。静态代理通过创建接口的实现类和代理类实现,但当接口方法增多时,维护成本较高。JDK动态代理通过InvocationHandler接口和Proxy类实现,解决了静态代理的局限,但要求被代理类实现接口。最后,文章讲解了CGLib代理,它能为没有接口的类创建代理,适用于JDK动态代理无法覆盖的情况。
摘要由CSDN通过智能技术生成

目录

1、Java静态代理

(1)静态代理的优缺点

(2)静态代理的实现

2、JDK动态代理

(1)使用JDK动态代理的步骤

(2)JDK动态代理的实现

3、CGLIB 动态代理

(1)CGLIB 动态代理的特点

(2)CGLIB 动态代理的实现

(3)JDK 动态代理 vs CGLIB 动态代理


1、Java静态代理

        静态代理是一种设计模式,其中代理类在编译时就已经定义好了。静态代理通过实现与目标对象相同的接口,在代理类中调用目标对象的方法并在其前后执行一些操作。它可以在不修改目标对象代码的前提下,对目标对象的功能进行扩展//不修改目标对象,对功能进行扩展

(1)静态代理的优缺点

优点:

        简洁明确:代理类在编译时生成,结构清晰,易于理解和维护。

        符合单一职责原则:可以通过代理类扩展目标对象的功能,而不改变目标对象自身的代码。

缺点:

        代码冗余:每个目标对象都需要编写对应的代理类,如果接口增加方法,代理类需要同步修改,容易导致代码膨胀。

        不灵活:只能代理特定接口的实现类,不能动态代理其他类

(2)静态代理的实现

        下面是一个静态代理的实现示例,展示如何使用静态代理来扩展目标对象的功能。

        首先定义一个接口,包含目标对象和代理对象都要实现的方法。

public interface HelloService {
    void sayHello();
}

        目标对象类实现HelloService接口,包含具体的业务逻辑。

public class HelloServiceImpl implements HelloService {
    @Override
    public void sayHello() {
        System.out.println("Hello, World!");
    }
}

        代理类同样实现HelloService接口,并包含对目标对象的引用。在代理类的方法中,调用目标对象的方法,并在其前后加入额外的逻辑。

public class ServiceProxy implements HelloService {

    private final HelloService target;

    public ServiceProxy(HelloService target) {
        this.target = target;
    }

    @Override
    public void sayHello() {
        // 方法调用前的操作
        System.out.println("Before performing task in ServiceProxy...");

        // 调用目标对象的方法
        target.sayHello();

        // 方法调用后的操作
        System.out.println("After performing task in ServiceProxy...");
    }
}

        使用代理类来调用目标对象的方法,可以看到代理类在方法调用前后执行的额外操作。

public class StaticProxyTest {
    public static void main(String[] args) {
        // 创建目标对象
        HelloService service = new HelloServiceImpl();

        // 创建代理对象
        HelloService proxy = new ServiceProxy(service);

        // 通过代理对象调用方法
        proxy.sayHello();
    }
}

        静态代理的实现方式比较简单,适合在需要为少量对象添加代理功能的场景中使用。它缺点在于需要为每个接口都编写代理类,当接口方法变更时,代理类也需要进行同步修改,这可能导致代码膨胀和维护成本增加。在实际开发中,为了减少代码冗余和提高灵活性,会更多地使用动态代理,如JDK动态代理和CGLIB动态代理。

2、JDK动态代理

        JDK动态代理是一种在运行时动态生成代理类的机制,它允许我们在不修改原始类代码的情况下增强或修改方法行为。动态代理通常用于实现跨切面(如日志记录、事务管理等),在AOP(面向切面编程)中使用广泛。

(1)使用JDK动态代理的步骤

        使用JDK动态代理一般有如下步骤:

  • 定义接口: 要使用动态代理,首先需要定义一个接口,代理类和被代理的实际类都要实现这个接口。
  • 实现接口: 实现接口的具体类就是我们要代理的目标对象。
  • 创建InvocationHandler: 实现InvocationHandler接口,该接口的invoke方法会被代理对象的每个方法调用。
  • 使用Proxy类创建代理对象: 通过Proxy.newProxyInstance方法来创建代理对象。

(2)JDK动态代理的实现

        对于HelloService接口和HelloServiceImpl实现类,下面是一个JDK动态代理的实现示例。

        以下是一个通用JDK动态代理类的实现示例:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * 通用动态代理工具库:
 * 只能代理实现了接口的类(接口代理)。
 * 使用java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口实现。
 * JDK动态代理是在运行时生成代理类的字节码,并创建代理对象。
 */
public class DynamicProxyHandler implements InvocationHandler {
    private final Object target;

    // 构造函数,接受一个被代理的目标对象
    public DynamicProxyHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 在方法执行前的操作,例如日志、权限检查等
        beforeMethod(method, args);

        // 执行目标对象的方法
        Object result = method.invoke(target, args);

        // 在方法执行后的操作,例如日志、结果处理等
        afterMethod(method, args);

        return result;
    }

    private void beforeMethod(Method method, Object[] args) {
        System.out.println("Before method: " + method.getName());
        // 这里可以加入其他的通用操作
    }

    private void afterMethod(Method method, Object[] args) {
        System.out.println("After method: " + method.getName());
        // 这里可以加入其他的通用操作
    }

    // 静态方法用于创建代理对象
    @SuppressWarnings("unchecked")
    public static <T> T createProxy(T target) {
        return (T) Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            new DynamicProxyHandler(target)
        );
    }
}

        我们可以使用通用该动态代理类来代理HelloService对象:

public class ProxyTest {
    public static void main(String[] args) {
        // 创建目标对象
        HelloService helloService = new HelloServiceImpl();

        // 创建代理对象
        HelloService proxy = DynamicProxyHandler.createProxy(helloService);

        // 调用代理对象的方法
        proxy.sayHello();
    }
}

        JDK动态代理的类为什么必须需要有接口呢?

        查看JDK的动态代理源码你会发现,动态代理实际上是程序在运行中,根据被代理的接口来动态生成代理类的.class文件,并加载.class文件运行的过程,通过反编译被生成的 $Proxy0.class 文件发现,.class类被定义为:

public final class $Proxy0 extends Proxy implements Interface {
    public $Proxy0(InvocationHandler paramInvocationHandler) {
        super(paramInvocationHandler);
    }
}

        由于Java的单继承,动态生成的代理类已经继承了Proxy类的,就不能再继承其他的类,所以只能靠实现被代理类的接口的形式,故JDK的动态代理必须有接口。

3、CGLIB 动态代理

(1)CGLIB 动态代理的特点

        CGLIB 动态代理能够代理没有实现接口的类(类代理)。该方式使用字节码增强技术生成代理类的子类,通过继承代理目标类来实现代理。因为 CGLIB 代理是通过生成目标类的子类来实现的,所以被代理类不能是 final 类。此外,CGLIB 是第三方库,需要额外引入依赖。

(2)CGLIB 动态代理的实现

        对于HelloService接口和HelloServiceImpl实现类,下面是一个CGLIB动态代理的实现示例。

        引入依赖:使用 Maven 时,添加以下依赖:

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

        以下是一个通用CGLIB动态代理类的实现示例:

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * 通用CGLIB动态代理工具类:
 * 能够代理没有实现接口的类(类代理)。
 * 使用字节码增强技术生成代理类的子类,通过继承代理目标类来实现代理。
 * CGLIB 是第三方库,需要额外引入依赖。
 * 代理类不能是 final 类,因为 CGLIB 代理通过生成目标类的子类来实现。
 */
public class CglibProxyHandler implements MethodInterceptor {

    private Object target;

    // 构造方法,接收被代理的对象
    public CglibProxyHandler(Object target) {
        this.target = target;
    }

    // 创建代理对象
    public Object getProxyInstance() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass());
        enhancer.setCallback(this);
        return enhancer.create();
    }

    // 拦截方法
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        // 在方法执行前的操作
        beforeMethod(method, args);

        // 执行目标对象的方法
        Object result = proxy.invokeSuper(obj, args);

        // 在方法执行后的操作
        afterMethod(method, args);

        return result;
    }

    private void beforeMethod(Method method, Object[] args) {
        System.out.println("Before method: " + method.getName());
        // 这里可以加入其他的通用操作
    }

    private void afterMethod(Method method, Object[] args) {
        System.out.println("After method: " + method.getName());
        // 这里可以加入其他的通用操作
    }
}

        我们可以使用通用该动态代理类来代理HelloService对象:

public class CglibProxyTest {
    /**
     * 在JDK 9及以上版本(包括JDK 17)中,Java引入了模块系统(Project Jigsaw)
     * 为了确保CGLIB能够正确地访问和代理某些类,需要确保代理类所在的包被正确开放。
     * 如果项目没有模块化,可以直接这样使用:
     * --add-opens java.base/java.lang=ALL-UNNAMED
     * @param args
     */
    public static void main(String[] args) {
        // 创建目标对象
        HelloServiceImpl helloService = new HelloServiceImpl();

        // 创建代理对象
        CglibProxyHandler proxyHandler = new CglibProxyHandler(helloService);
        HelloServiceImpl proxyInstance = (HelloServiceImpl) proxyHandler.getProxyInstance();

        // 调用代理对象的方法
        proxyInstance.sayHello();
    }
}

        需要注意的是,由于模块系统(introduced in Java 9)和一些类加载器的变化,CGLIB 可能需要一些额外的配置或对模块进行开放访问,以确保代理生成能够顺利进行。

        当模块未进行开放访问时,使用CGLIB动态代理,可能会抛出以下错误:

Caused by: net.sf.cglib.core.CodeGenerationException: java.lang.reflect.InaccessibleObjectException-->Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain) throws java.lang.ClassFormatError accessible: module java.base does not "opens java.lang" to unnamed module @11028347

        如果你的项目不是模块化的,解决此问题你可以通过JVM参数来开放包。启动Java应用时,使用以下参数:

--add-opens java.base/java.lang=ALL-UNNAMED

       例如,当在 idea 中运行时,添加 JVM 参数的示例图如下:

        如果你正在使用Java模块系统并且定义了module-info.java文件,你需要确保目标类的包对CGLIB开放。假设你有一个模块your.module.name,并且CGLIB需要代理com.example.service包中的类,可以通过以下方式开放包:

module your.module.name {
    requires cglib;
    opens com.example.service to cglib;
}

        这行代码的意思是将com.example.service包开放给CGLIB模块,以便它能够反射性地访问该包中的类和成员。

(3)JDK 动态代理 vs CGLIB 动态代理

特点JDK 动态代理CGLIB 动态代理
代理对象代理接口代理类(生成子类)
性能通常性能较好,适用于代理接口的场景性能稍逊于 JDK 动态代理,适用于代理类
实现方式基于反射机制基于字节码生成
限制只能代理接口不能代理 final 类和 final 方法

        JDK 动态代理 是官方的解决方案,适用于需要代理实现了接口的类。CGLIB 动态代理适用于没有实现接口的类或需要代理类的情况。它通过生成目标类的子类来实现代理。

        至此,全文结束。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

swadian2008

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值