Mybatis|Mybatis mapper动态代理的原理详解
在开始动态代理的原理讲解以前,我们先看一下集成mybatis以后dao层不使用动态代理以及使用动态代理的两种实现方式,通过对比我们自己实现dao层接口以及mybatis动态代理可以更加直观的展现出mybatis动态代理替我们所做的工作,有利于我们理解动态代理的过程,讲解完以后我们再进行动态代理的原理解析,此讲解基于mybatis的环境已经搭建完成,并且已经实现了基本的用户类编写以及用户类的Dao接口的声明,下面是Dao层的接口代码
1 public interface UserDao {
2/*
3查询所有用户信息
4*/
5List findAll();
6
7/**
8* 保存用户
9* @param user
10*/
11void save(User user);
12
13/**
14* 更新用户
15* @return
16*/
17void update(User user);
18/**
19* 删除用户
20*/
21void delete(Integer userId);
22
23/**
24* 查找一个用户
25* @param userId
26* @return
27*/
28User findOne(Integer userId);
29
30/**
31* 根据名字模糊查询
32* @param name
33* @return
34*/
35List findByName(String name);
36/**
37* 根据组合对象进行模糊查询
38* @param vo
39* @return
40*/
41List findByQueryVo(QueryVo vo);
42 }
一、Mybatis dao层两种实现方式的对比
1.dao层不使用动态代理 dao层不使用动态代理的话,就需要我们自己实现dao层的接口,为了简便起见,我只是实现了Dao接口中的findAll方法,以此方法为例子来展现我们自己实现Dao的方式的情况,让我们来看代码:
1 public class UserDaoImpl implements UserDao{
2private SqlSessionFactory factory;
3public UserDaoImpl(SqlSessionFactory factory){
4this.factory = factory;
5}
6public List findAll() {
7//1.获取sqlSession对象
8SqlSession sqlSession = factory.openSession();
9//2.调用selectList方法
10List list = sqlSession.selectList("com.example.dao.UserDao.findAll");
11//3.关闭流
12sqlSession.close();
13return list;
14}
15
16public void save(User user) {
17
18}
19
20public void update(User user) {
21
22}
23
24public void delete(Integer userId) {
25
26}
27
28public User findOne(Integer userId) {
29return null;
30}
31
32public List findByName(String name) {
33return null;
34}
35
36public List findByQueryVo(QueryVo vo) {
37return null;
38}
这里的关键代码 List 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 list = mapper.findAll(); 获取到dao层的代理对象以后通过代理对象调用查询方法就可以实现查询所有用户列表的功能。
二、Mybatis动态代理实现方式的原理解析
动态代理中最重要的类:SqlSession、MapperProxy、MapperMethod,下面开始从入口方法到调用结束的过程分析。调用方法的开始: //4.获取dao的代理对象
UserDao mapper = session.getMapper(UserDao.class); 因为SqlSesseion为接口,所以我们通过Debug方式发现这里使用的实现类为DefaultSqlSession。找到DeaultSqlSession中的getMapper方法,发现这里没有做其他的动作,只是将工作继续抛到了Configuration类中,Configuration为类不是接口,可以直接进入该类的getMapper方法中
@Override
public T getMapper(Class type) {
return configuration.getMapper(type, this);
}3
. 找到Configuration类的getMapper方法,这里也是将工作继续交到MapperRegistry的getMapper的方法中,所以我们继续向下进行。
public T getMapper(Class type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}4
. 找到MapperRegistry的getMapper的方法,看到这里发现和以前不一样了,通过MapperProxyFactory的命名方式我们知道这里将通过这个工厂生成我们所关注的MapperProxy的代理类,然后我们通过
mapperProxyFactory.newInstance(sqlSession);
进入MapperProxyFactory的newInstance方法中public T getMapper(Class type, SqlSession sqlSession) {
final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) 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. 找到Map
【Mybatis|Mybatis mapper动态代理的原理详解】perProxyFactory的newIntance方法,通过参数类型SqlSession可以得知,上面的调用先进入第二个newInstance方法中并创建我们所需要重点关注的MapperProxy对象,第二个方法中再调用第一个newInstance方法并将MapperProxy对象传入进去,根据该对象创建代理类并返回。这里已经得到需要的代理类了,但是我们的代理类所做的工作还得继续向下看MapperProxy类。
protected T newInstance(MapperProxy mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}public T newInstance(SqlSession sqlSession) {
final MapperProxy mapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}6. 找到M
apperProxy类,发现其确实实现了JDK动态代理必须实现的接口InvocationHandler,所以我们重点关注invoke()方法,这里看到在invoke方法里先获取MapperMethod类,然后调用mapperMethod.execute(),所以我们继续查看MapperMethod类的execute方法。
public class MapperProxy implements InvocationHandler, Serializable {private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class mapperInterface;
private final Map methodCache;
public MapperProxy(SqlSession sqlSession, Class mapperInterface, Map 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 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的实现类的方法是一样的,经过一大圈的代理又回到了原地,这就是整个动态代理的实现过程了。
```java
1public class MapperMethod {
2
3private final SqlCommand command;
4private final MethodSignature method;
5
6public MapperMethod(Class> mapperInterface, Method method, Configuration config) {
7this.command = new SqlCommand(config, mapperInterface, method);
8this.method = new MethodSignature(config, mapperInterface, method);
9}
10
11public Object execute(SqlSession sqlSession, Object[] args) {
12Object result;
13switch (command.getType()) {
14case INSERT: {
15Object param = method.convertArgsToSqlCommandParam(args);
16result = rowCountResult(sqlSession.insert(command.getName(), param));
17break;
18}
19case UPDATE: {
20Object param = method.convertArgsToSqlCommandParam(args);
21result = rowCountResult(sqlSession.update(command.getName(), param));
22break;
23}
24case DELETE: {
25Object param = method.convertArgsToSqlCommandParam(args);
26result = rowCountResult(sqlSession.delete(command.getName(), param));
27break;
28}
29case SELECT:
30if (method.returnsVoid() && method.hasResultHandler()) {
31executeWithResultHandler(sqlSession, args);
32result = null;
33} else if (method.returnsMany()) {
34result = executeForMany(sqlSession, args);
35} else if (method.returnsMap()) {
36result = executeForMap(sqlSession, args);
37} else if (method.returnsCursor()) {
38result = executeForCursor(sqlSession, args);
39} else {
40Object param = method.convertArgsToSqlCommandParam(args);
41result = sqlSession.selectOne(command.getName(), param);
42}
43break;
44case FLUSH:
45result = sqlSession.flushStatements();
46break;
47default:
48throw new BindingException("Unknown execution method for: " + command.getName());
49}
50if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
51throw new BindingException("Mapper method '" + command.getName()
52+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
53}
54return result;
55}
56
57private Object rowCountResult(int rowCount) {
58final Object result;
59if (method.returnsVoid()) {
60result = null;
61} else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) {
62result = rowCount;
63} else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) {
64result = (long)rowCount;
65} else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) {
66result = rowCount > 0;
67} else {
68throw new BindingException("Mapper method '" + command.getName() + "' has an unsupported return type: " + method.getReturnType());
69}
70return result;
71}
72
73private void executeWithResultHandler(SqlSession sqlSession, Object[] args) {
74MappedStatement ms = sqlSession.getConfiguration().getMappedStatement(command.getName());
75if (void.class.equals(ms.getResultMaps().get(0).getType())) {
76throw new BindingException("method " + command.getName()
77+ " needs either a @ResultMap annotation, a @ResultType annotation,"
78+ " or a resultType attribute in XML so a ResultHandler can be used as a parameter.");
79}
80Object param = method.convertArgsToSqlCommandParam(args);
81if (method.hasRowBounds()) {
82RowBounds rowBounds = method.extractRowBounds(args);
83sqlSession.select(command.getName(), param, rowBounds, method.extractResultHandler(args));
84} else {
85sqlSession.select(command.getName(), param, method.extractResultHandler(args));
86}
87}
88
89private Object executeForMany(SqlSession sqlSession, Object[] args) {
90List result;
91Object param = method.convertArgsToSqlCommandParam(args);
92if (method.hasRowBounds()) {
93RowBounds rowBounds = method.extractRowBounds(args);
94result = sqlSession.selectList(command.getName(), param, rowBounds);
95} else {
96result = sqlSession.selectList(command.getName(), param);
97}
98// issue #510 Collections & arrays support
99if (!method.getReturnType().isAssignableFrom(result.getClass())) {
100if (method.getReturnType().isArray()) {
101return convertToArray(result);
102} else {
103return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
104}
105}
106return result;
107}
108
109private Cursor executeForCursor(SqlSession sqlSession, Object[] args) {
110Cursor result;
111Object param = method.convertArgsToSqlCommandParam(args);
112if (method.hasRowBounds()) {
113RowBounds rowBounds = method.extractRowBounds(args);
114result = sqlSession.selectCursor(command.getName(), param, rowBounds);
115} else {
116result = sqlSession.selectCursor(command.getName(), param);
117}
118return result;
119}
120
121private Object convertToDeclaredCollection(Configuration config, List list) {
122Object collection = config.getObjectFactory().create(method.getReturnType());
123MetaObject metaObject = config.newMetaObject(collection);
124metaObject.addAll(list);
125return collection;
126}
127
128@SuppressWarnings("unchecked")
129private Object convertToArray(List list) {
130Class> arrayComponentType = method.getReturnType().getComponentType();
131Object array = Array.newInstance(arrayComponentType, list.size());
132if (arrayComponentType.isPrimitive()) {
133for (int i = 0;
i < list.size();
i++) {
134Array.set(array, i, list.get(i));
135}
136return array;
137} else {
138return list.toArray((E[])array);
139}
140}
141
142private Map executeForMap(SqlSession sqlSession, Object[] args) {
143Map result;
144Object param = method.convertArgsToSqlCommandParam(args);
145if (method.hasRowBounds()) {
146RowBounds rowBounds = method.extractRowBounds(args);
147result = sqlSession.selectMap(command.getName(), param, method.getMapKey(), rowBounds);
148} else {
149result = sqlSession.selectMap(command.getName(), param, method.getMapKey());
150}
151return result;
152}
153
154public static class ParamMap extends HashMap {
155
156private static final long serialVersionUID = -2212268410512043556L;
157
158@Override
159public V get(Object key) {
160if (!super.containsKey(key)) {
161throw new BindingException("Parameter '" + key + "' not found. Available parameters are " + keySet());
162}
163return super.get(key);
164}
165
166}
167
168public static class SqlCommand {
169
170private final String name;
171private final SqlCommandType type;
172
173public SqlCommand(Configuration configuration, Class> mapperInterface, Method method) {
174final String methodName = method.getName();
175final Class> declaringClass = method.getDeclaringClass();
176MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
177configuration);
178if (ms == null) {
179if (method.getAnnotation(Flush.class) != null) {
180name = null;
181type = SqlCommandType.FLUSH;
182} else {
183throw new BindingException("Invalid bound statement (not found): "
184+ mapperInterface.getName() + "." + methodName);
185}
186} else {
187name = ms.getId();
188type = ms.getSqlCommandType();
189if (type == SqlCommandType.UNKNOWN) {
190throw new BindingException("Unknown execution method for: " + name);
191}
192}
193}
194
195public String getName() {
196return name;
197}
198
199public SqlCommandType getType() {
200return type;
201}
202
203private MappedStatement resolveMappedStatement(Class> mapperInterface, String methodName,
204Class> declaringClass, Configuration configuration) {
205String statementId = mapperInterface.getName() + "." + methodName;
206if (configuration.hasStatement(statementId)) {
207return configuration.getMappedStatement(statementId);
208} else if (mapperInterface.equals(declaringClass)) {
209return null;
210}
211for (Class> superInterface : mapperInterface.getInterfaces()) {
212if (declaringClass.isAssignableFrom(superInterface)) {
213MappedStatement ms = resolveMappedStatement(superInterface, methodName,
214declaringClass, configuration);
215if (ms != null) {
216return ms;
217}
218}
219}
220return null;
221}
222}
223
224public static class MethodSignature {
225
226private final boolean returnsMany;
227private final boolean returnsMap;
228private final boolean returnsVoid;
229private final boolean returnsCursor;
230private final Class> returnType;
231private final String mapKey;
232private final Integer resultHandlerIndex;
233private final Integer rowBoundsIndex;
234private final ParamNameResolver paramNameResolver;
235
236public MethodSignature(Configuration configuration, Class> mapperInterface, Method method) {
237Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
238if (resolvedReturnType instanceof Class>) {
239this.returnType = (Class>) resolvedReturnType;
240} else if (resolvedReturnType instanceof ParameterizedType) {
241this.returnType = (Class>) ((ParameterizedType) resolvedReturnType).getRawType();
242} else {
243this.returnType = method.getReturnType();
244}
245this.returnsVoid = void.class.equals(this.returnType);
246this.returnsMany = (configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray());
247this.returnsCursor = Cursor.class.equals(this.returnType);
248this.mapKey = getMapKey(method);
249this.returnsMap = (this.mapKey != null);
250this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
251this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
252this.paramNameResolver = new ParamNameResolver(configuration, method);
253}
254
255public Object convertArgsToSqlCommandParam(Object[] args) {
256return paramNameResolver.getNamedParams(args);
257}
258
259public boolean hasRowBounds() {
260return rowBoundsIndex != null;
261}
262
263public RowBounds extractRowBounds(Object[] args) {
264return hasRowBounds() ? (RowBounds) args[rowBoundsIndex] : null;
265}
266
267public boolean hasResultHandler() {
268return resultHandlerIndex != null;
269}
270
271public ResultHandler extractResultHandler(Object[] args) {
272return hasResultHandler() ? (ResultHandler) args[resultHandlerIndex] : null;
273}
274
275public String getMapKey() {
276return mapKey;
277}
278
279public Class> getReturnType() {
280return returnType;
281}
282
283public boolean returnsMany() {
284return returnsMany;
285}
286
287public boolean returnsMap() {
288return returnsMap;
289}
290
291public boolean returnsVoid() {
292return returnsVoid;
293}
294
295public boolean returnsCursor() {
296return returnsCursor;
297}
298
299private Integer getUniqueParamIndex(Method method, Class> paramType) {
300Integer index = null;
301final Class>[] argTypes = method.getParameterTypes();
302for (int i = 0;
i < argTypes.length;
i++) {
303if (paramType.isAssignableFrom(argTypes[i])) {
304if (index == null) {
305index = i;
306} else {
307throw new BindingException(method.getName() + " cannot have multiple " + paramType.getSimpleName() + " parameters");
308}
309}
310}
311return index;
312}
313
314private String getMapKey(Method method) {
315String mapKey = null;
316if (Map.class.isAssignableFrom(method.getReturnType())) {
317final MapKey mapKeyAnnotation = method.getAnnotation(MapKey.class);
318if (mapKeyAnnotation != null) {
319mapKey = mapKeyAnnotation.value();
320}
321}
322return mapKey;
323}
324}
推荐阅读
- 关于QueryWrapper|关于QueryWrapper,实现MybatisPlus多表关联查询方式
- mybatisplus如何在xml的连表查询中使用queryWrapper
- mybatisplus|mybatisplus where QueryWrapper加括号嵌套查询方式
- MybatisPlus|MybatisPlus LambdaQueryWrapper使用int默认值的坑及解决
- MybatisPlus使用queryWrapper如何实现复杂查询
- 动态组件与v-once指令
- iview|iview upload 动态改变上传参数
- react-navigation|react-navigation 动态修改 tabBar 样式
- K14|K14 9/15销售提问法D2
- MyBatis|MyBatis Generator配置