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