Java – Spring MVC 快查
SpringMVC简介
Spring Web MVC是基于Servlet API构建的原始Web框架,从一开始就包含在Spring Framework中。正式名称“Spring Web MVC”来自其源模块的名称( spring-webmvc ),但它通常被称为“Spring MVC”。
SpringMVC的主要运行原理
1.DispatcherServlet : SpringMVC提供,我们需要使用web.xml配置使其生效,它是整个流程处理的核心,所有请求都经过它的处理和分发!
2. HandlerMapping : SpringMVC提供,我们需要进行IoC配置使其加入IoC容器方可生效,它内部缓存handler(controller方法)和handler访问路径数据,被DispatcherServlet调用,用于查找路径对应的handler
3. HandlerAdapter : SpringMVC提供,我们需要进行IoC配置使其加入IoC容器方可生效,它可以处理请求参数和处理响应数据数据,每次DispatcherServlet都是通过handlerAdapter间接调用handler,他是handler和DispatcherServlet之间的适配器!
4. Handler : handler又称处理器,他是Controller类内部的方法简称,是由我们自己定义,用来接收参数,向后调用业务,最终返回响应结果!
5. ViewResovler : SpringMVC提供,我们需要进行IoC配置使其加入IoC容器方可生效!视图解析器主要作用简化模版视图页面查找的,但是需要注意,前后端分离项目,后端只返回JSON数据,不返回页面,那就不需要视图解析器!所以,视图解析器,相对其他的组件不是必须的!
配置 Spring MVC
SpringMVC与Servlet的启动关系
SpringMVC 其实是 Servlet 的封装,当用户访问服务器的时候,请求会到达 DispatcherServlet ,由DispatcherServlet 向 HandlerMapping 查找对应的 url 与 controller 方法。
DispatcherServlet 把数据提交给 HandlerAdapter 以整理请求的数据,让HandlerAdapter 去找 controller方法(Handler ),由 Handler 处理业务需求后返回数据给 HandlerAdapter ,HandlerAdapter 把返回的数据整理好发给 DispatcherServlet 并返回给用户
在这期间,DispatcherServlet 、HandlerMapping 、HandlerAdapter 、Handler 都应该被IoC所管理,所以要执行一个Web应用时,这4个组件都应该存放到Spring IoC 中。
Servlet 的启动其实是由Tomcat管理,Tomcat 作为Servlet的容器,Servlet运行在Tomcat中,而SpringMVC又运行在Servlet中,因此,我们需要利用Servlet来启动Spring IoC容器。而Servlet是由web.xml来配置启动的,按原本来说,我们应该在web.xml文件中配置DispatcherServlet 作为Servlet的处理类。
但是 SpringMVC 提供了一个更好的方式,可以免除使用 web.xml 来配置SpringIoC 容器,默认让Servlet的处理由DispatcherServlet 处理,并提供 AbstractAnnotationConfigDispatcherServletInitializer 类进行其它必要的组件配置。
在Servlet启动后,会调用 WebApplicationInitializer 接口的 onStartUp 方法,在这个方法中创建了 IoC容器和DispatcherServlet
我们通过继承 AbstractAnnotationConfigDispatcherServletInitializer 类的方法,来处理启动时初始化到IoC容器的配置类。
/**
* 用于替代 web.xml 配置 Servlet 的类
* SpringMVC 会自动创建一个 IoC 容器,并把配置类引入到IoC中
*/
public class SpringMvcInit extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[0];
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{SpringMVCConfig.class};
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
其中,getServletConfigClasses 方法则是声明存入IoC的初始化配置类,getServletMappings 方法则是声明请求拦截规则,“/” 表示所有的请求都会被拦截。
接下来我们看看SpringMVCConfig类的代码
@Configuration
@ComponentScan(basePackages = "cn.unsoft")
public class SpringMVCConfig {
@Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping(){
return new RequestMappingHandlerMapping();
}
@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter(){
return new RequestMappingHandlerAdapter();
}
}
可以看到,在配置类中,我们把 HandlerMapping 和 HandlerAdapter 都存入IoC容器中了。而Handler则通过 @Controller 或 @RestController 注解进入IoC容器中。
至此,DispatcherServlet 、HandlerMapping 、HandlerAdapter 、Handler四个必须的成员都依次存入IoC容器中了。
接下来只要配置 Tomcat 服务器,就可以启动由SpriongMVC驱动的Web项目了
SpringMVC 接收数据
访问路径设置 @RequestMapping
@RequestMapping 的作用是注册地址,将handler方法注册到handlerMapping中。
1.请求地址不要求有 / 开头,如 "user/login" "/user/login" "/user/login/" 都可以
2.请求地址可以有多个
3.请求地址支持模糊匹配,* 表示任意一层字符串,** 表示任意层任意字符串
@RequestMapping 声明位置,可以放在类上,也可以放在方法上
放在类上,则该请求url串将应用在整个类上。
放在方法上,则该请求会拼接放在类上的@RequestMapping请求url地址
@RequestMapping 请求方式指定
在不指定请求方式默认情况下,任意请求方式都被接收。
指定其它请求方式,可指定一个或多个:
public enum RequestMethod {
GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE;
}
单独注解代替 @RequestMapping 请求方式,只能使用在方法上
@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
@PatchMapping
Param参数接收
直接接收
前端发送过来的param参数,可以直接进行接收,前提是接收的形参名需要与param传入的参数名一致
/param?name=TZMing&age=18
@GetMapping("/param")
public String param(String name, Integer age) {
return null;
}
不传递也不会报错。
@RequestParam 注解接收
指定任意的请求参数名,可以设置要求必须传递,也可以设置不要求必须传递,同时也可以设置参数默认值。
@GetMapping("/reqparam")
public String reqparam(@RequestParam(value = "name",required = true,defaultValue = "admin") String name,Integer age){
return null;
}
value:指定接收前端传入的param参数名
required:指定该参数前端必须传入,默认是必须传递,不则会报 400 错误
defaultValue:指定参数的默认值,当指定默认值时,required 必须为 false
特殊值接收
一名多值情况:
当param传递了一名多值,如 key=a&key=b&key=c 时:
使用List集合进行接收:
@GetMapping("/list")
public String list(@RequestParam("key") List<String> key){
return null;
}
注意:集合接收时必须使用@RequestParam,若不加@RequestParam将key对应的一个字符串直接赋值给集合,产生类型异常。
使用实体对象接收情况:
准备一个对应属性和带有get和set方法的实体类即可。
/object?name=TZMing&age=18
@Data
public class Person {
private String name;
private Integer age;
}
@GetMapping("object")
public String object(Person person){
return null;
}
注意,使用实体类对象接收无法使用 @RequestParam 定义,因此实体对象的成员变量必须与param参数名一致
@PathVariable 路径参数接收
@PathVariable 所接收的是请求url中的可变参数,使用 { key } 来定其参数名
@GetMapping("path/{id}")
public String path(@PathVariable("id") String id){
return null;
}
Json 参数接收
Java Servlet 默认不支持 JSON 数据,要支持JSON传参数,
1.需要导入json处理的依赖
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.14.2</version>
</dependency>
2.在handlerAdapter中配置Json转化器,添加 @EnableWebMvc 注解
@Configuration
@ComponentScan(basePackages = "cn.unsoft")
@EnableWebMvc // 相当于 handlerAdapter中配置了json转化器
public class SpringMVCConfig { ... }
@EnableWebMvc 注解说明
@EnableWebMvc注解效果等同于在 XML 配置中,可以使用 <mvc:annotation-driven> 元素!我们来解析<mvc:annotation-driven>对应的解析工作!
让我们来查看下<mvc:annotation-driven>具体的动作!
可以看到 <mvc:annotation-driven> 标签会触发处理类 MvcNamespaceHandler,而MvcNamespaceHandler中就有处理 annotation-driven 动作的代码:
打开 AnnotationDrivenBeanDefinitionParser 类,可以看到相关的处理代码:
class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
public static final String HANDLER_MAPPING_BEAN_NAME = RequestMappingHandlerMapping.class.getName();
public static final String HANDLER_ADAPTER_BEAN_NAME = RequestMappingHandlerAdapter.class.getName();
static {
ClassLoader classLoader = AnnotationDrivenBeanDefinitionParser.class.getClassLoader();
javaxValidationPresent = ClassUtils.isPresent("jakarta.validation.Validator", classLoader);
romePresent = ClassUtils.isPresent("com.rometools.rome.feed.WireFeed", classLoader);
jaxb2Present = ClassUtils.isPresent("jakarta.xml.bind.Binder", classLoader);
jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) &&
ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
jackson2SmilePresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", classLoader);
jackson2CborPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.cbor.CBORFactory", classLoader);
gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);
}
@Override
@Nullable
public BeanDefinition parse(Element element, ParserContext context) {
//handlerMapping加入到ioc容器
readerContext.getRegistry().registerBeanDefinition(HANDLER_MAPPING_BEAN_NAME, handlerMappingDef);
//添加jackson转化器
addRequestBodyAdvice(handlerAdapterDef);
addResponseBodyAdvice(handlerAdapterDef);
//handlerAdapter加入到ioc容器
readerContext.getRegistry().registerBeanDefinition(HANDLER_ADAPTER_BEAN_NAME, handlerAdapterDef);
return null;
}
//具体添加jackson转化对象方法
protected void addRequestBodyAdvice(RootBeanDefinition beanDef) {
if (jackson2Present) {
beanDef.getPropertyValues().add("requestBodyAdvice",
new RootBeanDefinition(JsonViewRequestBodyAdvice.class));
}
}
protected void addResponseBodyAdvice(RootBeanDefinition beanDef) {
if (jackson2Present) {
beanDef.getPropertyValues().add("responseBodyAdvice",
new RootBeanDefinition(JsonViewResponseBodyAdvice.class));
}
}
可以看到,这个处理类中,会创建 handlerMapping 和 handlerAdapter 存入 IoC 容器中,顺便把处理Json数据的类加入到 handlerMapping 和 handlerAdapter 中,上面的代码还能看出,它同时支持google.gson.Gson 和 fasterxml.jackson.databind 等 Json 解析器。
因此,我们启用了 @EnableWebMvc 相当于配置了 Json 解析器的同时,也帮我们把 handlerMapping 和 handlerAdapter 两个一起创建了,于是我们可以省略了手动创建 handlerMapping 和 handlerAdapter 的动作了:
@Configuration
@ComponentScan(basePackages = "cn.unsoft")
@EnableWebMvc // 启用了后,就无需手动创建下面两个处理器了
public class SpringMVCConfig {
@Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping(){
return new RequestMappingHandlerMapping();
}
@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter(){
return new RequestMappingHandlerAdapter();
}
}
通过上面的配置后,SpringMvc就可以处理Json数据了,然后就可以按以下的方法接收Json数据
@RequestBody 所接收的是请求中提交的Json数据。
{
"name":"张三",
"age":18
}
@PostMapping("/json")
public String json(@RequestBody Person person){
return null;
}
接收Cookie值
通过使用 @CookieValue("cookieName") 注解,能获取到请求的Cookie值,SpringMvc会自动把Cookie的值注入给对应的形参中。
@GetMapping("/cookie")
public String cookie(@CookieValue("cookieName") String value){
return null;
}
保存新的Cookie值
通过接收 HttpServletResponse 并向 response 中增加 Cookie 值对象即可。
@GetMapping("/cookie")
public String cookie(@CookieValue("cookieName") String value, HttpServletResponse response){
Cookie cookie = new Cookie("key","value");
response.addCookie(cookie);
return null;
}
获取请求头值
通过使用 @RequestHeader("name") 注解,能获取到请求头的值,并注入到形参变量中。
@GetMapping("/header")
public String header(@RequestHeader("key") String value){
return null;
}
其它获取请求原生api变量值
@Resource
private ServletContext servletContext;
@GetMapping("/other")
public String other(HttpServletRequest request,
HttpServletResponse response,
HttpSession session
){
ServletContext servletContext1 = request.getServletContext();
ServletContext servletContext2 = session.getServletContext();
return null;
}
其它生生参数接收如下表:
Controller method argument 控制器方法参数 | Description |
---|---|
jakarta.servlet.ServletRequest , jakarta.servlet.ServletResponse |
请求/响应对象 |
jakarta.servlet.http.HttpSession |
强制存在会话。因此,这样的参数永远不会为 null 。 |
java.io.InputStream , java.io.Reader |
用于访问由 Servlet API 公开的原始请求正文。 |
java.io.OutputStream , java.io.Writer |
用于访问由 Servlet API 公开的原始响应正文。 |
@PathVariable |
接收路径参数注解 |
@RequestParam |
用于访问 Servlet 请求参数,包括多部分文件。参数值将转换为声明的方法参数类型。 |
@RequestHeader |
用于访问请求标头。标头值将转换为声明的方法参数类型。 |
@CookieValue |
用于访问Cookie。Cookie 值将转换为声明的方法参数类型。 |
@RequestBody |
用于访问 HTTP 请求正文。正文内容通过使用 HttpMessageConverter 实现转换为声明的方法参数类型。 |
java.util.Map , org.springframework.ui.Model , org.springframework.ui.ModelMap |
共享域对象,并在视图呈现过程中向模板公开。 |
Errors , BindingResult |
验证和数据绑定中的错误信息获取对象! |
共享域传参数
什么是共享域?
在Controller到页面之间,Controller 需要传递 response 数据到视图中,而Controller 又无法直接把数据直接传递给视图。那么,在Controller 与视图之间,就存在着一个用于存放 response 数据的区域,Controller 把 response 数据先存到共享域中,然后跳转或转发到其它Controller 或视图后,视图或其它Controller 再从共享域中获取 response 数据,则该区域就是共享域。
常见的共享域有哪些?
Request: HttpServletRequest 是针对每一次请求的共享域,该共享域的数据有效期为一个请求。通常用于每一次请求时的数据传递。
Session: HttpSession 是针对一个浏览器一次会话的共享域,该共享域的数据有效期为一个浏览器与服务器一个会话,通常用于每一个账户登陆有效期的数据传递。
ServletContext: ServletContext 是针对整个系统项目中最大的共享域,该共享域的数据有效期为服务后台整个项目的启动周期。
Request 共享域的几种配置方法:
请求域中,用的最多的当属Request共享域了,因为每一次的请求都会使用到该共享域,因此,SpringMvc 封装了多种设置共享域数据的方法,方法如下:
1.Servlet 原生的设置共享域方法:使用 HttpServletRequest 设置
@GetMapping("/requestServlet")
public String requestServlet(HttpServletRequest request){
request.setAttribute("key","value");
return null;
}
2.SpringMvc 提供的设置共享域数据方法一:使用 Model 设置
@GetMapping("/requestModel")
public String requestServlet(Model model){
model.addAttribute("key","value");
return null;
}
3.SpringMvc 提供的设置共享域数据方法二:使用 ModelMap 设置
@GetMapping("/requestModelMap")
public String requestServlet(ModelMap modelMap){
modelMap.addAttribute("key","value");
return null;
}
4.SpringMvc 提供的设置共享域数据方法三:使用 Map 设置
@GetMapping("/requestMap")
public String requestServlet(Map map){
map.put("key","value");
return null;
}
5.SpringMvc 提供的设置共享域数据方法四:使用 ModelAndView 设置
@GetMapping("/modelAndView")
public ModelAndView modelAndView(){
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("key","value");
modelAndView.setViewName("index");
return modelAndView;
}
ModelAndView 对比上面几种方式有所不同的点:
ModelAndView 是用于 JSP 页面技术的共享域数据传递,因此需要设置JSP页面名称,会自动跳转。
ModelAndView 不能用于前后端分离项目,因为它不能只传递 Json 数据。
SpringMvc 响应数据
返回 jsp 页面
对于使用混合开发模式的SpringWeb项目,可以通过创建JSP页面进行渲染
1.使用混合开发,Jsp需要使用视图渲染器,除了 Themelaf 渲染器外,还有Java官方的Jsp视图渲染器,这些渲染器应该创建对象并存放到IoC容器中。
2.需要对Jsp页面的前缀和后缀进行设置,SpringMvc 为了方便用户对Jsp的设置,SpringMvc已经对 Jsp 渲染器做了默认的封装和创建,并提供了重写类 WebMvcConfigurer 类,对Jsp 的设置做了封装,我们不需要再重新 new 一个 Jsp 渲染器进行加入IoC和设置,只需要重写 WebMvcConfigurer 类中的配置方法即可。
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
// 配置 jsp 的路径前后缀
registry.jsp("/WEB-INF/views/",".jsp");
}
Controller 请求输出 jsp 页面:
@GetMapping("jsp")
public String jsp(){
System.out.println("jsp页面被访问");
return "index";
}
注意,Controller 中不需使用 @ResponseBody 注解
使用转发
@GetMapping("forward")
public String forward(){
System.out.println("jsp页面被转发");
return "forward:/index";
}
使用重定向
@GetMapping("redirect")
public String redirect(){
System.out.println("jsp页面被重定向");
return "forward:https://www.baidu.com";
}
注意:如果是项目下的资源,转发和重定向都一样都是项目下路径!都不需要添加项目根路径
返回静态资源
通过重写 WebMvcConfigurer 类中的 configureDefaultServletHandling 方法
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
概念:
1.DispatcherServlet 接到请求后会向 handlerMapping 查询是否有对应的请求方法,如果 handlerMapping 没有,则直接报404.
2.开启 DefaultServletHandlerConfigurer 即相当于在 DispatcherServlet 下加上一个 DefaultServletHandler ,当 DispatcherServlet 向 handlerMapping 得出404后,会接着查询 DefaultServletHandler ,而 DefaultServletHandler 的执行原理就是简单的 forward 转发到实际url地址。
原理(了解):
1.开启 DefaultServletHandler 相当于 XML 中配置 <mvc:default-servlet-handler> 标签,其应用在:
MvcNamespaceHandler
之中
this.registerBeanDefinitionParser("default-servlet-handler", new DefaultServletHandlerBeanDefinitionParser());
进去我们可以看到,其原理代码:
public BeanDefinition parse(Element element, ParserContext parserContext) {
.......
RootBeanDefinition defaultServletHandlerDef = new RootBeanDefinition(DefaultServletHttpRequestHandler.class);
.......
}
把DefaultServletHttpRequestHandler放到DispatcherServlet 下,其 DefaultServletHttpRequestHandler 的作用如下:
public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Assert.state(this.servletContext != null, "No ServletContext set");
RequestDispatcher rd = this.servletContext.getNamedDispatcher(this.defaultServletName);
if (rd == null) {
throw new IllegalStateException("A RequestDispatcher could not be located for the default servlet '" + this.defaultServletName + "'");
} else {
rd.forward(request, response);
}
}
只是简单的转发。
返回 json 数据
Controller 中使用 @ResponseBody 注解,即可让SpringMvc把返回的数据以Json 的形式传递转换。
概念:通过添加 @ResponseBody 注解后,handlerAdapter 会对返回的 java 对象转为 json 数据并返回给 DispatcherServlet 并返回给用户。
@GetMapping("json")
@ResponseBody
public Person json(){
Person person = new Person();
person.setName("TZMing");
person.setAge(18);
return person;
}
@ResponseBody 注解 放到类中,可以使得整个 Controller 的请求都以 Json 方式返回。
@RestController 注解可以让 Controller 的 @Controller 和 @ResponseBody 合并为一个注解使用
异常处理
SpringMvc 提供使用注解配置声明式异常处理功能。
1.使用注解 @ControllerAdvice 和 @RestControllerAdvice 注解标记用于配置全局异常处理的类。
2.@ControllerAdvice 是可使用于Jsp上的页面跳转的异常处理类
3.使用注解 @ExceptionHandler(class) 注解标记当抛出什么类型的异常时会被处理什么样的异常处理操作。
/**
* 全局异常处理类
*/
@RestControllerAdvice
public class GlobalExceptionConfig {
@ExceptionHandler(NullPointerException.class)
public String nullPointException(NullPointerException e){
System.out.println("e = " + e);
return "nullPointException";
}
}
拦截器 interceptor
拦截器与过滤器的区别
1.过滤器Filter是JavaWeb的Servlet 提供的官方方法,它的拦截时期位于用户发送请求到DispatcherServlet之间的区域,该区域并未进入SpringMvc区域,因此过滤器Filter不能使用IoC中的资源。
2.拦截器interceptor的执行时机是位于SpringMvc内部,所以其运行在IoC容器中,可以使用IoC容器中的对象资源。
3.拦截器interceptor是由SpringMvc提供的拦截方法,它的拦截时期位于三个地方,分别是
- handlerAdapter 到 handler之间(preHandler)
- handler 到 handlerAdapter 之间(postHandler)
- DispatherServlet 到 Servlet 之间(afterCompletion)
4.过滤器Filter与拦截器interceptor的作用都是一样的,就是为了拦推数据的来与回。
配置拦截器
SpringMvc提供了实现拦截器的标准接口 HandlerInterceptor 接口,我们需要实现该接口。
HandlerInterceptor 接口提供三个抽象方法,它们的拦截时机如下:
- handlerAdapter 到 handler之间(preHandler)
- handler 到 handlerAdapter 之间(postHandler)
- DispatherServlet 到 Servlet 之间(afterCompletion)
实现代码如下:
@Component
public class MyInterceptor implements HandlerInterceptor {
/**
*
* @param request 请求体对象
* @param response 响应体对象
* @param handler 执行handler方法的所在方法
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}
/**
*
* @param request 请求体对象
* @param response 响应体对象
* @param handler 执行handler方法的所在方法
* @param modelAndView 视图与共享域数据对象
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("request = " + request + ", response = " + response + ", handler = " + handler + ", modelAndView = " + modelAndView);
}
/**
*
* @param request 请求体对象
* @param response 响应体对象
* @param handler 执行handler方法的所在方法
* @param ex handler 抛出执常时的异常信息
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("request = " + request + ", response = " + response + ", handler = " + handler + ", ex = " + ex);
}
}
注意:
1.preHandle 需要返回 true 或者 false ,如果返回 true 则表示该请求通过拦截,否则为不通过拦截,该请求不会进入下一个环节,也就不会执行 postHandle 和 afterCompletion 了。
2.postHandle 和 afterCompletion 本身没有返回值,因为这两个处理方法已经处理请求处完完成的阶段,不能产生是否拦截的功能,但能处理一些响应数据。
加入拦截器到SpringMvc中
SpringMvc 中提供的 WebMvcConfigurer 中提供了把拦截器加入到SpringMvc中的方法
addInterceptors,通过重写该方法,并把我们的拦截器 new 出来加进去即可。
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor());
}
注意:节接增加拦截器不做任何配置的情况下,默认是对所有的请求都会被拦截处理。
拦截器参数设置
指定地址拦截
增加拦截器支持链式编程,从而实现具体配置。
addPathPatterns(): 支持匹配的指定地址精准拦截,* 表示一层的任意方法被拦截,** 表示下面多层的任意方法都被拦截。
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/user/*");
}
排除拦截
对某些匹配的地址做排除拦截,* 表示一层的任意方法不会被拦截,** 表示下面多层的任意方法都不会被拦截。
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor())
.addPathPatterns("/user/*")
.excludePathPatterns("/user/xxx");
}
注意:排除拦截的前提必须是前面增加拦截规则的范围内。
拦截器的执行顺序
遵循先来后出规律,选增加的拦截器先拦截,出口时,先增加的拦截器后拦截。
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor());
registry.addInterceptor(new MyInterceptor1());
}
preHandler: MyInterceptor , MyInterceptor1
postHandle: MyInterceptor1, MyInterceptor
afterCompletion: MyInterceptor1, MyInterceptor
参数校验
当我们使用pojo实体类进行接收参数数据时,未免会让前端发送一些不符合我们预期的数据类型,针对这种数据类型的约束,JSR-303 提供了一种参数校验的规范,该规范起初由 hibernate 实现,但SpringMvc 也支持hibernate实现的规范。
配置 @EnableWebMvc后,SpringMVC 会默认装配好一个 LocalValidatorFactoryBean,通过在处理方法的入参上标注 @Validated 注解即可让 SpringMVC 在完成数据绑定后执行数据校验的工作。
1.要使用参数校验,我们需要导入 JSR-303 规范坐标,与 hibernate 坐标。
<!-- 校验注解 -->
<dependency>
<groupId>jakarta.platform</groupId>
<artifactId>jakarta.jakartaee-web-api</artifactId>
<version>9.1.0</version>
<scope>provided</scope>
</dependency>
<!-- 校验注解实现-->
<!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>8.0.0.Final</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator-annotation-processor -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator-annotation-processor</artifactId>
<version>8.0.0.Final</version>
</dependency>
2.在实体类中的成员变量加上对应不同的注解,来对该成员变量实现不同类型的数据类型约束。
详细的约束注解如下:
注解 | 规则 |
---|---|
@Null | 标注值必须为 null |
@NotNull | 标注值不可为 null |
@AssertTrue | 标注值必须为 true |
@AssertFalse | 标注值必须为 false |
@Min(value) | 标注值必须大于或等于 value |
@Max(value) | 标注值必须小于或等于 value |
@DecimalMin(value) | 标注值必须大于或等于 value |
@DecimalMax(value) | 标注值必须小于或等于 value |
@Size(max,min) | 标注值大小必须在 max 和 min 限定的范围内 |
@Digits(integer,fratction) | 标注值值必须是一个数字,且必须在可接受的范围内 |
@Past | 标注值只能用于日期型,且必须是过去的日期 |
@Future | 标注值只能用于日期型,且必须是将来的日期 |
@Pattern(value) | 标注值必须符合指定的正则表达式 |
以上是JSR-303规范的类型约束注解,hibernate 除了实现以上的规范外,还另外新增了一些类型约束注解,如下:
注解 | 规则 |
---|---|
标注值必须是格式正确的 Email 地址 | |
@Length | 标注值字符串大小必须在指定的范围内 |
@NotEmpty | 标注值字符串不能是空字符串 |
@Range | 标注值必须在指定的范围内 |
3.接下来在实体类中对不同的成员变量加上约束注册,案例如下:
public class User {
// username 必须不能为空
@NotBlank
private String username;
// 必须最小从10开始
@Min(10)
private int age;
// 其长度必须大于3位,且小于10位
@Length(min = 3,max = 10)
private String name;
//email 必须邮箱格式
@Email
private String email;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
注意:
@NotNull、@NotEmpty、@NotBlank 都是用于在数据校验中检查字段值是否为空的注解,但是它们的用法和校验规则有所不同。
1. @NotNull (包装类型不为null),用于判断对象是否为空时使用
@NotNull 注解是 JSR 303 规范中定义的注解,当被标注的字段值为 null 时,会认为校验失败而抛出异常。该注解不能用于字符串类型的校验,若要对字符串进行校验,应该使用 @NotBlank 或 @NotEmpty 注解。
2. @NotEmpty (集合类型长度大于0)
@NotEmpty 注解同样是 JSR 303 规范中定义的注解,对于 CharSequence、Collection、Map 或者数组对象类型的属性进行校验,校验时会检查该属性是否为 Null 或者 size()==0,如果是的话就会校验失败。但是对于其他类型的属性,该注解无效。需要注意的是只校验空格前后的字符串,如果该字符串中间只有空格,不会被认为是空字符串,校验不会失败。用于判断集合是否没有成员时使用
3. @NotBlank (字符串,不为null,切不为" "字符串)。用于判断字符串是否为空时使用
@NotBlank 注解是 Hibernate Validator 附加的注解,对于字符串类型的属性进行校验,校验时会检查该属性是否为 Null 或 “” 或者只包含空格,如果是的话就会校验失败。需要注意的是,@NotBlank 注解只能用于字符串类型的校验。
总之,这三种注解都是用于校验字段值是否为空的注解,但是其校验规则和用法有所不同。在进行数据校验时,需要根据具体情况选择合适的注解进行校验。
4.在Controller 中接收该实体类时,使用 @Validated 注解开启对该实体类的参数校验功能
对出现参数不符合校验要求的,可以在实体类对象旁边增加一个 BindingResult 对象(该对象必须紧挨着校验实体类对象),该对象可以判断接收的参数中是否存在不合规的情况,用户可以自行操作异常抛出。
@PostMapping("/person")
public Person person(@Validated @RequestBody Person person, BindingResult result) throws Exception {
if (result.hasErrors()){
throw new Exception("参数字段不符合要求");
}
return person;
}
共有 0 条评论