文章目录
深入了解Spring MVC
配置DispatcherServlet
DispatcherServlet初始化参数
DispatcherServlet支持以下几个初始化参数(通过<servlet>
标签下的<init-params>
标签配置)
contextClass
指定WebApplicationContext的具体实现类,默认是XmlWebApplicationContext
contextConfigLocation
指定用于创建WebApplicationContext上下文的配置文件路径,可以支持多个(多个之间用,
隔开),也支持spring通配符写法。 多个配置文件中如果有相同的bean,后面配置文件中的bean会覆盖前面定义的。namespace
指定DispatcherServlet对应的WebApplicationContext的命名空间,默认是[servlet-name]-servlet
throwExceptionIfNoHandlerFound
当无法找到合适的Handler处理请求时,是否抛出异常
主流方式(web.xml配置)
单个DispatcherServlet
web.xml中:
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/DispatcherServlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
多个DispatcherServlet
在绝大部分应用中,我们只需要配置一个DisptacherServlet就够了,这往往也是最明智的选择,但在某些特殊的场景下,如果你的业务需要配置不同的DispatcherServlet分别处理不同的请求, 也是可以的。SpringMVC提供了这个支持,这种情况下你可以配置多个不同的DispatcherServlet,分别拦截不同的请求pattern。而有些bean是应该在所有的DispatcherServlet实例中共享的(比如service、dao、数据源这些bean)我们就需要有一个可以在多个DispatcherServlet中共享的WebApplicationContext,通常这时候的架构如下所示:
我们通过ContextLoaderListener接口来初始化全局的WebApplicationContext
web.xml
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/DispatcherServlet.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
然后,不同的DispatcherServlet加载自己的配置文件,形成Servlet级别的WebApplicatoinContext
web.xml
<!-- 处理订单相关 -->
<servlet>
<servlet-name>order</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/order-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>order</servlet-name>
<url-pattern>/order/*</url-pattern>
</servlet-mapping>
<!-- 处理商品相关 -->
<servlet>
<servlet-name>goods</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/goods-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>goods</servlet-name>
<url-pattern>/goods/*</url-pattern>
</servlet-mapping>
编码方式配置(Servlet3.0以上)
/**
* 使用编程方式初始化WebApplicationContext、动态注册DispatcherServlet
*/
@Slf4j
public class MyWebInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
XmlWebApplicationContext ctx = new XmlWebApplicationContext();
ctx.setConfigLocations("classpath:/applicationContext.xml", "WEB-INF/DispatcherServlet.xml");
DispatcherServlet dispatcherServlet = new DispatcherServlet(ctx);
ServletRegistration.Dynamic servletRegistration = servletContext.addServlet("Dispatcher", dispatcherServlet);
servletRegistration.setLoadOnStartup(1);
servletRegistration.addMapping("/");
}
}
WebApplicationInitializer接口是SpringMVC提供的用于用户自定义初始化操作的回调接口,实现该接口的类会自动被SpringServletContainerInitializer类调用,而SpringServletContainerInitializer类本身实现了Serlvet3.0的ServletContainerInitializer接口,会在容器启动时自动被调用
Spring MVC中特殊的Bean
HandlerMapping
用于根据前端请求地址,查找能够处理请求的Handler
-
RequestMappingHandlerMapping:用于查找通过
@RequestMapping
注解方法形式定义的Handler -
BeanNameUrlHandlerMapping
-
SimpleUrlHandlerMapping
HandlerAdapter
负责调用具体的Handler
- RequestMappingHandlerAdapter:用于执行通过
@RequestMapping
注解方法形式定义的Handler - HttpRequestHandlerAdapter:用于处理
HttpRequestHandler
这种类型的Handler - SimpleControllerHandlerAdapter:用于处理
Controller
类型的Handler,比如典型的<mvc:view-controller>
的实现类ParameterizableViewController
就是派生自Controller接口
ViewResolver
视图解析器,负责将Handler中返回的视图名称解析成实际的视图(页面、或其他格式数据返回)
- ContentNegotiatingViewResolver:一个特殊的视图解析器,实现了ViewResolver。 自己不负责实际解析视图,只负责在众多视图解析其中寻找最匹配的请求类型的视图解析器(如果不用
ContentNegotiatingViewResolver
,默认找到一个可以解析视图的解析器就直接返回,不再继续找其他视图解析器)
View
具体的视图对象。 通过
render
方法将数据和页面结合后返回给调用方
MultipartResolver
负责处理文件上传
HandlerExceptionResolver
处理异常
LocalResolver
语言国际化相关支持
ThemeResolver
多主题样式支持
Spring MVC配置<mvc:xxx>
启用Spring MVC配置
xml方式
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<mvc:annotation-driven/>
</beans>
注解方式
@Configuration
@EnableWebMvc
public class WebConfig {
}
配置类型转换器
配置数据校验器
配置拦截器
配置视图控制器
一般用于控制无逻辑视图的跳转,配置见下面的《配置无逻辑视图跳转控制器》章节
配置支持的mime类型
配置视图解析器
配置方法详解下面的《视图相关》章节:
在Spring IOC容器中配置一个ViewResolver类型的
<bean>
在SpringMVC中视图解析器的作用是连接逻辑视图名和具体的视图页面。 具体来说就是用来解析RequestMapping方法返回的逻辑视图名,返回具体的视图给客户端(如:可以是一个页面、一个文件或JSON格式内容)
SpringMVC支持同时配置多个视图解析器(每个视图解析器就是在IOC中配置一个bean),它们会组成一个视图解析器链,可以通过指定order属性来设定他们的顺序,order属性值越大,在链中越靠后。
如果配置了多个视图解析器,必须确保InternalResourceViewResolver是最后一个
ViewResolver架构层级图
AbstractCachingViewResolver
扩展自该类的子类都支持缓存View的实例,可以通过设置cache
属性为false
禁止缓存
XmlViewResolver
UrlBasedViewResolver
该类是ViewResolver
接口的一个简单实现,支持将一个逻辑视图名映射成一个具体的URL,而无需配置显示的映射定义。适用于逻辑视图名和视图资源有明确对应关系的情况。
- InternalResourceViewResolver:扩展自
UrlBasedViewResolver
,支持解析InternalResourceView
视图(比如:Servlet、JSP) - FreeMarkerViewResolver:扩展自
UrlBasedViewResolver
,支持解析FreeMarkerView
视图
ResourceBundleViewResolver
ContentNegotiatingViewResolver
ViewResolver
顶层接口的另一种实现,基于请求的文件名或Accept
Header,来选择合适的ViewResolver解析视图(自己本身不负责解析视图)
配置无逻辑视图跳转控制器
无逻辑视图跳转即不经过Action,直接跳转页面
如果我们有些请求只是想跳转页面,不需要来后台处理什么逻辑,我们无法在Action中写一个空方法来跳转,直接在中配置一个如下的视图跳转控制器即可(不经过Action,直接跳转页面)
<mvc:view-controller path="/" view-name="home"/>
静态资源处理
配置示例:
<mvc:resources mapping="/resources/**"
location="/public, classpath:/static/"
cache-period="31556926" />
配置消息转换器
详见上一章的消息转换器。
完全使用默认的配置
默认的配置在spring-webmvc包中的DispatcherServlet.properties文件中可以看到
支持的注解配置
这一部分的内容大多已经在第二章Spring MVC详解(二)讲过,就不再详细说明。
定义Controller
- @Controller
- @RestController
请求映射
获取请求地址映射参数
处理请求方法相关
方法注解
- @RequestMapping:可以处理所有的请求,可以在Method属性中指明处理的请求方式,只处理某种请求
- @GetMapping:只能处理Get请求
- @PostMapping:只能处理Post请求
方法参数
-
@RequestHeader
-
@RequestParam
-
@CookieValue
-
@SessionAttribute
-
@RequestAttribute
-
@PathVariable:获取URI中的参数
-
@RequestBody
- 获取请求体内容。
- 如果该注解标注的参数是一个自定义类型SpringMVC会自动调用MessageConterver将请求体中的数据转成对应的Object对象。(
比如客户端提交的JSON格式数据会自动直接转成方法参数中对应的Object类型
)
-
@RequestPart
-
Part、MultiPartFile
-
HttpMethod
-
原生javax对象
javax.servlet.ServletRequest
javax.servlet.ServletResponse
javax.servlet.http.HttpSession
-
org.springframework.ui.Model、ModelAndView、ModelMap、java.util.Map:用于将后台数据传递到视图中
示例:
Controller代码:public String queryGoods(Model model) { List<Goods> goodsList = goodsService.queryGoods(...); model.addAttribute("goods", goodsList); return "goods_list"; }
goods_list.jsp代码:
<body> ${goods} </body>
-
InputStream、OutputStream
InputStream
参数用于获取request的请求体OutputStream
参数用于获取response.getOutputStream对象
方法返回值
-
String视图名称
-
View视图名称
-
@RequestBody:将返回的内容本身写入响应体中,而不是以视图的方式渲染
-
ResponseEntity
和@ResponseBody类似,不过增加了响应状态和响应头。
示例:@RequestMapping("/test/resp_entity") public ResponseEntity<String> testRespEntity() { System.out.println("testRespEntity"); return ResponseEntity.ok().header("Content-Type", "application/json;charset=UTF-8").body("{\"name\": \"张三\", \"age\": 21}"); }
-
Map、Model、ModelAndView
-
void:适合于方法中带有
HttpResponse
、OutputStream
或者通过@ResponseStatus
注解标注的方法
异常处理
可以通过在一个Controller中的一个方法上添加@ExceptionHandler注解来处理全局的异常
/**
* 处理全局异常
*
* @author 召
*/
@Slf4j
@ControllerAdvice
public class ExceptionControllerAdvice {
/**
* 定义全局异常处理
*
* @param ex 异常栈信息
* @return
*/
@ExceptionHandler
public ResponseEntity<String> handle(Exception ex) {
log.error("出错了", ex);
return new ResponseEntity(HttpStatus.BAD_REQUEST);
}
}
数据绑定
默认数据绑定器
SpringMVC提供了一个默认的数据绑定器WebDataBinder
负责将请求中的参数封装到JavaBean的同名属性中,大大减少了我们的代码量。
@RequestMapping("/test/bind_obj")
public String testBindObject(Student student, Model model) {
System.out.println("testBindObject, student: " + student);
model.addAttribute("msg", student);
return "main";
}
访问地址:
http://localhost:8080/test/bind_obj?sname=张三&age=34&id=2
更改默认数据绑定器行为
所有通过@Controller
、@ControllerAdvice
注解标注的类都可以定义一个用@InitBinder
注解标注的方法来自定义数据绑定器的行为,如:
@Controller
public class FormController {
@InitBinder
public void initBinder(WebDataBinder binder) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
dateFormat.setLenient(false);
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
}
// ...
}
自定义Formatter
@Controller
public class FormController {
@InitBinder
protected void initBinder(WebDataBinder binder) {
binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd"));
}
// ...
}
视图相关
都是通过视图控制器来处理
Thymeleaf
FreeMarker
xml中配置:
<mvc:view-resolvers>
<mvc:freemarker/>
</mvc:view-resolvers>
<!-- Configure FreeMarker... -->
<mvc:freemarker-configurer>
<mvc:template-loader-path location="/WEB-INF/freemarker"/>
</mvc:freemarker-configurer>
JSP&JSTL
配置示例:
<bean id="viewResolver" 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>
JSON
详见第三章的返回JSON数据
跨多个Controller共享配置
我们在Controller中配置的@ExceptionHandler
、@InitBinder
、@ModelAttribute
(下面简称:配置项)都只在当前Controller类中有效,如果想让他们可以在多个Controller中共享(全局有效),则需要将其定义到通过@ControllerAdvice
或者@RestControllerAdvice
注解的类中
用法如下:
// 此类中的配置项在所有通过@RestController注解的Controller中共享
@ControllerAdvice(annotations = RestController.class)
public class ExampleAdvice1 {}
// 此类中的配置项在指定包的Controller类中共享
@ControllerAdvice("org.example.controllers")
public class ExampleAdvice2 {}
// // 此类中的配置项在所有扩展自指定类的Controller中共享
@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})
public class ExampleAdvice3 {}
异步请求
处理跨域请求
全局跨域请求配置
配置示例:
<mvc:cors>
<mvc:mapping path="/api/**"
allowed-origins="https://domain1.com, https://domain2.com"
allowed-methods="GET, PUT"
allowed-headers="header1, header2, header3"
exposed-headers="header1, header2" allow-credentials="true"
max-age="123" />
<mvc:mapping path="/resources/**"
allowed-origins="https://domain1.com" />
</mvc:cors>
在类级别和方法级别启用跨域请求支持
SpringMVC支持类级别和方法级别的跨域请求配置
方法级别,示例:
@CrossOrigin
@GetMapping("/{id}")
public Account retrieve(@PathVariable Long id) {
// ...
}
类级别,示例:
@CrossOrigin(origins = "https://domain2.com", maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {
@GetMapping("/{id}")
public Account retrieve(@PathVariable Long id) {
// ...
}
@DeleteMapping("/{id}")
public void remove(@PathVariable Long id) {
// ...
}
}
缓存相关
异常处理链
SpringMVC支持我们配置多个异常处理器, 他们会像过滤器一样,以chain的方式依次调用,下面是SpringMVC内置的四个异常处理实现类:
- SimpleMappingExceptionResolver
- DefaultHandlerExceptionResolver
- ResponseStatusExceptionResolver
- ExceptionHandlerExceptionResolver
通用的异常处理约定如下:
-
在请求处理方法中返回一个指向错误页面的ModelAndView对象
-
如果某个一个异常解析器中已经处理了异常则返回一个空的ModelAndView对象
-
如果当前异常解析器不能处理异常,则返回null, 这样下一个异常解析器就会尝试处理异常,一直往下直到某个异常解析器处理掉异常,如果所有异常解析器都无法处理,则将异常抛给Servlet容器
REST客户端工具
- RestTemplate
- WebClient