目录
Spring MVC 简介和体验
Spring MVC原理简单解析
Spring MVC涉及的组件
Spring MVC 快速体验
Spring MVC 接收数据
访问路径设置
接收参数(重点)
param和json参数比较
param参数接收
路径参数接收
json参数接收
@EnableWebMvc注解
接收Cookie和请求头数据
原生Api对象和共享域对象操作
Spring MVC 响应数据
handler方法分析
页面跳转控制
快速返回模板视图
转发和重定向
返回JSON数据(重点)
返回静态资源处理
RestFul风格设计
RESTFul风格特点
Spring MVC 其他拓展
全局异常处理机制
异常处理的两种方式
基于注解异常声明异常处理
拦截器使用
拦截器概念
拦截器使用
参数校验
- SSM为SpringFramework + SpringMVC + MyBatis
- Spring的3大特性:IoC(控制反转),DI(依赖注入),AOP(面向切面编程)。
- 框架 = jar + 配置文件
Spring MVC 简介和体验
Spring Web MVC 是一个基于Servlet API 构建的原始Web框架,一开始包含在Spring Framework下,目前普遍被选为JavaEE项目表述层开发的首选。
Spring MVC框架的两个核心功能:1.简化前端参数接收(形参列表)
2.简化后端数据响应(返回值)
Spring MVC原理简单解析
Spring MVC 框架内主要工作的有:
- DispatcherServlet:处理所有请求,用户的所有请求都由它接收
- HandlerMapping:缓存handler方法和地址,根据地址(如:/user/login)查找项目中的方法(如:UserController中的login方法)
- HandlerAdapter:适配器,真正进行参数和响应简化
- 视图解析器:查找视图页面,如果要返回的页面如:/WEB-INF/html/index.html,只需要返回“index”,它可以帮我们添加前缀和后缀,并查找页面信息并返回。
执行流程:
1.用户发送请求
2.请求到达DispatcherServlet
3.DispatcherServlet根据请求地址到HandlerMapping查找方法
4.HandlerMapping查找到handler方法并返回方法信息到DispatcherServlet。
5.DispatcherServlet根据handler方法信息将参数信息和方法信息发送到HandlerAdapter
6.HandlerAdapter进行简化参数处理,并调用handler方法
7.handler方法执行并返回数据给HandlerAdapter
8.HandlerAdapter接收数据并发送给DispatcherServlet
9.DispatcherServlet接收数据,如果接收的是页面地址则进行 操作10 ,不是则直接将数据返回给用户
10.视图解析器为页面添加前后缀并查找页面信息后返回给DispatcherServlet,DispatcherServlet将页面信息返回给用户。
Spring MVC涉及的组件
1.DispatcherServlet:Spring MVC提供,需要配置web.xml(Web项目配置文件)才能生效。
2.HandlerMapping:Spring MVC提供,需要进行IOC配置,将它加入到IOC容器才能生效,它内部缓存handler(controller的方法)和handler访问路径数据。
3.HandlerAdapter:Spring MVC提供,需要进行IOC配置,将它加入到IOC容器才能生效,它可以处理请求参数和处理响应数据。
4.Handler:handler又叫处理器,它是controller层方法的简称,由我们自己定义,接收参数,调用业务方法,返回数据。
5.ViewResolver(视图解析器):Spring MVC提供,需要进行IOC配置,将它加入到IOC容器才能生效,简化视图页面查找,前后端分离后,后端只需要返回JSON数据,不再需要视图解析器。
Spring MVC 快速体验
场景:项目启动后,用户访问/springmvc/hello服务器响应“hello springmvc!!”
1.创建项目ssm-springmvc-quick并转为web项目
2.导入项目所需的依赖:
- ioc:spring-context
- webmvc:spring-webmvc
- servlet:jakarta.jakartaee-web-api
3.创建一个controller类:HelloController
@Controller public class HelloController { @RequestMapping("springmvc/hello")//用户访问地址 @ResponseBody//直接返回数据,不需要经过视图解析器 public String hello(){ System.out.println("Hello , Spring MVC!"); return "hello springmvc"; } }4.创建spring配置类
//将controller配置到ioc容器 //将handlerMapping handlerAdapter加入到ioc容器 @Configuration @ComponentScan("com.qiu.controller") public class MvcConfig { @Bean public RequestMappingHandlerMapping handlerMapping(){ return new RequestMappingHandlerMapping(); } @Bean public RequestMappingHandlerAdapter handlerAdapter(){ return new RequestMappingHandlerAdapter(); } }5.初始化ioc容器,设置dispatcherServlet访问路径(就是想要访问项目应该有的地址前缀)
以前我们需要在web.xml下操作:
<servlet> <servlet-name>ds</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>ds</servlet-name> <url-pattern>/</url-pattern><!--访问路径--> </servlet-mapping>但现在可以创建一个用来初始化web项目的类,让他继承
AbstractAnnotationConfigDispatcherServletInitializer,项目启动后会自动扫描他,该类的作用是扫描Spring配置类来初始化ioc容器和设置DispatcherServlet的访问路径
//继承AbstractAnnotationConfigDispatcherServletInitializer类 //用来替换web.xml,会被web项目自动加载,会初始化ioc容器,会设置dispatcherServlet的访问地址 public class SpringMvcInit extends AbstractAnnotationConfigDispatcherServletInitializer { //service mapper 层的ioc容器 @Override protected Class<?>[] getRootConfigClasses() { return new Class[0]; } //设置spring配置类 springmvc controller的ioc容器 @Override protected Class<?>[] getServletConfigClasses() { return new Class[]{MvcConfig.class}; } //设置springmvc自带servlet的访问地址 @Override protected String[] getServletMappings() { return new String[]{"/"}; } }6.部署项目
7.启动项目并访问项目localhost:8080/springmvc/hello
Spring MVC 接收数据
访问路径设置
之前:@WebServlet("必须以 / 开头") /user/login
现在:@RequestMapping(不必须以 / 开头) /user/login user/login /user/login/
1.精准地址 :[一个 | 多个] /user/login | {"/user/login","/user/login2"}
2.模糊地址:* 一层模糊 ** 任意层模糊
/user/* -> /user/a,/user/aaa 可以;/user/a/b 不可以。
/user/** -> /user/a,/user/a/b,/user/a/a/a/a
3.该注解可以使用在类上或方法上,最终访问路径为:类地址 + 方法地址
4.请求方式指定:
请求方式主要有:GET,POST,PUT,DELETE
方式1:@RequestMapping("login",method=RequestMethod.GET)
方式2:@GetMapping("login") ,该注解只能用在方法上
不指定时默认所有请求方法都可以
多个请求方法@RequestMapping("a",method={RequestMethod.GET,RequestMethod.POST})
如果有类注解,方法注解内可以不写路径,该方法的访问路径默认为类注解的路径
接收参数(重点)
param和json参数比较
1.参数编码
param参数会被编译为ASCII码,如name=john doe,会被编译为name=john%20doe;而JSON参数会被编译为UTF-8。
2.参数顺序
param参数没有顺序限制,但JSON参数有顺序限制。
3.数据类型
param类型仅支持字符串类型、数值类型和布尔类型;JSON参数则支持更复杂的类型:数组,对象等。
4.嵌套性
param参数不支持嵌套,JSON参数支持
param参数接收
1.直接接收
如果形参数名和传递参数名相同,即可自动接收。
2.@RequestParam注解
可以使用该注解将Servlet请求参数绑定到controller中的方法的参数上。
使用场景: 指定绑定的请求参数名,要求参数必须传递,为请求参数提供默认值。
演示
@Controller @RequestMapping("user") public class UserController { /** * 直接接收 * 请求地址:/user/data1?name=qiu&age=18 * 1.方法参数名和请求参数名一致,2.请求参数可以不传时 */ @ResponseBody @RequestMapping("data1") public String test(String name, int age){ System.out.println("name = " + name); System.out.println("age = " + age); return "name="+name+" age="+age; } /** * 注解传参 * 指定任意的请求参数名,默认要求必须传递, * 如果设置为不必须传递则要设置默认。 * /user/data2?account=qiu&age=18 */ @ResponseBody @RequestMapping("data2") public String test2(@RequestParam("account") String name, @RequestParam(required = false,defaultValue = "1") int age){ System.out.println("name = " + name + ", age = " + age); return "name=" + name +"&age="+age; } /** * 特殊值 * 一个属性传多个值,直接使用集合接收 * 必须使用@RequestParam,不然可能会把第一个ids当作集合ids直接传值,类型不同会报错 * /user/data3?ids=1&ids=2&ids=3 */ @ResponseBody @RequestMapping("data3") public String test3(@RequestParam("ids")List<String> ids){ System.out.println("ids = " + ids); return ids.toString(); } /** * 使用实体类接收值 * 创建User类 类属性有name 和 age,必须要有set/get方法 * 属性名必须等于参数名 * /user/data4?name=qiu&age=18 */ @ResponseBody @RequestMapping("data4") public String test4(User user){ System.out.println("user = " + user); return user.toString(); } }测试
路径参数接收
像/user/{name}/18,如果我们想接收name的值,则可以使用路径参数接收
步骤:1、设置动态路径
2、接收动态路径参数
// /user/data5/qiu/123 @ResponseBody @RequestMapping("data5/{name}/{password}")//设置动态路径 //获取路径参数 public String test5(@PathVariable("name") String username,@PathVariable String password){ return username+password; }
json参数接收
前端传递JSON数据时,SpringMVC框架可以使用@RequestBody来将JSON数据转换为Java对象。
Java对象Person
@Data public class Person { private String name; private int age; private String gender; }
Controller类PersonController
@Controller @RequestMapping("json") public class PersonController { @RequestMapping("person") @ResponseBody public String test(@RequestBody Person person){ return person.toString(); } }
发送请求
结果会报415错误,因为java原生的api只能接收路径传参和param参数,不支持json参数
解决方法:1、导入json处理的依赖 2、为handlerAdapter配置json转化器
添加依赖
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.15.0</version> </dependency>
在spring配置类上添加@EnableWebMvc:为handlerAdapter配置了json转化器
这样请求就发送成功了
@EnableWebMvc注解
在spring配置类上添加该注解相当于在springxml配置文件添加了<mvc:annotation-driven/>
而<mvc:annotation-driven/>在底层会自动帮我们把json处理器添加到一个HandlerAdapter上,再把这个handlerAdapter和一个handlerMapping添加到ioc容器内。
因此当我们添加该注解后就不需要在spring配置类中手动添加HandlerAdapter和HandlerMapping了。
接收Cookie和请求头数据
在参数前添加@CookieValue和@RequestHeader就行
public String method(@CookieValue("cookie") String value, @RequestHeader("header")String value2){ ... }
原生Api对象和共享域对象操作
想要使用原生Api对象可以直接通过参数获得
@Autowired private ServletContext context; public String getApi(HttpServletRequest request, HttpServletResponse response, HttpSession session){ // 要获取ServletContext // ServletContext [1.最大的配置文件,2.全局最大的共享域,3.核心api getRealPath] // 方式1:通过request,session获取 ServletContext servletContext = request.getServletContext(); ServletContext servletContext1 = session.getServletContext(); // 方式2:声明一个全局变量,然后再上面添加一个@Autowire // (ServletContext在程序启动时会自动添加到ioc容器内) return ""; }
Spring MVC 响应数据
handler方法分析
handler方法其实就是我们自己创建的controller类下的各种方法
接收请求数据,我们通过handler方法的形参列表
返回数据响应,我们通过return关键字
页面跳转控制
快速返回模板视图
当使用前后端分离模式(当前主流)时,该功能不再被需要。
要把jsp等视图返回给用户,需要使用视图解析器。
因此要先把视图解析器加入ioc容器
@Configuration @ComponentScan("com.qiu.controller") @EnableWebMvc //WebMvcConfigurer 可以快速配置springmvc的组件 public class MvcConfig implements WebMvcConfigurer { // 将视图解析器加入到ioc容器内, // registry.jsp("/WEB-INF/views/",".jsp") 为handler返回的视图路径添加前后缀,其底层就是字符串拼接 @Override public void configureViewResolvers(ViewResolverRegistry registry) { registry.jsp("/WEB-INF/views/",".jsp"); } }
编写index.jsp文件
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <%-- ${data}意思是在request域中获取key=data的value,在request.setAttribute("data","hello jsp")--%> <font color="red">${data}</font> </body> </html>
编写handler方法
@Controller @RequestMapping("jsp") public class HelloController { @GetMapping("index") public String hello(HttpServletRequest request){ //在请求域中设置data:hello jsp,为index.jsp文件内的${data}赋值 request.setAttribute("data","hello jsp"); //此handler没有@ResponseBody, // 所以将把“index”与视图解析器设置的前后缀拼接并查找文件并返回给用户 return "index"; } }
这样用户就可以获取到index.jsp
转发和重定向
直接通过以下代码和注释学习
@GetMapping("index") public String hello(HttpServletRequest request){ //在请求域中设置data:hello jsp,为index.jsp文件内的${data}赋值 request.setAttribute("data","hello jsp"); //此handler没有@ResponseBody, // 所以将把“index”与视图解析器设置的前后缀拼接并查找文件并返回给用户 return "index"; } /** * 转发和重定向依然不能添加@ResponseBody * 要转发时,需要在返回的地址前添加 forward: * 要重定向时,需要在返回的地址前添加 redirect: */ @GetMapping("forward") public String forward(){ return "forward:/jsp/index"; } //原本重定向的地址如果是项目内需要包含根路径, // 即 http://localhost:8080/mvcpro/jsp/index 应该返回 redirect:/mvcpro/jsp/index // 但springmvc内部做了优化,不需要根路径 @GetMapping("redirect") public String redirect(){ return "redirect:/jsp/index"; } @GetMapping("baidu") public String baidu(){ return "redirect:http://www.baidu.com"; }
返回JSON数据(重点)
接收JSON和返回JSON都需要导入jackson-databind依赖
@ResponseBody:返回JSON的注解,添加到类或方法上,不走视图解析器直接把数据返回给前端。
当我们想要返回JSON数据给前端时,先导入依赖,把JSON解析器添加到ioc容器中(使用@EnableWebMvc),然后在handler方法上添加@ResponseBody注解,最后直接把pojo类返回就行了,springmvc会自动把该类转换为JSON数据再发送给前端。
pojo类
@Data @AllArgsConstructor @NoArgsConstructor public class User { private String name; private String gender; }
handler方法
@Controller @ResponseBody @RequestMapping("json") public class JsonController { @GetMapping("user") public User json(){ return new User("秋","男"); } }
返回静态资源处理
如果项目结构像这样,
我们无法通过 …/images/OIP-C.jpg访问静态资源。(jsp属于动态资源)
原因:DispatcherServlet会接收所有请求,包括对静态资源的请求,并且他只会在HandlerMapping中根据路径查找对应的handler方法,而静态资源没有对应的handler方法。
解决办法:需要我们开启静态资源查找:
还是在 WebMvcConfigurer 接口下,有个 configureDefaultServletHandling 方法
//相当与开启了<mvc:default-servlet-handler/> @Override public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { configurer.enable();//开启 }
开启后dispatcherServlet在HandlerMapping中没有找到资源,会再拿着路径查找有没有对应的静态资源。
RestFul风格设计
RestFul:Http协议的标准使用方案和风格。
前端往后端发送请求时,需要考虑:
1.路径如何设计(如:/user/add?) 2.要使用哪种传递参数方案(param?json?path?)
3.要使用哪种请求方式(get?post?delete?put?)
RestFul就是解决这些问题的一种方法。
RESTFul风格特点
1.每一种URI代表一种资源(URI是名词)
设计URI时尽量不使用动词,如:要新增用户不用:POST /user/add,而是使用:POST /user; 要删除用户不用:DELETE /user/delete?id=1,而是使用:DELETE /user/1
2.客户端使用GET,POST,PUT,DELETE 4个表示操作方式的动作来对服务器资源进行操作:
GET用来获取资源,POST用来新建资源,PUT用来更新资源,DELETE用来删除资源。
3.资源使用xml或JSON
Spring MVC 其他拓展
全局异常处理机制
异常处理的两种方式
异常处理一般分为:编程式异常处理(使用trycatch手动显示地处理) 和 声明式异常处理(将异常处理的逻辑从具体业务逻辑中脱离,通过配置等方式进行统一的管理和处理)。
基于注解异常声明异常处理
1.声明异常处理控制器类
2.声明异常处理handler方法
//该注解 = @ControllerAdvice + @ResponseBody
//@ControllerAdvice 代表当前类的异常处理controller
//异常发生时,会走此类写的handler,
@RestControllerAdvice
public class GlobalExceptionHandler {
//发送异常 -》 ControllerAdvice -》@ExceptionHandler -》根据异常类调用方法
@ExceptionHandler(ArithmeticException.class)
public Object ArithmeticExceptionHandler(ArithmeticException e){
//自定义处理异常
return null;
}
@ExceptionHandler(Exception.class)
public Object ExceptionHandler(Exception e){
return null;
}
}
拦截器使用
拦截器概念
Filter过滤器
在javaweb项目中,可以使用Filter过滤器,当请求来到服务器时,会先经过过滤器的处理(登录保护,编码格式,权限处理),再到对应的目标类。
但SpringMVC中,Filter过滤器就不适用了,SpringMVC使用DispatcherServlet接收请求,当我们设置了Filter时,请求会在到达DispatcherServlet前经Filter处理,而SpringMVC内部细化流程Filter无法拦截。
HandlerInterceptor 拦截器(SpringMVC环境下推荐使用)
SpringMVC提供了HandlerInterceptor拦截器,它会在handlerAdapter调用handler之前和之后拦截以及整体处理之后拦截。
拦截器使用
1.创建拦截器类
public class MyInterceptor implements HandlerInterceptor { //在handler执行前拦截:编码格式设置,登录保护,权限处理 /** * * @param request 请求对象 * @param response 响应对象 * @param handler 我们要调用的方法对象 * @return true 放行 false 拦截 */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return HandlerInterceptor.super.preHandle(request, response, handler); } //在handler执行后触发,没有拦截机制(方法已执行,拦截无意义) // modelAndView 返回的视图和共享域对象 // 可以进行敏感词检查 @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { HandlerInterceptor.super.postHandle(request, response, handler, modelAndView); } //在数据返回给客户端前拦截 @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { HandlerInterceptor.super.afterCompletion(request, response, handler, ex); } }
2.修改配置类添加拦截器
@Configuration @ComponentScan("com.qiu.controller") @EnableWebMvc //WebMvcConfigurer 可以快速配置springmvc的组件 public class MvcConfig implements WebMvcConfigurer { // 将视图解析器加入到ioc容器内, // registry.jsp("/WEB-INF/views/",".jsp") 为handler返回的视图路径添加前后缀,其底层就是字符串拼接 @Override public void configureViewResolvers(ViewResolverRegistry registry) { registry.jsp("/WEB-INF/views/",".jsp"); } @Override public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { configurer.enable(); } @Override public void addInterceptors(InterceptorRegistry registry) { //配置方案1:拦截所有请求 //registry.addInterceptor(new MyInterceptor()); //配置方案2:指定地址拦截 /**:拦截路径下所有请求 //registry.addInterceptor(new MyInterceptor()) // .addPathPatterns("/user/**"); //配置方案3:排除拦截 registry.addInterceptor(new MyInterceptor()) .addPathPatterns("/user/**") .excludePathPatterns("/user/data"); } }
参数校验
场景:每次请求参数都需要判断是否为空和检查格式是否正确。
java通过了jsr303系列注解,只需要准备对应的实体类并在其属性上添加注解,当该实体类对象的属性的数据为空或者格式有误,就会报错。
使用
1.导入依赖
<!-- 校验注解 -->
<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.应用
@Data
public class User {
//age 1 <= age < = 150
@Min(10)
private int age;
//name 3 <= name.length <= 6
@Length(min = 3,max = 10)
private String name;
//email 邮箱格式
@Email
private String email;
}
3.handler方法标记
@RestController
@RequestMapping("user")
public class UserController {
/**
* @Validated 代表应用校验注解! 必须添加!
*/
@PostMapping("save")
public Object save(@Validated @RequestBody User user,
//在实体类参数和 BindingResult 之间不能有任何其他参数, BindingResult可以接受错误信息,避免信息抛出!
BindingResult result){
//判断是否有信息绑定错误! 有可以自行处理!
if (result.hasErrors()){
System.out.println("错误");
String errorMsg = result.getFieldError().toString();
return errorMsg;
}
//没有,正常处理业务即可
System.out.println("正常");
return user;
}
}