深化代理模式,Mybaits如何做到调用接口
Mybaits如何做到调用接口?
毫无疑问,动态代理,本文将透过一些源码深化代理模式
1.源码篇
1)生成代理
先上测试代码,相关包扫描和Mybaits的配置文件都已经写好了
public class MapperTest {
@Test
public void testMapper() throws IOException, MyException {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:/spring/applicationContext-dao.xml");
SqlSessionFactory factory = (SqlSessionFactory) context.getBean("sqlSessionFactory");
//可DEBUG这一行,这里创建并返回了Mapper的代理
TestMapper mapper = factory.openSession().getMapper(TbItemMapper.class);
//发送一个查询请求,这一行揭示了如何做到调用接口
mapper.selectByExample(new TestMapper ());
}
}
这里不必细看,知道在哪进入即可
public class MapperRegistry {
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//检索Mapper是否注册代理工厂
MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
//进入这一行,就可以看到代理的产生
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception var5) {
throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
}
}
}
//此处省略其他代码
}
注意重载方法,该对象已经装配好了配置文件中的接口信息,还配置了一个缓存
- MapperProxy就是一个InvocationHandler
- 再用他生成真正的JDK代理对象并返回
至此动态代理对象已经生成
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private Map<Method, MapperMethod> methodCache = new ConcurrentHashMap();
protected T newInstance(MapperProxy<T> mapperProxy) {
//JDK代理
***return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);***
}
//DEBUG进入在这一行
public T newInstance(SqlSession sqlSession) {
MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
return this.newInstance(mapperProxy);
}
//此处省略其他代码
}
2)方法如何调用
invoke方法接收到方法调用,只做了两件事
- 创建并缓存MapperMethod类
- 调用MapperMethod.execute()方法
//实现了InvocationHandler
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//如果调用了Object类的方法
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
} else {
//缓存并创建一个MapperMethod对象,KEY method VALUE mapperMethod
MapperMethod mapperMethod = this.cachedMapperMethod(method);
//把session和方法参数委托给MapperMethod
return mapperMethod.execute(this.sqlSession, args);
}
}
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = (MapperMethod)this.methodCache.get(method);
if (mapperMethod == null) {
//用接口信息,方法信息,配置信息构造MapperMethod
mapperMethod = new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());
this.methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
}
MapperMethod 类做了什么?(指令模式?)
- 将方法调用信息包装成SqlCommand
- 将方法xml配置信息包装成MethodSignature
- 发送SQL语句,获取结果返回
public class MapperMethod {
private final MapperMethod.SqlCommand command;
private final MapperMethod.MethodSignature method;
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new MapperMethod.SqlCommand(config, mapperInterface, method);
this.method = new MapperMethod.MethodSignature(config, method);
}
//进入这个方法,匹配指令执行SQL
public Object execute(SqlSession sqlSession, Object[] args) {
Object param;
Object result;
if (SqlCommandType.INSERT == this.command.getType()) {
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
} else if (SqlCommandType.UPDATE == this.command.getType()) {
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
} else if (SqlCommandType.DELETE == this.command.getType()) {
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
} else {
if (SqlCommandType.SELECT != this.command.getType()) {
throw new BindingException("Unknown execution method for: " + this.command.getName());
}
if (this.method.returnsVoid() && this.method.hasResultHandler()) {
this.executeWithResultHandler(sqlSession, args);
result = null;
} else if (this.method.returnsMany()) {
result = this.executeForMany(sqlSession, args);
} else if (this.method.returnsMap()) {
result = this.executeForMap(sqlSession, args);
} else {
param = this.method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(this.command.getName(), param);
}
}
if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
} else {
return result;
}
}
}
自然而然,这个类之后的事就和我们使用JDBC一样,拼装SQL,调用SQL,装配返回结果
但这里的动态代理和以往我所见过的不同,以往InvocationHandler需要持有代理对象的引用,在这里却没有,因为Mapper本身并不存在实现,就是这一点引发了我的思考
2.深化代理模式
代理是什么?我的思想转变(不包括CGLIB)
阶段一:初识AOP
这个问题在我初学AOP的时候遇到,当时我的理解跟随着AOP,生成代理的目的就是给方法进行增强,在方法前后添加业务逻辑,接口,所以代理就是一种方法增强的模式,而事务很适合用这一点。毕竟就是在方法前后添加开启事务,提交事务。
而Proxy.newProxyInstance(classloader,interface,invocationhandler)这个API,拿到手我是看不懂的。
阶段二:印象强化
在网上看了一些文章,理解到了静态代理,才感知到动态代理,是Java运用反射机制,沿用代理的思想的一个实现,目的就是为了解耦。
Proxy.newProxyInstance的参数就很明显了
- classloader和interface 运行时反射创建代理类
- invocationhandler 持有代理对象的引用,负责转发的请求处理
当然我以为这样就结束了
阶段三:代理模式
走马观花看了一遍《headfirst设计模式》,才知道代理的玩法太多了,代理更像是一个影武者(傀儡),方法增强不过是冰山一角,比如保护代理,在代理类中编写如果调用了XXX方法就抛出异常等等。
阶段四:开拓视野
我看的源码肯定不多,所以Mybaits中的代理着实让我开了眼界,对于没有实现类的接口也可以代理,传递方法和参数,委托自己的类来进行处理,方法信息和SQL语句绑定,调用方法就会执行相关的Statement,原来SQL语句是这么调用的,当然过程比我说的复杂得多。
3.总结
1.Mybaits中的Mapper接口使用动态代理来实现方法调用
2.Mapper的动态代理并不处理方法,而是转发给MapperMethod。
3.Mapper接口不需要实现类,但需要有相关的XML配置,因为MapperMethod根据方法匹配XML配置