Spring Boot 2.0之Web MVC 核心
Spring Framework 时代的一般认识
实现 Controller
@Controller
public class HelloWorldController {
@RequestMapping("")
public String index() {
return "index";
}
}
配置 Web MVC 组件
app-context.xml
<?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: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="com.imooc.web"/>
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>
<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>
</beans>
部署 DispatcherServlet
web.xml
<web-app>
<servlet>
<servlet-name>app</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/app-context.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>app</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
使用可执行 Tomcat Maven 插件
<build>
<plugins>
<!--tomcat7 插件-->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<executions>
<execution>
<id>tomcat-run</id>
<goals>
<goal>exec-war-only</goal>
</goals>
<phase>package</phase>
<configuration>
<!-- ServletContext path -->
<path>/</path>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
pom.xml其他依赖
<packaging>war</packaging>
<dependencies>
<!--Servlet 3.1 API-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
<!--spring web mvc依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
</dependencies>
Spring Framework 时代的重新认识
Web MVC 核心组件
组件 Bean 类型 | 说明 |
---|---|
HandlerMapping | 映射请求(Request)到处理器(Handler)加上其关联的拦截器(HandlerInterceptor)列表,其映射关系基于不同的 HandlerMapping 实现的一些标准细节。其中两种主要 HandlerMapping 实现, RequestMappingHandlerMapping支持标注 @RequestMapping 的方法, SimpleUrlHandlerMapping 维护精确的URI路径与处理器的映射 |
HandlerAdapter | 帮助 DispatcherServlet 调用请求处理器(Handler),无需关注其中实际的调用细节。比如,调用注解实现的 Controller 需要解析其关联的注解. HandlerAdapter的主要目的是为了屏蔽与 DispatcherServlet 之间的诸多细节 |
HandlerExceptionResolver | 解析异常,可能策略是将异常处理映射到其他处理器(Handlers) 、或到某个 HTML错误页面,或者其他。 |
ViewResolver | 从处理器(Handler)返回字符类型的逻辑视图名称解析出实际的 View 对象,该对象将渲染后的内容输出到HTTP 响应中。 |
LocaleResolver,LocaleContextResolver | 从客户端解析出 Locale ,为其实现国际化视图。 |
MultipartResolver | 解析多部分请求(如 Web 浏览器文件上传)的抽象实现 |
交互流程
源代码分析
在DispatcherServle
t中,处理请求的是doDispatch
首先,根据请求路径从handlerMappings
中拿到HandlerExecutionChain
,handlerMappings
是初始化容器时赋值,具体这里先不讲。
mappedHandler = getHandler(processedRequest);
然后通过handle
找到合适的HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
处理后返回一个ModelAndView
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
处理请求结果
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
返回结果
render(mv, request, response);
根据viewName先找到View
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
由具体得View
返回视图
view.render(mv.getModelInternal(), request, response);
Web MVC 注解驱动
版本依赖 : Spring Framework 3.1 +
基本配置步骤
- 注解配置: @Configuration ( Spring 范式注解 )
- 组件激活: @EnableWebMvc (Spring 模块装配)
- 自定义组件: WebMvcConfigurer (Spring Bean)
示例重构
将app-context.xml
的配置注释起来,编程方式写一个配置类WebMvcConfig
@Configuration
@EnableWebMvc
public class WebMvcConfig {
// <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>-->
@Bean
public ViewResolver viewResolver(){
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setViewClass(JstlView.class);
viewResolver.setPrefix("/WEB-INF/jsp/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
}
断点,可以看到多了一个ViewResolver
,ViewResolverComposite
是通过@EnableWebMvc装配进来的
编程实现DispatcherServlet
- 基础接口: WebApplicationInitializer
- 编程驱动: AbstractDispatcherServletInitializer
- 注解驱动: AbstractAnnotationConfigDispatcherServletInitializer
实现WebApplicationInitializer
接口太复杂,AbstractAnnotationConfigDispatcherServletInitializer
比较好实现
将web.xml
注释起来
public class DefaultAnnotationConfigDispatcherServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {// 对应web.xml
return new Class[0];
}
@Override
protected Class<?>[] getServletConfigClasses() {// 对应DispatcherServlet
return new Class[]{DispatcherServletConfiguration.class};
}
@Override
protected String[] getServletMappings() {// 对应url-pattern
return new String[]{"/"};
}
}
DispatcherServlet
配置实现类
@ComponentScan(basePackages = {"com.imooc.web"})
public class DispatcherServletConfiguration {
}
自定义组件 : WebMvcConfigurer (Spring Bean)
实现WebMvcConfigurer
接口,里面有很多方法,这里只重写添加拦截器方法。
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new HandlerInterceptor() {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("handler="+handler);
System.out.println("拦截中");
return true;
}
});
}
控制台打印
handler=public java.lang.String com.imooc.web.HelloWorldController.index()
拦截中
常用注解
- 注册模型属性: @ModelAttribute
- 读取请求头: @RequestHeader
- 读取 Cookie: @CookieValue
- 校验参数: @Valid 、 @Validated
- 注解处理: @ExceptionHandler
- 切面通知: @ControllerAdvice
HelloWorldControllerAdvice.java所有的注解处理抽离出一个类
// 切面通知的目标对象
@ControllerAdvice(assignableTypes = HelloWorldController.class)
public class HelloWorldControllerAdvice {
@ModelAttribute("connection")
public String getConnection(@RequestHeader("Connection") String connection){
return connection;
}
@ModelAttribute("cookies")
public String getMessage(@RequestHeader("Cookie") String cookie){
return cookie;
}
@ModelAttribute("JSESSIONID")
public String getJSESSIONID(@CookieValue("JSESSIONID") String JSESSIONID){
return JSESSIONID;
}
@ModelAttribute("message")
public String getMessage(){
return "hello world.";
}
@ExceptionHandler(Throwable.class)
public ResponseEntity<String> onException(Throwable throwable){
return ResponseEntity.ok(throwable.getMessage());
}
}
index.jsp
<div>${message}</div>
<div>${connection}</div>
<div>${cookies}</div>
<div>${JSESSIONID}</div>
页面展示http://localhost:8080/?value=1
hello world.
keep-alive
JSESSIONID=F178F3EC65995638A2F3DFF932BD2CD1
F178F3EC65995638A2F3DFF932BD2CD1
如果不带value=1,则报错,但是页面返回200,因为使用@ExceptionHandler(Throwable.class)做了处理
Required int parameter 'value' is not present
Spring Boot 时代的简化
重构示例
其他任何配置类都不需要,只需要一个配置类``SpringBootWebMvcBootstrap`
@SpringBootApplication(scanBasePackages = "com.imooc.web")
public class SpringBootWebMvcBootstrap {
public static void main(String[] args) {
SpringApplication.run(SpringBootWebMvcBootstrap.class, args);
}
}
通过配置文件设置视图application.properties
spring.mvc.view.prefix = /WEB-INF/jsp/
spring.mvc.view.suffix = .jsp
完全自动装配
- 自动装配 DispatcherServlet : DispatcherServletAutoConfiguration
- 替换 @EnableWebMvc : WebMvcAutoConfiguration
- Servlet 容器 : ServletWebServerFactoryAutoConfiguration
理解自动配置顺序性
绝对顺序: @AutoConfigureOrder
相对顺序: @AutoConfigureAfter
装配条件
Web 类型判断( ConditionalOnWebApplication )
API 判断( @ConditionalOnClass )
Bean 判断( @ConditionalOnMissingBean 、 @ConditionalOnBean )
pom配置
<packaging>war</packaging>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
<!-- Provided -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
打包测试
cmd:$ mvn -Dmaven.test.skip -U clean package
启动:java -jar target/springboot-webmvc-0.0.1-SNAPSHOT.war