簡單註解+AOP+反射實現特定功能
阿新 • • 發佈:2018-12-01
先描述下完成功能的場景:
先查一個訂單表,想要取得使用者表的相關資訊,但由於某些原因使用者表不能進行關聯查詢,這個時候往往會想到冗餘使用者表字段,但這也會帶來一個問題,就是使用者表裡的欄位改變值後,比較但以維護(因為訂單表的欄位也需要同步修改)。
所以直接先查一遍訂單表再查使用者表,當然這樣資料庫效能肯定比較低。下面假設我們就要實現這個功能。
這是我們service實現這個功能的方法
public List<Order> query(){ //先查詢所有訂單 List<Order> orderList = orderMapper.queryOrder(); //侵入程式碼 //根據訂單的使用者id去查詢使用者名稱 for(Order order : orderList){ String customerName = userMapper.queryNameById(order.getCustomerId()); order.setCustomerName(customerName); } return orderList; }
很容易發現,如果對於其他類似功能,那麼我們這段類似的侵入程式碼要重複在多個方法內出現。
所以現在想要實現以下這麼一個功能:給物件的某些欄位自動去查詢資料庫注入值。
那麼問題的關鍵就是
- 要為哪個類設定屬性?
- 為哪些屬性設定值?
- 需要藉助哪個類去查詢資料庫?
- 藉助類的哪個具體方法查詢?
所以定義一個簡單的註解來取得這四個值
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface SetValue { /** * 查詢類的類名 * @return */ Class<?> className(); /** * 查詢的方法 * @return */ String methodName(); /** * 藉助的欄位 * @return */ String paraNameForGet(); /** * 設定的欄位 * @return */ String paraNameForSet(); }
這樣子在實體類中我們就可以通過使用該註解設定這些資訊
public class Order { /* 訂單id */ private Integer id; /* 下訂單的使用者id */ private Integer customerId; /* 下訂單的使用者名稱 */ @SetValue(className = MUserMapper.class, methodName = "queryNameById", paraNameForGet = "customerId", paraNameForSet = "customerName") private String customerName; @SetValue(className = MUserMapper.class,methodName = "queryAgeById",paraNameForGet = "customerId",paraNameForSet = "customerAge") private Integer customerAge; 。。。。。。 }
獲得這些資訊後,接下來就是要執行上面侵入程式碼的邏輯,下面通過AOP來實現,在此之前再寫一個註解讓AOP知道哪些方法需要被增強
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NeedSetFieldValue {
}
@Component
@Aspect
public class SetValueAnnotationAspect {
@Autowired
private BeanUtil beanUtil;
@Around("@annotation(com.ay.mybatis.annotion.annotation.NeedSetFieldValue)")
public Object setValueAround(ProceedingJoinPoint joinPoint){
System.out.println("-----------------環繞前置通知--------------------");
try {
Object o =joinPoint.proceed();
//返回型別是個集合
if(o instanceof Collection){
boolean result = beanUtil.setFieldValue((Collection)o);
if(!result){
System.out.println("設定Field值不成功");
}
}else{
System.out.println("返回型別不是集合");
}
System.out.println("-----------------環繞後置通知--------------------");
return o;
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return null;
}
}
增強後置的功能封裝在BeanUtil中,那麼接下來的關鍵就是通過反射取得註解的四個值,結合特定業務功能實現邏輯。
@Component
public class BeanUtil implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public boolean setFieldValue(Collection c) throws NoSuchMethodException, IllegalAccessException, InstantiationException {
//得到需要設定欄位的類名
Iterator iterator = c.iterator();
Class cla =iterator.next().getClass();
Field[] fields = cla.getDeclaredFields();
for(Field m : fields) {
//得到註解
SetValue s = m.getAnnotation(SetValue.class);
if(s != null){
//查詢類,介面無法例項化,通過springIOC容器取得例項!!!!!
Object o = applicationContext.getBean(s.className());
//引數型別一定要加!!! 不然沒有方法進行匹配
//預設引數是空!!!!
Class cla2 = s.className();
//查詢field方法
Method[] c2m = cla2.getMethods();
Method queryMethod = null;
// 無法知道要呼叫的引數型別,只好採用遍歷
// cla2.getMethod(s.methodName(),Integer.class);
for(Method m2 : c2m){
if(m2.getName().equals(s.methodName())){
queryMethod = m2;
break;
}
}
//get方法
Method getMethod = cla.getMethod("get" + StringUtils.capitalize(s.paraNameForGet()));
//set方法
Method setMethod = null;
Method[] mcla = cla.getMethods();
for(Method m2: mcla){
if(m2.getName().equals("set" + StringUtils.capitalize(s.paraNameForSet()))){
setMethod = m2;
break;
}
}
iterator = c.iterator();
while (iterator.hasNext()) {
Object object = iterator.next();
try {
//獲取值
Object getField = getMethod.invoke(object);
//查詢
Object field = queryMethod.invoke(o, getField);
//設定值
setMethod.invoke(object, field);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
return true;
}
}
思路就是取得查詢類的例項物件,因為這邊使用的是mybatis介面,無法直接例項化,所以通過容器獲得例項。然後獲得要反射呼叫的三個方法,然後invoke呼叫三個方法實現即可,很簡單的反射邏輯。
這樣寫的優點主要是使得程式碼編寫變得規範了很多。下面我們實現相同功能的service方法裡就只要這樣寫就行了。
@NeedSetFieldValue //需要自動注入引數值
public List<Order> query(){
List<Order> orderList = orderMapper.queryOrder();
return orderList;
}
當然這裡主要是想練習下手寫註解以及反射的相關知識。
具體程式碼碼雲