一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

Mysql|Sql Server|Oracle|Redis|MongoDB|PostgreSQL|Sqlite|DB2|mariadb|Access|數據庫技術|

服務器之家 - 數據庫 - Mysql - Mybatis mapper動態代理的原理解析

Mybatis mapper動態代理的原理解析

2020-11-30 16:38全me村的希望 Mysql

這篇文章主要介紹了Mybatis mapper動態代理的原理解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下

前言

在開始動態代理的原理講解以前,我們先看一下集成mybatis以后dao層不使用動態代理以及使用動態代理的兩種實現方式,通過對比我們自己實現dao層接口以及mybatis動態代理可以更加直觀的展現出mybatis動態代理替我們所做的工作,有利于我們理解動態代理的過程,講解完以后我們再進行動態代理的原理解析,此講解基于mybatis的環境已經搭建完成,并且已經實現了基本的用戶類編寫以及用戶類的Dao接口的聲明,下面是Dao層的接口代碼

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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的方式的情況,讓我們來看代碼:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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接口的代理對象,關鍵代碼如下:

?
1
2
3
4
5
6
//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方法中

?
1
2
3
4
@Override
 public <T> T getMapper(Class<T> type) {
 return configuration.<T>getMapper(type, this);
 }

3. 找到Configuration類的getMapper方法,這里也是將工作繼續交到MapperRegistry的getMapper的方法中,所以我們繼續向下進行。

?
1
2
3
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}

4. 找到MapperRegistry的getMapper的方法,看到這里發現和以前不一樣了,通過MapperProxyFactory的命名方式我們知道這里將通過這個工廠生成我們所關注的MapperProxy的代理類,然后我們通過mapperProxyFactory.newInstance(sqlSession);進入MapperProxyFactory的newInstance方法中

?
1
2
3
4
5
6
7
8
9
10
11
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類。

?
1
2
3
4
5
6
7
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方法。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
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整個類。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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的實現類的方法是一樣的,經過一大圈的代理又回到了原地,這就是整個動態代理的實現過程了。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
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() ? (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;
 }
 }

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。

原文鏈接:https://www.cnblogs.com/hopeofthevillage/p/11384848.html

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: jizz中国jizz老师水多 | 精品视频免费 | 国产盗摄美女嘘嘘视频 | mm在线 | 国产a片毛片| 亚洲444777KKK在线观看 | 国产欧美曰韩一区二区三区 | 美人的淫事[纯hh] | 蜜桃视频一区二区 | 超级毛片 | 日本小网站 | 国产欧美va欧美va香蕉在线观 | 欧美日韩不卡视频 | 国产 日韩 一区 | 国产一卡| 欧美黑人成人免费全部 | 秘书在办公室疯狂被hd | 四虎网址大全 | 欧美日韩精品在线视频 | 午夜免费无码福利视频麻豆 | 日韩精品一二三区 | 成年人免费在线播放 | 深夜福利影院在线观看 | 欧美视频一区二区三区四区 | 国产第一福利 | 草莓视频幸福宝 | 免费看一级毛片 | 性奶老妇 视频 | 欧美第一视频 | 亚洲第成色999久久网站 | 五月色婷婷久久综合 | 免费在线看a | 久久re热在线视频精6 | 男男playh片在线观看 | 欧美一级久久久久久久大片 | 波多野结中文字幕在线69视频 | 国产成人免费在线观看 | 国产欧美日韩亚洲精品区2345 | 欧美兽皇video | 狠狠久久久久综合网 | 精品一区二区91 |