代理模式(Proxy Pattern):通过创建代理对象来控制对另一个对象的访问,通常用于实现横切关注点(cross-cutting concerns),例如日志记录、系统安全性检查等。常见的代理模式就是动态代理和静态代理。
一、静态代理
静态代理:在编译时就已经确定代理关系,也就是在编译时创建代理对象。在静态代理中,代理类和被代理类通常是通过接口或继承关系来建立联系,并且代理类需要实现与被代理类相同的接口,以便能够对外提供相同的服务。
静态代理的简单实现步骤如下:
- 定义一个接口(或抽象类),声明代理类和被代理类需要实现的方法。
- 创建一个被代理类,实现接口并提供具体的实现逻辑。
- 创建一个代理类,实现接口并在内部持有一个被代理类的引用。
- 在代理类的方法中,调用被代理类的相应方法,可以在调用前后进行额外的处理。
实例:用户登录代理认证
// 接口
interface UserService {//目标方法void login(String username, String password);
}// 被代理类
class UserServiceImpl implements UserService {//具体实现public void login(String username, String password) {System.out.println("用户:" + username + " 登录成功.");}
}// 静态代理类
class AuthProxy implements UserService {//引用private UserService userService;//初始化public AuthProxy(UserService userService) {this.userService = userService;}//增强方法public void login(String username, String password) {//在调用目标方法前增强逻辑if (authenticate(username, password)) {userService.login(username, password);//原始方法} else {System.out.println("身份认证失败: " + username);}}private boolean authenticate(String username, String password) {// 简单实现身份验证逻辑return username.equals("alice") && password.equals("123");}
}// 简单测试
public class Main{public static void main(String[] args) {UserService userService = new AuthProxy(new UserServiceImpl());userService.login("alice", "123"); // 身份验证成功userService.login("john", "123"); // 身份验证失败}
}
静态代理的优点:简单易懂、编码方便,可以在代理类中灵活添加额外的功能。
静态代理的缺点:代理类和被代理类之间的关系在编译时就确定了,如果有多个类需要代理,就需要编写多个代理类,导致代码冗余。
二、动态代理
动态代理:在运行时动态生成代理类,运行时才创建代理对象。
相比于静态代理,动态代理更加灵活,可以在运行时决定代理类和被代理类的关系,无需提前编写代理类。Java中实现动态代理的关键类是Proxy和InvocationHandler。Proxy类提供了创建动态代理类的方法,而InvocationHandler接口则需要自定义一个实现类,用于处理代理类的方法调用。
动态代理的简单实现步骤如下:
- 定义一个接口,声明代理类和被代理类需要实现的方法。
- 创建一个InvocationHandler的实现类,实现invoke方法,在该方法中定义对被代理类方法的处理逻辑。
- 使用Proxy类的静态方法newProxyInstance创建动态代理类的实例,传入ClassLoader、接口列表和InvocationHandler实例。
- 调用动态代理类的方法,实际上会调用InvocationHandler中的invoke方法来处理方法调用。
实例:(和上面静态代理一样)
// 接口
interface UserService {//目标方法void login(String username, String password);
}// 被代理类
class UserServiceImpl implements UserService {//具体实现public void login(String username, String password) {System.out.println("用户:" + username + " 登录成功.");}
}// 实现类,也就是工具类
class AuthenticationHandler implements InvocationHandler {private Object target;public AuthenticationHandler (Object target) {this.target = target;}public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {if (authenticate(args[0].toString(), args[1].toString())) {return method.invoke(target, args);} else {System.out.println("身份认证失败: " + args[0]);return null;}}private boolean authenticate(String username, String password) {return username.equals("alice") && password.equals("123");}
}// 简单测试
public class Main{public static void main(String[] args) {UserService userService = new UserServiceImpl();//创建代理对象,参数有类加载器、被代理对象、代理对象UserService proxy = (UserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(),userService.getClass().getInterfaces(),new AuthenticationHandler (userService));proxy.login("alice", "123"); // 身份验证成功proxy.login("john", "123"); // 身份验证失败}
}
动态代理优点:在运行时决定代理类和被代理类的关系,无需提前编写代理类,减少了代码冗余。
动态代理缺点:相比于静态代理,它在运行时会带来一定的性能开销。
详细内容链接:Java动态代理
三、动态代理和静态代理的区别
-
实现方式:静态代理在编译时就确定代理关系,而动态代理是在运行时动态生成代理类。
-
灵活性:静态代理的关系在编译时确定,无法动态修改,而动态代理可以在运行时决定代理关系。
-
代码复用:动态代理相比静态代理具有更好的代码复用性。在静态代理中,每个被代理类都需要编写一个对应的代理类,导致代码冗余。而在动态代理中,可以通过一个通用的InvocationHandler实现类来处理多个被代理类的方法调用,减少了代码的冗余。
-
性能开销:动态代理在运行时通过反射机制动态生成代理类,相比静态代理会带来一定的性能开销。每次方法调用都需要通过反射调用InvocationHandler中的invoke方法,在方法调用频繁的情况下可能会影响性能。而静态代理在编译时就确定了代理关系,直接调用被代理类的方法,性能更高。
应用场景:
- 静态代理适用于代理类数量较少且固定的情况,可以在编译时确定代理关系,并在代理类中添加额外的功能。例如,日志记录、权限校验等横切关注点可以通过静态代理来实现。
- 动态代理适用于代理类数量较多或不确定的情况,可以在运行时动态生成代理类,并通过InvocationHandler实现类来处理不同的代理逻辑。例如,AOP(面向切面编程)中的方法拦截、事务管理等功能就是通过动态代理来实现。