一、SpringMVC注解驱动开发
1、基于Servlet3.0的环境搭建
1.1、导入坐标
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!--导入jackson坐标-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.0</version>
</dependency>
</dependencies>
1.2、编写控制器
@Controller
public class HelloController {
@RequestMapping("/hello")
public String sayHello(){
System.out.println("控制器方法执行了");
return "success";
}
}
1.3、Spring的配置类
/**
* Spring的配置类,它替代了applicationContext.xml
*/
@Configuration
@ComponentScan(value="com.itheima", // 不需要扫描Controller注解,Controller有专门的配置类来扫描
excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Controller.class))
public class SpringConfiguration {
}
1.4、SpringMVC的配置类
package config;
/**
* springmvc的配置类,用于替代springmvc.xml配置文件
*/
@Configuration
@ComponentScan("com.itheima.web")
@EnableWebMvc // 开启注解MVC的支持,添加内置Bean对象。使用@DateTimeFormat(pattern = "yyyy-MM-dd")时,必须加上@EnableWebMvc
public class SpringMvcConfiguration implements WebMvcConfigurer {
/**
* 添加资源处理规则
* @param registry
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/js/**","/images/**","/css/**")
.addResourceLocations("/js/","/images/","/css/")
.resourceChain(true)
.addResolver(new VersionResourceResolver().addContentVersionStrategy("/**"));
}
/**
* 创建视图解析器并存入ioc容器
* @return
*/
@Bean
public ViewResolver createViewResolver(){
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/pages/"); // 设置前缀
viewResolver.setSuffix(".jsp"); // 设置后缀
return viewResolver;
}
}
1.5、初始化Spring和SpringMVC容器的配置类
package config;
/**
* 初始化spring和springmvc ioc容器的配置类
* 这是基于servlet3.0规范,这个类执行完成后才会执行Spring和SpringMVC的容器创建
*/
public class Config extends AbstractDispatcherServletInitializer {
/**
* 注册字符集过滤器
* @param servletContext
* @throws ServletException
*/
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
// 触发父类的onStartup
super.onStartup(servletContext);
// 1.创建字符集过滤器对象
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
// 2.设置使用的字符集
characterEncodingFilter.setEncoding("UTF-8");
// 3.添加到容器(它不是ioc容器,而是ServletContainer)
FilterRegistration.Dynamic registration = servletContext.addFilter("characterEncodingFilter", characterEncodingFilter);
// 4.添加映射
registration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST,DispatcherType.FORWARD, DispatcherType.INCLUDE), false,"/*");
//解决跨域的过滤器
// FilterRegistration.Dynamic registration1 = servletContext.addFilter("crossOriginFilter", new CrossOriginFilter());
// registration1.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST,DispatcherType.FORWARD, DispatcherType.INCLUDE), false,"/*");
}
/**
* 创建servlet容器,可以访问servlet容器bean。用于创建springmvc的Ioc容器
* @return
*/
@Override
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext acw = new AnnotationConfigWebApplicationContext();
acw.register(SpringMvcConfiguration.class);
return acw;
}
/**
* 用于指定DispatcherServlet的请求映射,访问规则
* @return
*/
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
/**
* 创建根容器,不能访问servlet容器bean。用于创建spring的ioc容器
* @return 效果:即controller可以调用service,但service不能调用controller。
*/
@Override
protected WebApplicationContext createRootApplicationContext() {
AnnotationConfigWebApplicationContext acw = new AnnotationConfigWebApplicationContext();
acw.register(SpringConfiguration.class);
return acw;
}
}
1.6、编写页面
-- index.jsp
<%@page pageEncoding="UTF-8" language="java" contentType="text/html; UTF-8" %>
<html>
<body>
<!--入门案例的请求-->
<a href="${pageContext.request.contextPath}/hello">SpringMVC基于servlet3.0规范纯注解开发的入门</a>
</body>
</html>
-- success.jsp
<%@page pageEncoding="UTF-8" language="java" contentType="text/html; UTF-8" %>
<html>
<body>
<h2>执行成功!</h2>
</body>
</html>
2、入门案例执行过程分析
2.1、初始化过程分析(P125-P126)
2.1.1、Servlet3.0规范加入的内容
/**
* Servlet3.0规范提供的标准接口。
* servlet容器的初始化接口,任何想要使用servlet3.0规范,都必须提供ServletContainerInitializer的实现类
* 而且这个实现类必须放到spring-web-5.1.6.RELEASF.jar下的services中,标注好全限定类名
* 否则servlet3.0规范读取不到这个初始化方法
*/
public interface ServletContainerInitializer {
/**
* 启动容器是做一些初始化操作,例如注册Servlet, Filter ,Listener等等。
* @see javax.servlet.annotation.HandlesTypes
* @since Servlet 3.0
*/
public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException;
}
/**
* 用于指定要加载到ServletContainerInitializer接口实现类中的字节码
* @see javax.servlet.ServletContainerInitializer
* @since Servlet 3.0
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface HandlesTypes {
/**
* 指定要加载到ServletContainerInitializer实现类的onStartUp方法中类的字节码。
* 字节码可以是接口,抽象类或者普通类。
*/
Class[] value();
}
任何要使用Servlet3.0规范且脱离web.xml的配置,在使用时都必须在对应的jar包的META-INF/services 目录创建一个名为javax.servlet.ServletContainerInitializer的文件,文件内容指定具体的ServletContainerInitializer实现类,那么,当web容器启动时就会运行这个初始化器做一些组件内的初始化工作。
2.2.3、Confifig类中的onStartUp方法**
public class Config extends AbstractDispatcherServletInitializer{
/**
* 添加字符集过滤器
* @param servletContext
* @throws ServletException
*/
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
//执行父类的onStartUp();
super.onStartup(servletContext);
//添加字符集过滤器
CharacterEncodingFilter characterEncodingFilter = new
CharacterEncodingFilter();
characterEncodingFilter.setEncoding("UTF-8");
servletContext.addFilter("characterEncodingFilter",characterEncodingFilter);
}
/**
* 创建web的Ioc容器
* @return
*/
@Override
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext acw = new
AnnotationConfigWebApplicationContext();
acw.register(SpringMVCConfiguration.class);
return acw;
}
/**
*/
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
/**
* 创建根容器(非web层的对象容器)
* @return
*/
@Override
protected WebApplicationContext createRootApplicationContext() {
AnnotationConfigWebApplicationContext acw = new
AnnotationConfigWebApplicationContext();
acw.register(SpringConfiguration.class);
return acw;
}
}
2.2.4、AbstractDispatcherServletInitializer中的onStartUp方法
public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
// 执行父类的onStartUp方法
super.onStartup(servletContext);
// 注册DispatcherServlet
registerDispatcherServlet(servletContext);
}
/**
* 注册DispatcherServlet方法
*/
protected void registerDispatcherServlet(ServletContext servletContext) {
String servletName = getServletName();
Assert.hasLength(servletName, "getServletName() must not return null or empty");
//创建表现层IoC容器
WebApplicationContext servletAppContext = createServletApplicationContext();
Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");
//创建DispatcherServlet对象
FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
servletContext.addServlet(servletName, dispatcherServlet);
if (registration == null) {
throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. " + "Check if there is another servlet registered under the same name.");
}
registration.setLoadOnStartup(1);
registration.addMapping(getServletMappings());
registration.setAsyncSupported(isAsyncSupported());
Filter[] filters = getServletFilters();
if (!ObjectUtils.isEmpty(filters)) {
for (Filter filter : filters) {
registerServletFilter(servletContext, filter);
}
}
customizeRegistration(registration);
}
/**
* 创建DispatcherServlet方法
*/
protected FrameworkServlet createDispatcherServlet(WebApplicationContext servletAppContext) {
return new DispatcherServlet(servletAppContext);
}
/**
* 设置Servlet的映射
*/
protected abstract String[] getServletMappings();
//其余代理略
}
2.2.5、注册DisptatcherServlet
public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer {
/**
* onStartUp方法,调用注册ContextLoaderListener方法
*/
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
registerContextLoaderListener(servletContext);
}
/**
* 创建根容器方法,并注册ContextLoaderListener
*/
//创建根容器方法
WebApplicationContext rootAppContext = createRootApplicationContext();
if (rootAppContext != null) {
//创建ContextLoaderListener
ContextLoaderListener listener = new
ContextLoaderListener(rootAppContext);
listener.setContextInitializers(getRootApplicationContextInitializers());
//注册ContextLoaderListener
servletContext.addListener(listener);
}else {
logger.debug("No ContextLoaderListener registered, as " + "createRootApplicationContext() did not return an application context");
}
}
//其余代码略
2.2、时序图
2.3、官方流程图
二、常用注解说明
1、基础注解
1.1、@Controller
1.1.1、源码
/**
* 此注解用用于修饰表现层控制器的注解
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
/**
* 用于指定存入IOC容器是Bean的唯一标识
*/
@AliasFor(annotation = Component.class)
String value() default "";
}
1.1.2、示例
// 默认以短类名helloController作为Bean的ID存入容器中
@Controller
public class HelloController {
}
1.2、@RequesetMapping
1.2.1、源码
/**
* 作用:
* 用于建立请求URL和处理请求方法之间的对应关系。
* 注意:
* 属性只要出现2个或以上时,他们的关系是与的关系。表示必须同时满足条件。
* 出现位置:
* 1、写在类上
* 请求URL的第一级访问目录。此处不写的话,就相当于应用的根目录。
* 它出现的目的是为了使我们的URL可以按照模块化管理,使我们的URL更加精细:
* 例如:
* 账户模块:
* /account/add
* /account/update
* /account/delete
* ...
* 订单模块:
* /order/add
* /order/update
* /order/delete
* 2、方法上:
* 请求URL的第二级访问目录。
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
/**
* name:给请求URL提供一个名称。
*/
String name() default "";
/**
* value:用于指定请求的URL。它和path属性的作用是一样的。
* 细节:在配置此属性时,写不写/都是可以的。
*/
@AliasFor("path")
String[] value() default {};
/**
* 它是4.2版本中加入的注解,和value属性是一样的。
* @since 4.2
*/
@AliasFor("value")
String[] path() default {};
/**
* method:用于指定请求的方式。它支持以下这些类型:
* GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE.
* 这些值是通过RequestMethod枚举指定的。
*/
RequestMethod[] method() default {};
/**
* params:用于指定限制请求参数的条件。它支持简单的表达式。要求请求参数的key和value必须
* 和配置的一模一样。
* 例如:
* params = {"accountName"},表示请求参数必须有accountName
* params = {"moeny!100"},表示请求参数中money不能是100。
*/
String[] params() default {};
/**
* headers:用于指定限制请求消息头的条件。
* 例如:
* RequestMapping(value = "/something", headers = "content-type=text/*")
*/
String[] headers() default {};
/**
* consumes:用于指定可以接收的请求正文类型(MIME类型)
* 例如:
* consumes = "text/plain"
* consumes = {"text/plain", "application/*"}
*/
String[] consumes() default {};
/**
* produces:用于指定可以生成的响应正文类型。(MIME类型)
* 例如:
* produces = "text/plain"
* produces = {"text/plain", "application/*"}
* produces = MediaType.APPLICATION_JSON_UTF8_VALUE
*/
String[] produces() default {};
}
1.2.2、示例
/**
* RequestMapping注解讲解的控制器
*/
@Controller
@RequestMapping("/springmvc")
public class RequestMappingController {
/**
* 用于处理请求的方法。除了name,其他条件都是"与"的关系,只要一个不满足,请求就会失败。
* 比如设置headers = {"Accept-Encoding1"},请求就会失败。因为请求头不含有Accept-Encoding1
* @return
*/
@RequestMapping(value = "/useRequestMapping", name = "使用RequestMapping注解的请求映射",
method = RequestMethod.GET, params = {"name"}, headers = {"Accept-Encoding"})
// @PostMapping(value = "useRequestMapping", name = "使用RequestMapping注解的请求映射",
// params = {"name"}, headers = {"Accept-Encoding"})
public String useRequestMapping(){
System.out.println("使用RequestMapping注解");
return "success";
}
}
<%@page contentType="text/html; UTF-8" language="java" pageEncoding="UTF-8" %>
<html>
<body>
<!--RequestMapping注解的使用-->
<a href="springmvc/useRequestMapping?name1">RequestMapping注解的使用</a>
<form action="springmvc/useRequestMapping" method="post">
<input type="text" name="name1" value="123">
<input type="submit" value="RequestMapping注解的使用 Post方式请求">
</form>
<hr/>
<a href="${pageContext.request.contextPath}/springmvc/useRequestMapping?name">RequestMapping注解的使用</a>
<form action="${pageContext.request.contextPath}/springmvc/useRequestMapping" method="post">
<input type="text" name="name" value="123">
<input type="submit" value="RequestMapping注解的使用 Post方式请求">
</form>
</body>
</html>
使用细节一:
使用细节二:
1.2.4、衍生注解@GetMapping@PostMapping@PutMapping@DeleteMapping
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.GET)
public @interface GetMapping {
//属性略
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.GET)
public @interface GetMapping {
//属性略
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.PUT)
public @interface PutMapping {
//属性略
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.DELETE)
public @interface DeleteMapping {
//属性略
}
1.3、@RequestParam
Spring可以接收多种多样的数据类型,但表单能够提供的数据类型只有三种:String(或其他基本数据类型)、String数组、文件。针对对象、集合等数据类型需要进行请求参数的封装。封装到list集合需要指定集合中的索引,要封装到map集合里面就需要指定map的key
1.3.1 说明
/**
* 此注解是从请求正文中获取请求参数,给控制器方法形参赋值的。
* 当请求参数的名称和控制器方法形参变量名称一致时,无须使用此注解。
* 同时,当没有获取到请求参数时,此注解还可以给控制器方法形参提供默认值。
* 注意:
* 它只能出现在方法的参数上
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {
/**
* 用于指定获取请求参数的名称。它和name属性的作用是一样的,
*/
@AliasFor("name")
String value() default "";
/**
* 它是在4.2版本中加入的。和value属性互为引用。
* @since 4.2
*/
@AliasFor("value")
String name() default "";
/**
* 指定参数是否必须有值。当为true时,参数没有值会报错。
*/
boolean required() default true;
/**
* 在参数没有值时的默认值。
*/
String defaultValue() default ValueConstants.DEFAULT_NONE;
}
P132
public class Config extends AbstractDispatcherServletInitializer {
/**
* 注册字符集过滤器
* @param servletContext
* @throws ServletException
*/
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
//触发父类的onStartup
super.onStartup(servletContext);
//1.创建字符集过滤器对象
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
//2.设置使用的字符集
characterEncodingFilter.setEncoding("UTF-8");
//3.添加到容器(它不是ioc容器,而是ServletContainer)
FilterRegistration.Dynamic registration = servletContext.addFilter("characterEncodingFilter",characterEncodingFilter);
//4.添加映射
registration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST,DispatcherType.FORWARD,DispatcherType.INCLUDE), false,"/*");
}
}
1.3.2 基本类型和String类型的封装
1.3.3 实体类类型的封装
表单元素的属性和实体类中的属性名称必须对应,不是实体类声明的变量,而是匹配赋值的set方法名称。
1.3.4 RequestParam注解的使用
1、解决controller中的参数名称与请求提供的名称(表单属性)不一致的问题
2、给controller中的参数加个默认值
/**
* RequestParam注解的使用
* @param page
* @return
* 1、若请求中的参数名称与controller的形参名称不一致,同时未设置@RequestParam映射,则请求接口报错
* 2、value:在请求中匹配currentPage这个名称,若能够找到,则将请求中该名称的值赋值给controller的形参page;
* 若找不到,同时未设置defaultValue,则请求接口报错,若设置有defaultValue,则将defaultValue的值赋值给形参page
* 3、defaultValue:若利用currentPage在请求中匹配不到数据,则将默认值66赋值给形参page
* 当请求参数时基本数据类型或string,入参的参数名称与控制器方法的形参名称不一致时,@RequestParam可以实现绑定的操作
* 从而实现从请求参数中获取指定名称的数据给控制器方法的形参赋值,来保证我们的形参能够拿到值
* 4、当控制器方法的形参没有值或从请求参数中获取不到值,@RequestParam可以提供默认值(分页功能)
* 5、required:若没有数据,而又设置为true,则报错
*/
@RequestMapping("/useRequestParam")
public String useRequestParam(@RequestParam(value = "currentPage", defaultValue = "66") int page){
System.out.println("当前页是:" + page);
return "success";
}
场景一:请求提供的参数名称与value名称一致,且设置有默认值,controller的形参名称值为请求值
场景二:请求提供的参数名称与value名称不一致,且设置有默认值,controller的形参名称值为默认值
场景三:请求提供的参数名称与value名称不一致,但与controller形参名称一致,且设置有默认值,controller的形参名称值为默认值
场景四:请求提供的参数名称与value名称不一致,且未设置有默认值,访问接口报错
场景五:请求提供的参数名称与value名称不一致,但与controller形参名称一致,且未设置有默认值,访问接口报错
1.4、@lnitBinder
Springmvc默认没有对Date进行转换,会导致接口请求异常
<!--InitBinder注解的使用-->
<form action="useInitBinder" method="post">
用户名:<input type="text" name="username"><br/>
密码:<input type="text" name="password"><br/>
年龄:<input type="text" name="age"><br/>
性别:<input type="text" name="gender"><br/>
生日:<input type="text" name="birthday"><br/>
<input type="submit" value="提交">
</form>
<hr/>
@RequestMapping("/useInitBinder")
public String useInitBinder(User user){
System.out.println("user is " + user);
return "success";
}
1.4.1、说明
/**
* 用于初始化表单请求参数的数据绑定器。
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface InitBinder {
/**
* 指定给哪些参数进行绑定操作。
*/
String[] value() default {};
}
1.4.2、示例
// 用户实体类
@Data
@ToString
public class User implements Serializable {
private Integer id;
private String username;
private String password;
private Integer age;
private String gender;
private Date birthday;
}
<form action="${pageContext.request.contextPath}/saveUser" method="post">
用户名:<input type="text" name="username"></br>
密码:<input type="text" name="password"></br>
年龄:<input type="text" name="age"></br>
生日:<input type="text" name="birthday"></br>
性别:<input type="radio" name="gender" value="男">男
<input type="radio" name="gender" value="女">女</br>
<input type="submit" value="提交">
</form>
// InitBinder注解的使用,只能对当前controller起作用
@Controller
public class InitBinderController {
@RequestMapping("/useInitBinder")
public String useInitBinder(User user){
System.out.println("user is " + user);
return "success";
}
/**
* @param dataBinder:用户自定义的格式化器
*
* @InitBinder可以实现初始化数据转换器,比如WebDataBinder
* 若未指定value,则对所有参数都实现绑定操作;也可以使用value对某些参数实现绑定操作
* @InitBinder只能对当前controller的参数起作用,不能对其他controller起作用
*/
// 指定给user、product对象做绑定;若没有指定,则对所有对象进行绑定操作
@InitBinder(value = {"user", "product"})
public void initBinder(WebDataBinder dataBinder){
dataBinder.addCustomFormatter(new DateFormatter("yyyy-MM-dd"));
}
}
1.4.3、扩展注解@DateTimeFormat
// 用户实体类
@Data
@ToString
public class User implements Serializable {
private Integer id;
private String username;
private String password;
private Integer age;
private String gender;
// 可以对Date进行转换,但需要在主配置文件上添加注解@EnableWebMvc,开启注解MVC的支持,添加内置Bean对象
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date birthday;
}
1.5、@ControllerAdvice
1.5.1、说明
执行控制器方法之前,指定需要执行的操作(比如:数据格式转换)。
/**
* 用于给控制器提供一个增强的通知。
* 以保证可以在多个控制器之间实现增强共享。
* 它可以配合以下三个注解来用:
* {@link exceptionhandler@exceptionhandler} :处理全局异常
* {@link initbinder@initbinder} :初始化数据绑定器
* {@link modeltattribute@modeltattribute}
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ControllerAdvice { // 控制器的通知
/**
* 用于指定对哪些包下的控制器进行增强
*
*/
@AliasFor("basePackages")
String[] value() default {};
/**
* 它是4.0版本新加入的属性,作用和value是一样的。
* @since 4.0
* 若未指定basePackages,则对所有的controller都增强一次
*/
@AliasFor("value")
String[] basePackages() default {};
/**
* 它是4.0新加入的注解,可以通过指定类的字节码的方式来指定增强作用范围。
* @since 4.0
*/
Class<?>[] basePackageClasses() default {};
/**
* 它是4.0新加入的注解,用于指定特定的类型提供增强。
* @since 4.0
*/
Class<?>[] assignableTypes() default {};
/**
* 它是4.0新加入的注解,用于指定给特定注解提供增强。
* @since 4.0
*/
Class<? extends Annotation>[] annotations() default {};
}
1.5.2、示例
// 若未指定basePackages,则对所有的controller都增强一次
@ControllerAdvice(basePackageClasses = InitBinderController.class)
public class InitBinderAdvice {
/**
* 初始化数据绑定器
* @param dataBinder
*/
// @InitBinder只能对当前controller的参数起作用,不能对其他controller起作用
// 配合@ControllerAdvice可以解决对其他controller增强的作用
@InitBinder("user")
public void dateBinder(WebDataBinder dataBinder){
dataBinder.addCustomFormatter(new DateFormatter("yyyy-MM-dd"));
}
}
/**
* 自定义类型转换器
*/
@Component
public class StringToDateConverter implements Converter<String,Date>{
@Override
public Date convert(String source) {
//1.判断来源是否有值
if (StringUtils.isEmpty(source)) {
throw new NullPointerException("Source can not be null!");
}
try{
//2.定义转换器
DateFormat format = new SimpleDateFormat("yyyy-MM-dd");
//3.转换并返回
return format.parse(source);
}catch (Exception e){
e.printStackTrace();
}
return null;
}
}
/**
* 控制器的通知
*/
@ControllerAdvice
public class InitBinderAdvice {
@Autowired
private StringToDateConverter stringToDateConverter;
/**
* 初始化数据绑定器
* @param dataBinder
*/
@InitBinder
public void initBinder(WebDataBinder dataBinder){
//1.获取转换服务对象
ConversionService conversionService = dataBinder.getConversionService();
//2.判断conversionService是否为GenericConversionService类型
if(conversionService instanceof GenericConversionService){
//3.强转
GenericConversionService genericConversionService = (GenericConversionService)conversionService;
//4.添加类型转换器
genericConversionService.addConverter(stringToDateConverter);
}
dataBinder.addCustomFormatter(new DateFormatter("yyyy-MM-dd"));
}
}
1.6、@RequestHeader
1.6.1、说明
/**
* 此注解是从请求消息头中获取消息头的值,并把值赋给控制器方法形参。
* 注意:
* 它只能出现在方法的参数上
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestHeader {
/**
* 用于指定请求消息头的名称。它和name属性作用一样。
*/
@AliasFor("name")
String value() default "";
/**
* 它是4.2版本引入的。和value属性互为引用。
* @since 4.2
*/
@AliasFor("value")
String name() default "";
/**
* 用于指定是否必须有此消息头。当取默认值时,没有此消息头会报错。
*/
boolean required() default true;
/**
* 用于指定消息头的默认值。
*/
String defaultValue() default ValueConstants.DEFAULT_NONE;
}
场景一:存在requestHeader
场景二:不存在requestHeader,同时设置有默认值
场景三:不存在requestHeader,同时未设置默认值,且required = true
1.6.3、示例
<!--RequestHader和CookieValue注解的使用-->
<a href="useRequestHeader">RequestHeader注解的使用</a>
@RequestMapping("/useRequestHeader")
public String useRequestHeader(@RequestHeader(value = "Tccept-Language",
required = false,
defaultValue = "test")
String header){
System.out.println("Accept-Language:" + header);
return "success";
}
1.7、@CookieValue
1.7.1、说明
/**
* 此注解是从请求消息头中获取Cookie的值,并把值赋给控制器方法形参。
* 注意:
* 它只能出现在方法的参数上
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CookieValue {
/**
* 用于指定cookie的名称
*/
@AliasFor("name")
String value() default "";
/**
* 它是4.2版本引入的。和value属性互为引用。
* @since 4.2
*/
@AliasFor("value")
String name() default "";
/**
* 用于指定是否必须有此消息头。当取默认值时,没有此消息头会报错。
*/
boolean required() default true;
/**
* 用于指定消息头的默认值。
*/
String defaultValue() default ValueConstants.DEFAULT_NONE;
}
1.7.2、示例
<!--RequestHader和CookieValue注解的使用-->
<a href="useRequestHeader">RequestHeader注解的使用</a>
<br/>
<a href="useCookieValue">CookieValue注解的使用</a>
<hr/>
@Controller
public class CookieValueController {
@RequestMapping("/useCookieValue")
public String useCookieValue(@CookieValue(value="JSESSIONID",required=false)
String cookieValue){
System.out.println(cookieValue);
return "success";
}
}
1.8、@ModelAttribute
1.8.1、说明
/**
* 它可以用于修饰方法,或者是参数。
* 当修饰方法时,表示执行控制器方法之前,被此注解修饰的方法都会执行。
* 当修饰参数时,用于获取指定的数据给参数赋值。。
*/
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ModelAttribute {
/**
* 当注解写在方法上,则表示存入时的名称。(值是方法的返回值)
* 当注解写在参数上,可以从ModelMap、Model、Map中的获取数据。(前提是之前存入过)
* 指定的是存入时的key。
*/
@AliasFor("name")
String value() default "";
/**
* 它和value的作用是一样的。
*/
@AliasFor("value")
String name() default "";
/**
* 用于指定是否支持数据绑定。它是4.3版本中新加入的属性。
* @since 4.3
*/
boolean binding() default true;
}
方式一:
方式二:
1.8.2、示例
@Controller
public class ModelAttributeController {
/**
* 被ModelAttribute修饰的方法
* @param username
*/
@ModelAttribute("username")
public String showModel(String username) {
System.out.println("执行了showModel方法" + username);
name = name + "aaa";
return name;
}
/**
* 接收请求的方法
* @param username
* @return
*/
@RequestMapping("/testModelAttribute")
public String testModelAttribute(@ModelAttribute("username") String username) {
System.out.println("执行了控制器的方法" + username);
return "success";
}
}
1.9、@SessionAttribute和@SessionAttributes
1.9.1、说明
/**
* 此注解是用于让开发者和ServletAPI进行解耦。
* 让开发者可以无需使用HttpSession的getAttribute方法即可从会话域中获取数据。
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SessionAttribute {
/**
* 用于指定在会话域中数据的名称。
*/
@AliasFor("name")
String value() default "";
/**
* 它和value属性互为引用
*/
@AliasFor("value")
String name() default "";
/**
* 用于指定是否必须从会话域中获取到数据。默认值是true,表示如果指定名称不存在会报错。
*/
boolean required() default true;
}
/**
* 此注解是用于让开发者和ServletAPI进行解耦。
* 通过此注解即可实现把数据存入会话域,而无需在使用HttpSession的setAttribute方法。
* 当我们在控制器方法形参中加入Model或者ModelMap类型参数时,默认是存入请求域的。
* 但当控制器上使用了此注解,就会往会话域中添加数据。
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface SessionAttributes {
/**
* 指定可以存入会话域中的名称。
*/
@AliasFor("names")
String[] value() default {};
/**
* 它是4.2版本中加入的属性。作用和value是一样的。
* @since 4.2
*/
@AliasFor("value")
String[] names() default {};
/**
* 指定可以存入会话域中的数据类型。
*/
Class<?>[] types() default {};
}
1.9.2、@SessionAttributes:往会话域存入数据
1.9.3、@SessionAttributes:从会话域获取数据
1.9.4、示例
@Controller
@SessionAttributes(value ={"username","password"},types={Integer.class})
public class SessionAttributesController {
/**
* 把数据存入SessionAttribute
* @param model
* @return
* Model是spring提供的一个接口,该接口有一个实现类ExtendedModelMap
* 该类继承了ModelMap,而ModelMap就是LinkedHashMap子类
*/
@RequestMapping("/testPut")
public String testPut(Model model){
model.addAttribute("username", "泰斯特");
model.addAttribute("password","123456");
model.addAttribute("age", 31);
//跳转之前将数据保存到username、password和age中,因为注解@SessionAttribute中有这几个参数
return "success";
}
@RequestMapping("/testGet")
public String testGet(ModelMap model){
System.out.println(model.get("username")+";"+model.get("password")+";"+model.get("age"));
return "success";
}
@RequestMapping("/testClean")
public String complete(SessionStatus sessionStatus){
sessionStatus.setComplete();
return "success";
}
@RequestMapping("/testSessionAttribute")
public String testSessionAttribute(@SessionAttribute("username")String username){
System.out.println("username is "+username);
return "success";
}
}
1.10、@ExceptionHandler
1.10.1、说明
/**
* 用于注释方法,表明当前方法是控制器执行产生异常后的处理方法
* 系统异常、错误异常
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExceptionHandler {
/**
* 指定用于需要捕获的异常类型
*/
Class<? extends Throwable>[] value() default {};
}
1.10.2、示例
/**
* ExceptionHandler注解的使用
*/
@Controller
public class ExceptionHandlerController {
@RequestMapping("/useExceptionHandler")
public String useExceptionHandler(Integer age)throws Exception{
if(age == null){
throw new NullPointerException();
}
if(age > 100){
throw new CustomerException("年龄不合法!");
}
System.out.println("年龄是:" + age);
return "success";
}
// 当前异常处理器只对当前controller有效
@ExceptionHandler(Exception.class)
public String handleException(Model model, Exception e){
String errorMsg = "";
// 判断Exception的类型是不是CustomerException,若是则执行业务异常
if(e instanceof CustomerException){
errorMsg = e.getMessage();
}else {
// 执行系统异常
errorMsg = "服务器内部错误,请联系管理员!";
}
model.addAttribute("errorMsg", errorMsg);
return "error";
}
}
/**
* 自定义异常(处理业务异常)
*/
public class CustomerException extends Exception{
private String message;
public CustomerException(String message){
this.message = message;
}
@Override
public String getMessage() {
return message;
}
}
场景一:执行业务异常
场景二:执行系统异常
// 单独声明异常处理器,利用@ControllerAdvice,可以对所有controller起作用
@ControllerAdvice
public class ExceptionHandlerAdvice {
// 当前异常处理器对所有controller有效
@ExceptionHandler(Exception.class)
public String handleException(Model model, Exception e){
String errorMsg = "";
//判断Exception的类型是不是CustomerException
if(e instanceof CustomerException){
errorMsg = e.getMessage();
}else {
//系统异常
errorMsg = "服务器内部错误,请联系管理员!";
}
model.addAttribute("errorMsg",errorMsg);
return "error";
}
}
2、JSON数据交互相关注解
2.1、@RequestBody
2.1.1、说明
/**
* 用于获取全部的请求体
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestBody {
/**
* 用于指定是否必须有请求体。
*/
boolean required() default true;
}
注意点:@RequestBody只是获取全部请求体的内容,而不能将内容转换为JSON,对JSON数据的处理需要用到其他组件
2.1.2、示例
@Controller
public class RequestBodyController {
/**
* RequestBody注解
* @return
*/
@RequestMapping("/useRequestBody")
public String useRequestBody(@RequestBody(required=false) String body){
System.out.println(body);
return "success";
}
}
2.2、@ResponseBody
2.2.1、说明
/**
* 用于用流输出响应正文
* 可以写到方法上、方法前(public字段后),代表这个方法的返回值用流的形式输出到浏览器上
* 可以写到类上,表示当前类中所有方法的返回值均采用流的形式输出到浏览器上
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseBody {
}
2.2.2、示例
@Controller
public class ResponseBodyController {
/**
* 控制器方法
* @param name
* @return
*/
@RequestMapping("useResponseBody")
public @ResponseBody String useResponseBody(String name){
return "success";
}
}
2.3、@RestController
2.3.1、说明
/**
* 它具备@Controller注解的全部功能,同时多了一个@ResponseBody注解的功能
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
/**
* 用于指定存入ioc容器时bean的唯一标识。
* @since 4.0.1
*/
@AliasFor(annotation = Controller.class)
String value() default "";
}
2.3.2、示例
@RestController
public class RestHelloController {
/**
* 控制器方法
* @return
*/
@RequestMapping("resthello")
public String sayHello(){
return "success";
}
}
2.4、@RestControllerAdvice
2.4.1、说明
/**
* 它和@ControllerAdvice注解的作用一样,并且支持@ResponseBody的功能
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ControllerAdvice
@ResponseBody
public @interface RestControllerAdvice {
@AliasFor("basePackages")
String[] value() default {};
@AliasFor("value")
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<?>[] assignableTypes() default {};
Class<? extends Annotation>[] annotations() default {};
}
2.4.2、示例
@RestControllerAdvice
public class ExceptionHandlerAdvice {
@ExceptionHandler(Exception.class)
public String handleException(Exception e){
System.err.println(e.getMessage());
return "error";
}
}
3、Rest风格URL请求相关注解
3.1、@PathVariable
3.1.1、说明
/**
* 它是springmvc框架支持rest风格url的标识。
* 它可以用于获取请求url映射中占位符对应的值。
* 若不使用PathVariable,即使名称一致,也不会获取到对应的值
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PathVariable {
/**
* 指定url映射中占位符的名称
*/
@AliasFor("name")
String value() default "";
/**
* 它是4.3.3版本新加入的属性。作用和value是一样的。
* @since 4.3.3
*/
@AliasFor("value")
String name() default "";
/**
* 它是4.3.3版本新加入的属性,用于指定是否必须有此占位符。当取默认值时,没有会报错。
* @since 4.3.3
*/
boolean required() default true;
}
3.1.2、示例
/**
* PathVariable注解的讲解
*/
@Controller
@ResponseBody
@RequestMapping("/user")
public class PathVariableController {
/**
* PathVariable注解
* @return
*/
@RequestMapping("/usePathVariable/{id}")
public String usePathVariable(@PathVariable("id") Integer id){
System.out.println(id);
return "success";
}
/**
* 保存方法
* @param user
* @return
*/
@RequestMapping(method = RequestMethod.POST)
public String save(@RequestBody User user){
System.out.println("user is "+user);
return "success";
}
/**
* 更新方法
* @param id
* @param user
* @return
*/
@RequestMapping(method = RequestMethod.PUT, value = "/{id}")
public String update(@PathVariable Integer id, User user){
//1.给user的id赋值
user.setId(id);
//2.输出
System.out.println("user is " + user);
return "success";
}
/**
* 删除方法,获取url上的userId赋值给形参id
* @param id
* @return
*/
@RequestMapping(method = RequestMethod.DELETE, value = "/{userId}")
public String delete(@PathVariable("userId") Integer id){
System.out.println("删除用户的Id是:"+ id);
return "success";
}
/**
* 根据id查询
* @param id
* @return
*/
@RequestMapping(method = RequestMethod.GET,value = "/{id}")
public String findById(@PathVariable("id") Integer id){
System.out.println("查询用户的Id是:" + id);
return "success";
}
}
4、跨域访问
4.1、关于跨域访问
跨域访问即跨站 HTTP 请求(Cross-site HTTP request),它是指发起请求的资源所在域不同于该请求所指向资源所在的域的 HTTP 请求。
比如说,域名A(http://www.itheima.example)的某 Web 应用程序中通过标签引入了域名 B(http://www.itheima.foo)站点的某图片资源(http://www.itheima.foo/image.jpg),域名A的那 Web 应用就会导致浏览器发起一个跨站 HTTP 请求。在当今的 Web 开发中,使用跨站 HTTP 请求加载各类资源(包括CSS、图片、JavaScript 脚本以及其它类资源),已经成为了一种普遍且流行的方式。
/* 允许跨域的主机地址 */
response.setHeader("Access-Control-Allow-Origin", "*");
/* 允许跨域的请求方法GET, POST, HEAD 等 */
response.setHeader("Access-Control-Allow-Methods", "*");
/* 重新预检验跨域的缓存时间 (s) */
response.setHeader("Access-Control-Max-Age", "3600");
/* 允许跨域的请求头 */
response.setHeader("Access-Control-Allow-Headers", "*");
/* 是否携带cookie */
response.setHeader("Access-Control-Allow-Credentials", "true");
4.1、@CrossOrigin
4.1.1、说明
/**
* 此注解用于指定是否支持跨域访问
*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CrossOrigin {
/** @deprecated as of Spring 5.0, in favor of {@link
CorsConfiguration#applyPermitDefaultValues} */
@Deprecated
String[] DEFAULT_ORIGINS = { "*" };
/** @deprecated as of Spring 5.0, in favor of {@link
CorsConfiguration#applyPermitDefaultValues} */
@Deprecated
String[] DEFAULT_ALLOWED_HEADERS = { "*" };
/** @deprecated as of Spring 5.0, in favor of {@link
CorsConfiguration#applyPermitDefaultValues} */
@Deprecated
boolean DEFAULT_ALLOW_CREDENTIALS = false;
/** @deprecated as of Spring 5.0, in favor of {@link
CorsConfiguration#applyPermitDefaultValues} */
@Deprecated
long DEFAULT_MAX_AGE = 1800;
/**
* 它和origins属性一样
*/
@AliasFor("origins")
String[] value() default {};
/**
* 所有支持域的集合,例如"http://domain1.com"。
* <p>这些值都显示在请求头中的Access-Control-Allow-Origin
* "*"代表所有域的请求都支持
* <p>如果没有定义,所有请求的域都支持
* @see #value
*/
@AliasFor("value")
String[] origins() default {};
/**
* 允许请求头中的header,默认都支持
*/
String[] allowedHeaders() default {};
/**
* 响应头中允许访问的header,默认为空
*/
String[] exposedHeaders() default {};
/**
* 用于指定支持的HTTP请求方式列表
*/
RequestMethod[] methods() default {};
/**
* 是否允许cookie随请求发送,使用时必须指定具体的域
*/
String allowCredentials() default "";
/**
* 预请求的结果的有效期
* 默认值是:1800秒 (30分钟).
*/
long maxAge() default -1;
}
4.1.2、示例
@RestController
@CrossOrigin
public class CrossOriginController {
@RequestMapping("/useCrossOrigin")
public String useCrossOrigin()throws Exception{
System.out.println("支持跨域访问");
return "success";
}
}
5、注解总结
1、用于配置控制器
@Controller
@RestController
2、用于提供方法映射的
@RequestMapping
@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
@PatchMapping
3、用于增强控制器方法的
@ModelAttribute
@ExceptionHandler
@InitBinder
4、用于给控制器方法提供通知的注解
@ControllerAdive
@RestControllerAdive
5、用于绑定控制器方法参数的注解
@RequestParam
@RequstBody
@RequestHeader
@CookieValue
@PathVariable
@SessionAttribute
6、用于改变响应方式的
响应的方式:转发,重定向,用流输出
@ResponeBody 用流输出的方式
7、用于在控制器方法提供Model或者ModelMap存入会话域的
@SessionAttribues
8、用于提供跨域访问
@CrossOrigin
三、SpringMVC中各组件详解及源码分析
1、前端控制器:DispatcherServlet
1.1、作用
用户请求到达前端控制器,它就相当于mvc模式中的c, dispatcherservlet是整个流程控制的中心, 由它调用其它组件处理用户的请求,dispatcherservlet的存在降低了组件之间的耦合性。
1.2、执行过程分析
1.2.1、doService方法
接收到请求首先执行的方法,它就相当于Servlet中的service方法
1.2.2、doDispatche方法
/*
处理请求分发的核心方法
它负责通过反射调用我们的控制器方法
负责执行拦截器
负责处理结果视图
/
2、处理器映射器HandlerMapping
2.1、作用
HandlerMapping负责根据用户请求找到Handler即处理器,SpringMVC提供了不同的映射器实现不
同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。
2.2、RequestMappingHandlerMapping的执行时机
3、处理器适配器HandlerAdapter
3.1、适配器模式
适配器模式就是把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口原因不匹配而无法一起工作的两个类能够一起工作。适配类可以根据参数返还一个合适的实例给客户端。
通过HandlerAdapter对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的
处理器进行执行。
3.2、控制器的三种编写方式
3.2.1、Controller注解
/**
* 用于注释控制器类
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
//指定存入ioc容器中bean的唯一标识
@AliasFor(annotation = Component.class)
String value() default "";
}
3.3.2、Controller接口
@FunctionalInterface
public interface Controller {
/**
* 用于处理请求并返回ModelAndView
*/
@Nullable
ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse
response) throws Exception;
}
3.3.3、HttpRequestHandler接口
@FunctionalInterface
public interface HttpRequestHandler {
/**
* 用于处理器请求,并由使用者提供相应
*/
void handleRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException;
}
4、视图解析器ViewResovler和View
4.1、View
首先,我们得先了解一下SpringMVC中的视图。视图的作用是渲染模型数据,将模型里的数据以某种形式呈现给客户。 为了实现视图模型和具体实现技术的解耦,Spring在org.springframework.web.servlet包中定义了一个高度抽象的View接口。我们的视图是无状态的,所以他们不会有线程安全的问题。无状态是指对于每一个请求,都会创建一个View对象。
在SpringMVC中常用的视图类型:
分类 | 视图类型 | 说明 |
---|---|---|
URL视图 | InternalResourceView | 将JSP或者其他资源封装成一个视图,是 InternaleResourceViewResolver默认使用的视图类型 |
JstlView | 它是当我们在页面中使用了JSTL标签库的国际化标签后,需要采用的类型。p | |
文档类视图 | AbstractPdfViewc | PDF文档视图的抽象类 |
AbstarctXlsViewc | Excel文档视图的抽象类,该类是4.2版本之后才有的。 之前使用的是AbstractExcelView | |
JSON视图 | MappingJackson2JsonView | 将模型数据封装成Json格式数据输出。它需要借助 Jackson开源框架 |
XML视图 | MappingJackson2XmlView | 将模型数据封装成XML格式数据。它是从4.1版本之后才加入的 |
4.2、ViewResolver
View Resolver负责将处理结果生成View视图,View Resolver首先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成View视图对象,最后对View进行渲染将处理结果通过页面展示给用户。视图对象是由视图解析器负责实例化。
视图解析器的作用是将逻辑视图转为物理视图,所有的视图解析器都必须实现ViewResolver接口。
SpringMVC为逻辑视图名的解析提供了不同的策略,可以在Spring WEB上下文中配置一种或多种解析
策略,并指定他们之间的先后顺序。每一种映射策略对应一个具体的视图解析器实现类。程序员可以选择一种视图解析器或混用多种视图解析器。可以通过order属性指定解析器的优先顺序,order越小优先级越高,SpringMVC会按视图解析器顺序的优先顺序对逻辑视图名进行解析,直到解析成功并返回视图对象,否则抛出ServletException异常。
5、请求参数封装的源码分析
5.1、传统表单数据封装原理
5.2、@RequestBody注解执行原理
5.3、@PathVariable注解实现原理
6、拦截器的执行时机和调用过程
6.1、执行流程图
6.2、拦截器的执行过程分析
6.3、拦截器的责任链模式
责任链模式是一种常见的行为模式。它是使多个对象都有处理请求的机会,从而避免请求的发送者和接收者之间的耦合关系。将这些对象串成一条链,并沿着这条链一直传递该请求,直到有对象处理它为止
1、优势:
- 解耦了请求与处理;
- 请求处理者(节点对象)只需关注自己感兴趣的请求进行处理即可,对于不感兴趣的请求,直接转发给下一级节点对象;
- 具备链式传递处理请求功能,请求发送者无需知晓链路结构,只需等待请求处理结果;
- 链路结构灵活,可以通过改变链路结构动态地新增或删减责任;
- 易于扩展新的请求处理类(节点),符合 开闭原则;
2、弊端:
- 责任链路过长时,可能对请求传递处理效率有影响;
-
7、类型转换器和异常处理器
7.1、类型转换器
7.1.1、Converter接口
@FunctionalInterface
public interface Converter<S, T> {
/**
* 提供类型转换的逻辑
*/
@Nullable
T convert(S source);
}
7.1.2、自定义Converter
public class StringToDateConverter implements Converter<String,Date>{
private String pattern;
public void setPattern(String pattern) {
this.pattern = pattern;
}
private DateFormat format;
@Override
public Date convert(String source) {
try{
//1.实例化format对象
if(StringUtils.isEmpty(pattern)){
pattern = "yyyy-MM-dd";
}
format = new SimpleDateFormat(pattern);
//2.转换字符串
return format.parse(source);
}catch (Exception e){
throw new IllegalArgumentException("给定的日期格式不对!");
}
}
}
7.1.3、注册类型转换器
// 控制器的通知
@ControllerAdvice
public class InitBinderAdvice {
@Autowired
private Converter stringToDateConverter;
/**
* 初始化数据绑定器
* @param dataBinder
*/
@InitBinder
public void initBinder(WebDataBinder dataBinder){
ConversionService conversionService = dataBinder.getConversionService();
if(conversionService instanceof GenericConversionService){
GenericConversionService genericConversionService = (GenericConversionService)conversionService;
genericConversionService.addConverter(stringToDateConverter);
}
}
}
7.2、异常处理器
7.2.1、HandlerExceptionResovler
/**
* 异常处理器的根接口
*/
public interface HandlerExceptionResolver {
/**
* 用于提供异常处理的逻辑
*/
@Nullable
ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
}
7.2.2、自定义异常处理器
@Component
public class CustomeExceptionHandlerResovler implements HandlerExceptionResolver {
/**
* 此方法是处理异常的。异常就分为系统异常和业务异常
* @param request 请求
* @param response 响应
* @param handler 当前控制器对象
* @param ex 异常对象
* @return
*/
@Override
public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
//1.创建返回值
ModelAndView mv = new ModelAndView();
//2.判断当前的ex是系统异常还是业务异常
CustomeException ce = null;
if(ex instanceof CustomeException){
//业务异常
ce = (CustomeException)ex;
//设置错误信息
mv.addObject("errorMsg", ce.getMessage());
}else{
//系统异常
//设置错误信息
mv.addObject("errorMsg","服务器忙!" + ex.getMessage());
//只输出系统异到控制台
ex.printStackTrace();
}
//3.设置响应视图
mv.setViewName("error");
return mv;
}
}
8、SpringMVC中的文件上传
8.1、MultipartFile
8.1.1、源码
/**
* SpringMVC中对上传文件的封装。
*/
public interface MultipartFile extends InputStreamSource {
/**
* 获取临时文件名称
*/
String getName();
/**
* 获取真实(原始)文件名称
*/
@Nullable
String getOriginalFilename();
/**
* 获取上传文件的MIME类型
*/
@Nullable
String getContentType();
/**
* 是否是空文件
*/
boolean isEmpty();
/**
* 获取上传文件的字节大小
*/
long getSize();
/**
* 获取上传文件的字节数组
*/
byte[] getBytes() throws IOException;
/**
* 获取上传文件的字节输入流
*/
@Override
InputStream getInputStream() throws IOException;
/**
* 把上传文件转换成一个Resource对象
*/
default Resource getResource() {
return new MultipartFileResource(this);
}
/**
* 把临时文件移动到指定位置并重命名,参数是一个文件对象
*/
void transferTo(File dest) throws IOException, IllegalStateException;
/**
* 把临时文件移动到指定位置并重命名,参数是一个文件路径
*/
default void transferTo(Path dest) throws IOException, IllegalStateException
{
FileCopyUtils.copy(getInputStream(), Files.newOutputStream(dest));
}
}
8.2、MultipartResolver
8.2.1、源码
/**
* 它是SpringMVC中文件解析器的标准。
* 通过一个接口规定了文件解析器中必须包含的方法
*/
public interface MultipartResolver {
/**
* 判断是否支持文件上传
*/
boolean isMultipart(HttpServletRequest request);
/**
* 解析HttpServletRequest
*/
MultipartHttpServletRequest resolveMultipart(HttpServletRequest request)
throws MultipartException;
/**
* 删除临时文件和一些清理操作
*/
void cleanupMultipart(MultipartHttpServletRequest request);
}
8.2.1、CommonsFileUploadResolver