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>

※

2017年12月31日 星期日

上傳、國際化 (Spring3.x MVC 八)

※上傳


官網說要有 commons-fileupload

※maven、gradle

compile group: 'commons-fileupload', name: 'commons-fileupload', version: '1.3.3'
-------------------------------
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.3</version>
</dependency>

※


※spring 設定檔

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver" p:defaultEncoding="UTF-8" p:maxUploadSize="10240" />

※不需要 <mvc:annotation-driven />

※id 如果不是這個,就會出「Expected MultipartHttpServletRequest: is a MultipartResolver configured?」的錯,所以複製官網的就好



※controller

@RequestMapping(value = "test_upload", method=RequestMethod.POST)
public String upload(@RequestParam("file") MultipartFile file) {
    System.out.println("類型=" + file.getContentType());
    System.out.println("參數類型=" + file.getName());
    System.out.println("名稱=" + file.getOriginalFilename());
    System.out.println("大小=" + file.getSize());
    try {
        System.out.println("大小=" + file.getBytes().length);
        System.out.println("InputStream=" + file.getInputStream());
    } catch (IOException e) {
        e.printStackTrace();
    }
    return "hello";
}

※不寫 @RequestParam,就會出「Could not instantiate bean class [org.springframework.web.multipart.MultipartFile]: Specified class is an interface」的錯


※測試

<form action="ooo/xxx/test_upload.mvc" enctype="multipart/form-data" method="post">
    <input type="file" name="file" /> 
    <input type="reset" value="重置">
    <input type="submit" value="提交">
</form>

※如果使用 GET ,就會出「The current request is not a multipart request」的錯



※國際化

※自動轉換

瀏覽器是什麼語言會自動轉換

※properties

------------------ i18n.properties ------------------
hello=default hello
mvc.lang=default value
    
    
------------------ i18n_en_US.properties ------------------
hello=hello
mvc.lang=english {0}{1}
    
    
------------------ i18n_zh_CN.properties ------------------
hello=\u4F60\u597D
mvc.lang=\u7B80\u4E2D {0}{1}
    
    
------------------ i18n_zh_TW.properties ------------------
hello=\u4F60\u597D
mvc.lang=\u7E41\u4E2D {0}{1}

※hello=你好,mvc.lang=繁中、简中


※spring 設定檔

<context:component-scan base-package="controller" />
    
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/" />
    <property name="suffix" value=".jsp" />
</bean>
    
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"
    p:basename="i18n" p:defaultEncoding="UTF-8" />
    
<mvc:annotation-driven />
    
<mvc:view-controller path="ooo/xxx/next1.mvc" view-name="jump1" />

※連 id 都不能改



※頁面

------------------ index.jsp ------------------
<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
    
<fmt:bundle basename="i18n">    
    <fmt:message key="hello" />
</fmt:bundle>
    
<fmt:message key="hello" />
    
<a href="ooo/xxx/next1.mvc">next1</a>
    
    
------------------ jump1.jsp ------------------
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
    
<h1>jump1</h1>
<fmt:message key="mvc.lang">
    <c:choose>
        <c:when test="${not empty zero && not empty one}">
            <fmt:param value="${zero}" />
            <fmt:param value="${one}" />
        </c:when>
        <c:otherwise>
            <fmt:param value="" />
            <fmt:param value="" />
        </c:otherwise>
    </c:choose>
</fmt:message>
<a href="next2.mvc">next2</a>
    
    
------------------ jump2.jsp ------------------
<h1>jump2</h1>
${msg}<br />
<fmt:message key="mvc.lang"></fmt:message>

※fmt:bundle basename 是用 jstl 的功能

※注意 index 是第一頁,這時 spring 還沒有連到國際化的檔案,所以只使用第 9 行,就只會出現「???hello???」,按 next1 會有 request,所以再來就不用「fmt:bundle basename」了

※jump1 看起來看多,其實只是要把「{0}{1}」變不見而已

※注意 jump1 要跳到 jump2 的超連結,不用再加 ooo/xxx


※controller

package controller;
    
import java.util.Locale;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
    
@Controller
@RequestMapping("/ooo/xxx/")
public class XxxAction {
    @Autowired
    private ResourceBundleMessageSource messageSource;
    
    @RequestMapping("next2")
    public ModelAndView next2(Locale locale) {
        String zero = null, one = null, suc = null;
        if (Locale.TAIWAN.equals(locale)) {
            zero = "對";
            one = "嗎?";
            suc = "成功";
        } else if (Locale.PRC.equals(locale)) {
            zero = "对";
            one = "吗?";
            suc = "成功";
        } else if (Locale.US.equals(locale)) {
            zero = "all";
            one = " right?";
            suc = "success";
        }
    
        System.out.println(messageSource.getMessage("mvc.lang", new Object[] { zero, one }, locale));
    
        ModelAndView mav = new ModelAndView();
        mav.setViewName("jump2");
        mav.addObject("msg", suc);
        mav.addObject("zero", zero);
        mav.addObject("one", one);
        return mav;
    }
}

※注意替換 {0} 這種東西時,只有在 java 有用,顯示還是會將「{0}{1}」顯示出來,並不會在頁面替換,所以才有 zero、one 這兩個變數讓前端顯示



※手動轉換

頁面有超連結,想換什麼語言就換什麼語言,有三種,參考官網,這裡只寫 SessionLocaleResolver

※頁面

<a href="ooo/xxx/btnChangeLang.mvc?locale=en_US">english</a>
<a href="ooo/xxx/btnChangeLang.mvc?locale=zh_CN">简体中文</a>
<a href="ooo/xxx/btnChangeLang.mvc?locale=zh_TW">繁體中文</a>

※jump1 和 jump2 的頁面和自動轉換一樣


※controller

@RequestMapping("btnChangeLang")
public String btnChangeLang(Locale locale) {
    System.out.println(messageSource.getMessage("mvc.lang", null, locale));
    return "jump1";
}

※jump1 有個超連結,會到 next2 方法,方法和自動轉換一樣


※spring 設定檔

<context:component-scan base-package="controller" />
    
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/" />
    <property name="suffix" value=".jsp" />
</bean>
    
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver" />
    
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"
p:basename="i18n" p:defaultEncoding="UTF-8" />
    
<mvc:interceptors>
    <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor" />
</mvc:interceptors>
    
<mvc:default-servlet-handler />
    
<mvc:annotation-driven />

※id localeResolver、messageSource 不能改

※和自動轉換比,增加了 SessionLocaleResolver 和攔截器 LocaleChangeInterceptor,最後刪除了 mvc:view-controller

2017年12月29日 星期五

JSON (Spring3.x MVC 七)

※Maven、Gradle

def jacksonVer = '2.9.3'
    
compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: "${jacksonVer}"
compile group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: "${jacksonVer}"
// compile group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: "${jacksonVer}"
-------------------------------------------------------
<properties>
    <jacksonVer>2.9.3</jacksonVer>
</properties>
    
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>${jacksonVer}</version>
</dependency>
    
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-annotations</artifactId>
    <version>${jacksonVer}</version>
</dependency>
    
<!-- <dependency> -->
<!-- <groupId>com.fasterxml.jackson.core</groupId> -->
<!-- <artifactId>jackson-core</artifactId> -->
<!-- <version>${jacksonVer}</version> -->
<!-- </dependency> -->

※使用 @ResponseBody、@RequestBody 註解,不用下載這些 jar 包,還是 import 的到,但是只要其中一包沒下到,執行時都會出 406

※下載 jackson-databind 就會將 3 包都一起下載了,但 jackson-annotations 居然只有 2.9.0,所以我才將 jackson-annotations 加進去



※spring 設定檔

<mvc:default-servlet-handler />
<mvc:annotation-driven />

※一定要加 annotation-driven,不然執行時也會出 406

※default-servlet-handler 視情況加,第四篇有說明



※@ResponseBody

1.將資料轉成 JSON 後,回傳給客戶端
2.只能寫在方法上


※java bean

public class Book {
    private Integer id;
    private String name;
    private Integer price;
    private Date date;
    // setter/getter...
    
    public Book() {}
    
    public Book(Integer id, String name, Integer price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }
}

※注意建構子我沒加 date


※controller

@RequestMapping("resBody")
@ResponseBody
public List<Book> json() {
    List<Book> list = new ArrayList<>();
    list.add(new Book(1, "七龍珠", 50));
    list.add(new Book(2, "寶島少年", 80));
    list.add(new Book(3, "尼羅河的女兒", 70));
    return list;
}

※不加 @ResponseBody,呼叫時會出 404


※測試

$(_ => {
    $("#btn").click(_ => {
        $.ajax({ 
            url:"ooo/xxx/resBody.mvc",
            dataType:"json",
            contentType:"application/json",
            success:function(data){
                for(d of data){
                    console.info(d.id, d.name, d.price);
                    console.info("--------------------");
                }
            }
        });
    });
});
--------------------
<input id="btn" type="button" value="click me" />

※結果會發現不加的 date 也會出現,是null


※@RequestBody

1.前端傳集合過來,可使用這個標籤來接
2.使用時一定要用 POST,否則會出 400
3.只能寫在參數裡


※controller

@ResponseBody
@RequestMapping(value = "reqBody", method = RequestMethod.POST)
public List<Book> json(@RequestBody List<Book> listBook) {
    for (Book b : listBook) {
        System.out.println(b.getId());
        System.out.println(b.getName());
        System.out.println(b.getPrice());
        System.out.println("----------");
    }
    return listBook;
}

※此功能是前端傳集合過來,然後又傳回給客戶端

※@ResponseBody 一定要有,否則無法傳回,會 404



※測試

let array=[];  
array.push({"id":"1","name":"七龍珠", "price":"50"});  
array.push({"id":"2","name":"寶島少年", "price":"80"});         
array.push({"id":"3","name":"尼羅河的女兒", "price":"70"});
    
$("#btn2").click(_ => {
    $.ajax({ 
        url:"ooo/xxx/reqBody.mvc",
        type:"POST",
        dataType:"json",
        contentType:"application/json",
        data:JSON.stringify(array),
        success:function(data){
            for(d of data){
                console.info(d.id, d.name, d.price);
                console.info("--------------------");
            }
        }
    });
});
--------------------
<input id="btn2" type="button" value="按我" />

※



※HttpEntity

可以塞值到 HTTP Header 裡
不用寫 <mvc:annotation-driven />,也不用下載 jackson

※
@RequestMapping("resEntity")
public HttpEntity<String> resEntity(HttpServletRequest req) throws IOException {
    HttpHeaders head = new HttpHeaders();
    head.set("xxx", "ooo");
    
    // HttpEntity<byte[]> he = new HttpEntity<>(head);
    HttpEntity<String> he = new HttpEntity<>("bruce", head);
    return he;
}
----------------------------------------------
<a href="ooo/xxx/resEntity.mvc?">resEntity</a>

※注解那一行只有塞到 HTTP Header 裡,沒註解那一行還多一個顯示 bruce 在畫面上



※ResponseEntity

為 HttpEntity 的子類,多個狀態碼
可實現下載功能
※
@RequestMapping("resEntity")
public ResponseEntity<byte[]> resEntity(HttpServletRequest req) throws IOException {
    InputStream is = req.getServletContext().getResourceAsStream("/WEB-INF/xxx.txt");
    byte[] bArray = new byte[is.available()];
    is.read(bArray);
    
    HttpHeaders head = new HttpHeaders();
    head.add("Content-Disposition", "attachment;filename=downloadName.txt");
    
    return new ResponseEntity<byte[]>(bArray, head, HttpStatus.OK);
}
----------------------------------------------
<a href="ooo/xxx/resEntity.mvc?">resEntity</a>

※

2017年12月26日 星期二

資料挷定 (Spring3.x MVC 六)

※基本資料型別和 Wrapper、String,都能被 springmvc 轉換成功,但日期不行,會出 400且還沒有錯誤訊息

※@InitBinder

※java bean

public class Book {
    private Integer id;
    private String name;
    private Integer price;
    private Date date;
    
    // setter/getter...
}

※

※controller

@RequestMapping("book")
public String pojo(Book b) {
    System.out.println(b.getId());
    System.out.println(b.getName());
    System.out.println(b.getPrice());
    System.out.println(b.getDate());
    return "hello";
}
    
    
    
@InitBinder
public void binder(WebDataBinder bind) {
    bind.setDisallowedFields("id");
    
    DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
    df.setLenient(false);// 不給打數字
    bind.registerCustomEditor(Date.class, new CustomDateEditor(df, true)); // 第二個參數為可以空
}

※標 @InitBinder 的方法只能回傳 null

※第一個表示 id 設為空,所以前端塞值也沒用

※測試

<a href="ooo/xxx/book.mvc?id=99&name=book&price=500&date=2000-01-01">bind</a>

※



※@NumberFormat、@DateTimeFormat


@InitBinder 寫好後,每個使用上例的方式,都會去抓,例如我再寫一個 ComicBook,裡面一樣有日期欄位,且名稱也一樣,但可能一個只想要年月日,另一個是年月日時分秒,這時候可以使用這兩個 annotation,都是在 spring-context,jar 包裡的 org.springframework.format.annotation;


※
public class Book {
    private Integer id;
    private String name;
    
    @NumberFormat(style=Style.CURRENCY, pattern="#,###.#")
    private Integer price;
    
    @DateTimeFormat(iso=ISO.DATE, pattern="yyyy-MM-dd")
    private Date date;
    
    // setter/getter...
}

※@DateTimeFormat 裡的 iso 有預設值,這個 pattern 就是預設值,所以也可以不寫,其他預設值,點進去看有註解



※Hibernate 驗證

※官網有說明,要使用必需添加 <mvc:annotation-driven/> ,然後配合 @Valid 使用,但其實還要有 jar 包,否則 import 不到,可看 Hibernate Validator 第六版的文件
hibernate-validator 6.0.7 依賴三個 jar 包,validation-api、jboss-logging、classmate

 ※validation-api 裡的 javax.validation.constraints 的 API,有 22 個,然後每個都有 List,所以總共是 44 個,但 List 我怎麼試都是 500,網上也有說可能是規格錯誤



※教學的部分,我發現 4.3 版翻譯的較完全

※以下是看文檔翻譯的,我沒有每個都試過

空:

@Null:必需是 null
@NotNull:不能是 null
@NotBlank:不能是 null 或全是空格,只支援 CharSequence
@NotEmpty:不能是 null 或空,支援 CharSequence Collection Map Array


布林:

@AssertTrue:必需是 true
@AssertFalse:必需是 false


數字:

.@Mix、@Max 支援 6 種格式,byte short int long BigInteger BigDecimal


@Min(number):最小數字是什麼,所以必需是數字,而且要 >= 設定的數字,@Null是有效的

@Max(number):最大數字是什麼,所以必需是數字,而且要 <= 設定的數字,@Null是有效的



.@DecimalMin、@DecimalMax、@Digits 支援 7 種格式,byte short int long BigInteger BigDecimal CharSequence,要注意,並沒有 float double


@DecimalMin(number):最小數字是什麼,所以必需是數字,而且要 >= 設定的數字,@Null是有效的

@DecimalMax(number):最大數字是什麼,所以必需是數字,而且要 <= 設定的數字,@Null是有效的


.範圍

@Digits:(integer, fraction):必需是數字,且介於指定的範圍,@Null是有效的

@Size(min, max):必需介於指定的範圍,支援 CharSequence Collection Map array



正負數:

有四種,支援 8 種格式,byte short int long float double BigInteger BigDecimal
@Positive:必需為正數且不是0,@Null是有效的
@PositiveOrZero:必需為正數,0有效,@Null是有效的
@Negative:必需為負數且不是0,@Null是有效的
@NegativeOrZero:必需為負數,0有效,@Null是有效的

日期:

有四種,支援 16 種 格式
java.util.Date
java.util.Calendar
java.time.Instant
java.time.LocalDate
java.time.LocalDateTime
java.time.LocalTime
java.time.MonthDay
java.time.OffsetDateTime
java.time.OffsetTime
java.time.Year
java.time.YearMonth
java.time.ZonedDateTime
java.time.chrono.HijrahDate
java.time.chrono.JapaneseDate
java.time.chrono.MinguoDate
java.time.chrono.ThaiBuddhistDate

@PastOrPresent:必需是過去日期(包括現在日期),@Null是有效的
@Past:必需是過去日期,@Null是有效的
@Future:必需是未來日期,@Null是有效的
@FutureOrPresent:必需是未來日期(包括現在日期),@Null是有效的


其他:

@Pattern(regexp):必需合乎正則,只支援 CharSequence,@Null是有效的
@Email(regexp):必需是 email,只支援 CharSequence


※每個 annotation 都一定會有的三個屬性,message、groups、payload
message 是驗證不過時要顯示在控制台的訊息
其他兩個我沒看懂,參考這裡


※範例

設定好<mvc:annotation-driven/>後,如下

※java bean

public class Book {
    @Max(value=5, message="數字太大")
    // @Max.List({@Max(5),@Max(15),@Max(10)})
    private Integer id;
    
    @NotNull(message="不能為空")
    private String name;
    
    private Integer price;
    
    private Date date;
    
    // setter/getter...
}

※List 沒有試成功過

※controller

@RequestMapping("book")
public String pojo(@Valid Book b, BindingResult br) {
    System.out.println(b.getId());
    System.out.println(b.getName());
    System.out.println(b.getPrice());
    System.out.println(b.getDate());
    
    if(br.getErrorCount() > 0) {
        /*
        for(ObjectError oe:br.getAllErrors()) {
            System.out.println(oe.getObjectName());
            System.out.println(oe.getDefaultMessage());
    
            System.out.println();
            System.out.println("Arguments");
            for(Object o:oe.getArguments()) {
                System.out.println(o);
            }
    
            System.out.println();
            System.out.println("Codes");
            for(String s:oe.getCodes()) {
                System.out.println(s);
            }
        }
        */
    
        System.out.println("FieldErrors");
        for(FieldError fe:br.getFieldErrors()) {
            System.out.println(fe.getDefaultMessage());
            return "error";
        }
    }
    
    return "hello";
}

※<a href="ooo/xxx/book.mvc?id=10&name=book&price=100&date=2000-01-01">bind</a>

※如果不寫  @Valid,那 java bean 有寫跟沒寫一樣

※使用 BindingResult 可以判斷有沒有錯,跳轉到其他頁面



※hibernate-validator 裡 org.hibernate.validator.constraints 的 API


也有很多,只列出幾個常用的,標為棄用,我是用第六版試的,有可能第五版是 ok 的

@Length(min, max):必需介於指定的範圍
@Range(min, max):必需介於指定的範圍,應用在數字和字串
@CodePointLength(min, max):字串的長度必需在指定的範圍
@URL:必需是網址
@NotEmpty:標為棄用
@NotBlank:標為棄用
@Email:標為棄用

2017年12月23日 星期六

轉換器 (Spring3.x MVC 五)

.指的是前端發出 request 到 controller 的轉換,spring 已經幫我們實作很多常用的,但如有特殊需求,就要自己寫了

.官網連接 ,可以看出有三種方式

.目前只寫 converter,其他兩種有空再寫


※converter

※Java Bean

public class Book {
    private Integer id;
    private String name;
    private Integer price;
    // setter/getter...
    
    public Book() {}
    
    public Book(Integer id, String name, Integer price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }
}

※假設前端傳三個值為 id@name@price 這樣的格式,所以我在此類有三個屬性

※轉換類

@Component
public class TestConverter implements Converter<String, Book> {
    @Override
    public Book convert(String source) {
        String[] array = source.split("@");
        return new Book(Integer.valueOf(array[0]), array[1], Integer.valueOf(array[2]));
    }
}

※

※spring 設定檔
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean" p:converters-ref="testConverter" />
<mvc:annotation-driven conversion-service="conversionService" />

※conversion-service 不加會 500


※測試

@RequestMapping("book")
public String pojo(@RequestParam("xxx") Book b) {
    System.out.println(b.getId());
    System.out.println(b.getPrice());
    System.out.println(b.getName());
    return "hello";
}
------------------------------
<a href="ooo/xxx/book.mvc?xxx=123@toy@500">bookTransfer</a>

※如果不合乎自訂的轉換要求,會出現 400,描述為「The request sent by the client was syntactically incorrect.」

※注意:如果前端傳 Book 的屬性,如 id、name、price 只是給 Book 塞值,並不會去轉換



※傳特殊字元

public void bruceTest() throws UnsupportedEncodingException {
    final String encode = "UTF-8";
    final String and = URLEncoder.encode("&", encode);
    System.out.println(and);
    System.out.println(URLDecoder.decode(and, encode));
}
------------------------------
const and = encodeURIComponent("&");
console.log(and);
console.log(decodeURIComponent(and));

※上面是 java,下面是javascript,使用此方式,可以知道網址列的特殊字元的編碼為何

※假設想傳 ooo/xxx/book.mvc?xxx=123&toy&500,但「&」是 get 請求的關鍵字,所以要用「%26」取代,變成 ooo/xxx/book.mvc?xxx=123%26toy%26500

2017年12月22日 星期五

自定義 View、加載靜態資源 (Spring3.x MVC 四)

※自定義 View


※DefinedSelfView.java

@Component
public class DefinedSelfView implements View{
    @Override
    public String getContentType() {
        return "text/html";
    }
    
    @Override
    public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
            throws Exception {
        response.setContentType("text/html; charset=UTF-8");
        response.getWriter().println("耶!自定義的 View 耶");
    }
}

※繼承 View 後,實作兩個方法


※設定檔

<context:component-scan base-package="controller,view"></context:component-scan>
    
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/" />
    <property name="suffix" value=".jsp" />
</bean>
    
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver" p:order="50" />

※尤於有多個 ViewResolver,所以要加上 order,這個數字是較小的優先,InternalResourceViewResolver 的父類 UrlBasedViewResolver 已經有定義 order = Integer.MAX_VALUE;,所以基本上,隨便打個數字都比它優先


※測試

@Controller
@RequestMapping("/ooo/xxx/")
public class XxxAction {
    @RequestMapping("definedSelfView")
    public String getView() {
        return "definedSelfView"; // bean id
    }
}
------------------------------
<a href="ooo/xxx/definedSelfView.mvc">definedSelfView</a>

※



※加載靜態資源 

官網
在首頁加載 jquery,如果 web.xml 是全部給 spring 處理時,在伺服器起動時,會發現「No mapping found for HTTP request with URI [/SpringMVC/jquery-3.2.1.js] in DispatcherServlet with name 'testServlet'」,當然可用副檔名的方式來避免這個問題,但如果不想用這招,還可以用以下的方式

※web.xml

<servlet-mapping>
    <servlet-name>testServlet</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

※全部的資源都給 spring 處理,不管是給container處理、還是給 spring 管理,都必需這麼做才能體現他的功能


※給 container 處理

※XML 設定

<mvc:default-servlet-handler default-servlet-name="default" />
<mvc:annotation-driven />

※使用 default-servlet-handler 可將靜態資源交給 container 處理,預設名稱是 default,如此例的 default-servlet-name 可不用寫,大部分的 container 都會取名為 default,如果不是就加這個屬性

※本來不加 default-servlet-handler 是可以處理 @RequestMapping 的,但加了 default-servlet-handler,annotation-driven 不加的話,那就無法處理  @RequestMapping,所以一加就兩個都要加才行,看官網


※annotation 設定

@Component
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }
}

※如果預設名稱不是 default,可以寫在 enable() 裡面


※tomcat 裡的 web.xml

<servlet>
    <servlet-name>default</servlet-name>
    <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
    <init-param>
        <param-name>debug</param-name>
        <param-value>0</param-value>
    </init-param>
    <init-param>
        <param-name>listings</param-name>
        <param-value>false</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

※這只是從 tomcat 裡的 web.xml 貼出來參考,並不用改



※給 spring 管理


※紅框由上而下,分別是 靜態資源、spring設定檔、測試頁

※XML 設定

<mvc:resources mapping="/xxx/**" location="/WEB-INF/static/" />

※location 裡寫的是真實路徑,可用 classpath

※mapping 如有多個,可用逗號隔開

※這個意思表示 /WEB-INF/static 底下的目錄可用 xxx 取代


※測試

<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
    
<script src='<c:url value="/xxx/js/jquery-3.2.1.js" />'></script>
    
<!-- <script src="xxx/js/jquery-3.2.1.js"></script> -->
    
$( _ => {
    alert("xxx");
});

※註解那一行也行,就看你要不要用 JSTL 了,前面有「/」的差別


※annotation 設定

@Component
@EnableWebMvc
public class Xxx extends WebMvcConfigurerAdapter {
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/xxx/**").addResourceLocations("/WEB-INF/static/");
    }
}

※addResourceLocations 是 ... 參數,所以可以寫多個

※和 container 的 annotation 設定很像,差在覆寫不同的方法

2017年12月15日 星期五

Windows 批次檔-迴圈、if

※迴圈

help for 看文檔
在批次檔裡變數用兩個 %;cmd只要一個 % 即可
變數名有區分大小寫,其他沒有
如果批次檔沒有寫好,會閃一下就結束了


※檔案(不加參數)

@echo off
::列出當前路徑的所有檔案名稱,只有檔名
::for %%i in (*) do echo %%i
for %%i in (*) do (
    echo %%i
)
    
echo.
echo.
    
::列出指定路徑下的所有副檔名為txt的檔案名稱,路徑+檔名
for %%j in ("C:\Program Files\Java\jdk1.8.0_121\*.txt") do echo %%j
pause

※如有多個指令要操作,可像此例用「( )」包起來

※do 後面不能換行,至少要有「(」


※目錄(/d):directory

@echo off
::列出當前路徑的所有目錄名稱,只有目錄名稱
for /d %%i in (*) do echo %%i
    
echo.
echo.
    
::列出指定路徑的所有目錄名稱為j開頭的,路徑+目錄名
for /d %%j in ("C:\Program Files\Java\jdk1.8.0_121\j*") do echo %%j
pause

※


※遞迴(/r):recursive

@echo off
::列出當前路徑的所有檔案名稱,路徑+檔名
for /r %%i in (*) do echo %%i
    
echo.
echo.
    
::列出當前路徑的所有目錄名稱,路徑+目錄名且以\.結尾
for /r %%j in (.) do echo %%j
    
echo.
echo.
    
::列出指定路徑下的所有副檔名為txt的檔案名稱,路徑+檔名
for /r "C:\Program Files\Java\jdk1.8.0_121" %%k in (*.txt) do echo %%k
pause

※檔案和目錄都只有操作一層而已,但用遞迴可以從最外層到最裡層拆開


※列出(/L):list

@echo off
::列出-5到100,每次增加5的數字
for /L %%i in (-5,5,100) do echo %%i
    
echo.
echo.
    
::新增xxx1~xxx3的目錄
for /L %%j in (1,1,3) do mkdir xxx%%j && echo 新建xxx%%j目錄成功
pause

※第一和第三個參數是開始和結束,中間是增加或減少,顯示出來的是數字

※L 最好用大寫,避免混淆



※檔案內容(/f)

一行一行的抓取檔案內容,要注意 delims 的預設是空格、換行
( )裡有三種 1.檔名  2.命令(用`包起來)  3.字串(用"包起來)

※test.txt

aaa,
bbb,
ccc,
ddd,
eee,
fff,
ggg,
hhh,
iii,
jjj,

※

※test2.txt

kkk,
lll,
mmm,
nnn,
ooo

※

※
::基本款,多檔用空格隔開
for /f %%i in (test.txt test2.txt) do echo %%i
    
::直接寫字串,但我不知道怎麼寫換行
for /f %%i in ("abcde") do echo %%i
    
::如果檔名有空格,必須用"包起來,但會被誤認為字串,所以前面加上 usebackq 即可
for /f usebackq %%i in (test.txt "test 2.txt") do echo %%i
    
::使用命令時也一樣,一定會有空格,所以也要加上 usebackq
for /f usebackq %%i in (`type test.txt`) do echo %%i
    
::換行改成用,,這樣就不是預設的空格、換行了
for /f "delims=," %%i in (test.txt test2.txt) do echo %%i
    
::每個檔案忽略前三行
for /f "skip=3" %%i in (test.txt test2.txt) do echo %%i
    
::可以混合使用
for /f "delims=,skip=3" %%i in (test.txt test2.txt) do echo %%i
    
::忽略 f 開頭
for /f "eol=f" %%i in (test.txt test2.txt) do echo %%i
    
::特殊語法
for /f %%i in (test.txt test2.txt) do echo %%~di%%i

※特殊語法可參考文檔



※tokens

行中的內容

※test3.txt

a b c d e f g h i j
k l m n o p q r s t

※
::取2到5個(注意預設是以空格換行區分第幾個),2到5有4個變數,直接用即可,不用宣告
for /f "tokens=2-5" %%i in (test3.txt) do echo %%i %%j %%k %%l
    
::取第2個之後,%%j表示第3個到最後
for /f "tokens=2*" %%i in (test3.txt) do echo %%i %%j
    
::取2和4,然後7到9,所以有5個變數
for /f "tokens=2,4,7-9" %%i in (test3.txt) do echo %%i %%j %%k %%l %%m

※變數宣告為%%i,注意後面是依序的,如%%i、%%j、%%k…
又如果是宣告為%%a,那後面自然就是%%b、%%c…

※變數不一定要拿來用,也不一定要先印 i 才能印 j,隨自己需求



※if

※
@echo off
setlocal
    
set a=apple
set b=banana
    
if /i "%a%" == "APPLE" (
    echo is apple
) else (
    echo is not apple
)
    
if not "%b%" == "banana" (
    echo is not banana
) else (
    echo is other
)
    
endlocal
pause

※/i 是忽略大小寫,注意繁中版的文檔寫錯了;not 可判斷相反的情形

※/i 和 not 都不是必要的

※判斷除了上面 2 種以外,還有 6 種
equ:equals,等於,等同此例的 ==
neq:not equals,不等於,等同此例的 not ==
lss:less,小於
leq:less equals,小於等於
gtr:greater,大於
geq:greater equals,大於等於


※exist

@echo off
setlocal
    
if exist "x.txt" (
    echo o
) else (
    echo x
)
    
endlocal
pause

※判斷本路徑下有沒有 x.txt 這支檔案或目錄


※迴圈裡判斷檔案

@echo off
setlocal
    
for %%i in (*) do (
    ::if exist "fileName" (
    if "%%i" == "fileName" (
        echo o
    ) else (
        echo x
    )
)
    
endlocal
pause

※如果在迴圈裡判斷,不要用 exist,它只要路徑下有,所有迴圈都會認為是有,除非剛好此路徑下只有一支檔案


※迴圈裡判斷目錄

@echo off
setlocal
    
for /d %%i in (*) do (
    ::if exist "folderName" (
    if "%%i" == "folderName" (
        echo o
    ) else (
        echo x
    )
)
    
endlocal
pause

※同樣的,也不要在裡面判斷目錄,它只要路徑下有這個目錄,所有迴圈都會認為是有,除非剛好此路徑下只有一個目錄


※判斷變數

@echo off
setlocal
    
set a=aaa
if defined a (
    echo have a
)
    
endlocal
pause

※使用 defined 判斷有沒有變數