SpringMVC
SpringMVC简介
简介
Spring Web MVC是基于Servlet API构建的原始Web框架,从一开始就包含在Spring Framework中。正式名称“Spring Web MVC”来自其源模块的名称( spring-webmvc
),但它通常被称为“Spring MVC”。
在控制层框架历经Strust、WebWork、Strust2等诸多产品的历代更迭之后,目前业界普遍选择了SpringMVC作为Java EE项目表述层开发的首选方案。之所以能做到这一点,是因为SpringMVC具备如下显著优势:
- Spring 家族原生产品,与IOC容器等基础设施无缝对接
- 表述层各细分领域需要解决的问题全方位覆盖,提供全面解决方案
- 代码清新简洁,大幅度提升开发效率
- 内部组件化程度高,可插拔式组件即插即用,想要什么功能配置相应组件即可
- 性能卓著,尤其适合现代大型、超大型互联网项目要求
核心组件和调用流程理解
Spring MVC与许多其他Web框架一样,是围绕前端控制器模式设计的,其中中央 Servlet
DispatcherServlet
做整体请求处理调度!
除了DispatcherServlet
SpringMVC还会提供其他特殊的组件协作完成请求处理和响应呈现。
SpringMVC处理请求流程:
SpringMVC涉及组件理解:
- DispatcherServlet : SpringMVC提供,我们需要使用web.xml配置使其生效,它是整个流程处理的核心,所有请求都经过它的处理和分发![ CEO ]
- HandlerMapping : SpringMVC提供,我们需要进行IoC配置使其加入IoC容器方可生效,它内部缓存handler(controller方法)和handler访问路径数据,被DispatcherServlet调用,用于查找路径对应的handler![秘书]
- HandlerAdapter : SpringMVC提供,我们需要进行IoC配置使其加入IoC容器方可生效,它可以处理请求参数和处理响应数据数据,每次DispatcherServlet都是通过handlerAdapter间接调用handler,他是handler和DispatcherServlet之间的适配器![经理]
- Handler : handler又称处理器,他是Controller类内部的方法简称,是由我们自己定义,用来接收参数,向后调用业务,最终返回响应结果![打工人]
- ViewResovler : SpringMVC提供,我们需要进行IoC配置使其加入IoC容器方可生效!视图解析器主要作用简化模版视图页面查找的,但是需要注意,前后端分离项目,后端只返回JSON数据,不返回页面,那就不需要视图解析器!所以,视图解析器,相对其他的组件不是必须的![财务]
快速入门
相关依赖
<!-- web相关依赖 -->
<!-- 在 pom.xml 中引入 Jakarta EE Web API 的依赖 -->
<!-- 在 Spring Web MVC 6 中,Servlet API 迁移到了 Jakarta EE API,因此在配置 DispatcherServlet 时需要使用Jakarta EE 提供的相应类库和命名空间。-->
<dependency>
<groupId>jakarta.platform</groupId>
<artifactId>jakarta.jakartaee-web-api</artifactId>
<version>${servlet.api}</version>
<scope>provided</scope>
</dependency>
<!-- springwebmvc相关依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
Controller声明
@Controller
public class HelloController {
@RequestMapping("/springmvc/hello")
@ResponseBody
public String hello(){
System.out.println("HelloController.hello");
return "hello springmvc!!";
}
}
Spring MVC核心组件配置类
@EnableWebMvc
@Configuration
@ComponentScan(basePackages = "com.atguigu.controller")
public class SpringMvcConfig implements WebMvcConfigurer {
@Bean
public HandlerMapping handlerMapping(){
return new RequestMappingHandlerMapping();
}
@Bean
public HandlerAdapter handlerAdapter(){
return new RequestMappingHandlerAdapter();
}
}
SpringMVC环境搭建
//TODO: SpringMVC提供的接口,是替代web.xml的方案,更方便实现完全注解方式ssm处理!
//TODO: Springmvc框架会自动检查当前类的实现类,会自动加载 getRootConfigClasses / getServletConfigClasses 提供的配置类
//TODO: getServletMappings 返回的地址 设置DispatherServlet对应处理的地址
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
/**
* 指定service / mapper层的配置类
*/
@Override
protected Class<?>[] getRootConfigClasses() {
return null;
}
/**
* 指定springmvc的配置类
* @return
*/
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] { SpringMvcConfig.class };
}
/**
* 设置dispatcherServlet的处理路径!
* 一般情况下为 / 代表处理所有请求!
*/
@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
}
SpringMVC接收数据
访问路径设置
@RequestMapping注解的作用就是将请求的 URL 地址和处理请求的方式(handler方法)关联起来,建立映射关系。
SpringMVC 接收到指定的请求,就会来找到在映射关系中对应的方法来处理这个请求。
- 精准路径匹配
在@RequestMapping注解指定 URL 地址时,不使用任何通配符,按照请求地址进行精确匹配。
@Controller
public class UserController {
//精准设置访问地址 /user/login
@RequestMapping(value = {"/user/login"})
@ResponseBody
public String login(){
return "login success!!";
}
}
- 模糊路径匹配
在@RequestMapping注解指定 URL 地址时,通过使用通配符,匹配多个类似的地址
@Controller
public class ProductController {
/**
* 路径设置为 /product/*
* /* 为单层任意字符串 /product/a /product/aaa 可以访问此handler
* 路径设置为 /product/**
* /** 为任意层任意字符串 /product/a /product/aaa 可以访问此handler
*/
@RequestMapping("/product/*")
@ResponseBody
public String show(){
...
}
}
- 类和方法级别区别
@RequestMapping
注解可以用于类级别和方法级别,它们之间的区别如下:
-
设置到类级别:
@RequestMapping
注解可以设置在控制器类上,用于映射整个控制器的通用请求路径。这样,如果控制器中的多个方法都需要映射同一请求路径,就不需要在每个方法上都添加映射路径。 -
设置到方法级别:
@RequestMapping
注解也可以单独设置在控制器方法上,用于更细粒度地映射请求路径和处理方法。当多个方法处理同一个路径的不同操作时,可以使用方法级别的@RequestMapping
注解进行更精细的映射。 -
附带请求方式限制
HTTP 协议定义了八种请求方式,在 SpringMVC 中封装到了下面这个枚举类:
public enum RequestMethod {
GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE
}
默认情况下:@RequestMapping(“/logout”) 任何请求方式都可以访问!
如果需要特定指定:
@Controller
public class UserController {
// 注意:违背请求方式会出现405异常!
@RequestMapping(value = {"/user/login"} , method = RequestMethod.POST)
@ResponseBody
public String login(){
System.out.println("UserController.login");
return "login success!!";
}
}
还有 @RequestMapping
的 HTTP 方法特定快捷方式变体:
@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
@PatchMapping
接收参数
param和json参数比较
在 HTTP 请求中,我们可以选择不同的参数类型,如 param 类型和 JSON 类型。下面对这两种参数类型进行区别和对比:
-
参数编码:
param 类型的参数会被编码为 ASCII 码。例如,假设
name=john doe
,则会被编码为name=john%20doe
。而 JSON 类型的参数会被编码为 UTF-8。 -
参数顺序:
param 类型的参数没有顺序限制。但是,JSON 类型的参数是有序的。JSON 采用键值对的形式进行传递,其中键值对是有序排列的。
-
数据类型:
param 类型的参数仅支持字符串类型、数值类型和布尔类型等简单数据类型。而 JSON 类型的参数则支持更复杂的数据类型,如数组、对象等。
-
嵌套性:
param 类型的参数不支持嵌套。但是,JSON 类型的参数支持嵌套,可以传递更为复杂的数据结构。
-
可读性:
param 类型的参数格式比 JSON 类型的参数更加简单、易读。但是,JSON 格式在传递嵌套数据结构时更加清晰易懂。
总的来说,param 类型的参数适用于单一的数据传递,而 JSON 类型的参数则更适用于更复杂的数据结构传递。根据具体的业务需求,需要选择合适的参数类型。在实际开发中,常见的做法是:在 GET 请求中采用 param 类型的参数,而在 POST 请求中采用 JSON 类型的参数传递。
param参数接收
-
直接接值
只要形参数名和类型与传递参数相同,即可自动接收! -
@RequestParam注解
可以使用 @RequestParam
注释将 Servlet 请求参数(即查询参数或表单数据)绑定到控制器中的方法参数。
@RequestParam
使用场景:
- 指定绑定的请求参数名
- 要求请求参数必须传递
- 为请求参数提供默认值
基本用法:
/**
* 前端请求: http://localhost:8080/param/data?name=xx&stuAge=18
* 使用@RequestParam注解标记handler方法的形参
* 指定形参对应的请求参数@RequestParam(请求参数名称)
*/
@GetMapping(value="/data")
@ResponseBody
public Object paramForm(@RequestParam("name") String name,
@RequestParam("stuAge") int age){
System.out.println("name = " + name + ", age = " + age);
return name+age;
}
默认情况下,使用此批注的方法参数是必需的,但您可以通过将 @RequestParam
批注的 required
标志设置为 false
!
如果没有设置非必须,也没有传递参数会出现400错误
将参数设置非必须,并且设置默认值:
@GetMapping(value="/data")
@ResponseBody
public Object paramForm(@RequestParam("name") String name,
@RequestParam(value = "stuAge",required = false,defaultValue = "18") int age){
System.out.println("name = " + name + ", age = " + age);
return name+age;
}
-
特殊场景接值
- 一名多值
多选框,提交的数据的时候一个key对应多个值,我们可以使用集合进行接收!
@GetMapping(value="/mul") @ResponseBody public Object mulForm(@RequestParam List<String> hbs){ System.out.println("hbs = " + hbs); return hbs; }
- 实体接收
@Controller @RequestMapping("param") public class ParamController { @RequestMapping(value = "/user", method = RequestMethod.POST) @ResponseBody public String addUser(User user) { // 在这里可以使用 user 对象的属性来接收请求参数 System.out.println("user = " + user); return "success"; } }
路径参数接收
路径传递参数是一种在 URL 路径中传递参数的方式。在 RESTful 的 Web 应用程序中,经常使用路径传递参数来表示资源的唯一标识符或更复杂的表示方式。而 Spring MVC 框架提供了 @PathVariable
注解来处理路径传递参数。
@PathVariable
注解允许将 URL 中的占位符映射到控制器方法中的参数。
例如,如果我们想将 /user/{id}
路径下的 {id}
映射到控制器方法的一个参数中,则可以使用 @PathVariable
注解来实现。
@GetMapping("/user/{id}/{name}")
@ResponseBody
public String getUser(@PathVariable Long id,
@PathVariable("name") String uname) {
System.out.println("id = " + id + ", uname = " + uname);
return "user_detail";
}
json参数接收
前端传递 JSON 数据时,Spring MVC 框架可以使用 @RequestBody
注解来将 JSON 数据转换为 Java 对象。@RequestBody
注解表示当前方法参数的值应该从请求体中获取,并且需要指定 value 属性来指示请求体应该映射到哪个参数上。其使用方式和示例代码如下:
- 前端发送 JSON 数据的示例:
{
"name": "张三",
"age": 18,
"gender": "男"
}
- 定义一个用于接收 JSON 数据的 Java 类,例如:
public class Person {
private String name;
private int age;
private String gender;
// getter 和 setter 略
}
- 在控制器中,使用
@RequestBody
注解来接收 JSON 数据,并将其转换为 Java 对象,例如:
@PostMapping("/person")
@ResponseBody
public String addPerson(@RequestBody Person person) {
...
}
运行可能会发生415错误!
原因:
- 不支持json数据类型处理
- 没有json类型处理的工具(jackson)
解决:
springmvc handlerAdpater配置json转化器,配置类需要明确:
json传递需要的依赖
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.0</version>
</dependency>
@EnableWebMvc注解说明
@EnableWebMvc注解效果等同于在 XML 配置中,可以使用 <mvc:annotation-driven>
元素!json数据处理,必须使用此注解,因为他会加入json处理器!添加了此注解后,也不需要在配置类中配置handlerMapping和handlerAdapter对象.
接收Cookie数据
可以使用 @CookieValue
注释将 HTTP Cookie 的值绑定到控制器中的方法参数。
@GetMapping("/demo")
public void handle(@CookieValue("JSESSIONID") String cookie) {
...
}
`
接收请求头数据
可以使用 @RequestHeader
批注将请求标头绑定到控制器中的方法参数。
请考虑以下带有标头的请求:
Host localhost:8080
Accept text/html,application/xhtml+xml,application/xml;q=0.9
Accept-Language fr,en-gb;q=0.7,en;q=0.3
Accept-Encoding gzip,deflate
Accept-Charset ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive 300
@GetMapping("/demo")
public void handle(
@RequestHeader("Accept-Encoding") String encoding,
@RequestHeader("Keep-Alive") long keepAlive) {
//...
}
获取原生api
/**
* 如果想要获取请求或者响应对象,或者会话等,可以直接在形参列表传入,并且不分先后顺序!
* 注意: 接收原生对象,并不影响参数接收!
*/
@GetMapping("api")
@ResponseBody
public String api(HttpSession session , HttpServletRequest request,
HttpServletResponse response){
String method = request.getMethod();
System.out.println("method = " + method);
return "api";
}
共享域对象操作
在 JavaWeb 中,共享域指的是在 Servlet 中存储数据,以便在同一 Web 应用程序的多个组件中进行共享和访问。常见的共享域有四种:ServletContext
、HttpSession
、HttpServletRequest
、PageContext
。
ServletContext
共享域:ServletContext
对象可以在整个 Web 应用程序中共享数据,是最大的共享域。一般可以用于保存整个 Web 应用程序的全局配置信息,以及所有用户都共享的数据。在ServletContext
中保存的数据是线程安全的。HttpSession
共享域:HttpSession
对象可以在同一用户发出的多个请求之间共享数据,但只能在同一个会话中使用。比如,可以将用户登录状态保存在HttpSession
中,让用户在多个页面间保持登录状态。HttpServletRequest
共享域:HttpServletRequest
对象可以在同一个请求的多个处理器方法之间共享数据。比如,可以将请求的参数和属性存储在HttpServletRequest
中,让处理器方法之间可以访问这些数据。PageContext
共享域:PageContext
对象是在 JSP 页面Servlet 创建时自动创建的。它可以在 JSP 的各个作用域中共享数据,包括pageScope
、requestScope
、sessionScope
、applicationScope
等作用域。
共享域的作用是提供了方便实用的方式在同一 Web 应用程序的多个组件之间传递数据,并且可以将数据保存在不同的共享域中,根据需要进行选择和使用。
SpringMVC响应数据
页面跳转控制
转发和重定向
在 Spring MVC 中,Handler 方法返回值来实现快速转发,可以使用 redirect
或者 forward
关键字来实现重定向。
@RequestMapping("/redirect-demo")
public String redirectDemo() {
// 重定向到 /demo 路径
return "redirect:/demo";
}
@RequestMapping("/forward-demo")
public String forwardDemo() {
// 转发到 /demo 路径
return "forward:/demo";
}
//注意: 转发和重定向到项目下资源路径都是相同,都不需要添加项目根路径!填写项目下路径即可!
返回json数据
@ResponseBody
方法上使用@ResponseBody
可以在方法上使用 @ResponseBody
注解,用于将方法返回的对象序列化为 JSON 或 XML 格式的数据,并发送给客户端。在前后端分离的项目中使用!
测试方法:
@GetMapping("/accounts/{id}")
@ResponseBody
public Object handle() {
// ...
return obj;
}
具体来说,@ResponseBody
注解可以用来标识方法或者方法返回值,表示方法的返回值是要直接返回给客户端的数据,而不是由视图解析器来解析并渲染生成响应体(viewResolver没用)。
类上使用@ResponseBody
如果类中每个方法上都标记了 @ResponseBody 注解,那么这些注解就可以提取到类上。
@RestController
类上的 @ResponseBody 注解可以和 @Controller 注解合并为 @RestController 注解。所以使用了 @RestController 注解就相当于给类中的每个方法都加了 @ResponseBody 注解。
返回静态资源处理
静态资源概念
资源本身已经是可以直接拿到浏览器上使用的程度了,不需要在服务器端做任何运算、处理。典型的静态资源包括:
- 纯HTML文件
- 图片
- CSS文件
- JavaScript文件
- ……
访问方法,直接通过文件的地址访问即可,但是这样会报错
问题分析
- DispatcherServlet 的 url-pattern 配置的是“/”
- url-pattern 配置“/”表示整个 Web 应用范围内所有请求都由 SpringMVC 来处理
- 对 SpringMVC 来说,必须有对应的 @RequestMapping 才能找到处理请求的方法
- 现在 images/mi.jpg 请求没有对应的 @RequestMapping 所以返回 404
问题解决
- 在 SpringMVC 配置配置类:
@EnableWebMvc
@Configuration
@ComponentScan(basePackages = "com.xxx.controller")
public class SpringMvcConfig implements WebMvcConfigurer {
//开启静态资源处理 <mvc:default-servlet-handler/>
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
SpringMVC其他扩展
基于注解异常声明异常处理
声明异常处理控制器类
异常处理控制类,统一定义异常处理handler方法!
@RestControllerAdvice
public class GlobalExceptionHandler {
}
声明异常处理hander方法
异常处理handler方法和普通的handler方法参数接收和响应都一致!
只不过异常处理handler方法要映射异常,发生对应的异常会调用!
普通的handler方法要使用@RequestMapping注解映射路径,发生对应的路径调用!
/**
* 异常处理handler
* @ExceptionHandler(HttpMessageNotReadableException.class)
* 该注解标记异常处理Handler,并且指定发生异常调用该方法!
*
*
* @param e 获取异常对象!
* @return 返回handler处理结果!
*/
@ExceptionHandler(Exception.class)
public Object handlerException(Exception e){
return null;
}
具体异常处理Handler优先级更高!例如: 发生NullPointerException异常!会触handlerNullException方法,不会触发handlerException方法!
配置文件扫描控制器类配置
确保异常处理控制类被扫描
拦截器
拦截器 Springmvc VS 过滤器 javaWeb:
- 相似点
- 拦截:必须先把请求拦住,才能执行后续操作
- 过滤:拦截器或过滤器存在的意义就是对请求进行统一处理
- 放行:对请求执行了必要操作后,放请求过去,让它访问原本想要访问的资源
- 不同点
- 工作平台不同
- 过滤器工作在 Servlet 容器中
- 拦截器工作在 SpringMVC 的基础上
- 拦截的范围
- 过滤器:能够拦截到的最大范围是整个 Web 应用
- 拦截器:能够拦截到的最大范围是整个 SpringMVC 负责的请求
- IOC 容器支持
- 过滤器:想得到 IOC 容器需要调用专门的工具方法,是间接的
- 拦截器:它自己就在 IOC 容器中,所以可以直接从 IOC 容器中装配组件,也就是可以直接得到 IOC 容器的支持
- 工作平台不同
选择:
功能需要如果用 SpringMVC 的拦截器能够实现,就不使用过滤器。
拦截器使用
创建拦截器类
public class Process01Interceptor implements HandlerInterceptor {
// if( ! preHandler()){return;}
// 在处理请求的目标 handler 方法前执行
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 返回true:放行
// 返回false:不放行
return true;
}
// 在目标 handler 方法之后,handler报错不执行!
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
...
}
// 渲染视图之后执行(最后),一定执行!
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
...
}
}
修改配置类添加拦截器
@EnableWebMvc //json数据处理,必须使用此注解,因为他会加入json处理器
@Configuration
@ComponentScan(basePackages = {"com.xxx.controller","com.xxx.exceptionhandler"}) //TODO: 进行controller扫描
public class SpringMvcConfig implements WebMvcConfigurer {
//添加拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
//将拦截器添加到Springmvc环境,默认拦截所有Springmvc分发的请求
registry.addInterceptor(new Process01Interceptor());
}
}
配置详情
- 默认拦截全部
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new Process01Interceptor());
}
- 精准配置
@Override
public void addInterceptors(InterceptorRegistry registry) {
//也支持 /* 和 /** 模糊路径。 * 任意一层字符串 ** 任意层 任意字符串
registry.addInterceptor(new Process01Interceptor()).addPathPatterns("/common/request/one","/common/request/tow");
}
- 排除配置
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new Process01Interceptor()).addPathPatterns("/common/request/one","/common/request/tow").excludePathPatterns("/common/request/tow");
}
- 多个拦截器执行顺序
- preHandle() 方法:SpringMVC 会把所有拦截器收集到一起,然后按照配置顺序调用各个 preHandle() 方法。
- postHandle() 方法:SpringMVC 会把所有拦截器收集到一起,然后按照配置相反的顺序调用各个 postHandle() 方法。
- afterCompletion() 方法:SpringMVC 会把所有拦截器收集到一起,然后按照配置相反的顺序调用各个 afterCompletion() 方法。
参数校验
JSR 303 是 Java 为 Bean 数据合法性校验提供的标准框架,它已经包含在 JavaEE 6.0 标准中。JSR 303 通过在 Bean 属性上标注类似于 @NotNull、@Max 等标准的注解指定校验规则,并通过标准的验证接口对Bean进行验证。
相关依赖
<!-- 校验注解 -->
<dependency>
<groupId>jakarta.platform</groupId>
<artifactId>jakarta.jakartaee-web-api</artifactId>
<version>9.1.0</version>
<scope>provided</scope>
</dependency>
<!-- 校验注解实现-->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>8.0.0.Final</version>
</dependency>
应用校验注解
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Min;
import org.hibernate.validator.constraints.Length;
/**
* projectName: com.atguigu.pojo
*/
public class User {
//age 1 <= age < = 150
@Min(10)
private int age;
//get,set...
}
handler标记和绑定错误收集
@RestController
@RequestMapping("user")
public class UserController {
// @Validated 代表应用校验注解! 必须添加!
@PostMapping("save")
public Object save(@Validated @RequestBody User user,
//在实体类参数和 BindingResult 之间不能有任何其他参数, BindingResult可以接受错误信息,避免信息抛出!
BindingResult result){
if (result.hasErrors()){
//错误处理
}
//正常处理
}
}
SSM整合原理
SSM整合核心问题
SSM整合需要几个IoC容器?
两个容器
本质上说,整合就是将三层架构和框架核心API组件交给SpringIoC容器管理!
一个容器可能就够了,但是我们常见的操作是创建两个IoC容器(web容器和root容器),组件分类管理!
这种做法有以下好处和目的:
- 分离关注点:通过初始化两个容器,可以将各个层次的关注点进行分离。这种分离使得各个层次的组件能够更好地聚焦于各自的责任和功能。
- 解耦合:各个层次组件分离装配不同的IoC容器,这样可以进行解耦。这种解耦合使得各个模块可以独立操作和测试,提高了代码的可维护性和可测试性。
- 灵活配置:通过使用两个容器,可以为每个容器提供各自的配置,以满足不同层次和组件的特定需求。每个配置文件也更加清晰和灵活。
总的来说,初始化两个容器在SSM整合中可以实现关注点分离、解耦合、灵活配置等好处。它们各自负责不同的层次和功能,并通过合适的集成方式协同工作,提供一个高效、可维护和可扩展的应用程序架构!
每个IoC容器对应哪些类型组件?
总结:
容器名 | 盛放组件 |
---|---|
web容器 | web相关组件(controller,springmvc核心组件) |
root容器 | 业务和持久层相关组件(service,aop,tx,dataSource,mybatis,mapper等) |
IoC容器之间关系和调用方向?
子IoC容器可以单向的注入父IoC容器的组件!
结论:web容器是root容器的子容器,父子容器关系。
- 父容器:root容器,盛放service、mapper、mybatis等相关组件
- 子容器:web容器,盛放controller、web相关组件
具体多少配置类以及对应容器关系?
配置类的数量不是固定的,但是至少要两个,为了方便编写,我们可以三层架构每层对应一个配置类,分别指定两个容器加载即可!
IoC初始化方式和配置位置?
在web项目下,我们可以选择web.xml和配置类方式进行ioc配置,推荐配置类。
对于使用基于 web 的 Spring 配置的应用程序,建议这样做,如以下示例所示:
public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
//指定root容器对应的配置类
//root容器的配置类
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[] { ServiceJavaConfig.class,MapperJavaConfig.class };
}
//指定web容器对应的配置类 webioc容器的配置类
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] { WebJavaConfig.class };
}
//指定dispatcherServlet处理路径,通常为 /
@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
}
图解配置类和容器配置:
MyBatis整合
其他部分不在这里详细写,只补充如何整合持久层框架
mybatis整合思路
mybatis核心api使用回顾:
//1.读取外部配置文件
InputStream ips = Resources.getResourceAsStream("mybatis-config.xml");
//2.创建sqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(ips);
//3.创建sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
//4.获取mapper代理对象
EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class);
//5.数据库方法调用
int rows = empMapper.deleteEmpById(1);
System.out.println("rows = " + rows);
//6.提交和回滚
sqlSession.commit();
sqlSession.close();
mybatis核心api介绍回顾:
-
SqlSessionFactoryBuilder
这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。
因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。 无需ioc容器管理! -
SqlSessionFactory
一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,因此 SqlSessionFactory 的最佳作用域是应用作用域。 需要ioc容器管理!
-
SqlSession
每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 无需ioc容器管理!
-
Mapper映射器实例
映射器是一些绑定映射语句的接口。映射器接口的实例是从 SqlSession 中获得的。虽然从技术层面上来讲,任何映射器实例的最大作用域与请求它们的 SqlSession 相同。但方法作用域才是映射器实例的最合适的作用域。
从作用域的角度来说,映射器实例不应该交给ioc容器管理!
但是从使用的角度来说,业务类(service)需要注入mapper接口,所以mapper应该交给ioc容器管理!
总结
- 将SqlSessionFactory实例存储到IoC容器
- 将Mapper实例存储到IoC容器
mybatis整合思路理解:
mybatis的api实例化需要复杂的过程。
例如,自己实现sqlSessionFactory加入ioc容器:
@Bean
public SqlSessionFactory sqlSessionFactory(){
//1.读取外部配置文件
InputStream ips = Resources.getResourceAsStream("mybatis-config.xml");
//2.创建sqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(ips);
return sqlSessionFactory;
}
过程比较繁琐,为了提高整合效率,mybatis提供了提供封装SqlSessionFactory和Mapper实例化的逻辑的FactoryBean组件,我们只需要声明和指定少量的配置即可!
mybatis整合思路总结:
- 需要将SqlSessionFactory和Mapper实例加入到IoC容器
- 使用mybatis整合包提供的FactoryBean快速整合
准备外部配置文件
jdbc.user=root
jdbc.password=root
jdbc.url=jdbc:mysql:///mybatis-example
jdbc.driver=com.mysql.cj.jdbc.Driver
整合方式(完全配置类方式)
持久层Mapper配置、数据库配置、Mybatis配置信息
MapperJavaConfig.java(命名随意)
@Configuration
@PropertySource("classpath:jdbc.properties")
public class MapperJavaConfig {
@Value("${jdbc.user}")
private String user;
@Value("${jdbc.password}")
private String password;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.driver}")
private String driver;
//数据库连接池配置
@Bean
public DataSource dataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUsername(user);
dataSource.setPassword(password);
dataSource.setUrl(url);
dataSource.setDriverClassName(driver);
return dataSource;
}
/**
* 配置SqlSessionFactoryBean,指定连接池对象和外部配置文件即可
* @param dataSource 需要注入连接池对象
* @return 工厂Bean
*/
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){
//实例化SqlSessionFactory工厂
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
//设置连接池
sqlSessionFactoryBean.setDataSource(dataSource);
org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
//settings
configuration.setMapUnderscoreToCamelCase(true);
configuration.setLogImpl(Slf4jImpl.class);
configuration.setAutoMappingBehavior(AutoMappingBehavior.FULL);
sqlSessionFactoryBean.setConfiguration(configuration);
//typeAliases
sqlSessionFactoryBean.setTypeAliasesPackage("com.xxx.pojo");
//分页插件配置
PageInterceptor pageInterceptor = new PageInterceptor();
Properties properties = new Properties();
properties.setProperty("helperDialect","mysql");
pageInterceptor.setProperties(properties);
sqlSessionFactoryBean.addPlugins(pageInterceptor);
return sqlSessionFactoryBean;
}
/**
* 配置Mapper实例扫描工厂,配置 <mapper <package 对应接口和mapperxml文件所在的包
* @return
*/
@Bean
public MapperScannerConfigurer mapperScannerConfigurer(){
MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
//设置mapper接口和xml文件所在的共同包
mapperScannerConfigurer.setBasePackage("com.xxx.mapper");
return mapperScannerConfigurer;
}
}
问题:
当你在Spring配置类中添加了sqlSessionFactoryBean
和mapperScannerConfigurer
配置方法时,可能会导致@Value
注解读取不到值为null的问题。这是因为SqlSessionFactoryBean
和MapperScannerConfigurer
是基于MyBatis框架的配置,它们的初始化顺序可能会导致属性注入的问题。
SqlSessionFactoryBean
和MapperScannerConfigurer
在配置类中通常是用来配置MyBatis相关的Bean,例如数据源、事务管理器、Mapper扫描等。这些配置类通常在@Configuration
注解下定义,并且使用@Value
注解来注入属性值。
当配置类被加载时,Spring容器会首先处理Bean的定义和初始化,其中包括sqlSessionFactoryBean
和mapperScannerConfigurer
的初始化。在这个过程中,如果@Value
注解所在的Bean还没有被完全初始化,可能会导致注入的属性值为null。
解决方案:
分成两个配置类独立配置,互不影响,数据库提取一个配置类,mybatis提取一个配置类即可解决!
容器初始化配置类
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
//指定root容器对应的配置类
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[] {MapperJavaConfig.class, ServiceJavaConfig.class, DataSourceJavaConfig.class };
}
//指定web容器对应的配置类
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] { WebJavaConfig.class };
}
//指定dispatcherServlet处理路径,通常为 /
@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
}
分页查询的功能实现
controller
/*
@CrossOrigin 注释在带注释的控制器方法上启用跨源请求
*/
@CrossOrigin
@RequestMapping("schedule")
@RestController
public class ScheduleController
{
@Autowired
private ScheduleService scheduleService;
@GetMapping("/{pageSize}/{currentPage}")
public R showList(@PathVariable(name = "pageSize") int pageSize, @PathVariable(name = "currentPage") int currentPage){
PageBean<Schedule> pageBean = scheduleService.findByPage(pageSize,currentPage);
return R.ok(pageBean);
}
}
service
@Slf4j
@Service
public class ScheduleServiceImpl implements ScheduleService {
@Autowired
private ScheduleMapper scheduleMapper;
@Override
public PageBean<Schedule> findByPage(int pageSize, int currentPage) {
//1.设置分页参数
PageHelper.startPage(currentPage,pageSize);
//2.数据库查询
List<Schedule> list = scheduleMapper.queryPage();
//3.结果获取
PageInfo<Schedule> pageInfo = new PageInfo<>(list);
//4.pageBean封装
PageBean<Schedule> pageBean = new PageBean<>(pageInfo.getPageNum(),pageInfo.getPageSize(),pageInfo.getTotal(),pageInfo.getList());
log.info("分页查询结果:{}",pageBean);
return pageBean;
}
}
mapper
mapper接口
public interface ScheduleMapper {
List<Schedule> queryPage();
}
mapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace等于mapper接口类的全限定名,这样实现对应 -->
<mapper namespace="com.xxx.mapper.ScheduleMapper">
<select id="queryPage" resultType="schedule">
select * from schedule
</select>
</mapper>