本章只介绍简单的原理,思路如下,先谈谈动态代理,以熟悉的mybatis举例,如何做到注入mapper的,引申到dubbo。。
代理模式
本节知识点陈旧,可以跳过。先回顾一下什么是代理,代理模式是指给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。这种模式有什么用呢?它可以在原对象的基础上增强原对象的功能,比如在原对象调用一个方法的前后进行日志、事务操作等。Spring AOP就使用了代理模式。
代理分静态代理、动态代理。下面分别使用两种方式代理以下接口
1 2 3
| public interface People { void say(); }
|
静态代理
静态代理是指我们设计模式中用到的那样,新建一个代理类,把要代理对象的传入到这个类中,手动调用这个代理类的方法。稍微感受一下。
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class PeopleProxy implements People{ private People target;
public PeopleProxy(People target) { this.target = target; }
public void say() { System.out.println("Do something before call"); target.say(); System.out.println("Do something after call"); } }
|
动态代理
动态代理主要分为JDK动态代理和cglib动态代理两大类,本文主要对JDK动态代理进行探讨。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class ProxyFactory implements InvocationHandler {
public <T> T getProxyObj(Class clazz) { return (T) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{clazz}, this); }
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("proxy the method: " + method.getDeclaringClass().getName() + "." + method.getName() + "();"); // do something. return null; } }
public class Main { public static void main(String[] args) { ProxyFactory proxyFactory = new ProxyFactory(); People proxyObj = proxyFactory.getProxyObj(People.class); proxyObj.say(); } }
|
以上代码正确执行并输出proxy the method:xxx;
,代码中没有写任何People的实现类,更不要说People的实例了,却能调用到say()方法。
总结一下,动态代理可以帮我们根据接口生成对应的接口的代理对象,而在这个代理对象执行时,我们还可以做一些增强处理。
mybatis原理
上面了解回顾动态代理实现和作用。是不是马上想到了,和我们在mybatis中写Mapper类很像。那么接下来我们寻找mybatis源码,一起验证上面的猜想。
在日常开发中,我们都或多或少了解到,mybatis为了代替手工使用 SqlSessionDaoSupport 或 SqlSessionTemplate 编写数据访问对象(DAO)的代码,MyBatis-Spring 提供了一个动态代理的实现MapperFactoryBean。那么我们从这下手一定可以找到怎么生成代理对象的,并且在代理对象中怎么执行sql的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
private Class<T> mapperInterface;
private boolean addToConfig = true;
public MapperFactoryBean() { } public MapperFactoryBean(Class<T> mapperInterface) { this.mapperInterface = mapperInterface; }
@Override public T getObject() throws Exception { return getSqlSession().getMapper(this.mapperInterface); } }
|
以上代码为MapperFactoryBean部分源码,它提供一个构造方法传入接口的Class对象,在getObject()方法中返回这个接口的代理对象,那么我们着重看getMapper()方法,一直往下,我们会找到调用MapperRegistry.getMapper()方法。
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
| public class MapperRegistry {
private final Configuration config; private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
@SuppressWarnings("unchecked") 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); } } }
public class MapperProxyFactory<T> {
@SuppressWarnings("unchecked") protected T newInstance(MapperProxy<T> mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); }
}
|
走到这里,进入到mapperProxyFactory.newInstance();方法中,就会看到我们开章举例的动态代理代码。
至此,生成代理对象的过程到此结束,这里解释了为什么我们使用mybatis,只写一个对应的接口,为什么spring能注入这个接口的对象。
那么剩下的问题就是,怎么执行对应sql语句的。知道了mybatis通过动态代理生成代理对象的,那么我们推测应该是通过实现InvocationHandler接口的invoke方法中来统一执行sql的。那么接下来我们找到一个目标MapperProxy
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 30 31
| 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;
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } else if (isDefaultMethod(method)) { return invokeDefaultMethod(proxy, method, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } final MapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args); } private MapperMethod cachedMapperMethod(Method method) { MapperMethod mapperMethod = methodCache.get(method); if (mapperMethod == null) { mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()); methodCache.put(method, mapperMethod); } return mapperMethod; }
}
|
我们可以看到invoke方法。由于mapper是一个代理对象,那么它就会运行到invoke方法里面,invoke首先判断是否一个类,显然这里mapper是一个接口,不是类所以判定失败。那么跟着就会生成MapperMethod对象,它是通过cachedMapperMethod方法对其初始化的,然后执行execute方法,把sqlSession和当前运行的参数传递进去。
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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| 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 INSERT: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.insert(command.getName(), param)); break; } case UPDATE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.update(command.getName(), param)); break; } case DELETE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.delete(command.getName(), param)); break; } case SELECT: if (method.returnsVoid() && method.hasResultHandler()) { executeWithResultHandler(sqlSession, args); result = null; } else if (method.returnsMany()) { result = executeForMany(sqlSession, args); } else if (method.returnsMap()) { result = executeForMap(sqlSession, args); } else if (method.returnsCursor()) { result = executeForCursor(sqlSession, args); } else { Object param = method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(command.getName(), param); } break; case FLUSH: result = sqlSession.flushStatements(); break; default: throw new BindingException("Unknown execution method for: " + command.getName()); } if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) { throw new BindingException("Mapper method '" + command.getName() + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ")."); } return result; } }
|
至此,mybatis原理,真相大白。在execute方法中,判断需要执行的sql是insert、update、select,做对应的处理,通过sqlSession执行对应的sql语句。
这也就解释了为什么xml中的命名空间要和接口全名对应,sql的id要和方法名对应。因为mybatis是在这里通过类名+方法名,定位到某一条sql的。
dubbo基础原理
本节本来想借助上面mybatis的原理,我们写一个类似dubbo的rpc框架,但是篇幅内容过长,解释不清,下一次我们一起写一个rpc了解dubbo执行原理。点击查看 - 手写dubbo