- IOC 概念
- IOC 底层原理
- 例子
- IOC 接口(BeanFactory)
- ApplicationContext 实现类
- IOC 操作 Bean 管理
- Bean 管理
- 基于 XML 配置文件
- 基于 XML 创建对象
- 基于 XML 注入属性
- 依赖注入
- set 方法注入
- 有参构造器注入
- set 注入简化 -- p 名称空间注入
- xml 注入其他类型数据
- null 值
- 带特殊符号的内容
- 注入属性--外部 bean
- 注入属性--内部 bean
- 注入属性--级联赋值
- 注入属性--集合
- 集合中注入对象类型
- 提取集合注入部分
- FactoryBean
- 使用方式
- Bean 的作用域
- 单实例、多实例设置方法
- singleton 和 prototype 区别
- bean 生命周期
- bean 的后置处理器
- XML 自动装配
- 根据属性名称自动装配
- 根据属性类型自动装配
- 引入外部属性文件
- 基于注解方式
- 什么是注解
- 基于注解创建对象
- 组件扫描的细节配置
- 通过注解实现属性注入
- @AutoWired
- @Qualifier
- @Resource
- @Value
- 全注解开发
学习内容来自 B站尚硅谷视频 IOC 概念
IOC 全称 Inversion of Control,也就是控制反转,是面向对象编程的一种设计原则,可以降低代码的耦合度
通俗的说,就是把创建对象、调用对象的过程交给 Spring 进行管理,这样做的目的是为了降低代码的耦合度
学习笔记一的入门案例就是 IOC 实现
IOC 底层原理主要使用的技术:
- XML 解析
- 工厂模式
- 反射
原始方式,Service 需要关联 Dao,Dao 关联具体类,这样耦合度较高,如果中间有类的路径改变,则需要挨个改变相关联的类
使用工厂模式解耦合:
IOC 过程:
- 创建 XML 配置文件,配置创建的对象
- 创建工厂类,在工厂类中通过解析配置文件获得类的全类名,然后使用反射创建对象实例
IOC 主要基于 IOC 容器,而 IOC 容器底层就是对象工厂
Spring 提供 IOC 容器的两种实现方式,即两个接口
- BeanFactory:IOC 容器基本实现方式,Spring 内部使用接口,不建议开发人员使用
BeanFactory 在加载配置文件的时候并不会创建对象,只有在获取使用对象时才会创建(懒汉式)
- ApplicationContext:BeanFactory 的子接口,提供更多定制功能,建议开发人员使用
ApplicationContext 实现类ApplicationContext 在加载配置文件时就会创建对象(饿汉式)
ApplicationContext 有一些实现类:
其中有两个重要的实现类:
- FileSystemXmlApplicationContext
使用该实现类加载 Xml 需要制定文件在系统盘中的路径
- ClassPathXmlApplicationContext
使用该实现类主要是相对于当前项目代码文件的路径,即 src 下的路径
BeanFactory 也有这两个实现类
IOC 操作 Bean 管理 Bean 管理Bean 管理主要包括两个操作:
- Spring 创建对象,如入门案例使用 ApplicationContext 读取配置文件然后创建对象
- Spring 注入属性,即根据类的 set 方法设置对象相关属性值
主要包括两种实现方式
- 继续 XML 配置文件
- 基于注解
- 准备配置文件
在配置文件中使用 bean 标签配置
bean 标签常用属性
- id:给对象起一个标识符
- class:类的全类名
- name:和 id 功能类似,不过该属性中可以有特殊符号,不常用
- 创建对象
创建对象的时候默认使用对象的无参构造器
基于 XML 注入属性 依赖注入IOC 中有一个概念为 DI,表示依赖注入,也就是注入属性
注入方式有两种:
- 使用相应属性的 set 方法注入
- 使用类的有参构造器注入
- 在对象中设置属性的 set 方法
public class Book {
private String name;
private String author;
public void setAuthor(String author) {
this.author = author;
}
public void setName(String name) {
this.name = name;
}
}
- 在 Spring 配置文件中配置对象创建,以及属性注入
- 测试
public void testBook1() {
ApplicationContext context =
new ClassPathXmlApplicationContext("bean1.xml");
Book book = context.getBean("book", Book.class);
book.testDemo();
}
有参构造器注入
- 设置好有参构造器
public class Order {
private String name;
private String address;
public Order(String name, String address) {
this.name = name;
this.address = address;
}
public void testDemo() {
System.out.println(name + ", " + address);
}
}
- 在 Spring 配置文件中配置对象创建,以及属性注入
- 测试
public void testOrder1() {
ApplicationContext context =
new ClassPathXmlApplicationContext("bean1.xml");
Order order = context.getBean("order", Order.class);
order.testDemo();
}
set 注入简化 – p 名称空间注入
配置文件头部依赖加上
xmlns:p="http://www.springframework.org/schema/p"
在配置文件中配置创建对象方式:
这里使用标签 p:属性名=属性值 注入对应属性
xml 注入其他类型数据 null 值在配置文件中配置属性时,可以通过以下方法将属性设置为空值
带特殊符号的内容
在配置文件中配置属性时,属性值中不可以包含特殊符号(如 “<” 、 “>”)可以通过以下方法使属性值包含特殊符号
- 使用转义字符代替,如 < >表示 “<” 、 “>”
- 把带特殊符号的内容写到 中
注入属性–外部 bean>]]>
假设现在有两个类:service 类和 dao 类
其中,service 类中调用 dao 类中的方法
dao 接口
public interface UserDao {
void update();
}
dao 实现类
public class UserDaoImpl implements UserDao{
@Override
public void update() {
System.out.println("dao update sth");
}
}
service 类
public class UserService {
public void add() {
System.out.println("service add sth");
// 创建 UserDao 对象
// 常规方法
UserDao userDao = new UserDaoImpl();
userDao.update();
}
}
也可以在 spring 配置文件中进行配置
首先,需要更改 service 类,加上属性 UserDao
public class UserService {
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void add() {
System.out.println("service add sth");
// 创建 UserDao 对象
// 常规方法
UserDao userDao = new UserDaoImpl();
userDao.update();
userDao.update();
}
}
然后在配置文件中配置:
编写测试:
public void testAdd() {
ApplicationContext context =
new ClassPathXmlApplicationContext("bean2.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.add();
}
注入属性–内部 bean
假设现在有两个类
部门类
public class Dept {
private String name;
public void setName(String name) {
this.name = name;
}
}
员工类
public class Empl {
private String name;
private String gender;
// 员工所属部门
private Dept dept;
public void setDept(Dept dept) {
this.dept = dept;
}
public void setName(String name) {
this.name = name;
}
public void setGender(String gender) {
this.gender = gender;
}
public void add() {
System.out.println(name + ", " + gender + ", " + dept);
}
}
其中,一个部门可以有多个员工,这两个类是一对多的关系
于是可以在 spring 配置文件中进行如下配置:
这里使用内部 bean 配置对象属性
编写测试代码
@Test
public void testBean2() {
ApplicationContext context =
new ClassPathXmlApplicationContext("bean3.xml");
Empl emp = context.getBean("emp", Empl.class);
emp.add();
}
注入属性–级联赋值
级联赋值就是给属性的属性赋值
首先,需要在有对应属性的属性的 get 方法
修改员工类如下,添加了 dpet 的 get 方法:
public class Empl {
private String name;
private String gender;
// 员工所属部门
private Dept dept;
public Dept getDept() {
return dept;
}
public void setDept(Dept dept) {
this.dept = dept;
}
public void setName(String name) {
this.name = name;
}
public void setGender(String gender) {
this.gender = gender;
}
public void add() {
System.out.println(name + ", " + gender + ", " + dept);
}
}
在配置文件中配置:
注入属性–集合
- 注入数组类型
- 注入 List 集合
- 注入 Set 集合
假设有一个类 Stu
public class Stu {
private String[] courses;
private Set friends;
private List bills;
private Map score;
public void testBean() {
for (String cours : courses) {
System.out.println(cours + " ");
}
System.out.println();
for (String friend : friends) {
System.out.println(friend + " ");
}
System.out.println();
for (String bill : bills) {
System.out.println(bill + " ");
}
System.out.println();
score.forEach((s, integer) -> System.out.println(s + ": " + integer));
}
public void setCourses(String[] courses) {
this.courses = courses;
}
public void setFriends(Set friends) {
this.friends = friends;
}
public void setBills(List bills) {
this.bills = bills;
}
public void setScore(Map score) {
this.score = score;
}
}
编写配置文件:
计算机组成原理
计算机系统结构
张三
李四
刚刚花了一块钱
现在花了五块钱
编写测试代码
public void testBead01() {
ApplicationContext context =
new ClassPathXmlApplicationContext("bean1.xml");
Stu stu = context.getBean("stu", Stu.class);
stu.testBean();
}
集合中注入对象类型
在上面的例子中,集合中存储的都是字符串或者基本类型这些字面量,如果集合中存储的是对象的话,需要用如下方法进行配置
假设现在有一个课程类:
public class Course {
private String name;
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Course{" +
"name='" + name + ''' +
'}';
}
}
修改类 Stu 为:
public class Stu {
private String[] courses;
private Set friends;
private List bills;
private Map score;
private List courseList;
public void setCourseList(List courseList) {
this.courseList = courseList;
}
public void testBean() {
for (Course cours : courseList) {
System.out.println(cours + " ");
}
System.out.println();
for (String friend : friends) {
System.out.println(friend + " ");
}
System.out.println();
for (String bill : bills) {
System.out.println(bill + " ");
}
System.out.println();
score.forEach((s, integer) -> System.out.println(s + ": " + integer));
}
public void setCourses(String[] courses) {
this.courses = courses;
}
public void setFriends(Set friends) {
this.friends = friends;
}
public void setBills(List bills) {
this.bills = bills;
}
public void setScore(Map score) {
this.score = score;
}
}
于是配置 courseList 方法为:
计算机组成原理
计算机系统结构
张三
李四
刚刚花了一块钱
现在花了五块钱
提取集合注入部分
将配置文件中集合配置部分提取出来,作为一个公共部分
现在有一个类 Extraction
public class Extraction {
private List list;
public void setList(List list) {
this.list = list;
}
}
- 首先,需要在 spring 配置文件头部引入名称空间 util
- 使用 util 标签完成 list 集合初始化
杰瑞狗
jerry dog
Jerry
- 注入提取后的 list 集合作为属性
这样就完成了 list 集合的提取和注入
FactoryBeanSpring 有两种类型的 bean,一种是普通 bean,一种是 FactoryBean
- 普通 bean 就是之前提到的配置文件,在配置文件中
标签的属性 class 对应的就是要返回类的全类名 - FactoryBean 配置中的定义类型与返回类型可以不一致
- 创建类,让该类作为 FactoryBean(工厂 bean),实现接口 FactoryBean
- 实现接口里的方法,在实现的方法中定义返回的 bean 的类型
public class MyBean implements FactoryBean{ // 定义返回 bean 的类型 @Override public Course getObject() throws Exception { Course course = new Course(); course.setName("abc"); return course; } @Override public Class> getObjectType() { return Course.class; } @Override public boolean isSingleton() { return FactoryBean.super.isSingleton(); } }
然后在配置文件中配置工厂 bean
编写测试
public void testBead03() {
ApplicationContext context =
new ClassPathXmlApplicationContext("bean3.xml");
Course myBean = context.getBean("myBean", Course.class);
System.out.println(myBean);
}
输出结果:
Course{name='abc'}
Bean 的作用域
在 Spring 里面,可以设置创建 bean 实例是单实例还是多实例
默认情况下,都是单实例
比如,之前提到的 Extraction 类的测试,修改为下:
public void testBead02() {
ApplicationContext context =
new ClassPathXmlApplicationContext("bean2.xml");
Extraction extract1 = context.getBean("extract", Extraction.class);
Extraction extract2 = context.getBean("extract", Extraction.class);
System.out.println(extract1);
System.out.println(extract2);
}
输出结果:
com.sict.spring5.collectionType.Extraction@2a65fe7c com.sict.spring5.collectionType.Extraction@2a65fe7c
可以看到两个对象地址一致,也就是说明返回的 bean 是单实例的
单实例、多实例设置方法在配置文件中 bean 标签中有属性 scope 用于设置单实例还是多实例
scope 有两个值可以设置:
- singleton 表示单实例,默认值
- prototype 表示多实例
于是可以配置文件中写上:
杰瑞狗
jerry dog
Jerry
重新运行测试代码,结果为:
com.sict.spring5.collectionType.Extraction@2a65fe7c com.sict.spring5.collectionType.Extraction@4135c3b
可以看到两个对象地址不同,也就是返回两个不一样的对象
singleton 和 prototype 区别- singleton 单实例、prototype 多实例
- scope 为 singleton,加载 spring 配置文件的时候就会创建单实例对象
- scope 为 prototype,调用 getBean() 方法时才会创建对象
对象创建到销毁的过程就是对象的生命周期
bean 的生命周期包含:
- 通过参数构造器(无参构造器)创建 bean 实例
- 为 bean 的属性设置值或对其它 bean 的引用(调用 set 方法)
- 调用 bean 的初始化方法(需要进行配置)
- 使用 bean
- 容器关闭时,调用销毁 bean 的销毁方法(需要进行配置)
代码演示:
- 先创建一个 Orders 类
public class Orders {
private String name;
public void setName(String name) {
this.name = name;
System.out.println("2. set 方法被调用,用来设置值");
}
public Orders() {
System.out.println("1. 无参构造器被调用,用来创建 bean 实例");
}
// 创建 bean 执行的初始化的方法
public void initMethod() {
System.out.println("3. 执行初始化的方法");
}
// 销毁 bean 执行的销毁方法
public void destoryMethod() {
System.out.println("5. 执行销毁方法");
}
}
- 配置 bean
- 编写测试代码
public void testBead04() {
ApplicationContext context =
new ClassPathXmlApplicationContext("bean4.xml");
Orders orders = context.getBean("orders", Orders.class);
System.out.println("4. 使用 bean");
System.out.println(orders);
// 手动销毁 bean 实例
// close 方法是 ClassPathXmlApplicationContext 类的方法,需要强转
((ClassPathXmlApplicationContext) context).close();
}
输出结果为:
1. 无参构造器被调用,用来创建 bean 实例 2. set 方法被调用,用来设置值 3. 执行初始化的方法 4. 使用 bean com.sict.spring5.bean.Orders@515aebb0 5. 执行销毁方法bean 的后置处理器
bean 的生命周期还有更完整的两个步骤(bean 的后置处理器),补充之前的步骤后如下:
- 通过参数构造器(无参构造器)创建 bean 实例
- 为 bean 的属性设置值或对其它 bean 的引用(调用 set 方法)
- 把 bean 实例传递给 bean 的后置处理器的方法 postProcessBeforeInitialization
- 调用 bean 的初始化方法(需要进行配置)
- 把 bean 实例传递给 bean 的后置处理器的方法 postProcessAfterInitialization
- 使用 bean
- 容器关闭时,调用销毁 bean 的销毁方法(需要进行配置)
添加的代码演示:
- 添加自定义的 bean 后置处理器类,实现 BeanPostProcessor 接口
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
// 初始化之前调用的方法
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("初始化之前执行的方法");
return bean;
}
@Override
// 初始化之后调用的方法
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("初始化之后执行的方法");
return bean;
}
}
- 在配置文件中配置后置处理器,该后置处理器应用于整个配置文件
执行测试代码后的结果为:
1. 无参构造器被调用,用来创建 bean 实例 2. set 方法被调用,用来设置值 初始化之前执行的方法 3. 执行初始化的方法 初始化之后执行的方法 4. 使用 bean com.sict.spring5.bean.Orders@6c64cb25 5. 执行销毁方法XML 自动装配
之前提到的配置方法,通过指定属性名称和值配置初始化 bean 实例的方法就是手动配置
自动装配是指根据指定装配规则(属性名称或者类型),Spring 自动将匹配的属性值进行注入
bean 标签有属性 autowire,配置自动装配
autowire 有两个常用属性值:
- byName 根据属性名称注入,注入值 bean 的 id 值和类的属性名称要一致
- byType 根据属性类型注入
假设有两个类:
public class Dept {
@Override
public String toString() {
return "Dept{}";
}
}
public class Emp {
private Dept dept;
public void setDept(Dept dept) {
this.dept = dept;
}
@Override
public String toString() {
return "Emp{" +
"dept=" + dept +
'}';
}
public void test() {
System.out.println(dept);
}
}
在配置文件中配置:
编写测试:
public void testBead05() {
ApplicationContext context =
new ClassPathXmlApplicationContext("bean5.xml");
Emp emp = context.getBean("emp", Emp.class);
System.out.println(emp);
}
根据属性类型自动装配
在配置文件中进行配置:
注意,根据属性类型自动装配,在配置文件中只能由一个对应类型的 bean,如果有多个将会出错
引入外部属性文件在配置文件中可以引入外部文件来配置属性
比如配置 druid 数据库连接池有两种方式
- 直接配置数据库信息
- 引入外部属性文件配置数据库连接池
先在配置文件头部引入 context 名称空间
准备一个 properties 文件在 src 目录下:
prop.driverClass=com.mysql.jdbc.Driver prop.url=com.mysql.jdbc.driver prop.username=root prop.password=root
然后在配置文件中引入:
基于注解方式
什么是注解
- 注解是代码特殊标记,格式@注解名称(属性名称=属性值...)
- 注解可以作用在类、方法、属性上面
- 注解的目的是为了简化 XML 配置
Spring 针对 Bean 管理提供了四种注解:
- @component 普通组件
- @Service 用在 service 层或者业务逻辑层
- @Controller 用在 web 层
- @Repository 用在 dao 层或者持久层
这四个注解都可以用来创建 bean 实例,不用注解一般用在不同层中
基于注解创建对象-
引入依赖
-
开启组件扫描,使 Spring 扫描加注解的类
引入 context 名称空间
开启组件扫描
- 创建类,在类名上加上注解
// 等价于// value 属性值可以忽略,默认值为 首字母小写后的类名称 @Service(value = "userService") public class UserService { public void add() { System.out.println("service add...."); } }
- 编写测试代码
public void testService() {
ApplicationContext context =
new ClassPathXmlApplicationContext("bean1.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.add();
}
输出结果:
service add....组件扫描的细节配置
在上面的例子中,开启组件扫描时会扫描包下所有的类
实际上可以配置具体扫描的类
即默认规则即为全包扫描,可以通过use-default-filters="false" 关闭全包扫描,然后通过context:include-filter 表示要扫描的内容;
也可以在全包扫描时通过context:exclude-filter过滤不进行扫描的内容
通过注解实现属性注入三种常用的属性注入的注解为:
- @AutoWired 根据属性类型自动注入
- @Qualifier 根据属性名称自动注入
- @Resource 可以根据属性类型,也可根据属性名称
- @Value 注入字面量
- 创建 service 和 dao 类,在类中加入对象注解
@Service(value = "userService")
public class UserService {
public void add() {
System.out.println("service add....");
}
}
@Repository
public class UserDaoImpl implements UserDao{
@Override
public void add() {
System.out.println("dao add...");
}
}
- 在 service 添加 dao 属性,在属性上加上注解
@Service(value = "userService")
public class UserService {
// 使用该注解不需要加上 set 方法
@Autowired
private UserDao userDao;
public void add() {
userDao.add();
System.out.println("service add....");
}
}
@Qualifier
该注解需要和 @Autowired 一起使用
在上面的例子中,如果属性对应接口有多个实现类,就可能会引发错误
这时候就可以使用 @Qualifier 注解指定具体实现类
首先给 UserDao 加上自定义名称
@Repository(value = "userDaoImpl1")
public class UserDaoImpl implements UserDao{
@Override
public void add() {
System.out.println("dao add...");
}
}
然后在 UserService 中加上 @Qualifier 注解
// 等价于@Resource// value 属性值可以忽略,默认值为 首字母小写后的类名称 @Service(value = "userService") public class UserService { @Autowired @Qualifier(value = "userDaoImpl1") private UserDao userDao; public void add() { userDao.add(); System.out.println("service add...."); } }
直接在属性上加上 @Resource 注解,默认即为根据类型注入
@Service(value = "userService")
public class UserService {
// 根据类型注入
@Resource
private UserDao userDao;
public void add() {
userDao.add();
System.out.println("service add....");
}
}
如果在 @Resource 后面加上属性 name 那么即为根据名称注入
@Service(value = "userService")
public class UserService {
// 根据名称注入
@Resource(name = "userDaoImpl1")
private UserDao userDao;
public void add() {
userDao.add();
System.out.println("service add....");
}
}
注意,@Resource 注解不是 spring 官方的包,而是 javax.annotation 包下
@Value修改 UserService 类
@Service(value = "userService")
public class UserService {
// 根据名称注入
@Resource(name = "userDaoImpl1")
private UserDao userDao;
// 注入字符串 abc
@Value(value = "abc")
private String name;
public void add() {
userDao.add();
System.out.println("service add...." + name);
}
}
全注解开发
即不使用配置文件,仅使用注解开发
步骤如下:
- 创建配置类,替代配置文件
@Configuration // 加上该注解,该类即为配置类,可以替代 xml 配置文件
@ComponentScan(basePackages = {"com.sict"}) // 开启组件扫描
public class SpringConfig {
}
- 和之前步骤一致,创建 service 和 dao 类,加上相应注解



