反射實現(xiàn)批量驗證DAO層接口

場景描述

之前寫過一篇通過AOP在DAO層做分表插件的文章,最近要將其實際用在生產(chǎn)中。因為目前項目的DAO層是從之前的項目框架中整體遷移過來的,有很大的變化,為了避免疏漏,所以要對新mapper的接口做測試驗證以及問題修復(fù)。首先想到的是部署測試環(huán)境,逐個驗證請求接口,再一一處理有問題的部分。目前也確實是如此做的,好在之前遷移是使用自己寫的程序統(tǒng)一轉(zhuǎn)換的,錯誤相對來說比較少,主要是一些遺漏的補(bǔ)充,如resultMap等,所以驗證和修復(fù)效率比較高,沒遇到特別棘手的問題。

回到正題,以前有想法是通過反射來構(gòu)造默認(rèn)參數(shù)和值,來統(tǒng)一的驗證單個調(diào)用的可用性,恰好最近也有時間,所以做了個初步的實現(xiàn),這里簡單介紹下這種方式,懂行大佬輕拍。

思路描述

一個調(diào)用或者稱為方法,都有幾個要素:返回值、參數(shù)、方法名稱。比如: var func(param a, param b)。我們這里只關(guān)注參數(shù),而參數(shù)要關(guān)注的,就是參數(shù)的類型和具體值,且這里的參數(shù)類型和值是嚴(yán)格相關(guān)的。舉個例子:對象的值,就是個新對象;基本類型如int,long 可以用Integer代替;泛型List還得關(guān)注包裝的具體類型List<String>,其他如boolean,enum,也需要不同的處理??偨Y(jié)來說,就是根據(jù)方法中參數(shù)類型生成對應(yīng)的值,然后方法用這些參數(shù)值,來進(jìn)行調(diào)用,最終可以驗證方法的可用性。

那為什么要這么做呢? 在工作中,對接口,方法的測試和驗證是非常常見的內(nèi)容之一,除非要驗證接口的邏輯功能或者關(guān)注返回值,沒必要每個接口都自己手動構(gòu)造有效參數(shù)來驗證,非常繁瑣。在項目中就遇到過對近70個接口做遷移后的使用驗證,這非??简?zāi)托?。所以,如果能自動生成默認(rèn)值,至少對這個接口方法來說參數(shù)是有效的,寫一個公共工具,就可以批量進(jìn)行不同接口的驗證。目前這個實現(xiàn)只是一個初步的構(gòu)建,是對偏自動化測試的嘗試,可以基于此做延伸,實現(xiàn)更復(fù)雜的功能。

代碼和處理過程解析


//由于項目是springboot引入測試
@RunWith(SpringRunner.class)
@SpringBootTest
public class MapperTest {

    //這里是對應(yīng)的mapper類,因為有很多,所以從idea復(fù)制引用出來
    //可以直接在包名上右鍵批量復(fù)制引用(References)
    String mappers = "com.xxx.xxx.xxx.mapper.CashXxxMapper\n";
    List<String> names = Lists.newArrayList(mappers.split("\n"));
    
    @Resource
    private ApplicationContext context;//用來獲取bean,即DAO層的各種mapper

    @Test
    public void mapperTest() {
        for (String s : names) {
            try {
                Class<?> clazz = Class.forName(s);
                Method[] methods = clazz.getMethods();
                Object obj = context.getBean(clazz);
                for (Method method : methods) {
                //用來存儲參數(shù)的值
                    List<Object> values = Lists.newArrayList();
                    //拿到方法
                    for (Parameter parameter : method.getParameters()) {
                        String type = parameter.getType().getName();
                        Object o;
                        //基本類型和數(shù)字
                        if (type.equals("long") || type.equals("int") || type.contains("lang.Long") || type.contains("lang.Integer")) {
                            o = new Integer(2);
                            //列表泛型的處理
                        } else if (type.contains("util.List")) {
                            String rawType = parameter.getParameterizedType().getTypeName().replace("java.util.List<", "").replace(">", "");
                            Object raw;
                            //封裝類型處理
                            if (rawType.contains("lang.Integer") || rawType.contains("lang.Long") || rawType.equals("long") || rawType.equals("int")) {
                                raw = new Integer(3);
                            } else {
                                raw = Class.forName(rawType).getDeclaredConstructor().newInstance();
                            }
                            ArrayList list = new ArrayList();
                            list.add(raw);
                            o = list;
                        } else if (type.contains("java.util.Date")) {
                            //項目里邊用到
                            o = new Date();
                        } else {
                            o = Class.forName(type).getDeclaredConstructor().newInstance();
                            //過濾其他元類型
                            if (!type.contains("java.lang")) {
                                try {
                                    //對象參數(shù)
                                    fieldsFill(o);
                                } catch (Exception e) {
                                    System.err.println(type);
                                }
                            }
                        }
                        values.add(o);
                    }
                    try {
                        method.invoke(obj, values.toArray());
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        //        context.getBean()
    }

    //填充對象參數(shù),可以看到和上面比較重復(fù)
    private void fieldsFill(Object object) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Class clazz = object.getClass();
        Field[] fields = clazz.getDeclaredFields();
        for (Field f : fields) {
            int modifier = f.getModifiers();
            //exclude static final
            //前綴修飾符,很好理解是一個數(shù)字
            if (Modifier.isFinal(modifier) && Modifier.isStatic(modifier)) {
                continue;
            }
            String type = f.getType().getName();
            //業(yè)務(wù)字段特殊需要,基于用thrift生成的對象
            if (type.contains("_") || type.contains("metaDataMap") || type.contains("Enum")) {
                continue;
            }
            f.setAccessible(true);
            Object fo;
            if (type.equals("long") || type.equals("int") || type.contains("lang.Long") || type.contains("lang.Integer")) {
                fo = new Integer(2);
            } else if (type.contains("List")) {
                fo = new ArrayList<>();
            } else if (type.equals("boolean")) {
                continue;
            } else {
                fo = Class.forName(type).getDeclaredConstructor().newInstance();
            }
            f.set(object, fo);
        }
    }
}

補(bǔ)充內(nèi)容

因為這個實現(xiàn)是和數(shù)據(jù)庫mapper操作有關(guān)的,為了方便驗證結(jié)果,在本地運行中,對執(zhí)行SQL加了日志,可以很方便的追蹤結(jié)果。由于項目使用的druid連接數(shù)據(jù)源,之前在jdbc連接串后面加&profileSQL=true參數(shù)失敗,所以在application.yml配置文件添加了

mybatis:
configuration:
  log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

也能起到記錄執(zhí)行SQL的作用。結(jié)果如下:

SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7da40bf4] was not registered for synchronization because synchronization is not active
JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@3f36c191] will not be managed by Spring
==>  Preparing: select `cash`, `event`, `create_time` from `xxx_record_2` WHERE `biz_type` = ? AND `user_id` = ? ORDER BY `create_time` DESC LIMIT ?, ? 
==> Parameters: 2(Integer), 2(Long), 2(Integer), 2(Integer)
<==      Total: 0
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7da40bf4]

總結(jié)

如上,通過這個實現(xiàn),對Java反射有了更深的了解,功能確實非常強(qiáng)大,而且連private static final 修飾的字段也可以修改。總體來說,實現(xiàn)了最開始要求的目標(biāo),不過中間生成默認(rèn)值的部分還是不夠優(yōu)雅,可以看到有些情況是類似遞歸的,而且if條件也可以優(yōu)化下,提高可讀性。再者后續(xù)最好能找些優(yōu)秀的框架代碼多看看,反射相關(guān)在通用框架里使用還是非常多的,當(dāng)然也不只這一點,也可以用來學(xué)習(xí)更好的實現(xiàn)方式和代碼規(guī)范。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者。

相關(guān)閱讀更多精彩內(nèi)容

  • 1. 簡介 1.1 什么是 MyBatis ? MyBatis 是支持定制化 SQL、存儲過程以及高級映射的優(yōu)秀的...
    笨鳥慢飛閱讀 6,236評論 0 4
  • 這是16年5月份編輯的一份比較雜亂適合自己觀看的學(xué)習(xí)記錄文檔,今天18年5月份再次想寫文章,發(fā)現(xiàn)簡書還為我保存起的...
    Jenaral閱讀 3,143評論 2 9
  • 對象的創(chuàng)建與銷毀 Item 1: 使用static工廠方法,而不是構(gòu)造函數(shù)創(chuàng)建對象:僅僅是創(chuàng)建對象的方法,并非Fa...
    孫小磊閱讀 2,184評論 0 3
  • 跟隨雨馨多年,從一起做到的共讀經(jīng)典活動,感佩于她的謙和與淵博,到她發(fā)出一年自助助人心理研習(xí)邀約時,我毫不猶豫報名,...
    寧靜__致遠(yuǎn)_閱讀 297評論 0 4
  • 今天重新?lián)炱鸲诰毩?xí)。 看到恐怖的處女座給我的反饋,讓我回想起了那些睡不好起得早練習(xí)多的日子。 不開心,真正的主動...
    Leonor_Z閱讀 222評論 0 0

友情鏈接更多精彩內(nèi)容