Ch12.5-3 : Exception
[Exception]
錯誤是個開發程式時必經的過程,如何讓錯誤的訊息,更容易讓開發者知道,程式到底錯誤在哪裡,我找了一些參考資料後,尋找到fastClasspathScanner這個免費開源專案,只要設定好要尋找專案的package就可以找出專案中相關的類別,利用這個特性,我們就可以把錯誤訊息的StackTraceElement進行比較,找出只屬於我們專案內的錯誤,減少不要相關錯誤訊息印出,加速我們找到問題的根源,那我們就開始實作吧!
[相關程式路徑]
build.sbt
app
└ controllers
└ AcidController.java <-- ACID主要controller
└ test
└ db
└ Test_FooLocalDAO.java <-- 測試程式
└pojo
└ error
└ ErrorLogInfo.java <-- 錯誤紀錄資訊
RelatedClass.java <-- 錯誤相關類別
└ error
└ HelperException.java <-- 例外發生時候,利用Helper去尋找相關的程式錯誤
└ utils
└ tool
└ Utils_FastScan.java <--- 根據我們定義的package,去尋找相關的類別
[相關程式]
build.sbt
首先需要先新增fast-classpath-scanner到我們的build.sbt,這樣我們專案就可以使用這個專案的相關程式了。
libraryDependencies ++= Seq(
...
// fast-classpath-scanner
"io.github.lukehutch" % "fast-classpath-scanner" % "2.0.9",
...
)
utils.tool.Utils_FastScan.java
這隻類別主要前面的String陣列是目前該專案的package的名稱,放在前面全域變數的宣告。getProjectClasses這個方法會根據classPackages去找出專案相關的類別。最後getProjectClassMethodMapping除了找到專案的類別之外,會去把每一隻類別的方法,組成一個Map物件來存放,這樣我們就可以利用類別名稱,再去比對方法,就可以找到相關類別的方法。
package utils.tool;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner;
public enum Utils_FastScan {
un;
/**
* Add your project top level package
*/
final static String[] classPackages
= {"annotation" , "aop" , "common" , "error" , "filters" , "modules" ,
"pojo" , "services" , "utils" , "views" , "test"};
/**
* According classPackages to find our classes
*/
public List<String> getProjectClasses() {
List<String> classNames = new ArrayList<String>();
FastClasspathScanner fastScaner = new FastClasspathScanner(classPackages);
classNames.addAll(fastScaner.scan().getNamesOfAllClasses());
return classNames;
}
/**
* According class to Map our class the Method
*/
public Map<String , HashMap<String , String>> getProjectClassMethodMapping(){
Map<String , HashMap<String , String>> classMaps = new HashMap<String , HashMap<String , String>>();
List<String> classNames = getProjectClasses();
for(String className : classNames){
Method[] classMethods;
try {
classMethods = Class.forName(className).getMethods();
HashMap<String , String> methodMaps = new HashMap<String , String>();
for(int index = 0 ; index < classMethods.length ; index++){
methodMaps.put(classMethods[index].getName(), classMethods[index].getName());
}
classMaps.put(className, methodMaps);
} catch (SecurityException e) {
} catch (ClassNotFoundException e) {
}
}
return classMaps;
}
}
app.pojo.error.RelatedClass.java
這個類別是用來紀錄相關類別被呼叫的方法,以及例外發生時,相關程式丟出錯誤的行數。
package pojo.error;
public class RelatedClass {
/** 類別名稱 */
private String className;
/** 類別方法名稱 */
private String method;
/** 行數 */
private String lineNumber;
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public String getLineNumber() {
return lineNumber;
}
public void setLineNumber(String lineNumber) {
this.lineNumber = lineNumber;
}
}
app.pojo.error.ErrorLogInfo
這個類別是當程式發生例外的時候,要儲存的資訊。
package pojo.error;
import java.util.List;
public class ErrorLogInfo {
/** 錯誤原因 */
private String casue;
/** 錯誤訊息 */
private String message;
/** 錯誤訊息 */
private String localMessage;
/** 發生例外的類別,所擁有的inputs */
private List<Object> inputs;
/** 發生例外後,我們專案相關的類別 */
private List<RelatedClass> relatedClasses;
public String getCasue() {
return casue;
}
public void setCasue(String casue) {
this.casue = casue;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getLocalMessage() {
return localMessage;
}
public void setLocalMessage(String localMessage) {
this.localMessage = localMessage;
}
public List<Object> getInputs() {
return inputs;
}
public void setInputs(List<Object> inputs) {
this.inputs = inputs;
}
public List<RelatedClass> getRelatedClasses() {
return relatedClasses;
}
public void setRelatedClasses(List<RelatedClass> relatedClasses) {
this.relatedClasses = relatedClasses;
}
}
app.error.HelperException.java
這個Helper,第一個參數主要接收Exception 的錯誤物件,第二個傳入的參數是可以0~N的物件,主要會傳入參數是發生例外的類別時,如果有傳入參數的話,我們可以接受這些參數,來幫助我們辨別是否例外跟我們傳入的參數或物件有關係。
package error;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import play.libs.Json;
import pojo.error.ErrorLogInfo;
import pojo.error.RelatedClass;
import utils.tool.Utils_FastScan;
public enum HelperException {
un;
/**
* <pre>
* Check 1 : 確認是否有 Exception
* Check 2 : 印出相關errorMessage
* Check 3 : 若有input,印出相關input的參數
* Check 4 : 印出相關Class method , line number error
* </pre>
*/
public String genException(Exception e , Object... inputs) {
if(e == null){
return "";
}
ErrorLogInfo errorLogInfo = new ErrorLogInfo();
errorLogInfo.setCasue(e.getCause()!=null ? e.getCause().toString() : "null");
errorLogInfo.setMessage(e.getMessage());
errorLogInfo.setLocalMessage(e.getLocalizedMessage());
errorLogInfo.setInputs(Arrays.asList(inputs));
Map<String , HashMap<String , String>> classMaps = Utils_FastScan.un.getProjectClassMethodMapping();
List<RelatedClass> relatedClasses = new ArrayList<RelatedClass>();
for (StackTraceElement ele : e.getStackTrace()) {
if(classMaps != null &&
classMaps.get(ele.getClassName()) != null &&
classMaps.get(ele.getClassName()).get(ele.getMethodName()) != null ){
RelatedClass realtedClass = new RelatedClass();
realtedClass.setClassName(ele.getClassName());
realtedClass.setMethod(ele.getMethodName());
realtedClass.setLineNumber(Integer.toString(ele.getLineNumber()));
relatedClasses.add(realtedClass);
}
}
errorLogInfo.setRelatedClasses(relatedClasses);
play.Logger.error(Json.toJson(errorLogInfo).toString());
return Json.toJson(errorLogInfo).toString();
}
}
app.test.db.Test_FooLocalDAO.java
針對我們寫好的HelperException,來寫case3的測試案例。
package test.db;
import org.apache.ibatis.session.SqlSessionManager;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import com.google.inject.Guice;
import com.google.inject.Injector;
import error.HelperException;
import pojo.web.signup.request.SignupRequest;
import services.WebService;
public class Test_FooLocalDAO extends AbstractFooDAO{
static Test_FooLocalDAO dao;
@BeforeClass
public static void before(){
play.Logger.info("Test_FooLocalDAO , test case start");
Injector injector = Guice.createInjector(new modules.MyBatisModule());
dao = new Test_FooLocalDAO();
dao.sqlSessionManager = injector.getInstance(SqlSessionManager.class);
dao.webService = injector.getInstance(WebService.class);
dao.fooServiceImpl = injector.getInstance(FooServiceImpl.class);
}
...
@Test
public void case3(){
play.Logger.info("----------------------------------------------");
play.Logger.info("Case 3 Test Annation Transactional rollback with HelperException - start");
SignupRequest request = new SignupRequest();
try{
request.setEmail("[email protected]");
request.setUsername("333");
request.setPassword("333");
dao.testErrorWithAnnotationTransation(request);
} catch (Exception e) {
HelperException.un.genException(e , request );
}
play.Logger.info("Test Annation Transactional rollback - end");
play.Logger.info("----------------------------------------------");
}
...
@AfterClass
public static void after(){
play.Logger.info("Test_FooLocalDAO , test case finish");
}
}
app.controllers.AcidController.java
測試程式完成之後,我們就可以把這段程式,寫到controller測試是否可以印出相關例外程式了。
package controllers;
import javax.inject.Inject;
import error.HelperException;
import play.mvc.Controller;
import play.mvc.Result;
import pojo.web.signup.request.SignupRequest;
import test.db.FooDAO;
/**
* ACID testing
*
* TEST CASE : @link {Test_FooLocalDAO}
*/
public class AcidController extends Controller {
@Inject
FooDAO fooDao ;
public Result annotationACIDWithHelperException(){
String tewat = "";
SignupRequest request = new SignupRequest();
try{
request.setEmail("[email protected]");
request.setUsername("333");
request.setPassword("333");
fooDao.testErrorWithAnnotationTransation(request);
} catch (Exception e) {
e.printStackTrace();
tewat = HelperException.un.genException(e,request);
}
return ok("annotationACID With HelperException rollback Testing " +
"\n Error info = " + tewat);
}
}
conf.routes
寫下對應的網址與呼叫的controller
# http://127.0.0.1:9000/acid/annotationACIDWithHelperException
GET /acid/annotationACIDWithHelperException controllers.AcidController.annotationACIDWithHelperException()
Offline CASE1:
當錯誤發生時,會根據FastClasspathScanner與Exception的比較之後,會把相關結果放到EerrorLogInfo,這樣就可以大幅度減少例外訊息時,更為精準的提示使用者發生的錯誤訊息。
[info] application - Test_FooLocalDAO , test case start
[info] application - ----------------------------------------------
[info] application - Case 3 Test Annation Transactional rollback with HelperException - start
[error] application - {"casue":"java.lang.ArithmeticException: / by zero","message":"insert database error",
"localMessage":"insert database error","inputs":[{"email":"[email protected]","username":"333","password":"333",
"retypePassword":null}],"relatedClasses":[{"className":"test.db.AbstractFooDAO","method":"testErrorWithAnnotationTransation"
,"lineNumber":"46"},{"className":"test.db.Test_FooLocalDAO","method":"case3","lineNumber":"70"}]}
[info] application - Test Annation Transactional rollback - end
[info] application - ----------------------------------------------
[info] application - Test_FooLocalDAO , test case finish
Online CASE1:
測試之後,我們就可以更容易釐清錯誤發生時,相關的程式是哪些,更容易讓我們去找出錯誤的問題點。
Test URL : http://127.0.0.1:9000/acid/annotationACIDWithHelperException
annotationACID With HelperException rollback Testing
Error info = {"casue":"java.lang.ArithmeticException: / by zero","message":"insert database error",
"localMessage":"insert database error","inputs":[{"email":"[email protected]","username":"333","password":"333",
"retypePassword":null}],"relatedClasses":[{"className":"test.db.AbstractFooDAO",
"method":"testErrorWithAnnotationTransation","lineNumber":"46"}]}
[Final]
這個小節學習使用別人寫好的專案,來去整理與設計一個錯誤訊息提示的功能,可以大幅降低看例外發生時的錯誤訊息。加快錯誤時可以找到合適的修正方式。
[Reference]
1.can-you-find-all-classes-in-a-package-using-reflection
http://stackoverflow.com/questions/520328/can-you-find-all-classes-in-a-package-using-reflection
2.fast-classpath-scanner