天天看点

飞5的Spring Boot2(18)- web开发(上)

飞5的Spring Boot2(18)- web开发(上)

spring MVC​

本文主要是讲解使用Spring Boot开发web应用,关于使用Spring 5的基于响应式的编程,将在下一节给出介绍。

开发Web应用

Spring Boot非常适合开发web应用程序。你可以使用内嵌的Tomcat,Jetty或Undertow轻轻松松地创建一个HTTP服务器。大多数的web应用都可以使用spring-boot-starter-web模块进行快速搭建和运行。

Tomcat是Apache 软件基金会(Apache Software Foundation)的Jakarta 项目中的一个核心项目,由Apache、Sun 和其他一些公司及个人共同开发而成。由于有了Sun 的参与和支持,最新的Servlet 和JSP 规范总是能在Tomcat 中得到体现,Tomcat 5支持最新的Servlet 2.4 和JSP 2.0 规范。因为Tomcat 技术先进、性能稳定,而且免费,因而深受Java 爱好者的喜爱并得到了部分软件开发商的认可,成为目前比较流行的Web 应用服务器。

Jetty 是一个开源的servlet容器,它为基于Java的web容器,例如JSP和servlet提供运行环境。Jetty是使用Java语言编写的,它的API以一组JAR包的形式发布。开发人员可以将Jetty容器实例化成一个对象,可以迅速为一些独立运行(stand-alone)的Java应用提供网络和web连接。

Undertow 是红帽公司(RedHat)的开源产品,是 WildFly8(JBoos) 默认的 Web 服务器。

官网API给出一句话概述Undertow:

Undertow is a flexible performant web server written in java, providing both blocking and non-blocking API’s based on NIO.

译文: Undertow是一个用java编写的灵活的高性能Web服务器,提供基于NIO的阻塞和非阻塞API。

Spring Web MVC框架

Spring Web MVC框架(通常简称为”Spring MVC”)是一个富“模型,视图,控制器”web框架, 允许用户创建特定的@Controller或@RestController beans来处理传入的HTTP请求,通过@RequestMapping注解可以将控制器中的方法映射到相应的HTTP请求。

示例:

1@RestController
 2@RequestMapping(value="/users")
 3public class MyRestController {
 4    @RequestMapping(value="/{user}", method=RequestMethod.GET)
 5    public User getUser(@PathVariable Long user) {
 6        // ...
 7    }
 8    @RequestMapping(value="/{user}/customers", method=RequestMethod.GET)
 9    List<Customer> getUserCustomers(@PathVariable Long user) {
10        // ...
11    }
12    @RequestMapping(value="/{user}", method=RequestMethod.DELETE)
13    public User deleteUser(@PathVariable Long user) {
14        // ...
15    }
16}      

Spring MVC是Spring框架的核心部分。

Spring MVC自动配置

Spring Boot为Spring MVC提供的auto-configuration适用于大多数应用,并在Spring默认功能上添加了以下特性:

  1. 引入ContentNegotiatingViewResolver和BeanNameViewResolver beans。
  2. 对静态资源的支持,包括对WebJars的支持。
  3. 自动注册Converter,GenericConverter,Formatter beans。
  4. 对HttpMessageConverters的支持。
  5. 自动注册MessageCodeResolver。
  6. 对静态index.html的支持。
  7. 对自定义Favicon的支持。
  8. 自动使用ConfigurableWebBindingInitializer bean。

如果保留Spring Boot MVC特性,你只需添加其他的MVC配置(拦截器,格式化处理器,视图控制器等)。你可以添加自己的WebMvcConfigurerAdapter类型的@Configuration类,而不需要注解@EnableWebMvc。如果希望使用自定义的RequestMappingHandlerMapping,RequestMappingHandlerAdapter,或ExceptionHandlerExceptionResolver,你可以声明一个WebMvcRegistrationsAdapter实例提供这些组件。

如果想全面控制Spring MVC,你可以添加自己的@Configuration,并使用@EnableWebMvc注解。

飞5的Spring Boot2(18)- web开发(上)

HttpMessageConverters

Spring MVC使用HttpMessageConverter接口转换HTTP请求和响应,默认配置可以开箱即用,例如对象自动转换为JSON(使用Jackson库)或XML(如果Jackson XML扩展可用,或者使用JAXB),字符串默认使用UTF-8编码。

可以使用Spring Boot的HttpMessageConverters类添加或自定义转换类:

1import org.springframework.boot.autoconfigure.web.HttpMessageConverters;
 2import org.springframework.context.annotation.*;
 3import org.springframework.http.converter.*;
 4@Configuration
 5public class MyConfiguration {
 6    @Bean
 7    public HttpMessageConverters customConverters() {
 8        HttpMessageConverter<?> additional = ...
 9        HttpMessageConverter<?> another = ...
10        return new HttpMessageConverters(additional, another);
11    }
12}      

上下文中出现的所有HttpMessageConverter bean都将添加到converters列表,你可以通过这种方式覆盖默认的转换器列表(converters)。

自定义JSON序列化器和反序列化器

如果使用Jackson序列化,反序列化JSON数据,你可能想编写自己的JsonSerializer和JsonDeserializer类。自定义序列化器(serializers)通常通过Module注册到Jackson,但Spring Boot提供了@JsonComponent注解这一替代方式,它能轻松的将序列化器注册为Spring Beans。

MessageCodesResolver

Spring MVC有一个实现策略,用于从绑定的errors产生用来渲染错误信息的错误码:MessageCodesResolver。Spring Boot会自动为你创建该实现,只要设置spring.mvc.message-codes-resolver.format属性为PREFIX_ERROR_CODE或POSTFIX_ERROR_CODE(具体查看DefaultMessageCodesResolver.Format枚举值)。

静态内容

默认情况下,Spring Boot从classpath下的/static(/public,/resources或/META-INF/resources)文件夹,或从ServletContext根目录提供静态内容。这是通过Spring MVC的ResourceHttpRequestHandler实现的,你可以自定义WebMvcConfigurerAdapter并重写addResourceHandlers方法来改变该行为(加载静态文件)。

在单机web应用中,容器会启动默认的servlet,并用它加载ServletContext根目录下的内容以响应那些Spring不处理的请求。大多数情况下这都不会发生(除非你修改默认的MVC配置),因为Spring总能够通过DispatcherServlet处理这些请求。

你可以设置spring.resources.staticLocations属性自定义静态资源的位置(配置一系列目录位置代替默认的值),如果你这样做,默认的欢迎页面将从自定义位置加载,所以只要这些路径中的任何地方有一个index.html,它都会成为应用的主页。

此外,除了上述标准的静态资源位置,有个例外情况是Webjars内容。任何在/webjars/**路径下的资源都将从jar文件中提供,只要它们以Webjars的格式打包。

注意

如果你的应用将被打包成jar,那就不要使用src/main/webapp文件夹。尽管该文件夹是通常的标准格式,但它仅在打包成war的情况下起作用,在打包成jar时,多数构建工具都会默认忽略它。

Spring Boot也支持Spring MVC提供的高级资源处理特性,可用于清除缓存的静态资源或对WebJar使用版本无感知的URLs。

如果想使用针对WebJars版本无感知的URLs(version agnostic),只需要添加webjars-locator依赖,然后声明你的Webjar。以jQuery为例,”/webjars/jquery/dist/jquery.min.js”实际为”/webjars/jquery/x.y.z/dist/jquery.min.js”,x.y.z为Webjar的版本。

注意

如果使用JBoss,你需要声明webjars-locator-jboss-vfs依赖而不是webjars-locator,否则所有的Webjars将解析为404。

以下的配置为所有的静态资源提供一种缓存清除(cache busting)方案,实际上是将内容hash添加到URLs中,比如:

1spring.resources.chain.strategy.content.enabled=true
2spring.resources.chain.strategy.content.paths=/**      

注意

实现该功能的是ResourceUrlEncodingFilter,它在模板运行期会重写资源链接,Thymeleaf,Velocity和FreeMarker会自动配置该filter,JSP需要手动配置。其他模板引擎还没自动支持,不过你可以使用ResourceUrlProvider自定义模块。

当使用比如JavaScript模块加载器动态加载资源时,重命名文件是不行的,这也是提供其他策略并能结合使用的原因。下面是一个”fixed”策略,在URL中添加一个静态version字符串而不需要改变文件名:

1spring.resources.chain.strategy.content.enabled=true
2spring.resources.chain.strategy.content.paths=/**
3spring.resources.chain.strategy.fixed.enabled=true
4spring.resources.chain.strategy.fixed.paths=/js/lib/
5spring.resources.chain.strategy.fixed.version=v12      

使用以上策略,JavaScript模块加载器加载”/js/lib/”下的文件时会使用一个固定的版本策略”/v12/js/lib/mymodule.js”,其他资源仍旧使用内容hash的方式。查看ResourceProperties获取更多支持的选项。

注意

该特性在一个专门的博文和Spring框架参考文档中有透彻描述。

欢迎页面

飞5的Spring Boot2(18)- web开发(上)

Spring Boot支持静态和模板欢迎页面。它首先index.html在配置的静态内容位置中查找 文件。如果找不到,则会查找index模板。如果找到任何一个,它将自动用作应用程序的欢迎页面。

自定义Favicon

Spring Boot favicon.ico在配置的静态内容位置和类路径的根目录(按此顺序)中查找a 。如果存在这样的文件,它会自动用作应用程序的图标。

路径匹配和内容协商

Spring MVC可以通过查看请求路径并将它匹配到应用程序中定义的映射(例如@GetMapping Controller方法上的注释),将传入的HTTP请求映射到处理程序。

Spring Boot选择默认禁用后缀模式匹配,这意味着请求”GET /projects/spring-boot.json”不会匹配 @GetMapping(“/projects/spring-boot”)映射。这被认为是Spring MVC应用程序的最佳实践。此功能在过去对于没有发送正确的“Accept”请求标头的HTTP客户端来说非常有用; 我们需要确保将正确的内容类型发送到客户端。如今,内容协商更可靠。

还有其他一些方法可以处理不一致地发送适当的“接受”请求标头的HTTP客户端。我们可以使用查询参数来确保类似的请求”GET /projects/spring-boot?format=json” 将映射到@GetMapping(“/projects/spring-boot”)以下内容,而不是使用后缀匹配:

1spring.mvc.contentnegotiation.favor-parameter = true
2#我们可以更改参数名称,默认为“格式”:
3#spring.mvc.contentnegotiation.parameter-name = myparam
4#我们还可以通过以下方式注册其他文件扩展名/媒体类型:
5spring.mvc.contentnegotiation.media-types.markdown = text / markdown      

如果您了解注意事项并仍然希望应用程序使用后缀模式匹配,则需要进行以下配置:

1spring.mvc.contentnegotiation.favor-path-extension = true
2#您也可以将该功能限制为已知扩展
3#spring.mvc.pathmatch.use-registered-suffix-pattern = true
4#我们还可以通过以下方式注册其他文件扩展名/媒体类型:
5#spring.mvc.contentnegotiation.media-types.adoc = text / asciidoc      

ConfigurableWebBindingInitializer

Spring MVC使用WebBindingInitializer为每个特殊的请求初始化相应的WebDataBinder,如果你创建自己的ConfigurableWebBindingInitializer @Bean,Spring Boot会自动配置Spring MVC使用它。

模板引擎

正如REST web服务,你也可以使用Spring MVC提供动态HTML内容。Spring MVC支持各种各样的模板技术,包括Velocity, FreeMarker和JSPs,很多其他的模板引擎也提供它们自己的Spring MVC集成。

Spring Boot为以下的模板引擎提供自动配置支持:

  • FreeMarker
  • Groovy
  • Thymeleaf
  • Velocity(1.4已不再支持)
  • Mustache

    注意

由于在内嵌servlet容器中使用JSPs存在一些已知的限制,所以建议尽量不使用它们。

使用以上引擎中的任何一种,并采用默认配置,则模块会从src/main/resources/templates自动加载。

注意

IntelliJ IDEA根据你运行应用的方式会对classpath进行不同的排序。在IDE里通过main方法运行应用,跟从Maven,或Gradle,或打包好的jar中运行相比会导致不同的顺序,这可能导致Spring Boot不能从classpath下成功地找到模板。如果遇到这个问题,你可以在IDE里重新对classpath进行排序,将模块的类和资源放到第一位。或者,你可以配置模块的前缀为classpath*:/templates/,这样会查找classpath下的所有模板目录。

错误处理

Spring Boot默认提供一个/error映射用来以合适的方式处理所有的错误,并将它注册为servlet容器中全局的 错误页面。对于客户端(相对于浏览器而言,浏览器偏重于人的行为),它会产生一个具有详细错误,HTTP状态,异常信息的JSON响应。对于浏览器客户端,它会产生一个白色标签样式(whitelabel)的错误视图,该视图将以HTML格式显示同样的数据(可以添加一个解析为’error’的View来自定义它)。为了完全替换默认的行为,你可以实现ErrorController,并注册一个该类型的bean定义,或简单地添加一个ErrorAttributes类型的bean以使用现存的机制,只是替换显示的内容。

注BasicErrorController可以作为自定义ErrorController的基类,如果你想添加对新context type的处理(默认处理text/html),这会很有帮助。你只需要继承BasicErrorController,添加一个public方法,并注解带有produces属性的@RequestMapping,然后创建该新类型的bean。

你也可以定义一个@ControllerAdvice去自定义某个特殊controller或exception类型的JSON文档:

1@ControllerAdvice(basePackageClasses = FooController.class)
 2public class FooControllerAdvice extends ResponseEntityExceptionHandler {
 3    @ExceptionHandler(YourException.class)
 4    @ResponseBody
 5    ResponseEntity<?> handleControllerException(HttpServletRequest request, Throwable ex) {
 6        HttpStatus status = getStatus(request);
 7        return new ResponseEntity<>(new CustomErrorType(status.value(), ex.getMessage()), status);
 8    }
 9    private HttpStatus getStatus(HttpServletRequest request) {
10        Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
11        if (statusCode == null) {
12            return HttpStatus.INTERNAL_SERVER_ERROR;
13        }
14        return HttpStatus.valueOf(statusCode);
15    }
16}      

在以上示例中,如果跟FooController相同package的某个controller抛出YourException,一个CustomerErrorType类型的POJO的json展示将代替ErrorAttributes展示。

自定义错误页面

如果想为某个给定的状态码展示一个自定义的HTML错误页面,你需要将文件添加到/error文件夹下。错误页面既可以是静态HTML(比如,任何静态资源文件夹下添加的),也可以是使用模板构建的,文件名必须是明确的状态码或一系列标签。

例如,映射404到一个静态HTML文件,你的目录结构可能如下:

1src/
2 +- main/
3     +- java/
4     |   + <source code>
5     +- resources/
6         +- public/
7             +- error/
8             |   +- 404.html
9             +- <other public assets>      

使用FreeMarker模板映射所有5xx错误,你需要如下的目录结构:

1src/
2 +- main/
3     +- java/
4     |   + <source code>
5     +- resources/
6         +- templates/
7             +- error/
8             |   +- 5xx.ftl
9             +- <other templates>      

对于更复杂的映射,你可以添加实现ErrorViewResolver接口的beans:

1public class MyErrorViewResolver implements ErrorViewResolver {
2    @Override
3    public ModelAndView resolveErrorView(HttpServletRequest request,
4            HttpStatus status, Map<String, Object> model) {
5        // Use the request or status to optionally return a ModelAndView
6        return ...
7    }
8}      

你也可以使用Spring MVC特性,比如@ExceptionHandler方法和@ControllerAdvice,ErrorController将处理所有未处理的异常。

映射Spring MVC以外的错误页面

对于不使用Spring MVC的应用,你可以通过ErrorPageRegistrar接口直接注册ErrorPages。该抽象直接工作于底层内嵌servlet容器,即使你没有Spring MVC的DispatcherServlet,它们仍旧可以工作。

1@Bean
 2public ErrorPageRegistrar errorPageRegistrar(){
 3    return new MyErrorPageRegistrar();
 4}
 5// ...
 6private static class MyErrorPageRegistrar implements ErrorPageRegistrar {
 7    @Override
 8    public void registerErrorPages(ErrorPageRegistry registry) {
 9        registry.addErrorPages(new ErrorPage(HttpStatus.BAD_REQUEST, "/400"));
10    }
11}      

注意

如果你注册一个ErrorPage,该页面需要被一个Filter处理(在一些非Spring web框架中很常见,比如Jersey,Wicket),那么该Filter需要明确注册为一个ERROR分发器(dispatcher),例如:
1@Bean
2public FilterRegistrationBean myFilter() {
3    FilterRegistrationBean registration = new FilterRegistrationBean();
4    registration.setFilter(new MyFilter());
5    ...
6    registration.setDispatcherTypes(EnumSet.allOf(DispatcherType.class));
7    return registration;
8}      

(默认的FilterRegistrationBean不包含ERROR dispatcher类型)。

WebSphere应用服务器的错误处理

当部署到一个servlet容器时,Spring Boot通过它的错误页面过滤器将带有错误状态的请求转发到恰当的错误页面。request只有在response还没提交时才能转发(forwarded)到正确的错误页面,而WebSphere应用服务器8.0及后续版本默认情况会在servlet方法成功执行后提交response,你需要设置com.ibm.ws.webcontainer.invokeFlushAfterService属性为false来关闭该行为。

Spring HATEOAS

如果正在开发基于超媒体的RESTful API,你可能需要Spring HATEOAS,而Spring Boot会为其提供自动配置,这在大多数应用中都运作良好。 自动配置取代了@EnableHypermediaSupport,只需注册一定数量的beans就能轻松构建基于超媒体的应用,这些beans包括LinkDiscoverers(客户端支持),ObjectMapper(用于将响应编排为想要的形式)。ObjectMapper可以根据spring.jackson.*属性或Jackson2ObjectMapperBuilder bean进行自定义。

通过注解@EnableHypermediaSupport,你可以控制Spring HATEOAS的配置,但这会禁用上述ObjectMapper的自定义功能。

CORS支持

跨域资源共享(CORS)是一个大多数浏览器都实现了的W3C标准,它允许你以灵活的方式指定跨域请求如何被授权,而不是采用那些不安全,性能低的方式,比如IFRAME或JSONP。

1@Configuration
 2public class MyConfiguration {
 3    @Bean
 4    public WebMvcConfigurer corsConfigurer() {
 5        return new WebMvcConfigurerAdapter() {
 6            @Override
 7            public void addCorsMappings(CorsRegistry registry) {
 8                registry.addMapping("/api/**");
 9            }
10        };
11    }
12}      

总结

继续阅读