使用反射解析class
上一篇我們完成了class到表映射關系的建立,但是這個並不能被代碼正確處理,我們還需要讓程序能夠正確的識別這些映射關系。
這一篇主要講的是建立一個從class到表的模型,使我們在class上添加的注解能夠正確的被識別並處理。這里主要用到的是java中的反射相關的知識。不了解的同學請自行百度一下,不是很難~,另外這一篇也會稍微的提到一點反射的用法。
現在開始。
我們主要的需求是根絕我們添加的注解,生成各種類型的sql語句,所以我們首先要能夠獲取添加在java類名,屬性,方法上的注解,並獲取注解中的值。所以第一步:
獲取特定的注解
-
獲取class上的注解
在Java的Class中提供了.getAnnotation(annotationClass)的方法,
這里我在這個方法的基礎上包了一層,主要是使用斷言做了一些驗證,在驗證不通過的時候拋出我認識的異常信息。下面的方法都是如此處理的。
/** * 獲取類上的注解 * * @param clz * @param annotationClass * @param <T> * @return */ public static <T extends Annotation> T getAnnotation(Class<?> clz, Class<T> annotationClass) { Assert.notNull(clz, CLASS_NOT_NULL); Assert.notNull(annotationClass, ANNOTATIONCLASS_NOT_NULL); return clz.getAnnotation(annotationClass); }
-
獲取屬性上的注解
/** * 獲取屬性上的注解 * * @param field * @param annotationClass * @param <T> * @return */ public static <T extends Annotation> T getAnnotation(Field field, Class<T> annotationClass) { Assert.notNull(field, FIELD_NOT_NULL); Assert.notNull(annotationClass, ANNOTATIONCLASS_NOT_NULL); return field.getAnnotation(annotationClass); }
這里我們獲取注解的方法就寫完了,可以通過這些方法獲取我們需要的注解,並通過獲取到的注解拿到其中的值。
大致是這樣的:(這里的User.class)可以看 上一篇。
//代碼(獲取class上的注解)
@Test
public void getClassAnnotation() {
Table annotation = EntityUtils.getAnnotation(User.class, Table.class);
System.out.println(annotation.name());
}
//輸出結果
user
//代碼(獲取field上的注解)
@Test
public void getFieldAnnotation() throws NoSuchFieldException {
Class userClass = User.class;
//getDeclaredField和getField是有一定區別的,這里用getDeclaredField
Field field = userClass.getDeclaredField("createDate");
Column annotation = EntityUtils.getAnnotation(field, Column.class);
System.out.println(annotation.name());
}
//輸出結果
create_date
這樣就可以獲取到我們在class上添加的注解,以及注解中的值了。下面是第二步
獲取Id以及Column
這里依然是通過反射實現,主要就是獲取到這個class中的所有的屬性(只屬於這個class的,不包括父類)后,循環遍歷一遍,根據每個屬性上不同的注解加以區分就好了。這里為了簡單,我定義了幾個方法:
-
boolean isTable(Class aClass)
是不是添加了@Table
-
boolean isColumn(Field field)
是不是添加了@Column
-
boolean isId(Field field)
是不是添加了@Id
這幾個方法的具體代碼我就不貼出來了,很簡單的。下面是取出一個class中所有屬性的代碼:
for (Field field : clz.getDeclaredFields()) {
if (isColumn(field)) {
//執行需要的操作。
}
}
在這個遍歷個過程中我們可以新建一個類,里面用來存放表和class的各種對應關系。比如:
- class屬性名稱與表字段名稱的對應。
- 表中的id是class中哪一個屬性。
- 表的字段名稱對應的是class里的那個個屬性。
- 等等~~
代碼大致是這樣的 EntityTableRowMapper.java:
/**
* id的字段名稱
*/
private String idName = null;
/**
* table對應的class
*/
private Class<T> tableClass = null;
/**
* 對應的數據庫名稱
*/
private String tableName = null;
/**
* 表中所有的字段
*/
private Set<String> columnNames = null;
/**
* 表中所有的字段對應的屬性名稱
*/
private Set<String> fieldNames = null;
/**
* 屬性名稱和數據庫字段名的映射
* K: 屬性名
* V:表字段名稱
*/
private Map<String, String> fieldNameColumnMapper = null;
/**
* 數據庫字段名和class屬性的映射
* K:表字段名稱
* V:class屬性
*/
private Map<String, Field> columnFieldMapper = null;
這些用來描述表和class之間的關系就已經夠用了。只要按照關系將里面的數據一一填充完畢就好。我寫了一個方法來填充這些數據,代碼是這樣的:
//這里只是示例
Class clz = User.class();
//這里是主要代碼
EntityTableRowMapper mapper = new EntityTableRowMapper();
Map<String, Field> columnFieldMap = EntityUtils.columnFieldMap(clz);
int size = columnFieldMap.size();
Map<String, String> fieldNameColumnMapper = new HashMap<>(size);
Set<String> columnNames = new HashSet<>(size);
Set<String> fieldNames = new HashSet<>(size);
mapper.setTableClass(clz);
mapper.setTableName(EntityUtils.tableName(clz));
mapper.setIdName(EntityUtils.idColumnName(clz));
mapper.setColumnFieldMapper(columnFieldMap);
for (Map.Entry<String, Field> entry : columnFieldMap.entrySet()) {
String columnName = entry.getKey();
Field field = entry.getValue();
String fieldName = field.getName();
fieldNameColumnMapper.put(fieldName, columnName);
fieldNames.add(fieldName);
columnNames.add(columnName);
}
mapper.setColumnNames(columnNames);
mapper.setFieldNameColumnMapper(fieldNameColumnMapper);
mapper.setFieldNames(fieldNames);
這里漏了一個Map<String, Field> columnFieldMap = EntityUtils.columnFieldMap(clz);的代碼,在下面補上:
/**
* 獲取Table的列名與Entity屬性的映射Map
*
* @param clz
* @param <T>
* @return
*/
public static <T> Map<String, Field> columnFieldMap(Class<T> clz) {
Field[] declaredFields = clz.getDeclaredFields();
Map<String, Field> map = new HashMap<>(declaredFields.length);
for (Field field : declaredFields) {
if (isColumn(field)) {
map.put(columnName(field), field);
}
}
return map;
}
這時候,解析class里面的工作就完成了,下一步就是要通過拿到的數據來拼裝sql了。