1.引言
1.1 Spring IoC容器的概念
Spring IoC(Inversion of Control)容器是Spring框架的核心功能之一,它负责管理应用程序中的对象(Bean)的生命周期、依赖关系以及配置。
两个核心概念的解释:
控制反转(IoC):IoC是一种软件设计原则,它反转了传统的应用程序控制流程。传统方式中,应用程序负责创建和管理对象,而在IoC中,控制权被转移到容器中,容器负责创建、管理和注入对象,应用程序只需使用这些对象。
容器:容器是IoC的核心,它是一个运行时环境,用于管理应用程序中的Bean。容器负责创建、配置、装配和管理Bean,并提供对它们的依赖注入。
2.IoC容器的背景
2.1回顾传统的应用程序架构和问题
传统的开发模式【以反射举例子】
以下是一个简单的 JdbcUtils 类示例,通过配置文件来完善数据库连接信息。这里使用了 properties 配置文件来存储数据库相关的信息。
首先,创建一个 jdbc.properties 配置文件,包含如下内容(根据你的数据库信息修改):
# 数据库连接配置
jdbc.url=jdbc:mysql://localhost:3306/mydb
jdbc.username=username
jdbc.password=password
接下来,创建 JdbcUtils.java 类,并使用 Java 代码读取配置文件中的信息,并创建数据库连接:
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
public class JdbcUtils {
private static String url;
private static String username;
private static String password;
// 静态代码块,在类加载时执行,用于初始化数据库连接信息
static {
try {
// 从配置文件加载数据库连接信息
Properties properties = new Properties();
InputStream inputStream = JdbcUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
properties.load(inputStream);
// 获取配置信息
url = properties.getProperty("jdbc.url");
username = properties.getProperty("jdbc.username");
password = properties.getProperty("jdbc.password");
// 注册数据库驱动
Class.forName("com.mysql.jdbc.Driver");
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
throw new RuntimeException("Failed to load JDBC properties or register driver.", e);
}
}
// 获取数据库连接
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(url, username, password);
}
// 关闭数据库连接
public static void closeConnection(Connection connection) {
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
用图解的方式解释就是这样的:
传统方式:
- 程序员编程程序,在程序中读取配置信息
- 创建对象,new Object()/反射方式
- 使用对象完成任务
我们可以试着分析传统方式的缺点:
- 紧耦合:在传统的应用程序中,各个模块或组件之间通常存在紧耦合。这意味着一个模块的修改可能会影响其他模块,导致系统难以维护和扩展。
- 复杂的依赖管理:传统应用程序中,手动管理对象之间的依赖关系通常需要大量的代码。这包括对象的创建、初始化、连接和销毁等操作。
- 配置硬编码:配置信息通常被硬编码到应用程序的源代码中,这意味着任何配置更改都需要修改代码并重新编译,缺乏灵活性。
- 难以测试:由于紧耦合和复杂的依赖关系,传统的应用程序往往难以进行单元测试和集成测试,从而导致质量问题。
- 不易重用:传统的应用程序通常缺乏模块化,难以重用和组合不同的组件。
2.2 引出IOC容器
因此我们将引出本次话题的主角IOC,让我们看看IOC是如何解决以上问题的。
解读上图
- Spring 根据配置文件 xml/注解,创建对象,并放入到容器(ConcurrentHashMap)中,并可以完成对象之间的依赖
- 当需要使用某个对象实例的时候,就直接从容器中获取即可
- 这样程序员可以更加关注如何使用对象完成相应的业务
Spring IoC容器通过控制反转(IoC)的方式解决了传统应用程序架构的问题,具有以下优势:
-
解耦和模块化:Spring IoC容器通过依赖注入将组件之间的依赖关系外部化,降低了耦合度,使得应用程序更容易维护和扩展。模块化的设计使得组件可以被独立开发和测试,然后轻松组合成完整的应用程序。
-
灵活的配置:Spring IoC容器允许将配置信息外部化,通常使用XML配置文件、Java注解或Java配置类。这使得配置更加灵活,可以在不修改代码的情况下进行更改。
-
依赖注入:Spring IoC容器通过依赖注入自动管理组件之间的依赖关系,无需手动编写繁琐的依赖管理代码。这简化了开发,提高了可维护性。
-
测试友好:由于组件之间的松散耦合,Spring应用程序更容易进行单元测试和集成测试,从而提高了代码质量和可靠性。
-
重用和扩展性:Spring IoC容器提供了丰富的库和模块,可以轻松地集成各种技术组件,如数据库访问、事务管理、消息队列等,从而增强了应用程序的重用和扩展性。
3. IOC容器使用的快速入门和详细解析
3.1需求说明
- 通过 Spring 的方式【配置文件的方式】,获取 JavaBean:Monster 的对象,并给该对象属性赋值,输出该对象的信息。
3.2完成步骤
- 先下载 Spring 开发包【本示例用的是Spring5.3.8】
- 新建 Java 工程
- 引入 Spring 开发基本包【1.Bean 2.Core 3.Context 4.Expression 5.commons-logging 写日志】
右键add as library - 创建 Monster.java 类
package com.hspedu.spring.bean;
/**
* @Author ZhangShuo
* @Date 2023/10/2 20:30
* @Version 1.0
* JavaBean/entity
*/
public class Monster {
private Integer monsterId;
private String name;
private String skill;
public Monster(Integer monsterId, String name, String skill) {
this.monsterId = monsterId;
this.name = name;
this.skill = skill;
}
//无参构造器一定要给,Spring反射创建对象时,需要使用
public Monster() {
}
public Integer getMonsterId() {
return monsterId;
}
public void setMonsterId(Integer monsterId) {
this.monsterId = monsterId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSkill() {
return skill;
}
public void setSkill(String skill) {
this.skill = skill;
}
@Override
public String toString() {
return "Monster{" +
"monsterId=" + monsterId +
", name='" + name + '\'' +
", skill='" + skill + '\'' +
'}';
}
}
- 创建容器配置文件 beans.xml
<?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.xsd">
<!--
1.配置Monster对象
2.在beans中可以配置多个bean
3.bean代表一个java对象
4.class属性是用来指定类的全路径【spring底层使用反射创建】
5.id属性表示该java对象在spring容器中的id【通过id可以获取到该对象】
6.<property name="monsterId" value="100"/>用于给该对象的属性赋值,没有给就是默认值
-->
<bean class="com.hspedu.spring.bean.Monster" id="monster01">
<property name="monsterId" value="100"/>
<property name="name" value="牛魔王"/>
<property name="skill" value="芭蕉扇"/>
</bean>
</beans>
6.新建测试类spring/src/com/hspedu/spring/test/SpringBeanTest.java
package com.hspedu.spring.test;
import com.hspedu.spring.bean.Monster;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @Author ZhangShuo
* @Date 2023/10/2 20:43
* @Version 1.0
*/
public class SpringBeanTest {
@Test
public void getMonster(){
//解读:
//1.创建容器 ApplicationContext
//2.该容器和容器配置文件是关联的
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
//3.通过getBean获取对应的对象
//默认返回的是Object,但是它的运行类型是Monster
//Object monster01 = ioc.getBean("monster01");
Monster monster01 = (Monster) ioc.getBean("monster01");
//4.输出看效果
System.out.println("monster01="+monster01+" 运行类型"+monster01.getClass());
System.out.println("monster01="+monster01+" 获取对应的属性 name="+monster01.getName()+
"monsterId="+monster01.getMonsterId());
//5.也可以在获取的时候直接指定Class类型,上面获取过一次,下面也可以再次获取
Monster monster011 = ioc.getBean("monster01", Monster.class);
System.out.println("monster011="+monster011);
System.out.println("monster011.name="+monster011.getName());
System.out.println("ok~~~");
}
}
输出:
3.3注意事项和细节
- 说明
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
为什么能读取到 beans.xml?
ClassPathXmlApplicationContext 是按照类加载路径读取的,而类加载路径是spring/out/production/spring,因此读到的是spring/out/production/spring/beans.xml - 解释一下类加载路径,输出一下
//验证类加载路径
@Test
public void classPath(){
File file = new File(this.getClass().getResource("/").getPath());
//这个地方就能看到类的加载路径
System.out.println("file="+file);
}
输出:file=/Users/zhangshuo/MyJavaProject/hspedu_spring/spring/out/production/spring
3. debug 看看 Spring 容器结构/机制