Spring参考手册 6 网络

翻译自Spring官方文档 4.1.2版本

相关文章:

一、Web MVC framework

1.1 Spring Web MVC framework简介

Spring Web model-view-controller (MVC) framework的核心是一个DispatcherServlet,这个调度器负责分配请求到处理器,可实现配置处理器映射、视图解析、本地化、时区和主题解析还有上传文件的支持。默认的处理器基于@Controller@RequestMapping注解,提供了一个很灵活的处理方法。在Spring 3.0的简介可了解到,@Controller机制还允许你通过@PathVariable注解和其他特性创建RESTful网址和应用程序。

1.2 DispatcherServlet

Spring web MVC framework与其他许多MCV框架类似,请求驱动,围绕一个中心Servlet,由它分配请求到控制器并提供其他功能来促进web应用程序开发。Spring的DispatcherServlet然而做的要更多,它已经完全与Spring IoC容器整合,这样就允许你使用Spring的其他特性。

Spring Web MVC DispatcherServlet的请求处理工作流通过下面的图示来说明。懂设计模式的读者会认出DispatcherServlet使用的是一种前端控制器设计模式。

DispatcherServlet是一个实实在在的Servlet,这样它可以被声明在web.xml里。你需要映射你希望DispatcherServlet处理的请求。这是一个标准的Java EE Servlet配置:

<web-app>
    <servlet>
        <servlet-name>example</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>example</servlet-name>
        <url-pattern>/example/*</url-pattern>
    </servlet-mapping>

</web-app>

所有以/example开始的请求都将会被名为example的DispatcherServlet实例处理。在Servlet 3.0+环境,你还可以通过编程方式来配置Servlet容器,下面的代码基本上与上面的web.xml是等价的:

public class MyWebApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext container) {
        ServletRegistration.Dynamic registration = container.addServlet("dispatcher", new DispatcherServlet());
        registration.setLoadOnStartup(1);
        registration.addMapping("/example/*");
    }

}

在初始化一个DispatcherServlet时,Spring MVC会查找在WEB-INF文件夹下的一个名为[servlet-name]-servlet.xml的文件并在创建bean,重写全局范围内相同名字的bean。

假设有如下DispatcherServlet配置:

<web-app>
    <servlet>
        <servlet-name>golfing</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>golfing</servlet-name>
        <url-pattern>/golfing/*</url-pattern>
    </servlet-mapping>
</web-app>

你需要一个文件/WEB-INF/golfing-servlet.xml,它将会包含所有Spring Web MVC特定的组件(beans)。也可以通过一个Servlet初始化参数来改变这个位置。

针对单一DispatcherServlet场景也可以只有一个根上下文:

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

1.2.1 DispatcherServlet处理顺序

在你设置一个DispatcherServlet后,当一个请求进入DispatcherServlet会开始处理请求:

  • WebApplicationContext被搜索并且绑定请求作为一个属性这样controller和其他元素在处理过程中可以使用它。默认它被绑定在DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTEkey下。

  • 本地化解析器被绑定到这个请求。如果你不需要本地化解析那你就不需要它。

  • 主题解析器被绑定到这个请求。如果你不使用主题你可以忽略它。

  • 如果你指定了一个multipart文件解析器,这个请求将会被检查;如果找到了multipart这个请求会被包装成一个MultipartHttpServletRequest来进行进一步处理。(比如上传文件时)

  • 一个适当的处理器被搜索。如果找到了就链接到处理器来准备一个model或者渲染。

  • 一个model被返回,视图被渲染

1.3 实现控制器

在Spring 2.5里已经介绍了一种基于注解的编程模型,使用像@RequestMapping, @RequestParam, @ModelAttribute这样的注解。通过这种形式实现控制器不需要继承指定的父类或者实现指定的接口。此外它们通常也不直接依赖Servlet APIs。

@Controller
public class HelloWorldController {

    @RequestMapping("/helloWorld")
    public String helloWorld(Model model) {
        model.addAttribute("message", "Hello World!");
        return "helloWorld";
    }
}

如你所见,@Controller@RequestMapping注解允许灵活的方法名。@Controller@RequestMapping和许多其他注解构成了Spring MVC实现的基础。

1.3.1 使用@Controller定义一个控制器

@Controller注解标明一个指定的类扮演控制器的角色。Spring不要求你继承任何父控制器类或者引用Servlet API。但是如果需要你仍然可以引用Servlet的特性。

你可以在DispatcherServlet上下文中使用标准的Spring bean定义规则明确的定义一个控制器bean。但是@Controller也允许自动检测,自动注册为bean。

为了自动检测被注解的控制器,你需要在配置文件添加组件扫描:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 控制器所在的包路径 -->
    <context:component-scan base-package="org.springframework.samples.petclinic.web"/>

    <!-- ... -->

</beans>

1.3.2 使用@RequestMapping映射请求

你可以使用@RequestMapping注解来映射URL就像这个/appointments,既可以在类上也可以在指定方法上。

下面这个例子展示了Spring MVC中使用注解的控制器:

@Controller
@RequestMapping("/appointments")
public class AppointmentsController {

    private final AppointmentBook appointmentBook;

    @Autowired
    public AppointmentsController(AppointmentBook appointmentBook) {
        this.appointmentBook = appointmentBook;
    }

    @RequestMapping(method = RequestMethod.GET)
    public Map<String, Appointment> get() {
        return appointmentBook.getAppointmentsForToday();
    }

    @RequestMapping(value="/{day}", method = RequestMethod.GET)
    public Map<String, Appointment> getForDay(@PathVariable @DateTimeFormat(iso=ISO.DATE) Date day, Model model) {
        return appointmentBook.getAppointmentsForDay(day);
    }

    @RequestMapping(value="/new", method = RequestMethod.GET)
    public AppointmentForm getNewForm() {
        return new AppointmentForm();
    }

    @RequestMapping(method = RequestMethod.POST)
    public String add(@Valid AppointmentForm appointment, BindingResult result) {
        if (result.hasErrors()) {
            return "appointments/new";
        }
        appointmentBook.addAppointment(appointment);
        return "redirect:/appointments";
    }
}

这个例子里@RequestMapping被用到了很多地方。第一个用例实在类级别,表示所有处理方法都在/appointments路径下。get()方法将请求映射更加细化:它只接收GET请求,HTTP GET /appointments会调用这个方法。getNewForm()联合了HTTP方法定义和路径,那么GET请求appointments/new将会被这个方法处理。

getForDay()方法展示了@RequestMapping的另一种用法:URI 模版。(详见下一章)

@RequestMapping定义在类级别并不是必须的。没有了它所有路径都是绝对的不是相对的。下面的例子展示了一个多处理活动的控制器:

@Controller
public class ClinicController {

    private final Clinic clinic;

    @Autowired
    public ClinicController(Clinic clinic) {
        this.clinic = clinic;
    }

    @RequestMapping("/")
    public void welcomeHandler() {
    }

    @RequestMapping("/vets")
    public ModelMap vetsHandler() {
        return new ModelMap(this.clinic.getVets());
    }

}

上面的例子并没有指定GET,PUT,POST等等,因为@RequestMapping默认会映射所有HTTP方法。使用@RequestMapping(method = RequestMethod.POST)缩小了映射。

URI Template模式

URI templates 可以被用于在@RequestMapping方法中方便的访问URL中被选中的一部分。

一个URI Template是一个类似URI的字符串,包含了一个或多个变量的名字。当你替换了这些变量模版就会变成一个URI。例如http://www.example.com/users/{userId}包含变量userId。指定fred为变量的值就变成http://www.example.com/users/fred

在Spring MVC你可以用@PathVariable注解来绑定一个URI template 的变量:

@RequestMapping(value="/owners/{ownerId}", method=RequestMethod.GET)
public String findOwner(@PathVariable String ownerId, Model model) {
    Owner owner = ownerService.findOwner(ownerId);
    model.addAttribute("owner", owner);
    return "displayOwner";
}

当一个请求/owners/fred到来后,String ownerId的值就是fred。

你还可以指定URI template变量的名字:

@RequestMapping(value="/owners/{ownerId}", method=RequestMethod.GET)
public String findOwner(@PathVariable("ownerId") String theOwner, Model model) {
    // implementation omitted
}

当URI template变量的名字与方法参数的名字相同时可以省略@PathVariable注解中的值。

一个方法可以有任意数量的@PathVariable注解:

@RequestMapping(value="/owners/{ownerId}/pets/{petId}", method=RequestMethod.GET)
public String findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
    Owner owner = ownerService.findOwner(ownerId);
    Pet pet = owner.getPet(petId);
    model.addAttribute("pet", pet);
    return "displayPet";
}

当一个@PathVariable注解被用在一个Map<String, String>参数,这个map会被所有URI template变量填充。

路径模式

除了URI template外,@RequestMapping注解还支持Ant-style路径,例如/myPath/*.do。也支持URI template变量和Ant-style联合使用,例如:/owners/*/pets/{petId}

消费媒体类型

你可以通过指定一系列的消费媒体类型缩窄主映射范围。只有请求头的Content-Type与指定的媒体类型匹配时请求才会被接受,例如:

@Controller
@RequestMapping(value = "/pets", method = RequestMethod.POST, consumes="application/json")
public void addPet(@RequestBody Pet pet, Model model) {
    // implementation omitted
}

只有当请求头的Content-Type包含application/json时这个请求才会被接受并处理。消费类型表达式可以是否定的,例如!text/plain,这将会匹配所有Content-Type不包含text/plain的请求。

生产媒体类型

你可以通过指定一系列的生成媒体类型缩窄主映射范围。只有请求头的Accept与其中一个媒体类型匹配时请求才会被接受,例如:

@Controller
@RequestMapping(value = "/pets/{petId}", method = RequestMethod.GET, produces="application/json")
@ResponseBody
public Pet getPet(@PathVariable String petId, Model model) {
    // implementation omitted
}

与消费媒体类型类似,生产类型表达式可以是否定的,例如!text/plain,这将会匹配所有Accept不包含text/plain的请求。

请求参数和请求头

你可以通过请求参数条件缩窄请求映射范围,例如"myParam", "!myParam"或者"myParam=myValue"。前两个是测试参数是否存在的,第三个是指定一个参数的值。下面是一个例子:

@Controller
@RequestMapping("/owners/{ownerId}")
public class RelativePathUriTemplateController {

    @RequestMapping(value = "/pets/{petId}", method = RequestMethod.GET, params="myParam=myValue")
    public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
        // implementation omitted
    }

}

这个例子里,只有请求的参数里有myParam并且它的值为myParam时才会被这个方法处理。同样的方法可以用来测试请求头:

@Controller
@RequestMapping("/owners/{ownerId}")
public class RelativePathUriTemplateController {

    @RequestMapping(value = "/pets", method = RequestMethod.GET, headers="myHeader=myValue")
    public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
        // implementation omitted
    }

}

1.3.3 定义@RequestMapping处理方法

一个@RequestMapping处理方法可以拥有一个非常灵活的签名。

支持的参数类型

下面是支持的方法参数:

  • Request 或者 response对象 (Servlet API)
  • Session 对象 (Servlet API),这个参数永远不会是null
  • org.springframework.web.context.request.WebRequest或者org.springframework.web.context.request.NativeWebRequest。允许通用的请求参数访问,就像request/session属性访问一样,但是没有被束缚于Servlet/Portlet API
  • java.util.Locale当前请求的本地化
  • 用来访问请求内容的java.io.InputStream / java.io.Reader
  • 用来生产响应内容的java.io.OutputStream / java.io.Writer
  • org.springframework.http.HttpMethodHTTP请求方法
  • java.security.Principal当前已验证的用户
  • @PathVariable注解的参数
  • @MatrixVariable注解的参数
  • @RequestParam注解的参数
  • @RequestHeader注解的参数,用来访问HTTP请求头。
  • @RequestBody注解的参数,用来访问HTTP请求体
  • @RequestPart注解的参数,用来访问一个请求的multipart/form-data部分
  • HttpEntity<?>参数用来访问HTTP请求头和内容。
  • java.util.Map / org.springframework.ui.Model / org.springframework.ui.ModelMap传递值给web视图
  • org.springframework.web.servlet.mvc.support.RedirectAttributes在请求重定向时传递属性,如果一个方发返回一个"redirect:"前缀的视图或者RedirectView
  • @ModelAttribute注解,用来绑定请求的参数到一个实体类,例如一个User类
  • org.springframework.validation.Errors / org.springframework.validation.BindingResult上面实体类的校验结果
  • org.springframework.web.bind.support.SessionStatus标记表单处理状态为完成
  • org.springframework.web.util.UriComponentsBuilder构建当前请求的host, port, scheme, context path

需要注意的是Errors 或者 BindingResult需要紧跟着实体类。这个例子将不会正常工作:

@RequestMapping(method = RequestMethod.POST)
public String processSubmit(@ModelAttribute("pet") Pet pet, Model model, BindingResult result) { ... }

下面这个例子才是正确的:

@RequestMapping(method = RequestMethod.POST)
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result, Model model) { ... }

支持的返回类型

下面是支持的返回类型:

  • 一个ModelAndView对象
  • 一个Model对象
  • 一个Map对象
  • 一个View对象
  • 一个String对象,它可以被理解为一个本地视图名称
  • void,如果方法自己处理响应
  • 如果一个方法上有@ResponseBody注解,返回类型将被写到响应体内,如果配置了JSON转换器,返回的内容还会被转换成JSON字符串。
  • 一个HttpEntity<?> 或者 ResponseEntity<?>对象,可以用来访问HTTP响应头和内容
  • 一个HttpHeaders对象,返回一个没有响应体的response
  • 一个Callable<?>可以被返回当应用程序想在一个Spring MVC管理的线程里生产异步的返回值
  • 一个DeferredResult<?>或者ListenableFuture<?>可以被返回当应用程序想在一个它自己选择的线程里生产异步的返回值
  • 任何其他的返回值都会被当做一个model的属性暴露给视图

使用@RequestParam绑定请求参数

在controller里使用@RequestParam注解可以绑定一个请求参数到一个方法的参数。

下面的展示了使用方法:

@Controller
@RequestMapping("/pets")
@SessionAttributes("pet")
public class EditPetForm {

    // ...

    @RequestMapping(method = RequestMethod.GET)
    public String setupForm(@RequestParam("petId") int petId, ModelMap model) {
        Pet pet = this.clinic.loadPet(petId);
        model.addAttribute("pet", pet);
        return "petForm";
    }

    // ...

}

这个注解的参数默认是必须存在的,但是你可以指定它为可选的,例如:@RequestParam(value="petId", required=false)

如果方法参数的类型不是String,那么将会自动进行类型转换。

如果@RequestParam注解使用在一个Map<String, String> 或者 MultiValueMap<String, String>参数上,请求里所有的参数都会被填充进来。

使用@RequestBody注解映射请求体

@RequestBody注解声明一个方法的参数被绑定了HTTP请求体的值。

@RequestMapping(value = "/something", method = RequestMethod.PUT)
public void handle(@RequestBody String body, Writer writer) throws IOException {
    writer.write(body);
}

HttpMessageConverter负责转换HTTP请求信息到一个对象也可以把一个对象转换为HTTP响应体。

下面是几个默认的HttpMessageConverter

  • ByteArrayHttpMessageConverter 转换 byte 数组
  • StringHttpMessageConverter 转换 字符串
  • FormHttpMessageConverter 转换表单数据 到/从 一个MultiValueMap<String, String>
  • SourceHttpMessageConverter 转换 到/从 javax.xml.transform.Source

JSON转换器(译者增加):

  • FastJsonHttpMessageConverter使用FastJson转换

相关配置:

<bean id="fastJsonHttpMessageConverter"
          class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
        <property name="supportedMediaTypes">
            <list>
                <value>text/html;charset=UTF-8</value>
                <value>application/json</value>
            </list>
        </property>
        <property name="features">
            <list>
                <value>WriteMapNullValue</value>
                <value>QuoteFieldNames</value>
            </list>
        </property>
    </bean>

    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
        <property name="messageConverters">
            <list >
                <ref bean="fastJsonHttpMessageConverter" />
            </list>
        </property>
    </bean>

使用@ResponseBody注解映射响应体

@RequestBody相似。这个注解表明返回类型要被直接写入响应体中:

@RequestMapping(value = "/something", method = RequestMethod.PUT)
@ResponseBody
public String helloWorld() {
    return "Hello World";
}

当然Spring会使用HttpMessageConverter转换对象,然后才写入相应体,如果配置的是FastJsonHttpMessageConverter那么对象将会被转换成JSON字符串写入响应体。

使用@RestController注解创建REST Controllers

简单的讲就是将一个Controller所有的@RequestMapping方法自动加上@ResponseBody

使用@RequestHeader注解映射请求头

@RequestHeader注解允许一个方法参数绑定一个请求头。

@RequestMapping("/displayHeaderInfo.do")
public void displayHeaderInfo(@RequestHeader("Accept-Encoding") String encoding,
        @RequestHeader("Keep-Alive") long keepAlive) {
    //...
}

@RequestHeader注解在一个Map<String, String>, MultiValueMap<String, String>, 或者 HttpHeaders时会填充所有请求头。

使用@ControllerAdvice注解为controller增加全局处理

@ControllerAdvice注解是一个组件注解,允许实现的类被自动发现。当使用MVC命名空间时它会自动启用。它注解的
类里可以包含@ExceptionHandler, @InitBinder@ModelAttribute注解的方法,这些方法将会应用到相应的@RequestMapping方法上。

@ControllerAdvice注解还允许匹配controller的一部分子集:

// 匹配所有@RestController注解的Controller
@ControllerAdvice(annotations = RestController.class)
public class AnnotationAdvice {}

// 匹配指定包下的Controller
@ControllerAdvice("org.example.controllers")
public class BasePackageAdvice {}

// 匹配指定Controller类
@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})
public class AssignableTypesAdvice {}

1.4 解析视图

所有的MVC框架针对于web应用程序都提供了一种方法来处理视图。Spring提供了视图解析器,允许你在浏览器中渲染模型而不用你尝试使用一个特别的视图技术。Spring允许你使用JSP, Velocity templates and XSLT等视图。

有两个接口对Spring处理视图很重要,它们是ViewResolverViewViewResolver在视图名称和真实视图之间提供了一个映射。View接口引导传递请求到一个视图。

1.4.1 视图解析和ViewResolver接口

Spring提供了不少视图解析器,这个表格列出了它们的大部分;一些例子紧随其后。

ViewResolver 描述
AbstractCachingViewResolver 抽象视图解析缓存视图。通常视图需要准备后才能使用;拓展这个视图解析器可以提供缓存
XmlViewResolver 允许将配置写在XML文件里,默认的配置文件是/WEB-INF/views.xml
ResourceBundleViewResolver 使用一个properties文件配置视图。默认的文件名views.properties
UrlBasedViewResolver 根据逻辑名称匹配视图资源
InternalResourceViewResolver UrlBasedViewResolver的子类,支持InternalResourceView
VelocityViewResolver / FreeMarkerViewResolver 支持返回模板引擎的视图
ContentNegotiatingViewResolver 根据请求文件名或者Accept头解析视图

例如,如果使用JSP作为视图,你可以使用UrlBasedViewResolver。这个视图解析器将一个视图名转化为一个URL,传递请求到RequestDispatcher来渲染视图。

<bean id="viewResolver"
        class="org.springframework.web.servlet.view.UrlBasedViewResolver">
    <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
    <property name="prefix" value="/WEB-INF/jsp/"/>
    <property name="suffix" value=".jsp"/>
</bean>

当返回一个字符串test作为逻辑视图名称时,这个视图解析器会传递请求到RequestDispatcher,它会将请求送到/WEB-INF/jsp/test.jsp

当你在web应用程序中联合不同的视图技术时,可以使用ResourceBundleViewResolver

<bean id="viewResolver"
        class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
    <property name="basename" value="views"/>
    <property name="defaultParentView" value="parentView"/>
</bean>

ResourceBundleViewResolver根据basename属性指定的值检查ResourceBundle。它会使用[viewname].(class)属性作为视图类,使用[viewname].url属性作为视图url。views.properties默认路径在WEB-INF/classes

welcome.(class)=org.springframework.web.servlet.view.JstlView
welcome.url=/WEB-INF/jsp/welcome.jsp

productList.(class)=org.springframework.web.servlet.view.velocity.VelocityView
productList.url=/WEB-INF/template/test2.vm 

还有一个种方式来使用多视图技术,Spring支持多个视图解析器,它会按照配置的顺序依次匹配合适的视图。通过order属性可以指定顺序,order值越大顺序越靠后,下面是个例子:

<bean id="jspViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
    <property name="prefix" value="/WEB-INF/jsp/"/>
    <property name="suffix" value=".jsp"/>
</bean>

<bean id="excelViewResolver" class="org.springframework.web.servlet.view.XmlViewResolver">
    <property name="order" value="1"/>
    <property name="location" value="/WEB-INF/views.xml"/>
</bean>

如果到最后Spring一个视图解析都没有匹配上就会抛出一个ServletException

1.4.2 重定向视图

特殊的redirect:前缀允许你完成重定向操作。如果你返回的一个视图名字拥有redirect:前缀,UrlBasedViewResolver(及其子类)会识别出这个前缀,剩下的视图名字会被当做重定向的URL。一个逻辑视图名像这样的:redirect:/myapp/some/resource会被重定向到当前的Servlet上下文中,而redirect:http://myhost.com/some/arbitrary/path这个会重定向到指定的URL。

1.4.3 转发视图

几乎与重定向一样,只不过使用forward:前缀。

1.5 使用flash attributes

Flash attributes提供了一种方式,让一个请求存储属性来让另一个请求使用。通常用于重定向时,例如Post/Redirect/Get模式。Flash attributes在重定向前被临时存储,当重定向后被置为可用并被立即移除。

Spring MVC有两个主要的抽象支持flash attributes。FlashMap被用来承载flash attributes,FlashMapManager被用来存储,取回和管理FlashMap实例。

使用注解的controllers通常不需要直接使用FlashMap。一个@RequestMapping方法可以接收一个RedirectAttributes类型的参数,在重定向场景使用它添加flash attributes。

1.6 构建URIs

Spring MVC提供了一种机制用来构建并且编码一个URI使用UriComponentsBuilderUriComponents

例如你可以拓展和编码一个URI模版字符串:

UriComponents uriComponents = UriComponentsBuilder.fromUriString(
        "http://example.com/hotels/{hotel}/bookings/{booking}").build();

URI uri = uriComponents.expand("42", "21").encode().toUri();

注意UriComponents是不可变的,expand()encode()操作返回一个新的实例。

你还可以使用一个单独的URI components:

UriComponents uriComponents = UriComponentsBuilder.newInstance()
        .scheme("http").host("example.com").path("/hotels/{hotel}/bookings/{booking}").build()
        .expand("42", "21")
        .encode();

在一个Servlet环境ServletUriComponentsBuilder子类提供了一个静态工厂方法从一个Servlet requests中复制可用的URL信息:

HttpServletRequest request = ...

// 复用 host, scheme, port, path and query string
// 替换 the "accountId" 查询参数

ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromRequest(request)
        .replaceQueryParam("accountId", "{id}").build()
        .expand("123")
        .encode();

1.7 Spring 文件上传支持

Spring内置的multipart支持在web应用程序处理文件上传。Spring针对使用Apache Commons FileUpload的用户提供了一个MultipartResolver实现,另一个实现是针对使用Servlet 3.0 multipart request parsing的用户。

默认的,Spring不处理文件上传,因为一些开发者想自己处理文件上传。你可以通过添加一个multipart解析器到web应用程序的上下文中来启用Spring文件上传处理。每一个请求都会被检查是否包含文件上传的部分。如果没有找到文件上传的部分,请求将会继续。如果在一个请求中找到了文件上传部分,MultipartResolver将会被使用。经过处理后请求里的文件上传的属性像其他别的属性被对待。

1.7.1 MultipartResolver与Commons FileUpload

下面的例子展示了如何配置CommonsMultipartResolver

<bean id="multipartResolver"
        class="org.springframework.web.multipart.commons.CommonsMultipartResolver">

    <!-- 最大的文件尺寸(bytes) -->
    <property name="maxUploadSize" value="100000"/>

</bean>

你还需要添加Commons FileUpload的依赖:

<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.1</version>
</dependency>

当Spring DispatcherServlet发现一个上传文件的请求,它会激活配置的MultipartResolver并把请求传递过去。解析器会包装当前的HttpServletRequest成为一个MultipartHttpServletRequest来支持文件上传。使用MultipartHttpServletRequest你可以获得文件上传数据的相关信息也可以访问到文件上传的数据。

1.7.2 MultipartResolver与Servlet 3.0

为了使用基于Servlet 3.0的上传文件处理你需要在web.xml中使用"multipart-config"标记DispatcherServlet。还有编程式、使用javax.servlet.annotation.MultipartConfig注解自定义Servlet。

一旦配置好后你需要添加StandardServletMultipartResolver到你的Spring配置:

<bean id="multipartResolver"
        class="org.springframework.web.multipart.support.StandardServletMultipartResolver">
</bean>

(只要不是关于Spring的配置一般都没有例子,感兴趣可以自行搜索)

1.7.3 处理从表单上传的文件

首先创建一个表单来允许用户上传文件:

<html>
    <head>
        <title>Upload a file please</title>
    </head>
    <body>
        <h1>Please upload a file</h1>
        <form method="post" action="/form" enctype="multipart/form-data">
            <input type="text" name="name"/>
            <input type="file" name="file"/>
            <input type="submit"/>
        </form>
    </body>
</html>

下一步创建一个controller处理文件上传。

@Controller
public class FileUploadController {

    @RequestMapping(value = "/form", method = RequestMethod.POST)
    public String handleFormUpload(@RequestParam("name") String name,
            @RequestParam("file") MultipartFile file) {

        if (!file.isEmpty()) {
            byte[] bytes = file.getBytes();
            // store the bytes somewhere
            return "redirect:uploadSuccess";
        }

        return "redirect:uploadFailure";
    }

}

当使用Servlet 3.0文件上传解析时你还可以使用javax.servlet.http.Part作为方法参数:

@Controller
public class FileUploadController {

    @RequestMapping(value = "/form", method = RequestMethod.POST)
    public String handleFormUpload(@RequestParam("name") String name,
            @RequestParam("file") Part file) {

        InputStream inputStream = file.getInputStream();
        // store bytes from uploaded file somewhere

        return "redirect:uploadSuccess";
    }

}

1.8 处理异常

@ExceptionHandler注解可以在一个controller里或者一个有@ControllerAdvice注解的类里处理异常。定义在一个controller里只对那个controller里@RequestMapping方法产生的异常有效;定义在@ControllerAdvice类时对所有的controller生效。下面这个例子只对该controller本地生效:

@Controller
public class SimpleController {

    // @RequestMapping 方法省略 ...

    @ExceptionHandler(IOException.class)
    public ResponseEntity<String> handleIOException(IOException ex) {
        // prepare responseEntity
        return responseEntity;
    }

}

下面是在@ControllerAdvice注解的类中使用:

@ControllerAdvice
public class SimpleExceptionHandler {

    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleIOException(Exception ex) {
        // prepare responseEntity
        return responseEntity;
    }

}

所有Controller产生的异常都会被捕获。

自定义默认Servlet容器错误页面

你可以在web.xml声明一个<error-page>元素:

<error-page>
    <location>/error</location>
</error-page>

或者指定一个HTTP状态码:

<error-page>
    <error-code>404</error-code>
    <location>/error</location>
</error-page>

注意:实际的地址可以是一个JSP页面或者容器内其他URL包括一个@Controller方法。

@Controller
public class ErrorController {

    @RequestMapping(value="/error", produces="application/json")
    @ResponseBody
    public Map<String, Object> handle(HttpServletRequest request) {

        Map<String, Object> map = new HashMap<String, Object>();
        map.put("status", request.getAttribute("javax.servlet.error.status_code"));
        map.put("reason", request.getAttribute("javax.servlet.error.message"));

        return map;
    }

}

1.9 配置Spring MVC

1.9.1 启用MVC

Java config方式

@Configuration
@EnableWebMvc
public class WebConfig {

}

MVC XML Namespace方式

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <mvc:annotation-driven />

</beans>

1.9.2 自定义默认配置

为了使用Java自定义默认配置你仅仅需要实现WebMvcConfigurer接口或者继承WebMvcConfigurerAdapter类并重写你需要的方法。

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    protected void addFormatters(FormatterRegistry registry) {
        // Add formatters and/or converters
    }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        // Configure the list of HttpMessageConverters to use
    }

}

自定义<mvc:annotation-driven />默认配置:

<mvc:annotation-driven conversion-service="conversionService">
    <mvc:message-converters>
        <bean class="org.example.MyHttpMessageConverter"/>
        <bean class="org.example.MyOtherHttpMessageConverter"/>
    </mvc:message-converters>
</mvc:annotation-driven>

<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
    <property name="formatters">
        <list>
            <bean class="org.example.MyFormatter"/>
            <bean class="org.example.MyOtherFormatter"/>
        </list>
    </property>
</bean>

1.9.3 拦截器

你可以配置HandlerInterceptors或者WebRequestInterceptors应用在所有即将到来的请求,或者只匹配特殊的URL路径。

一个用Java注册拦截器的例子:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LocaleInterceptor());
        registry.addInterceptor(new ThemeInterceptor()).addPathPatterns("/").excludePathPatterns("/admin/");
        registry.addInterceptor(new SecurityInterceptor()).addPathPatterns("/secure/*");
    }

}

或者在XML文件中使用<mvc:interceptors>元素:

<mvc:interceptors>
    <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor" />
    <mvc:interceptor>
        <mvc:mapping path="/"/>
        <mvc:exclude-mapping path="/admin/"/>
        <bean class="org.springframework.web.servlet.theme.ThemeChangeInterceptor" />
    </mvc:interceptor>
    <mvc:interceptor>
        <mvc:mapping path="/secure/*"/>
        <bean class="org.example.SecurityInterceptor" />
    </mvc:interceptor>
</mvc:interceptors>

1.9.4 视图控制器

这个一个快捷方式定义一个ParameterizableViewController当执行时立即跳转到一个视图。使用它在静态视图的场景,当在视图生成相应之前没有Java controller逻辑执行。

一个Java配置例子,使/请求跳转到一个叫做home的视图:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("home");
    }

}

同样的配置,在XML中使用<mvc:view-controller>元素:

<mvc:view-controller path="/" view-name="home"/>

1.9.5 视图解析器

配置一个velocity模版引擎视图解析器:

<bean id="velocityViewResolver"
    class="org.springframework.web.servlet.view.velocity.VelocityViewResolver">
    <property name="suffix" value=".vm" />
    <property name="contentType" value="text/html;charset=utf-8" />
    <property name="exposeSessionAttributes" value="true" />
    <property name="allowSessionOverride" value="true" />
    <property name="toolboxConfigLocation" value="/WEB-INF/Toolbox.xml" />
</bean>

配置一个JSP视图解析器:

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
          p:prefix="/WEB-INF/view/"
          p:suffix=".jsp"/>

1.9.6 静态Resources服务

这个选项允许在一个特殊的URL路径下请求静态资源,由ResourceHttpRequestHandler服务于任何列表中的Resource位置。这提供了一种方便的方法从位置服务静态资源而不是从web应用程序根目录,包括classpath的位置。处理器还会正确的计算Last-Modified头,这样对于那些已经缓存在客户端的资源一个304状态码将会被返回,避免不必要的间接开销。

例如使用/resources/**URL模式来服务静态资源,静态资源文件目录是web应用程序根目录下的public-resources文件夹,使用Java配置:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/**").addResourceLocations("/public-resources/");
    }

}

用XML配置:

<mvc:resources mapping="/resources/**" location="/public-resources/"/>

设置一个1年的有效时间:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/**").addResourceLocations("/public-resources/").setCachePeriod(31556926);
    }

}

XML配置:

<mvc:resources mapping="/resources/**" location="/public-resources/" cache-period="31556926"/>

mapping属性必须是一个可以被SimpleUrlHandlerMapping使用的Ant模式,location必须指定一个或多个有效的资源文件夹地址。多个资源位置可以使用逗号分割的一列值。例如使用web应用程序的根目录和/META-INF/public-web-resources/

@EnableWebMvc
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/**")
                .addResourceLocations("/", "classpath:/META-INF/public-web-resources/");
    }

}

使用XML配置:

<mvc:resources mapping="/resources/**" location="/, classpath:/META-INF/public-web-resources/"/>

当一个新版本应用程序被部署时静态资源可能改变,推荐你合并一个版本字符串到映射模式,强制客户端请求新版本的资源。版本化的URL已经被内置在框架里,可以通过在资源处理器里配置一个资源链来启用。

内置的VersionResourceResolver可以被配置为不同的策略。例如,一个FixedVersionStrategy可以使用一个属性、一个日期或者其他的东西来作为版本。一个ContentVersionStrategy使用一个由资源的内容生产的MD5字符串作为版本。

ContentVersionStrategy是一个不错的默认的选择,除了有些请求不能使用(例如,与JavaScript module loaders)。你可以为不同的URL模式配置不同的版本策略。请牢记根据内容来计算版本的做法是消耗昂贵的,因此资源链缓存在产品环境一定要开启。

Java配置示例:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/")
                .addResourceLocations("/public-resources/")
                .resourceChain(true).addResolver(
                    new VersionResourceResolver().addContentVersionStrategy("/"));
    }

}

XML示例:

<mvc:resources mapping="/resources/" location="/public-resources/">
    <mvc:resource-chain>
        <mvc:resource-cache />
        <mvc:resolvers>
            <mvc:version-resolver>
                <mvc:content-version-strategy patterns="/"/>
            </mvc:version-resolver>
        </mvc:resolvers>
    </mvc:resource-chain>
</mvc:resources>

为了使上面配置生效,应用程序必须使用版本渲染URL。最简单的方式是配置ResourceUrlEncodingFilter包装response重写encodeURL方法。这个对JSP, FreeMarker, Velocity, 和其他任何调用response encodeURL 方法的视图技术都有效。

Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容