文章目录
第2章 创建简单的映射器代理工厂
背景
代理模式(Proxy Pattern)是一种设计模式,它为其他对象提供一个代理或占位符以控制对这个对象的访问。在Java中,代理模式可以通过静态代理和动态代理两种方式实现。
静态代理
静态代理在编译时就已经确定代理类和目标类的关系。代理类和目标类通常实现相同的接口。
步骤:
- 定义主题接口:代理类和目标类都实现这个接口。
- 创建目标类:实现主题接口的具体业务逻辑。
- 创建代理类:实现主题接口,并在内部持有目标类的引用,增加额外的处理。
- 客户端代码:使用代理类来访问目标对象。
示例代码:
// 主题接口
public interface Subject {
void request();
}
// 真实主题
public class RealSubject implements Subject {
public void request() {
System.out.println("RealSubject: Handling request.");
}
}
// 代理类
public class Proxy implements Subject {
private RealSubject realSubject;
public Proxy() {
this.realSubject = new RealSubject();
}
public void request() {
preRequest();
realSubject.request();
postRequest();
}
private void preRequest() {
System.out.println("Proxy: Logging the time of request.");
}
private void postRequest() {
System.out.println("Proxy: Logging the time of request completion.");
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Subject proxy = new Proxy();
proxy.request();
}
}
动态代理(JDK Proxy)
动态代理在运行时动态创建代理类,代理类不需要与目标类实现相同的接口,而是通过反射机制来调用目标对象的方法。
步骤:
- 创建目标接口:代理类和目标类都实现这个接口。
- 创建目标类:实现目标接口的具体业务逻辑。
- 创建代理工厂:使用
java.lang.reflect.Proxy
类和java.lang.reflect.InvocationHandler
接口来创建代理对象。 - 客户端代码:使用代理对象来访问目标对象。
示例代码:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 目标接口
public interface Subject {
void request();
}
// 真实主题
public class RealSubject implements Subject {
public void request() {
System.out.println("RealSubject: Handling request.");
}
}
// 代理工厂
public class ProxyFactory implements InvocationHandler {
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
public Object getProxyInstance() {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before the request.");
Object result = method.invoke(target, args);
System.out.println("After the request.");
return result;
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
RealSubject realSubject = new RealSubject();
ProxyFactory factory = new ProxyFactory(realSubject);
Subject proxyInstance = (Subject) factory.getProxyInstance();
proxyInstance.request();
}
}
在动态代理中,InvocationHandler
的invoke
方法会在目标对象的方法被调用之前和之后执行,允许在目标方法执行前后添加额外的处理逻辑。这种方式使得代理模式的应用更加灵活,不需要在编译时就确定代理类。
目标
Mybatis ORM框架的实现,首先解决第一个问题:通过代理方式实现数据库操作接口IXxxDao和映射器mapper的关联。
设计
设计一个 ORM 框架的过程,需要要考虑怎么把用户定义的数据库操作接口
、xml配置的SQL语句
、数据库
三者联系起来。这里使用代理的方式进行处理,实现数据库操作接口和映射器的关联。因为代理可以封装一个复杂的流程为接口对象的实现类,设计如图 :
- 首先提供一个映射器的代理实现类
MapperProxy
,通过代理类包装对数据库的操作,目前我们本章节会先提供一个简单的包装,模拟对数据库的调用。 - 之后对
MapperProxy
代理类,提供工厂实例化操作 MapperProxyFactory#newInstance,为每个 IDAO 接口生成代理类。这块其实用到的就是一个简单工厂模式
实现
工程代码

类图

- MapperProxy 负责实现 InvocationHandler 接口的 invoke 方法,最终所有的实际调用都会调用到这个方法包装的逻辑。
- MapperProxyFactory 是对 MapperProxy 的包装,对外提供实例化对象的操作。当我们后面开始给每个操作数据库的接口映射器注册代理的时候,就需要使用到这个工厂类了。
实现步骤
1.映射器代理类MapperProxy
- 通过实现 InvocationHandler#invoke 代理类接口,封装操作逻辑,对外接口提供数据库操作对象。
- 目前只是简单的封装了一个 sqlSession 的 Map 对象,你可以想象成所有的数据库语句操作,都是通过
接口名称+方法名称作为key
,操作作为值的方式进行使用的。那么在反射调用中则获取对应的操作直接执行并返回结果即可。当然这还只是最核心的简化流程,后续不断补充内容后,会看到对数据库的操作 - 另外这里要注意如果是 Object 提供的 toString、hashCode 等方法是不需要代理执行的,所以添加
Object.class.equals(method.getDeclaringClass())
判断。
public class MapperProxy<T> implements InvocationHandler, Serializable {
// sql会话.
private Map<String, Object> sqlSession;
// 映射器接口.
private final Class<T> mapperInterface;
public MapperProxy(Map<String,Object> sqlSession, Class<T> mapperInterface){
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(Object.class.equals(method.getDeclaringClass())){
return method.invoke(this,args);
} else {
return sqlSession.get(mapperInterface.getName()+"."+method.getName());
}
}
}
2.代理类工厂MapperProxyFactory
- 工厂操作相当于把代理的创建给封装起来了,如果不做这层封装,那么每一个创建代理类的操作,都需要自己使用
Proxy.newProxyInstance
进行处理,那么这样的操作方式就显得比较麻烦了。
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
public MapperProxyFactory(Class<T> mapperInterface){
this.mapperInterface = mapperInterface;
}
// 创建代理对象.
public T newInstance(Map<String, Object> sqlSession){
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface);
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);
}
}
测试
事先准备
定义一个数据库接口 IUserDao
IUserDao
public interface IUserDao {
String queryUserName(String uid);
Integer queryUserAge(String uid);
}
测试用例
public class ApiTest {
private Logger logger = LoggerFactory.getLogger(ApiTest.class);
// 测试mapperProxyFactory
@Test
public void testMapperProxyFactory(){
//创建MapperProxyFactory
MapperProxyFactory<IUserDao> mapperProxyFactory = new MapperProxyFactory<>(IUserDao.class);
//模拟sqlSession, 方法名和方法调用响应.
Map<String, Object> sqlSession = new HashMap<>();
sqlSession.put("cn.suwg.mybatis.test.dao.IUserDao.queryUserName", "模拟执行 Mapper.xml 中 SQL 语句的操作:查询用户姓名:小苏");
sqlSession.put("cn.suwg.mybatis.test.dao.IUserDao.queryUserAge", 28);
IUserDao userDao = mapperProxyFactory.newInstance(sqlSession);
//调用方法
String result = userDao.queryUserName("10001");
Integer age = userDao.queryUserAge("10001");
logger.info("测试结果:{}, 年龄:{}",result, age);
}
}
测试结果
从输出的结果来看,我们的接口已经被代理类实现了,可以在代理类中进行自己的操作封装。那么在我们后续实现的数据库操作中,就可以对这部分内容进行扩展了。
总结
- 本章节我们初步对 Mybatis 框架中的
数据库 DAO 操作接口和映射器通过代理类的方式进行链接
,这一步也是 ORM 框架里非常核心的部分。后续就可以在代理类中进行自定义的逻辑扩展了。 - 在框架实现方面引入简单工厂模式包装代理类,屏蔽创建细节,这个设计模式的使用,也可以迁移到大家的日常开发中。
- 目前内容还比较简单的,可以手动操作练习,随着我们内容的增加,会有越来越多的包和类引入,完善 ORM 框架功能。
参考书籍:《手写Mybatis渐进式源码实践》
书籍源代码:https://github.com/fuzhengwei/book-small-mybatis