Spring Boot非常适合Web应用程序的开发。 您可以使用嵌入式Tomcat,Jetty或Undertow轻松创建自包含的HTTP服务器。 大多数Web应用程序将使用spring-boot-starter-web模块来快速启动和运行。
Spring Web MVC框架(通常简称为“Spring MVC”)是一个丰富的“模型视图控制器”Web框架。 Spring MVC允许您创建特殊的@Controller或@RestController bean来处理传入的HTTP请求。 您的控制器中的方法使用@RequestMapping注释映射到HTTP。
这里是一个典型的例子@RestController来提供JSON数据:
@RestController
@RequestMapping(value="/users")
public class MyRestController {
@RequestMapping(value="/{user}", method=RequestMethod.GET)
public User getUser(@PathVariable Long user) {
// ...
}
@RequestMapping(value="/{user}/customers", method=RequestMethod.GET)
List getUserCustomers(@PathVariable Long user) {
// ...
}
@RequestMapping(value="/{user}", method=RequestMethod.DELETE)
public User deleteUser(@PathVariable Long user) {
// ...
}
}
Spring Boot为Spring MVC提供了自动配置,可以与大多数应用程序配合使用。自动配置在Spring的默认设置之上添加以下功能:
如果你想保持Spring Boot的MVC特性,并且你还想添加额外的MVC配置(拦截器,格式化器,视图控制器等),你可以添加你自己的@Configuration类型的WebMvcConfigurerAdapter。 如果您希望提供RequestMappingHandlerMapping,RequestMappingHandlerAdapter或ExceptionHandlerExceptionResolver的自定义实例,则可以声明提供此类组件的WebMvcRegistrationsAdapter实例。
如果你想完全控制Spring MVC,你可以添加你自己的用@EnableWebMvc注解的@Configuration。
Spring MVC使用HttpMessageConverter接口来转换HTTP请求和响应。 默认包括拆箱即用的功能,例如对象可以自动转换为JSON(使用Jackson库)或XML(如果Jackson XML可以使用则使用Jackson XML扩展,否则使用JAXB)。 字符串默认使用UTF-8编码。如果您需要添加或自定义转换器,可以使用Spring Boot的HttpMessageConverters类。
import org.springframework.boot.autoconfigure.web.HttpMessageConverters;
import org.springframework.context.annotation.*;
import org.springframework.http.converter.*;
@Configuration
public class MyConfiguration {
@Bean
public HttpMessageConverters customConverters() {
HttpMessageConverter> additional = ...
HttpMessageConverter> another = ...
return new HttpMessageConverters(additional, another);
}
}
JSON序列化器和反序列化器
如果您使用Jackson来序列化和反序列化JSON数据,则可能需要编写自己的JsonSerializer和JsonDeserializer类。 自定义序列化器通常通过模块向Jackson注册,但Spring Boot提供了一个替代的@JsonComponent注释,这使得更容易直接注册Spring Bean。
import java.io.*;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import org.springframework.boot.jackson.*;
@JsonComponent
public class Example {
public static class Serializer extends JsonSerializer {
// ...
}
public static class Deserializer extends JsonDeserializer {
// ...
}
}
ApplicationContext中的所有@JsonComponent bean将被自动注册到Jackson,并且由于@JsonComponent使用@Component进行元注释,所以通常的组件扫描规则适用。
Spring Boot还提供了JsonObjectSerializer和JsonObjectDeserializer基类,在序列化对象时为标准Jackson版本提供了有用的替代方法。
假设我们有个controller可以获取当前日期。我们可以发现:如果不加任何日期序列化工具,将会返回当前日期对应的毫秒数。
@RequestMapping("formatedate")
@JsonSerialize
public TestDate getFormatDate() {
TestDate testDate = new TestDate();
testDate.setCreatedate(new Date());
return testDate;
}
如果我们自定义一个日期序列化工具,实现日期的格式化输出:
@JsonComponent
public class DateSerializer {
public static class Serializer extends JsonSerializer {
@Override
public void serialize(Date date, JsonGenerator jsonGenerator, SerializerProvider serializers) throws IOException, JsonProcessingException {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
jsonGenerator.writeString(simpleDateFormat.format(date));
}
}
public static class Deserializer extends JsonDeserializer {
@Override
public Date deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException, JsonProcessingException {
String date = jsonParser.getText();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
try {
return simpleDateFormat.parse(date);
} catch (ParseException e) {
e.printStackTrace();
}
return new Date();
}
}
}
观察输出,可以发现返回我们需要的日期格式:
同样反序列也也是一样的道理:
@RequestMapping(value = "str2date")
public void getFormatDate(@RequestBody DateVO dateVO) {
System.out.println(dateVO);
}
@Data
public class DateVO {
private Date date;
}
入参我们用一个字符串时间:{"date":"2018-01-01 00:00:00"}我们自定的反序列化工具可以实现将字符串转换成Date对象DateVO(date=Mon Jan 01 00:00:00 CST 2018)。
Spring MVC有一个策略,用于从绑定的errors产生用来渲染错误信息的错误码:MessageCodesResolver。如果设置spring.mvc.message-codes-resolver.format属性为PREFIX_ERROR_CODE或POSTFIX_ERROR_CODE(具体查看DefaultMessageCodesResolver.Format枚举值),Spring Boot会为你创建一个MessageCodesResolver。
默认情况下,Spring Boot将从类路径中的/ static(或/ public或/ resources或/ META-INF / resources)目录或从ServletContext的根目录中提供静态内容。 它使用Spring MVC中的ResourceHttpRequestHandler,因此可以通过添加自己的WebMvcConfigurerAdapter并重写addResourceHandlers方法来修改该行为。
默认情况下,资源被映射到/ **,但是你可以通过spring.mvc.static-path-pattern来调整。 例如,将所有资源重定位到/ resources / **可以实现如下:
spring.mvc.static-path-pattern=/resources/**
您还可以使用spring.resources.static-locations(使用目录位置列表替换默认值)自定义静态资源位置。 如果您这样做,默认的欢迎页面检测将切换到您的自定义位置。 因此,如果您的任何位置在启动时都有index.html,它将成为应用程序的主页。
除了上面的“标准”静态资源位置之外,Webjars是一个特殊情况。 如果资源以Webjars格式打包,并且在/ webjars / **目录下,将会被当成jar包使用。
如果您的应用程序将被打包为jar,请不要使用src/main/webapp目录。 虽然这个目录是一个通用的标准,但它只能用于war包,如果你要生成一个jar包,该目录会被大多数构建工具默默地忽略。
Spring Boot会在配置的静态内容位置和类路径的根目录(按此顺序)中查找favicon.ico。 如果这样的文件存在,它会自动用作应用程序的图标。
Spring MVC使用WebBindingInitializer为特定请求初始化WebDataBinder。 如果您创建自己的ConfigurableWebBindingInitializer @Bean,Spring Boot将自动配置Spring MVC以使用它。
除了REST Web服务,您还可以使用Spring MVC为动态HTML内容提供服务。 Spring MVC支持各种模板技术,包括Thymeleaf,FreeMarker和JSP。 许多其他模板引擎也提供了自己的Spring MVC集成。
Spring Boot包含以下模板引擎的自动配置支持:
## Freemarker 配置
## 文件配置路径
spring.freemarker.template-loader-path=classpath:/templates/
spring.freemarker.cache=false
spring.freemarker.charset=UTF-8
spring.freemarker.check-template-location=true
spring.freemarker.content-type=text/html
spring.freemarker.expose-request-attributes=true
spring.freemarker.expose-session-attributes=true
spring.freemarker.request-context-attribute=request
spring.freemarker.suffix=.ftl
@RequestMapping("/welcome/{name}")
public ModelAndView demo(@PathVariable String name) {
Map objectMap = new HashMap<>(1);
objectMap.put("name", name);
return new ModelAndView("welcome");
}
Insert title here
${name} 您好,欢迎您
执行以下http://localhost:8080/users/welcome/张三,发现返回如下页面:
更多关于freemaker的配置可以参考如下:
# FREEMARKER (FreeMarkerAutoConfiguration)
spring.freemarker.allow-request-override=false # Set whether HttpServletRequest attributes are allowed to override (hide) controller generated model attributes of the same name.
spring.freemarker.allow-session-override=false # Set whether HttpSession attributes are allowed to override (hide) controller generated model attributes of the same name.
spring.freemarker.cache=false # Enable template caching.
spring.freemarker.charset=UTF-8 # Template encoding.
spring.freemarker.check-template-location=true # Check that the templates location exists.
spring.freemarker.content-type=text/html # Content-Type value.
spring.freemarker.enabled=true # Enable MVC view resolution for this technology.
spring.freemarker.expose-request-attributes=false # Set whether all request attributes should be added to the model prior to merging with the template.
spring.freemarker.expose-session-attributes=false # Set whether all HttpSession attributes should be added to the model prior to merging with the template.
spring.freemarker.expose-spring-macro-helpers=true # Set whether to expose a RequestContext for use by Spring's macro library, under the name "springMacroRequestContext".
spring.freemarker.prefer-file-system-access=true # Prefer file system access for template loading. File system access enables hot detection of template changes.
spring.freemarker.prefix= # Prefix that gets prepended to view names when building a URL.
spring.freemarker.request-context-attribute= # Name of the RequestContext attribute for all views.
spring.freemarker.settings.*= # Well-known FreeMarker keys which will be passed to FreeMarker's Configuration.
spring.freemarker.suffix=.ftl # Suffix that gets appended to view names when building a URL.
spring.freemarker.template-loader-path=classpath:/templates/ # Comma-separated list of template paths.
spring.freemarker.view-names= # White list of view names that can be resolved.
Spring Boot默认提供了一个/error映射,以合理的方式处理所有的错误,并在servlet容器中被注册为一个“全局”错误页面。 对于机器客户端,它将产生一个JSON响应,包含错误的详细信息,HTTP状态和异常消息。 对于浏览器客户端,有一个'whitelabel'错误视图,它以HTML格式呈现相同的数据(自定义它只是添加一个解决'错误'的视图)。 要完全替换默认行为,可以实现ErrorController并注册该类型的bean定义,或者简单地添加一个ErrorAttributes类型的bean来使用现有的机制,但是替换内容。
BasicErrorController可以用作自定义ErrorController的基类。 如果您想为新的内容类型添加处理程序(默认情况下专门处理text / html并提供其他所有内容的回退),这一点尤其有用。 要做到这一点,只需扩展BasicErrorController并添加一个带有@RequestMapping属性的公共方法,并创建一个新类型的bean。
您还可以定义@ControllerAdvice来定制JSON文档以返回特定的控制器和/或异常类型.
@ControllerAdvice(basePackageClasses = FooController.class)
public class FooControllerAdvice extends ResponseEntityExceptionHandler {
@ExceptionHandler(YourException.class)
@ResponseBody
ResponseEntity> handleControllerException(HttpServletRequest request, Throwable ex) {
HttpStatus status = getStatus(request);
return new ResponseEntity<>(new CustomErrorType(status.value(), ex.getMessage()), status);
}
private HttpStatus getStatus(HttpServletRequest request) {
Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
if (statusCode == null) {
return HttpStatus.INTERNAL_SERVER_ERROR;
}
return HttpStatus.valueOf(statusCode);
}
}
在上面的例子中,如果由与FooController相同的包中定义的控制器抛出YourException,则将使用CustomErrorType POJO的json表示而不是ErrorAttributes表示形式。
举个栗子:
@ControllerAdvice(basePackageClasses = MyRestController.class)
public class CommonControllerAdvice extends ResponseEntityExceptionHandler {
@ExceptionHandler(Exception.class)
@ResponseBody
ResponseEntity> handleControllerException(HttpServletRequest request, Throwable ex) {
HttpStatus status = getStatus(request);
return new ResponseEntity<>(new CustomErrorType(status.value(), ex.getMessage()), status);
}
private HttpStatus getStatus(HttpServletRequest request) {
Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
if (statusCode == null) {
return HttpStatus.INTERNAL_SERVER_ERROR;
}
return HttpStatus.valueOf(statusCode);
}
}
@RequestMapping("exception")
public void exceptionDemo() {
throw new RuntimeException("An uncaught Exception happened");
}
可以看到控制台输出:
java.lang.RuntimeException: An uncaught Exception happened
at com.example.springboot.controller.MyRestController.exceptionDemo(MyRestController.java:55) ~[classes/:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_161]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_161]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_161]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_161]
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205) ~[spring-web-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:133) ~[spring-web-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:97) ~[spring-webmvc-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827) ~[spring-webmvc-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738) ~[spring-webmvc-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85) ~[spring-webmvc-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:967) ~[spring-webmvc-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:901) ~[spring-webmvc-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970) ~[spring-webmvc-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861) ~[spring-webmvc-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:635) ~[tomcat-embed-core-8.5.27.jar:8.5.27]
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846) ~[spring-webmvc-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:742) ~[tomcat-embed-core-8.5.27.jar:8.5.27]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) ~[tomcat-embed-core-8.5.27.jar:8.5.27]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-8.5.27.jar:8.5.27]
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) ~[tomcat-embed-websocket-8.5.27.jar:8.5.27]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-8.5.27.jar:8.5.27]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-8.5.27.jar:8.5.27]
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99) ~[spring-web-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-8.5.27.jar:8.5.27]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-8.5.27.jar:8.5.27]
at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:108) ~[spring-web-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-8.5.27.jar:8.5.27]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-8.5.27.jar:8.5.27]
at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:81) ~[spring-web-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-8.5.27.jar:8.5.27]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-8.5.27.jar:8.5.27]
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197) ~[spring-web-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-8.5.27.jar:8.5.27]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-8.5.27.jar:8.5.27]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:199) ~[tomcat-embed-core-8.5.27.jar:8.5.27]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) [tomcat-embed-core-8.5.27.jar:8.5.27]
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:504) [tomcat-embed-core-8.5.27.jar:8.5.27]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140) [tomcat-embed-core-8.5.27.jar:8.5.27]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81) [tomcat-embed-core-8.5.27.jar:8.5.27]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87) [tomcat-embed-core-8.5.27.jar:8.5.27]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342) [tomcat-embed-core-8.5.27.jar:8.5.27]
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:803) [tomcat-embed-core-8.5.27.jar:8.5.27]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) [tomcat-embed-core-8.5.27.jar:8.5.27]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:790) [tomcat-embed-core-8.5.27.jar:8.5.27]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1459) [tomcat-embed-core-8.5.27.jar:8.5.27]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-8.5.27.jar:8.5.27]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_161]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_161]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-8.5.27.jar:8.5.27]
at java.lang.Thread.run(Thread.java:748) [na:1.8.0_161]
如果要为给定的状态代码显示自定义的HTML错误页面,请将文件添加到/ error文件夹。 错误页面可以是静态HTML(即添加在任何静态资源文件夹下),也可以使用模板构建。 该文件的名称应该是确切的状态码或一系列掩码。
例如,要将404映射到静态HTML文件,您的文件夹结构如下所示:
src/
+- main/
+- java/
| +
要使用FreeMarker模板来映射所有的5xx错误,你会有这样的结构:
src/
+- main/
+- java/
| +
对于更复杂的映射,您还可以添加实现ErrorViewResolver接口的bean。
public class MyErrorViewResolver implements ErrorViewResolver {
@Override
public ModelAndView resolveErrorView(HttpServletRequest request,
HttpStatus status, Map model) {
// Use the request or status to optionally return a ModelAndView
return ...
}
}
您还可以使用常规的Spring MVC功能,如@ExceptionHandler方法和@ControllerAdvice。 ErrorController将会接收任何未处理的异常。
git demo地址:https://github.com/PerseveranceForever/springmcv_demo.git
https://github.com/PerseveranceForever/springmcv_demo.git