在 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 方法。在实际开发中,我们可以根据具体的需求选择合适的代理模式。
评论