七星彩网投网站建设运营商大数据精准营销
一: 前言
我们尝试在前几篇文章的内容中串联起来,防止各位不知所云。
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(){@Overridepublic 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代理对象创建
@Testpublic 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;}@Overridepublic 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;}@Overridepublic 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;}@UsesJava7private 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 supportif (!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;@Overridepublic 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:对方法入参和方法返参的描述