说明:Reflector是Mybatis反射工具的基础,每个Reflector对应一个类,在Reflector中封装有该类的元信息,

以及基于类信息的一系列反射应用封装API

  1. public class Reflector {
  2.  
  3. private static final String[] EMPTY_STRING_ARRAY = new String[0];
  4.  
  5. /**
  6. * 对应的类Class对象
  7. */
  8. private Class<?> type;
  9. /**
  10. * 类中可读属性的集合,就是存在相应的getter方法的属性
  11. */
  12. private String[] readablePropertyNames = EMPTY_STRING_ARRAY;
  13. /**
  14. * 类中可写属性的集合,就是存在相应的setter方法的属性
  15. */
  16. private String[] writeablePropertyNames = EMPTY_STRING_ARRAY;
  17. /**
  18. * 记录了属性相应的setter方法,key是属性名称,value是invoker对象,
  19. * invoker是对setter方法对应Method对象的封装
  20. */
  21. private Map<String, Invoker> setMethods = new HashMap<String, Invoker>();
  22. /**
  23. * 记录了属性相应的getter方法,key是属性名称,value是invoker对象,
  24. * invoker是对getter方法对应Method对象的封装
  25. */
  26. private Map<String, Invoker> getMethods = new HashMap<String, Invoker>();
  27. /**
  28. * 记录了属性相应的setter方法的参数值类型,key是属性名称,value是setter方法的参数值类型
  29. */
  30. private Map<String, Class<?>> setTypes = new HashMap<String, Class<?>>();
  31. /**
  32. * 记录了属性相应的getter方法的返回值类型,key是属性名称,value是getter方法的返回值类型
  33. */
  34. private Map<String, Class<?>> getTypes = new HashMap<String, Class<?>>();
  35. /**
  36. * 记录了默认构造方法
  37. */
  38. private Constructor<?> defaultConstructor;
  39. /**
  40. * 记录了所有属性名称的集合
  41. */
  42. private Map<String, String> caseInsensitivePropertyMap = new HashMap<String, String>();
  43.  
  44. /**
  45. * 在Reflector构造方法中会指定某个类的Class对象,并填充上述集合
  46. */
  47. public Reflector(Class<?> clazz) {
  48. type = clazz;
  49. /**
  50. * 查找clazz的默认构造方法(无参构造)
  51. */
  52. addDefaultConstructor(clazz);
  53. /**
  54. * 处理clazz中的getter方法,填充getMethods集合和getTypes集合
  55. */
  56. addGetMethods(clazz);
  57. /**
  58. * 处理clazz中的setter方法,填充setMethods集合和setTypes集合
  59. */
  60. addSetMethods(clazz);
  61. /**
  62. * 处理没有getter/setter方法的字段
  63. */
  64. addFields(clazz);
  65. /**
  66. * 根据getMethods、setMethods集合,初始化可读/写属性名称的集合
  67. */
  68. readablePropertyNames = getMethods.keySet().toArray(new String[getMethods.keySet().size()]);
  69. writeablePropertyNames = setMethods.keySet().toArray(new String[setMethods.keySet().size()]);
  70.  
  71. /**
  72. * 初始化caseInsensitivePropertyMap,其中记录了所有大写格式的属性名称
  73. */
  74. for (String propName : readablePropertyNames) {
  75. caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
  76. }
  77. for (String propName : writeablePropertyNames) {
  78. caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
  79. }
  80. }
  81.  
  82. private void addDefaultConstructor(Class<?> clazz) {
  83. /**
  84. * 返回类中声明的所有构造函数Constructor,包括public、protected、default、private声明
  85. * 如果Class对象是一个接口、抽象类、数组类或void,则返回length=0的Constructor数组
  86. */
  87. Constructor<?>[] consts = clazz.getDeclaredConstructors();
  88. for (Constructor<?> constructor : consts) {
  89. if (constructor.getParameterTypes().length == 0) {
  90. if (canAccessPrivateMethods()) {
  91. try {
  92. constructor.setAccessible(true);
  93. } catch (Exception e) {
  94. // Ignored. This is only a final precaution, nothing we can do.
  95. }
  96. }
  97. if (constructor.isAccessible()) {
  98. this.defaultConstructor = constructor;
  99. }
  100. }
  101. }
  102. }
  103.  
  104. private void addGetMethods(Class<?> cls) {
  105. Map<String, List<Method>> conflictingGetters = new HashMap<String, List<Method>>();
  106. /**
  107. * 获取当前类中所有的方法,包括自己、父类、父类的父类...,接口等
  108. */
  109. Method[] methods = getClassMethods(cls);
  110. for (Method method : methods) {
  111. String name = method.getName();
  112. if (name.startsWith("get") && name.length() > 3) {
  113. if (method.getParameterTypes().length == 0) {
  114. /**
  115. * PropertyNamer:
  116. * setter、getter方法名=>属性名称转换,和属性getter、setter方法判断
  117. */
  118. name = PropertyNamer.methodToProperty(name);
  119. addMethodConflict(conflictingGetters, name, method);
  120. }
  121. } else if (name.startsWith("is") && name.length() > 2) {
  122. if (method.getParameterTypes().length == 0) {
  123. name = PropertyNamer.methodToProperty(name);
  124. addMethodConflict(conflictingGetters, name, method);
  125. }
  126. }
  127. }
  128. //填充getMethods和getTypes集合
  129. resolveGetterConflicts(conflictingGetters);
  130. }
  131.  
  132. private void resolveGetterConflicts(Map<String, List<Method>> conflictingGetters) {
  133. for (String propName : conflictingGetters.keySet()) {
  134. List<Method> getters = conflictingGetters.get(propName);
  135. Iterator<Method> iterator = getters.iterator();
  136. Method firstMethod = iterator.next();
  137. if (getters.size() == 1) {
  138. addGetMethod(propName, firstMethod);
  139. } else {
  140. Method getter = firstMethod;
  141. Class<?> getterType = firstMethod.getReturnType();
  142. while (iterator.hasNext()) {
  143. Method method = iterator.next();
  144. Class<?> methodType = method.getReturnType();
  145. if (methodType.equals(getterType)) {
  146. throw new ReflectionException("Illegal overloaded getter method with ambiguous type for property "
  147. + propName + " in class " + firstMethod.getDeclaringClass()
  148. + ". This breaks the JavaBeans " + "specification and can cause unpredictable results.");
  149. } else if (methodType.isAssignableFrom(getterType)) {
  150. // OK getter type is descendant
  151. } else if (getterType.isAssignableFrom(methodType)) {
  152. getter = method;
  153. getterType = methodType;
  154. } else {
  155. throw new ReflectionException("Illegal overloaded getter method with ambiguous type for property "
  156. + propName + " in class " + firstMethod.getDeclaringClass()
  157. + ". This breaks the JavaBeans " + "specification and can cause unpredictable results.");
  158. }
  159. }
  160. addGetMethod(propName, getter);
  161. }
  162. }
  163. }
  164.  
  165. private void addGetMethod(String name, Method method) {
  166. if (isValidPropertyName(name)) {
  167. getMethods.put(name, new MethodInvoker(method));
  168. Type returnType = TypeParameterResolver.resolveReturnType(method, type);
  169. getTypes.put(name, typeToClass(returnType));
  170. }
  171. }
  172.  
  173. private void addSetMethods(Class<?> cls) {
  174. Map<String, List<Method>> conflictingSetters = new HashMap<String, List<Method>>();
  175. Method[] methods = getClassMethods(cls);
  176. for (Method method : methods) {
  177. String name = method.getName();
  178. if (name.startsWith("set") && name.length() > 3) {
  179. if (method.getParameterTypes().length == 1) {
  180. name = PropertyNamer.methodToProperty(name);
  181. addMethodConflict(conflictingSetters, name, method);
  182. }
  183. }
  184. }
  185. resolveSetterConflicts(conflictingSetters);
  186. }
  187.  
  188. private void addMethodConflict(Map<String, List<Method>> conflictingMethods, String name, Method method) {
  189. List<Method> list = conflictingMethods.get(name);
  190. if (list == null) {
  191. list = new ArrayList<Method>();
  192. conflictingMethods.put(name, list);
  193. }
  194. list.add(method);
  195. }
  196.  
  197. private void resolveSetterConflicts(Map<String, List<Method>> conflictingSetters) {
  198. for (String propName : conflictingSetters.keySet()) {
  199. List<Method> setters = conflictingSetters.get(propName);
  200. Class<?> getterType = getTypes.get(propName);
  201. Method match = null;
  202. ReflectionException exception = null;
  203. for (Method setter : setters) {
  204. Class<?> paramType = setter.getParameterTypes()[0];
  205. if (paramType.equals(getterType)) {
  206. // should be the best match
  207. match = setter;
  208. break;
  209. }
  210. if (exception == null) {
  211. try {
  212. match = pickBetterSetter(match, setter, propName);
  213. } catch (ReflectionException e) {
  214. // there could still be the 'best match'
  215. match = null;
  216. exception = e;
  217. }
  218. }
  219. }
  220. if (match == null) {
  221. throw exception;
  222. } else {
  223. addSetMethod(propName, match);
  224. }
  225. }
  226. }
  227.  
  228. private Method pickBetterSetter(Method setter1, Method setter2, String property) {
  229. if (setter1 == null) {
  230. return setter2;
  231. }
  232. Class<?> paramType1 = setter1.getParameterTypes()[0];
  233. Class<?> paramType2 = setter2.getParameterTypes()[0];
  234. if (paramType1.isAssignableFrom(paramType2)) {
  235. return setter2;
  236. } else if (paramType2.isAssignableFrom(paramType1)) {
  237. return setter1;
  238. }
  239. throw new ReflectionException("Ambiguous setters defined for property '" + property + "' in class '"
  240. + setter2.getDeclaringClass() + "' with types '" + paramType1.getName() + "' and '"
  241. + paramType2.getName() + "'.");
  242. }
  243.  
  244. private void addSetMethod(String name, Method method) {
  245. if (isValidPropertyName(name)) {
  246. setMethods.put(name, new MethodInvoker(method));
           // TypeParameterResolver解析类型,下一篇博文介绍
  247. Type[] paramTypes = TypeParameterResolver.resolveParamTypes(method, type);
  248. setTypes.put(name, typeToClass(paramTypes[0]));
  249. }
  250. }
  251.  
  252. private Class<?> typeToClass(Type src) {
  253. Class<?> result = null;
  254. if (src instanceof Class) {
  255. result = (Class<?>) src;
  256. } else if (src instanceof ParameterizedType) {
  257. result = (Class<?>) ((ParameterizedType) src).getRawType();
  258. } else if (src instanceof GenericArrayType) {
  259. Type componentType = ((GenericArrayType) src).getGenericComponentType();
  260. if (componentType instanceof Class) {
  261. result = Array.newInstance((Class<?>) componentType, 0).getClass();
  262. } else {
  263. Class<?> componentClass = typeToClass(componentType);
  264. result = Array.newInstance((Class<?>) componentClass, 0).getClass();
  265. }
  266. }
  267. if (result == null) {
  268. result = Object.class;
  269. }
  270. return result;
  271. }
  272.  
  273. private void addFields(Class<?> clazz) {
  274. Field[] fields = clazz.getDeclaredFields();
  275. for (Field field : fields) {
  276. if (canAccessPrivateMethods()) {
  277. try {
  278. field.setAccessible(true);
  279. } catch (Exception e) {
  280. // Ignored. This is only a final precaution, nothing we can do.
  281. }
  282. }
  283. if (field.isAccessible()) {
  284. if (!setMethods.containsKey(field.getName())) {
  285. // issue #379 - removed the check for final because JDK 1.5 allows
  286. // modification of final fields through reflection (JSR-133). (JGB)
  287. // pr #16 - final static can only be set by the classloader
  288. int modifiers = field.getModifiers();
  289. if (!(Modifier.isFinal(modifiers) && Modifier.isStatic(modifiers))) {
  290. addSetField(field);
  291. }
  292. }
  293. if (!getMethods.containsKey(field.getName())) {
  294. addGetField(field);
  295. }
  296. }
  297. }
  298. //递归
  299. if (clazz.getSuperclass() != null) {
  300. addFields(clazz.getSuperclass());
  301. }
  302. }
  303.  
  304. private void addSetField(Field field) {
  305. if (isValidPropertyName(field.getName())) {
  306. setMethods.put(field.getName(), new SetFieldInvoker(field));
  307. Type fieldType = TypeParameterResolver.resolveFieldType(field, type);
  308. setTypes.put(field.getName(), typeToClass(fieldType));
  309. }
  310. }
  311.  
  312. private void addGetField(Field field) {
  313. if (isValidPropertyName(field.getName())) {
  314. getMethods.put(field.getName(), new GetFieldInvoker(field));
  315. Type fieldType = TypeParameterResolver.resolveFieldType(field, type);
  316. getTypes.put(field.getName(), typeToClass(fieldType));
  317. }
  318. }
  319.  
  320. private boolean isValidPropertyName(String name) {
  321. return !(name.startsWith("$") || "serialVersionUID".equals(name) || "class".equals(name));
  322. }
  323.  
  324. /*
  325. * This method returns an array containing all methods
  326. * declared in this class and any superclass.
  327. * We use this method, instead of the simpler Class.getMethods(),
  328. * because we want to look for private methods as well.
  329. *
  330. * @param cls The class
  331. * @return An array containing all methods in this class
  332. */
  333. private Method[] getClassMethods(Class<?> cls) {
  334. //key是方法签名(唯一标识,下文生成),value是Method对象
  335. Map<String, Method> uniqueMethods = new HashMap<String, Method>();
  336. Class<?> currentClass = cls;
  337. while (currentClass != null) {
  338. /**
  339. * currentClass.getDeclaredMethods()返回当前类或接口中所有被声明的方法,包括
  340. * public、protected、default、private,但是排除集成的方法
  341. *
  342. * 此方法是将当前类中非桥接(下文有介绍)方法填入uniqueMethods集合
  343. */
  344. addUniqueMethods(uniqueMethods, currentClass.getDeclaredMethods());
  345.  
  346. // we also need to look for interface methods -
  347. // because the class may be abstract
  348. Class<?>[] interfaces = currentClass.getInterfaces();
  349. for (Class<?> anInterface : interfaces) {
  350. addUniqueMethods(uniqueMethods, anInterface.getMethods());
  351. }
  352. /**
  353. * Returns the {@code Class} representing the superclass of the entity
  354. * (class, interface, primitive type or void) represented by this
  355. * {@code Class}. If this {@code Class} represents either the
  356. * {@code Object} class, an interface, a primitive type, or void, then
  357. * null is returned. If this object represents an array class then the
  358. * {@code Class} object representing the {@code Object} class is
  359. * returned
  360. */
  361. currentClass = currentClass.getSuperclass();
  362. }
  363.  
  364. Collection<Method> methods = uniqueMethods.values();
  365.  
  366. return methods.toArray(new Method[methods.size()]);
  367. }
  368.  
  369. private void addUniqueMethods(Map<String, Method> uniqueMethods, Method[] methods) {
  370. for (Method currentMethod : methods) {
  371. /**
  372. * 桥接方法是 JDK 1.5 引入泛型后,为了使Java的泛型方法生成的字节码和 1.5 版本前的字节码相兼容,
  373. * 由编译器自动生成的方法。我们可以通过Method.isBridge()方法来判断一个方法是否是桥接方法。
  374. */
  375. if (!currentMethod.isBridge()) {
  376. //获取当前方法的签名
  377. String signature = getSignature(currentMethod);
  378. // check to see if the method is already known
  379. // if it is known, then an extended class must have
  380. // overridden a method
  381. if (!uniqueMethods.containsKey(signature)) {
  382. if (canAccessPrivateMethods()) {
  383. try {
  384. currentMethod.setAccessible(true);
  385. } catch (Exception e) {
  386. // Ignored. This is only a final precaution, nothing we can do.
  387. }
  388. }
  389.  
  390. uniqueMethods.put(signature, currentMethod);
  391. }
  392. }
  393. }
  394. }
  395. /**
  396. * 获取一个方法的签名,根据方法的返回值、方法、方法参数构建一个签名
  397. */
  398. private String getSignature(Method method) {
  399. StringBuilder sb = new StringBuilder();
  400. Class<?> returnType = method.getReturnType();
  401. if (returnType != null) {
  402. sb.append(returnType.getName()).append('#');
  403. }
  404. sb.append(method.getName());
  405. Class<?>[] parameters = method.getParameterTypes();
  406. for (int i = 0; i < parameters.length; i++) {
  407. if (i == 0) {
  408. sb.append(':');
  409. } else {
  410. sb.append(',');
  411. }
  412. sb.append(parameters[i].getName());
  413. }
  414. return sb.toString();
  415. }
  416.  
  417. private static boolean canAccessPrivateMethods() {
  418. try {
  419. //java安全管理器具体细节,可看博客中另一篇介绍
  420. SecurityManager securityManager = System.getSecurityManager();
  421. if (null != securityManager) {
  422. securityManager.checkPermission(new ReflectPermission("suppressAccessChecks"));
  423. }
  424. } catch (SecurityException e) {
  425. return false;
  426. }
  427. return true;
  428. }
  429.  
  430. /*
  431. * Gets the name of the class the instance provides information for
  432. *
  433. * @return The class name
  434. */
  435. public Class<?> getType() {
  436. return type;
  437. }
  438.  
  439. public Constructor<?> getDefaultConstructor() {
  440. if (defaultConstructor != null) {
  441. return defaultConstructor;
  442. } else {
  443. throw new ReflectionException("There is no default constructor for " + type);
  444. }
  445. }
  446.  
  447. public boolean hasDefaultConstructor() {
  448. return defaultConstructor != null;
  449. }
  450.  
  451. public Invoker getSetInvoker(String propertyName) {
  452. Invoker method = setMethods.get(propertyName);
  453. if (method == null) {
  454. throw new ReflectionException("There is no setter for property named '" + propertyName + "' in '" + type + "'");
  455. }
  456. return method;
  457. }
  458.  
  459. public Invoker getGetInvoker(String propertyName) {
  460. Invoker method = getMethods.get(propertyName);
  461. if (method == null) {
  462. throw new ReflectionException("There is no getter for property named '" + propertyName + "' in '" + type + "'");
  463. }
  464. return method;
  465. }
  466.  
  467. /*
  468. * Gets the type for a property setter
  469. *
  470. * @param propertyName - the name of the property
  471. * @return The Class of the propery setter
  472. */
  473. public Class<?> getSetterType(String propertyName) {
  474. Class<?> clazz = setTypes.get(propertyName);
  475. if (clazz == null) {
  476. throw new ReflectionException("There is no setter for property named '" + propertyName + "' in '" + type + "'");
  477. }
  478. return clazz;
  479. }
  480.  
  481. /*
  482. * Gets the type for a property getter
  483. *
  484. * @param propertyName - the name of the property
  485. * @return The Class of the propery getter
  486. */
  487. public Class<?> getGetterType(String propertyName) {
  488. Class<?> clazz = getTypes.get(propertyName);
  489. if (clazz == null) {
  490. throw new ReflectionException("There is no getter for property named '" + propertyName + "' in '" + type + "'");
  491. }
  492. return clazz;
  493. }
  494.  
  495. /*
  496. * Gets an array of the readable properties for an object
  497. *
  498. * @return The array
  499. */
  500. public String[] getGetablePropertyNames() {
  501. return readablePropertyNames;
  502. }
  503.  
  504. /*
  505. * Gets an array of the writeable properties for an object
  506. *
  507. * @return The array
  508. */
  509. public String[] getSetablePropertyNames() {
  510. return writeablePropertyNames;
  511. }
  512.  
  513. /*
  514. * Check to see if a class has a writeable property by name
  515. *
  516. * @param propertyName - the name of the property to check
  517. * @return True if the object has a writeable property by the name
  518. */
  519. public boolean hasSetter(String propertyName) {
  520. return setMethods.keySet().contains(propertyName);
  521. }
  522.  
  523. /*
  524. * Check to see if a class has a readable property by name
  525. *
  526. * @param propertyName - the name of the property to check
  527. * @return True if the object has a readable property by the name
  528. */
  529. public boolean hasGetter(String propertyName) {
  530. return getMethods.keySet().contains(propertyName);
  531. }
  532.  
  533. public String findPropertyName(String name) {
  534. return caseInsensitivePropertyMap.get(name.toUpperCase(Locale.ENGLISH));
  535. }
  536. }

ReflectorFactory接口主要实现了对Reflector对象的创建和缓存,Mybatis为该接口提供了仅有的一个实现类DefaultReflectorFactory:

  1. public interface ReflectorFactory {
  2.  
  3. /**
  4. * 检测ReflectorFactory对象是否启用缓存Reflector功能
  5. */
  6. boolean isClassCacheEnabled();
  7.  
  8. /**
  9. * 设置ReflectorFactory对象是否启用缓存Reflector功能
  10. */
  11. void setClassCacheEnabled(boolean classCacheEnabled);
  12.  
  13. /**
  14. * 创建指定Class对象对应的Reflector,如果缓存有,就从缓存取,否则新建一个
  15. */
  16. Reflector findForClass(Class<?> type);
  17. }
  1. public class DefaultReflectorFactory implements ReflectorFactory {
  2. /**
  3. * 默认启用缓存Reflector的功能
  4. */
  5. private boolean classCacheEnabled = true;
  6. /**
  7. * 缓存Reflector的数据结构,key[Class对象],value[Class对应的Reflector对象]
  8. */
  9. private final ConcurrentMap<Class<?>, Reflector> reflectorMap = new ConcurrentHashMap<Class<?>, Reflector>();
  10.  
  11. public DefaultReflectorFactory() {
  12. }
  13.  
  14. @Override
  15. public boolean isClassCacheEnabled() {
  16. return classCacheEnabled;
  17. }
  18.  
  19. @Override
  20. public void setClassCacheEnabled(boolean classCacheEnabled) {
  21. this.classCacheEnabled = classCacheEnabled;
  22. }
  23.  
  24. @Override
  25. public Reflector findForClass(Class<?> type) {
  26. if (classCacheEnabled) {
  27. // synchronized (type) removed see issue #461
  28. Reflector cached = reflectorMap.get(type);
  29. if (cached == null) {
  30. cached = new Reflector(type);
  31. reflectorMap.put(type, cached);
  32. }
  33. return cached;
  34. } else {
  35. return new Reflector(type);
  36. }
  37. }
  38. }

Mybatis框架基础支持层——反射工具箱之Reflector&ReflectorFactory(3)的更多相关文章

  1. Mybatis框架基础支持层——反射工具箱之MetaClass(7)

    简介:MetaClass是Mybatis对类级别的元信息的封装和处理,通过与属性工具类的结合, 实现了对复杂表达式的解析,实现了获取指定描述信息的功能 public class MetaClass { ...

  2. Mybatis框架基础支持层——反射工具箱之实体属性Property工具集(6)

    本篇主要介绍mybatis反射工具中用到的三个属性工具类:PropertyTokenizer.PropertyNamer.PropertyCopier. PropertyTokenizer: 主要用来 ...

  3. Mybatis框架基础支持层——反射工具箱之对象工厂ObjectFactory&DefaultObjectFactory(5)

    ObjectFactory官方简介:MyBatis每次创建结果集对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成. 默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认 ...

  4. Mybatis框架基础支持层——反射工具箱之泛型解析工具TypeParameterResolver(4)

    简介:TypeParameterResolver是一个工具类,提供一系列的静态方法,去解析类中的字段.方法返回值.方法参数的类型. 在正式介绍TypeParameterResolver之前,先介绍一个 ...

  5. Mybatis框架基础支持层——日志模块(8)

    前言: java开发中常用的日志框架有Log4j,Log4j2,Apache Commons Log,java.util.logging,slf4j等,这些工具对外的接口不尽相同.为了统一这些工具的接 ...

  6. Mybatis框架基础支持层——解析器模块(2)

    解析器模块,核心类XPathParser /** * 封装了用于xml解析的类XPath.Document和EntityResolver */ public class XPathParser { / ...

  7. MyBatis源码分析-基础支持层反射模块Reflector/ReflectorFactory

    本文主要介绍MyBatis的反射模块是如何实现的. MyBatis 反射的核心类Reflector,下面我先说明它的构造函数和成员变量.具体方法下面详解. org.apache.ibatis.refl ...

  8. 精尽 MyBatis 源码分析 - 基础支持层

    该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

  9. MyBatis 框架 基础应用

    1.ORM的概念和优势 概念: 对象关系映射(Object Relational Mapping,简称ORM)是通过使用描述对象和数据库之间映射的元数据,将面向对象语言程序中的对象自动持久化到关系数据 ...

随机推荐

  1. CS61A Lecture3 Note

    本次lec主讲控制流 本文档只列一些py控制流与C不同的地方  print的功能不同 可以print出来None这种东西 重点讲了函数运行机制,我的理解是这样的,在调用函数之前,def会产生一个glo ...

  2. Microsoft Azure IoTHub Serials 2 - 如何为android应用添加IoTHub支持

    1. 在build.gradle(app)文件的dependencies中添加对以下项的依赖: 'com.microsoft.azure.sdk.iot:iot-device-client:1.5.3 ...

  3. 背水一战 Windows 10 (120) - 后台任务: 后台上传任务

    [源码下载] 背水一战 Windows 10 (120) - 后台任务: 后台上传任务 作者:webabcd 介绍背水一战 Windows 10 之 后台任务 后台上传任务 示例演示 uwp 的后台上 ...

  4. myEclipse配置jdk1.7

    第一步:下载jdk1.7 下载地址:http://download.csdn.net/download/chun201010/7824469 第二步:安装jdk1.7 将下载的压缩包进行解压,得到一个 ...

  5. Cloud-Platform部署学习

    1. Cloud-Platform部署学习 1.1. 介绍 Cloud-Platform是国内首个基于Spring Cloud微服务化开发平台,核心技术采用Spring Boot2以及Spring C ...

  6. Spring Boot 自定义日志详解

    本节内容基于 Spring Boot 2.0. 你所需具备的基础 什么是 Spring Boot? Spring Boot 核心配置文件详解 Spring Boot 开启的 2 种方式 Spring ...

  7. 分享一个基于web的满意度调查问卷源码系统

    问卷调查系统应用于各行各业,对于企业的数据回收统计分析战略决策起到至关作用.而现有的问卷调查系统大都是在线使用并将数据保存在第三方服务器上.这种模式每年都要缴纳费用并且数据安全性得不到保证.所以说每个 ...

  8. CentOS 7.x 安装 Docker-Compose

    一.安装步骤 添加企业版附加包. yum -y install epel-release 安装 PIP. yum -y install python-pip 更新 PIP. pip install - ...

  9. Android--多线程之Looper

    前言 上一篇博客讲解了Handler实现线程间通信,这篇博客讲解一下Handler运行的原理,其中涉及到MessageQueue.Looper.简要来讲,Handler会把一个线程消息发送给当前线程的 ...

  10. 大数据入门基础系列之Hadoop1.X、Hadoop2.X和Hadoop3.X的多维度区别详解(博主推荐)

    不多说,直接上干货! 在前面的博文里,我已经介绍了 大数据入门基础系列之Linux操作系统简介与选择 大数据入门基础系列之虚拟机的下载.安装详解 大数据入门基础系列之Linux的安装详解 大数据入门基 ...