增加微信订阅号(在右侧),关注后,及时收到最新更新的文章。

Spring主要功能解读(2)-Spring MVC

JAVA 智菲尔 19098℃ 0评论

在Spring MVC出现之前,有很多MVC的框架,其中最著名的要数Struts。相信,如果Spring MVC相比其它的MVC框架没有优势的话,它就没有出现的必要了。

1、Spring MVC的特性

  • 清晰的角色分离。各个角色都可以由一个特别的对象来满足功能:控制器(controller)、校验器(validator)、form对象、model对象、DispatcherServlet、映射处理器、视图解析器等。
  • 使用简单的JavaBean配置来构建强大的功能。在同一个Context中更容易关联各个组件,如Web Controller和业务对象、校验器之间可以很容易关联。
  • 可灵活扩展、非侵入性。在普通的Controller对象上的一些方法上简单地使用一些注解(如@RequestParam, @RequestHeader, @PathVariable等)就可以提供一个服务。
  • 可复用的业务代码,而不需要复制。
  • 自定义绑定和校验。可以自定义form表单对象的绑定和应用级别的日期、数字等校验。
  • 自定义映射处理器和视图解析器。
  • 灵活的model对象传输。Model采用最简单的key/value Map来构成。
  • 自定义本地化和主题解析器。
  • 强大的JSP的标签库。spring.tld、spring-form.tld。
  • 由HTTP request和HTTP Session来决定Bean的生命周期。

2、DispatcherServlet

像其它的MVC框架一样,Spring MVC被设计为:请求驱动、分发请求至Controller、以及其它web应用支持的功能。这些功能由一个核心类DispatcherServlet来支撑,并且它和Spring IoC容器集成,这就可以使用IoC容器的任何功能。DispatcherServlet类的工作原理如下图:

mvc

DispatcherServlet代表的就是图中的”Front controller“组件。

DispatcherServlet其实就是一个Servlet,要加载使用它,需要在web.xml中进行配置,每加载一个DispatcherServlet,就会启动一个相关联的WebApplicationContext,这个WebApplicationContext会继承自一个根上下文(它也是一个WebApplicationContext对象)。

2.1、配置DispatcherServlet

2.1.1、在web.xml中配置

    <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>

DispatcherServlet会加载一个对应的spring xml配置文件来创建Web Ioc容器WebApplicationContext,默认配置文件的路径为/WEB-INF/[servlet-name]-servlet.xml,上例中的配置文件路径即为:/WEB-INF/golfing-servlet.xml。DispatcherServlet的个别初始化配置参数解释如下:

    • contextClass:指定加载的WebApplicationContext的实现类,默认使用的是org.springframework.web.context.support.XmlWebApplicationContext。由FrameworkServlet.DEFAULT_CONTEXT_CLASS指定:
      	/**
      	 * Default context class for FrameworkServlet.
      	 * @see org.springframework.web.context.support.XmlWebApplicationContext
      	 */
      	public static final Class<?> DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class;
    • contextConfigLocation:指定DispatcherServlet加载的对应xml配置文件的路径,相对于web工程的根目录。如/WEB-INF/Spring/servlet.xml。如果指定了此参数,则会忽略namespace参数。
    • namespace:指定 WebApplicationContext的命名空间,默认为[servlet-name]-servlet;如果未指定contextConfigLocation参数,则根据此参数来生成配置文件的路径:/WEB-INF/[namespace].xml。源码XmlWebApplicationContext如下:
      	/** Default config location for the root context */
      	public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml";
      
      	/** Default prefix for building a config location for a namespace */
      	public static final String DEFAULT_CONFIG_LOCATION_PREFIX = "/WEB-INF/";
      
      	/** Default suffix for building a config location for a namespace */
      	public static final String DEFAULT_CONFIG_LOCATION_SUFFIX = ".xml";
      //...
      
      	@Override
      	protected String[] getDefaultConfigLocations() {
      		if (getNamespace() != null) {
      			return new String[] {DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX};
      		}
      		else {
      			return new String[] {DEFAULT_CONFIG_LOCATION};
      		}
      	}

2.1.2、容器WebApplicationContext的加载

在使用了Spring MVC的Web项目中,会加载两类WebApplicationContext。
第一类为Root容器,也可以叫做根容器,它是一个父容器,它保存在ServletContext上下文中,保存它的key由WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE来进行指定:

String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";

这个父容器是由web.xml中配置的监听器org.springframework.web.context.ContextLoaderListener进行初始加载的。它是由所有的子容器共享的。

	<!-- spring ContextLoaderListener -->
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>/WEB-INF/Spring/applicationContext.xml</param-value>
	</context-param>

第二类为和DispatcherServlet相关联的WebApplicationContext。这就是上面提到的子容器,每个配置的DispatcherServlet都会关联一个子容器,子容器之间是不可以共享的,子容器都可以共享父容器中的bean。它也保存在ServletContext下下文中,保存它的key由FrameworkServlet.getServletContextAttributeName()来指定。FrameworkServlet的部分实现代码:

public static final String SERVLET_CONTEXT_PREFIX = FrameworkServlet.class.getName() + ".CONTEXT.";

//... 

protected WebApplicationContext initWebApplicationContext() {
//...
		if (this.publishContext) {
			// Publish the context as a servlet context attribute.
			String attrName = getServletContextAttributeName();
			getServletContext().setAttribute(attrName, wac);
			if (this.logger.isDebugEnabled()) {
				this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
						"' as ServletContext attribute with name [" + attrName + "]");
			}
		}

		return wac;

}

//...

	public String getServletContextAttributeName() {
		return SERVLET_CONTEXT_PREFIX + getServletName();
	}

如果在开发中需要访问这两类容器,Spring MVC也给开发者提供了工具方法。

访问根容器:WebApplicationContextUtils.getWebApplicationContext(ServletContext sc)。

访问子容器:RequestContextUtils.getWebApplicationContext(ServletRequest request)。

2.2、WebApplicationContext下的功能Bean

  • HandlerMapping:请求到处理器的映射,关联处理器拦截器的列表(执行前置后置的拦截程序)。
  • HandlerAdapter:处理器,真正的执行请求类。
  • HandlerExceptionResolver:异常处理器。将异常映射到视图。
  • ViewResolver:视图解析器。将String类型的视图名称映射到View类型的视图对象。
  • LocaleResolver:本地化解析器。
  • ThemeResolver:主题解析器。
  • MultipartResolver:文件上传解析器。
  • FlashMapManager:支持将一个请求的数据可用于其它请求。

3、Controller实现

众所周知,Controller在MVC框架中代表的是“C”,它是一个相当重要的部件,它能识别请求输入,并将请求传递至一个Model,处理结束后,为客户端展示View。Spring提供了一种非常简单的方式来实现Controller,在Spring 2.5之后引入了基于注解的配置支持,并得到了广泛的应用,所以本文仅就注解的方式来说明Spring MVC。提前先看一个Controller的示例:

@Controller
public class HelloWorldController {

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

3.1、使用@Controller来定义Controller

使用@Controller注解的普通类就可以扮演一个Controller的角色。Spring MVC不要求Controller必须实现一个Controller基类或者遵循Servlet API。当然在Controller中还是可以使用Servlet容器的一些特性。

仅仅标注了@Controller注解,Spring MVC是识别不了的,需要在第2部分中提到的DispatcherServlet对应的xml配置文件中配置让Spring自动探测注解的类包路径。如下:

<?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>
  • xml中使用spring-context的schema来进行语法检验,使用<context:component-scan />标签来指定需要探测的类包路径。

3.2、@RequestMapping映射请求

注解@RequestMapping用于映射请求,它既可以注解于类上,也可以注解于方法上,一个完整的的URL映射应该由类上的URL模式和方法上的URL模式合并而成;@RequestMapping有一个变量method用于指定请求的方法,如:method=RequestMethod.GET,先看一个使用它的一些示例:

@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";
    }
}

上例中,getNewForm()方法上对应的完整URL映射应该为/appointments/new。下面来看一下,@RequestMapping的value属性所支持的URL模式:

3.2.1、全路径完全匹配

使用此方式,所请求的URL必须与配置模式完全匹配。示例:

@RequestMapping("/vets")

3.2.2、变量占位路径匹配

在路径中添加变量,使其更有Restful的风格。在这种情况下,要获得路径中的占位变量的值,可在方法的形参中使用注解@PathVariable获取,示例:

@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";
}

当然,@PathVariable也可以指定参数名:

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

3.2.3、模糊匹配模式

@RequestMapping也支持Ant风格的模糊匹配语法,如/myPath/*.do、/owners/*/pets/{petId}。

3.2.4、正则匹配模式

如果有类似这样的一个URL请求:”/spring-web/spring-web-3.0.5.jar”。那怎么样来配置呢?@RequestMapping支持正则表达式的语法:{varName:regex},示例:

@RequestMapping("/spring-web/{symbolicName:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{extension:\\.[a-z]+}")
  public void handle(@PathVariable String version, @PathVariable String extension) {
    // ...
  }
}

3.2.5、环境变量的支持

在模式配置中,还可以使用${…}的变量,这个变量可以是在Spring中使用PropertyPlaceholderConfigurer来配置的变量值。

3.2.6、Matrix参数

在URL中使用;号进行分段的参数叫做Matrix参数,如:”/cars;color=red;year=2012″。示例:

// GET /pets/42;q=11;r=22

@RequestMapping(value = "/pets/{petId}", method = RequestMethod.GET)
public void findPet(@PathVariable String petId, @MatrixVariable int q) {

  // petId == 42
  // q == 11

}

复杂一点的示例:

// GET /owners/42;q=11;r=12/pets/21;q=22;s=23

@RequestMapping(value = "/owners/{ownerId}/pets/{petId}", method = RequestMethod.GET)
  public void findPet(
        @MatrixVariable Map<String, String> matrixVars,
        @MatrixVariable(pathVar="petId"") Map<String, String> petMatrixVars) {

    // matrixVars: ["q" : [11,22], "r" : 12, "s" : 23]
    // petMatrixVars: ["q" : 11, "s" : 23]

  }

3.2.7、指定消费媒体类型(Consumable Media Types)

HTTP的请求头Content-Type指定了请求媒体类型,而@RequestMapping可以指定可消费的媒体类型,这就形成了一定的请求约束。使用变量consumes指定,示例:

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

consumes也可以使用“!text/plain”这种形式,表示非text/plain的媒体类型均可接受。

3.2.8、指定生产媒体类型(Producible Media Types)

和3.2.7类似,此处限制的是HTTP请求头Accept指定的媒体类型,示例:

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

它也可以使用”!”符号来配置。

3.2.9、请求参数和头参数限制

根据请求参数的存在与否,或是否等于某值来决定是否匹配。使用属性params来指定。可以取值为”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
  }
}

头信息类似:

@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
  }
}

注意,3.2.7和3.2.8中提到的规则会覆盖此处的头规则。

3.2.10、访问RequestMappingHandlerMapping和RequestMappingHandlerAdapter

在注解了@Controller的类中,@RequestMapping注解的方法中使用RequestMappingHandlerMapping和RequestMappingHandlerAdapter来访问映射和处理器信息。如:

@Autowired  
private RequestMappingHandlerMapping requestMappingHandlerMapping;

//...

@RequestMapping(value = "/")   
public void list(HttpServletResponse response) {
	Map<RequestMappingInfo, HandlerMethod> map = requestMappingHandlerMapping.getHandlerMethods();
}

3.3、定义处理器方法

通过@RequestMapping注解映射请求,最终的真正执行代码为处理器方法,即@RequestMapping注解的方法,此方法的定义有哪些规范呢?方法的签名非常灵活。

3.3.1、支持的方法参数

  • Servlet API中的Request和Response。如:ServletRequest或HttpServletRequest。
  • Session对象。如:HttpSession。注意此对象不是线程安全的,可设置RequestMappingHandlerAdapter的 “synchronizeOnSession”属性为true来同步。
  • org.springframework.web.context.request.WebRequest或org.springframework.web.context.request.NativeWebRequest。
  • java.util.Locale对象。需要在Servlet环境中配置 LocaleResolver。
  • java.io.InputStream / java.io.Reader用于访问请求输入流。
  • java.io.OutputStream / java.io.Writer用于生成的输出流。
  • java.security.Principal用于保存当前的认证用户。
  • @PathVariable注解的参数。
  • @MatrixVariable注解的参数。
  • @RequestParam注解的请求参数。
  • @RequestHeader注解的请求头参数。
  • @RequestBody注解的请求体参数。
  • @RequestPart注解的上传文件请求体参数。请求类型为”multipart/form-data”。
  • HttpEntity<?>:请求实体对象,可以访问头信息和内容体。
  • java.util.Map / org.springframework.ui.Model / org.springframework.ui.ModelMap:用于响应模型类,存储响应数据。
  • org.springframework.web.servlet.mvc.support.RedirectAttributes:用于在redirect的重定向中传递数据。
  • Command或form对象。
  • org.springframework.validation.Errors / org.springframework.validation.BindingResult对象。
  • org.springframework.web.bind.support.SessionStatus可以用来控制Session的清理。
  • org.springframework.web.util.UriComponentsBuilder可以用来查询当前请求的主机名、端口号、Schema、Context Path、Servlet Mapping信息。

3.3.2、支持的方法返回类型

  • ModelAndView 对象。包含Model和View对象,可以通过它访问@ModelAttribute注解的对象。
  • Model 对象。仅包含数据访问,通过 RequestToViewNameTranslator 来隐蔽地决定此请求返回的View视图对象。
  • Map对象。和Model相似。
  • View对象。仅包含视图数据,而model数据隐含在@ModelAttribute注解标注的对象中、或者Command对象中(方法参数的Model对象)。
  • String 值。表示View视图的名称。数据信息的保存同上。
  • void 值。当开发都直接操作ServletResponse / HttpServletResponse进行请求跳转,或者View由 RequestToViewNameTranslator 隐蔽地决定时,可使用此返回值。
  • 任意对象。如果方法被@ResponseBody注解,可采用此值。Spring会使用HttpMessageConverters将对象转化成文本输出。
  • HttpEntity<?>或ResponseEntity<?> 对象。使用此值,Spring也会使用HttpMessageConverters将对象转化成文本输出。
  • Callable<?>对象。异步请求时使用。
  • DeferredResult<?>对象。当Spring决定使用选择的某个线程产生值时可以使用此对象。

3.3.3、使用@RequestParam来获取请求参数

示例:

@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=”id”, required=false)。

3.3.4、使用@RequestBody进行请求体绑定

注解@RequestBody能将请求body映射至方法的参数中,如:

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

由HttpMessageConverter来转换body至对象。它可以被转换成支持的任意类型。 RequestMappingHandlerAdapter 默认的支持有:

  • ByteArrayHttpMessageConverter :byte[]转换。
  • StringHttpMessageConverter :转换成String。
  • FormHttpMessageConverter :转换成MultiValueMap<String, String>。
  • SourceHttpMessageConverter :转换成javax.xml.transform.Source。

转换的参数还可以加上@Valid注解,以进行转换校验,如果校验失败将抛出MethodArgumentNotValidException,此异常默认情况由 DefaultHandlerExceptionResolver进行管理,将会向客户端发送400错误码。

配置自定义的转换器,如:

<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
    <property name="messageConverters">
      <util:list id="beanList">
        <ref bean="stringHttpMessageConverter"/>
        <ref bean="marshallingHttpMessageConverter"/>
      </util:list>
    </property
</bean>

<bean id="stringHttpMessageConverter"
       class="org.springframework.http.converter.StringHttpMessageConverter"/>

<bean id="marshallingHttpMessageConverter"
      class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter">
  <property name="marshaller" ref="castorMarshaller" />
  <property name="unmarshaller" ref="castorMarshaller" />
</bean>

<bean id="castorMarshaller" class="org.springframework.oxm.castor.CastorMarshaller"/>

3.3.5、使用@ResponseBody进行响应体映射

它和上面提到的@RequestBody类似,只不过它是用来将对象转换成响应体的。转换规则和上面提到的通用。如:

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

3.3.6、使用HttpEntity<?>

HttpEntity用于接收请求数据,和@RequestBody很相似。示例:

@RequestMapping("/something")
public ResponseEntity<String> handle(HttpEntity<byte[]> requestEntity) throws UnsupportedEncodingException {
  String requestHeader = requestEntity.getHeaders().getFirst("MyRequestHeader"));
  byte[] requestBody = requestEntity.getBody();
  // do something with request header and body

  HttpHeaders responseHeaders = new HttpHeaders();
  responseHeaders.set("MyResponseHeader", "MyValue");
  return new ResponseEntity<String>("Hello World", responseHeaders, HttpStatus.CREATED);
}

3.3.7、在方法上使用@ModelAttribute

@ModelAttribute使用在方法上,就相当于一个公共model池一样(前提是在同一个Controller中),在同一个Controller中,所有@RequestMapping注解的方法执行前,都要先执行这些由@ModelAtrribute注解的方法,将数据存入到当前请求的Model中。它可以单个添加属性,也可以批量地添加。如下示例所示:

// Add one attribute
// The return value of the method is added to the model under the name "account"
// You can customize the name via @ModelAttribute("myAccount")

@ModelAttribute
public Account addAccount(@RequestParam String number) {
    return accountManager.findAccount(number);
}

// Add multiple attributes

@ModelAttribute
public void populateModel(@RequestParam String number, Model model) {
    model.addAttribute(accountManager.findAccount(number));
    // add more ...
}

@ModelAttribute当然也可以注解到@RequestMapping注解的方法上。

3.3.8、在方法的参数上使用@ModelAttribute

在方法的参数上使用@ModelAttribute注解,被注解的参数会先从请求的Model中查找,如果未找到,则会实例化一个新对象,放入请求的Model中。查找的地方有以下几个地方:

  • 存在于@SessionAttributes中。
  • 存在于同一个Controller中的注解于方法级别上的@ModelAttribute中。
  • Url的模式匹配和转换中。
@RequestMapping(value="/accounts/{account}", method = RequestMethod.PUT)
public String save(@ModelAttribute("account") Account account) {

}

在此示例中,account可能存在于注册的 Converter<String, Account>中,由{account}映射的Account中。

3.3.9、@SessionAttributes

在Controller类上使用此注解,会从请求的model中查找@SessionAttributes指定的属性名或属性类型,将它们保存到Session中。如:

@Controller
@RequestMapping("/editPet.do")
@SessionAttributes("pet")
public class EditPetForm {
    // ...
}

注:如果Controller使用了接口,则应该将@SessionAttributes或@RequestMapping注解于接口类上。

3.3.10、指定redirect转发时的属性列表

方法的签名中指定一个RedirectAttributes 类型的参数,用于在请求返回 RedirectView时传递数据。

3.3.11、处理”application/x-www-form-urlencoded”数据

Servlet API仅能处理GET和POST的请求。Spring-web为服务提供了PUT及PATCH请求的支持,使用过滤器HttpPutFormContentFilter来实现,web.xml中的配置如下:

<filter>
  <filter-name>httpPutFormFilter</filter-name>
  <filter-class>org.springframework.web.filter.HttpPutFormContentFilter</filter-class>
</filter>

<filter-mapping>
  <filter-name>httpPutFormFilter</filter-name>
  <servlet-name>dispatcherServlet</servlet-name>
</filter-mapping>

<servlet>
  <servlet-name>dispatcherServlet</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>

这个过滤器可以处理内容类型(content type)为application/x-www-form-urlencoded的PUT及PATCH请求,可以使用方法参数@RequestBody MultiValueMap<String, String>或者HttpEntity<MultiValueMap<String, String>>来获取请求参数。

3.3.12、使用@CookieValue获取Cookie数据

比较简单,直接看示例吧:

@RequestMapping("/displayHeaderInfo.do")
public void displayHeaderInfo(@CookieValue("JSESSIONID") String cookie)  {
  //...
}

3.3.13、使用@RequestHeader获取请求头数据

获取请求头数据,示例:

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

3.3.14、方法参数及类型转换

Controller接收到的原始请求数据都是文本类型,而先前使用的@RequestParam、@CookieValue等注解的参数可以为String类型,也可以是其它,Spring默认支持一些简单的数据类型如int、long、Date等类型的自动转换,如果需要对自定义的类型或复杂类型进行转换的话,需要自定义转换器绑定。绑定的方式有两种:局部绑定、全局绑定(它还有两种实现形式):

局部绑定:在@Controller中使用@InitBinder注解方法进行转换器的绑定,用它绑定的方法返回类型应设置成void,方法参数WebDataBinder集合了WebRequest和java.util.Locale,它可以将转换器绑定至Context中:

@Controller
public class MyFormController {

    @InitBinder
    public void initBinder(WebDataBinder binder) {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        dateFormat.setLenient(false);
        binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
    }
    // ...
}

上例中的CustomDateEditor就是绑定的转换器。它的作用域为当前Controller。

全局绑定:一种方式是实现一个WebBindingInitializer 接口类,将它绑定至全局配置中,如实现类ClinicBindingInitializer:

<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
    <property name="cacheSeconds" value="0" />
    <property name="webBindingInitializer">
        <bean class="org.springframework.samples.petclinic.web.ClinicBindingInitializer" />
    </property>
</bean>

另一种方式是使用@InitBinder注解于@ControllerAdvice注解的类中,来实现全局绑定。

3.3.15、如何支持Last-Modified响应

当请求头中含有If-Modified-Since时,服务端可能需要返回304告诉客户端内容无变化。在Spring中可使用对象WebRequest来实现,如:

@RequestMapping
public String myHandleMethod(WebRequest webRequest, Model model) {

    long lastModified = // 1. application-specific calculation

    if (request.checkNotModified(lastModified)) {
        // 2. shortcut exit - no further processing necessary
        return null;
     }

    // 3. or otherwise further request processing, actually preparing content
    model.addAttribute(...);
    return "myViewName";
}

检查是否改变后,直接返回null,响应码为304。

4、处理器映射器(Handler mappings)

在Spring配置请求映射器需要使用到HandlerMapping接口,在老的Spring版本中,需要在xml配置一个或多个HandlerMapping来配置请求映射。而新版本在多数使用在Controller中使用@RequestMapping来配置请求映射,这些配置由HandlerMapping的实现类RequestMappingHandlerMapping自动扫描进行加载。需要知道的是,实现了AbstractHandlerMapping的类可以使用它的一些属性来自定义功能:

  • interceptors:使用的拦截器列表,拦截器需要实现HandlerInterceptor接口。
  • defaultHandler:默认处理器,当在处理器映射中找不到对应的处理器时使用。
  • order:指定当前HandlerMapping的序号,此序号会影响Spring搜寻Handler的顺序。
  • alwaysUseFullPath:指示匹配时是根据请求的全路径进行匹配(true),还是根据Servlet Mapping的子路径进行匹配,默认为false。比如:Servlet Mapping为/testing/*,而请求路径为/testing/viewpage.html,则Spring会采用/viewpage.html去HandlerMapping中进行匹配。
  • urlDecode:默认为true,是否对请求URL进行解码。

4.1、配置拦截器(HandlerInterceptor)

Spring MVC提供了对拦截器的支持,由接口org.springframework.web.servlet.HandlerInterceptor定义,它提供了3个需要实现的方法:preHandle(..)在处理器方法执行之前执行;postHandle(..)在处理器方法执行完成后执行;afterCompletion(..)在请求完成、渲染完成后执行。拦截器的配置如:

<bean id="handlerMapping"
          class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
        <property name="interceptors">
            <list>
                <ref bean="officeHoursInterceptor"/>
            </list>
        </property>
</bean>

5、视图解析(Resolving views)

Spring提供了两个主要的接口(ViewResolver和View)来支持视图解析,ViewResolver提供了在视图名称和视图之间的映射关系,View接口用于请求的结果视图展示。

5.1、视图解析器(ViewResolver)

Spring MVC的处理器方法最终都要解析至一个视图(ModelAndView中包含),下面是Spring提供的一些视图解析器:

  • AbstractCachingViewResolver:抽象视图解析器提供了对视图的缓存。
  • XmlViewResolver:实现ViewResolver接口,提供使用xml配置视图解析映射关系,默认的配置文件为/WEB-INF/views.xml。
  • ResourceBundleViewResolver:ResourceBundleViewResolver实现ViewResolver, 在一个ResourceBundle中寻找所需bean的定义。 这个bundle通常定义在一个位于classpath中的属性文件中。默认的属性文件是views.properties。
  • UrlBasedViewResolver:UrlBasedViewResolver实现ViewResolver, 将视图名直接解析成对应的URL,不需要显式的映射定义。 如果你的视图名和视图资源的名字是一致的,就可使用该解析器,而无需进行映射。
  • InternalResourceViewResolver:作为UrlBasedViewResolver的子类, 它支持InternalResourceView(对Servlet和JSP的包装), 以及其子类JstlView和TilesView。 通过setViewClass方法,可以指定用于该解析器生成视图使用的视图类。 更多信息请参考UrlBasedViewResolver的Javadoc。
  • VelocityViewResolver / FreeMarkerViewResolver:作为UrlBasedViewResolver的子类, 它能支持VelocityView(对Velocity模版的包装)和FreeMarkerView以及它们的子类。
  • ContentNegotiatingViewResolver:实现ViewResolver接口,基于请求文件名称或者Accept头来解析视图。

看个例子,使用JSP视图,可以使用UrlBasedViewResolver,如:

<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,那么视图将会解析至/WEB-INF/jsp/test.jsp。

5.2、视图解析链

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>

<!-- in views.xml -->

<beans>
  <bean name="report" class="org.springframework.example.ReportExcelView"/>
</beans>

在使用解析器的时候,要查看说明,看使用的解析器是否支持发现不存在的视图,如InternalResourceViewResolver就不支持这种作用,也就是Spring在调用了这个解析器后,就不会再往后使用其它解析器了,如果找不到合适的视图,就会抛出异常。

5.3、Redirect重定向

在某此时候,需要使用重定向。Spring提供了一个RedirectView,可以让控制器返回这个类的一个实例,在这种情况下,DispatcherServlet不会使用通常的视图解析机制,RedirectView会调用HttpServletResponse.sendRedirect()方法。

5.3.1、redirect:前缀

处理器方法返回redirect:前缀这种方式,使得处理器只需要和视图解析器打交道,而不需要关心是需要重定向还是直接渲染,也就是控制器根本就不知道重定向的发生。

5.3.2、forward:前缀

使用包含有forward:前缀的视图名,这些视图名会被UrlBasedViewResolver和它的子类正确解析。 解析的内部实现是生成一个InternalResourceView, 这个视图最终会调用RequestDispatcher.forward()方法,将forward视图名的其余部分作为URL。 所以,当使用InternalResourceViewResolver/InternalResourceView, 并且你所用的视图技术是JSP时,你没有必要使用这个前缀。 但是,当你主要使用其它的视图技术,但仍需要对Servlet/JSP engine处理的页面强制forward时, 这个forward前缀还是很有用的。

5.4、内容协商解析器(ContentNegotiatingViewResolver)

ContentNegotiatingViewResolver本身并不进行视图解析,它只是根据请求的一些信息,来决定使用哪个解析器来解析。一般它有两种机制:后缀名、Accept头,如下示例:

<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
  <property name="mediaTypes">
    <map>
      <entry key="atom" value="application/atom+xml"/>
      <entry key="html" value="text/html"/>
      <entry key="json" value="application/json"/>
    </map>
  </property>
  <property name="viewResolvers">
    <list>
      <bean class="org.springframework.web.servlet.view.BeanNameViewResolver"/>
      <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <property name="suffix" value=".jsp"/>
      </bean>
    </list>
  </property>
  <property name="defaultViews">
    <list>
      <bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView" />
    </list>
  </property>
</bean>


<bean id="content" class="com.springsource.samples.rest.SampleContentAtomView"/>

6、文件上传支持

Spring MVC内置了对文件上传处理的支持,可以使用org.springframework.web.multipart.MultipartResolver来解析文件上传,Spring提供了一个基于Commons FileUpload的实现。默认情况下,Spring是不会自动打开文件上传处理的功能,因为一些开发者想自己来灵活地处理这一功能;如果要开启它,需要在Spring的容器中定义文件上传解析器,Spring会检查每个请求,是否有文件上传,如果有,在容器中定义的MultipartResolver将会被启用。定义示例如下:

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

    <!-- one of the properties available; the maximum file size in bytes -->
    <property name="maxUploadSize" value="100000"/>
</bean>

当然使用这个实现,需要在其classpath路径中增加依赖的jar包commons-fileupload.jar。

当Spring的DispatcherServlet探测到一个multi-part的请求,就会触发定义的MultipartResolver解析器,解析器会将当前HttpServletRequest包装成MultipartHttpServletRequest来支持文件上传功能。直接使用MultipartHttpServletRequest,你可以访问包含在请求中文件信息,实际上也可以自己更灵活地处理文件。

6.1、文件上传的完整示例

先看看html中的文件上传标签定义,特别注意的是,需要将form的enctype属性指定为multipart/form-data。如:

<form method="post" action="/form" enctype="multipart/form-data">
            <input type="text" name="name"/>
            <input type="file" name="file"/>
            <input type="submit"/>
</form>

定义Controller,方法的参数中用于处理文件的参数,可以用MultipartHttpServletRequest,也可以用更直接的MultipartFile,如:

    @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";
       } else {
           return "redirect:uploadFailure";
       }
    }

7、异常处理

7.1、异常处理器HandlerExceptionResolver

Spring的HandlerExceptionResolver的实现用于处理在controller执行中产生的异常,它和web.xml文件中配置的<error-page>非常相似。实现HandlerExceptionResolver的resolveException(Exception, Handler)方法,该方法返回ModelAndView对象,也可以使用Spring提供的实现类SimpleMappingExceptionResolver或者使用@ExceptionHandler定义方法来实现。@ExceptionHandler注解于@Controller类中,则此异常会应用于当前的Controller中的每个handler方法;如果需要全局生效可以定义于@ControllerAdvice类中。

7.2、注解@ExceptionHandler

HandlerExceptionResolver接口和SimpleMappingExceptionResolver实现可以将异常映射至特定的视图,也可以更方便地设置响应状态码和错误信息。下面是一个使用@ExceptionHandler注解的例子:

@Controller
public class SimpleController {
  // @RequestMapping methods omitted ...
  @ExceptionHandler(IOException.class)
  public ResponseEntity<String> handleIOException(IOException ex) {

    // prepare responseEntity

    return responseEntity;
  }
}

@ExceptionHandler的value值可以设置为异常类型数组,只要有一个异常匹配,就会执行其注解的方法,如果value值未设定,则将会匹配方法的参数列表类型。

7.3、处理标准的Spring MVC异常

Spring MVC在处理请求时可能会抛出一些异常,SimpleMappingExceptionResolver解析器可以将任意异常解析至一个错误视图,可能实际项目中,需要将特定的异常映射至特定的响应code码。Spring的默认解析器DefaultHandlerExceptionResolver会帮助开发者做这些事情,它会自动注解作用,它的映射关系如下:


Exception HTTP Status Code
BindException 400 (Bad Request)
ConversionNotSupportedException 500 (Internal Server Error)
HttpMediaTypeNotAcceptableException 406 (Not Acceptable)
HttpMediaTypeNotSupportedException 415 (Unsupported Media Type)
HttpMessageNotReadableException 400 (Bad Request)
HttpMessageNotWritableException 500 (Internal Server Error)
HttpRequestMethodNotSupportedException 405 (Method Not Allowed)
MethodArgumentNotValidException 400 (Bad Request)
MissingServletRequestParameterException 400 (Bad Request)
MissingServletRequestPartException 400 (Bad Request)
NoSuchRequestHandlingMethodException 404 (Not Found)
TypeMismatchException 400 (Bad Request)

当然,DefaultHandlerExceptionResolver仅能映射code码,对于Restful API服务,这可能不是友好的。在@ControllerAdvice注解的类上使用@ExceptionHandler注解方法来处理异常并返回ResponseEntity是非常方便的,这种方式允许自定义响应内容。详细可查看ResponseEntityExceptionHandler 的API文档。

7.4、使用@ResponseStatus注解业务异常

开发者可能希望自定义的业务异常能让Spring来自动管理,Spring提供了一个简单的机制,可以在自定义的异常上面使用注解@ResponseStatus,映射异常至特定的code码,DispatcherServlet会自动注解ResponseStatusExceptionResolver来处理这件事。

7.5、在Servlet容器中定义<error-page/>

开发者可以在web.xml中定义<error-page/>来映射特殊的错误及code码至特定的url,映射的url可以是jsp、可以是静态页面、可以是一个controller定义的请求。

8、Spring MVC的一些其它配置

Spring提供了一个<mvc:/>命名空间用于配置基于Spring MVC的相关配置,使用<mvc:annotation-driven />会自动帮助开发者注册一些关键的组件类。如:

<?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>

它会自动注册 RequestMappingHandlerMapping、 RequestMappingHandlerAdapter、 ExceptionHandlerExceptionResolver类,它还会自动注册一些校验机制,HttpMessageConverter数据类型转换类。

8.1、自定义一些数据转换器

如:

<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>

8.2、配置拦截器

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

8.3、内容协商

<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager" />

<bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
    <property name="favorPathExtension" value="false" />
    <property name="favorParameter" value="true" />
    <property name="mediaTypes" >
        <value>
            json=application/json
            xml=application/xml
        </value>
    </property>
</bean>

8.4、视图控制器

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

8.5、mvc:default-servlet-handler

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

转载请注明:子暃之路 » Spring主要功能解读(2)-Spring MVC

喜欢 (5)or分享 (0)
发表我的评论
取消评论
表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
(11)个小伙伴在吐槽
  1. 不止一次的来访,一如既往的支持。
    尚吾网2015-05-14 09:22 回复
  2. 第一次访问,支持一下哈。
  3. 第一次访问,支持一下哈。
    song1992.356688.com2015-05-23 22:29 回复
  4. 不知怎么就进来了,先看看
    quezhan17082015-06-13 19:13 回复
  5. 我就是随便看看
    xianjuda2015-06-18 08:44 回复
  6. 雁过留声,人过留评
    zhanglihu1012015-06-24 18:45 回复
  7. 学习学习,研究研究,呵呵
    yun2015-07-04 13:09 回复
  8. 拜读一下,哈哈
    yameimei2015-07-12 08:20 回复
  9. 持续更新,持续来访。
    eeequn2015-07-18 12:11 回复
  10. 希望你的博客如这个夏天一样火热。
    shui77772015-07-27 09:42 回复
  11. 从百度点进来的,支持一下
    bateer2015-11-11 09:26 回复