漫谈反射在业务代码中的应用详解编程语言

    很多人都觉得写业务代码很枯燥,没有什么技术含量,大部分就是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             }

这个实现方案,最大的好处就是,后面的扩展和修改特别的方便,只需要改动配置就行,避免了硬编码,做到了业务和代码的解耦,并且可读性很高,实现起来逻辑简单明了。

当然,缺点也是有的,那就是反射的性能没有手动去判断的实现方式高,并且,增加了代码运行的不确定性。

原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/7945.html

(0)
上一篇 2021年7月18日
下一篇 2021年7月18日

相关推荐

发表回复

登录后才能评论