一篇搞定SpringMVC

111 阅读12分钟

SpringMVC

  • 基于 Servlet API 构建的原始Web控制层框架

    • 简化参数的接收和响应
  • 在没有SpringMVC框架时,用户请求直达Controller,由我们自己处理参数,并处理数据格式响应

  • 有了SpringMVC不再直接访问Controller,访问流程如下SpringMVC内部工作体系

    • 所有请求先到达DispatcherServlet(CEO)
    • DispatcherServlet 在 HanlerMapping(缓存handler方法和地址,秘书) 查找是否存在对应handler(Controller里的每个方法,打工人)
    • 接着将请求发送给 HandlerAdaptor(简化请求参数和响应结果(例如对象转JSON),经理) ,HandlerAdaptor处理后调用对应的 Handler 处理,并获取结果处理后返回给DispatcherServlet
    • 最后若是不响应JSON 要响应视图页面DispatcherServlet就去找 ViewResolver(视图解析器,简化视图查找,财务) ,然后响应给 CEO。 (前后端分离项目,后端只返回JSON数据,无需视图解析器)
    • 最后 CEO 将结果响应给用户

截屏2024-01-22 18.38.31.png

一、入门案例

记住

  • @ResponseBody@RequestBody@RestController 的区别

0、下载插件:jblJavatoWeb,创建maven-web app项目

1、引入依赖

  • spring-webmvc(包含spirng的core、aoc、aop、context)
  • Jarkata.servlet-api(用于DispatcherServlet)
  • Tomcat10.0.26
<dependency>
     <groupId>org.springframework</groupId>
     <artifactId>spring-webmvc</artifactId>
     <version>6.0.9</version>
</dependency>
<dependency>
   <groupId>jakarta.servlet</groupId>
   <artifactId>jakarta.servlet-api</artifactId>
   <version>5.0.0</version>
</dependency>
<dependency>

2、创建配置类

package com.elias.config;
/**
* 1、配置IOC容器,将handlerMapping(秘书)、handlerAdapter(经理) 加入到IOC容器
*/@Configuration
@ComponentScan("com.elias")//开启注解声明bean
public class MvcConfig {
   //添加秘书、经理bean到IOC容器
   @Bean
   public RequestMappingHandlerMapping handlerMapping(){
       return new RequestMappingHandlerMapping();
  }
​
   @Bean
   public RequestMappingHandlerAdapter handlerAdapter(){
       return new RequestMappingHandlerAdapter();
  }
}
​

3、创建SpringMVC环境搭建类,创建SpringMvcInit类并继承AbstractAnnotationConfigDispatcherServletInitializer接口

  • 该接口也是层层继承,其实是实现了一个WebApplicationInitializer接口
  • 重写了它的onStartUP()方法:每当web项目启动,就会自动调用该方法
  • 我们就可以在里面初始化我们的IOC容器并配置servlet
package com.elias.config;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
​
//该类的作用:继承接口后
// 可以被web项目自动加载,初始化IOC容器
// 并帮我们设置dispatcherServlet的地址("/"),所有的请求都会到达CEO了
public class SpringMvcInit extends AbstractAnnotationConfigDispatcherServletInitializer {

//service、mapper层的ioc容器配置(后期会用到)
@Override
   protected Class<?>[] getRootConfigClasses() {
       return new Class[0];
  }
​
   //设置我们项目的配置类(IOC容器配置):springmvc、controller
   @Override
   protected Class<?>[] getServletConfigClasses() {
       return new Class[]{SpringMvcInit.class};
  }
​
//配置SpringMVC内部自带servlet(DS)的访问地址
   @Override
   protected String[] getServletMappings() {
       return new String[]{"/"};
  }
}

4、定义Controller 和 Handler(打工人)

package com.elias;
​
@Controller//添加到IOC容器
@RequestMapping("/test/hello") //作用:注册地址,将Handler注册到HandlerMapping
@ResponseBody //设置直接响应JSON字符串,不要找视图解析器
public class TestController {
   
   //定义Handler
   public String hello(){
       System.out.println("Hello");
​
       //返回给前端
       return "hello Spring";
  }
}
​

二、SpringMVC接收数据

1、路径注解设置

**
* @RequestMapping:用于将Handler注册到HanlderMapping
* @WebSerlet("/test/hello):必须以 / 开头
* @RequestMapping("test/hello")、("/test/hello"):都可以
* @RequestMapping可放在类上作为公共路径,放在方法上就是Handler私有路径,访问Handler地址即公共+私有
*      1、精准地址:可以为Handler设置多个访问地址,用{}包起来
*          例如:RequestMapping({"/test/hello1","/test/hello2"})
*      2、模糊匹配:* 表示匹配 任意一层字符串,** 表示匹配 任意层任意字符串
*          /test/* : /test/aa、/test/aaaa(ok);/test/aa/aa(not ok)
*         /test/** :/test/aa、/test/aa/aa、/test/a/a/a/a(都ok)
*     3、请求方式指定:默认情况,任意方式都可以请求
*         指定请求方式:@RequestMapping("test/hello",method = RequestMethod.GET)
*         不符合请求方式:405
*     4、注解进阶
*         @GetMapping、@DeleteMapping、@UpdateMapping、@PutMapping,只能使用在方法上
*         =@RequestMapping(xxx,method=RequestMethod.xxxMapping)
*/

2、Param参数和Json参数

  • Get方法传参数用Param
  • Post方法穿参数用Json

3、Param参数接收

1)行参名和类型 与 传递参数相同,自动接收

  • 可以不传递、不报错
//直接接收:行参名和类型 与 传递参数相同,自动接收
//可以不传递、不报错
@GetMapping("/1")
public void test1(int age,String name){
   System.out.println(age+name);
}

2)@RequestParam

  • 使用场景

    • 接收指定参数
    • 要求该参数必须传递
    • 为请求参数提供默认值
  • 基本用法

    • 接收指定参数,必须穿参数
    • 非必需传值,要设置默认值
//接收指定参数,必须穿参数
//非必需传值,要设置默认值
@GetMapping("/2")
//http://localhost:8080/test?age=10
public void test2(@RequestParam(value = "name") String myName,
                 @RequestParam(required = false,defaultValue = "10") int age){
   System.out.println(myName+age);
}

3)集合参数

请求:...?hobby=1&hobby=2&hobby=3

//接收集合参数:加了注解,经理才会用add方法将值加入集合
//...?hb=1&hb=2
@GetMapping("/3")
public void test3(@RequestParam List<String> hb){
   System.out.println(hb);
}

4)实体类参数

  • 直接用实体类接收即可,不强求传
//实体对象接收值,例如用户注册的信息
@GetMapping("/3")
//...?name=1&age=2
public void test3(User user){
   System.out.println(user);
}

4、路径参数

  • 必须使用 @PathVarible

  • 请求:/test/{age}

    • 要接受的路径参数要用{}包裹起来
//接收路径参数
@GetMapping("/5/{age}/{name}")
//...?name=1&age=2
public void test4(@PathVariable(value="age") int myAge
                 @PathVariable String name){
   System.out.println(age);
}

5、Json参数

  • 接收json格式参数,用post请求方式,用java对象接收属性名与json的key值一致
@PostMapping
public User test1(@RequestBody User user){
   System.out.println(user);
   return user;
}

1)报错:前端报错415,不支持数据类型

2)原因:Java原生的api 不支持json格式(前端的格式)、而且没有json类型处理的工具(jakson)

3)解决:1、导入json处理的依赖 2、给handlerAdaptor配置Json转化器

  • 导入json处理的依赖:jackson-databind
<dependency>
   <groupId>com.fasterxml.jackson.core</groupId>
   <artifactId>jackson-databind</artifactId>
   <version>2.15.3</version>
</dependency>
  • 给handlerAdaptor配置Json转化器:在MVCconfig配置文件加上注解@EnableWebMvc
package com.elias.config;
​
@Configuration
@ComponentScan("com.elias")
@EnableWebMvc//作用:添加秘书、添加经理(下面的两个bean就不用写了)、给经理添加jackson json处理器
public class MvcConfig {
   //@Bean
   //public RequestMappingHandlerMapping handlerMapping(){
   //   return new RequestMappingHandlerMapping();
   //}
   //@Bean
   //public RequestMappingHandlerAdapter handlerAdapter(){
   //   return new RequestMappingHandlerAdapter();
   //}
}

6、获取Cookie和请求头

//cookie的存储
@GetMapping("save")
public String save(HttpServletResponse response){
   Cookie cookie=new Cookie("cookieName","root");
   response.addCookie(cookie);
   return "ok";
}
​
//接收cookie
@RequestMapping("data")
public String cookie(@CookieValue(value = "cookieName") String value){
   System.out.println("value:"+value);
   return "ok";
}
​
//获取请求头:@RequestHeader("header名"),要获取什么header用对应的name
@RequestMapping("header")
public String header(@RequestHeader("Host") String host){
   System.out.println(host);
   return "ok";
}

7、获取原生对象

package com.elias;
​
@RestController
@RequestMapping("/test")
public class TestController {
​
   //注入ServletContext对象
   @Autowired
   private ServletContext servletContext;
​
   //获取原生对象可以直接在Handler里声明参数即可,然后直接食用即可
   @GetMapping
   public void data(HttpServletResponse response,
                    HttpServletRequest request,
                    HttpSession session){
​
       //ServletContext [1、最大的配置文件 2、全剧最大共享域 3、核心api getRealPath()]
       //获取方式1:request获取 or session获取
       ServletContext servletContext1 = request.getServletContext();
       ServletContext servletContext2 = session.getServletContext();
       //获取方式2:ServletContext 会自动装入IOC容器,直接注入即可
       servletContext1.setAttribute("name","zhj");
       
  }
}

8、共享域对象操作

  • 共享域:方便同一web'应用程序的多个组件之间传递数据

  • 三大共享域

    • request:一次请求或者转发
    • session:一次会话期间(一个浏览器的多次请求)
    • servletContext:整个项目

使用

//获取原生对象可以直接在Handler里声明参数即可,然后直接食用即可
@GetMapping
public void data(HttpServletResponse response,
                HttpServletRequest request,
                HttpSession session){
   //三大共享域:request、session、servletContext
   // 都用:setAttribute()方法存数据,getAttribute()获取数据
   ServletContext servletContext = request.getServletContext();
   servletContext.setAttribute("name","zhj");
   String name = (String) servletContext.getAttribute("name");
}

三、SpringMVC响应数据

1、开发模式介绍

  • 混合开发、前后端分离开发

截屏2024-01-22 18.39.36.png

2、响应视图、转发、重定向

  • 前后端分离一般不涉及

3、响应Json数据

@ResponseBody @RestController

@RestController//@Controller+@ResponseBody(返回json的注解,可以放到类上或方法上)
@RequestMapping("/json")
public class TestController {
​
   @GetMapping
   //返回类型是什么,HandlerAdaptor(经理)都会帮我们转成json格式
   //@ResponseBody:数据直接走响应体返回,不走视图解析器(快速查找视图,转发和重定向都不生效了)
   public User getUser(){
       User user=new User();
       user.setName("zhj");
       return user;
  }
}

4、静态资源处理

4.1、WebMvcConfigure 接口

  • 该接口有一系列方法 用于简化配置 组件(不用写@Bean,重写方法即可)

4.2、静态资源访问

webapp下的images静态文件里的图片 访问不到(访问路径当作寻找handler了)

解决方案

在mvc配置类文件里重写方法方法 configureDefaultServletHandling()

package com.elias.config;
​
@Configuration
@ComponentScan("com.elias")
@EnableWebMvc//作用:添加秘书、添加经理(下面的两个bean就不用写了)、给经理添加jackson json处理器public class MvcConfig implements WebMvcConfigurer {
   //开启静态资源查找<mvc:default-servlet-handler/>,底层做了个转发
   //dispatcherServlet -> handlerMapping找有没有对应的handler -> 没有 -> 找有没有静态资源
   @Override
   public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
     //调用参数的enable()方法开启静态资源查找(给老板配了个二秘书)
       configurer.enable();
  }
}

四、RESTFUL风格设计和实战

1、Restful开发风格介绍

  • HTTP协议的标准使用方案

    • 设计路径
    • 设计参数传递
    • 设计请求方式

截屏2024-01-22 18.40.25.png

2、接口设计

功能接口和请求方式请求参数返回值
分页查询GET /usersPage1&size=10 param{响应数据}
用户添加POST /users{user数据}{响应数据}
单个用户详情GET /users/1路径参数{响应数据}
用户更新PUT /users{user更新数据}{响应数据}
用户删除DELETE /users/1路径参数{响应数据}
条件模糊查询GET /users/searchPage1&size=10&keyword=关键字 param{响应数据}
  • 路径参数:用于指定资源的唯一标识或者ID

  • 请求参数:用于指定查询条件或操作参数

  • 敏感信息:用POST和请求体来传递(登陆、注册的账号密码)

    • GET、DELETE无请求体

      • 用路径传参数(参数是id,用路径)
      • param传参数(参数不是id,是范围参数,用param)
    • POST、PUT有请求体

      • 路径、param
      • 请求体(json) ,一般都用这个

五、SpringMVC其他扩展

1、全局异常处理

1.1异常处理的两种方式
  • 编程式:在代码内部对异常编写处理逻辑
  • 声明式:将异常处理逻辑放到外部编写,通过配置统一管理
1.2、全局异常处理机制
  • error.GlobalExceptionHandler
package com.elias.erroe;
​
//全局异常处理器,异常发生就会走此类的handler
@ControllerAdvice//可以返回逻辑视图,转发和重定向的
@RestControllerAdvice//@ResponseBody 直接返回JSON字符串
public class GlobalExceptionHandler {
​
   //@@ExceptionHandler(要处理的异常的字节码对象),可以直接用Exception.class接受所有异常
   //指定的异常未找到,会查找其父异常
   @ExceptionHandler(ArithmeticException.class)
   //参数是捕获的异常
   public String arithmeticExceptionHandler(ArithmeticException e){
       //自定义处理异常即可
       return e.getMessage();
  }
   @ExceptionHandler(Exception.class)
   public String exceptionHandler(Exception e){
       //自定义处理异常即可
       return e.getMessage();
  }
}

2、拦截器

2.1、拦截器概念
  • 对请求进行拦截,统一处理后选择放行或拦截的一种组件

  • 执行handler之前!调用的拦截方法!

    • 编码格式设置
    • 登录保护
    • 权限设置

拦截器

  • 由SpringMVC提供,拦截SpringMVC负责的请求,就在IOC容器中,只需代码装配一下

过滤器

  • 工作在 Servlet 容器中,拦截的最大范围是 整个Web应用,IOC容器不支持,需调用专门的工具方法

二者作用范围不同

  • Servlet(DispatcherServlet)

截屏2024-01-22 18.41.44.png

选择

  • 功能需要如果MVC能实现,就不用过滤器
2.2、拦截器使用

1)创建interceptor.MyInterceptor 拦截器类,实现 HandlerInterceptor 接口

2)根据需求重写三个拦截方法

package com.elias.interceptor;
​
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
​
public class MyInterceptor implements HandlerInterceptor {
​
   /**
    * 拦截方法
    * @param request 请求对象
    * @param response 响应对象
    * @param handler 我们要调用的方法对象
    * @return true:放行 false:拦截住
    * @throws Exception
    */
   @Override
   public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
       System.out.println("拦截器preHandle()方法");
       return true;//放行
  }
​
   /**
    * handler执行完毕后调用的方法,无拦截机制,只有PreHandler return true才执行
    * @param @param request 请求对象
    * @param response 响应对象
    * @param handler 我们要调用的方法对象
    * @param modelAndView 返回的视图和共享域数据对象
    * @throws Exception
    
    对结果处理,一般用于敏感词汇检查
    */
   @Override
   public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
       System.out.println("拦截器postHandle()方法");
  }
​
   /**
    * 整体执行完后调用的方法,无拦截机制
    * @param @param request 请求对象
    * @param response 响应对象
    * @param handler 我们要调用的方法对象
    * @param ex handler报错,异常的对象
    * @throws Exception
    */
   @Override
   public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throwsException {
       System.out.println("拦截器afterCompletion()方法");
  }
}
​
  • 三个方法的执行位置

截屏2024-01-22 18.42.42.png

3)修改MVC配置类,添加拦截器

package com.elias.config;
​
@Configuration
@ComponentScan("com.elias")
@EnableWebMvc//作用:添加秘书、添加经理(下面的两个bean就不用写了)、给经理添加jackson json处理器
public class MvcConfig implements WebMvcConfigurer {
​
   //在IOC容器注册拦截器
   @Override
   public void addInterceptors(InterceptorRegistry registry) {
       //配置一、默认拦截所有请求
       registry.addInterceptor(new MyInterceptor());
       //配置二、设置拦截指定地址请求
       //支持模糊匹配 * 任意一层、** 任意多层
       registry.addInterceptor(new MyInterceptor())
              .addPathPatterns("/user/*");
       
       //配置三、排除拦截,排除的地址必须在拦截地址内部
       registry.addInterceptor(new MyInterceptor())
              .addPathPatterns("/user/*")
              .excludePathPatterns("/user/a");
       
       //多个拦截器,按添加先后顺序拦截(pre以次,post、after反向以次)
       registry.addInterceptor(new MyInterceptor1());
       registry.addInterceptor(new MyInterceptor2());
  }
}
​

3、参数校验注解jsr303

  • java提供注解,hibernate实现,但springmvc支持这套实现
  • Java为Bean数据合法性校验提供的标准框架,通过注解,标准的验证接口进行验证

1)注解

2)使用步骤

  • 引入依赖
<dependency>
     <groupId>org.hibernate.validator</groupId>
     <artifactId>hibernate-validator</artifactId>
     <version>8.0.0.Final</version>
</dependency>
<dependency>
 <groupId>org.hibernate.validator</groupId>
 <artifactId>hibernate-validator-annotation-processor</artifactId>
 <version>8.0.0.Final</version>
</dependency>

截屏2024-01-22 18.42.59.png

  • 在实体类添加限制注解
package com.elias;
​
@Data
public class User {
   @NotBlank
   private String name;
   @Min(1)
   private int age;
   @Length(min=6)
   private String password;
   @Past
   private Date birthday;
   @Email
   private String email;
}
  • 在handler方法中添加处理

    • @Validated
    • BindingResult result
package com.elias;
​
@RestController//@Controller+@ResponseBody(返回json的注解,可以放到类上或方法上)
@RequestMapping("/user")
public class TestController {
​
   //接受用户数据,校验
   /*
       1、实体类添加校验注解
       2、handler(@Validated 实体类 对象)
       3、细节: param|json 校验注解都有效果
               json参数 - 加上@RequestBody即可
       如果,不符合校验规则,直接会向前端跑出异常
       接收错误绑定!自定义返回结果
       捕获错误绑定错误信息:
           1、handler(校验对象,BindingResult result) 要求:bindingResult必须紧挨 校验对象
           2、bindingresult获取绑定错误
    */
​
   @PostMapping("/test")
   public Object test(@Validated @RequestBody User user, BindingResult result){
       //判断是否有绑定错误,不进行直接返回,自己决定
       if(result.hasErrors()){
           Map data=new HashMap();
           data.put("code",400);
           data.put("msg","参数校验异常,请检查");
           return data;
      }
       return user;
  }
}
​