一、介紹
在某些操作中我們經常需要去從A表中查詢出字段信息,然後去關聯查詢B表。最後做組合。此類爲我們常用的業務,除了在SQL層進行處理外,我們可以通過封裝組件的形式進行處理,形成全局通用化功能!
二、思想解析
-------瞭解開閉原則,爲擴展性開發提供了一個方向
-------瞭解單一職責原則,明確方法應該具備單一職能的原則
-------瞭解元註解的使用,元註解的使用方式兩種與基本配置
-------瞭解反射體系分爲公用反射與暴力反射
-------瞭解spring的框架源碼
-------瞭解AOP的原理與應用
-------瞭解SpirngBoot註解的基本配置
三、代碼的解析與設計與優化
1、開閉原則:
允許對組件功能的擴展修改,對代碼的修改是關閉的。也就是說你可以在不改變我代碼的前提下豐富或者刪減我的行爲或者屬性。
研討交流中,我們重新定義對對象的瞭解是這樣的:
編程是一種構造世界的行爲,萬物皆對象,每個對象都有其自己的特性和方式。死物我們約定爲 特性和功能。活物我們約定爲行爲和屬性。
(這些只是我們自己的思考方式,只要你開心能理解,把對象定義爲屎都OK)
既然對象是一塊屎,啊呸,呸,呸,呸~。絕不允許這種屎味的編程,這是異端,堅決剷除~
從java語言規範來說,一般而言,我們常用的有兩種方式,【我們可以通過繼承或者實現的方式對目標對象進行封裝擴展,也可以通過代理模式對對象進行代理和豐富。】(常規開閉原則的使用)
說明:一般情況下,生產中的項目對於屬性都是執行了私有化操作,我們都知道【子類無法繼承父類的私有化變量】這句話,但實際上。當子類存在的時候,在內存中是將子類的屬性與父類的屬性放置在一起作爲子類的屬性,可以理解爲子類包裹着父類的屬性和方法,只是由於權限修飾符的限制導致了父類與子類之間的域不同,限制了訪問。
當子類繼承了父類之後,子類可以通過父類的公共get/set方法間接地執行修改。實際上這個位置並不是將父類的私有變量暴露給子類,而是父類選擇開放了使用權給子類,子類向父類提交申請,但是父類如何操作的細節並沒有向子類開放。(因爲內存結構的原因而提出子類同樣繼承了父類的私有屬性的說法,實際上是錯誤的)
(說了那麼多是爲了說明開閉原則在對對象功能進行擴展時,如果使用繼承或實現類方式,父類必須開放私有屬性的繼承權限給子類,否則子類需要全量引用),如下:
子類:
結論:基於開閉原則我們如果存在對對象屬性的豐富,可以通過繼承或者實現的方式來滿足其開閉原則。在進行數據數據傳傳遞利用面向接口編程的特性實現無原對象無異動的修改。
2、單一職責原則
一個類或者模塊應該有且只有一個改變的原因。一個類只有一個職責,如果職責過多,代碼就會臃腫,可讀性更差,也更難以維護。
研討交流中,我們認爲一個模塊最佳狀態是保持每個方法都只有單一的功能單元,如果存在多個,隨着開發系統的維護和複雜度越來越高,這個模塊或方法的職能將越來越複雜。不利於理解。
說明:爲維護單一職責的原則,一般而言,我們有如下幾種方式。【通過切面註解的方式將公共的功能給獨立出來】,【將複雜的功能給予封裝】、【將請求傳遞過來的數據給予分拆處理】。
四、組件
場景:當我們需要維護一個數據對象時候,可能存在某些字段(屬性)缺失,或者需要根據查詢出來的數據作爲條件進行二次查詢
這種場景,我們必然維護以上的開閉原則和單一職責原則,結合其處理方式,我們可以將此部分查詢進行分拆。以切面的形式完成二次查詢。從而避免在同一個方法裏面進行二次查詢。
<定義一個用於切面的錨點>
/**
* @Author: CYQ
* @Description: 關聯查詢-組件化封裝---直接給別人用作爲一個切面標籤(切在方法上)
* @Date: Created in 2020/4/9 10:42
* @Modified By:
*/
@Target({ElementType.TYPE,ElementType.METHOD}) //封裝限定在方法上(元註解[註解的註解])
@Retention(RetentionPolicy.RUNTIME) //系統編譯環境運行時,編譯解析型語言(開發-->編譯-->加載)
public @interface LinkedQuery{
}
說明:此處的註解爲一個空白的錨點標籤。用於給AOP代理進行捕獲定位。
/**
* @Author: CYQ
* @Description: 設置入參註解--將查詢對象中確失的註解進行添加獲取想要調用的對象,方法名,傳入參數
* @Date: Created in 2020/4/9 10:58
* @Modified By:
*/
@Target({ElementType.METHOD}) //方法範圍
@Retention(RetentionPolicy.RUNTIME) //限定階段
public @interface NeedSetValue {
//需要調用哪個對象
Class<?> beanClass();
//需要給到的參數
String[] params();
//需要調用的方法
String method();
//需要獲取目標方法查詢出來的結果裏面的哪個屬性
String targetFeild();
//是否寫入緩存中
boolean isCache() default false;
}
說明:用於註解數據對象中缺失的字段,二次查詢的目標是爲了通過另外的查詢方式查到該數據並補充完整。
/**
* @Author: CYQ
* @Description: 封裝攔截器
* @Date: Created in 2020/4/9 10:56
* @Modified By:
*/
@Aspect
@Component
public class SetFeildValueAspect {
//環繞註解(圍繞這個註解進行切面)
@Around("@annotation(com.yuanyue.steam.comom.mode.query.LinkedQuery)")
public Object doSetFeildValue(ProceedingJoinPoint pjp) throws Throwable {
Object res = pjp.proceed();//執行被切面的方法獲取結果集[{id:1,customId: null @NeedSetValue(屬性)},{},{}]
//獲取結果集合,將沒有值的集合設置進值即可。
if (res instanceof Collection) {
BeanUtils.setFieldValueForceCol((Collection) res);
}
return res;
}
}
反射工具類:
/**
* @Author: CYQ
* @Description: 註解化--利用註解反射從代碼中獲取集合--利用註解爲空屬性賦值
* @Date: Created in 2020/4/9 11:03
* @Modified By:
*/
@Component
public class BeanUtils implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
/**
* CYQ: 封裝的組件信息--->獲取註解--->AOP橫切註解的方法,得到集合數據---->
* @param col ---查詢的結果列表(對象中添加了)
* @throws Exception
*/
public static void setFieldValueForceCol(Collection col) throws Exception {
//獲取註解 --- 反射method想要調用的方法名 method.invoke():通過反射方式調用方法--->User對象---->name屬性--->set到結果集中
Class<?> clazz = col.iterator().next().getClass();
Field[] fields = clazz.getDeclaredFields();
//參數用於存放可能存在的
Map<String,Object> cache = new HashMap<>();//設置緩存--->存放容器
for (Field needfield : fields){
NeedSetValue sv= needfield.getAnnotation(NeedSetValue.class);
if (sv == null){
continue;
}
needfield.setAccessible(true);//設置爲可見操作
Object bean = applicationContext.getBean(sv.beanClass());
//得到方法需要入參的參數屬性與個數(多個)
Class[] cArg = new Class[sv.params().length];
Field[] paramsFields = new Field[sv.params().length];
for (int i=0 ;i<cArg.length;i++){
cArg[i] = clazz.getDeclaredField(sv.params()[i]).getType();
Field paramsField = clazz.getDeclaredField(sv.params()[i]);
paramsField.setAccessible(true);
paramsFields[i] = paramsField;
}
//得到方法
Method method = sv.beanClass().getMethod(sv.method(),cArg);//獲取方法名,new Class[]{int.class, int.class}
//獲取key值
Field targetField = null;
Boolean needInnerField = StringUtil.isEmpty(sv.targetFeild());
String keyPrefix = sv.beanClass()+"-"+sv.method()+"-"+sv.targetFeild()+"-";
for (Object obj:col){
//設置查詢參數
StringBuffer sbf = new StringBuffer();
Object[] paramValues = new Object[paramsFields.length];
for (int j=0;j<paramsFields.length;j++){
Object paramValue = paramsFields[j].get(obj);
if (Objects.isNull(paramValue)){
paramValues[j] = "";
}else{
paramValues[j] = paramValue;
}
sbf.append(paramValue);
}
//從緩存中獲取數據參數
Object value = null;
String key = keyPrefix + sbf.toString();
if (sv.isCache()){
if (cache.containsKey(key)){
value = cache.get(key);
needfield.set(obj,value);
}
}else{
value = method.invoke(bean,paramValues);//動態入參,new Object[]{1, 2}
if (needInnerField){
if (value != null){
if (targetField == null){
targetField = value.getClass().getDeclaredField(sv.targetFeild());
targetField.setAccessible(true);
}
value = targetField.get(value);
}
}
cache.put(key,value);
}
}
}
}
}