1.Spring IOC 与依赖注入课程笔记
课程简介
本节课介绍了 Spring 框架中的核心概念——IOC(控制反转)和 DI(依赖注入)。Spring 是 Java 生态中最重要的框架之一,几乎所有的 Java 项目开发都会使用它。理解 Spring 的 IOC 和 DI 是进入实际开发的关键。
课程重点
- Spring 的快速入门:理解 Spring 框架与传统开发方式的区别。
- Spring IOC 容器的配置:学习通过 XML 配置 Spring IOC 容器。
- 对象实例化与依赖注入:理解如何实例化对象并实现依赖注入。
- Spring 单元测试:如何在 Spring 中进行单元测试。
什么是 IOC(控制反转)
- 定义:IOC(Inversion of Control,控制反转)是一种软件设计理念,它将对象的控制权交给第三方进行管理。
- 现实案例:举例说明生活中的 IOC 控制反转,例如水果摊老板帮你挑选合适的苹果,这就是一种控制反转的应用场景。消费者不再直接控制对象的创建,而是通过代理人来获取对象。
核心概念
- 传统开发中的问题:消费者必须掌握每个对象的细节,并手动创建和管理对象。这增加了耦合度,导致代码复杂且不灵活。
- 引入代理人角色(IOC 容器):引入 IOC 后,对象的创建和管理由一个中间角色(如水果摊老板)负责。消费者只需通过这个中间人来获取对象。
- 容器管理对象:在 Spring 中,IOC 容器相当于一个“冷冻仓库”,负责存储和管理对象。对象的创建、销毁等生命周期管理都交由容器完成。
核心理念
- 解耦:IOC 的目的是降低对象之间的耦合度,消费者与对象之间通过容器进行间接交互。
- 对象灵活性:通过引入代理人,可以让对象的创建和管理变得更加灵活,消费者无需关心对象的创建细节。
什么是 DI(依赖注入)
- 定义:DI(Dependency Injection,依赖注入)是具体实现 IOC 的技术手段。在程序运行时,将对象的依赖关系注入到对象中。
- DI 与 IOC 的关系:IOC 是一种设计理念,而 DI 是实现这种理念的具体技术。DI 的本质是通过反射技术动态创建和设置对象的依赖关系。
- Java 中的 DI 实现:在 Java 中,DI 是通过反射机制实现的,反射可以在运行时动态创建对象、设置属性等。
DI 的应用
- 反射技术:DI 在 Java 中的底层实现依赖于反射技术,利用反射动态创建对象、设置属性并调用方法。
- 跨语言支持:DI 是一种普遍的技术,不仅限于 Java,在其他语言(如 Python、.NET)中也有类似的实现技术。
IOC 与 DI 的区别与联系
- IOC:宏观上的设计理念,强调控制权的反转。
- DI:具体的实现手段,在代码层面实现 IOC 的设计理念。
- Java 实现:在 Java 中,依赖注入的实现是通过反射技术。
课程总结
- IOC 的核心理念:将对象的创建权从消费者转移到代理人(IOC 容器),降低了对象之间的耦合度。
- 容器管理对象:IOC 容器(如 Spring 容器)负责管理所有对象的创建、销毁和生命周期管理。
- DI 的实现:DI 是 IOC 的具体实现手段,在 Java 中通过反射技术动态管理对象的依赖关系。
- Spring 框架中的应用:Spring 框架通过 IOC 和 DI 实现了灵活的对象管理和依赖注入,极大简化了开发工作。
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--bean标签默认通过默认构造方法创建对象-->
<bean id="apple1" class="com.imooc.spring.ioc.entity.Apple">
</bean>
<!--使用带参构造方法实例化对象-->
<bean name="apple2" class="com.imooc.spring.ioc.entity.Apple">
<constructor-arg name="title" value="红富士"/>
<constructor-arg name="color" value="红色"/>
<constructor-arg name="origin" value="欧洲"/>
<constructor-arg name="price" value="19.8"/>
</bean>
<bean id="apple3" class="com.imooc.spring.ioc.entity.Apple">
<constructor-arg index="0" value="红富士"/>
<constructor-arg index="1" value="欧洲"/>
<constructor-arg index="2" value="红色"/>
<constructor-arg index="3" value="19.8"/>
</bean>
<!--利用静态工厂获取对象-->
<bean id="apple4" class="com.imooc.spring.ioc.factory.AppleStaticFactory"
factory-method="createSweetApple"/>
<!--利用工厂实例方法获取对象-->
<bean id="factoryInstance" class="com.imooc.spring.ioc.factory.AppleFactoryInstance"/>
<bean id="apple5" factory-bean="factoryInstance" factory-method="createSweetApple"/>
</beans>
package com.imooc.spring.ioc;
import com.imooc.spring.ioc.entity.Apple;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringApplication {
public static void main(String[] args) {
String[] configLocations = new String[]{"classpath:applicationContext.xml","classpath:applicationContext-1.xml"};
//初始化IoC容器并实例化对象
ApplicationContext context = new ClassPathXmlApplicationContext(configLocations);
/*Apple apple4 = context.getBean("apple4", Apple.class);
System.out.println(apple4.getTitle());
Apple apple3 = (Apple)context.getBean("apple3");
System.out.println(apple3.getTitle());*/
Apple apple2 = context.getBean("apple2", Apple.class);
System.out.println(apple2.getTitle());
Apple apple7 = context.getBean("apple7", Apple.class);
System.out.println(apple7.getTitle());
Apple apple = context.getBean("com.imooc.spring.ioc.entity.Apple", Apple.class);
System.out.println(apple.getTitle());
}
}
2.Spring IOC 容器中的依赖注入
2. 1. 依赖注入的概念
依赖注入是将对象之间的依赖关系通过Spring IOC容器动态注入,而不是通过代码中的 new
来创建依赖对象。
依赖注入的两种形式
- 基于Setter方法注入:通过Setter方法为对象的属性注入依赖。
- 基于构造方法注入:通过构造方法为对象注入依赖。
2.2. Setter方法注入对象
2.1 静态数值的注入
-
使用
property
标签进行属性的静态值注入。 -
property
标签的两个属性:name
:表示类中属性的名称。value
:表示属性的值。
例如,为
apple
对象的title
属性赋值为sweet apple
:<bean id="sweetApple" class="com.example.Apple"> <property name="title" value="红富士" /> <property name="color" value="红色" /> <property name="origin" value="欧洲" /> </bean>
-
注意:静态数值也能作为对象注入,因为在Java中,
String
和基本类型都有对应的包装类,可以作为对象处理。
2.2 测试Setter方法注入
在测试时,可以通过设置断点或在Setter方法中打印调试信息,验证Spring通过反射调用 set
方法为属性赋值。
例如:
public void setTitle(String title) {
System.out.println("Title属性设置为:" + title);
this.title = title;
}
3. Setter方法注入对象依赖
除了静态数值的注入,还可以通过Setter方法注入其他对象。
3.1 引用对象的注入
使用 ref
属性来注入已在IOC容器中定义的Bean。
例如,Lily喜欢吃甜苹果,Lily
对象依赖于 sweetApple
对象:
<bean id="lily" class="com.example.Child">
<property name="name" value="Lily" />
<property name="apple" ref="sweetApple" />
</bean>
3.2 测试对象之间的依赖注入
在 Child
类的 setApple
方法中打印调试信息,观察依赖注入的过程:
package com.imooc.spring.ioc;
import com.imooc.spring.ioc.entity.Apple;
import com.imooc.spring.ioc.entity.Child;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringApplication {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
Apple sweetApple = context.getBean("sweetApple", Apple.class);
// System.out.println(sweetApple.getTitle());
Child andy = context.getBean("andy", Child.class);
andy.eat();
}
}
通过Spring IOC实现对象解耦
1. 案例背景
通过一个书店应用案例,展示如何利用Spring IOC容器实现对象之间的解耦,进而在团队开发中实现成员之间的解耦。
2.Spring配置文件
创建两个不同的Spring配置文件:
- application.context-service.xml:用于保存所有的Service类(业务逻辑)。
- application.context-dao.xml:用于保存所有的DAO类(数据访问类)。
3. DAO层的实现
3.1 创建DAO接口
public interface BookDao {
void insert();
}
3.2 实现DAO接口
public class BookDaoImpl implements BookDao {
@Override
public void insert() {
System.out.println("向mysql表插入一条数据");
}
}
3.3 DAO XML配置
<bean id="bookDao" class="com.imock.spring.ioc.dao.impl.BookDaoImpl" />
4. Service层的实现
4.1 创建Service类
public class BookService {
private BookDao bookDao;
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
public void purchase() {
System.out.println("正在执行图书采购业务");
bookDao.insert();
}
}
4.2 Service XML配置
<bean id="bookService" class="com.imock.spring.ioc.service.BookService">
<property name="bookDao" ref="bookDao" />
</bean>
5. IOC容器配置与应用程序入口
5.1 创建应用程序入口
public class BookshopApplication {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:application.context-*.xml");
BookService bookService = (BookService) context.getBean("bookService");
bookService.purchase();
}
}
5.2 运行结果
运行后输出:
正在执行图书采购业务
向mysql表插入一条数据
6. 通过Spring IOC实现对象解耦
通过将DAO和Service分离管理,每个人可以负责自己的模块,互不干扰。利用Spring IOC容器自动注入依赖,实现了DAO和Service的解耦。
7. 案例扩展:数据库迁移
7.1 实现新的DAO类(Oracle数据库)
public class BookDaoOracleImpl implements BookDao {
@Override
public void insert() {
System.out.println("向oracle数据库的book表插入一条数据");
}
}
7.2 替换DAO配置
在 application.context-dao.xml
中将实现类更换为 BookDaoOracleImpl
:
<bean id="bookDao" class="com.imock.spring.ioc.dao.impl.BookDaoOracleImpl" />
7.3 运行效果
运行程序时,无需修改Service层代码,程序会自动将数据插入到Oracle数据库中,输出:
正在执行图书采购业务
向oracle数据库的book表插入一条数据
8. 结论
通过Spring IOC容器实现对象的解耦,使得DAO和Service的开发人员可以各司其职,互不干扰。当系统需求变化时,如数据库从MySQL迁移到Oracle,只需修改DAO层的实现,Service层无需改动,极大地提升了系统的可维护性和扩展性。这种解耦对于大型软件工程的团队协作具有重要意义,能大幅度节约时间成本。
Spring IOC中的集合注入
在前面的课程中,我们学习了如何基于IOC容器在运行时动态注入单个对象。本节将介绍如何使用IOC容器注入集合类型的对象,如 List
、Set
、Map
和 Properties
。
1. 注入List集合
关键点:
- List 属性类型应该是
List
。 - IOC容器可以通过
list
标签来为 List 设置数据。 - 有两种方式为 List 属性注入数据:
- 静态数值:使用
value
标签。 - 引用其他对象:使用
ref
引用相应的 Bean。
- 静态数值:使用
示例:
<bean id="myBean" class="com.example.MyClass">
<property name="myList">
<list>
<value>Value1</value>
<ref bean="someOtherBean"/>
</list>
</property>
</bean>
2. 注入Set集合
关键点:
- Set 属性类型应该是
Set
。 - 使用
set
标签为 Set 属性注入数据。 Set
的数据不会重复,而List
允许重复数据。
示例:
<bean id="myBean" class="com.example.MyClass">
<property name="mySet">
<set>
<value>Value1</value>
<ref bean="someOtherBean"/>
</set>
</property>
</bean>
3. 注入Map集合
关键点:
- Map 是键值对集合,通常
key
是字符串,value
可以是静态数值或对象引用。 - 使用
map
标签来定义键值对,entry
标签用于定义每个键值对。- 静态数值:使用
value
。 - 对象引用:使用
ref
。
- 静态数值:使用
示例:
<bean id="myBean" class="com.example.MyClass">
<property name="myMap">
<map>
<entry key="key1" value="value1" />
<entry key="key2" value-ref="someOtherBean" />
</map>
</property>
</bean>
4. 注入Properties集合
关键点:
Properties
类型与Map
类似,但Properties
的key
和value
必须是字符串。- 不能像
Map
一样在value
部分引用其他对象。 - 常用于保存配置文件,例如数据库连接信息等。
示例:
<bean id="myBean" class="com.example.MyClass">
<property name="myProperties">
<props>
<prop key="username">admin</prop>
<prop key="password">secret</prop>
</props>
</property>
</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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="c1" class="com.imooc.spring.ioc.entity.Computer">
<constructor-arg name="brand" value="联想"/>
<constructor-arg name="type" value="台式机"/>
<constructor-arg name="sn" value="8389283012"/>
<constructor-arg name="price" value="3085"/>
</bean>
<bean class="com.imooc.spring.ioc.entity.Computer">
<constructor-arg name="brand" value="微星"/>
<constructor-arg name="type" value="台式机"/>
<constructor-arg name="sn" value="8389280012"/>
<constructor-arg name="price" value="3000"/>
</bean>
<bean class="com.imooc.spring.ioc.entity.Computer">
<constructor-arg name="brand" value="华硕"/>
<constructor-arg name="type" value="笔记本"/>
<constructor-arg name="sn" value="9089380012"/>
<constructor-arg name="price" value="6000"/>
</bean>
<bean id="company" class="com.imooc.spring.ioc.entity.Company">
<property name="rooms">
<set>
<value>2001-总裁办</value>
<value>2003-总经理办公室</value>
<value>2010-研发部会议室</value>
<value>2010-研发部会议室</value>
</set>
</property>
<property name="computers">
<map>
<entry key="dev-88172" value-ref="c1"/>
<entry key="dev-88173">
<bean class="com.imooc.spring.ioc.entity.Computer">
<constructor-arg name="brand" value="联想"/>
<constructor-arg name="type" value="笔记本"/>
<constructor-arg name="sn" value="1280258012"/>
<constructor-arg name="price" value="5060"/>
</bean>
</entry>
</map>
</property>
<property name="info">
<props>
<prop key="phone">010-12345678</prop>
<prop key="address">北京市朝阳区XX路XX大厦</prop>
<prop key="website">http://www.xxx.com</prop>
</props>
</property>
</bean>
</beans>
3. 课程笔记:Spring IOC 中的 Scope 属性
3. 1. Scope 属性的概念
Scope
是 Spring IOC 容器中用来决定对象的创建时机和作用范围的属性。通过设置 scope
属性,可以影响容器内对象的数量以及它们的生命周期。
3. 2. 常见的 Scope 属性值
Spring 支持六种常见的 Scope 属性值:
- singleton(单例模式):容器中的对象全局唯一,所有对该
bean
的引用指向同一个实例。是默认的 Scope 值。 - prototype(多例模式):每次获取
bean
时都会创建一个新的实例。 - request:每个 HTTP 请求都会创建一个新的实例。
- session:每个用户会话拥有一个唯一实例。
- globalSession:适用于 Portlet 环境中的全局会话。
- webSocket:在每个 WebSocket 连接中创建唯一实例。
3. 3. singleton 和 prototype 的区别
singleton(单例模式)
- 在 IOC 容器启动时即创建该对象。
- 对象在容器中全局唯一,共享实例。
- 存在线程安全问题,可能导致并发问题。
- 适用于频繁使用、无需重复创建的对象。
- 默认 Scope 值。
prototype(多例模式)
- 每次调用
getBean()
方法时,创建一个新的对象实例。 - 对象不共享,每个调用者获取的都是新的实例。
- 不会出现线程安全问题,但会消耗更多的内存和 CPU 资源。
- 适用于每次需要单独对象实例的场景。
3. 4. 其他 Scope
- request:每个 HTTP 请求都会创建一个新的实例,不同请求的实例互不影响,生命周期与请求一致。
- session:每个用户的会话中存在一个唯一实例,不同用户的会话拥有不同的实例。
- globalSession:应用于 Portlet 环境,多个 Portlet 共享同一个会话实例。
- webSocket:用于 WebSocket 连接,每个连接拥有一个独立的实例。
3. 5. singleton 的线程安全问题
在多线程环境下,单例模式可能会导致线程安全问题。比如,多个线程同时对同一个对象的属性进行修改时,会出现数据混乱的问题。可以通过同步机制(如加锁)来解决此问题,或者使用 prototype
来为每个线程创建独立的实例,从根本上避免并发问题。
4. 课程笔记:Spring IOC 容器中 Bean 的生命周期
4. 1. Bean 的生命周期概述
在 Spring IOC 容器中,Bean 的生命周期指的是从创建到销毁的整个过程。Bean 的生命周期可以分为五个主要阶段:实例化、属性注入、初始化、业务方法调用和销毁。下面我们详细介绍每个阶段。
4. 2. Bean 的生命周期阶段
1. 解析配置文件
首先,IOC 容器会解析 applicationContext.xml
文件,了解需要创建哪些对象以及为这些对象注入哪些属性。这是整个生命周期的第一步。
2. 实例化对象
IOC 容器通过反射机制根据配置文件中的定义来实例化对应的 Bean,并调用其构造方法。此时,Bean 还没有属性值。
3. 属性注入
容器实例化对象后,会根据配置文件为 Bean 注入属性。属性的注入会调用对应的 set
方法。
4. 初始化(init-method)
在 Bean 的属性注入完成后,IOC 容器会调用配置中的 init-method
方法。此方法用于在属性设置完毕后进行进一步的初始化操作。构造方法和 init-method
的区别在于:构造方法创建对象时 Bean 还没有属性值,而 init-method
是在属性已经注入后调用的。
5. 业务方法调用
IOC 容器初始化完毕后,业务代码就可以调用这个 Bean 来完成具体的业务操作。例如,通过 getBean()
方法获取实例并调用其方法。
6. 销毁(destroy-method)
当容器销毁时,会调用配置的 destroy-method
来释放与当前 Bean 相关的资源。这一步通常用于关闭文件、断开数据库连接等操作。
5. Spring 注解方式配置 IOC 容器
5. 1. Spring IOC 容器的三种配置方式
- 在前面的课程中,我们已经学习了如何使用 XML 配置 方式来配置 Spring IOC 容器。
- 除了 XML 配置,Spring 还支持 注解 和 Java Config 两种配置方式。
- 三种配置方式的底层原理是一样的,都是通过反射机制来动态实例化对象和注入属性。
5. 2. 注解的优势
- 摆脱繁琐的 XML 配置,不再需要大量的
bean
和property
标签来进行配置。 - 注解的配置更直观,可以直接在代码中通过注解声明类、属性和方法的依赖关系,提升开发体验。
- 注解基于声明式的原则,更适合轻量级的现代企业应用开发。
5. 3. Spring 常见的三类注解
在 Spring 中,注解可以分为以下三类:
- 组件类型注解:声明类的功能和职责,告诉 IOC 容器应该如何管理这个类。
- 自动装配注解:根据属性特征自动注入对象或值。
- 元数据注解:细化辅助 IOC 容器管理对象。
3.1 组件类型注解
组件类型注解有四种,分别是:
- @Component:表示这是一个通用组件类,适用于不确定职责的类。
- @Service:用于标记业务逻辑层类,通常是业务服务类。
- @Controller:用于标记控制器类,在 MVC 模式中使用,处理用户请求和返回响应。
- @Repository:用于标记数据持久层类,通常是 DAO 类,用于与数据库交互。
这些注解用于告诉 Spring IOC 容器哪些类需要被管理,并根据类的功能自动注入相关的扩展模块。
3.2 @Component 注解
- @Component 是一个通用的组件注解,适用于各种类,不区分具体职责。
- 如果无法明确类的职责,可以使用这个注解。
3.3 @Service 注解
- @Service 适用于业务逻辑层的类,用于实现系统的核心业务逻辑。
3.4 @Controller 注解
- @Controller 适用于控制层的类,处理客户端的请求,调用业务逻辑,并返回响应。
- 在学习 Spring MVC 时会更加详细地学习它的用法。
3.5 @Repository 注解
- @Repository 适用于持久层的类,通常是 DAO 类,用于数据库的增删改查操作。
4. 开启组件扫描
- 要让 Spring IOC 容器识别并管理这些带有注解的类,需要在
applicationContext.xml
中开启组件扫描。 - 通过
context:component-scan
标签设置扫描的包路径,并且可以排除不希望扫描的类。
<context:component-scan base-package="com.example">
<context:exclude-filter type="regex" expression="com.example.excluded.*"/>
</context:component-scan>
5.Spring IOC 注解中的自动装配
自动装配的两种方式
- 按类型装配:通过属性的数据类型来完成自动注入。
- 按名称装配:通过 bin 的名称来完成依赖注入。
自动装配注解分类
-
按类型装配:
@Autowired
:Spring 提供的按类型自动装配注解。@Inject
:JSR-330 提供的行业标准注解,与@Autowired
功能相似。
-
按名称装配:
@Named
:JSR-330 提供的注解,配合@Inject
使用,支持通过名称注入。@Resource
:JSR-250 的注解,支持通过名称优先匹配,如果名称不匹配则按类型进行注入。
@Autowired
示例
@Service
public class UserService {
@Autowired
private UserDao userDao;
// Getter and Setter
}
@Autowired
放在属性上,Spring IOC 容器会根据属性的数据类型,自动寻找并注入相应的对象。- 如果放在方法上,则 Spring 会为方法的参数进行自动注入。
自动装配注解的执行方式差异
- 在属性上使用
@Autowired
:Spring 使用反射技术,将私有属性临时设为公共属性进行赋值。 - 在方法上使用
@Autowired
:会直接调用set
方法,将依赖注入到方法参数中。
按类型注入的潜在问题
- 当 IOC 容器中存在多个相同类型的 bean 时,Spring 不知道该选择哪个对象进行注入,容易出现
NoUniqueBeanDefinitionException
。
示例:
@Repository
public class UserDaoImpl implements IUserDao {
// UserDao implementation
}
@Repository
public class UserOracleDaoImpl implements IUserDao {
// Oracle specific UserDao implementation
}
当有两个 IUserDao
的实现时,按类型注入将会失败。
解决方案
- 去掉某个实现的
@Repository
注解:避免重复的 bean 存在。 - 使用
@Primary
注解:指定默认优先注入的实现类。
@Repository
@Primary
public class UserDaoImpl implements IUserDao {
// Default UserDao implementation
}
按名称装配注解
1. 按名称自动装配注解
在实际项目中,优先推荐使用按名称自动装配注解。常见的按名称注解是 @Resource
,这是基于 JSR 250 的标准,广泛应用于 JAVA 项目中。
2. @Resource
注解的介绍
@Resource
是一种智能的自动装配注解,它优先尝试按名称进行注入,当名称匹配不到时,才会按类型匹配,具有较高的灵活性。- 常用规则:
- 如果设置了
name
属性:按name
指定的名称查找对应的Bean
。 - 如果没有设置
name
属性:会默认使用属性名作为Bean
名称查找匹配。 - 当
name
匹配失败后,才会按类型进行匹配。
- 如果设置了
3. 示例代码
假设我们有一个 DepartmentService
类,它依赖于 UserDAO
。我们来演示如何使用 @Resource
来注入 UserDAO
。
@Service
public class DepartmentService {
// 定义一个需要注入的属性
@Resource
private IUserDao userDao;
public void joinDepartment() {
// 打印userDao的实际类型
System.out.println(userDao.toString());
}
}
在 SpringApplication
中调用这个方法:
public class SpringApplication {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 获取departmentService实例
DepartmentService departmentService = context.getBean(DepartmentService.class);
departmentService.joinDepartment();
}
}
4. @Resource
注解的装配规则
- 按名称装配:默认情况下,
@Resource
会使用属性名作为Bean
名称进行匹配。如果userDao
属性名称与Bean
名称一致,那么它将会被注入。 - 按类型装配:如果名称匹配失败,则会按类型进行装配。例如,如果有多个
UserDAO
实现类,比如UserMySQLDAO
和UserOracleDAO
,那么就会发生冲突。
Spring IOC注解元数据
本节我们学习了Spring IOC注解中的元数据注解。元数据注解的作用是为Spring IOC容器管理对象提供辅助信息。
常用的元数据注解
-
@Primary:按类型装配时,如果出现多个相同类型的对象,则优先将
@Primary
注解的对象进行注入。 -
@PostConstruct:相当于XML中配置的
init-method
,在IOC容器对属性进行注入后自动执行初始化方法。 -
@PreDestroy:相当于XML中配置的
destroy-method
,用于在IOC容器销毁时执行销毁方法,释放资源。 -
@Scope:用于设置bin的
scope
属性,决定对象的生存周期。例如,单例模式(Singleton)或多例模式(Prototype)。 -
@Value:为属性注入静态数据,通常用于从配置文件中加载数据。
@Primary 注解
@Primary
注解用于解决按类型装配时出现多个相同类型对象的冲突。它告诉Spring在有多个候选对象时优先选择标注了@Primary
的对象进行注入。
@Primary
@Repository
public class UserOracleDao implements IUserDao {
// 实现代码...
}
@PostConstruct 注解
@PostConstruct
注解在对象创建并且依赖注入完成后,执行指定的初始化方法。相当于在XML中配置的init-method
。
@Service
public class UserService {
@PostConstruct
public void init() {
System.out.println("UserService 初始化");
}
}
@PreDestroy 注解
@PreDestroy
注解在对象销毁时执行相应的资源释放方法,类似于在XML中配置的destroy-method
。
@PreDestroy
public void destroy() {
System.out.println("UserService 销毁");
}
@Scope 注解
@Scope
注解用于指定对象的作用域。可以设置为singleton
(单例模式)或prototype
(多例模式)。
@Service
@Scope("prototype")
public class UserService {
// 该类将以多例的形式被创建
}
@Value 注解
@Value
注解用于为属性注入静态值,通常结合配置文件使用。首先我们需要创建一个properties
文件,比如config.properties
:
metadata=Imooc.com
然后在Spring的配置文件中加载该属性文件:
<context:property-placeholder location="classpath:config.properties"/>
接着,在类中使用@Value
注解引入配置文件中的值:
@Service
public class UserService {
@Value("${metadata}")
private String metadata;
@PostConstruct
public void init() {
System.out.println("Metadata: " + metadata);
}
}
Spring IOC 使用 Java Config 配置方式
Java Config 介绍
- Java Config 是 Spring 3.0 之后推出的一种配置方式,用于替代传统的 XML 配置。
- 使用独立的 Java 类 来管理对象与依赖,完全摆脱 XML 的束缚。
- 提供了对象的集中管理,避免注解分散在各个类中,便于大型工程中的管理。
- 在编译时进行依赖检查,能够更早地发现配置问题。
主要注解
- @Configuration:用于标记一个类为配置类,代替 XML 文件的作用。
- @Bean:用于方法上,表示方法返回的对象将由 IOC 容器管理,默认 Bean ID 为方法名。
- @ImportResource:用于加载静态配置文件,常与 @Value 配合使用。
- @ComponentScan:用于扫描项目中的四种组件类型(如 Service、Controller),类似于 XML 中的
context:component-scan
。
示例演示
@Configuration
public class Config {
@Bean
public UserDao userDao() {
return new UserDao();
}
@Bean
public UserService userService() {
UserService userService = new UserService();
userService.setUserDao(userDao());
return userService;
}
@Bean
public UserController userController() {
UserController userController = new UserController();
userController.setUserService(userService());
return userController;
}
}
- 解释:
@Configuration
:标记当前类为配置类,替代 XML 配置文件。@Bean
:每个带有该注解的方法返回的对象将由 IOC 容器管理,默认 Bean 的 ID 为方法名。
主程序入口
public class SpringApplication {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
// 打印所有 Bean 的名称
String[] beanNames = context.getBeanDefinitionNames();
for (String beanName : beanNames) {
System.out.println("Bean Name: " + beanName + " - Object: " + context.getBean(beanName));
}
}
}
- AnnotationConfigApplicationContext:用于基于注解配置的应用上下文,传入配置类
Config
来初始化 IOC 容器。
Spring IOC 使用 Java Config 进行依赖注入
Java Config 中的依赖注入
- 在 Java Config 中,依赖注入的过程主要通过 方法参数 来完成。
- 当一个对象(如
UserService
)依赖另一个对象(如UserDao
)时,只需将依赖的对象作为方法的参数,Spring 会自动注入该对象。
示例讲解
-
UserService 依赖于 UserDao:
- 在
Config
类中定义UserService
的方法时,添加UserDao
作为参数:@Bean public UserService userService(UserDao userDao) { UserService userService = new UserService(); userService.setUserDao(userDao); return userService; }
- Spring 会自动将容器中的
UserDao
对象注入到UserService
的setUserDao
方法中。
- 在
-
Controller 依赖于 Service:
- 同理,在
UserController
中注入UserService
:@Bean public UserController userController(UserService userService) { UserController userController = new UserController(); userController.setUserService(userService); return userController; }
- 同理,在
-
验证依赖注入过程:
- 在
Config
类的各个方法中增加输出语句:System.out.println("已创建 UserDao 对象"); System.out.println("已创建 UserService 对象,且注入了 UserDao"); System.out.println("已创建 UserController 对象,且注入了 UserService");
- 通过控制台输出,可以看到对象的创建顺序和注入过程。
- 在
依赖注入的方式
-
按名称注入:
- Spring 首先按 名称 注入依赖,方法参数名与 Bean ID 相同,则会根据名称匹配进行注入。
-
按类型注入:
- 当名称不匹配时,Spring 会尝试按 类型 进行注入。
- 如果存在多个相同类型的 Bean,则会抛出错误。
避免类型冲突
- 当存在多个相同类型的 Bean 时,可以使用
@Primary
注解来指定优先注入的 Bean。@Bean @Primary public UserDao userDao1() { return new UserDao(); }
多例对象与注解兼容
-
可以通过
@Scope("prototype")
来指定 Bean 为多例模式。@Bean @Scope("prototype") public UserController userController(UserService userService) { UserController userController = new UserController(); userController.setUserService(userService); return userController; }
- 在多例模式下,容器不会在启动时创建对象,而是在使用时才会创建。
-
通过
@ComponentScan
兼容注解形式:@ComponentScan(basePackages = "com.imock.spring")
- 容器启动时会自动扫描指定包路径下的组件并进行实例化。
XML 与 Java Config 的对比
-
优点:
- Java Config 提供了更好的开发体验,支持编译时检查,方便在开发阶段发现问题,适合快速迭代的项目。
- XML 配置 适合团队协作,可以通过配置文件将模块职责分离,更具可维护性。
-
缺点:
- Java Config 的修改需要重新编译,适合敏捷开发。
- XML 配置 虽然更加分离,但配置繁琐。
Spring Test 模块与 JUnit 整合
Spring Test 介绍
- Spring Test 是 Spring 框架中专用于测试的模块,常用于与 JUnit 单元测试的整合。
- 通过 Spring Test,可以在 JUnit 单元测试开始时自动初始化 IOC 容器,避免手动初始化
ApplicationContext
对象。
Spring 与 JUnit 4 的整合过程
-
Maven 依赖:
- 引入 Spring Test 模块和 JUnit 4 的依赖:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.2.6.RELEASE</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency>
- 引入 Spring Test 模块和 JUnit 4 的依赖:
-
测试类注解:
- 使用
@RunWith
和@ContextConfiguration
注解:@RunWith
:将 JUnit 的执行权交给 Spring,由 Spring 来管理测试用例的执行过程。@ContextConfiguration
:指定 Spring 容器加载的配置文件。
- 使用
-
示例代码:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = {"classpath:applicationContext.xml"}) public class SpringTest { @Resource private UserService userService; @Test public void testCreateUser() { userService.createUser(); } }
具体步骤
1. 引入依赖
- 在
pom.xml
中添加 Spring Test 和 JUnit 的依赖,确保测试模块和框架整合成功。
2. 创建测试用例类
- 在
src/test/java
目录下创建测试用例类,并在类上使用@RunWith
和@ContextConfiguration
注解。 @RunWith(SpringJUnit4ClassRunner.class)
:将 JUnit 的运行权交给 Spring Test 模块。@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
:指定 Spring 容器要加载的配置文件。
3. 属性注入与测试方法
- 使用
@Resource
注解注入需要测试的 Service 对象。 - 在测试方法中调用 Service 的方法,并通过断言或日志验证代码逻辑是否正确。
示例代码结构
-
DAO 类:
public class UserDao { public void insert() { System.out.println("新增用户数据"); } }
-
Service 类:
public class UserService { private UserDao userDao; public void setUserDao(UserDao userDao) { this.userDao = userDao; } public void createUser() { System.out.println("调用创建用户业务代码"); userDao.insert(); } }
-
applicationContext.xml 配置文件:
<beans> <bean id="userDao" class="com.imock.spring.ioc.dao.UserDao"/> <bean id="userService" class="com.imock.spring.ioc.service.UserService"> <property name="userDao" ref="userDao"/> </bean> </beans>
-
测试用例类:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = {"classpath:applicationContext.xml"}) public class SpringTest { @Resource private UserService userService; @Test public void testCreateUser() { userService.createUser(); } }