前言
在开始动态代理的原理讲解以前,我们先看一下集成mybatis以后dao层不使用动态代理以及使用动态代理的两种实现方式,通过对比我们自己实现dao层接口以及mybatis动态代理可以更加直观的展现出mybatis动态代理替我们所做的工作,有利于我们理解动态代理的过程,讲解完以后我们再进行动态代理的原理解析,此讲解基于mybatis的环境已经搭建完成,并且已经实现了基本的用户类编写以及用户类的Dao接口的声明,下面是Dao层的接口代码
public interface UserDao { /* 查询所有用户信息 */ List<User> findAll(); /** * 保存用户 * @param user */ void save(User user); /** * 更新用户 * @return */ void update(User user); /** * 删除用户 */ void delete(Integer userId); /** * 查找一个用户 * @param userId * @return */ User findOne(Integer userId); /** * 根据名字模糊查询 * @param name * @return */ List<User> findByName(String name); /** * 根据组合对象进行模糊查询 * @param vo * @return */ List<User> findByQueryVo(QueryVo vo); }
一、Mybatis dao层两种实现方式的对比
1.dao层不使用动态代理
dao层不使用动态代理的话,就需要我们自己实现dao层的接口,为了简便起见,我只是实现了Dao接口中的findAll方法,以此方法为例子来展现我们自己实现Dao的方式的情况,让我们来看代码:
public class UserDaoImpl implements UserDao{ private SqlSessionFactory factory; public UserDaoImpl(SqlSessionFactory factory){ this.factory = factory; } public List<User> findAll() { //1.获取sqlSession对象 SqlSession sqlSession = factory.openSession(); //2.调用selectList方法 List<User> list = sqlSession.selectList("com.example.dao.UserDao.findAll"); //3.关闭流 sqlSession.close(); return list; } public void save(User user) { } public void update(User user) { } public void delete(Integer userId) { } public User findOne(Integer userId) { return null; } public List<User> findByName(String name) { return null; } public List<User> findByQueryVo(QueryVo vo) { return null; }
这里的关键代码 List<User> list = sqlSession.selectList("com.example.dao.UserDao.findAll"),需要我们自己手动调用SqlSession里面的方法,基于动态代理的方式最后的目标也是成功的调用到这里。
注意:如果是添加,更新或者删除操作的话需要在方法中增加事务的提交。
2.dao层使用Mybatis的动态代理
使用动态代理的话Dao层的接口声明完成以后只需要在使用的时候通过SqlSession对象的getMapper方法获取对应Dao接口的代理对象,关键代码如下:
//3.获取SqlSession对象 SqlSession session = factory.openSession(); //4.获取dao的代理对象 UserDao mapper = session.getMapper(UserDao.class); //5.执行查询所有的方法 List<User> list = mapper.findAll();
获取到dao层的代理对象以后通过代理对象调用查询方法就可以实现查询所有用户列表的功能。
二、Mybatis动态代理实现方式的原理解析
动态代理中最重要的类:SqlSession、MapperProxy、MapperMethod,下面开始从入口方法到调用结束的过程分析。
1.调用方法的开始:
//4.获取dao的代理对象
UserDao mapper = session.getMapper(UserDao.class); 因为SqlSesseion为接口,所以我们通过Debug方式发现这里使用的实现类为DefaultSqlSession。
2.找到DeaultSqlSession中的getMapper方法,发现这里没有做其他的动作,只是将工作继续抛到了Configuration类中,Configuration为类不是接口,可以直接进入该类的getMapper方法中
@Override public <T> T getMapper(Class<T> type) { return configuration.<T>getMapper(type, this); }
3. 找到Configuration类的getMapper方法,这里也是将工作继续交到MapperRegistry的getMapper的方法中,所以我们继续向下进行。
public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); }
4. 找到MapperRegistry的getMapper的方法,看到这里发现和以前不一样了,通过MapperProxyFactory的命名方式我们知道这里将通过这个工厂生成我们所关注的MapperProxy的代理类,然后我们通过mapperProxyFactory.newInstance(sqlSession);进入MapperProxyFactory的newInstance方法中
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); } }
5. 找到MapperProxyFactory的newIntance方法,通过参数类型SqlSession可以得知,上面的调用先进入第二个newInstance方法中并创建我们所需要重点关注的MapperProxy对象,第二个方法中再调用第一个newInstance方法并将MapperProxy对象传入进去,根据该对象创建代理类并返回。这里已经得到需要的代理类了,但是我们的代理类所做的工作还得继续向下看MapperProxy类。
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); }
6. 找到MapperProxy类,发现其确实实现了JDK动态代理必须实现的接口InvocationHandler,所以我们重点关注invoke()方法,这里看到在invoke方法里先获取MapperMethod类,然后调用mapperMethod.execute(),所以我们继续查看MapperMethod类的execute方法。
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(); } }
7. 找到类MapperMethod类的execute方法,发现execute中通过调用本类中的其他方法获取并封装返回结果,我们来看一下MapperMethod整个类。
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; }
8. MapperMethod类是整个代理机制的核心类,对SqlSession中的操作进行了封装使用。
该类里有两个内部类SqlCommand和MethodSignature。 SqlCommand用来封装CRUD操作,也就是我们在xml中配置的操作的节点。每个节点都会生成一个MappedStatement类。
MethodSignature用来封装方法的参数以及返回类型,在execute的方法中我们发现在这里又回到了SqlSession中的接口调用,和我们自己实现UerDao接口的方式中直接用SqlSession对象调用DefaultSqlSession的实现类的方法是一样的,经过一大圈的代理又回到了原地,这就是整个动态代理的实现过程了。
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 (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() " 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; } }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
《魔兽世界》大逃杀!60人新游玩模式《强袭风暴》3月21日上线
暴雪近日发布了《魔兽世界》10.2.6 更新内容,新游玩模式《强袭风暴》即将于3月21 日在亚服上线,届时玩家将前往阿拉希高地展开一场 60 人大逃杀对战。
艾泽拉斯的冒险者已经征服了艾泽拉斯的大地及遥远的彼岸。他们在对抗世界上最致命的敌人时展现出过人的手腕,并且成功阻止终结宇宙等级的威胁。当他们在为即将于《魔兽世界》资料片《地心之战》中来袭的萨拉塔斯势力做战斗准备时,他们还需要在熟悉的阿拉希高地面对一个全新的敌人──那就是彼此。在《巨龙崛起》10.2.6 更新的《强袭风暴》中,玩家将会进入一个全新的海盗主题大逃杀式限时活动,其中包含极高的风险和史诗级的奖励。
《强袭风暴》不是普通的战场,作为一个独立于主游戏之外的活动,玩家可以用大逃杀的风格来体验《魔兽世界》,不分职业、不分装备(除了你在赛局中捡到的),光是技巧和战略的强弱之分就能决定出谁才是能坚持到最后的赢家。本次活动将会开放单人和双人模式,玩家在加入海盗主题的预赛大厅区域前,可以从强袭风暴角色画面新增好友。游玩游戏将可以累计名望轨迹,《巨龙崛起》和《魔兽世界:巫妖王之怒 经典版》的玩家都可以获得奖励。
更新动态
- 凤飞飞《我们的主题曲》飞跃制作[正版原抓WAV+CUE]
- 刘嘉亮《亮情歌2》[WAV+CUE][1G]
- 红馆40·谭咏麟《歌者恋歌浓情30年演唱会》3CD[低速原抓WAV+CUE][1.8G]
- 刘纬武《睡眠宝宝竖琴童谣 吉卜力工作室 白噪音安抚》[320K/MP3][193.25MB]
- 【轻音乐】曼托凡尼乐团《精选辑》2CD.1998[FLAC+CUE整轨]
- 邝美云《心中有爱》1989年香港DMIJP版1MTO东芝首版[WAV+CUE]
- 群星《情叹-发烧女声DSD》天籁女声发烧碟[WAV+CUE]
- 刘纬武《睡眠宝宝竖琴童谣 吉卜力工作室 白噪音安抚》[FLAC/分轨][748.03MB]
- 理想混蛋《Origin Sessions》[320K/MP3][37.47MB]
- 公馆青少年《我其实一点都不酷》[320K/MP3][78.78MB]
- 群星《情叹-发烧男声DSD》最值得珍藏的完美男声[WAV+CUE]
- 群星《国韵飘香·贵妃醉酒HQCD黑胶王》2CD[WAV]
- 卫兰《DAUGHTER》【低速原抓WAV+CUE】
- 公馆青少年《我其实一点都不酷》[FLAC/分轨][398.22MB]
- ZWEI《迟暮的花 (Explicit)》[320K/MP3][57.16MB]