漫談反射在業務代碼中的應用


    很多人都覺得寫業務代碼很枯燥,沒有什么技術含量,大部分就是if-else邏輯的疊加。寫業務代碼確實沒有寫中間件來的高大上,但是我覺得不管是寫什么代碼,想要寫出好的代碼都不是一件容易的事情。這不,最近我們生產系統的版本迭代過程中一個需求就讓我思考了很多,並且在實現方式上做得更加的優雅。

   場景如下:我們在生產系統中需要維護各個游戲的狀態,當需要上線一個游戲時,需要對該游戲的各方面的信息做一遍檢查,當檢查所有的字段都達到要求之后,再切換該游戲的狀態為已上線。首先,針對安卓游戲,我們需要檢查該游戲的資質審核狀態,運營包地址和渠道包地址是否分發完成,基本信息中的必填字段是否非空,是否接入公司的sdk,若接入sdk,則還需要檢查該游戲的支付信息中的必填字段是否已經完成等等,針對ios游戲,h5游戲以及網頁游戲,我們需要檢查的字段各不相同。

   首先看到這個需求,給人的第一感覺是很繁瑣,每個游戲的字段很多,由於在后台使用了mbg,游戲的支付信息,安裝包信息和基本信息等字段並不在同一個bean中,並且我們還要區分游戲的類型,不同的游戲要檢查的字段也不盡相同,最后,關於游戲的基本信息,支付信息等信息的必填字段還在不斷的增加,這一部分的業務的變化也較為頻繁。若后續的必填字段有調整或者新增,那這里的代碼也要做改動。

   好了,我們立馬動手寫,取出這個游戲的所有的字段,為需要校驗的字段去一個個的判斷是否符合要求......這時候,我們發現字段太多了,寫起來很累,而且這段代碼無論是從可讀性和可擴展性上來講,都做的不夠好。在非常沮喪的時候,我想到了反射,如果把反射用在這里,是不是使得代碼中避免了大量的if(XXX == null)這樣的語句呢,趕緊動手寫。代碼如下:

   首先把最核心的判斷解決了,如果該游戲有一個字段不符合要求,直接拋出我們自定義的異常傳給前端展示:

 1 /**
 2      * 通過反射比較配置的必填字段和數據庫取出的bean,找出必填的字段是不是空,傳入gameId便於直接返回ajax信息
 3      *
 4      * @param tGameInfo
 5      * @param gameRequiredInfoConfig
 6      * @param gameId
 7      * @param gameInfoType
 8      * @throws GameRequiredInfoException
 9      */
10     private void reflectCheckRequiredInfo(Object tGameInfo, List<String> gameRequiredInfoConfig, long gameId, int gameInfoType) throws GameRequiredInfoException {
11         try {
12             // 反射model,校驗gameInfo的必填屬性的值是不是空
13             Field[] gameInfoFields = tGameInfo.getClass().getDeclaredFields();
14             for (Field gameInfoField : gameInfoFields) {
15                 gameInfoField.setAccessible(true);
16                 if (gameRequiredInfoConfig.contains(gameInfoField.getName()) && Objects.isNull(gameInfoField.get(tGameInfo))) {
17                     if (gameInfoType == GameInfoType.BASE.getValue()) {
18                         throw new GameRequiredInfoException("該游戲基本信息不完整,請前往<a href=\'" + applicationConfig.getBaseUrl() + "/game/editBaseInfo?gameId=" + gameId + "\' target=\"_blank\">游戲詳情</a>頁面完善資料后重試!");
19                     } else if (gameInfoType == GameInfoType.PAY.getValue()) {
20                         throw new GameRequiredInfoException("該游戲支付信息不完整,請前往<a href=\'" + applicationConfig.getBaseUrl() + "/game/editInterfaceInfo?gameId=" + gameId + "\' target=\"_blank\">游戲詳情</a>頁面完善資料后重試!");
21                     } else if (gameInfoType == GameInfoType.PACKAGE.getValue()) {
22                         throw new GameRequiredInfoException("該游戲安裝包信息不完整,請前往<a href=\'" + applicationConfig.getBaseUrl() + "/game/editPackageInfo?gameId=" + gameId + "\' target=\"_blank\">游戲詳情</a>頁面完善資料后重試!");
23                     }
24                 }
25             }
26         } catch (IllegalAccessException e) {
27             throw new GameRequiredInfoException("獲取字段信息失敗,后台配置錯誤");
28 
29         }
30     }

  稍微解釋一下:tGameInfo是我們通過mbg從數據庫取出來的bean,通過反射我們可以查看這個字段的值是否符合要求,gameRequiredInfoConfig是我們將需要校驗的字段做成了一個spring配置,gameInfoType是我們自定義的枚舉類型,便於個性化的向前端展示校驗的結果。GameRequiredInfoException是我們自定義的異常,便於我們在上層調用時統一捕捉這個異常。

下面是我們做的spring的配置,配置了不同類型的游戲需要校驗的字段信息:

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <beans xmlns="http://www.springframework.org/schema/beans"
 3        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4        xsi:schemaLocation="http://www.springframework.org/schema/beans
 5         http://www.springframework.org/schema/beans/spring-beans.xsd">
 6 
 7     <bean id="gameRequiredInfoConfig" class="com.jy.game.gamecms.config.GameRequiredInfoConfig">
 8         <!--所有游戲必填字段-->
 9         <property name="baseGameRequiredInfo">
10             <list>
11                 <value>gameName</value>
12                 <value>initial</value>
13                 ...
14             </list>
15         </property>
16         <!--安卓游戲必填字段-->
17         <property name="androidGameRequiredInfo">
18             <list>
19                 <value>needAuth</value>
20                 <value>capture</value>
21                 <value>captureAspectRatio</value>
22                 ...
23             </list>
24         </property>
25         <!--ios游戲必填字段-->
26         ...
27         <!--web游戲必填字段-->
28        ...
29         <!--h5游戲必填字段-->
30         ...
31         <!--ios安裝包信息必填字段-->
32         ...
33     </bean>
34 </beans>

下面開始執行檢查流程:對於所有的游戲,都要檢查基本信息的必填字段:

 1  /**
 2      * 校驗游戲基本信息必填字段
 3      *
 4      * @param tGameInfo
 5      * @throws GameRequiredInfoException
 6      */
 7     private void checkBaseGameRequiredInfo(TGameInfo tGameInfo) throws GameRequiredInfoException {
 8 
 9         //先校驗公共字段
10         List<String> baseGameRequiredInfo = gameRequiredInfoConfig.getBaseGameRequiredInfo();
11         //接入sdk,還要校驗sdk類型
12         if (1 == tGameInfo.getIqiyiSdk().intValue()) {
13             baseGameRequiredInfo.add("sdkType");
14         }
15         reflectCheckRequiredInfo(tGameInfo, baseGameRequiredInfo, tGameInfo.getGameId(), GameInfoType.BASE.getValue());
16 
17         //校驗安卓特殊必填字段
18         if (tGameInfo.getTerminal().intValue() == Terminal.ANDROID.getValue()) {
19             List<String> androidGameRequiredInfo = gameRequiredInfoConfig.getAndroidGameRequiredInfo();
20             reflectCheckRequiredInfo(tGameInfo, androidGameRequiredInfo, tGameInfo.getGameId(), GameInfoType.BASE.getValue());
21             //校驗ios特殊必填字段
22         } else if (tGameInfo.getTerminal().intValue() == Terminal.IOS.getValue()) {
23            //...
24             //校驗h5特殊必填字段
25         } else if (tGameInfo.getTerminal().intValue() == Terminal.ANDROID_H5.getValue()) {
26             //...
27             //校驗pc_web特殊必填字段
28         } else if (tGameInfo.getTerminal().intValue() == Terminal.PC_WEB.getValue()) {
29            //...
30         }
31     }

我們可以看到,只需要從數據庫取出這個游戲的信息,再根據游戲的類型,一起和我們做成的配置作為參數傳入我們上面封裝好的方法中,就可以完成所有的字段校驗。

最后,我們只需要在service里根據需求執行校驗流程,調用相應的方法即可:

 1             //執行檢查流程
 2             try {                 //安卓檢驗運營包信息
 9                 if (dbGameInfo.getTerminal().intValue() == Terminal.ANDROID.getValue()) {
10                     checkAndroidOperatePackage(dbGameInfo);
11                 }
12                 //檢查基本必填字段
13                 checkBaseGameRequiredInfo(dbGameInfo);
14                 //接入sdk還要檢查支付必填字段
15                 if (dbGameInfo.getIqiyiSdk().intValue() == 1) {
16                     checkGamePayRequiredInfo(dbGameInfo.getGameId());
17                 }
18                 //IOS要校驗安裝包的必填信息
19                 if (dbGameInfo.getTerminal() == Terminal.IOS.getValue()) {
20                     checkIosPackageInfo(dbGameInfo.getGameId());
21                 }
22             } catch (GameRequiredInfoException e) {
23                 return AjaxResult.fail(e.getMessage());
24             }

這個實現方案,最大的好處就是,后面的擴展和修改特別的方便,只需要改動配置就行,避免了硬編碼,做到了業務和代碼的解耦,並且可讀性很高,實現起來邏輯簡單明了。

當然,缺點也是有的,那就是反射的性能沒有手動去判斷的實現方式高,並且,增加了代碼運行的不確定性。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM