使用反射解析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了。