001/* 002 * Copyright (c) 2022-2025, Mybatis-Flex (fuhai999@gmail.com). 003 * <p> 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * <p> 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * <p> 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package com.mybatisflex.core.table; 017 018import com.mybatisflex.annotation.Column; 019import com.mybatisflex.annotation.ColumnAlias; 020import com.mybatisflex.annotation.ColumnMask; 021import com.mybatisflex.annotation.Id; 022import com.mybatisflex.annotation.InsertListener; 023import com.mybatisflex.annotation.NoneListener; 024import com.mybatisflex.annotation.SetListener; 025import com.mybatisflex.annotation.Table; 026import com.mybatisflex.annotation.TableRef; 027import com.mybatisflex.annotation.UpdateListener; 028import com.mybatisflex.core.BaseMapper; 029import com.mybatisflex.core.FlexGlobalConfig; 030import com.mybatisflex.core.exception.FlexExceptions; 031import com.mybatisflex.core.query.QueryChain; 032import com.mybatisflex.core.query.QueryColumn; 033import com.mybatisflex.core.query.QueryCondition; 034import com.mybatisflex.core.query.QueryWrapper; 035import com.mybatisflex.core.util.ClassUtil; 036import com.mybatisflex.core.util.CollectionUtil; 037import com.mybatisflex.core.util.MapUtil; 038import com.mybatisflex.core.util.Reflectors; 039import com.mybatisflex.core.util.StringUtil; 040import org.apache.ibatis.io.ResolverUtil; 041import org.apache.ibatis.reflection.Reflector; 042import org.apache.ibatis.reflection.TypeParameterResolver; 043import org.apache.ibatis.type.JdbcType; 044import org.apache.ibatis.type.TypeException; 045import org.apache.ibatis.type.TypeHandler; 046import org.apache.ibatis.type.TypeHandlerRegistry; 047import org.apache.ibatis.type.UnknownTypeHandler; 048 049import java.lang.reflect.Constructor; 050import java.lang.reflect.Field; 051import java.lang.reflect.Method; 052import java.lang.reflect.Modifier; 053import java.lang.reflect.ParameterizedType; 054import java.lang.reflect.Type; 055import java.lang.reflect.TypeVariable; 056import java.math.BigDecimal; 057import java.math.BigInteger; 058import java.sql.Time; 059import java.sql.Timestamp; 060import java.time.Instant; 061import java.time.LocalDate; 062import java.time.LocalDateTime; 063import java.time.LocalTime; 064import java.time.Month; 065import java.time.OffsetDateTime; 066import java.time.OffsetTime; 067import java.time.Year; 068import java.time.YearMonth; 069import java.time.ZonedDateTime; 070import java.time.chrono.JapaneseDate; 071import java.util.ArrayList; 072import java.util.Arrays; 073import java.util.Collection; 074import java.util.Date; 075import java.util.HashMap; 076import java.util.HashSet; 077import java.util.LinkedHashSet; 078import java.util.List; 079import java.util.Map; 080import java.util.Objects; 081import java.util.Set; 082import java.util.concurrent.ConcurrentHashMap; 083import java.util.stream.Collectors; 084 085public class TableInfoFactory { 086 087 private TableInfoFactory() { 088 } 089 090 public static final Set<Class<?>> defaultSupportColumnTypes = CollectionUtil.newHashSet( 091 int.class, Integer.class, 092 short.class, Short.class, 093 long.class, Long.class, 094 float.class, Float.class, 095 double.class, Double.class, 096 boolean.class, Boolean.class, 097 Date.class, java.sql.Date.class, Time.class, Timestamp.class, 098 Instant.class, LocalDate.class, LocalDateTime.class, LocalTime.class, OffsetDateTime.class, OffsetTime.class, ZonedDateTime.class, 099 Year.class, Month.class, YearMonth.class, JapaneseDate.class, 100 byte[].class, Byte[].class, Byte.class, 101 BigInteger.class, BigDecimal.class, 102 char.class, String.class, Character.class 103 ); 104 105 static final Set<Class<?>> ignoreColumnTypes = CollectionUtil.newHashSet( 106 QueryWrapper.class, QueryColumn.class, QueryCondition.class, QueryChain.class 107 ); 108 109 110 private static final Map<Class<?>, TableInfo> mapperTableInfoMap = new ConcurrentHashMap<>(); 111 private static final Map<Class<?>, TableInfo> entityTableMap = new ConcurrentHashMap<>(); 112 private static final Map<String, TableInfo> tableInfoMap = new ConcurrentHashMap<>(); 113 private static final Set<String> initializedPackageNames = new HashSet<>(); 114 115 116 /** 117 * 用于解决 https://github.com/mybatis-flex/mybatis-flex/pull/376 的问题 118 * 119 * @param mapperPackageName mapper 的包名 120 */ 121 public synchronized static void init(String mapperPackageName) { 122 if (!initializedPackageNames.contains(mapperPackageName)) { 123 ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>(); 124 resolverUtil.find(new ResolverUtil.IsA(BaseMapper.class), mapperPackageName); 125 Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses(); 126 for (Class<? extends Class<?>> mapperClass : mapperSet) { 127 ofMapperClass(mapperClass); 128 } 129 initializedPackageNames.add(mapperPackageName); 130 } 131 } 132 133 134 public static TableInfo ofMapperClass(Class<?> mapperClass) { 135 return MapUtil.computeIfAbsent(mapperTableInfoMap, mapperClass, key -> { 136 Class<?> entityClass = getEntityClass(mapperClass); 137 if (entityClass == null) { 138 return null; 139 } 140 return ofEntityClass(entityClass); 141 }); 142 } 143 144 145 public static TableInfo ofEntityClass(Class<?> entityClass) { 146 return MapUtil.computeIfAbsent(entityTableMap, entityClass, aClass -> { 147 TableInfo tableInfo = createTableInfo(entityClass); 148 // Entity 和 VO 有相同的表名,以第一次放入的 Entity 解析的 TableInfo 为主 149 tableInfoMap.putIfAbsent(tableInfo.getTableNameWithSchema(), tableInfo); 150 return tableInfo; 151 }); 152 } 153 154 155 public static TableInfo ofTableName(String tableName) { 156 return StringUtil.isNotBlank(tableName) ? tableInfoMap.get(tableName) : null; 157 } 158 159 160 private static Class<?> getEntityClass(Class<?> mapperClass) { 161 if (mapperClass == null || mapperClass == Object.class) { 162 return null; 163 } 164 return getEntityClass(mapperClass, null); 165 } 166 167 private static Class<?> getEntityClass(Class<?> mapperClass, Type[] actualTypeArguments) { 168 // 检查基接口 169 Type[] genericInterfaces = mapperClass.getGenericInterfaces(); 170 for (Type type : genericInterfaces) { 171 if (type instanceof ParameterizedType) { 172 // 泛型基接口 173 ParameterizedType parameterizedType = (ParameterizedType) type; 174 Type rawType = parameterizedType.getRawType(); 175 Type[] typeArguments = parameterizedType.getActualTypeArguments(); 176 if (actualTypeArguments != null && actualTypeArguments.length > 0) { 177 adjustTypeArguments(mapperClass, actualTypeArguments, typeArguments); 178 } 179 if (rawType == BaseMapper.class) { 180 // 找到了 181 if (typeArguments[0] instanceof Class) { 182 return (Class<?>) typeArguments[0]; 183 } 184 } else if (rawType instanceof Class) { 185 // 其他泛型基接口 186 Class<?> entityClass = getEntityClass((Class<?>) rawType, typeArguments); 187 if (entityClass != null) { 188 return entityClass; 189 } 190 } 191 } else if (type instanceof Class) { 192 // 其他基接口 193 Class<?> entityClass = getEntityClass((Class<?>) type); 194 if (entityClass != null) { 195 return entityClass; 196 } 197 } 198 } 199 // 检查基类 200 Class<?> superclass = mapperClass.getSuperclass(); 201 if (superclass == null || superclass == Object.class) { 202 return null; 203 } 204 Type[] typeArguments = null; 205 Type genericSuperclass = mapperClass.getGenericSuperclass(); 206 if(genericSuperclass instanceof ParameterizedType){ 207 typeArguments = ((ParameterizedType) genericSuperclass).getActualTypeArguments(); 208 if (actualTypeArguments != null && actualTypeArguments.length > 0) { 209 adjustTypeArguments(mapperClass, actualTypeArguments, typeArguments); 210 } 211 } 212 return getEntityClass(superclass, typeArguments); 213 } 214 215 private static void adjustTypeArguments(Class<?> subclass, Type[] subclassTypeArguments, Type[] typeArguments) { 216 for (int i = 0; i < typeArguments.length; i++) { 217 if (typeArguments[i] instanceof TypeVariable) { 218 TypeVariable<?> typeVariable = (TypeVariable<?>) typeArguments[i]; 219 TypeVariable<?>[] typeParameters = subclass.getTypeParameters(); 220 for (int j = 0; j < typeParameters.length; j++) { 221 if (Objects.equals(typeVariable.getName(), typeParameters[j].getName())) { 222 typeArguments[i] = subclassTypeArguments[j]; 223 break; 224 } 225 } 226 } 227 } 228 } 229 230 231 private static TableInfo createTableInfo(Class<?> entityClass) { 232 233 TableInfo tableInfo = new TableInfo(); 234 tableInfo.setEntityClass(entityClass); 235 Reflector reflector = Reflectors.of(entityClass); 236 tableInfo.setReflector(reflector); 237 238 // 初始化表名 239 Table table = entityClass.getAnnotation(Table.class); 240 if (table == null) { 241 TableRef vo = entityClass.getAnnotation(TableRef.class); 242 if (vo != null) { 243 TableInfo refTableInfo = ofEntityClass(vo.value()); 244 // 设置 VO 类对应的真实的表名 245 tableInfo.setSchema(refTableInfo.getSchema()); 246 tableInfo.setTableName(refTableInfo.getTableName()); 247 // 将 @Table 注解的属性复制到 VO 类当中 248 if (vo.copyTableProps()) { 249 tableInfo.setComment(refTableInfo.getComment()); 250 tableInfo.setCamelToUnderline(refTableInfo.isCamelToUnderline()); 251 tableInfo.setDataSource(refTableInfo.getDataSource()); 252 253 tableInfo.setOnSetListeners(refTableInfo.getOnSetListeners()); 254 tableInfo.setOnInsertColumns(refTableInfo.getOnInsertColumns()); 255 tableInfo.setOnUpdateListeners(refTableInfo.getOnUpdateListeners()); 256 } 257 } else { 258 // 默认为类名转驼峰下划线 259 String tableName = StringUtil.camelToUnderline(entityClass.getSimpleName()); 260 tableInfo.setTableName(tableName); 261 } 262 } else { 263 tableInfo.setSchema(table.schema()); 264 tableInfo.setTableName(table.value()); 265 tableInfo.setCamelToUnderline(table.camelToUnderline()); 266 tableInfo.setComment(table.comment()); 267 268 if (table.onInsert().length > 0) { 269 List<InsertListener> insertListeners = Arrays.stream(table.onInsert()) 270 .filter(listener -> listener != NoneListener.class) 271 .map(ClassUtil::newInstance) 272 .collect(Collectors.toList()); 273 tableInfo.setOnInsertListeners(insertListeners); 274 } 275 276 if (table.onUpdate().length > 0) { 277 List<UpdateListener> updateListeners = Arrays.stream(table.onUpdate()) 278 .filter(listener -> listener != NoneListener.class) 279 .map(ClassUtil::newInstance) 280 .collect(Collectors.toList()); 281 tableInfo.setOnUpdateListeners(updateListeners); 282 } 283 284 if (table.onSet().length > 0) { 285 List<SetListener> setListeners = Arrays.stream(table.onSet()) 286 .filter(listener -> listener != NoneListener.class) 287 .map(ClassUtil::newInstance) 288 .collect(Collectors.toList()); 289 tableInfo.setOnSetListeners(setListeners); 290 } 291 292 if (StringUtil.isNotBlank(table.dataSource())) { 293 tableInfo.setDataSource(table.dataSource()); 294 } 295 } 296 297 // 初始化字段相关 298 List<ColumnInfo> columnInfoList = new ArrayList<>(); 299 List<IdInfo> idInfos = new ArrayList<>(); 300 301 302 String logicDeleteColumn = null; 303 String versionColumn = null; 304 String tenantIdColumn = null; 305 306 // 数据插入时,默认插入数据字段 307 Map<String, String> onInsertColumns = new HashMap<>(); 308 309 // 数据更新时,默认更新内容的字段 310 Map<String, String> onUpdateColumns = new HashMap<>(); 311 312 // 大字段列 313 Set<String> largeColumns = new LinkedHashSet<>(); 314 315 // 默认查询列 316 Set<String> defaultQueryColumns = new LinkedHashSet<>(); 317 318 List<Field> entityFields = getColumnFields(entityClass); 319 320 FlexGlobalConfig config = FlexGlobalConfig.getDefaultConfig(); 321 322 TypeHandlerRegistry typeHandlerRegistry = null; 323 if (config.getConfiguration() != null) { 324 typeHandlerRegistry = config.getConfiguration().getTypeHandlerRegistry(); 325 } 326 327 for (Field field : entityFields) { 328 329 Class<?> fieldType = reflector.getGetterType(field.getName()); 330 331 // 移除默认的忽略字段 332 boolean isIgnoreField = false; 333 for (Class<?> ignoreColumnType : ignoreColumnTypes) { 334 if (ignoreColumnType.isAssignableFrom(fieldType)) { 335 isIgnoreField = true; 336 break; 337 } 338 } 339 340 if (isIgnoreField) { 341 continue; 342 } 343 344 Column columnAnnotation = field.getAnnotation(Column.class); 345 346 /* 347 * 满足以下 4 种情况,不支持该类型的属性自动映射为字段 348 * 1、注解上未配置 TypeHandler 349 * 2、类型不是枚举 350 * 3、默认的自动类型不包含该类型 351 * 4、没有全局 TypeHandler 352 */ 353 if ((columnAnnotation == null || columnAnnotation.typeHandler() == UnknownTypeHandler.class) 354 && !fieldType.isEnum() 355 && !defaultSupportColumnTypes.contains(fieldType) 356 && (typeHandlerRegistry == null || !typeHandlerRegistry.hasTypeHandler(fieldType)) 357 ) { 358 // 忽略 集合 实体类 解析 359 if (columnAnnotation != null && columnAnnotation.ignore()) { 360 continue; 361 } 362 // 集合嵌套 363 if (Collection.class.isAssignableFrom(fieldType)) { 364 Type genericType = TypeParameterResolver.resolveFieldType(field, entityClass); 365 if (genericType instanceof ParameterizedType) { 366 Type actualTypeArgument = ((ParameterizedType) genericType).getActualTypeArguments()[0]; 367 if (actualTypeArgument instanceof Class) { 368 tableInfo.addCollectionType(field, (Class<?>) actualTypeArgument); 369 } 370 } 371 } 372 // 实体类嵌套 373 else if (!Map.class.isAssignableFrom(fieldType) 374 && !fieldType.isArray()) { 375 tableInfo.addAssociationType(field.getName(), fieldType); 376 } 377 // 不支持的类型直接跳过 378 continue; 379 } 380 381 // 列名 382 String columnName = getColumnName(tableInfo.isCamelToUnderline(), field, columnAnnotation); 383 384 // 逻辑删除字段 385 if ((columnAnnotation != null && columnAnnotation.isLogicDelete()) 386 || columnName.equals(config.getLogicDeleteColumn())) { 387 if (logicDeleteColumn == null) { 388 logicDeleteColumn = columnName; 389 } else { 390 throw FlexExceptions.wrap("The logic delete column of entity[%s] must be less then 2.", entityClass.getName()); 391 } 392 } 393 394 // 乐观锁版本字段 395 if ((columnAnnotation != null && columnAnnotation.version()) 396 || columnName.equals(config.getVersionColumn())) { 397 if (versionColumn == null) { 398 versionColumn = columnName; 399 } else { 400 throw FlexExceptions.wrap("The version column of entity[%s] must be less then 2.", entityClass.getName()); 401 } 402 } 403 404 // 租户ID 字段 405 if ((columnAnnotation != null && columnAnnotation.tenantId()) 406 || columnName.equals(config.getTenantColumn())) { 407 if (tenantIdColumn == null) { 408 tenantIdColumn = columnName; 409 } else { 410 throw FlexExceptions.wrap("The tenantId column of entity[%s] must be less then 2.", entityClass.getName()); 411 } 412 } 413 414 415 if (columnAnnotation != null && StringUtil.isNotBlank(columnAnnotation.onInsertValue())) { 416 onInsertColumns.put(columnName, columnAnnotation.onInsertValue().trim()); 417 } 418 419 420 if (columnAnnotation != null && StringUtil.isNotBlank(columnAnnotation.onUpdateValue())) { 421 onUpdateColumns.put(columnName, columnAnnotation.onUpdateValue().trim()); 422 } 423 424 425 if (columnAnnotation != null && columnAnnotation.isLarge()) { 426 largeColumns.add(columnName); 427 } 428 429 // 主键配置 430 Id id = field.getAnnotation(Id.class); 431 ColumnInfo columnInfo; 432 if (id != null) { 433 columnInfo = new IdInfo(id); 434 idInfos.add((IdInfo) columnInfo); 435 } else { 436 columnInfo = new ColumnInfo(); 437 columnInfoList.add(columnInfo); 438 } 439 440 ColumnAlias columnAlias = null; 441 442 // 属性上没有别名,查找 getter 方法上有没有别名 443 Method getterMethod = ClassUtil.getFirstMethod(entityClass, m -> ClassUtil.isGetterMethod(m, field.getName())); 444 if (getterMethod != null) { 445 columnAlias = getterMethod.getAnnotation(ColumnAlias.class); 446 } 447 448 if (columnAlias == null) { 449 columnAlias = field.getAnnotation(ColumnAlias.class); 450 } 451 452 if (columnAlias != null) { 453 columnInfo.setAlias(columnAlias.value()); 454 } 455 456 columnInfo.setColumn(columnName); 457 columnInfo.setProperty(field.getName()); 458 columnInfo.setPropertyType(fieldType); 459 columnInfo.setIgnore(columnAnnotation != null && columnAnnotation.ignore()); 460 461 if (columnAnnotation != null) { 462 columnInfo.setComment(columnAnnotation.comment()); 463 } 464 465 466 // 默认查询列 没有忽略且不是大字段 467 if (columnAnnotation == null || (!columnAnnotation.isLarge() && !columnAnnotation.ignore())) { 468 defaultQueryColumns.add(columnName); 469 } 470 471 472 // typeHandler 配置 473 if (columnAnnotation != null && columnAnnotation.typeHandler() != UnknownTypeHandler.class) { 474 TypeHandler<?> typeHandler = null; 475 476 // 集合类型,支持泛型 477 // fixed https://gitee.com/mybatis-flex/mybatis-flex/issues/I7S2YE 478 if (Collection.class.isAssignableFrom(fieldType)) { 479 typeHandler = createCollectionTypeHandler(entityClass, field, columnAnnotation.typeHandler(), fieldType); 480 } 481 482 // 非集合类型 483 else { 484 Class<?> typeHandlerClass = columnAnnotation.typeHandler(); 485 if (typeHandlerRegistry != null) { 486 Class<?> propertyType = columnInfo.getPropertyType(); 487 JdbcType jdbcType = columnAnnotation.jdbcType(); 488 if (jdbcType != JdbcType.UNDEFINED) { 489 typeHandler = typeHandlerRegistry.getTypeHandler(propertyType, jdbcType); 490 } 491 if (typeHandler == null || !typeHandlerClass.isAssignableFrom(typeHandler.getClass())) { 492 typeHandler = typeHandlerRegistry.getInstance(propertyType, typeHandlerClass); 493 } 494 } 495 } 496 497 columnInfo.setTypeHandler(typeHandler); 498 } 499 500 // 数据脱敏配置 501 ColumnMask columnMask = field.getAnnotation(ColumnMask.class); 502 if (columnMask != null && StringUtil.isNotBlank(columnMask.value())) { 503 if (String.class != fieldType) { 504 throw new IllegalStateException("@ColumnMask() only support for string type field. error: " + entityClass.getName() + "." + field.getName()); 505 } 506 columnInfo.setMaskType(columnMask.value().trim()); 507 } 508 509 // jdbcType 配置 510 if (columnAnnotation != null && columnAnnotation.jdbcType() != JdbcType.UNDEFINED) { 511 columnInfo.setJdbcType(columnAnnotation.jdbcType()); 512 } 513 514 } 515 516 517 tableInfo.setLogicDeleteColumn(logicDeleteColumn); 518 tableInfo.setVersionColumn(versionColumn); 519 tableInfo.setTenantIdColumn(tenantIdColumn); 520 521 if (!onInsertColumns.isEmpty()) { 522 tableInfo.setOnInsertColumns(onInsertColumns); 523 } 524 525 if (!onUpdateColumns.isEmpty()) { 526 tableInfo.setOnUpdateColumns(onUpdateColumns); 527 } 528 529 if (!largeColumns.isEmpty()) { 530 tableInfo.setLargeColumns(largeColumns.toArray(new String[0])); 531 } 532 533 if (!defaultQueryColumns.isEmpty()) { 534 tableInfo.setDefaultQueryColumns(defaultQueryColumns.toArray(new String[0])); 535 } 536 537 // 此处需要保证顺序先设置 PrimaryKey,在设置其他 Column, 538 // 否则会影响 SQL 的字段构建顺序 539 tableInfo.setPrimaryKeyList(idInfos); 540 tableInfo.setColumnInfoList(columnInfoList); 541 542 543 return tableInfo; 544 } 545 546 /** 547 * 创建 typeHandler 548 * 参考 {@link TypeHandlerRegistry#getInstance(Class, Class)} 549 * 550 * @param entityClass 551 * @param field 552 * @param typeHandlerClass 553 * @param fieldType 554 */ 555 private static TypeHandler<?> createCollectionTypeHandler(Class<?> entityClass, Field field, Class<?> typeHandlerClass, Class<?> fieldType) { 556 Class<?> genericClass = null; 557 Type genericType = TypeParameterResolver.resolveFieldType(field, entityClass); 558 if (genericType instanceof ParameterizedType) { 559 Type actualTypeArgument = ((ParameterizedType) genericType).getActualTypeArguments()[0]; 560 if (actualTypeArgument instanceof Class) { 561 genericClass = (Class<?>) actualTypeArgument; 562 } 563 } 564 565 try { 566 Constructor<?> constructor = typeHandlerClass.getConstructor(Class.class, Class.class); 567 return (TypeHandler<?>) constructor.newInstance(fieldType, genericClass); 568 } catch (NoSuchMethodException ignored) { 569 } catch (Exception e) { 570 throw new TypeException("Failed invoking constructor for handler " + typeHandlerClass, e); 571 } 572 try { 573 Constructor<?> constructor = typeHandlerClass.getConstructor(Class.class); 574 return (TypeHandler<?>) constructor.newInstance(fieldType); 575 } catch (NoSuchMethodException ignored) { 576 } catch (Exception e) { 577 throw new TypeException("Failed invoking constructor for handler " + typeHandlerClass, e); 578 } 579 try { 580 Constructor<?> c = typeHandlerClass.getConstructor(); 581 return (TypeHandler<?>) c.newInstance(); 582 } catch (Exception e) { 583 throw new TypeException("Unable to find a usable constructor for " + typeHandlerClass, e); 584 } 585 } 586 587 588 static String getColumnName(boolean isCamelToUnderline, Field field, Column column) { 589 if (column != null && StringUtil.isNotBlank(column.value())) { 590 return column.value(); 591 } 592 if (isCamelToUnderline) { 593 return StringUtil.camelToUnderline(field.getName()); 594 } 595 return field.getName(); 596 } 597 598 599 public static List<Field> getColumnFields(Class<?> entityClass) { 600 List<Field> fields = new ArrayList<>(); 601 doGetFields(entityClass, fields); 602 return fields; 603 } 604 605 606 private static void doGetFields(Class<?> entityClass, List<Field> fields) { 607 ClassUtil.applyAllClass(entityClass, currentClass -> { 608 Field[] declaredFields = currentClass.getDeclaredFields(); 609 for (Field declaredField : declaredFields) { 610 int modifiers = declaredField.getModifiers(); 611 if (!Modifier.isStatic(modifiers) 612 && !Modifier.isTransient(modifiers) 613 && !existName(fields, declaredField)) { 614 fields.add(declaredField); 615 } 616 } 617 return true; 618 }); 619 } 620 621 622 private static boolean existName(List<Field> fields, Field field) { 623 for (Field f : fields) { 624 if (f.getName().equalsIgnoreCase(field.getName())) { 625 return true; 626 } 627 } 628 return false; 629 } 630 631}