代理(Proxy)模式允许将对象的操作转移给其他对象,并且可以在不改变其结构的情况下增强其功能或者控制其访问。
什么是代理模式
代理模式是一种用于控制访问和增强对象功能的设计模式。代理模式中,代理对象和目标对象都实现了相同的接口,并且代理对象在其内部维护了一个真实对象的引用。当客户端请求代理对象时,代理对象将其转发给真实对象,并在调用前或调用后进行一些增强操作。
代理模式有不同的类型,例如静态代理和动态代理。静态代理是在编译时创建的代理对象,而动态代理是在运行时创建的代理对象。因此,动态代理更加灵活和易于扩展。
在 Java 中,代理模式是广泛使用的一种设计模式,它可以用于实现 AOP(面向切面编程)等应用场景。
代理模式的使用场景
- 当目标对象不适合直接访问或者不方便访问时。
- 当需要在对象的访问上增加控制和安全性时。
- 当需要增强对象的功能时。
- 当对象的访问需要给予其他对象或每个对象不可能记住所有细节时。
静态代理的代码示例
以下是一个使用静态代理方式的代码示例,假设我们有一个 Subject
接口和一个 RealSubject
类,我们需要实现一个 StaticProxy
代理类对其进行增强处理:
// Subject 接口
public interface Subject {
void request();
}
// RealSubject 接口
public class RealSubject implements Subject {
public void request() {
System.out.println("访问真实对象");
}
}
// StaticProxy 代理类
public class StaticProxy implements Subject {
private Subject subject;
// 构造函数初始化目标对象
public StaticProxy(Subject subject) {
this.subject = subject;
}
public void request() {
System.out.println("调用方法前增强");
subject.request();
System.out.println("调用方法后增强");
}
}
// 客户端使用示例
public class Client {
public static void main(String[] args) {
// 创建目标对象
RealSubject realSubject = new RealSubject();
// 创建代理对象并初始化目标对象
StaticProxy proxy = new StaticProxy(realSubject);
// 通过代理对象调用目标对象方法
proxy.request();
}
}
在上面的示例中,StaticProxy
类实现了 Subject
接口,并在其内部维护了一个 RealSubject
对象的引用。在 request()
方法中,代理对象先进行增强处理,然后调用目标对象的 request()
方法,并在方法执行后再进行增强处理。
在客户端代码中,首先创建了目标对象 RealSubject
,然后创建代理对象 StaticProxy
并将其绑定到目标对象上,最后通过代理对象调用方法。
总的来说,静态代理是一种简单易用的代理模式,适合于一些小规模的应用场景,但对于大规模的系统来说,代理类的数量会非常多,增加代码维护的难度和复杂度。此时更加灵活、可扩展和易于维护的方式是动态代理。
动态代理的示例代码
Java JDK动态代理
JDK 动态代理主要涉及两个类:Proxy 和 InvocationHandler。
JDK 动态代理是在运行时动态生成代理类,实现动态代理主要是通过 Proxy 类完成,该类提供了一个静态方法 newProxyInstance(),用于创建代理对象。该方法需要传入三个参数:
- 类加载器(ClassLoader):用于加载代理类。
- 代理类需要实现的所有接口(Class<?>[] interfaces):代理对象需要实现的所有接口。
- 处理器对象(InvocationHandler):该代理对象的具体执行逻辑。
Object proxy = Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
InvocationHandler 接口是 Java JDK 动态代理的核心接口。该接口只有一个方法 invoke(),用于处理代理对象方法的调用。该方法接收三个参数:
- 代理对象(Object):被代理的对象实例。
- 当前调用的方法(Method):被调用的方法实例。
- 方法的参数(Object[]):方法的参数对象。
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
在 invoke() 方法中,可以根据需要对原方法进行增强、修改或绕过等操作,并返回代理对象的执行结果。
下面是一个简单的 JDK 动态代理示例,假设我们有一个 Hello 接口和实现类 HelloImpl,我们希望在调用 Hello 接口的 sayHello() 方法前后分别输出“Hello, Before”和“Hello, After”。
// Hello 接口
public interface Hello {
void sayHello();
}
// Hello 实现类
public class HelloImpl implements Hello {
@Override
public void sayHello() {
System.out.println("Hello, World!");
}
}
// 代理处理器
public class HelloProxy implements InvocationHandler {
private Object target;
public HelloProxy(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Hello, Before");
Object result = method.invoke(target, args);
System.out.println("Hello, After");
return result;
}
}
// 测试代码
public class ProxyTest {
public static void main(String[] args) {
// 创建目标对象
Hello hello = new HelloImpl();
// 创建代理对象
HelloProxy helloProxy = new HelloProxy(hello);
Hello helloProxyInstance = (Hello) Proxy.newProxyInstance(hello.getClass().getClassLoader(),
hello.getClass().getInterfaces(),
helloProxy);
// 调用代理对象方法
helloProxyInstance.sayHello();
}
}
运行结果如下:
Hello, Before
Hello, World!
Hello, After
从运行结果可以看出,在调用代理对象的 sayHello() 方法前后分别执行了“Hello, Before”和“Hello, After”的输出。这就是 JDK 动态代理的基本实现原理。
CGLIB 动态代理
使用 CGLIB 动态代理主要用到两个类:Enhancer 和 MethodInterceptor。
使用 Enhancer 类可以实现创建代理对象,并设置代理对象的父类、增强器等。主要流程如下:
Enhancer enhancer = new Enhancer(); // 创建 Enhancer 对象
enhancer.setSuperclass(TargetObject.class); // 设置父类
enhancer.setCallback(new TargetInterceptor()); // 设置增强器
TargetObject targetObjectProxy = (TargetObject) enhancer.create(); // 创建代理对象
需要设置代理对象的父类、增强器 Callback、接口等属性。
MethodInterceptor 接口是 CGLIB 动态代理的核心接口,必须实现该接口的 intercept() 方法。该方法会在代理对象方法调用时被调用。参数说明如下:
- 代理对象(Object):表示被代理的对象。
- 原始方法(Method):表示被代理对象被拦截的方法。
- 方法参数列表(Object[]):表示被代理对象被拦截的方法的参数列表。
- MethodProxy:表示对被代理对象方法的代理,invokeSuper() 方法可以调用实际的目标对象。
public interface MethodInterceptor {
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable;
}
在 intercept() 方法中,可以根据需要对原方法进行增强、修改或绕过等操作,并返回代理对象的执行结果。
以下是一个简单的 CGLIB 动态代理示例,假设我们有一个 UserService 接口和实现类 UserServiceImpl,我们希望在调用 UserService 接口的方法前后分别输出“Before”和“After”。
// UserService 接口
public interface UserService {
public void addUser();
}
// UserService 实现类
public class UserServiceImpl implements UserService {
public void addUser() {
System.out.println("Add a user");
}
}
// CGLIB 动态代理拦截器
public class UserServiceInterceptor implements MethodInterceptor {
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 ProxyTest {
public static void main(String[] args) {
// 创建目标对象
UserService userService = new UserServiceImpl();
// 创建增强器
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(userService.getClass());
enhancer.setCallback(new UserServiceInterceptor());
// 创建代理对象
UserService userServiceProxy = (UserService) enhancer.create();
// 调用代理对象方法
userServiceProxy.addUser();
}
}
运行结果如下:
Before
Add a user
After
从运行结果可以看出,在调用代理对象的 addUser() 方法前后分别执行了“Before”和“After”的输出。这就是 CGLIB 动态代理的基本实现原理。
代理模式的实际应用
- Spring AOP
Spring AOP 使用代理模式来实现面向切面编程(AOP),其中,代理对象在调用方法时负责添加横切逻辑,并最终将调用转发给目标对象。Spring AOP 提供了两种代理方式:JDK 动态代理和 CGLIB 代理。默认情况下,如果目标对象实现了至少一个接口,则使用 JDK 动态代理。否则,会使用 CGLIB 代理。
以下是通过 Spring AOP 配置增强方法的示例:
@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.example.demo.service.UserService.*(..))")
public void logBefore(JoinPoint joinPoint) {
System.out.println("调用方法前增强");
}
@After("execution(* com.example.demo.service.UserService.*(..))")
public void logAfter(JoinPoint joinPoint) {
System.out.println("调用方法后增强");
}
}
- Hibernate
Hibernate 是一个流行的 ORM(对象关系映射)框架,它使用代理模式来延迟加载对象。延迟加载是一种技术,在对象被请求时从数据库中加载。 以下是通过 Hibernate 实现懒加载的示例:
@Entity
public class Customer {
@Id
private int id;
private String name;
// ...
@OneToMany(mappedBy = "customer", fetch = FetchType.LAZY)
private Set<Order> orders = new HashSet<>();
// ...
}
@Entity
public class Order {
@Id
private int id;
private Date orderDate;
// ...
@ManyToOne(fetch = FetchType.LAZY)
private Customer customer;
// ...
}
在上面的示例中,通过 @OneToMany
和 @ManyToOne
注释配置了双向关联。在默认情况下,这种关联会自动进行懒加载,即在访问订单列表或客户详情时才会从数据库中加载。
- MyBatis
MyBatis 是一个流行的 SQL 映射框架,它使用了代理模式来延迟加载对象。和 Hibernate 类似,MyBatis 也可以通过配置来实现延迟加载。
以下是一个使用 MyBatis 和代理模式的示例:
// UserMapper.java
public interface UserMapper {
@Select("SELECT * FROM user WHERE id = #{id}")
@Results({
@Result(property = "id", column = "id"),
@Result(property = "name", column = "name"),
@Result(property = "orders", column = "id",
javaType = List.class, many = @Many(select = "com.example.demo.dao.OrderMapper.findByUserId"))
})
User findById(int id);
}
// OrderMapper.java
public interface OrderMapper {
@Select("SELECT * FROM order WHERE user_id = #{userId}")
List<Order> findByUserId(int userId);
}
// User.java
public class User {
private int id;
private String name;
private List<Order> orders;
// getters and setters
}
// Order.java
public class Order {
private int id;
private Date date;
// getters and setters
}
在上述示例中,通过配置 MyBatis 的 @Result
注释和 @Many
注释来实现延迟加载。当一个用户对象调用 getOrders()
方法时,代理对象会从数据库中查询该用户的所有订单并返回。
关注微信公众号:“小虎哥的技术博客”。我们会定期发布关于Java技术的详尽文章,让您能够深入了解该领域的各种技巧和方法,让我们一起成为更优秀的程序员👩💻👨💻!