2018年1月25日 星期四

基本的 Annotation (JUnit4 一)

官網連結,左下有很多連結,JavaDocs 是 API

※設定

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

※會依賴 hamcrest-core 1.3,此包可以使用 Matcher,這樣子 assertThat 方法就可以使用了

※JUnit 不可以用在多執行緒,main 執行緒一跑完,其他的執行緒也沒了,解決方法如下:
1.可以在主執行緒寫 sleep 睡久一點,超過其他執行緒的時間就可以了

2.使用 join,讓其他執行緒先跑完,如「執行緒名稱」.join(),就會先執行完該執行緒才會執行其他執行緒,如果有 5 個執行緒,那5個都得這樣做

3.使用 CountDownLatch 類別, 裡面有 await 和 countDown 方法

可看 JUnitCore 裡的 main 方法,使用的是 System.exit



※@BeforeClass、@Before、@AfterClass、@After、@Test、@Ignore

@BeforeClass
public static void start1() {
    System.err.println("class之前執行1");
}
    
@BeforeClass
public static void start2() {
    System.err.println("class之前執行2");
}
    
@Before
public void begin1() {
    System.out.println("方法之前執行1");
}
    
@Before
public void begin2() {
    System.out.println("方法之前執行2");
}
    
@After
public void end1() {
    System.out.println("方法之後執行1");
}
    
@After
public void end2() {
    System.out.println("方法之後執行2\r\n");
}
    
@AfterClass
public static void theEnd1() {
    System.err.println("class之後執行1");
}
    
@AfterClass
public static void theEnd2() {
    System.err.println("class之後執行2");
}
    
@Test
public void bruceTest1() {
    System.out.println("測試1");
}
    
@Test
public void bruceTest2() {
    System.out.println("測試2");
}
    
@Ignore
@Test
public void bruceTest3() {
    System.out.println("測試3");
}

※結果(注意執行順序):
class之前執行2
class之前執行1
方法之前執行2
方法之前執行1
測試1
方法之後執行1
方法之後執行2

方法之前執行2
方法之前執行1
測試2
方法之後執行1
方法之後執行2

class之後執行1
class之後執行2

※@BeforeClass、@AfterClass 一定要 static,錯誤會提示

※@BeforeClass、@AfterClass、@Before、@After、@Test,都只能寫在方法上,而且都沒有參數,也沒有回傳值,也都只能是 public

※@Test 才會執行,但如果又加上 @Ignore 就會忽略

※寫@Ignore 和不寫@Test 的差別如下圖:
紅框會發現有 skipped,此行表示執行了 3 個@Test 的方法,但有一個忽略了,第二個紅框有可以發現圖示是白色的;如果不寫@Test,那就什麼都沒有



※@Test、@Ignore 的參數

@Test(expected = ArithmeticException.class)
public void bruceTest1() {
    System.out.println(1 / 0);
}
    
@Test(timeout = 1000)
public void bruceTest2() {
    try {
        Thread.sleep(1100);
        System.out.println("哈哈哈");
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}
    
@Ignore("忽略理由")
@Test
public void bruceTest3() {
    System.out.println("測試3");
}

※@Ignore 可以寫在方法或類別上,忽略方法或忽略整個類別,參數可以寫忽略的理由

※@Test 的屬性 timeout 的單位是毫秒,如果超過這個時間就會報錯

※@Test 的屬性 expected 是預期會出現的 Exception,如果不出現就會報錯

2018年1月6日 星期六

Spring、Spring MVC 設定檔整合 (Spring3.x MVC 十)

雖然可以只用 spring MVC 的設定檔就可以了,但要整合也是 OK 的,但要注意整合的問題

※整合時會出現注入兩次

spring 可以有很多設定檔,可用 web.xml,或者在設定檔使用 import 標籤將其他的設定檔抓進來,但好幾個 spring 設定檔都掃瞄一樣的包,就算設了很多次,不管使用 web.xml 或 import 標籤,都只會注入一次,以上的情形,spring MVC也是一樣

但這兩個設定檔是分開的,兩個都掃瞄一樣的包,會出現兩次


※java bean

package controller;
    
@Controller
@RequestMapping("/ooo/xxx/")
public class XxxAction {
    public XxxAction() {
        System.out.println("XxxAction");
    }
}




package service;
    
@Service
public class XxxServiceImpl {
    public XxxServiceImpl(){
        System.out.println("XxxServiceImpl");
    }
}




※設定檔

※spring 設定檔
<context:component-scan base-package="controller, service" />



※spring MVC 設定檔
<import resource="init.TestDispatcherServlet-servlet2.xml" />
<context:component-scan base-package="controller, service" />




※web.xml

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/applicationContext*.xml</param-value>
</context-param>
    
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
    
<servlet>
    <servlet-name>testServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/init.TestDispatcherServlet-servlet.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
    
<servlet-mapping>
    <servlet-name>testServlet</servlet-name>
    <url-pattern>*.mvc</url-pattern>
</servlet-mapping>

※啟動伺服器後,會看到 console 會印出兩次 java bean 建構子的內容


※解決方式

可以使用掃瞄不同包來解決,如 spring 掃瞄 controller、spring MVC 掃瞄 service
也可以使用如下的方式:

<context:component-scan base-package="controller, service" use-default-filters="false">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
    <context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice" />
</context:component-scan>




<context:component-scan base-package="controller, service" use-default-filters="false">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
</context:component-scan>

※use-default-filters 預設是 true,表示可掃瞄 @Controller、@Service…等的包,將它變成 false 就沒辦法掃了,然後子標籤在和它說可以掃瞄什麼類型的注解

※如果使用 annotation,可以用 @ComponentScan(useDefaultFilters = false, includeFilters = { @Filter(type = FilterType.ANNOTATION, value = App.class) })
.FilterType.ASSIGNABLE_TYPE 將指定的 class 加入 bean id
.FilterType.CUSTOM 寫個類別繼承 TypeFilter 後,即可使用




※spring 抓 spring MVC 注入的變數

spring 就像個全域變數, spring MVC 像個區域變數
區域變數知道有全域變數,但全域變數不知有區域變數
所以 spring MVC 可以抓 spring 的注入屬性;相反則不能

以上個例子來說,假設 controller 是 spring 設定檔設定的;而 service 是 spring MVC 設定的
那 service 使用 @Autowire 抓 XxxAction,伺服器啟動成功;
controller 使用 @Autowire 抓 XxxServiceImpl,伺服器啟動失敗

2018年1月2日 星期二

攔截器、例外處理 (Spring3.x MVC 九)

※攔截器

※HandlerInterceptor

public class HelloInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        System.out.println("A1");
        return true;
    }
    
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
            ModelAndView modelAndView) throws Exception {
        System.out.println("A2");
    }
    
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        System.out.println("A3");
    }
}

※寫四支一模一樣的,分別是 ABCD 的 1~3

※preHandle 執行在目標方法之前
postHandle 執行在目標方法之後、view 之前
afterCompletion view 之後

※preHandle 回傳 false,那之後的都不會執行


※spring 設定


mapping 是映射什麼路徑
exclude-mapping 是排除路徑

<mvc:interceptors>
    <bean class="init.HelloInterceptor" />
    
    <mvc:interceptor>
        <mvc:mapping path="/**" />
        <bean class="init.HelloInterceptor2" />
    </mvc:interceptor>
    
    <mvc:interceptor>
        <mvc:mapping path="/ooo/xxx/a/b/*.mvc" />
        <bean class="init.HelloInterceptor3" />
    </mvc:interceptor>
    
    <mvc:interceptor>
        <mvc:mapping path="/**" />
        <mvc:exclude-mapping path="/ooo/xxx/a/*/*.mvc" />
        <bean class="init.HelloInterceptor4" />
    </mvc:interceptor>
</mvc:interceptors>
    
<mvc:view-controller path="ooo/xxx/b/a/c.mvc" view-name="jump1" />
<mvc:view-controller path="ooo/xxx/a/b/c1.mvc" view-name="jump1" />
<mvc:view-controller path="ooo/xxx/a/b/c2.mvc" view-name="jump1" />

※可以不加 <mvc:annotation-driven />

※HelloInterceptor、HelloInterceptor2 可以說是一樣的

※注意 exclude-mapping 最上面一定要有 mapping,否則會報錯,也就是先指定一個大範圍,然後排除,排除可以寫好幾個


※測試

<a href="ooo/xxx/b/a/c.mvc">c</a>
<a href="ooo/xxx/a/b/c1.mvc">c1</a>
<a href="ooo/xxx/a/b/c2.mvc">c2</a>

※第一個執行 ABD,其他兩個都是 ABC


※執行順序

preHandle:寫在設定檔的前面最先執行,全部的 preHandle執行完才執行 postHandle
postHandle:寫在設定檔的前面最後執行,全部的 postHandle 執行完才執行 afterCompletion
afterCompletion :寫在設定檔的前面最後執行

preHandle 是正序;postHandle、afterCompletion 執行順序是倒序



※例外處理

使用 @ExceptionHandler

※controller

@RequestMapping("exception")
public String testException(Integer i) {
    System.out.println(2/i);
    return "hello";
}
    
@ExceptionHandler(ArithmeticException.class)
public String exceptionMethod() {
    System.out.println("我錯了");
    return "errPage";
}

※回傳的字串是配合 spring 設定檔的 InternalResourceViewResolver

※測試

<a href="ooo/xxx/exception.mvc?i=0">exception</a>

※網址是不變的



※不能使用 Map

@ExceptionHandler(ArithmeticException.class)
public ModelAndView exceptionMethod(Exception ex /*, Model model*/) {
    System.out.println("我錯了" + ex);
    ModelAndView mav = new ModelAndView("errPage");
    mav.addObject("excep", ex);
    return mav;
}

※當然包括子類 Model,只是放在參數裡,都還沒有 put,就 500了,想塞值到前端,要用 ModelAndView


※多個 @ExceptionHandler

@ExceptionHandler(ArithmeticException.class)
public String exceptionMethod(Exception ex) {
    System.out.println("A");
    return "errPage";
}
    
@ExceptionHandler(Exception.class)
public String exceptionMethod2(Exception ex) {
    System.out.println("B");
    return "errPage";
}

※會以完全匹配為主,沒有完全匹配才會退而求其次


※全域的例外處理 @ControllerAdvice

@ControllerAdvice
public class GlobalException {
    @ExceptionHandler(ArithmeticException.class)
    public String exceptionMethod(Exception ex) {
        System.out.println("C");
        return "errPage";
    }
    
    @ExceptionHandler(Exception.class)
    public String exceptionMethod2(Exception ex) {
        System.out.println("D");
        return "errPage";
    }
}

※區域例外處理全部都沒有 @ExceptionHandler 才會執行全域的  @ControllerAdvice

※全域必需設定
1. <context:component-scan base-package="controller" />
    屬性 use-default-filters 不能是 false,預設是 true
2. <mvc:annotation-driven />
否則有寫也不會執行



※預設的例外處理

看原碼 DefaultHandlerExceptionResolver.doResolveException,Eclipse 可使用 Ctrl + Shift + T
以 3.2.13 版為例,會發現有 13 個預設的例外處理
NoSuchRequestHandlingMethodException
HttpRequestMethodNotSupportedException
HttpMediaTypeNotSupportedException
HttpMediaTypeNotAcceptableException
MissingServletRequestParameterException
ServletRequestBindingException
ConversionNotSupportedException
TypeMismatchException
HttpMessageNotReadableException
HttpMessageNotWritableException
MethodArgumentNotValidException
MissingServletRequestPartException
BindException

※上圖為預設的,下圖為修改過的,想修改就必需如下設定


※自訂例外

@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR, reason = "我錯了")
public class DefinedSelfException extends RuntimeException {}

※HttpStatus 有很多,隨便選即可

※controller

// @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR, reason = "我錯了2")
@RequestMapping("definedSelfExp")
public String definedSelfExp(Integer i) {
    if (i == 0) throw new DefinedSelfException();
    
    System.out.println(2 / i);
    return "hello";
}

※@ResponseStatus 若寫在方法上,i == 0 時不變;但若不是 0,不會跳頁,會產生例外,但寫在這裡的中文會亂碼,寫在另外一支 class 不會,這個我沒解

※測試

<a href="ooo/xxx/definedSelfExp.mvc?i=0">definedSelfExp</a>





※SimpleMappingExceptionResolver


※spring 設定檔

<bean id="simpleMappingExceptionResolver" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
    <property name="exceptionMappings">
        <props>    
            <prop key="java.lang.ArithmeticException">arithmeticPage</prop>
            <prop key="java.lang.ArrayIndexOutOfBoundsException">arrayIndexPage</prop>
        </props>
    </property>
    <property name="exceptionAttribute" value="xxx" />
</bean>
    
<mvc:default-servlet-handler />
    
<mvc:annotation-driven />

※prop 包起來的字串是頁面名稱,配合 InternalResourceViewResolver

※exceptionAttribute 如果不寫,預設是 exception,可以在 JSP 用 EL


※controller

@RequestMapping("simpleExp")
// @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR, reason = "我錯了2")
public String simpleExp(Integer i) {
    // if (i == 0) throw new DefinedSelfException();
    System.out.println(2 / i);
    return "hello";
}

※方法上的 @ResponseStatus 是有用的


※測試

<a href="ooo/xxx/simpleExp.mvc?i=0">simpleExp</a>