Java设计模式之代理(Proxy)模式

45 阅读7分钟

代理(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(),用于创建代理对象。该方法需要传入三个参数:

  1. 类加载器(ClassLoader):用于加载代理类。
  2. 代理类需要实现的所有接口(Class<?>[] interfaces):代理对象需要实现的所有接口。
  3. 处理器对象(InvocationHandler):该代理对象的具体执行逻辑。
Object proxy = Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);

InvocationHandler 接口是 Java JDK 动态代理的核心接口。该接口只有一个方法 invoke(),用于处理代理对象方法的调用。该方法接收三个参数:

  1. 代理对象(Object):被代理的对象实例。
  2. 当前调用的方法(Method):被调用的方法实例。
  3. 方法的参数(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() 方法。该方法会在代理对象方法调用时被调用。参数说明如下:

  1. 代理对象(Object):表示被代理的对象。
  2. 原始方法(Method):表示被代理对象被拦截的方法。
  3. 方法参数列表(Object[]):表示被代理对象被拦截的方法的参数列表。
  4. 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 动态代理的基本实现原理。

代理模式的实际应用

  1. 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("调用方法后增强");
    }
}
  1. 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 注释配置了双向关联。在默认情况下,这种关联会自动进行懒加载,即在访问订单列表或客户详情时才会从数据库中加载。

  1. 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技术的详尽文章,让您能够深入了解该领域的各种技巧和方法,让我们一起成为更优秀的程序员👩‍💻👨‍💻!