-- 控制反转与依赖注入 --
IoC容器
- 简介: 是 Spring 的核心, 负责创建和管理对象 (Bean) 的整个生命周期
- BeanFactory: IoC 容器的基础接口, 提供基本的 Bean 管理功能, 用的比较少
- ApplicationContext: BeanFactory 的子接口, 继承了 BeanFactory 的所有功能, 并增加了更多企业级特性
- IoC (控制反转) 与 DI (依赖注入):
- 控制反转: 是软件工程中的一项原则, 将对象或程序部分的控制权转移给容器或框架, 容器根据配置创建对象 (Bean), 并自动建立依赖关系, 代码只需"被动接收"依赖
- 依赖注入: 是实现 IoC 的一种模式, 核心思想为一个对象不应该自己去创建 (
new) 它所依赖的对象, 而是由外部容器 (如 Spring 框架) 负责创建并“注入”给它, 实现起来即"容器通过构造函数/setter 方法等方式, 将依赖对象主动注入目标对象 (Bean) 中" - 优点: 解耦 并提高可测试性 (比如在测试时, 可使用 DI 注入 Mock 对象, 专门用于测试)
SpringBean
- 概念: 类似于装有对象的特殊的全局变量, 存在独特的生命周期和作用域
- 作用域: 可通过配置 scope 来设置
- Singleton: 默认值, IoC 容器中只有一个 Bean 实例, 所有组件获取的都是同一个对象 (内存地址相同)
- Prototype: 每次获取 Bean 时 (getBean), 容器都会创建一个新的实例 (Prototype 作用域 Spring 容器不会销毁 Bean, 需要客户端自行管理资源的释放)
- Request: 每次 HTTP 请求都会产生一个新的 Bean
- Session: 同一个 HTTP Session 共享一个 Bean
Bean的注册/使用流程
- 通过 applicationContext.xml 注册
配置 SpringBean 的实现类 (Spring 的 Bean 要求比 JavaBean 要宽松):
@Component // 通用注解,标记该类为SpringBean,除此之外还有其派生注解@Service/@Repository/@Controller,本质都相同
public class <Bean的实现类名> {
/* setter注入,对应属性需要有对应的公有setter方法(getter方法可选),字段可以是公有(但推荐私有)
使用setter注入本质上是调用类的无参构造函数实例化对象后,再使用setter方法赋值,所以必须要有无参构造函数
(注意:定义了有参构造函数后,编译器将不再自动生成默认的无参构造函数) */
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
...
/* 构造函数注入,需要拥有对应的构造函数,字段可以是公有(但推荐私有) */
private String address;
private int age;
public <Bean的实现类名>(String address, int age){
this.address = address;
this.age = age;
}
...
/* 字段注入,该种方式必须用注解 */
@Autowired
private String profession;
}
通过配置 applicationContext.xml 注册 Bean (该种注册不需要注解):
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-5.3.xsd">
<!-- 扫描所在包下的Bean,并自动注册(可用base-package="<包名>"指定扫描的包)-->
<!-- 扫描到Bean后,会按照依赖注入注解,先通过构造函数注入,再进行字段注入,再进行Setter方法注入 -->
<context:component-scan/>
<!-- 手动注册Bean:setter注入 -->
<bean id="<Bean的名字>" class="<Bean的实现类的全限定名>" scope="<bean的作用域scope,可选>">
<property name="<成员名>" value="要赋予的值"></property>
<property name="<成员名>" ref="<当赋予的值为另外一个Bean时,这里填Bean的id>"></property>
...
</bean>
<!-- 手动注册Bean:构造函数注入 -->
<bean id="<Bean的名字>" class="<Bean的实现类的全限定名>" scope="<bean的作用域scope,可选>">
<constructor-arg index="0" value=要赋予的值/> <!-- 第一个参数 -->
<constructor-arg index="1" ref="<当赋予的值为另外一个Bean时,这里填Bean的id>"/> <!-- 第二个参数 -->
...
</bean>
...
</beans>
- 通过 java 配置类注册 (需要注解)
@Configuration // 标识这个是配置类(相当于<beans>标签)
@ComponentScan // 可选,扫描所在包下的Bean,并自动注册,可用basePackages="<包名>"指定扫描的包,
// ComponentScan扫描到Bean后,会按照依赖注入注解,先通过构造函数注入,再进行字段注入,再进行Setter方法注入
public class AppConfig { // AppConfig是约定名称,实际可以任意使用
@Bean // 手动注册Bean(方法名作为Bean的名字,相当于XML中的<bean>标签)
public <返回值类型> <方法名(Bean的名字)>(形参,...){
... // 在代码块里手动实现Setter注入/构造函数注入
return xxx; // 返回值会被注册为Bean
}
...
}
- 使用 Bean
-- 使用BeanFactory(Spring3.1后被标记过时,但仍可用) --
// 注:BeanFactory采用懒加载,只有在getBean时才创建对象
/* 引入 */
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
/* 实现代码 */
Resource resource=new ClassPathResource("applicationContext.xml");
BeanFactory factory=new XmlBeanFactory(resource);
/* 获取Bean */
<Bean的实现类> <变量名> = (<Bean的实现类>)factory.getBean("<Bean的名字>")
-- 使用ApplicationContext --
// 注: ApplicationContext在容器启动时就会预加载所有的单粒Bean
/* 引入 */
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/* 实现代码 */
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
/* 获取Bean */
<Bean的实现类> <变量名> = (<Bean的实现类>)context.getBean("<Bean的名字>")
Bean注解
注册Bean
@Component: 用于标识一个类为 SpringBean (用于非特定层的类, 即非 Controller/Service)@Respository: 用于标识一个类为 SpringBean (用于 DAO 层-数据访问层的类), 能将数据库访问抛出的特定异常转换为 Spring 的数据访问异常@Service: 用于标识一个类为 SpringBean (用于 Service 层-业务逻辑层的类), 主要用于处理复杂的业务逻辑@Controller: 用于标识一个类为 SpringBean (用于 Web 层-控制层的类), 通常与@RequestMapping配合使用, 处理 HTTP 请求
配置类
@Configuration: 用于标识一个类为配置类 (注: 配置类本身也是一个 Bean)@Bean: 标注在 (配置类的) 方法上, 告诉 Spring 该方法返回的对象应该被当作 Bean 注册, 用于手动注册 Bean@ComponentScan(basePackages = "<包名>"): 标注在配置类上, 其会扫描对应包中的 Bean (包括配置类), 然后注册 (若不写包名, 默认扫描配置类所在包)@Import({类名1.class, ...}): 标注在配置类上, 单独对指定的 Bean 进行注册
依赖注入
@Autowired- 应用场景: 可以用在 字段、构造函数、Setter 方法、方法参数上
- 作用: 先按类型, 再按名字
- 按类型装配, 在上下文中查找与类型匹配的 Bean, 并注入
- 若没找到类型匹配的 Bean, 则抛出异常; 若找到唯一一个, 则直接注入;
- 若找到多个, 若无
@Qualifier, 试查找 BeanID 与字段/参数名相同的 Bean 来注入, 若没找到则抛出异常
- 参数:
require=<boolean>: 当没找到 Bean 时, 是否抛出异常, 而非将字段/参数值设置为 null, 默认为 true (注: 当只有一个构造函数时, 默认会被当作被@Autowired标注)
@Qualifier("<BeanId>")- 应用场景: 可以用在 字段、方法参数上
- 作用: 当使用
@Autowired, 存在多个相同类型的 Bean 时, 通过@Qualifier指定注入哪一个名称的 Bean, 若没找到则抛出异常 (除非require=false)
@Resource- 应用场景: 可以用在 字段、构造函数、Setter 方法、方法参数上
- 作用: 先按名字, 再按类型
- 按名字装配, 在上下文中查找 id 与字段/参数名匹配的 Bean, 并注入
- 如果按名称没找到, 按类型查找, 若找到唯一一个, 则直接注入; 若没找到或找到多个, 则报错
- 参数:
name="<BeanId>": 直接指定注入的 BeanId
@Value- 应用场景: 可以用在 字段、方法参数上
- 作用: 用于注入各种值
- 注入 SpEL 表达式的值:
@Value(#{...}) - 注入字面量字符串:
@Value("<字符串>") - 注入 Resource 资源:
@Value("<路径字符串>")(当注入的字段/参数类型为 Resource 时, 会自动将路径字符串转换为 Resource 对象) - 注入外部值:
@Value(${<键名>})或@Value(${<键名>:<默认值>})(若未找到则使用默认值) 包括在application.properties或application.yml中定义键, 系统环境变量, JVM 系统属性, random (随机数)
- 注入 SpEL 表达式的值:
-- SpringMVC --
依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${org.springframework.version}</version>
</dependency>
简介
- MVC: 一种软件架构模式, 将应用程序划分为 Model (模型) - View (视图) - Controller (控制器) 三个组件, 旨在实现逻辑处理与界面展示的解耦
- Model (模型): 用于处理数据与业务逻辑
- 实体类 Bean (数据载体): 专门存储业务数据, 无业务逻辑, 是 JavaBean
- 业务 Bean (逻辑中枢): 包含 Service (业务逻辑层) 和 Dao (数据访问层), 负责数据的增删改查与规则约束, 是单例 SpringBean
- View (视图): 负责数据展示与交互
- 传统视图: 指工程中的 html 或 jsp 等服务端渲染的页面
- 现代视图: 前后端分离的架构中, JSON/XML 响应数据也是一种视图表现形式, 交由前端框架渲染
- Controller (控制器): 负责协调 Model 与 View 的交互, 接收请求与处理响应
- 前端控制器 (DispatcherServlet): Servlet, 框架的统一入口和调度中心, 负责接收请求并分发给具体的 Handler
- 业务控制器 (Handler): 开发者使用
@Controller/@RestController编写的 Java 类, 负责处理具体的业务请求, 本质上是 SpringBean - 配合: Spring MVC 采用了“前端控制器模式”, 即 1 个 Servlet (DispatcherServlet) + N 个 Controller (Handler)
工作流程
- 请求到达: 用户发送 HTTP 请求, 被 DispatcherServlet 捕获 (通常在 web.xml 中的
<servlet>或 JavaConfig 中定义捕获规则) - 查找 Handler: DispatcherServlet 调用 HandlerMapping (处理器映射器), 根据 URL (通常通过扫描
@Controller类中带有@RequestMapping注解的方法) 找到对应的 Handler 及其拦截器链 - 适配执行: DispatcherServlet 调用 HandlerAdapter (处理器适配器) 来执行 Handler (将 HTTP 请求参数赋值给 Handler 形参)
- 业务处理: Handler (Controller) 执行业务逻辑
- 情况 A (页面跳转): 返回
ModelAndView对象 - 情况 B (API 接口): 标注
@ResponseBody, 直接返回数据对象
- 情况 A (页面跳转): 返回
- 渲染/响应:
- 情况 A:
ViewResolver将逻辑视图名 (如 "success") 解析为具体的视图对象 (如success.html), View 渲染视图并返回 HTML - 情况 B:
HttpMessageConverter将数据对象序列化为 JSON 写入响应流
- 情况 A:
常用注解
控制器定义
@Controller: 标识一个类为控制器 (也即 Handler)@ResponseBody: 标识该控制器返回的是 Json 数据 (若不使用, 控制器返回默认被看作页面路径), Spring 会将返回的对象转换为 Json 格式发送给前端@RestController: 相当于@Controller+@ResponseBody
请求路径映射
@RequestMapping: 可标注在控制器及其方法上, 用于映射请求路径 (实际请求路径为控制器映射路径+方法映射路径)@GetMapping/@PostMapping/@PutMapping/@DeleteMapping: 标识特定方法的映射 (@RequestMapping默认会接收所有的请求方法的请求)
参数绑定
@RequestBody: 提取 HTTP 请求体中的 JSON/XML 数据, 并自动反序列化为 Java 对象 (默认通过 Jackson 实现)- 绑定参数类型: 可绑定
Map<String, Object>参数; 常绑定 JavaBean 参数, Spring 会根据 JSON 的键名与 Java 对象的属性名进行匹配, 并调用 setter 方法注入 (该 JavaBean 需要存在无参构造函数, 否则无法实例化 Bean)
- 绑定参数类型: 可绑定
@RequestParam: 提取 URL 中的查询参数/表单提交的参数- 绑定参数类型: 基本数据类型及其包装类 (Spring 会自动尝试将字符串参数转换为目标类型); 集合/数组; Map<String, String>; MultipartFile
- 注解参数:
required: 默认为 true, 设置参数是否必选, 为 true 时即当前端没传该参数时, 后端会直接报错400 Bad RequestdefaultValue: 设置当参数缺失时的默认值
@PathVariable: 提取路径映射注解中的动态路径参数 (用{参数名}标识)- 绑定的参数的类型: 基本数据类型及其包装类 (Spring 会自动尝试将字符串参数转换为目标类型)
@ModelAttribute:- 参数绑定:将请求参数(通常是 Form 表单)绑定到 Java Bean 对象。
- 数据预加载:如果标注在方法上,该方法会在控制器其他方法执行前先执行,常用于在 Model 中预先放入一些公共数据