在 Java 开发的世界里,代理模式是一种非常实用的设计模式,它就像是我们生活中的中介,帮助我们在不改变原有对象的基础上,对对象的功能进行增强或者控制。代理模式主要分为静态代理和动态代理,动态代理又可以细分为 JDK 动态代理和 CGLIB 动态代理。接下来,咱们就一起深入探究一下这些代理模式的实战应用。

一、静态代理

1.1 应用场景

静态代理在实际开发中应用场景还挺多的。比如说,我们有一个业务系统,需要在执行某些核心业务方法的前后添加日志记录,或者进行权限验证等操作,但是又不想在核心业务代码里掺杂这些额外的逻辑,这时候静态代理就派上用场了。

1.2 示例代码

下面咱们通过一个简单的示例来看看静态代理是怎么实现的。假设我们有一个接口 UserService,它有一个方法 saveUser 用于保存用户信息。

// 定义用户服务接口
interface UserService {
    void saveUser(String username);
}

// 实现用户服务接口
class UserServiceImpl implements UserService {
    @Override
    public void saveUser(String username) {
        System.out.println("保存用户:" + username);
    }
}

// 静态代理类
class UserServiceProxy implements UserService {
    private UserService target;

    // 构造方法,传入目标对象
    public UserServiceProxy(UserService target) {
        this.target = target;
    }

    @Override
    public void saveUser(String username) {
        // 在调用目标方法之前添加日志
        System.out.println("开始保存用户信息...");
        // 调用目标对象的方法
        target.saveUser(username);
        // 在调用目标方法之后添加日志
        System.out.println("用户信息保存完成。");
    }
}

1.3 调用示例

public class StaticProxyDemo {
    public static void main(String[] args) {
        // 创建目标对象
        UserService target = new UserServiceImpl();
        // 创建代理对象
        UserService proxy = new UserServiceProxy(target);
        // 调用代理对象的方法
        proxy.saveUser("张三");
    }
}

1.4 优缺点分析

优点:实现简单,容易理解,对于一些简单的功能增强场景非常适用。 缺点:如果接口中的方法很多,代理类需要实现所有的方法,代码会变得很臃肿。而且如果接口发生变化,代理类也需要相应地修改,维护成本较高。

1.5 注意事项

在使用静态代理时,要确保代理类和目标类实现相同的接口,这样才能保证代理类可以替代目标类使用。

二、JDK 动态代理

2.1 应用场景

JDK 动态代理适用于那些需要在运行时动态地为对象添加功能的场景。比如在 Spring AOP 中,就大量使用了 JDK 动态代理来实现面向切面编程,在方法执行前后添加事务管理、日志记录等功能。

2.2 示例代码

还是以 UserService 接口为例,我们来看看 JDK 动态代理是如何实现的。

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

// 定义用户服务接口
interface UserService {
    void saveUser(String username);
}

// 实现用户服务接口
class UserServiceImpl implements UserService {
    @Override
    public void saveUser(String username) {
        System.out.println("保存用户:" + username);
    }
}

// 实现 InvocationHandler 接口
class UserServiceInvocationHandler implements InvocationHandler {
    private Object target;

    // 构造方法,传入目标对象
    public UserServiceInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 在调用目标方法之前添加日志
        System.out.println("开始保存用户信息...");
        // 调用目标对象的方法
        Object result = method.invoke(target, args);
        // 在调用目标方法之后添加日志
        System.out.println("用户信息保存完成。");
        return result;
    }
}

2.3 调用示例

public class JdkDynamicProxyDemo {
    public static void main(String[] args) {
        // 创建目标对象
        UserService target = new UserServiceImpl();
        // 创建 InvocationHandler 对象
        InvocationHandler handler = new UserServiceInvocationHandler(target);
        // 创建代理对象
        UserService proxy = (UserService) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                handler
        );
        // 调用代理对象的方法
        proxy.saveUser("李四");
    }
}

2.4 优缺点分析

优点:JDK 动态代理是在运行时动态生成代理类,不需要手动编写代理类,减少了代码量,提高了代码的可维护性。 缺点:JDK 动态代理只能代理实现了接口的类,对于没有实现接口的类无法使用。

2.5 注意事项

在使用 JDK 动态代理时,要确保目标类实现了接口,并且在创建代理对象时,要传入目标类的类加载器和接口数组。

三、CGLIB 动态代理

3.1 应用场景

CGLIB 动态代理适用于那些没有实现接口的类,它可以在运行时动态地生成目标类的子类,从而实现对目标类的功能增强。比如在一些框架中,需要对一些没有实现接口的类进行代理时,就会使用 CGLIB 动态代理。

3.2 示例代码

首先,我们需要引入 CGLIB 的依赖。如果你使用的是 Maven 项目,可以在 pom.xml 中添加以下依赖:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.4.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;

// 目标类
class UserService {
    public void saveUser(String username) {
        System.out.println("保存用户:" + username);
    }
}

// 实现 MethodInterceptor 接口
class UserServiceMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        // 在调用目标方法之前添加日志
        System.out.println("开始保存用户信息...");
        // 调用目标对象的方法
        Object result = proxy.invokeSuper(obj, args);
        // 在调用目标方法之后添加日志
        System.out.println("用户信息保存完成。");
        return result;
    }
}

3.3 调用示例

public class CglibDynamicProxyDemo {
    public static void main(String[] args) {
        // 创建 Enhancer 对象
        Enhancer enhancer = new Enhancer();
        // 设置父类
        enhancer.setSuperclass(UserService.class);
        // 设置回调对象
        enhancer.setCallback(new UserServiceMethodInterceptor());
        // 创建代理对象
        UserService proxy = (UserService) enhancer.create();
        // 调用代理对象的方法
        proxy.saveUser("王五");
    }
}

3.4 优缺点分析

优点:CGLIB 动态代理可以代理没有实现接口的类,适用范围更广。 缺点:CGLIB 动态代理是通过生成目标类的子类来实现的,因此不能代理 final 类和 final 方法。

3.5 注意事项

在使用 CGLIB 动态代理时,要确保目标类不是 final 类,并且目标类的方法不是 final 方法。

四、总结

通过以上的介绍,我们对 Java 中的静态代理、JDK 动态代理和 CGLIB 动态代理有了更深入的了解。静态代理实现简单,但维护成本较高;JDK 动态代理适用于实现了接口的类,代码可维护性好;CGLIB 动态代理适用于没有实现接口的类,但不能代理 final 类和 final 方法。在实际开发中,我们可以根据具体的需求选择合适的代理模式。