一: 前言
我们尝试在前几篇文章的内容中串联起来,防止各位不知所云。
1:背景
我们基于Mybatis作为后台Orm框架进行编码的时候,有两种方式。
//编码方式1 UserDao userDao = sqlSession.getMapper(UserDao.class); userDao.queryAllUser(Map map); //有同学质疑为毛我从来没有sqlSesseion.getMapper(..)是因为我们使用Spring或者 //boot的时候右边给你封装了,导致你直接在Service里边把UserDao进行了注入,直接 //=右边的,你可能压根就没有机会写。 //编码方式2 sqlSession.select("com.shit.user.UserDao.queryAllUser",Map map);
我们提到了,最为底层的是下边这种方式,第一种方式是基于动态代理的技术实现的,底层也是第二种方式,基于此我们开始探索了动态代理的这种方式,整套流程下来是怎么玩的。
UserDao userDao = SqlSession.getMapper(User.class) userDao.queryuserById(); userDao.queryUsers();
2:代理设计模式(动态代理)的一些特点(简述)
这里的userDao是DAO接口的实现类的对象,那么我们就会有一个疑问,UserDao这个接口实现类在哪里呢?
这个Dao接口的实现类我们是见不到的,因为这里是一个典型的动态字节码技术,这个类是在虚拟机当中运行时创建的。虚拟机运行结束的时候,这个类就消失了
这和我们之前的这些类不一样,我们之前写的这些类就是实实在在的文件,在我们的硬盘当中,通过JVM的类加载的方式读到虚拟机的内存当中。这是我们传统意义上的一个类的创建的方式,通过复杂的类加载机制将这个类加载到JVM虚拟机当中之后,我们在这个类的Class对象创建出来。
动态字节码技术的特点是类的文件自始至终就没有,直接就在虚拟机当中的内存当中去创建,进而创建这个类的Class对象。是JVM在运行时的时候基于动态字节码技术将这个类创建出来的操作。
这就是为什么我们知道有这么一个类的存在,有这么一个类的对象的存在,但是我们就看不到的原因。
创建完成之后,就按照多态的特点,代理对象地址保存在这个接口的引用类型下边。按照多态的原则,所以,我们才可以有这个效果。
3:使用代理设计模式场景
1):为原始对象(目标)增加额外功能
ps:如果是和原始功能没有半毛钱关系的内容才叫做额外功能,与原始功能有关系的功能就不太算是额外功能了,这种情况下推进使用装饰器模式。
这个场景典型的Spring的AOP的功能。
2):远程代理(远程代理)
ps:远程代理:这时候额外功能就变成1:网络连接:2:数据传输嘛。这个最典型的就是Dubbo,远程代理代理的是远程服务。
3):无中生有(只有接口,除了接口毛都没有)
ps:这个最符合的就是Mybatis的Dao层的实现类的处理了。接口实现类这个东西,我们真的看不见,但是运行时确实可以体现出来。
在这三个场景下,我们需要考虑用到代理设计模式,第三种是典型的动态代理。而动态代理的典型的编码是:
Proxy.newProxyInstance(AssistantToHistoryPriceRule.class.getClassLoader(),new Class[]{BillSystemConfigGateWay.class}, new InvocationHandler(){ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("额外功能输出......"); return null; } });
二:实现类该如何去实现预定的功能呢?
1:猜想Mybatis的代理流程
我们现在有一个Ineterface接口中有这样的一个方法,实现类实现之后应该大致张这个样子:
以下这个是我们的大致流程猜想,当然最终结果肯定是与Mybatis的具体实现大差不差的。
interface UserDao{ List<User> queryAllUsers(); save(User user) } UserDaoImpl implements UserDAO { queryAllUsers(){ sqlSession.select(....); } save(){ sqlSession.insert("namespace.id",参数); } }
类加载器,接口,额外功能处理Handler三个条件满足之后,运行时会把这个代理对象给创建出来。
2:模拟Mybatis代理对象创建
@Test public void testProxy() throws IOException { InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); Class[] interfaces = new Class[]{UserDAO.class}; UserDAO userDAO = (UserDAO) Proxy.newProxyInstance(TestMybatis.class.getClassLoader(),interfaces ,new MyMapperProxy(sqlSession,UserDAO.class)); List<User> users = userDAO.queryAllUsersByPage(); for (User user : users) { System.out.println("user = " + user); } }
public class MyMapperProxy implements InvocationHandler { private SqlSession sqlSession; private Class daoClass; public MyMapperProxy(SqlSession sqlSession, Class daoClass) { this.sqlSession = sqlSession; this.daoClass = daoClass; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(daoClass.getName() + "." + method.getName()); return sqlSession.selectList(daoClass.getName() + "." + method.getName()); } }
这一步的创建代理的操作就等同于我们之前当中的SqlSession.getMapper(UserDao.class)的操作。
三:Mybatis代理对象创建的源码如下
Mybatis当中完成代理创建的核心类型是:MapperProxy和MapperProcyFactory
MapperProcyFactory:是个工厂,他的作用就是创建代理,工厂底层设计的过程中完成的就是Proxy.newProxyInstance(),为我们创建代理对象
MapperProxy:实现了InvocationHandler接口,他就是用调用具体method的。
/** * @author Lasse Voss */ public class MapperProxyFactory<T> { private final Class<T> mapperInterface; private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>(); public MapperProxyFactory(Class<T> mapperInterface) { this.mapperInterface = mapperInterface; } public Class<T> getMapperInterface() { return mapperInterface; } public Map<Method, MapperMethod> getMethodCache() { return methodCache; } @SuppressWarnings("unchecked") protected T newInstance(MapperProxy<T> mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); } public T newInstance(SqlSession sqlSession) { final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); } }
/** * @author Clinton Begin * @author Eduardo Macarron */ 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; } @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; } @UsesJava7 private Object invokeDefaultMethod(Object proxy, Method method, Object[] args) throws Throwable { final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class .getDeclaredConstructor(Class.class, int.class); if (!constructor.isAccessible()) { constructor.setAccessible(true); } final Class<?> declaringClass = method.getDeclaringClass(); return constructor .newInstance(declaringClass, MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED | MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC) .unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args); } /** * Backport of java.lang.reflect.Method#isDefault() */ private boolean isDefaultMethod(Method method) { return (method.getModifiers() & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC && method.getDeclaringClass().isInterface(); } }
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; } private Object rowCountResult(int rowCount) { final Object result; if (method.returnsVoid()) { result = null; } else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) { result = rowCount; } else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) { result = (long)rowCount; } else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) { result = rowCount > 0; } else { throw new BindingException("Mapper method '" + command.getName() + "' has an unsupported return type: " + method.getReturnType()); } return result; } private void executeWithResultHandler(SqlSession sqlSession, Object[] args) { MappedStatement ms = sqlSession.getConfiguration().getMappedStatement(command.getName()); if (!StatementType.CALLABLE.equals(ms.getStatementType()) && void.class.equals(ms.getResultMaps().get(0).getType())) { throw new BindingException("method " + command.getName() + " needs either a @ResultMap annotation, a @ResultType annotation," + " or a resultType attribute in XML so a ResultHandler can be used as a parameter."); } Object param = method.convertArgsToSqlCommandParam(args); if (method.hasRowBounds()) { RowBounds rowBounds = method.extractRowBounds(args); sqlSession.select(command.getName(), param, rowBounds, method.extractResultHandler(args)); } else { sqlSession.select(command.getName(), param, method.extractResultHandler(args)); } } private <E> Object executeForMany(SqlSession sqlSession, Object[] args) { List<E> result; Object param = method.convertArgsToSqlCommandParam(args); if (method.hasRowBounds()) { RowBounds rowBounds = method.extractRowBounds(args); result = sqlSession.<E>selectList(command.getName(), param, rowBounds); } else { result = sqlSession.<E>selectList(command.getName(), param); } // issue #510 Collections & arrays support if (!method.getReturnType().isAssignableFrom(result.getClass())) { if (method.getReturnType().isArray()) { return convertToArray(result); } else { return convertToDeclaredCollection(sqlSession.getConfiguration(), result); } } return result; } private <T> Cursor<T> executeForCursor(SqlSession sqlSession, Object[] args) { Cursor<T> result; Object param = method.convertArgsToSqlCommandParam(args); if (method.hasRowBounds()) { RowBounds rowBounds = method.extractRowBounds(args); result = sqlSession.<T>selectCursor(command.getName(), param, rowBounds); } else { result = sqlSession.<T>selectCursor(command.getName(), param); } return result; } private <E> Object convertToDeclaredCollection(Configuration config, List<E> list) { Object collection = config.getObjectFactory().create(method.getReturnType()); MetaObject metaObject = config.newMetaObject(collection); metaObject.addAll(list); return collection; } @SuppressWarnings("unchecked") private <E> Object convertToArray(List<E> list) { Class<?> arrayComponentType = method.getReturnType().getComponentType(); Object array = Array.newInstance(arrayComponentType, list.size()); if (arrayComponentType.isPrimitive()) { for (int i = 0; i < list.size(); i++) { Array.set(array, i, list.get(i)); } return array; } else { return list.toArray((E[])array); } } private <K, V> Map<K, V> executeForMap(SqlSession sqlSession, Object[] args) { Map<K, V> result; Object param = method.convertArgsToSqlCommandParam(args); if (method.hasRowBounds()) { RowBounds rowBounds = method.extractRowBounds(args); result = sqlSession.<K, V>selectMap(command.getName(), param, method.getMapKey(), rowBounds); } else { result = sqlSession.<K, V>selectMap(command.getName(), param, method.getMapKey()); } return result; } public static class ParamMap<V> extends HashMap<String, V> { private static final long serialVersionUID = -2212268410512043556L; @Override public V get(Object key) { if (!super.containsKey(key)) { throw new BindingException("Parameter '" + key + "' not found. Available parameters are " + keySet()); } return super.get(key); } } public static class SqlCommand { private final String name; private final SqlCommandType type; public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) { final String methodName = method.getName(); final Class<?> declaringClass = method.getDeclaringClass(); MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass, configuration); if (ms == null) { if (method.getAnnotation(Flush.class) != null) { name = null; type = SqlCommandType.FLUSH; } else { throw new BindingException("Invalid bound statement (not found): " + mapperInterface.getName() + "." + methodName); } } else { name = ms.getId(); type = ms.getSqlCommandType(); if (type == SqlCommandType.UNKNOWN) { throw new BindingException("Unknown execution method for: " + name); } } } public String getName() { return name; } public SqlCommandType getType() { return type; } private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName, Class<?> declaringClass, Configuration configuration) { String statementId = mapperInterface.getName() + "." + methodName; if (configuration.hasStatement(statementId)) { return configuration.getMappedStatement(statementId); } else if (mapperInterface.equals(declaringClass)) { return null; } for (Class<?> superInterface : mapperInterface.getInterfaces()) { if (declaringClass.isAssignableFrom(superInterface)) { MappedStatement ms = resolveMappedStatement(superInterface, methodName, declaringClass, configuration); if (ms != null) { return ms; } } } return null; } } public static class MethodSignature { private final boolean returnsMany; private final boolean returnsMap; private final boolean returnsVoid; private final boolean returnsCursor; private final Class<?> returnType; private final String mapKey; private final Integer resultHandlerIndex; private final Integer rowBoundsIndex; private final ParamNameResolver paramNameResolver; public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) { Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface); if (resolvedReturnType instanceof Class<?>) { this.returnType = (Class<?>) resolvedReturnType; } else if (resolvedReturnType instanceof ParameterizedType) { this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType(); } else { this.returnType = method.getReturnType(); } this.returnsVoid = void.class.equals(this.returnType); this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray(); this.returnsCursor = Cursor.class.equals(this.returnType); this.mapKey = getMapKey(method); this.returnsMap = this.mapKey != null; this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class); this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class); this.paramNameResolver = new ParamNameResolver(configuration, method); } public Object convertArgsToSqlCommandParam(Object[] args) { return paramNameResolver.getNamedParams(args); } public boolean hasRowBounds() { return rowBoundsIndex != null; } public RowBounds extractRowBounds(Object[] args) { return hasRowBounds() ? (RowBounds) args[rowBoundsIndex] : null; } public boolean hasResultHandler() { return resultHandlerIndex != null; } public ResultHandler extractResultHandler(Object[] args) { return hasResultHandler() ? (ResultHandler) args[resultHandlerIndex] : null; } public String getMapKey() { return mapKey; } public Class<?> getReturnType() { return returnType; } public boolean returnsMany() { return returnsMany; } public boolean returnsMap() { return returnsMap; } public boolean returnsVoid() { return returnsVoid; } public boolean returnsCursor() { return returnsCursor; } private Integer getUniqueParamIndex(Method method, Class<?> paramType) { Integer index = null; final Class<?>[] argTypes = method.getParameterTypes(); for (int i = 0; i < argTypes.length; i++) { if (paramType.isAssignableFrom(argTypes[i])) { if (index == null) { index = i; } else { throw new BindingException(method.getName() + " cannot have multiple " + paramType.getSimpleName() + " parameters"); } } } return index; } private String getMapKey(Method method) { String mapKey = null; if (Map.class.isAssignableFrom(method.getReturnType())) { final MapKey mapKeyAnnotation = method.getAnnotation(MapKey.class); if (mapKeyAnnotation != null) { mapKey = mapKeyAnnotation.value(); } } return mapKey; } } }
2:核心对象的拆解
1:MapMethod对象
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); } }
MapMethod当中需要两个成员变量:SqlCommand+MethodSignature
把Method(Dao接口中的某一个方法的对象,再次包装了一层,包装成了MapMethod对象),增添了SqlCommand+MethodSignature两个成员。
2:SqlCommand对象
包含两个关键成员:
private final String name; private final SqlCommandType type; private final String name; private final SqlCommandType type; //构造方法 public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) { final String methodName = method.getName(); final Class<?> declaringClass = method.getDeclaringClass(); MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass, configuration); if (ms == null) { if (method.getAnnotation(Flush.class) != null) { name = null; type = SqlCommandType.FLUSH; } else { throw new BindingException("Invalid bound statement (not found): " + mapperInterface.getName() + "." + methodName); } } else { name = ms.getId(); type = ms.getSqlCommandType(); if (type == SqlCommandType.UNKNOWN) { throw new BindingException("Unknown execution method for: " + name); } } }
name是nameSpace+id,type是insert,delete,update,select
3:MethodSignature
方法的入参类型和返回值类型。对SQL入参和SQL执行后的返回值的处理提供类型机制。
到这里我们是不是就有点清晰了,Mybatis中对代理设计模式的应用是这个样子的:
1:这个流程中几个核心的成员
MappedProxyFactory:执行Proxy.newProxyInstance(ClassLoader,UserDao.class,InvocationHandler); 来创建代理对象 MappedProxy:InvocationHandler的实习类,原始功能都没有的这种代理设计模式中的功能模块, 具体进行sqlSession.select,insert,update,delete的模块 MapMethod 是对方法对象的二次封装,我们知道Mybatis中UserDao中的method,可以提供nameSpace.id嘛, 这就是他的作用。 SqlCommand:对象nameSpace.id基于构造方法,放到了name属性里边,type是method增删改查的类型描述 MethodSignature:对方法入参和方法返参的描述