CheckStyle規則之ImportControl

軟件工程發展都到現在,“系統內部必須維持一定的層級關係,並確保每層之間的獨立性”,關於這一點應該沒有任何行內人士提出質疑,經典的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. 注意事項

  1. 實踐中我們發現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. 最後

藉助機器,我們就能解放寶貴的人力來處理更多複雜性的問題,這不論對於企業還是員工本人都是有百利而無一害的事情。

6. Links

  1. Office Site - ImportControl Rule
  2. Office - import-control.xml示例
  3. Tomcat源碼中對於ImportControl規則的使用
  4. SVN集成Checkstyle實現代碼自動靜態檢查
  5. stackoverflow - checkstyle-rule-to-limit-interactions-between-root-packages-with-importcontrol
  6. stackoverflow - checkstyle-import-control-rule-for-src-folder
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章