最近爬蟲項目須要根據返回的JSON結構建立相應的表,根據要求表字段必須添加儘可能完善的註解。緩存
一、一般一個JSON結構在70-250字段之間,要根據網頁表頭與JSON數據,對比出表字段註解;app
二、領域類每一個字段須要添加兩個和註解相關的註解;函數
三、數據源來自多種系統,並已進行了部分取數保存;工具
200多字段對比出一堆專業名詞枯燥機械,累。測試
所以建立一個小工具類,從已有類中讀取其註解中的字段註解部分,造成字典庫;而後遍歷JSON的字段信息進行註解添加,Model註解使用工具從表生成Java領域類,而後稍加改動便可。ui
主要代碼以下:編碼
反射工具:spa
1 private static final Field[] EMPTY_FIELD_ARRAY = new Field[0]; 2 private static final String[] EMPTY_CAMEL_LINE_FIELD_ARRAY = new String[0]; 3 4 /** 5 * 緩存 6 */ 7 private static final Map<Class<?>, Field[]> DECLARED_FIELDS_CACHE = new ConcurrentReferenceHashMap<>(256); 8 private static final Map<Class<?>, String[]> DECLARED_CAMEL_LINE_FIELDS_CACHE = new ConcurrentReferenceHashMap<>(256); 9 10 /** 11 * 獲取全部字段信息 12 * 13 * @param classType 類型 14 * @return Field[] 15 */ 16 public static Field[] getDeclaredFields(Class<?> classType) { 17 Assert.notNull(classType, "Class must not be null"); 18 Field[] result = DECLARED_FIELDS_CACHE.get(classType); 19 if (Objects.isNull(result)) { 20 try { 21 result = classType.getDeclaredFields(); 22 DECLARED_FIELDS_CACHE.put(classType, (result.length == 0 ? EMPTY_FIELD_ARRAY : result)); 23 } catch (Throwable ex) { 24 throw new IllegalStateException("Failed to introspect Class [" + classType.getName() + 25 "] from ClassLoader [" + classType.getClassLoader() + "]", ex); 26 } 27 } 28 return result; 29 } 30 31 /** 32 * 以CameLine字符串形式獲取全部字段信息 33 * 34 * @param classType 類型 35 * @return String[] 36 */ 37 public static String[] getDeclaredCameLineFields(Class<?> classType) { 38 Assert.notNull(classType, "Class must not be null"); 39 String[] result = DECLARED_CAMEL_LINE_FIELDS_CACHE.get(classType); 40 if (Objects.isNull(result)) { 41 Field[] fields = getDeclaredFields(classType); 42 result = Arrays.stream(fields).map(field -> StringUtils.camelLine(field.getName())).toArray(String[]::new); 43 DECLARED_CAMEL_LINE_FIELDS_CACHE.put(classType, (result.length == 0 ? EMPTY_CAMEL_LINE_FIELD_ARRAY : result)); 44 } 45 return result; 46 } 47 48 /** 49 * 獲取屬性的註解值 50 * 51 * @param field 屬性 52 * @param annotationClass 註解類型 53 * @param function 轉換函數 54 * @param <T> 註解類型 55 * @param <R> 值類型 56 * @return Optional<R> 57 */ 58 public static <T extends Annotation, R> Optional<R> getDeclaredFieldAnnotation(Field field, Class<T> annotationClass, Function<T, R> function) { 59 if (field.isAnnotationPresent(annotationClass)) { 60 return Optional.ofNullable(function.apply(field.getAnnotation(annotationClass))); 61 } 62 return Optional.empty(); 63 } 64 65 /** 66 * 獲取類的字段 -> 註解值映射 67 * 68 * @param classType 類型 69 * @param annotationClass 註解類型 70 * @param function 轉換函數 71 * @param <T> 註解類型 72 * @param <R> 值類型 73 * @return Map<Field, R> 74 */ 75 public static <T extends Annotation, R> Map<Field, R> getDeclaredFieldsAnnotation(Class<?> classType, Class<T> annotationClass, Function<T, R> function) { 76 Field[] declaredFields = getDeclaredFields(classType); 77 Map<Field, R> fieldAnnotationMap = new HashMap<>(14); 78 for (Field field : declaredFields) { 79 Optional<R> declaredFieldAnnotation = getDeclaredFieldAnnotation(field, annotationClass, function); 80 if (declaredFieldAnnotation.isPresent()) { 81 R r = declaredFieldAnnotation.get(); 82 fieldAnnotationMap.put(field, r); 83 } 84 } 85 return fieldAnnotationMap; 86 } 87 88 /** 89 * 獲取一組類的字段 -> 註解值映射 90 * 91 * @param classTypeList 類型集合 92 * @param annotationClass 註解類型 93 * @param function 轉換函數 94 * @param <T> 註解類型 95 * @param <R> 值類型 96 * @return Map<Field, Set < R>> 97 */ 98 public static <T extends Annotation, R> Map<Field, Set<R>> getAllClassDeclaredFieldAnnotations(List<Class<?>> classTypeList, Class<T> annotationClass, Function<T, R> function) { 99 return classTypeList.stream() 100 .flatMap(classType -> getDeclaredFieldsAnnotation(classType, annotationClass, function).entrySet().stream()) 101 .collect(Collectors.toMap( 102 Map.Entry::getKey, 103 entry -> { 104 HashSet<R> rHashSet = new HashSet<>(); 105 rHashSet.add(entry.getValue()); 106 return rHashSet; 107 }, 108 (k1, k2) -> { 109 if (k1.size() > k2.size()) { 110 k1.addAll(k2); 111 return k1; 112 } else { 113 k2.addAll(k1); 114 return k2; 115 } 116 } 117 )); 118 } 119 120 /** 121 * 將Field類型鍵轉換爲String類型 122 * 123 * @param fieldMap Map<String, Set<T>> 124 * @param <T> 泛型參數 125 * @return Map<String, Set < T>> 126 */ 127 public static <T> Map<String, Set<T>> fieldToCameLineOfMap(Map<Field, Set<T>> fieldMap) { 128 return fieldMap.entrySet() 129 .stream() 130 .collect(Collectors.toMap( 131 entry -> StringUtils.camelLine(entry.getKey().getName()), 132 Map.Entry::getValue, 133 // Map鍵自己不重複 134 (k1, k2) -> k1 135 )); 136 } 137 138 /** 139 * 將駝峯字符串轉換爲帶下劃線的字符串,例如MyAccout,轉換爲my_account 140 * 141 * @param str 源字符串 142 * @return 轉換後的字符串 143 */ 144 public static String camelLine(String str) { 145 StringBuilder stringBuilder = new StringBuilder(); 146 int index = 0; 147 for (int i = 1, length = str.length(); i < length; i++) { 148 if (Character.isUpperCase(str.charAt(i))) { 149 stringBuilder.append(str, index, i).append("_"); 150 index = i; 151 } 152 } 153 return stringBuilder.append(str.substring(index)).toString().toLowerCase(); 154 }
測試用例:code
1 @Test 2 public void generateTableSQL() { 3 String modelJsonStr = ""; 4 String tableName = "table_name"; 5 String tableComment = "table_comment"; 6 7 JSONObject modelJsonObject = JSONObject.parseObject(modelJsonStr); 8 Set<Map.Entry<String, Object>> entrySet = modelJsonObject.entrySet(); 9 10 Map<Field, Set<String>> allClassDeclaredFieldAnnotations = ReflectionUtils.getAllClassDeclaredFieldAnnotations( 11 // 字典集 12 Arrays.asList( 13 // 選取**系統相關類 14 15 ), 16 ApiModelProperty.class, 17 ApiModelProperty::value); 18 Map<String, Set<String>> dict = ReflectionUtils.fieldToCameLineOfMap(allClassDeclaredFieldAnnotations); 19 Set<String> dictKeys = dict.keySet(); 20 21 StringBuilder stringBuilder = new StringBuilder(); 22 stringBuilder.append(String.format("CREATE TABLE %s (\n", tableName)) 23 .append("\tid varchar(36) NOT NULL COMMENT 'ID',\n") 24 .append("\texport_date varchar(20) NULL COMMENT '導出時間',\n") 25 .append("\tadd_time varchar(20) NULL COMMENT '添加時間',\n") 26 .append("\tidx varchar(10) NULL COMMENT '序號',\n") 27 .append("\tdept_id varchar(50) NULL COMMENT '組織機構編碼',\n"); 28 29 Map<String, String> ignoreMap = new HashMap<>(14); 30 for (Map.Entry<String, Object> entry : entrySet) { 31 String key = entry.getKey(); 32 stringBuilder.append("\t").append(key) 33 .append(" varchar(100) NULL COMMENT '"); 34 if (dictKeys.contains(key)) { 35 // 可自定義字典查詢規則 36 stringBuilder.append(dict.get(key).stream().findFirst().orElse("").trim()); 37 } else { 38 ignoreMap.put(key, entry.getValue() == null ? "" : entry.getValue().toString()); 39 } 40 stringBuilder.append("',\n"); 41 } 42 stringBuilder.append("\tCONSTRAINT ").append(tableName).append(" PRIMARY KEY (id)\n") 43 .append(")\n") 44 .append("ENGINE=InnoDB\n") 45 .append("DEFAULT CHARSET=utf8\n") 46 .append("COLLATE=utf8_general_ci\n") 47 .append("COMMENT='").append(tableComment).append("';"); 48 // 不負責最終SQL的格式化 49 System.out.println(stringBuilder.toString()); 50 System.out.printf("字典未包含字段:%d個\n%s\n", ignoreMap.size(), ignoreMap.entrySet().stream().map(entry -> String.format("\t\"%s\": \"%s\"", entry.getKey(), entry.getValue())).collect(Collectors.joining(",\n", "{\n", "\n}"))); 51 }