Spring + 设计模式 (十二) 结构型 - 代理模式

51 阅读18分钟

代理模式

引言

代理模式是一种结构型设计模式,核心在于通过代理对象控制对目标对象的访问,为其提供额外的功能或限制,如权限检查、延迟加载或日志记录。它如同对象的“代言人”,在不修改原对象代码的前提下,优雅地扩展行为。代理模式强调控制与增强,特别适合需要隔离访问、优化性能或添加横切逻辑的场景,宛若为对象披上一层智能外衣。

实际开发中的用途

在实际开发中,代理模式广泛应用于权限控制、远程调用、缓存管理及性能优化。例如,在Web应用中,代理可用于拦截请求以验证用户权限;在分布式系统中,代理可封装远程服务调用,屏蔽网络细节;而在ORM框架中,代理实现延迟加载,减少数据库查询开销。代理模式通过解耦客户端与目标对象,降低了直接访问的复杂性,提升了系统的安全性、可维护性和性能,特别适合企业级应用的模块化设计。

Spring 源码中的应用

Spring 框架中,代理模式在 AOP(面向切面编程) 的实现中体现得淋漓尽致。Spring AOP 通过动态代理(基于 JDK 动态代理或 CGLIB)为目标 Bean 添加横切逻辑(如日志、事务管理),无需修改原始代码。核心类 ProxyFactoryBeanDefaultAopProxyFactory 负责创建代理对象,封装切面逻辑。

以下是 Spring 源码的典型片段(DefaultAopProxyFactory.java):

// Spring 框架中的 DefaultAopProxyFactory
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {

    @Override
    public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
        if (!config.isOptimize() && !config.isProxyTargetClass() && !hasNoUserSuppliedProxyInterfaces(config)) {
            // 使用 JDK 动态代理(基于接口)
            return new JdkDynamicAopProxy(config);
        } else {
            // 使用 CGLIB 代理(基于类)
            Class<?> targetClass = config.getTargetClass();
            if (targetClass == null) {
                throw new AopConfigException("TargetSource cannot determine target class: Either an interface or a target is required for proxy creation.");
            }
            return new ObjenesisCglibAopProxy(config);
        }
    }

    private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {
        // 检查是否无用户提供的代理接口
        return config.getProxiedInterfaces().length == 0;
    }
}

这段代码展示了 Spring AOP 如何通过代理模式创建代理对象。详细分析如下:

  1. 代理模式的体现
    • DefaultAopProxyFactory 决定使用 JDK 动态代理(基于接口)或 CGLIB 代理(基于类)创建代理对象,代理对象包装目标 Bean,拦截方法调用以应用切面逻辑(如事务、日志)。
    • 客户端通过代理对象调用目标方法,无需感知切面逻辑,符合代理模式的“控制访问”目标。
  2. 关键方法解析
    • createAopProxy 根据配置选择代理类型(JDK 或 CGLIB)。JDK 动态代理适用于实现接口的 Bean,CGLIB 适用于无接口的类。
    • 代理对象内部持有目标对象引用(通过 AdvisedSupport 配置),并在方法调用时插入切面逻辑(如 MethodInterceptor)。
  3. 解耦与扩展性
    • 代理模式解耦了客户端与目标 Bean,切面逻辑(如事务管理)与业务逻辑分离,遵循“开闭原则”。
    • Spring AOP 支持动态添加或移除切面,客户端代码无需修改即可启用新功能(如性能监控)。
  4. 实际问题解决
    • 解决了横切关注点(如日志、事务)的代码分散问题,通过代理集中管理,提升代码复用性。
    • 支持延迟加载(如 Spring 的 @Lazy 注解),通过代理延迟目标 Bean 的初始化,优化启动性能。

ProxyFactoryBeanDefaultAopProxyFactory 的实现使 Spring AOP 成为企业级应用的核心组件,广泛用于事务管理、日志记录和安全控制,显著提升了代码的模块化与可维护性。

代理模式在 MyBatis 中的用途

MyBatis 是一个轻量级持久化框架,其核心优势在于通过 Mapper 接口以声明式方式操作数据库。代理模式在 MyBatis 中用于为 Mapper 接口动态生成代理对象,这些代理对象拦截接口方法调用,自动完成 SQL 查询、参数绑定和结果映射。代理模式如同 MyBatis 的“幕后导演”,隐藏了复杂的 JDBC 操作,让开发者专注于业务逻辑,显著提升开发效率和代码简洁性。

在 MyBatis 中,代理模式主要解决以下问题:

  1. 简化数据库操作:开发者无需手动编写 JDBC 代码(如获取连接、执行语句、处理结果集),只需定义 Mapper 接口和对应的 SQL 语句。
  2. 解耦业务与持久化逻辑:通过代理,业务代码仅依赖接口,无需关心底层 SQL 执行或数据库驱动。
  3. 动态 SQL 执行:代理对象根据接口方法和 XML/注解配置,动态解析 SQL、绑定参数并映射结果。
  4. 扩展性与可维护性:代理模式支持热加载配置、插件拦截等功能,便于扩展(如分页、日志)。

例如,在一个用户管理模块中,开发者只需定义 UserMapper 接口和方法(如 selectById),MyBatis 的代理机制自动将方法调用转换为 SQL 执行,屏蔽了底层的复杂性。

MyBatis 中代理模式的实现原理

MyBatis 使用 JDK 动态代理 为 Mapper 接口生成实现。核心流程如下:

  1. Mapper 接口注册:MyBatis 在启动时扫描 Mapper 接口(如 UserMapper),将其注册到 MapperRegistry
  2. 代理对象创建:通过 MapperProxyFactory 为每个 Mapper 接口生成代理对象,代理对象实现接口并拦截方法调用。
  3. 方法拦截:代理对象将方法调用委托给 MapperProxy,后者根据方法签名查找对应的 MappedStatement(SQL 配置),执行 SQL 并处理结果。
  4. SQL 执行与结果映射:通过 SqlSession 执行 SQL,结合 ResultMap 将查询结果映射为 Java 对象。

以下从源码角度详细分析。

源码分析:MyBatis 代理模式的实现

MyBatis 的代理模式核心逻辑集中在 org.apache.ibatis.binding 包中,以下是关键类和方法的源码分析(基于 MyBatis 3.5.x)。

1. Mapper 接口注册

MyBatis 在启动时通过 Configuration 加载 Mapper 接口,注册到 MapperRegistry。以下是 MapperRegistry 的关键代码:

// org.apache.ibatis.binding.MapperRegistry
public class MapperRegistry {
    private final Configuration config;
    private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();

    public MapperRegistry(Configuration config) {
        this.config = config;
    }

    // 注册 Mapper 接口
    public <T> void addMapper(Class<T> type) {
        if (type.isInterface()) {
            if (hasMapper(type)) {
                throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
            }
            boolean loadCompleted = false;
            try {
                knownMappers.put(type, new MapperProxyFactory<>(type));
                // 解析 Mapper 注解或 XML 配置
                MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
                parser.parse();
                loadCompleted = true;
            } finally {
                if (!loadCompleted) {
                    knownMappers.remove(type);
                }
            }
        }
    }

    // 获取 Mapper 代理对象
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
        if (mapperProxyFactory == null) {
            throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        }
        try {
            return mapperProxyFactory.newInstance(sqlSession);
        } catch (Exception e) {
            throw new BindingException("Error getting mapper instance. Cause: " + e, e);
        }
    }
}

分析

  • addMapper 将 Mapper 接口注册到 knownMappers,创建对应的 MapperProxyFactory
  • MapperAnnotationBuilder 解析接口方法上的注解(如 @Select)或 XML 配置(如 select 标签),生成 MappedStatement
  • getMapper 通过 MapperProxyFactory 创建代理对象,供客户端使用。
2. 代理对象生成

MapperProxyFactory 负责生成 Mapper 接口的代理对象,核心方法是 newInstance

// org.apache.ibatis.binding.MapperProxyFactory
public class MapperProxyFactory<T> {
    private final Class<T> mapperInterface;

    public MapperProxyFactory(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    @SuppressWarnings("unchecked")
    protected T newInstance(MapperProxy<T> mapperProxy) {
        // 使用 JDK 动态代理生成代理对象
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(),
                new Class[] { mapperInterface }, mapperProxy);
    }

    public T newInstance(SqlSession sqlSession) {
        // 创建 MapperProxy 作为 InvocationHandler
        final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface);
        return newInstance(mapperProxy);
    }
}

分析

  • newInstance 使用 java.lang.reflect.Proxy 创建 JDK 动态代理,代理对象实现 mapperInterface(如 UserMapper)。
  • MapperProxy 作为 InvocationHandler,处理所有方法调用。
3. 方法拦截与执行

MapperProxy 是代理模式的核心,拦截 Mapper 接口方法并执行 SQL:

// org.apache.ibatis.binding.MapperProxy
public class MapperProxy<T> implements InvocationHandler, Serializable {
    private final SqlSession sqlSession;
    private final Class<T> mapperInterface;
    private final Map<Method, MapperMethodInvoker> methodCache;

    public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface) {
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
        this.methodCache = new ConcurrentHashMap<>();
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 处理 Object 方法(如 toString)
        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
        }
        // 获取或缓存 MapperMethodInvoker
        final MapperMethodInvoker invoker = cachedInvoker(method);
        return invoker.invoke(proxy, method, args, sqlSession);
    }

    private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
        return methodCache.computeIfAbsent(method, m -> {
            // 创建 PlainMethodInvoker 或其他类型
            return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
        });
    }
}

分析

  • invoke 拦截 Mapper 接口方法调用,委托给 MapperMethodInvoker
  • MapperMethod 封装方法签名与 MappedStatement 的映射,负责解析 SQL、绑定参数和执行查询。
  • methodCache 缓存方法调用逻辑,提升性能。
4. SQL 执行与结果映射

MapperMethodexecute 方法协调 SQL 执行:

// org.apache.ibatis.binding.MapperMethod
public class MapperMethod {
    private final SqlCommand command;
    private final MethodSignature method;

    public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
        this.command = new SqlCommand(config, mapperInterface, method);
        this.method = new MethodSignature(config, mapperInterface, method);
    }

    public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        switch (command.getType()) {
            case SELECT:
                if (method.returnsVoid() && method.hasResultHandler()) {
                    executeWithResultHandler(sqlSession, args);
                    result = null;
                } else if (method.returnsMany()) {
                    result = executeForMany(sqlSession, args);
                } else {
                    result = executeForOne(sqlSession, args);
                }
                break;
            case INSERT:
                result = executeForUpdate(sqlSession, args);
                break;
            // 其他 case(如 UPDATE、DELETE)
            default:
                throw new BindingException("Unknown execution method for: " + command.getName());
        }
        return result;
    }
}

分析

  • SqlCommandConfiguration 获取 MappedStatement,包含 SQL 和参数配置。
  • execute 根据方法类型(SELECTINSERT 等)调用 SqlSession 的相应方法(如 selectListinsert)。
  • 结果映射通过 ResultMap 或实体类注解完成(如 DefaultResultSetHandler 处理)。

代码使用案例:MyBatis 代理模式应用

以下是一个基于 MyBatis 的案例,展示代理模式如何为 UserMapper 接口生成实现。

// User 实体类
public class User {
    private Long id;
    private String username;
    // Getter 和 Setter
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getUsername() { return username; }
    public void setUsername(String username) { this.username = username; }
}

// UserMapper 接口
public interface UserMapper {
    @Select("SELECT * FROM users WHERE id = #{id}")
    User selectById(Long id);
}

// MyBatis 配置和主程序
public class MyBatisDemo {
    public static void main(String[] args) {
        // 加载 MyBatis 配置文件
        String resource = "mybatis-config.xml";
        InputStream inputStream = MyBatisDemo.class.getClassLoader().getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        // 获取 SqlSession
        try (SqlSession session = sqlSessionFactory.openSession()) {
            // 获取 Mapper 代理对象
            UserMapper mapper = session.getMapper(UserMapper.class);
            // 调用代理方法
            User user = mapper.selectById(1L);
            System.out.println("查询用户: " + user.getUsername());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

mybatis-config.xml(简化的配置文件):

<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/test"/>
                <property name="username" value="root"/>
                <property name="password" value="password"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper class="com.example.UserMapper"/>
    </mappers>
</configuration>

案例说明

  • UserMapper 定义了 selectById 方法,通过 @Select 注解指定 SQL。
  • MyBatis 在启动时为 UserMapper 生成代理对象,拦截 selectById 调用。
  • 代理对象通过 SqlSession 执行 SQL,并将结果映射为 User 对象。
  • 客户端只需调用接口方法,无需关心 JDBC 或结果映射,体现了代理模式的透明性。

总结

代理模式是一种结构型设计模式,通过代理对象控制对目标对象的访问,在不修改原有代码的前提下添加横切逻辑、延迟加载或简化复杂操作。MyBatis 深度运用代理模式,通过 JDK 动态代理为核心 Mapper 接口生成动态实现,巧妙控制数据库访问,屏蔽 SQL 执行与结果映射的复杂性。代理模式在此如精密门卫,MapperProxy 和 MapperProxyFactory 协作拦截接口方法调用,动态映射至 SQL,赋予接口声明式的简洁性与执行的高效性。这种代理机制不仅简化开发,还确保高内聚与低耦合,极大提升框架扩展性。无论单机还是 Spring 集成,MyBatis 的代理模式都展现了优雅与灵活,堪称持久化框架典范,启发开发者在架构设计中活用代理模式打造健壮系统。# 代理模式

引言

代理模式(Proxy Pattern)是一种结构型设计模式,核心在于通过代理对象控制对目标对象的访问,为其提供额外的功能或限制,如权限检查、延迟加载或日志记录。它如同对象的“代言人”,在不修改原对象代码的前提下,优雅地扩展行为。代理模式强调控制与增强,特别适合需要隔离访问、优化性能或添加横切逻辑的场景,宛若为对象披上一层智能外衣。

实际开发中的用途

在实际开发中,代理模式广泛应用于权限控制、远程调用、缓存管理及性能优化。例如,在Web应用中,代理可用于拦截请求以验证用户权限;在分布式系统中,代理可封装远程服务调用,屏蔽网络细节;而在ORM框架中,代理实现延迟加载,减少数据库查询开销。代理模式通过解耦客户端与目标对象,降低了直接访问的复杂性,提升了系统的安全性、可维护性和性能,特别适合企业级应用的模块化设计。

Spring 源码中的应用

Spring 框架中,代理模式在 AOP(面向切面编程) 的实现中体现得淋漓尽致。Spring AOP 通过动态代理(基于 JDK 动态代理或 CGLIB)为目标 Bean 添加横切逻辑(如日志、事务管理),无需修改原始代码。核心类 ProxyFactoryBeanDefaultAopProxyFactory 负责创建代理对象,封装切面逻辑。

以下是 Spring 源码的典型片段(DefaultAopProxyFactory.java):

// Spring 框架中的 DefaultAopProxyFactory
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {

    @Override
    public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
        if (!config.isOptimize() && !config.isProxyTargetClass() && !hasNoUserSuppliedProxyInterfaces(config)) {
            // 使用 JDK 动态代理(基于接口)
            return new JdkDynamicAopProxy(config);
        } else {
            // 使用 CGLIB 代理(基于类)
            Class<?> targetClass = config.getTargetClass();
            if (targetClass == null) {
                throw new AopConfigException("TargetSource cannot determine target class: Either an interface or a target is required for proxy creation.");
            }
            return new ObjenesisCglibAopProxy(config);
        }
    }

    private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {
        // 检查是否无用户提供的代理接口
        return config.getProxiedInterfaces().length == 0;
    }
}

这段代码展示了 Spring AOP 如何通过代理模式创建代理对象。详细分析如下:

  1. 代理模式的体现
    • DefaultAopProxyFactory 决定使用 JDK 动态代理(基于接口)或 CGLIB 代理(基于类)创建代理对象,代理对象包装目标 Bean,拦截方法调用以应用切面逻辑(如事务、日志)。
    • 客户端通过代理对象调用目标方法,无需感知切面逻辑,符合代理模式的“控制访问”目标。
  2. 关键方法解析
    • createAopProxy 根据配置选择代理类型(JDK 或 CGLIB)。JDK 动态代理适用于实现接口的 Bean,CGLIB 适用于无接口的类。
    • 代理对象内部持有目标对象引用(通过 AdvisedSupport 配置),并在方法调用时插入切面逻辑(如 MethodInterceptor)。
  3. 解耦与扩展性
    • 代理模式解耦了客户端与目标 Bean,切面逻辑(如事务管理)与业务逻辑分离,遵循“开闭原则”。
    • Spring AOP 支持动态添加或移除切面,客户端代码无需修改即可启用新功能(如性能监控)。
  4. 实际问题解决
    • 解决了横切关注点(如日志、事务)的代码分散问题,通过代理集中管理,提升代码复用性。
    • 支持延迟加载(如 Spring 的 @Lazy 注解),通过代理延迟目标 Bean 的初始化,优化启动性能。

ProxyFactoryBeanDefaultAopProxyFactory 的实现使 Spring AOP 成为企业级应用的核心组件,广泛用于事务管理、日志记录和安全控制,显著提升了代码的模块化与可维护性。

代理模式在 MyBatis 中的用途

MyBatis 是一个轻量级持久化框架,其核心优势在于通过 Mapper 接口以声明式方式操作数据库。代理模式在 MyBatis 中用于为 Mapper 接口动态生成代理对象,这些代理对象拦截接口方法调用,自动完成 SQL 查询、参数绑定和结果映射。代理模式如同 MyBatis 的“幕后导演”,隐藏了复杂的 JDBC 操作,让开发者专注于业务逻辑,显著提升开发效率和代码简洁性。

在 MyBatis 中,代理模式主要解决以下问题:

  1. 简化数据库操作:开发者无需手动编写 JDBC 代码(如获取连接、执行语句、处理结果集),只需定义 Mapper 接口和对应的 SQL 语句。
  2. 解耦业务与持久化逻辑:通过代理,业务代码仅依赖接口,无需关心底层 SQL 执行或数据库驱动。
  3. 动态 SQL 执行:代理对象根据接口方法和 XML/注解配置,动态解析 SQL、绑定参数并映射结果。
  4. 扩展性与可维护性:代理模式支持热加载配置、插件拦截等功能,便于扩展(如分页、日志)。

例如,在一个用户管理模块中,开发者只需定义 UserMapper 接口和方法(如 selectById),MyBatis 的代理机制自动将方法调用转换为 SQL 执行,屏蔽了底层的复杂性。

MyBatis 中代理模式的实现原理

MyBatis 使用 JDK 动态代理 为 Mapper 接口生成实现。核心流程如下:

  1. Mapper 接口注册:MyBatis 在启动时扫描 Mapper 接口(如 UserMapper),将其注册到 MapperRegistry
  2. 代理对象创建:通过 MapperProxyFactory 为每个 Mapper 接口生成代理对象,代理对象实现接口并拦截方法调用。
  3. 方法拦截:代理对象将方法调用委托给 MapperProxy,后者根据方法签名查找对应的 MappedStatement(SQL 配置),执行 SQL 并处理结果。
  4. SQL 执行与结果映射:通过 SqlSession 执行 SQL,结合 ResultMap 将查询结果映射为 Java 对象。

以下从源码角度详细分析。

源码分析:MyBatis 代理模式的实现

MyBatis 的代理模式核心逻辑集中在 org.apache.ibatis.binding 包中,以下是关键类和方法的源码分析(基于 MyBatis 3.5.x)。

1. Mapper 接口注册

MyBatis 在启动时通过 Configuration 加载 Mapper 接口,注册到 MapperRegistry。以下是 MapperRegistry 的关键代码:

// org.apache.ibatis.binding.MapperRegistry
public class MapperRegistry {
    private final Configuration config;
    private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();

    public MapperRegistry(Configuration config) {
        this.config = config;
    }

    // 注册 Mapper 接口
    public <T> void addMapper(Class<T> type) {
        if (type.isInterface()) {
            if (hasMapper(type)) {
                throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
            }
            boolean loadCompleted = false;
            try {
                knownMappers.put(type, new MapperProxyFactory<>(type));
                // 解析 Mapper 注解或 XML 配置
                MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
                parser.parse();
                loadCompleted = true;
            } finally {
                if (!loadCompleted) {
                    knownMappers.remove(type);
                }
            }
        }
    }

    // 获取 Mapper 代理对象
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
        if (mapperProxyFactory == null) {
            throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        }
        try {
            return mapperProxyFactory.newInstance(sqlSession);
        } catch (Exception e) {
            throw new BindingException("Error getting mapper instance. Cause: " + e, e);
        }
    }
}

分析

  • addMapper 将 Mapper 接口注册到 knownMappers,创建对应的 MapperProxyFactory
  • MapperAnnotationBuilder 解析接口方法上的注解(如 @Select)或 XML 配置(如 select 标签),生成 MappedStatement
  • getMapper 通过 MapperProxyFactory 创建代理对象,供客户端使用。
2. 代理对象生成

MapperProxyFactory 负责生成 Mapper 接口的代理对象,核心方法是 newInstance

// org.apache.ibatis.binding.MapperProxyFactory
public class MapperProxyFactory<T> {
    private final Class<T> mapperInterface;

    public MapperProxyFactory(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    @SuppressWarnings("unchecked")
    protected T newInstance(MapperProxy<T> mapperProxy) {
        // 使用 JDK 动态代理生成代理对象
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(),
                new Class[] { mapperInterface }, mapperProxy);
    }

    public T newInstance(SqlSession sqlSession) {
        // 创建 MapperProxy 作为 InvocationHandler
        final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface);
        return newInstance(mapperProxy);
    }
}

分析

  • newInstance 使用 java.lang.reflect.Proxy 创建 JDK 动态代理,代理对象实现 mapperInterface(如 UserMapper)。
  • MapperProxy 作为 InvocationHandler,处理所有方法调用。
3. 方法拦截与执行

MapperProxy 是代理模式的核心,拦截 Mapper 接口方法并执行 SQL:

// org.apache.ibatis.binding.MapperProxy
public class MapperProxy<T> implements InvocationHandler, Serializable {
    private final SqlSession sqlSession;
    private final Class<T> mapperInterface;
    private final Map<Method, MapperMethodInvoker> methodCache;

    public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface) {
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
        this.methodCache = new ConcurrentHashMap<>();
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 处理 Object 方法(如 toString)
        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
        }
        // 获取或缓存 MapperMethodInvoker
        final MapperMethodInvoker invoker = cachedInvoker(method);
        return invoker.invoke(proxy, method, args, sqlSession);
    }

    private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
        return methodCache.computeIfAbsent(method, m -> {
            // 创建 PlainMethodInvoker 或其他类型
            return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
        });
    }
}

分析

  • invoke 拦截 Mapper 接口方法调用,委托给 MapperMethodInvoker
  • MapperMethod 封装方法签名与 MappedStatement 的映射,负责解析 SQL、绑定参数和执行查询。
  • methodCache 缓存方法调用逻辑,提升性能。
4. SQL 执行与结果映射

MapperMethodexecute 方法协调 SQL 执行:

// org.apache.ibatis.binding.MapperMethod
public class MapperMethod {
    private final SqlCommand command;
    private final MethodSignature method;

    public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
        this.command = new SqlCommand(config, mapperInterface, method);
        this.method = new MethodSignature(config, mapperInterface, method);
    }

    public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        switch (command.getType()) {
            case SELECT:
                if (method.returnsVoid() && method.hasResultHandler()) {
                    executeWithResultHandler(sqlSession, args);
                    result = null;
                } else if (method.returnsMany()) {
                    result = executeForMany(sqlSession, args);
                } else {
                    result = executeForOne(sqlSession, args);
                }
                break;
            case INSERT:
                result = executeForUpdate(sqlSession, args);
                break;
            // 其他 case(如 UPDATE、DELETE)
            default:
                throw new BindingException("Unknown execution method for: " + command.getName());
        }
        return result;
    }
}

分析

  • SqlCommandConfiguration 获取 MappedStatement,包含 SQL 和参数配置。
  • execute 根据方法类型(SELECTINSERT 等)调用 SqlSession 的相应方法(如 selectListinsert)。
  • 结果映射通过 ResultMap 或实体类注解完成(如 DefaultResultSetHandler 处理)。

代码使用案例:MyBatis 代理模式应用

以下是一个基于 MyBatis 的案例,展示代理模式如何为 UserMapper 接口生成实现。

// User 实体类
public class User {
    private Long id;
    private String username;
    // Getter 和 Setter
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getUsername() { return username; }
    public void setUsername(String username) { this.username = username; }
}

// UserMapper 接口
public interface UserMapper {
    @Select("SELECT * FROM users WHERE id = #{id}")
    User selectById(Long id);
}

// MyBatis 配置和主程序
public class MyBatisDemo {
    public static void main(String[] args) {
        // 加载 MyBatis 配置文件
        String resource = "mybatis-config.xml";
        InputStream inputStream = MyBatisDemo.class.getClassLoader().getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        // 获取 SqlSession
        try (SqlSession session = sqlSessionFactory.openSession()) {
            // 获取 Mapper 代理对象
            UserMapper mapper = session.getMapper(UserMapper.class);
            // 调用代理方法
            User user = mapper.selectById(1L);
            System.out.println("查询用户: " + user.getUsername());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

mybatis-config.xml(简化的配置文件):

<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/test"/>
                <property name="username" value="root"/>
                <property name="password" value="password"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper class="com.example.UserMapper"/>
    </mappers>
</configuration>

案例说明

  • UserMapper 定义了 selectById 方法,通过 @Select 注解指定 SQL。
  • MyBatis 在启动时为 UserMapper 生成代理对象,拦截 selectById 调用。
  • 代理对象通过 SqlSession 执行 SQL,并将结果映射为 User 对象。
  • 客户端只需调用接口方法,无需关心 JDBC 或结果映射,体现了代理模式的透明性。

总结

代理模式是一种结构型设计模式,通过代理对象控制对目标对象的访问,在不修改原有代码的前提下添加横切逻辑、延迟加载或简化复杂操作。MyBatis 深度运用代理模式,通过 JDK 动态代理为核心 Mapper 接口生成动态实现,巧妙控制数据库访问,屏蔽 SQL 执行与结果映射的复杂性。代理模式在此如精密门卫,MapperProxy 和 MapperProxyFactory 协作拦截接口方法调用,动态映射至 SQL,赋予接口声明式的简洁性与执行的高效性。这种代理机制不仅简化开发,还确保高内聚与低耦合,极大提升框架扩展性。无论单机还是 Spring 集成,MyBatis 的代理模式都展现了优雅与灵活,堪称持久化框架典范,启发开发者在架构设计中活用代理模式打造健壮系统。

(对您有帮助 && 觉得我总结的还行) -> 受累点个免费的赞👍,谢谢