軟件工程發展都到現在,“系統內部必須維持一定的層級關係,並確保每層之間的獨立性”,關於這一點應該沒有任何行內人士提出質疑,經典的MVC,MVVM等已經屬於剛入門的萌新都能朗朗上口的概念。但認識是一回事,執行就成了另外一種情況的,Controller層中直接操作Dao的現象等往往並不罕見。
1. 背景
在本次推行自動化代碼靜態檢查過程中,筆者在通讀CheckStyle規則時偶然發現ImportControl規則能夠非常完美地解決一些困擾筆者良久的問題。
官方文檔對於規則ImportControl的解釋爲"可以用來控制哪些能夠被引入,該規則可以細化到每個package或file。這對於確保項目的層級規則不被違反將是非常有幫助的,尤其是在於大型項目中"。
在日常開發中,我們經常會要求研發人員"不要在Controller層裏寫業務邏輯",“不要再調用這些過時的方法了”,"我們有封裝的工具類,不要直接調用這些第三方庫"等等。話說三遍淡如水與其一直人工盯着耗時耗力不討好,還不如讓忠誠可靠的機器來協助你。
2. 實踐
關於ImportControl規則的使用介紹,官方文檔裏已經寫得很詳細了(底部鏈接不僅有官方示例,還有Tomcat源碼中對該規則的使用示例),這裏筆者僅給出自己使用這些典型樣例(只是拋磚引玉,因此剛剛開始應用,還沒來得及優化)。
import-control.xml
文件
<?xml version="1.0"?>
<!DOCTYPE import-control PUBLIC
"-//Checkstyle//DTD ImportControl Configuration 1.4//EN"
"https://checkstyle.org/dtds/import_control_1_4.dtd">
<import-control pkg="com.qqqq.zzzz" strategyOnMismatch="allowed">
<!-- //////////////// 以下From Tomcat7.x org-import-control.xml start //////////////// -->
<!-- Anything in J2SE is OK but need to list javax by package as not
all javax packages are in J2SE -->
<allow pkg="java"/>
<allow class="javax.imageio.ImageIO"/>
<allow pkg="javax.management"/>
<allow pkg="javax.naming"/>
<allow pkg="javax.net"/>
<allow pkg="javax.rmi"/>
<allow pkg="javax.security"/>
<allow pkg="javax.sql"/>
<allow pkg="javax.xml"/>
<allow pkg="org.w3c.dom"/>
<allow pkg="org.xml.sax"/>
<allow pkg="org.ietf.jgss"/>
<!-- //////////////// 以上From Tomcat7.x org-import-control.xml end //////////////// -->
<disallow pkg="sun"/>
<allow pkg="cn.hutool"/>
<!-- 工具類層面不允許出現業務層面的方法 ;;; util包裏不允許出現其它包中的內容,只允許依賴我們自己的工具類和同層級的其它工具類 -->
<subpackage name="util" strategyOnMismatch="allowed">
<disallow pkg="com\.qqqq\.zzzz\.jjjj" regex="true"/>
<!--Dao組件-->
<disallow class="com.qqqq.zzzz.plateform.dao.ICoreDao"/>
<!--Qd組件-->
<disallow class="com.qqqq.qd.use.dao.ICoreDao"/>
<disallow class="com.qqqq.qd.use.service.IBaseService"/>
<disallow class="com.qqqq.qd.use.service.impl.BaseServiceImpl"/>
<!--不允許使用原生JSON二方庫組件-->
<disallow pkg="net\.sf\.json" regex="true" />
<disallow pkg="com\.alibaba\.fastjson" regex="true" />
<disallow pkg="com\.fasterxml\.jackson" regex="true" />
<!-- 以下寫法也可以
<disallow pkg="net.sf.json"/>
<disallow pkg="com.alibaba.fastjson" />
<disallow pkg="com.fasterxml.jackson" />
-->
<!-- 使用我們自己的日期格式化工具 -->
<disallow class="java.text.SimpleDateFormat" />
<!-- 不要在Action層之外的地方使用SessionUtil,util和Service層應該是無狀態的,如果Service層需要用戶信息,就從action層傳給它 -->
<disallow class="com.qqqq.web.utils.SessionUtil"/>
<disallow class="com.qqqq.support.web.utils.SessionUtil"/>
<!-- 必須使用slf4j接口操作日誌 -->
<disallow class="java.util.logging.Logger" />
<disallow class="org.apache.logging.log4j.Logger" />
<!--不允許歷史工具類-->
<disallow class=".*\.MD5" regex="true" />
<disallow class=".*\.Resource" regex="true" />
<disallow class=".*\.Base64Util" regex="true" />
<disallow class=".*\.CompressedFileUtil" regex="true" />
<disallow class=".*\.DataSourceUtil" regex="true" />
<disallow class=".*\.DesUtil" regex="true" />
<disallow class=".*].util\.FileUtil" regex="true" />
</subpackage>
<!--注意:關於這裏的name, 在官方例子中啓用regex模式時候使用的pkg, 但經過筆者測試以及翻閱相應的DTD, 這裏name是必須的, 作用相當於pkg. 這一塊官方文檔沒有更新 -->
<!--Action層不允許出現 Dao層方法-->
<!--Controller層不允許出現Dao層方法,也就是ICoreDao類,以及BaseService, 只允許Service-->
<!-- com.qqqq.zzzz.jjjj.modules.xtgl.action -->
<subpackage name="jjjj\.modules\.\w+\.action" regex="true" strategyOnMismatch="allowed">
<!--Dao組件-->
<disallow class="com.qqqq.zzzz.plateform.dao.ICoreDao"/>
<!--Qd組件-->
<disallow class="com.qqqq.qd.use.dao.ICoreDao"/>
<disallow class="com.qqqq.qd.use.service.IBaseService"/>
<disallow class="com.qqqq.qd.use.service.impl.BaseServiceImpl"/>
<!--不允許使用原生JSON二方庫組件-->
<disallow pkg="net\.sf\.json" regex="true" />
<disallow pkg="com\.alibaba\.fastjson" regex="true" />
<disallow pkg="com\.fasterxml\.jackson" regex="true" />
<!-- 使用我們自己的日期格式化工具 -->
<disallow class="java.text.SimpleDateFormat" />
<!--不允許歷史工具類-->
<disallow class=".*\.MD5" regex="true" />
<disallow class=".*\.Resource" regex="true" />
<disallow class=".*\.Base64Util" regex="true" />
<disallow class=".*\.CompressedFileUtil" regex="true" />
<disallow class=".*\.DataSourceUtil" regex="true" />
<disallow class=".*\.DesUtil" regex="true" />
<disallow class=".*].util\.FileUtil" regex="true" />
</subpackage>
<!--Service層不允許出現Servlet相關的類 -->
<!--Service層不允許出現request, response, 當然更不允許出現Controller層裏類, 不允許是com.qqqq.qd.use.dao.ICoreDao-->
<!-- com.qqqq.zzzz.jjjj.modules.xtgl.service -->
<subpackage name="jjjj\.modules\.\w+\.service" regex="true" strategyOnMismatch="allowed">
<!--Servlet-->
<disallow class="javax.servlet.http.HttpServletRequest"/>
<disallow class="javax.servlet.http.HttpServletResponse"/>
<!-- Struts2 -->
<disallow class="org.apache.struts2.ServletActionContext"/>
<disallow class="com.opensymphony.xwork2.ActionSupport"/>
<!--Qd組件-->
<disallow class="com.qqqq.qd.use.dao.ICoreDao"/>
<!--不允許使用原生JSON二方庫組件-->
<disallow pkg="net\.sf\.json" regex="true" />
<disallow pkg="com\.alibaba\.fastjson" regex="true" />
<disallow pkg="com\.fasterxml\.jackson" regex="true" />
<!-- 使用我們自己的日期格式化工具 -->
<disallow class="java.text.SimpleDateFormat" />
<!--不允許歷史工具類-->
<disallow class=".*\.MD5" regex="true" />
<disallow class=".*\.Resource" regex="true" />
<disallow class=".*\.Base64Util" regex="true" />
<disallow class=".*\.CompressedFileUtil" regex="true" />
<disallow class=".*\.DataSourceUtil" regex="true" />
<disallow class=".*\.DesUtil" regex="true" />
<disallow class=".*].util\.FileUtil" regex="true" />
</subpackage>
</import-control>
3. 關聯規則配置文件
最後將上面的ImportControll文件關聯到主體規則配置文件中。
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
"-//Puppy Crawl//DTD Check Configuration 1.3//EN"
"http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
<module name="Checker">
<!-- 每個java文件一個語法樹 -->
<module name="TreeWalker">
<module name="ImportControl">
<property name="file" value="D:/orgs/_checkstyle-pmd/import-control.xml"/>
<property name="path" value="^.*[\\/]src[\\/]main[\\/].*$" />
</module>
...
</module>
...
</module>
4. 注意事項
- 實踐中我們發現ImportControl裏定義的規則是有先後順序的,因此指向性更明確的應該放在前面。例如下面兩個規則:
<!--以下這兩個subpackage必須採用這樣的順序進行定義(也就是更精確的規則應該在上面), 否則無法發揮預想中的效果~禁止定義impl子包 -->
<subpackage name="xxxx\.modules\.\w+\.service\.impl" regex="true" strategyOnMismatch="allowed">
<disallow class=".*" regex="true" />
</subpackage>
<!-- com.kanq.zrzy.jrxt.modules.xtgl.service -->
<subpackage name="xxxx\.modules\.\w+\.service" regex="true" strategyOnMismatch="allowed">
...
</subpackage>
<!-- 本來想要定義成這樣的, 但執行時候檢測程序本身報錯, 就只能用上面那種形式了
<subpackage name="xxxx\.modules\.\w+\.service" regex="true" strategyOnMismatch="allowed">
<subpackage name="impl" regex="true" strategyOnMismatch="allowed">
<disallow class=".*" regex="true" />
</subpackage>
...
</subpackage>
-->
5. 最後
藉助機器,我們就能解放寶貴的人力來處理更多複雜性的問題,這不論對於企業還是員工本人都是有百利而無一害的事情。