创建简单的映射器代理工厂 一、前言 着急和快,是最大的障碍!
慢下来,慢下来,只有慢下来,你才能看到更全的信息,才能学到更扎实的技术。而那些满足你快的短篇内容虽然有时候更抓眼球,但也容易把人在技术学习上带偏,总想着越快越好。
二、设计 在设计一个ORM框架的过程中,首先要考虑怎么把用户定义的数据库操作接口,xml配置的sql语句、数据库三者联系起来。
所以可以使用代理,封装一个复杂的流程为接口对象的实现类。
首先提供一个映射器的代理实现类 MapperProxy
,通过代理类包装对数据库的操作,目前我们本章节会先提供一个简单的包装,模拟对数据库的调用。 之后对 MapperProxy
代理类,提供工厂实例化操作 MapperProxyFactory#newInstance,为每个 IDAO 接口生成代理类。这块其实用到的就是一个简单工厂模式 三、实现 映射器代理类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public class MapperProxy <T > implements InvocationHandler , Serializable { private static final long serialVersionUID = 3754159323506184630L ; private Map<String, String> sqlSession; private final Class<T> mapperInterface; public MapperProxy (Map<String, String> 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()); } } }
通过实现 InvocationHandler#invoke 代理类接口,封装操作逻辑的方式,对外接口提供数据库操作对象。 目前我们这里只是简单的封装了一个 sqlSession 的 Map 对象,可以想象成所有的数据库语句操作,都是通过接口名称+方法名称作为key
,操作作为逻辑的方式进行使用的。那么在反射调用中则获取对应的操作直接执行并返回结果即可。 另外这里要注意如果是 Object 提供的 toString、hashCode 等方法是不需要代理执行的,所以添加 Object.class.equals(method.getDeclaringClass())
判断。 代理类工厂
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class MapperProxyFactory <T > { private final Class<T> mapperInterface; public MapperProxyFactory (Class<T> mapperInterface) { this .mapperInterface = mapperInterface; } public T newInstance (Map<String, String> sqlSession) { final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface); return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy); } }
工厂操作相当于把代理的创建给封装起来了,如果不做这层封装,那么每一个创建代理类的操作,都需要自己使用 Proxy.newProxyInstance
进行处理,那么这样的操作方式就显得比较麻烦了。 测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class ApiTest { private Logger logger = LoggerFactory.getLogger(ApiTest.class); @Test public void test_MapperProxyFactory () { MapperProxyFactory<IUserDao> factory = new MapperProxyFactory<>(IUserDao.class); Map<String, String> sqlSession = new HashMap<>(); sqlSession.put("com.zzc.hankz.test.dao.IUserDao.queryUserName" , "模拟执行 Mapper.xml 中 SQL 语句的操作:查询用户姓名" ); sqlSession.put("com.zzc.hankz.test.dao.IUserDao.queryUserAge" , "模拟执行 Mapper.xml 中 SQL 语句的操作:查询用户年龄" ); IUserDao userDao = factory.newInstance(sqlSession); userDao.toString(); String res = userDao.queryUserName("10001" ); logger.info("测试结果:{}" , res); } }
1 2 3 4 5 6 7 8 9 10 public interface IUserDao { String queryUserName (String uid) ; Integer queryUserAge (String uid) ; }
1 16:11:43.067 [main] INFO com.zzc.hankz.ApiTest - 测试结果:你的被代理了!模拟执行 Mapper.xml 中 SQL 语句的操作:查询用户姓名
在单测中创建 MapperProxyFactory 工厂,并手动给 sqlSession Map 赋值,这里的赋值相当于模拟数据库中的操作。 接下来再把赋值信息传递给代理对象实例化操作,这样就可以在我们调用具体的 DAO 方法时从 sqlSession 中取值了。 总结 对 Mybatis 框架中的数据库 DAO 操作接口和映射器通过代理类的方式进行链接,这一步也是 ORM 框架里非常核心的部分。 在框架实现方面引入简单工厂模式包装代理类,屏蔽创建细节