代理模式的实现

1. 引言

1.1 背景

代理模式(Proxy Pattern)是一种常用的设计模式,它允许通过一个代理对象来控制对另一个对象的访问。在面向对象编程的框架中,代理模式被广泛应用,尤其在Spring框架的AOP(面向切面编程)功能中,用于实现横切关注点的模块化,如事务管理、日志记录、安全检查等。

通过代理模式,我们能够将复杂的功能从主要业务逻辑中剥离出来,使代码更简洁易读。同时,它还能够在不修改原有代码的基础上,动态地增加新功能或优化现有功能,使代码更灵活可扩展。

1.2 目的

本文将详细介绍代理模式的基本概念、实现步骤。通过本篇文章,你将能够理解代理模式的工作原理,并学会如何在实际项目中有效地利用它。

2. 何为代理模式?

代理模式就像是生活中的中介或代理人,他们代表我们处理一些事务,让我们能够更方便、更高效地完成任务。想象一下,你想要买一套房子,但你没有时间或者不熟悉购房流程,这时候你可能会找一个房产中介来帮你处理这些事情。房产中介就是这个场景中的"代理",而你则是"客户端",房子和卖家则是"真实对象"。

在这个例子中,房产中介(代理)会:

  • 代表你与卖家沟通:中介会代替你与卖家协商价格、查看房子状况等,这样你就不需要亲自去做这些事情。

  • 提供额外的服务:中介可能会提供法律咨询、贷款协助等额外服务,帮助你更顺利地完成购房过程。

  • 控制访问:中介可能会筛选掉一些不符合你要求的房子,只向你推荐合适的房源,这样你就不需要自己去处理大量的信息。

  • 延迟处理:如果你暂时没有时间去看房,中介可以先帮你预约,等到你有时间再去,这样就避免了资源的浪费。

通过房产中介这个代理,你可以在不直接与卖家接触的情况下,完成购房的任务,同时还能享受到额外的服务和便利。

在软件开发中,代理模式也是类似的道理。比如,一个处理敏感数据的应用程序,可以通过代理来控制对数据的访问,确保只有授权的用户才能查看或修改数据。或者,一个需要处理大量计算的应用,可以通过代理来实现计算的延迟加载,只在真正需要时才进行计算,从而提高系统的效率。

2.1 代理模式的主要角色

  1. Subject(主体):定义了RealSubject和Proxy的共同接口,这样在任何使用RealSubject的地方都可以使用Proxy。
  2. RealSubject(真实主体):定义了Proxy所代表的真实对象。
  3. Proxy(代理):持有一个RealSubject的引用,并提供与RealSubject相同的接口,这样代理就可以代替RealSubject。代理对象可以在调用RealSubject的方法前后执行额外的操作。
java 复制代码
// 1. 主接口
public interface Subject {
    void house();
}


// 2. 真实对象类
public class RealSubject implements Subject {

    @Override
    public void house() {
        System.out.println("买家:筛选出想要的房子");
    }
}

// 3. 代理类
public class Proxy implements Subject {

    private RealSubject realSubject;


    @Override
    public void house() {
        if (realSubject == null) {
            realSubject = new RealSubject();
        }
        preRequest();
        realSubject.house();
        postRequest();
    }

    private void preRequest() {
        System.out.println("房产中介: 帮你筛选掉一些不符合要求的房子,只向你推荐合适的房源");
    }

    private void postRequest() {
        System.out.println("房产中介: 提供法律咨询、贷款协助等额外服务,帮助你更顺利地完成购房过程");
    }
}

// 4. 客户端代码
public class Client {
    public static void main(String[] args) {
        Subject proxy = new Proxy();
        proxy.house();
    }
}

2.2 代理模式的应用场景

  • 远程代理:代表一个位于不同地址空间的对象。
  • 虚拟代理:根据需要创建开销很大的对象。
  • 保护代理:控制对原始对象的访问,用于对象应该有不同的访问权限的情况。
  • 智能引用:在访问对象时执行额外的操作,例如计算引用次数。

2.3 代理模式的主要类型

代理模式通常可以分为两种主要类型:静态代理和动态代理。

静态代理(Static Proxy)

静态代理是指在编译时就已经确定的代理类。静态代理类需要手动编写,它实现了与目标对象相同的接口,并在代理类中持有一个目标对象的引用。

静态代理的优点是实现简单,容易理解,但缺点是每当需要代理一个新的接口或类时,都需要手动创建一个新的代理类,这会导致代码冗余和维护成本增加。

代理模式的默认实现通常是静态代理,因为静态代理是最直观和最容易理解的方式:

java 复制代码
// 1. 主接口
public interface Subject {
    void hourse();
}


// 2. 真实对象类
public class RealSubject implements Subject {
    @Override
    public void hourse() {
        System.out.println("买家:筛选出想要的房子");
    }
}

// 3. 代理类
public class Proxy implements Subject {
    private RealSubject realSubject;


    @Override
    public void hourse() {
        if (realSubject == null) {
            realSubject = new RealSubject();
        }
        preRequest();
        realSubject.hourse();
        postRequest();
    }

    private void preRequest() {
        System.out.println("房产中介: 帮你筛选掉一些不符合要求的房子,只向你推荐合适的房源");
    }

    private void postRequest() {
        System.out.println("房产中介: 提供法律咨询、贷款协助等额外服务,帮助你更顺利地完成购房过程");
    }
}

// 4. 客户端代码
public class Client {
    public static void main(String[] args) {
        Subject proxy = new Proxy();
        proxy.hourse();
    }
}

动态代理(Dynamic Proxy)

动态代理是指在运行时动态生成的代理类。动态代理不需要手动编写代理类,而是通过Java的反射机制在运行时创建代理对象。

动态代理的优点是灵活性高,可以为任意接口或类创建代理对象,而无需手动编写代理类。

Java提供了两种动态代理的实现方式:基于接口的动态代理(使用java.lang.reflect.Proxy类)和基于子类的动态代理(使用CGLIB库)。

我们还是基于买房卖房中介的场景,假设我们有一个房地产中介系统,其中有两个角色:买家和卖家。买家和卖家都可以通过中介进行交易。我们希望在交易过程中添加一些额外的功能,比如记录交易日志、检查交易资格等。

1. 基于接口的动态代理(买家)

基于接口的动态代理是Java标准库中提供的实现方式,使用java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口。

java 复制代码
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public interface Buyer {
    void buyHouse(String houseId);
}


public class RealBuyer implements Buyer {
    @Override
    public void buyHouse(String houseId) {
        System.out.println("买方:需要买的房子的ID是: " + houseId);
    }
}


public class BuyerInvocationHandler implements InvocationHandler {
    private Object realBuyer;

    public BuyerInvocationHandler(Object realBuyer) {
        this.realBuyer = realBuyer;
    }

    @Override
    public Object invoke(Object obj, Method method, Object[] args) throws Throwable {
        System.out.println("买方中介BuyerInvocationHandler:检查买方资格");
        Object result = method.invoke(realBuyer, args);
        System.out.println("买方中介BuyerInvocationHandler:日志记录买方的需求");
        return result;
    }
}

2. 基于子类的动态代理(卖家)

基于子类的动态代理通常使用第三方库,如CGLIB(Code Generation Library)。CGLIB通过生成目标类的子类来实现代理。

java 复制代码
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class Seller {
    public void sellHouse(String houseId) {
        System.out.println("卖家: 需要售卖的房子ID: " + houseId);
    }
}


public class SellerMethodInterceptor implements MethodInterceptor {

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("卖方中介SellerMethodInterceptor:检查卖方资格");
        Object result = methodProxy.invokeSuper(obj, args);
        System.out.println("卖方中介SellerMethodInterceptor:日志记录卖方的需求");
        return result;
    }
}

3. 这样,买方和卖方和中介(代理类)的关系就串起来了,形成一个完整的房地产中介系统

java 复制代码
public class Client {
    public static void main(String[] args) {
        // 买家代理
        RealBuyer realBuyer = new RealBuyer();
        InvocationHandler buyerHandler = new BuyerInvocationHandler(realBuyer);
        Buyer buyerProxy = (Buyer) Proxy.newProxyInstance(
                realBuyer.getClass().getClassLoader(),
                realBuyer.getClass().getInterfaces(),
                buyerHandler);
        buyerProxy.buyHouse("123");

        // 卖家代理
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Seller.class);
        enhancer.setCallback(new SellerMethodInterceptor());
        Seller sellerProxy = (Seller) enhancer.create();
        sellerProxy.sellHouse("456");
    }
}

3. Spring框架中代理模式实现AOP

在Spring框架中,代理模式被广泛应用于AOP(面向切面编程)。在此之前,先简单的过一下什么是AOP编程吧,好有个清晰的认知。

3.1 何为面向切面编程?

AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,旨在通过允许程序员模块化横切关注点(cross-cutting concerns)来提高代码的模块性。横切关注点是指那些影响多个模块的功能,如日志记录、事务管理、安全性检查等,这些功能通常会跨越多个模块,导致代码重复和耦合度增加。

3.2 AOP的核心概念

AOP通过以下几个核心概念来实现横切关注点的模块化:

  1. 切面(Aspect):一个模块化的横切关注点。切面可以包含多个通知(advice)和切入点(pointcut)。

  2. 通知(Advice):定义了切面在特定连接点(join point)上执行的动作。通知有多种类型,如前置通知(before advice)、后置通知(after advice)、环绕通知(around advice)等。

  3. 切入点(Pointcut):定义了通知应该应用的连接点的集合。切入点通过匹配特定的方法或代码位置来确定通知的执行时机。

  4. 连接点(Join Point):程序执行过程中的一个特定点,如方法调用、异常抛出等。在AOP中,连接点是通知可以插入的地方。

  5. 引入(Introduction):允许向现有类添加新的方法或字段,从而在不修改现有代码的情况下扩展类的功能。

  6. 目标对象(Target Object):包含连接点的对象,也就是被代理的对象。

  7. 代理(Proxy):在目标对象上应用切面后创建的对象。代理对象负责在调用目标对象的方法时插入通知。

3.3 AOP的主要优点包括

代码重用: 通过将横切关注点模块化为切面,可以在多个模块中重用这些功能。
降低耦合度: 将横切关注点从业务逻辑中分离出来,降低了代码的耦合度。
**提高可维护性:**模块化的横切关注点使得代码更易于理解和维护。

3.4 AOP的实现流程

Spring AOP支持两种类型的代理:基于接口的动态代理和基于子类的动态代理(CGLIB)。

我们依旧以买家卖家和中介为例,分为四步实现:

  1. 定义买卖双方的接口和各自的实现类

  2. 创建一个切面类

  3. 配置Spring上下文

  4. 在客户端测试代理模式是否生效

首先,我们定义买家和卖家接口及其实现类:

java 复制代码
public interface Buyer {
    void buyHouse(String houseId);
}

public class RealBuyer implements Buyer {
    @Override
    public void buyHouse(String houseId) {
        System.out.println("买方:需要买的房子的ID是: " + houseId);
    }
}

public interface Seller {
    void sellHouse(String houseId);
}

public class RealSeller implements Seller {
    @Override
    public void sellHouse(String houseId) {
        System.out.println("卖家: 需要售卖的房子ID: " + houseId);
    }
}

接下来,我们创建切面类,用于在买家和卖家操作前后添加额外的逻辑:

java 复制代码
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.After;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class TransactionAspect {

    @Before("execution(* Buyer.buyHouse(..)) || execution(* Seller.sellHouse(..))")
    public void beforeTransaction() {
        System.out.println("TransactionAspect: 检查是否有交易资格");
    }

    @After("execution(* Buyer.buyHouse(..)) || execution(* Seller.sellHouse(..))")
    public void afterTransaction() {
        System.out.println("TransactionAspect: 日志记录交易双方的需求");
    }
}

我们需要配置Spring上下文,启用AOP和组件扫描:

java 复制代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {

    @Bean
    public Buyer buyer() {
        return new RealBuyer();
    }

    @Bean
    public Seller seller() {
        return new RealSeller();
    }

    @Bean
    public TransactionAspect transactionAspect() {
        return new TransactionAspect();
    }
}

最后,我们编写一个测试类来验证代理模式是否生效:

java 复制代码
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Client {

    public static void main(String[] args) {

        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

        Buyer buyer = context.getBean(Buyer.class);
        Seller seller = context.getBean(Seller.class);

        buyer.buyHouse("123");
        seller.sellHouse("456");
    }
}

虽然Spring AOP默认使用JDK动态代理来实现AOP,但也可以通过配置强制使用CGLIB来实现。

依旧以买家卖家和中介为例,分为四步实现:

  1. 配置Spring上下文

  2. 定义切面类

  3. 定义目标类

  4. 在客户端测试代理模式是否生效

首先,我们需要在Spring配置类中启用AOP并设置proxyTargetClass属性为true,以强制使用CGLIB代理。

java 复制代码
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AppConfig {
    // 其他配置
}

接下来,我们创建一个切面类,并使用@Aspect注解标记。这个切面类将包含在买卖双方操作前后执行的通知。

java 复制代码
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.After;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class TransactionAspect {

    @Before("execution(* Buyer.buyHouse(..)) || execution(* Seller.sellHouse(..))")
    public void beforeTransaction() {
        System.out.println("TransactionAspect: 检查是否有交易资格");
    }

    @After("execution(* Buyer.buyHouse(..)) || execution(* Seller.sellHouse(..))")
    public void afterTransaction() {
        System.out.println("TransactionAspect: 日志记录交易双方的需求");
    }
}

定义买家和卖家类,这些类不需要实现任何接口。

java 复制代码
package com.example.service;

import org.springframework.stereotype.Service;

@Service
public class Buyer {

    public void buyHouse(String houseId) {
        System.out.println("买方:需要买的房子的ID是: " + houseId);
    }
}

@Service
public class Seller {

    public void sellHouse(String houseId) {
        System.out.println("卖家: 需要售卖的房子ID: " + houseId);
    }
}

最后,我们编写一个测试类来验证CGLIB代理是否生效。

java 复制代码
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.example.service.Buyer;
import com.example.service.Seller;

public class Client {

    public static void main(String[] args) {

        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

        Buyer buyer = context.getBean(Buyer.class);
        Seller seller = context.getBean(Seller.class);

        buyer.buyHouse("123");
        seller.sellHouse("456");
    }
}

4. 总结

讲了这么多,我们也可以看出来代理模式是一种强大的设计模式,它通过引入代理对象来控制对目标对象的访问,并可以在不改变目标对象的情况下添加额外的功能。代理模式在许多框架和库中得到了广泛应用,如Spring AOP、Hibernate等。通过合理使用代理模式,可以提高代码的模块化、可维护性和灵活性。

相关推荐
肘击鸣的百k路20 小时前
Java 代理模式详解
java·开发语言·代理模式
OkeyProxy1 天前
怎麼解決IP地址衝突的問題?
代理模式·proxy模式·ip地址·代理服务器·海外ip代理
G皮T4 天前
【设计模式】结构型模式(二):代理模式
java·设计模式·编程·代理模式·proxy pattern·结构型模式
zzzhpzhpzzz5 天前
设计模式——代理模式
设计模式·系统安全·代理模式
羽愿5 天前
技术总结(十九)
代理模式
__Black_Opium__5 天前
设计模式 - 代理模式
设计模式·代理模式
OkeyProxy6 天前
路由器中怎麼設置代理IP?
代理模式·路由器·proxy模式·代理服务器·海外ip代理
decode127 天前
代码随想录算法训练营第三十二天 | 动态规划理论基础 509.斐波那契数 70.爬楼梯 746.使用最小花费爬楼梯
算法·动态规划·代理模式
收破烂的小熊猫~7 天前
动态规划(基础版)70.爬楼梯
算法·动态规划·代理模式
金池尽干7 天前
设计模式之——代理模式
c++·设计模式·代理模式