Spring的核心就是提供了一个IoC容器,它可以管理所有轻量级的JavaBean组件,提供的底层服务包括组件的生命周期管理、配置和组装服务、AOP支持,以及建立在AOP基础上的声明式事务服务等。
1. 什么是Ioc容器
IoC全称Inversion of Control,直译为控制反转。
1.1 控制反转
先来举一个例子:
假设上图是机械式手表的结构,各个齿轮分别带动时针、分针和秒针顺时针旋转,从而在表盘上产生正确的时间,这些齿轮相互啮合在一起,协同工作,共同完成某项任务。
但是如果有一个齿轮出了问题,就可能会影响到整个齿轮组的正常运转
软件系统中的耦合关系和齿轮之间的啮合关系很像,随着系统规模越来越庞大,对象之间的依赖关系越来越复杂,如何降低耦合度,达到解耦的目的,成为软件工程领域追求的目标之一。
软件专家Michael Mattson 1996年提出了IOC理论,用来实现对象之间的“解耦”。
IOC理论借助使用“第三方”来实现对象之间的解耦。
通过上图,可以看出,由于引进了中间的“第三方”,也就是IOC容器,使得时针齿轮,分针齿轮和秒针齿轮没有了耦合关系,齿轮之间的传动全部依靠“第三方”了,全部对象的控制权全部上缴给“第三方”IOC容器,所以,IOC容器成了整个系统的关键核心。
IOC: 控制反转,将对象(或者称为资源)的控制权由应用程序转交给容器。
1.2 传统的程序实现
先来看一下,如果没有Spring的时候,我们是如何来实现代码的。
场景:商品购买
实现需求:
- 商品购买
- 购买历史
1.2.1 商品购买
public class BuyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1. 查询用户
UserService userService = new UserService();
User user = userService.getCurrentUser();
//2. 查询商品
GoodsService goodsService = new GoodsService();
Goods goods = goodsService.findGoods();
//3. 创建订单
OrderService orderService = new OrderService();
Order order = orderService.createOrder(user,goods);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1.2.2 购买历史
public class UserHistoryServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1. 查询用户
UserService userService = new UserService();
User user = userService.getCurrentUser();
//2. 查询用户订单
OrderService orderService = new OrderService();
List<Order> orderList = orderService.findListByUser(user);
//3. 根据订单中的商品id,查询详细的订单商品详情,展示
GoodsService goodsService = new GoodsService();
List<Goods> goodsList = goodsService.findGoodsList(orderList);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
我们发现了一个问题,UserService,OrderService,GoodsService每次在使用的时候都要new
1.2.3 Service代码
Service都要访问数据库,所以在service代码中,需要有数据库的连接。
public class GoodsService {
private HikariConfig configuration = new HikariConfig();
private HikariDataSource dataSource = new HikariDataSource(configuration);
public Goods findGoods() {
try {
Connection connection = dataSource.getConnection();
//执行查询 省略
} catch (SQLException e) {
e.printStackTrace();
}
return new Goods();
}
public List<Goods> findGoodsList(List<Order> orderList) {
try {
Connection connection = dataSource.getConnection();
//执行查询 省略
} catch (SQLException e) {
e.printStackTrace();
}
return new ArrayList<Goods>();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class OrderService {
private HikariConfig configuration = new HikariConfig();
private HikariDataSource dataSource = new HikariDataSource(configuration);
public Order createOrder(User user, Goods goods) {
try {
Connection connection = dataSource.getConnection();
//调用数据库 省略不写
} catch (SQLException e) {
e.printStackTrace();
}
return new Order();
}
public List<Order> findListByUser(User user) {
return new ArrayList<Order>();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class UserService {
private HikariConfig configuration = new HikariConfig();
private HikariDataSource dataSource = new HikariDataSource(configuration);
public User getCurrentUser() {
try {
Connection connection = dataSource.getConnection();
//调用数据库 省略不写
} catch (SQLException e) {
e.printStackTrace();
}
return new User();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
我们发现每次使用Service的时候,由于Service需要调用数据库来实现业务,所有都持有了HikariDataSource的对象,同时HikariDataSource又依赖于HikariConfig,所以有实例化了一个HikariConfig
1.2.4 结论
上述的代码中,我们发现了以下缺点:
- 多个Service之间都用到了DataSource,完全可以共享DataSource
- 多个Servlet之间都用到了Service,完全可以共享
- 生命周期的问题,比如DataSource使用完,应该被释放掉,如果被多个对象使用,引用,如果保证使用方全部销毁
- 随着系统的复杂,Servlet越写越多,Service越写越多,对象之间的依赖关系就会越来越复杂,代码写起来或者阅读起来会变的相当困难
如果一个系统有大量的组件,其生命周期和相互之间的依赖关系如果由组件自身来维护,不但大大增加了系统的复杂度,而且会导致组件之间极为紧密的耦合,继而给测试和维护带来了极大的困难。
于是通过引入IOC容器来解决这些问题。
代码就变成了这样:
public class UserService {
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
}
2
3
4
5
6
7
通过上述代码我们发现,DataSource不在由应用程序自己(UserService)来创建,而是通过外部通过setDataSource注入的方式引入
那么这个外部就称为是 IOC容器
1.3 依赖注入
首先明白一个概念,上述我们讲的对象,资源,组件等,在Spring中,统一被称为Bean
既然现在创建对象的行为是由IOC容器负责的,那么必然需要有一种注入机制,来将对象注入到应用程序中。
依赖注入有两种方式:
set注入
public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; }
1
2
3构造器注入
private DataSource dataSource; public UserService(DataSource dataSource){ this.dataSource = dataSource; }
1
2
3
4
控制反转和依赖注入是从不同角度描述的同一件事情,就是指通过引入IOC容器,利用依赖关系注入的方式,实现对象之间的解耦
控制反转:将对象(或者称为资源)的控制权由应用程序转交给Ioc容器
依赖注入:应用程序所需要的资源由Ioc容器主动注入
2. 入门案例
IOC容器的两个主要包是:org.spring framework.beans和org.springframework.context包。
如果想使用IOC容器,下面两个依赖是必须的:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.2.16.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.16.RELEASE</version>
</dependency>
2
3
4
5
6
7
8
9
10
11
2.1 需求
场景需求:
用户通过邮箱和密码注册的时候,会发送一封邮件到用户的邮箱
2.2 工程搭建
新建maven工程 spring01:
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>spring01</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.2.16.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.16.RELEASE</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
</dependencies>
</project>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
2.3 Service
package com.mszlu.service;
import com.mszlu.domain.User;
public class MailService {
public boolean sendRegisterMail(User user){
System.out.println(String.format("%s 正在注册码神之路,您可以点击以下的链接完成注册 %s,如果不是你注册的,请忽略本邮件",user.getNickname(),"http://www.mszlu.com"));
return true;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
package com.mszlu.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Long userId;
private String nickname;
private String mail;
private String password;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.mszlu.service;
import com.mszlu.domain.User;
import java.util.ArrayList;
import java.util.List;
public class UserService {
private List<User> users = new ArrayList<>(List.of(
new User(1L, "aaa","aaa@mszlu.com", "123456" ),
new User(2L, "bbb","bbb@mszlu.com", "123456" ),
new User(3L, "ccc","ccc@mszlu.com", "123456")));
private MailService mailService;
public void setMailService(MailService mailService) {
this.mailService = mailService;
}
public void registerUser(String mail,String password,String nickname){
users.forEach(user -> {
if(user.getMail().equalsIgnoreCase(mail)){
throw new RuntimeException("已经注册");
}
});
User user = new User(null,mail,password,nickname);
mailService.sendRegisterMail(user);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
注意到UserService
通过setMailService()
注入了一个MailService
。
2.4 Spring的xml配置文件
编写完上述的代码后,接下来我们需要编写一个特定的application.xml
配置文件,xml文件的名称可以随意指定,但建议使用spring,application开头的命名方式
application.xml
目前可以近似当做就是Ioc容器。
在配置中指名如何创建并组装Bean。
xml文件放在resources目录下
<?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="userService" class="com.mszlu.service.UserService">
<property name="mailService" ref="mailService" />
</bean>
<bean id="mailService" class="com.mszlu.service.MailService" />
</beans>
2
3
4
5
6
7
8
9
10
11
12
上述配置文件,其中与XML Schema相关的部分格式是固定的,我们只关注两个
<bean ...>
的配置
上述的配置,使用Java代码表示:
UserService userService = new UserService();
MailService mailService = new MailService();
userService.setMailService(mailService);
2
3
- 每个
<bean ...>
都有一个id
标识,相当于Bean的唯一ID - 在
userService
Bean中,通过<property name="..." ref="..." />
注入了另一个Bean
2.5 运行
通过application.xml 创建一个Ioc容器,ApplicationContext
package com.mszlu; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class App { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("application.xml"); } }
1
2
3
4
5
6
7
8
9
10
11
12通过Bean的id获取到对应的Bean实例 UserService
package com.mszlu; import com.mszlu.service.UserService; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class App { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("application.xml"); UserService userService = (UserService) context.getBean("userService"); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14调用registerUser方法
package com.mszlu; import com.mszlu.service.UserService; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class App { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("application.xml"); UserService userService = (UserService) context.getBean("userService"); userService.registerUser("ddd@mszlu.com","123456","ddd"); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2.6 分析
ApplicationContext就是Spring的容器,它是一个接口,有很多实现类,这里我们选择ClassPathXmlApplicationContext
,表示它会自动从classpath中查找指定的XML配置文件。
获得了
ApplicationContext
的实例,就获得了IoC容器的引用。从ApplicationContext
中我们可以根据Bean的ID获取Bean。
//也可以通过Bean的类型 来获取
UserService userService = context.getBean(UserService.class);
2
3. Bean配置说明
bean基础配置
类别 | 描述 |
---|---|
名称 | bean |
类型 | 标签 |
所属 | beans标签 |
功能 | 定义Spring核心容器管理的对象 |
格式 | <beans> <bean/> <bean></bean> </beans> |
属性 列表 | id:bean的id,使用容器可以通过id值获取对应的bean,在一个容器中id值唯一 class:bean的类型,即配置的bean的全路径类名 |
bean别名配置
类别 | 描述 |
---|---|
名称 | name |
类型 | 属性 |
所属 | bean标签 |
功能 | 定义bean的别名,可定义多个,使用逗号(,)分号(;)空格( )分隔 |
示例:
<!--通过mailService2也可以获得mailService的实例-->
<bean id="mailService" name="mailService2" class="com.mszlu.service.MailService" />
2
bean作用范围配置
类别 | 描述 |
---|---|
名称 | scope |
类型 | 属性 |
所属 | bean标签 |
功能 | 定义bean的作用范围,可选范围如下 singleton:单例(默认) prototype:非单例 |
示例:
<!--单例,默认是单例,可以不写-->
<bean id="mailService" name="mailService2" class="com.mszlu.service.MailService" scope="singleton"/>
<!--非单例,每次获取都是一个新的实例,不常用-->
<bean id="mailService" name="mailService2" class="com.mszlu.service.MailService" scope="prototype"/>
2
3
4