XhstormR


On a dark desert highway Cool wind in my hair


Spring MVC

Updated on 2017-06-22

MappingJackson2HttpMessageConverter

https://github.com/thymeleaf/thymeleaf

http://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html

Concept

  • REST:将资源的 状态适合 客户端的 格式 从服务端 传递 至客户端(或者反过来)。
    • 资源通过 URL 进行标识,通过 HTTP Method 进行管理,通过 HTTP Header 表述格式。
      • 资源:URL(统一资源定位器)
      • 格式:Accept、Content-Type 头部信息。
      • 动词:CRUD(增删改查)
        • 幂等:同一操作 执行 任意次数 所产生的影响或结果 相同

Configuration

build.gradle.kts

plugin("war")

compile("org.springframework:spring-webmvc:+")
compile("javax.servlet:javax.servlet-api:+")

compile("org.thymeleaf:thymeleaf-spring4:+")
compile("org.slf4j:slf4j-jdk14:+")

compile("org.hibernate:hibernate-validator:5.+")

compile("com.fasterxml.jackson.core:jackson-databind:+")

Code

AppConfig

import org.springframework.web.filter.CharacterEncodingFilter
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer
import javax.servlet.Filter
import javax.servlet.MultipartConfigElement
import javax.servlet.ServletRegistration

class AppConfig : AbstractAnnotationConfigDispatcherServletInitializer() {
    override fun getServletMappings(): Array<String> = arrayOf("/")

    override fun getRootConfigClasses(): Array<Class<*>> = arrayOf(RootConfig::class.java)

    override fun getServletConfigClasses(): Array<Class<*>> = arrayOf(WebConfig::class.java)

    override fun getServletFilters(): Array<Filter> = arrayOf(CharacterEncodingFilter("UTF-8", true))     

    override fun customizeRegistration(registration: ServletRegistration.Dynamic) {
        val mb = 1024 * 1024L      2 MB 4 MB
        registration.setMultipartConfig(MultipartConfigElement("D:/TEMP", 2 * mb, 4 * mb, 0))
    }
}

WebConfig

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.ComponentScan
import org.springframework.context.annotation.Configuration
import org.springframework.format.FormatterRegistry
import org.springframework.web.multipart.MultipartResolver
import org.springframework.web.multipart.support.StandardServletMultipartResolver
import org.springframework.web.servlet.ViewResolver
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer
import org.springframework.web.servlet.config.annotation.EnableWebMvc
import org.springframework.web.servlet.config.annotation.InterceptorRegistry
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter
import org.thymeleaf.spring4.SpringTemplateEngine
import org.thymeleaf.spring4.view.ThymeleafViewResolver
import org.thymeleaf.templateresolver.ITemplateResolver
import org.thymeleaf.templateresolver.ServletContextTemplateResolver
import javax.servlet.ServletContext

@Configuration
@EnableWebMvc      Spring MVC
@ComponentScan("controller")
open class WebConfig : WebMvcConfigurerAdapter() {
    @Bean     1
    open fun templateResolver(servletContext: ServletContext): ITemplateResolver =
            ServletContextTemplateResolver(servletContext).apply {
                this.characterEncoding = "UTF-8"
                this.prefix = "/WEB-INF/views/"
                this.suffix = ".html"
            }

    @Bean     2
    open fun templateEngine(templateResolver: ITemplateResolver): SpringTemplateEngine =
            SpringTemplateEngine().apply { this.setTemplateResolver(templateResolver) }

    @Bean     3
    open fun viewResolver(templateEngine: SpringTemplateEngine): ViewResolver =
            ThymeleafViewResolver().apply {
                this.characterEncoding = "UTF-8"
                this.templateEngine = templateEngine
            }

    @Bean
    open fun multipartResolver(): MultipartResolver = StandardServletMultipartResolver()     

    override fun configureDefaultServletHandling(configurer: DefaultServletHandlerConfigurer) = configurer.enable()     访 DefaultServletHttpRequestHandler

    override fun addInterceptors(registry: InterceptorRegistry) {
        registry.addInterceptor(MyInterceptor()).addPathPatterns("/**")     
    }

    override fun addFormatters(registry: FormatterRegistry) {
        registry.addFormatter(MyDateFormatter())              Formatter
        registry.addConverter(MyStringToDateConverter())      Converter
    }
}

RootConfig

import org.springframework.context.annotation.Configuration

@Configuration
open class RootConfig

MyInterceptor

import org.springframework.web.servlet.ModelAndView
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter
import java.lang.Exception
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse

class MyInterceptor : HandlerInterceptorAdapter() {     
    override fun preHandle(request: HttpServletRequest, response: HttpServletResponse, handler: Any): Boolean {
        println("预处理")
        return true     true false 
    }

    override fun postHandle(request: HttpServletRequest, response: HttpServletResponse, handler: Any, modelAndView: ModelAndView?) {
        println("后处理")
        if (modelAndView != null) {
            modelAndView.model["msg"] = "信息"
        }
    }

    override fun afterCompletion(request: HttpServletRequest, response: HttpServletResponse, handler: Any, ex: Exception?) {
        println("请求结束")
    }
}


 Servlet 使Filter
 Spring  使Interceptor

MyDateFormatter

import org.springframework.format.Formatter
import java.text.SimpleDateFormat
import java.util.*

class MyDateFormatter : Formatter<Date> {     FormatterString <-> T
    private val sdf = SimpleDateFormat("yyyy-MM-dd")

    override fun parse(text: String, locale: Locale): Date = sdf.parse(text)

    override fun print(`object`: Date, locale: Locale): String = sdf.format(`object`)
}

MyStringToDateConverter

import org.springframework.core.convert.converter.Converter
import java.text.SimpleDateFormat
import java.util.*

class MyStringToDateConverter : Converter<String, Date> {     ConverterS --> T
    private val sdf = SimpleDateFormat("yyyy-MM-dd")

    override fun convert(source: String): Date = sdf.parse(source)
}

entity

Account

package entity

import javax.validation.constraints.Min
import javax.validation.constraints.NotNull
import javax.validation.constraints.Size

data class Account(
        @field:NotNull     
        @field:Size(min = 2, max = 4)
        var username: String? = null,
        @field:NotNull
        @field:Size(min = 6, max = 12)
        var password: String? = null,
        @field:NotNull
        @field:Min(18)
        var age: Int? = null
)

controller

HomeController

package controller

import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestMethod

@Controller     
@RequestMapping("/")      127.0.0.1:8080/
class HomeController {
    @RequestMapping(method = arrayOf(RequestMethod.GET))      GET 
    fun home(): String {
        return "home"      /WEB-INF/views/home.html 
    }
}

AController

package controller

import entity.Account
import org.springframework.stereotype.Controller
import org.springframework.ui.Model
import org.springframework.validation.Errors
import org.springframework.web.bind.annotation.*
import org.springframework.web.multipart.MultipartFile
import org.springframework.web.servlet.mvc.support.RedirectAttributes
import java.io.File
import javax.validation.Valid

@Controller
@RequestMapping("/a")
class AController {     
    127.0.0.1:8080/a?username=&password=123456&age=20
    @RequestMapping(method = arrayOf(RequestMethod.GET))      @GetMapping
    fun a(@RequestParam(defaultValue = "张三") username: String,
          @RequestParam(defaultValue = "123456") password: String,
          @RequestParam(defaultValue = "20") age: Int,
          model: Model): String {
        if (!model.containsAttribute("account")) {     
            model.addAttribute("account", Account(username, password, age))
            
            model.addAttribute(Account(username, password, age))      Key
        }
        return "a"
    }

    a Keyaccount
    @RequestMapping(method = arrayOf(RequestMethod.GET))
    fun a(@RequestParam(defaultValue = "张三") username: String,
          @RequestParam(defaultValue = "123456") password: String,
          @RequestParam(defaultValue = "20") age: Int): Account {
        return Account(username, password, age)
    }

    127.0.0.1:8080/a//123456/20
    @RequestMapping(path = arrayOf("/{username}/{password}/{age}"), method = arrayOf(RequestMethod.GET))
    fun b(@PathVariable username: String,
          @PathVariable password: String,
          @PathVariable age: Int,
          model: Model): String {
        if (!model.containsAttribute("account")) {
            model.addAttribute(Account(username, password, age))
        }
        return "a"
    }

    
    @RequestMapping(path = arrayOf("/form"), method = arrayOf(RequestMethod.POST))
    fun c(@Valid account: Account, errors: Errors, model: Model): String {     
        if (errors.hasErrors()) {     
            return "redirect:/"     redirect:forward:
        }
        model.addAttribute("username", account.username)     
        model.addAttribute("password", account.password)
        model.addAttribute("age", account.age)

        return "redirect:/a/{username}/{password}/{age}"     127.0.0.1:8080/a//123456/20
        
        return "redirect:/a"     127.0.0.1:8080/a?username=&password=123456&age=20
    }

    
    @RequestMapping(path = arrayOf("/form"), method = arrayOf(RequestMethod.POST))
    fun c(@Valid account: Account, errors: Errors, model: RedirectAttributes): String {
        if (errors.hasErrors()) {
            return "redirect:/"
        }
        model.addFlashAttribute(account)     
        return "redirect:/a"      flash 
    }

    
    @RequestMapping(path = arrayOf("/upload"), method = arrayOf(RequestMethod.POST))      @PostMapping
    fun d(@RequestPart myPic: MultipartFile): String {
        myPic.transferTo(File("D:/TEMP2/${myPic.originalFilename}"))
        return "redirect:/"
    }

    
    @ExceptionHandler(Throwable::class)
    fun handleError(): String {
        return "error"
    }
}

BController

package controller

import entity.Account
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.validation.Errors
import org.springframework.web.bind.annotation.*
import javax.validation.Valid

@RestController     ()(ResponseBody + Controller)
@RequestMapping("/b")
class BController {     REST API
    //@ResponseBody     转换响应体(方法级别)
    @RequestMapping(path = arrayOf("/jsonOut"), method = arrayOf(RequestMethod.GET), produces = arrayOf(MediaType.APPLICATION_JSON_UTF8_VALUE))      Accept 
    @ResponseStatus(HttpStatus.OK)      HTTP 
    fun a(): Account {
        return Account("张三", "123456", 20)
    }

    @RequestMapping(path = arrayOf("/jsonInOut"), method = arrayOf(RequestMethod.POST), consumes = arrayOf(MediaType.APPLICATION_JSON_UTF8_VALUE))      Content-Type 
    @ResponseStatus(HttpStatus.CREATED)
    fun b(@RequestBody @Valid account: Account, errors: Errors): Account {     
        if (errors.hasErrors()) {
            throw AccountNotFoundException(9527)
        }
        return account.apply { this.username = "李四" }
    }

    @ExceptionHandler(AccountNotFoundException::class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    fun handleError(e: AccountNotFoundException): Error {
        return Error(4, "账户 ${e.accountId} 未找到")
    }
}

data class AccountNotFoundException(
        val accountId: Int
) : RuntimeException()

data class Error(
        val code: Int,
        val message: String
)

CController

package controller

import entity.Account
import org.springframework.format.annotation.DateTimeFormat
import org.springframework.web.bind.annotation.*
import java.util.*

@RestController
@RequestMapping("/c")
class CController {
    http://localhost:8080/c/base.do?age=123
    Int Kotlin 
    Int?Kotlin 
    @GetMapping("/base.do")
    fun base(age: Int?) = age.toString()

    Array
    http://localhost:8080/c/array.do?names=Tom&names=Lucy&names=Jim
    @GetMapping("/array.do")
    fun array(names: Array<String>) = Arrays.toString(names)

    Date
    http://localhost:8080/c/date.do?date=2017-07-15
     DateFormat.SHORT15/07/17 14:30
    @GetMapping("/date.do")
    fun date(@DateTimeFormat(iso = DateTimeFormat.ISO.DATE) date: Date) = date

    Object
    http://localhost:8080/c/object.do
    /*
    {
        "username": "张三",
        "password": "123456",
        "age": 20
    }
    */
    @PostMapping("/object.do")
    fun `object`(@RequestBody account: Account) = account

    List,Set
    http://localhost:8080/c/list.do
    /*
    [
        {
            "username": "张三",
            "password": "123456",
            "age": 20
        },
        {
            "username": "张三",
            "password": "123456",
            "age": 20
        }
    ]
    */
    @PostMapping("/list.do")
    fun list(@RequestBody accounts: List<Account>) = accounts

    Map
    http://localhost:8080/c/map.do
    /*
    {
        "a": {
            "username": "张三",
            "password": "123456",
            "age": 20
        },
        "b": {
            "username": "张三",
            "password": "123456",
            "age": 20
        }
    }
    */
    @PostMapping("/map.do")
    fun map(@RequestBody accounts: Map<String, Account>) = accounts
}

AppErrorHandler

package controller

import org.springframework.web.bind.annotation.ControllerAdvice
import org.springframework.web.bind.annotation.ExceptionHandler

@ControllerAdvice     
class AppErrorHandler {
    @ExceptionHandler(Throwable::class)     
    fun handleError(): String {
        return "error"
    }
}

HTML

a.html

<html xmlns:th="http://www.thymeleaf.org">     Thymeleaf 命名空间
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<p th:text="${account}"></p>
</body>
</html>

变量表达式:${ },基于 SpEL 表达式。
选择表达式:*{ },基于选中对象。
路径表达式:@{ }。
对象表达式:#{ }。

b.html

<html>
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/a/form" method="post">
    <input name="username" type="text"><br>
    <input name="password" type="text"><br>
    <input name="age" type="number"><br>
    <input type="submit"><br>
</form>
</body>
</html>

c.html

<html>
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/a/upload" method="post" enctype="multipart/form-data">     以 multipart 格式提交表单
    <input name="myPic" type="file"><br>
    <input type="submit"><br>
</form>
</body>
</html>
TOP