多層巢狀物件獲取底層物件屬性
最近在做一個需求,對業務層的查詢引數及返回值做校驗,檢視其是否是合法的值,使用AOP做。後來發現業務方法的返回值有多重巢狀,有Map,List,Set,Page,自定義包裝類等等,且包裝層還巢狀層,如Map< ?,List>,Page< List >,僅僅是通過反射獲取這些包裝之下實際Model就很花費精力,就想能不能將這些邏輯抽象出來,寫個特定的工具類,能很方便的剝離這些外層,獲取底層物件的指定屬性。
每一個包裝類其獲取下一層包裝的方法不盡相同,他們沒有什麼共同之處,因此提取各自的封裝邏輯是必不可少,那麼每一個包裝類至少需要各自的提取方法,如果想抽成通用的形式,則基本是使用方法的重寫來實現;但是如果用重寫則意味著會有很多的子類,且每多一種包裝就需要額外新增一個提取類,工具體系會很臃腫,不好。因此想到了使用列舉,藉助列舉的例項能重寫外部類的方法的特性,將每個包裝類的下層提取邏輯定義在不同的列舉例項裡。先看具體程式碼:
/**
* 巢狀物件屬性驗證列舉
*
* @create 2017-12-18 9:20
*/
public enum NestedObjectFieldCheckingProcessor {
LIST_RESULT(ListResult.class) {
@Override
public void process(Object object, Object expectedFieldVal) {
doProcessingInternal(((ListResult)object).getContent(), expectedFieldVal);
}
},
PAGE_RESULT(PageResult.class) {
@Override
public void process(Object object, Object expectedFieldVal) {
doProcessingInternal(((PageResult)object).getContent(), expectedFieldVal);
}
},
RESULT_MODEL(ResultModel.class) {
@Override
public void process(Object object, Object expectedFieldVal) {
doProcessingInternal(((ResultModel)object).getContent(), expectedFieldVal);
}
},
POJO_RESULT(PojoResult.class) {
@Override
public void process(Object object, Object expectedFieldVal) {
doProcessingInternal(((PojoResult)object).getContent(), expectedFieldVal);
}
},
PAGE(Page.class) {
@Override
public void process(Object object, Object expectedFieldVal) {
doProcessingInternal(((Page)object).getResult(), expectedFieldVal);
}
},
MAP(Map.class) {
@Override
public void process(Object object, Object expectedFieldVal) {
Map<?, ?> map = (Map)object;
if (CollectionUtils.isEmpty(map)) {
return;
}
COLLECTION.process(map.keySet(), expectedFieldVal);
COLLECTION.process(map.values(), expectedFieldVal);
}
},
COLLECTION(Collection.class) {
@Override
public void process(Object object, Object expectedFieldVal) {
if (CollectionUtils.isEmpty((Collection)object)) {
return;
}
for (Object element : (Collection)object) {
doProcessingInternal(element, expectedFieldVal);
}
}
},
PLAIN_OBJECT(Object.class) {
@Override
public void process(Object object, Object expectedFieldVal) {
Class<?> clazz = object.getClass();
Field field = FIELD_MAPPINGS.get(clazz);
if (field == null) {
field = ReflectionUtils.findField(clazz, CAMPUS_ID);
field.setAccessible(true);
FIELD_MAPPINGS.put(clazz, field != null ? field : NON_CAMPUS_ID_FIELD);
} else if (field == NON_CAMPUS_ID_FIELD) {
return;
}
Object actualFieldValue = ReflectionUtils.getField(field, object);
Assert.isTrue(expectedFieldVal.equals(actualFieldValue), clazz.getSimpleName());
}
};
private Class<?> clazz;
private static final String CAMPUS_ID = "campusId";
private static final Map<Class<?>, Field> FIELD_MAPPINGS = new ConcurrentHashMap<Class<?>, Field>();
private static final Field NON_CAMPUS_ID_FIELD = ReflectionUtils.findField(NestedObjectFieldCheckingProcessor.class, CAMPUS_ID);
NestedObjectFieldCheckingProcessor(Class<?> clazz) {
this.clazz = clazz;
}
/**
* @param clazz
* @return
*/
public static NestedObjectFieldCheckingProcessor valueOf(Class<?> clazz) {
for (NestedObjectFieldCheckingProcessor processorEnum : NestedObjectFieldCheckingProcessor.values()) {
if (processorEnum.clazz.isAssignableFrom(clazz)) {
return processorEnum;
}
}
return null;
}
/**
* @param value
* @param expectedFieldVal
*/
protected void doProcessingInternal(Object value, Object expectedFieldVal) {
if (value != null) {
valueOf(value.getClass()).process(value, expectedFieldVal);
}
}
/**
* @param object
* @param expectedFieldVal
*/
public abstract void process(Object object, Object expectedFieldVal);
使用方法:
ListResult<User> userlist = new ListResult<User>(new ArrayList<User>);
NestedObjectFieldCheckingProcessor processor = NestedObjectFieldCheckingProcessor.valueOf(userlist.getClass());
processor.process(userlist,10);
在列舉外殼類裡定義了一個抽象方法process(…),這個方法就是業務處理邏輯,可以定義不同的邏輯,沒什麼限制。
每一個包裝類對應的列舉裡重寫了此抽象方法,在方法內先提取其下一層的包裝物件(若是多層包裝),然後下層物件傳遞給doProcessingInternal(…)方法,在doProcessingInternal(…)方法內再次獲取引數物件對應的列舉,再呼叫其process(…)方法。
每一個包裝類的process(…)方法主要是提取其下層物件或包裝物件,然後在用下層物件去尋找適配的列舉處理器,在呼叫其process(…)方法。
遞迴式的處理,每一個包裝類對應的列舉例項僅僅負責提取下一層的包裝物件,而不負責實際的業務處理。借鑑職責鏈設計模式,每個元素僅對待處理物件做職責範圍內的處理,然後將其傳遞給下一個元素。
當然這個工具類有幾點需要注意:
- Enum的values()方法返回的是列舉例項列表,順序由上而下,所以我們定義包裝處理列舉時,要考慮其處理範圍,範圍越小的位置越靠前,處理範圍越大的越靠後,否則可能會出現意想不到的問題。
- 列舉例項的最後一個是Object對應的例項,它攔截所有的實際Model,也就是說到了這步包裝層應該都已經剝離了,它才是真正負責處理實際業務的,其他的實包裝類的處理列舉,而它是實際Model的處理列舉。
- 列舉裡可以重寫外部類的方法,和類方法重寫機制一樣。