## 一、Spring框架简介 #### 1、Spring Framework 是什么? Spring Framework 是一个开源的、轻量级的 **Java 平台应用程序框架** 和 **反转控制 (IoC) 容器**。它由 Rod Johnson 在 2003 年创建,并发布在其著作 *Expert One-on-One J2EE Design and Development* 中。 其核心使命是:简化企业级 Java 应用程序的开发。它通过提供一种全面的编程和配置模型,解决了传统 J2EE 开发的复杂性和侵入性问题,带来了以下关键好处: 1. **轻量级与非侵入性:** Spring 应用中的对象通常不依赖于 Spring 特有的接口或类(大部分情况下),更容易重用和测试。 2. **依赖注入 (DI) / 控制反转 (IoC):** 这是 Spring 最核心的基石之一,实现组件之间的解耦。 3. **面向切面编程 (AOP):** 这是另一个核心支柱,用于处理横切关注点(如日志、事务、安全),保持业务逻辑的纯净。 4. **声明式事务管理:** 通过配置或注解方式管理事务,极大简化了事务相关代码。 5. **模块化设计:** 用户可以根据需要选择使用不同的模块。 6. **灵活的配置机制:** 支持 XML、Java 注解 (Annotation) 和 Java 代码配置方式。 7. **强大的集成能力:** 方便地与各种企业级技术集成(如持久化框架 - Hibernate, JDBC; 消息队列 - JMS, AMQP; Web Services; 安全管理框架等)。 8. **简化测试:** 由于 DI 的存在,单元测试和集成测试变得更容易实现(特别是配合 Spring Test 模块)。 9. **活跃的社区和生态系统:** 拥有庞大的用户群体、丰富的文档和教程,以及基于 Spring 构建的广泛生态系统(如 Spring Boot, Spring Cloud, Spring Data, Spring Security 等)。 #### 2、Spring Framework 体系架构 (模块组成) Spring Framework 采用分层架构,由多个精心设计的模块组成。这些模块可以大致分为以下几个核心层: 1. **核心容器 (Core Container):** - `spring-core`, `spring-beans`: 提供框架的基础功能,包括 **IoC (控制反转)** 和 **DI (依赖注入)** 特性的实现。`BeanFactory` 是其核心接口,代表了一个基本的 IoC 容器。 - `spring-context`: 建立在 `core` 和 `beans` 之上,提供了一种更丰富的、面向企业级特性的上下文(`ApplicationContext`)。它增加了对国际化(i18n)、事件传播、资源加载等功能。`ApplicationContext` 是 `BeanFactory` 的一个功能更全的超集,是实际应用中使用最多的容器接口。 - `spring-expression` (SpEL): 提供强大的 **Spring 表达式语言**,用于在运行时查询和操作对象图。 2. **AOP 和 Instrumentation:** - `spring-aop`: 提供 **面向切面编程 (AOP)** 的支持,允许实现横切关注点(如日志、事务管理、安全等)与核心业务逻辑的分离。内置基于代理的 AOP 框架。 - `spring-aspects`: 提供与 AspectJ 框架的集成。 - *(较少直接使用):* `spring-instrument`, `spring-instrument-tomcat`: 提供类加载器实现和服务器特定的启动器,用于 JVM 级别的监视和管理(如使用 Java Agent)。 3. **数据访问/集成 (Data Access/Integration):** - `spring-jdbc`: 提供了一个 **JDBC 抽象层**,大大简化了传统 JDBC API 繁琐、易错的操作(如连接管理、异常处理)。 - `spring-orm`: 提供与流行的 **对象关系映射 (ORM)** 框架(如 Hibernate, JPA, JDO)的集成层。 - `spring-oxm`: 提供 **对象-XML 映射 (Object/XML Mapping - OXM)** 的支持(如 JAXB, Castor, XStream)。 - `spring-jms`: 提供用于 **消息传递 (Messaging)** 的抽象层(主要是 JMS)。 - `spring-tx`: 提供强大且灵活的 **声明式事务管理** 能力,可以管理多种数据源(JDBC, JPA, JTA等)的事务。 4. **Web:** - `spring-web`: 提供基本的 Web 功能,如文件上传、Servlet Listener 初始化 IoC 容器等。包含一个 HTTP 客户端 (`RestTemplate`) 和集成 MVC 框架所需的核心部分。 - `spring-webmvc`: 也称为 **Spring MVC**,是一个功能齐全的 **模型-视图-控制器 (Model-View-Controller)** 实现,用于构建 Web 应用程序。非常灵活且易于配置。 - `spring-webflux`: 在 Spring 5 引入,是一个非阻塞的、函数式、**反应式编程模型**(基于 Project Reactor)的 Web 框架,用于构建高并发、事件驱动的服务。 - `spring-websocket`: 提供 WebSocket 和 STOMP 协议支持,用于构建实时 Web 应用。 5. **测试:** - `spring-test`: 提供与 JUnit 或 TestNG 等测试框架的集成,支持使用依赖注入编写单元测试和集成测试,并提供了用于在测试中加载 Spring 上下文、模拟对象等的工具类。 #### 3、Spring Framework 的两大核心:DI 和 AOP ##### 1. 依赖注入 (Dependency Injection - DI) / 控制反转 (Inversion of Control - IoC) - **什么是依赖注入**:指工程中需要的组件无需自己创建,而是依赖外部环境注入。 - **核心理念:** **控制权反转 (IoC)** 是一种设计原则,它将对象的创建、配置和依赖关系的管理职责 **转交给一个外部实体(容器)**,而不是由对象自身主动去获取或创建它所依赖的对象。**依赖注入 (DI)** 是实现 IoC 原则的主要技术手段。 - **解决的问题:** 传统开发中,对象自己控制其依赖(`new` 关键字),导致代码耦合度高(硬编码依赖)、难以测试(依赖具体实现)、灵活性差(更换依赖困难)。 - **Spring DI 的工作原理:** 1. 组件(通常称为 **Bean**)在其定义(通过 XML、注解或 Java Config)中描述它**需要什么依赖**(接口或类)。 2. **Spring IoC 容器 (`ApplicationContext`)** 负责: - 根据配置创建 Bean 的实例(或管理其生命周期)。 - 解析 Bean 之间的依赖关系。 - 在创建 Bean**时**,将其所需的依赖项**自动注入**进来(通过构造器、Setter 方法或直接字段注入)。 - **关键点:** - **解耦:** Bean 不需要知道依赖来自哪里、如何创建。它只关心接口。 - **面向接口编程:** 更容易替换依赖的实现(例如,将 MySQL DAO 替换为 PostgreSQL DAO,只需改配置,业务代码不变)。 - **可测试性:** 在测试时,可以轻松地注入模拟对象 (`Mock`) 代替真实依赖。 - **配置集中化:** 依赖关系在配置文件或注解中清晰可见,易于管理。 - **注入方式:** - **构造器注入:** 通过构造器参数注入依赖。推荐方式(强制依赖、不变依赖)。 - **Setter 注入:** 通过 Setter 方法注入依赖。(可选依赖)。 - **字段注入:** 通过 `@Autowired` 等注解直接注入到字段。(通常不推荐,破坏了封装,使测试稍复杂)。 ##### 2. 面向切面编程 (Aspect-Oriented Programming - AOP) - **核心理念:** 软件系统由多个**关注点 (Concern)** 组成。 - **核心关注点:** 实现业务逻辑(如用户注册、订单创建)。 - **横切关注点:** 影响应用程序多个模块的功能(如日志记录、事务管理、安全性、性能监控、错误处理)。这些关注点散布在核心代码中,导致代码重复(散弹式修改)和核心逻辑不清晰(代码纠缠)。 - **解决的问题:** 将横切关注点从核心业务逻辑中分离出来,实现关注点分离,提高代码的**模块化、可重用性和可维护性**。 - **AOP 的关键概念:** - **切面:** **一个模块化的类**,它封装了与横切关注点相关的功能(包含通知和切点)。例如,一个 `LoggingAspect` 切面包含所有的日志记录逻辑。 - **通知:** 切面在**特定的连接点执行的动作**。常见的通知类型: - **前置通知 (`@Before`):** 在目标方法执行前执行。 - **后置通知 (`@AfterReturning`):** 在目标方法**成功**执行后执行。 - **异常通知 (`@AfterThrowing`):** 在目标方法**抛出异常**后执行。 - **最终通知 (`@After`):** 在目标方法执行后执行(不管成功还是异常)。 - **环绕通知 (`@Around`):** **包裹**目标方法执行的通知,可以在方法执行前后自定义行为,甚至可以控制方法是否执行或修改返回值。最强大。 - **连接点:** 应用程序执行过程中**可以插入通知的点**。在 Spring AOP 中,这通常指**方法执行**(Spring AOP 基于代理,主要拦截方法调用)。 - **切点:** 一个**表达式**,用于**匹配连接点**。通知会与一个切点表达式关联,并决定在**何处**执行该通知(匹配哪些方法)。Spring 使用 AspectJ 的切点表达式语言 (AspectJ Pointcut Expression Language)。 - **目标对象:** 被一个或多个切面通知的对象。也就是包含核心业务逻辑的对象。 - **代理:** AOP 框架为了实现切面功能而**为目标对象创建的对象**。在 Spring AOP 中,代理可以是 JDK 动态代理(实现接口)或 CGLIB 代理(子类化)。调用者实际调用的是代理对象,代理对象在适当的时候应用通知并最终调用目标对象的方法。 - **Spring AOP 特点:** - 主要基于 **代理机制** (Proxy)。 - 主要用于**方法执行**连接点。 - 对目标类**非侵入性**。 - 强大地集成了 Spring IoC 容器(可以注入 Bean 到切面)。 - 方便地与 AspectJ 注解配合使用(`@Aspect`, `@Pointcut`, `@Before` 等)。 ## 二、使用XML实现DI 使用Spring来重构Service层和DAO层 #### 1、新建项目 项目名称为`springTest`,构建系统为`Maven` #### 2、添加依赖 在项目的`pom.xml`配置中添加``、`` ```xml org.springframework spring-context 6.2.7 ``` #### 3、创建类包 为了提升效率,此处只是模拟DAO层,模拟数据库查询返回一个值。 创建`com.doooog.spring.dao`、`com.doooog.spring.test`、`com.doooog.spring.po`、`com.doooog.service`包 在`po`包内创建`User.java`持久层对象类 ```java package com.doooog.spring.po; public class User { private Integer uid; private String uname; private Integer age; public User(Integer uid, String uname, Integer age) { this.uid = uid; this.uname = uname; this.age = age; } public User() { } @Override public String toString() { return "User{" + "uid=" + uid + ", uname='" + uname + '\'' + ", age=" + age + '}'; } public Integer getUid() { return uid; } public void setUid(Integer uid) { this.uid = uid; } public String getUname() { return uname; } public void setUname(String uname) { this.uname = uname; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } } ``` 在`dao`包内创建`UserDao.java`接口 ```java package com.doooog.spring.dao; public interface UserDao { int saveUser(User user); } ``` 创建`com.doooog.spring.dao.impl`包 在`impl`包内创建`UserDaoImpl.java`实现类 ```java package com.doooog.spring.dao.impl; import com.doooog.spring.dao.UserDao; public class UserDaoImpl implements UserDao { @Override public int saveUser(User user) { System.out.println("数据库操作"); return 1; } } ``` #### 4、创建Spring框架配置 在`rescources`资源目录下创建目录`com/doooog/spring`,在`spring`目录下创建配置文件`SpringConfig.xml` ```xml ``` 其中``相当于Java的对象。 #### 5、加载Spring容器 在`test`包内新建测试类 ```java package com.doooog.spring.test; import com.doooog.spring.dao.UserDao; import com.doooog.spring.po.User; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Test { public static void main(String[] args) { // 加载Spring容器(读取Spring配置文件) ApplicationContext context = new ClassPathXmlApplicationContext("com/doooog/spring/SpringConfig.xml"); // getBean()返回的是Object类型,需要强转 UserDao userDao = (UserDao) context.getBean("userDaoImpl"); userDao.saveUser(new User()); } } ``` **注意**:`ApplicationContext` 接口是Spring容器的核心接口,负责管理Bean的生命周期和依赖注入,`ClassPathXmlApplicationContext` 类可以从类路径(classpath)加载XML配置文件的ApplicationContext实现类。 6、封装Service层 在`Service`包下创建`UserService.java`接口 ```java package com.doooog.spring.service; import com.doooog.spring.po.User; public interface UserService { int saveUser(User user); } ``` 新建`com.doooog.spring.service.impl`包,在`impl`包下创建`UserServiceImpl.java`实现类 ```java package com.doooog.spring.service.impl; import com.doooog.spring.dao.UserDao; import com.doooog.spring.po.User; import com.doooog.spring.service.UserService; public class UserServiceImpl implements UserService { private UserDao userDao; @Override public int saveUser(User user) { return userDao.saveUser(user); } public UserDao getUserDao() { return userDao; } public void setUserDao(UserDao userDao) { this.userDao = userDao; } } ``` 因为需要让Spring框架将`UserDao`自动注入到`UserService`里,所以要添加私有属性`UserDao userDao`用来存放`UserDao`对象 在`SpringConfig.xml`中添加`` ```xml ``` 因为封装`UserServiceImpl`包时添加了属性`userDao`,所以在此添加``标签去告知Spring框架要注入的属性和对象。 在测试类内调用即可 ```java // 加载Spring容器(读取Spring配置文件) ApplicationContext context = new ClassPathXmlApplicationContext("com/doooog/spring/SpringConfig.xml"); UserService userService = (UserService) context.getBean("userDaoService"); userService.saveUser(new User()); ``` 很明显在代码中没有显示的声明`UserDao`的对象,但在`UserService`对象中可以通过方法`saveUser`调用其`UserDao`属性。 ## 三、使用注解实现DI 注解(Annotation)的主要作用是**为代码提供元数据**,这些元数据可以被编译器、开发工具或运行时环境读取和使用。 其他注解见:[Java常用注解表](https://www.soki.me/archives/263.html) #### 1、为无属性类实现依赖注入 在需要配置为`bean`的类的类头上方添加`@Component`表示其是一个Spring组件。 在`UserDaoImpl.java`类头上添加`@Component("userDao")` ```java package com.doooog.spring.dao.impl; import com.doooog.spring.dao.UserDao; import com.doooog.spring.po.User; import org.springframework.stereotype.Component; @Component("userDao") public class UserDaoImpl implements UserDao { @Override public int saveUser(User user) { System.out.println("数据库操作"); return 1; } } ``` **注意**:`@Component`注解可以有`String`类型参数,意为Spring组件名,与`xml`中``定义的`id`意思相同。 #### 2、注释掉``标签 修改`SpringConfig.xml`文件,将第二步添加的所有``标签注释掉 ```java ``` #### 3、添加组件扫描器 在`SpringConfig.xml`文件中,添加``扫描器,扫描指定包下带有`@Component`注解的类,并将其变为`bean`组件纳入Spring容器。 ```xml ``` **注意**:属性`base-package`为需要扫描的包名,包名需要全名,扫描器会扫描包下所有的类包括子包中的类,所以一般将最大的包名写进去即可,若有需要分开写,则可以用`,`分割。 #### 4、测试无属性组件 ```java package com.doooog.spring.test; import com.doooog.spring.dao.UserDao; import com.doooog.spring.po.User; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Test { public static void main(String[] args) { // 加载Spring容器(读取Spring配置文件) ApplicationContext context = new ClassPathXmlApplicationContext("com/doooog/spring/SpringConfig.xml"); UserDao userDao = (UserDao) context.getBean("userDao"); userDao.saveUser(new User()); } } ``` #### 5、字段注入 为有属性类实现依赖注入,在`UserServiceImpl.java`类头上添加`@Component("userService")`,对于其拥有的属性,需要用到`@Autowired`注解,意为自动注入,被`@Autowired`注解修饰的属性会默认根据属性的类型去注入`bean`,因为直接注入到属性本身,所以不需要`Getter&Setter`,并且被注入的类型的`bean`的`id`并不重要,影响注入的只有属性类型和类型的实现类数量(不能大于一,否则需要按照名称自动注入)。 ```java package com.doooog.spring.service.impl; import com.doooog.spring.dao.UserDao; import com.doooog.spring.po.User; import com.doooog.spring.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component("userService") public class UserServiceImpl implements UserService { // @Autowired 默认根据类型去注入bean @Autowired private UserDao userDao; @Override public int saveUser(User user) { return userDao.saveUser(user); } } ``` #### 6、测试有属性组件 ```java package com.doooog.spring.test; import com.doooog.spring.po.User; import com.doooog.spring.service.UserService; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Test { public static void main(String[] args) { // 加载Spring容器(读取Spring配置文件) ApplicationContext context = new ClassPathXmlApplicationContext("com/doooog/spring/SpringConfig.xml"); UserService userService = (UserService) context.getBean("userService"); userService.saveUser(new User()); } } ``` #### 7、创建控制层(字段注入) 一个完整的项目包含数据层对象(po)、数据层(dao)、业务层(service)、控制层(controller),从左往右由小及大。 创建`com.doooog.spring.controller`包,在包下新建`UserController.java`类,并将其注册为Spring组件 ```java package com.doooog.spring.controller; import com.doooog.spring.po.User; import com.doooog.spring.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component("userController") public class UserController { @Autowired private UserService userService; public int getUser(){ userService.saveUser(new User()); return 2; } } ``` **注意**:这里三个层面的方法只是为了演示三层关联,并无实际意义,方法实现方式、返回值随意,主要提现调用`userController`会自动注入`userService`会自动注入`userDao`。 #### 8、语义化注解(含义) `@Component`泛指组件,但并不能表示是什么类型、哪一层的组件,为此Spring提供了更加直观说明其组件类层的注解,作用完全与`@Component`等效: `@Service`:业务层组件 `@Controller`:控制层组件 `@Repository`:数据层组件 所以修改之前的代码,使用以上三种注解替换dao、service层的注解。 9、Bean作用范围注解 `@Scope()`注解:设置Bean的作用域,值如下表: | 作用域 | 注解值 | 说明 | 生命周期 | | ------------- | ------------- | ------------------------------------------------ | ------------------------------- | | **单例** | `singleton` | `默认`作用域,整个Spring容器中只存在一个Bean实例 | 容器启动时创建,容器关闭时销毁 | | **原型** | `prototype` | 每次获取时都创建一个新的Bean实例 | 每次获取时创建,使用后由GC回收 | | **请求** | `request` | 每个HTTP请求创建一个新的Bean实例 | 请求开始时创建,请求结束时销毁 | | **会话** | `session` | 每个HTTP会话创建一个Bean实例 | 会话开始时创建,会话结束时销毁 | | **应用** | `application` | 每个ServletContext生命周期内一个实例 | 应用启动时创建,应用关闭时销毁 | | **WebSocket** | `websocket` | 每个WebSocket会话一个实例 | WebSocket连接时创建,断开时销毁 | #### 10、使用名称自动注入(字段注入) 当同一类型接口有多个实现类时,需要为每个类的`bean`取名,在注入属性时不能使用默认类型注入,需要通过名称自动注入。 添加`@Qualifier("")`注解,字符串值为需要注入实现类的`id`。 ```java package com.doooog.spring.service.impl; import com.doooog.spring.dao.UserDao; import com.doooog.spring.po.User; import com.doooog.spring.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; @Service public class UserServiceImpl implements UserService { @Autowired // 使其根据名称来自动注入属性 @Qualifier("userDao") private UserDao userDao; @Override public int saveUser(User user) { return userDao.saveUser(user); } } ``` #### 11、构造器注入与`Setter`注入 ##### 构造器注入(推荐) 在实现类的有参构造器方法头上添加注解`@Autowired`进行注入 以`UserController.java`为例 ```java package com.doooog.spring.controller; import com.doooog.spring.po.User; import com.doooog.spring.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; @Controller("userController") public class UserController { private UserService userService; public UserController() { } // 构造器注入 @Autowired public UserController(UserService userService) { this.userService = userService; } public int getUser(){ userService.saveUser(new User()); return 2; } } ``` ##### `Setter`方法注入 在实现类相应属性的`set*()`方法头上添加注解`@Autowired`进行注入 ```java package com.doooog.spring.controller; import com.doooog.spring.po.User; import com.doooog.spring.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; @Controller("userController") public class UserController { private UserService userService; public int getUser(){ userService.saveUser(new User()); return 2; } // Setter注入 @Autowired public void setUserService(UserService userService) { this.userService = userService; } } ``` ##### 在测试类内测试即可 ```java package com.doooog.spring.test; import com.doooog.spring.controller.UserController; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Test { public static void main(String[] args) { // 加载Spring容器(读取Spring配置文件) ApplicationContext context = new UserController userController = (UserController) context.getBean("userController"); userController.getUser(); } } ``` ## 四、javaConfig形式实现DI #### 1、创建javaConfig类 在`com.doooog.spring`包下创建`AppConfig.java`类,类使用`@Configuration`注解修饰,和被`@Component`标注的类同为一个`Bean`,可以被组件扫描器扫到,类中的每个使用`@Bean`修饰的方法都相当于提供了一个Bean的定义信息。 ```java package com.doooog.spring; import com.doooog.spring.dao.UserDao; import com.doooog.spring.dao.impl.UserDaoImpl; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class AppConfig { @Bean public UserDao userDao(){ return new UserDaoImpl(); } } ``` **注意**:这里方法名为`BeanId`,返回值为`BeanClass`,返回了一个new出来的实现类。 在测试类中调用即可 ```java package com.doooog.spring.test; import com.doooog.spring.dao.UserDao; import com.doooog.spring.po.User; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Test { public static void main(String[] args) { // 加载Spring容器(读取Spring配置文件) ApplicationContext context = new ClassPathXmlApplicationContext("com/doooog/spring/SpringConfig.xml"); UserDao userDao = (UserDao) context.getBean("userDao"); userDao.saveUser(new User()); } } ``` #### 2、注入带属性的`Bean` ```java package com.doooog.spring; import com.doooog.spring.controller.UserController; import com.doooog.spring.dao.UserDao; import com.doooog.spring.dao.impl.UserDaoImpl; import com.doooog.spring.service.UserService; import com.doooog.spring.service.impl.UserServiceImpl; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class AppConfig { @Bean public UserDao userDao(){ return new UserDaoImpl(); } @Bean public UserService userService(){ UserServiceImpl service = new UserServiceImpl(); service.setUserDao(userDao()); return service; } @Bean public UserController userController(){ UserController controller = new UserController(); controller.setUserService(userService()); return controller; } } ``` **注意**:因为`Bean`带有属性,需要为其属性注入同类型的`Bean`,所以要使用到`set*()`方法,因此要new一个`实现类`并将其返回,在`set*`方法中需要传入一个`SpringBean`,所以要带括号。 在测试类内调用即可 ```java package com.doooog.spring.test; import com.doooog.spring.controller.UserController; import com.doooog.spring.po.User; import com.doooog.spring.service.UserService; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Test { public static void main(String[] args) { // 加载Spring容器(读取Spring配置文件) ApplicationContext context = new ClassPathXmlApplicationContext("com/doooog/spring/SpringConfig.xml"); // UserService userService = (UserService) context.getBean("userService"); // userService.saveUser(new User()); UserController userController = (UserController) context.getBean("userController"); userController.getUser(); } } ``` ## 五、IOC与DI的区别 #### 1、Bean Factory与ApplicationContext ##### 基本介绍 BeanFactory是Spring框架最基础的IoC容器,提供了依赖注入的核心功能。 **主要功能** - Bean实例化与生命周期管理:负责创建、配置和管理Bean实例 - 依赖注入:解决Bean之间的依赖关系 - 配置灵活性:支持多种配置方式(XML、注解等) ApplicationContext是BeanFactory的子接口,提供了更丰富的企业级功能。 **主要功能** - 继承了BeanFactory的所有功能 - 事件发布机制:支持应用事件发布和监听 - 国际化支持:MessageSource接口支持多语言 - 资源访问:统一的资源文件访问方式 - AOP集成:内置AOP代理支持 - 更方便的Bean获取:支持泛型方式获取Bean ##### 功能对比 | 特性 | BeanFactory | ApplicationContext | | :------------------------------- | :----------- | :----------------- | | Bean实例化/装配 | ✅ | ✅ | | 延迟加载(Lazy Loading) | ✅ | 可配置 | | 自动BeanPostProcessor注册 | ❌ | ✅ | | 自动BeanFactoryPostProcessor注册 | ❌ | ✅ | | 便捷的MessageSource访问 | ❌ | ✅ | | 应用事件发布 | ❌ | ✅ | | 国际化支持 | ❌ | ✅ | | 资源加载 | 基础 | 增强 | | AOP支持 | 需要额外配置 | 内置支持 | ##### 代码示例对比 ```java // BeanFactory示例 public class BeanFactoryExample { public static void main(String[] args) { Resource resource = new ClassPathResource("beans.xml"); BeanFactory factory = new XmlBeanFactory(resource); // 需要手动注册后处理器 BeanPostProcessor processor = new MyBeanPostProcessor(); ((ConfigurableBeanFactory) factory).addBeanPostProcessor(processor); MyService service = factory.getBean(MyService.class); } } // ApplicationContext示例 public class ApplicationContextExample { public static void main(String[] args) { ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); // 自动处理注解、AOP、事件等 MyService service = context.getBean(MyService.class); // 发布应用事件 context.publishEvent(new MyCustomEvent(context, "事件数据")); } } ``` ##### 生命周期管理对比 BeanFactory的生命周期管理相对简单: ```java public class BeanFactoryLifecycle { public static void main(String[] args) { BeanFactory factory = new XmlBeanFactory(new ClassPathResource("beans.xml")); // Bean在第一次调用getBean()时实例化 MyBean bean = factory.getBean(MyBean.class); } } ``` ApplicationContext提供完整的生命周期管理: ```java public class ApplicationContextLifecycle { public static void main(String[] args) { AbstractApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); // 注册关闭钩子,确保优雅关闭 context.registerShutdownHook(); // 或者手动关闭 // context.close(); } } ``` ##### 性能对比 - **启动速度**:BeanFactory启动更快(延迟加载) - **内存占用**:BeanFactory更节省内存 - **运行时性能**:ApplicationContext在预初始化后性能更好 - **功能丰富度**:ApplicationContext提供完整的企业级功能 #### 2、IOC与DI ##### 基本概念 IOC(控制反转)是一种设计原则,它将传统上由程序代码直接操控的对象控制权交给容器(框架)来管理,组件与组件之间的依赖由主动(`new`)变为被动。 DI(依赖注入)是IOC的一种具体实现方式,容器在运行期动态地将依赖关系注入到组件中。 ##### 两者关系 图示 ```java IOC(控制反转,设计原则) │ └── DI(依赖注入,具体实现) │ ├── 构造器注入 ├── Setter注入 └── 字段注入 ``` 对比 | 方面 | IOC(控制反转) | DI(依赖注入) | | :----------- | :------------------- | :--------------------- | | **概念层级** | 设计原则、架构思想 | 具体实现模式、技术手段 | | **关注点** | 谁拥有控制权 | 如何管理依赖关系 | | **关系** | 容器控制对象生命周期 | 容器注入对象依赖关系 | | **实现方式** | 包括DI、DL等多种方式 | IOC的具体实现方式之一 | ##### 总结 IOC只是将组件控制权移交出去,但没有说明组件如何获取。而DI明确说明:组件依赖Spring容器获取。 DI是IOC思想的一种具体实现。 Loading... ## 一、Spring框架简介 #### 1、Spring Framework 是什么? Spring Framework 是一个开源的、轻量级的 **Java 平台应用程序框架** 和 **反转控制 (IoC) 容器**。它由 Rod Johnson 在 2003 年创建,并发布在其著作 *Expert One-on-One J2EE Design and Development* 中。 其核心使命是:简化企业级 Java 应用程序的开发。它通过提供一种全面的编程和配置模型,解决了传统 J2EE 开发的复杂性和侵入性问题,带来了以下关键好处: 1. **轻量级与非侵入性:** Spring 应用中的对象通常不依赖于 Spring 特有的接口或类(大部分情况下),更容易重用和测试。 2. **依赖注入 (DI) / 控制反转 (IoC):** 这是 Spring 最核心的基石之一,实现组件之间的解耦。 3. **面向切面编程 (AOP):** 这是另一个核心支柱,用于处理横切关注点(如日志、事务、安全),保持业务逻辑的纯净。 4. **声明式事务管理:** 通过配置或注解方式管理事务,极大简化了事务相关代码。 5. **模块化设计:** 用户可以根据需要选择使用不同的模块。 6. **灵活的配置机制:** 支持 XML、Java 注解 (Annotation) 和 Java 代码配置方式。 7. **强大的集成能力:** 方便地与各种企业级技术集成(如持久化框架 - Hibernate, JDBC; 消息队列 - JMS, AMQP; Web Services; 安全管理框架等)。 8. **简化测试:** 由于 DI 的存在,单元测试和集成测试变得更容易实现(特别是配合 Spring Test 模块)。 9. **活跃的社区和生态系统:** 拥有庞大的用户群体、丰富的文档和教程,以及基于 Spring 构建的广泛生态系统(如 Spring Boot, Spring Cloud, Spring Data, Spring Security 等)。 #### 2、Spring Framework 体系架构 (模块组成) Spring Framework 采用分层架构,由多个精心设计的模块组成。这些模块可以大致分为以下几个核心层: 1. **核心容器 (Core Container):** - `spring-core`, `spring-beans`: 提供框架的基础功能,包括 **IoC (控制反转)** 和 **DI (依赖注入)** 特性的实现。`BeanFactory` 是其核心接口,代表了一个基本的 IoC 容器。 - `spring-context`: 建立在 `core` 和 `beans` 之上,提供了一种更丰富的、面向企业级特性的上下文(`ApplicationContext`)。它增加了对国际化(i18n)、事件传播、资源加载等功能。`ApplicationContext` 是 `BeanFactory` 的一个功能更全的超集,是实际应用中使用最多的容器接口。 - `spring-expression` (SpEL): 提供强大的 **Spring 表达式语言**,用于在运行时查询和操作对象图。 2. **AOP 和 Instrumentation:** - `spring-aop`: 提供 **面向切面编程 (AOP)** 的支持,允许实现横切关注点(如日志、事务管理、安全等)与核心业务逻辑的分离。内置基于代理的 AOP 框架。 - `spring-aspects`: 提供与 AspectJ 框架的集成。 - *(较少直接使用):* `spring-instrument`, `spring-instrument-tomcat`: 提供类加载器实现和服务器特定的启动器,用于 JVM 级别的监视和管理(如使用 Java Agent)。 3. **数据访问/集成 (Data Access/Integration):** - `spring-jdbc`: 提供了一个 **JDBC 抽象层**,大大简化了传统 JDBC API 繁琐、易错的操作(如连接管理、异常处理)。 - `spring-orm`: 提供与流行的 **对象关系映射 (ORM)** 框架(如 Hibernate, JPA, JDO)的集成层。 - `spring-oxm`: 提供 **对象-XML 映射 (Object/XML Mapping - OXM)** 的支持(如 JAXB, Castor, XStream)。 - `spring-jms`: 提供用于 **消息传递 (Messaging)** 的抽象层(主要是 JMS)。 - `spring-tx`: 提供强大且灵活的 **声明式事务管理** 能力,可以管理多种数据源(JDBC, JPA, JTA等)的事务。 4. **Web:** - `spring-web`: 提供基本的 Web 功能,如文件上传、Servlet Listener 初始化 IoC 容器等。包含一个 HTTP 客户端 (`RestTemplate`) 和集成 MVC 框架所需的核心部分。 - `spring-webmvc`: 也称为 **Spring MVC**,是一个功能齐全的 **模型-视图-控制器 (Model-View-Controller)** 实现,用于构建 Web 应用程序。非常灵活且易于配置。 - `spring-webflux`: 在 Spring 5 引入,是一个非阻塞的、函数式、**反应式编程模型**(基于 Project Reactor)的 Web 框架,用于构建高并发、事件驱动的服务。 - `spring-websocket`: 提供 WebSocket 和 STOMP 协议支持,用于构建实时 Web 应用。 5. **测试:** - `spring-test`: 提供与 JUnit 或 TestNG 等测试框架的集成,支持使用依赖注入编写单元测试和集成测试,并提供了用于在测试中加载 Spring 上下文、模拟对象等的工具类。 #### 3、Spring Framework 的两大核心:DI 和 AOP ##### 1. 依赖注入 (Dependency Injection - DI) / 控制反转 (Inversion of Control - IoC) - **什么是依赖注入**:指工程中需要的组件无需自己创建,而是依赖外部环境注入。 - **核心理念:** **控制权反转 (IoC)** 是一种设计原则,它将对象的创建、配置和依赖关系的管理职责 **转交给一个外部实体(容器)**,而不是由对象自身主动去获取或创建它所依赖的对象。**依赖注入 (DI)** 是实现 IoC 原则的主要技术手段。 - **解决的问题:** 传统开发中,对象自己控制其依赖(`new` 关键字),导致代码耦合度高(硬编码依赖)、难以测试(依赖具体实现)、灵活性差(更换依赖困难)。 - **Spring DI 的工作原理:** 1. 组件(通常称为 **Bean**)在其定义(通过 XML、注解或 Java Config)中描述它**需要什么依赖**(接口或类)。 2. **Spring IoC 容器 (`ApplicationContext`)** 负责: - 根据配置创建 Bean 的实例(或管理其生命周期)。 - 解析 Bean 之间的依赖关系。 - 在创建 Bean**时**,将其所需的依赖项**自动注入**进来(通过构造器、Setter 方法或直接字段注入)。 - **关键点:** - **解耦:** Bean 不需要知道依赖来自哪里、如何创建。它只关心接口。 - **面向接口编程:** 更容易替换依赖的实现(例如,将 MySQL DAO 替换为 PostgreSQL DAO,只需改配置,业务代码不变)。 - **可测试性:** 在测试时,可以轻松地注入模拟对象 (`Mock`) 代替真实依赖。 - **配置集中化:** 依赖关系在配置文件或注解中清晰可见,易于管理。 - **注入方式:** - **构造器注入:** 通过构造器参数注入依赖。推荐方式(强制依赖、不变依赖)。 - **Setter 注入:** 通过 Setter 方法注入依赖。(可选依赖)。 - **字段注入:** 通过 `@Autowired` 等注解直接注入到字段。(通常不推荐,破坏了封装,使测试稍复杂)。 ##### 2. 面向切面编程 (Aspect-Oriented Programming - AOP) - **核心理念:** 软件系统由多个**关注点 (Concern)** 组成。 - **核心关注点:** 实现业务逻辑(如用户注册、订单创建)。 - **横切关注点:** 影响应用程序多个模块的功能(如日志记录、事务管理、安全性、性能监控、错误处理)。这些关注点散布在核心代码中,导致代码重复(散弹式修改)和核心逻辑不清晰(代码纠缠)。 - **解决的问题:** 将横切关注点从核心业务逻辑中分离出来,实现关注点分离,提高代码的**模块化、可重用性和可维护性**。 - **AOP 的关键概念:** - **切面:** **一个模块化的类**,它封装了与横切关注点相关的功能(包含通知和切点)。例如,一个 `LoggingAspect` 切面包含所有的日志记录逻辑。 - **通知:** 切面在**特定的连接点执行的动作**。常见的通知类型: - **前置通知 (`@Before`):** 在目标方法执行前执行。 - **后置通知 (`@AfterReturning`):** 在目标方法**成功**执行后执行。 - **异常通知 (`@AfterThrowing`):** 在目标方法**抛出异常**后执行。 - **最终通知 (`@After`):** 在目标方法执行后执行(不管成功还是异常)。 - **环绕通知 (`@Around`):** **包裹**目标方法执行的通知,可以在方法执行前后自定义行为,甚至可以控制方法是否执行或修改返回值。最强大。 - **连接点:** 应用程序执行过程中**可以插入通知的点**。在 Spring AOP 中,这通常指**方法执行**(Spring AOP 基于代理,主要拦截方法调用)。 - **切点:** 一个**表达式**,用于**匹配连接点**。通知会与一个切点表达式关联,并决定在**何处**执行该通知(匹配哪些方法)。Spring 使用 AspectJ 的切点表达式语言 (AspectJ Pointcut Expression Language)。 - **目标对象:** 被一个或多个切面通知的对象。也就是包含核心业务逻辑的对象。 - **代理:** AOP 框架为了实现切面功能而**为目标对象创建的对象**。在 Spring AOP 中,代理可以是 JDK 动态代理(实现接口)或 CGLIB 代理(子类化)。调用者实际调用的是代理对象,代理对象在适当的时候应用通知并最终调用目标对象的方法。 - **Spring AOP 特点:** - 主要基于 **代理机制** (Proxy)。 - 主要用于**方法执行**连接点。 - 对目标类**非侵入性**。 - 强大地集成了 Spring IoC 容器(可以注入 Bean 到切面)。 - 方便地与 AspectJ 注解配合使用(`@Aspect`, `@Pointcut`, `@Before` 等)。 ## 二、使用XML实现DI 使用Spring来重构Service层和DAO层 #### 1、新建项目 项目名称为`springTest`,构建系统为`Maven` #### 2、添加依赖 在项目的`pom.xml`配置中添加`<dependencies>`、`<dependency>` ```xml <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>6.2.7</version> </dependency> </dependencies> ``` #### 3、创建类包 为了提升效率,此处只是模拟DAO层,模拟数据库查询返回一个值。 创建`com.doooog.spring.dao`、`com.doooog.spring.test`、`com.doooog.spring.po`、`com.doooog.service`包 在`po`包内创建`User.java`持久层对象类 ```java package com.doooog.spring.po; public class User { private Integer uid; private String uname; private Integer age; public User(Integer uid, String uname, Integer age) { this.uid = uid; this.uname = uname; this.age = age; } public User() { } @Override public String toString() { return "User{" + "uid=" + uid + ", uname='" + uname + '\'' + ", age=" + age + '}'; } public Integer getUid() { return uid; } public void setUid(Integer uid) { this.uid = uid; } public String getUname() { return uname; } public void setUname(String uname) { this.uname = uname; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } } ``` 在`dao`包内创建`UserDao.java`接口 ```java package com.doooog.spring.dao; public interface UserDao { int saveUser(User user); } ``` 创建`com.doooog.spring.dao.impl`包 在`impl`包内创建`UserDaoImpl.java`实现类 ```java package com.doooog.spring.dao.impl; import com.doooog.spring.dao.UserDao; public class UserDaoImpl implements UserDao { @Override public int saveUser(User user) { System.out.println("数据库操作"); return 1; } } ``` #### 4、创建Spring框架配置 在`rescources`资源目录下创建目录`com/doooog/spring`,在`spring`目录下创建配置文件`SpringConfig.xml` ```xml <?xml version="1.0" encoding="utf-8" ?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd"> <!-- 相当于UserDaoImpl userDao = new UserDaoImpl() --> <bean id="userDaoImpl" class="com.doooog.spring.dao.impl.UserDaoImpl"/> </beans> ``` 其中`<bean>`相当于Java的对象。 #### 5、加载Spring容器 在`test`包内新建测试类 ```java package com.doooog.spring.test; import com.doooog.spring.dao.UserDao; import com.doooog.spring.po.User; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Test { public static void main(String[] args) { // 加载Spring容器(读取Spring配置文件) ApplicationContext context = new ClassPathXmlApplicationContext("com/doooog/spring/SpringConfig.xml"); // getBean()返回的是Object类型,需要强转 UserDao userDao = (UserDao) context.getBean("userDaoImpl"); userDao.saveUser(new User()); } } ``` **注意**:`ApplicationContext` 接口是Spring容器的核心接口,负责管理Bean的生命周期和依赖注入,`ClassPathXmlApplicationContext` 类可以从类路径(classpath)加载XML配置文件的ApplicationContext实现类。 6、封装Service层 在`Service`包下创建`UserService.java`接口 ```java package com.doooog.spring.service; import com.doooog.spring.po.User; public interface UserService { int saveUser(User user); } ``` 新建`com.doooog.spring.service.impl`包,在`impl`包下创建`UserServiceImpl.java`实现类 ```java package com.doooog.spring.service.impl; import com.doooog.spring.dao.UserDao; import com.doooog.spring.po.User; import com.doooog.spring.service.UserService; public class UserServiceImpl implements UserService { private UserDao userDao; @Override public int saveUser(User user) { return userDao.saveUser(user); } public UserDao getUserDao() { return userDao; } public void setUserDao(UserDao userDao) { this.userDao = userDao; } } ``` 因为需要让Spring框架将`UserDao`自动注入到`UserService`里,所以要添加私有属性`UserDao userDao`用来存放`UserDao`对象 在`SpringConfig.xml`中添加`<bean>` ```xml <bean id="userDaoService" class="com.doooog.spring.service.impl.UserServiceImpl"> <property name="userDao" ref="userDaoImpl"/> </bean> ``` 因为封装`UserServiceImpl`包时添加了属性`userDao`,所以在此添加`<property>`标签去告知Spring框架要注入的属性和对象。 在测试类内调用即可 ```java // 加载Spring容器(读取Spring配置文件) ApplicationContext context = new ClassPathXmlApplicationContext("com/doooog/spring/SpringConfig.xml"); UserService userService = (UserService) context.getBean("userDaoService"); userService.saveUser(new User()); ``` 很明显在代码中没有显示的声明`UserDao`的对象,但在`UserService`对象中可以通过方法`saveUser`调用其`UserDao`属性。 ## 三、使用注解实现DI 注解(Annotation)的主要作用是**为代码提供元数据**,这些元数据可以被编译器、开发工具或运行时环境读取和使用。 其他注解见:[Java常用注解表](https://www.soki.me/archives/263.html) #### 1、为无属性类实现依赖注入 在需要配置为`bean`的类的类头上方添加`@Component`表示其是一个Spring组件。 在`UserDaoImpl.java`类头上添加`@Component("userDao")` ```java package com.doooog.spring.dao.impl; import com.doooog.spring.dao.UserDao; import com.doooog.spring.po.User; import org.springframework.stereotype.Component; @Component("userDao") public class UserDaoImpl implements UserDao { @Override public int saveUser(User user) { System.out.println("数据库操作"); return 1; } } ``` **注意**:`@Component`注解可以有`String`类型参数,意为Spring组件名,与`xml`中`<bean>`定义的`id`意思相同。 #### 2、注释掉`<bean>`标签 修改`SpringConfig.xml`文件,将第二步添加的所有`<bean>`标签注释掉 ```java <?xml version="1.0" encoding="utf-8" ?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" 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.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd"> <!-- 相当于UserDaoImpl userDao = new UserDaoImpl() --> <!-- <bean id="userDaoImpl" class="com.doooog.spring.dao.impl.UserDaoImpl"/>--> <!-- <bean id="userDaoService" class="com.doooog.spring.service.impl.UserServiceImpl">--> <!-- <property name="userDao" ref="userDaoImpl"/>--> <!-- </bean>--> </beans> ``` #### 3、添加组件扫描器 在`SpringConfig.xml`文件中,添加`<context:component-scan>`扫描器,扫描指定包下带有`@Component`注解的类,并将其变为`bean`组件纳入Spring容器。 ```xml <context:component-scan base-package="com.doooog.spring"/> ``` **注意**:属性`base-package`为需要扫描的包名,包名需要全名,扫描器会扫描包下所有的类包括子包中的类,所以一般将最大的包名写进去即可,若有需要分开写,则可以用`,`分割。 #### 4、测试无属性组件 ```java package com.doooog.spring.test; import com.doooog.spring.dao.UserDao; import com.doooog.spring.po.User; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Test { public static void main(String[] args) { // 加载Spring容器(读取Spring配置文件) ApplicationContext context = new ClassPathXmlApplicationContext("com/doooog/spring/SpringConfig.xml"); UserDao userDao = (UserDao) context.getBean("userDao"); userDao.saveUser(new User()); } } ``` #### 5、字段注入 为有属性类实现依赖注入,在`UserServiceImpl.java`类头上添加`@Component("userService")`,对于其拥有的属性,需要用到`@Autowired`注解,意为自动注入,被`@Autowired`注解修饰的属性会默认根据属性的类型去注入`bean`,因为直接注入到属性本身,所以不需要`Getter&Setter`,并且被注入的类型的`bean`的`id`并不重要,影响注入的只有属性类型和类型的实现类数量(不能大于一,否则需要按照名称自动注入)。 ```java package com.doooog.spring.service.impl; import com.doooog.spring.dao.UserDao; import com.doooog.spring.po.User; import com.doooog.spring.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component("userService") public class UserServiceImpl implements UserService { // @Autowired 默认根据类型去注入bean @Autowired private UserDao userDao; @Override public int saveUser(User user) { return userDao.saveUser(user); } } ``` #### 6、测试有属性组件 ```java package com.doooog.spring.test; import com.doooog.spring.po.User; import com.doooog.spring.service.UserService; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Test { public static void main(String[] args) { // 加载Spring容器(读取Spring配置文件) ApplicationContext context = new ClassPathXmlApplicationContext("com/doooog/spring/SpringConfig.xml"); UserService userService = (UserService) context.getBean("userService"); userService.saveUser(new User()); } } ``` #### 7、创建控制层(字段注入) 一个完整的项目包含数据层对象(po)、数据层(dao)、业务层(service)、控制层(controller),从左往右由小及大。 创建`com.doooog.spring.controller`包,在包下新建`UserController.java`类,并将其注册为Spring组件 ```java package com.doooog.spring.controller; import com.doooog.spring.po.User; import com.doooog.spring.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component("userController") public class UserController { @Autowired private UserService userService; public int getUser(){ userService.saveUser(new User()); return 2; } } ``` **注意**:这里三个层面的方法只是为了演示三层关联,并无实际意义,方法实现方式、返回值随意,主要提现调用`userController`会自动注入`userService`会自动注入`userDao`。 #### 8、语义化注解(含义) `@Component`泛指组件,但并不能表示是什么类型、哪一层的组件,为此Spring提供了更加直观说明其组件类层的注解,作用完全与`@Component`等效: `@Service`:业务层组件 `@Controller`:控制层组件 `@Repository`:数据层组件 所以修改之前的代码,使用以上三种注解替换dao、service层的注解。 9、Bean作用范围注解 `@Scope()`注解:设置Bean的作用域,值如下表: | 作用域 | 注解值 | 说明 | 生命周期 | | ------------- | ------------- | ------------------------------------------------ | ------------------------------- | | **单例** | `singleton` | `默认`作用域,整个Spring容器中只存在一个Bean实例 | 容器启动时创建,容器关闭时销毁 | | **原型** | `prototype` | 每次获取时都创建一个新的Bean实例 | 每次获取时创建,使用后由GC回收 | | **请求** | `request` | 每个HTTP请求创建一个新的Bean实例 | 请求开始时创建,请求结束时销毁 | | **会话** | `session` | 每个HTTP会话创建一个Bean实例 | 会话开始时创建,会话结束时销毁 | | **应用** | `application` | 每个ServletContext生命周期内一个实例 | 应用启动时创建,应用关闭时销毁 | | **WebSocket** | `websocket` | 每个WebSocket会话一个实例 | WebSocket连接时创建,断开时销毁 | #### 10、使用名称自动注入(字段注入) 当同一类型接口有多个实现类时,需要为每个类的`bean`取名,在注入属性时不能使用默认类型注入,需要通过名称自动注入。 添加`@Qualifier("")`注解,字符串值为需要注入实现类的`id`。 ```java package com.doooog.spring.service.impl; import com.doooog.spring.dao.UserDao; import com.doooog.spring.po.User; import com.doooog.spring.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; @Service public class UserServiceImpl implements UserService { @Autowired // 使其根据名称来自动注入属性 @Qualifier("userDao") private UserDao userDao; @Override public int saveUser(User user) { return userDao.saveUser(user); } } ``` #### 11、构造器注入与`Setter`注入 ##### 构造器注入(推荐) 在实现类的有参构造器方法头上添加注解`@Autowired`进行注入 以`UserController.java`为例 ```java package com.doooog.spring.controller; import com.doooog.spring.po.User; import com.doooog.spring.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; @Controller("userController") public class UserController { private UserService userService; public UserController() { } // 构造器注入 @Autowired public UserController(UserService userService) { this.userService = userService; } public int getUser(){ userService.saveUser(new User()); return 2; } } ``` ##### `Setter`方法注入 在实现类相应属性的`set*()`方法头上添加注解`@Autowired`进行注入 ```java package com.doooog.spring.controller; import com.doooog.spring.po.User; import com.doooog.spring.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; @Controller("userController") public class UserController { private UserService userService; public int getUser(){ userService.saveUser(new User()); return 2; } // Setter注入 @Autowired public void setUserService(UserService userService) { this.userService = userService; } } ``` ##### 在测试类内测试即可 ```java package com.doooog.spring.test; import com.doooog.spring.controller.UserController; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Test { public static void main(String[] args) { // 加载Spring容器(读取Spring配置文件) ApplicationContext context = new UserController userController = (UserController) context.getBean("userController"); userController.getUser(); } } ``` ## 四、javaConfig形式实现DI #### 1、创建javaConfig类 在`com.doooog.spring`包下创建`AppConfig.java`类,类使用`@Configuration`注解修饰,和被`@Component`标注的类同为一个`Bean`,可以被组件扫描器扫到,类中的每个使用`@Bean`修饰的方法都相当于提供了一个Bean的定义信息。 ```java package com.doooog.spring; import com.doooog.spring.dao.UserDao; import com.doooog.spring.dao.impl.UserDaoImpl; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class AppConfig { @Bean public UserDao userDao(){ return new UserDaoImpl(); } } ``` **注意**:这里方法名为`BeanId`,返回值为`BeanClass`,返回了一个new出来的实现类。 在测试类中调用即可 ```java package com.doooog.spring.test; import com.doooog.spring.dao.UserDao; import com.doooog.spring.po.User; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Test { public static void main(String[] args) { // 加载Spring容器(读取Spring配置文件) ApplicationContext context = new ClassPathXmlApplicationContext("com/doooog/spring/SpringConfig.xml"); UserDao userDao = (UserDao) context.getBean("userDao"); userDao.saveUser(new User()); } } ``` #### 2、注入带属性的`Bean` ```java package com.doooog.spring; import com.doooog.spring.controller.UserController; import com.doooog.spring.dao.UserDao; import com.doooog.spring.dao.impl.UserDaoImpl; import com.doooog.spring.service.UserService; import com.doooog.spring.service.impl.UserServiceImpl; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class AppConfig { @Bean public UserDao userDao(){ return new UserDaoImpl(); } @Bean public UserService userService(){ UserServiceImpl service = new UserServiceImpl(); service.setUserDao(userDao()); return service; } @Bean public UserController userController(){ UserController controller = new UserController(); controller.setUserService(userService()); return controller; } } ``` **注意**:因为`Bean`带有属性,需要为其属性注入同类型的`Bean`,所以要使用到`set*()`方法,因此要new一个`实现类`并将其返回,在`set*`方法中需要传入一个`SpringBean`,所以要带括号。 在测试类内调用即可 ```java package com.doooog.spring.test; import com.doooog.spring.controller.UserController; import com.doooog.spring.po.User; import com.doooog.spring.service.UserService; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Test { public static void main(String[] args) { // 加载Spring容器(读取Spring配置文件) ApplicationContext context = new ClassPathXmlApplicationContext("com/doooog/spring/SpringConfig.xml"); // UserService userService = (UserService) context.getBean("userService"); // userService.saveUser(new User()); UserController userController = (UserController) context.getBean("userController"); userController.getUser(); } } ``` ## 五、IOC与DI的区别 #### 1、Bean Factory与ApplicationContext ##### 基本介绍 BeanFactory是Spring框架最基础的IoC容器,提供了依赖注入的核心功能。 **主要功能** - Bean实例化与生命周期管理:负责创建、配置和管理Bean实例 - 依赖注入:解决Bean之间的依赖关系 - 配置灵活性:支持多种配置方式(XML、注解等) ApplicationContext是BeanFactory的子接口,提供了更丰富的企业级功能。 **主要功能** - 继承了BeanFactory的所有功能 - 事件发布机制:支持应用事件发布和监听 - 国际化支持:MessageSource接口支持多语言 - 资源访问:统一的资源文件访问方式 - AOP集成:内置AOP代理支持 - 更方便的Bean获取:支持泛型方式获取Bean ##### 功能对比 | 特性 | BeanFactory | ApplicationContext | | :------------------------------- | :----------- | :----------------- | | Bean实例化/装配 | ✅ | ✅ | | 延迟加载(Lazy Loading) | ✅ | 可配置 | | 自动BeanPostProcessor注册 | ❌ | ✅ | | 自动BeanFactoryPostProcessor注册 | ❌ | ✅ | | 便捷的MessageSource访问 | ❌ | ✅ | | 应用事件发布 | ❌ | ✅ | | 国际化支持 | ❌ | ✅ | | 资源加载 | 基础 | 增强 | | AOP支持 | 需要额外配置 | 内置支持 | ##### 代码示例对比 ```java // BeanFactory示例 public class BeanFactoryExample { public static void main(String[] args) { Resource resource = new ClassPathResource("beans.xml"); BeanFactory factory = new XmlBeanFactory(resource); // 需要手动注册后处理器 BeanPostProcessor processor = new MyBeanPostProcessor(); ((ConfigurableBeanFactory) factory).addBeanPostProcessor(processor); MyService service = factory.getBean(MyService.class); } } // ApplicationContext示例 public class ApplicationContextExample { public static void main(String[] args) { ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); // 自动处理注解、AOP、事件等 MyService service = context.getBean(MyService.class); // 发布应用事件 context.publishEvent(new MyCustomEvent(context, "事件数据")); } } ``` ##### 生命周期管理对比 BeanFactory的生命周期管理相对简单: ```java public class BeanFactoryLifecycle { public static void main(String[] args) { BeanFactory factory = new XmlBeanFactory(new ClassPathResource("beans.xml")); // Bean在第一次调用getBean()时实例化 MyBean bean = factory.getBean(MyBean.class); } } ``` ApplicationContext提供完整的生命周期管理: ```java public class ApplicationContextLifecycle { public static void main(String[] args) { AbstractApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); // 注册关闭钩子,确保优雅关闭 context.registerShutdownHook(); // 或者手动关闭 // context.close(); } } ``` ##### 性能对比 - **启动速度**:BeanFactory启动更快(延迟加载) - **内存占用**:BeanFactory更节省内存 - **运行时性能**:ApplicationContext在预初始化后性能更好 - **功能丰富度**:ApplicationContext提供完整的企业级功能 #### 2、IOC与DI ##### 基本概念 IOC(控制反转)是一种设计原则,它将传统上由程序代码直接操控的对象控制权交给容器(框架)来管理,组件与组件之间的依赖由主动(`new`)变为被动。 DI(依赖注入)是IOC的一种具体实现方式,容器在运行期动态地将依赖关系注入到组件中。 ##### 两者关系 图示 ```java IOC(控制反转,设计原则) │ └── DI(依赖注入,具体实现) │ ├── 构造器注入 ├── Setter注入 └── 字段注入 ``` 对比 | 方面 | IOC(控制反转) | DI(依赖注入) | | :----------- | :------------------- | :--------------------- | | **概念层级** | 设计原则、架构思想 | 具体实现模式、技术手段 | | **关注点** | 谁拥有控制权 | 如何管理依赖关系 | | **关系** | 容器控制对象生命周期 | 容器注入对象依赖关系 | | **实现方式** | 包括DI、DL等多种方式 | IOC的具体实现方式之一 | ##### 总结 IOC只是将组件控制权移交出去,但没有说明组件如何获取。而DI明确说明:组件依赖Spring容器获取。 DI是IOC思想的一种具体实现。 最后修改:2025 年 10 月 16 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 👆请简单粗暴地爱我。