【spring】spring_IOC和DI
Spring概述
Spring是分层的 Java SE/EE应用full-stack轻量级开源框架,以IOC(Inverse Of Control:反转控制)和 AOP(Aspect Oriented Programming:面向切面编程)为内核。
提供了表现层SpringMVC和持久层Spring JDBCTemplate以及业务层事务管理等众多的企业级应用技术,简单的说就是简化java开发。还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的Java EE 企业应用开源框架。
这里需要理解几个基本概念
控制反转 :不是什么技术,它是一种降低对象耦合关系的一种设计思想。在Java开发中,Ioc意味着将对象交给容器控制
其中最常见的方式叫做 依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)
依赖注入 :指容器负责创建和维护对象之间的依赖关系,而不是通过对象本身负责自己的创建和解决自己的依赖。在当前类需要用到其他类的对象,由Spring为我们提供(就是省略
new
),我们只需要在配置中说明。面向切面编程 : 自己的理解:提取相同的代码,例如后台所有内容都需要登录为前提,呢么这个判断是否登录的方法就是面向切面编程
- 作用:在程序运行期间,在不修改源码的情况下对方法进行功能增强。
ioc和依赖注入的由来
我们知道在面向对象设计的软件系统中,它的底层都是由N个对象构成的,各个对象之间通过相互合作,最终实现系统地业务逻辑,例如一个怀表,它拥有多个独立的齿轮,这些齿轮相互啮合在一起,协同工作,共同完成某项任务。我们可以看到,在这样的齿轮组中,如果有一个齿轮出了问题,就可能会影响到整个齿轮组的正常运转。这就是耦合关系非常紧密的关系,所以为了解决,软件专家Michael Mattson 1996年提出了IOC理论,用来实现对象之间的“解耦”,目前这个理论已经被成功地应用到实践当中。
IOC是Inversion of Control的缩写,多数书籍翻译成“控制反转”。IOC理论提出的观点大体是这样的:借助于“第三方”实现具有依赖关系的对象之间的解耦。如下图:
大家看到了吧,由于引进了中间位置的“第三方”,也就是IOC容器,使得A、B、C、D这4个对象没有了耦合关系,齿轮之间的传动全部依靠“第三方”了,全部对象的控制权全部上缴给“第三方”IOC容器,所以,IOC容器成了整个系统的关键核心,它起到了一种类似“粘合剂”的作用,把系统中的所有对象粘合在一起发挥作用,如果没有这个“粘合剂”,对象与对象之间会彼此失去联系,这就是有人把IOC容器比喻成“粘合剂”的由来。
我们再来做个试验:把上图中间的IOC容器拿掉,然后再来看看这套系统:
我们现在看到的画面,就是我们要实现整个系统所需要完成的全部内容。这时候,A、B、C、D这4个对象之间已经没有了耦合关系,彼此毫无联系,这样的话,当你在实现A的时候,根本无须再去考虑B、C和D了,对象之间的依赖关系已经降低到了最低程度。所以,如果真能实现IOC容器,对于系统开发而言,这将是一件多么美好的事情,参与开发的每一成员只要实现自己的类就可以了,跟别人没有任何关系!
我们再来看看,控制反转(IOC)到底为什么要起这么个名字?我们来对比一下:
-
软件系统在没有引入IOC容器之前,如图1所示,对象A依赖于对象B,那么对象A在初始化或者运行到某一点的时候,自己必须主动去创建对象B或者使用已经创建的对象B。无论是创建还是使用对象B,控制权都在自己手上。
-
软件系统在引入IOC容器之后,这种情形就完全改变了,如图3所示,由于IOC容器的加入,对象A与对象B之间失去了直接联系,所以,当对象A运行到需要对象B的时候,IOC容器会主动创建一个对象B注入到对象A需要的地方。
通过前后的对比,我们不难看出来:对象A获得依赖对象B的过程,由主动行为变为了被动行为,控制权颠倒过来了,这就是“控制反转”这个名称的由来。
2004年,Martin Fowler探讨了同一个问题,既然IOC是控制反转,那么到底是“哪些方面的控制被反转了呢?”,经过详细地分析和论证后,他得出了答案:“获得依赖对象的过程被反转了”。控制被反转之后,获得依赖对象的过程由自身管理变为了由IOC容器主动注入。于是,他给“控制反转”取了一个更合适的名字叫做“依赖注入(Dependency Injection)”。他的这个答案,实际上给出了实现IOC的方法:注入。所谓依赖注入,就是由IOC容器在运行期间,动态地将某种依赖关系注入到对象之中。
所以,依赖注入(DI)和控制反转(IOC)是从不同的角度的描述的同一件事情,就是指通过引入IOC容器,利用依赖关系注入的方式,实现对象之间的解耦。
Spring的优势
1) 方便解耦,简化开发
通过Spring提供的IOC容器,可以将对象间的依赖关系交由Spring进行控制,避免硬编码所造成的过度耦合。
用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。
2) AOP 编程的支持
通过Spring的AOP功能,方便进行面向切面编程,许多不容易用传统OOP实现的功能可以通过AOP轻松实现。
3) 声明式事务的支持
可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务管理,提高开发效率和质量
4) 方便程序的测试
可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可做的事情。
Spring的体系结构
Spring配置文件 => IoC容器
Bean标签基本配置
作用:通过配置将对象的创建交给Spring容器进行管理。
默认情况下它调用的是类中的无参构造函数,如果没有无参构造函数则不能创建成功。
相关属性
- id:Bean实例在Spring容器中的唯一标识;
- class:Bean的全限定名称。
Bean标签范围配置
scope,指对象的作用范围,取值如下:
取值范围 | 说明 |
---|---|
singleton | 默认值,单例的 |
prototype | 多例的 |
request | WEB项目中,Spring创建一个Bean的对象,将对象存入到request域中 |
session | WEB项目中,Spring创建一个Bean的对象,将对象存入到session域中 |
global session | WEB项目中,应用在Portlet环境,如果没有Portlet环境那么globalSession相当于session |
当scope的取值为singleton时
- Bean的实例化个数:1个
- Bean的实例化时机:当Spring核心文件被加载时,实例化配置的Bean实例
- Bean的生命周期:
- 对象创建:当应用加载,创建容器时,对象就被创建了;
- 对象运行:只要容器在,对象一直活着;
- 对象销毁:当应用卸载,销毁容器时,对象就被销毁了。
当scope的取值为prototype时
- Bean的实例化个数:多个
- Bean的实例化时机:当调用getBean()方法时实例化Bean
- Bean的生命周期:
- 对象创建:当使用对象时,创建新的对象实例;
- 对象运行:只要对象在使用中,就一直活着;
- 对象销毁:当对象长时间不用时,被 Java 的垃圾回收器回收了。
bean生命周期配置
init-method:指定类中的初始化方法名称
destroy-method:指定类中销毁方法名称
bean实例化三种方式
使用无参构造方法实例化
根据默认无参构造方法来创建类对象,如果bean中没有默认无参构造函数,将会创建失败。
<bean id="userDao" class="com.qfedu.dao.impl.UserDaoImpl">
工厂静态方法实例化
创建静态工厂
public class StaticBeanFactory {
public static UserDao getUserDaoImpl() {
return new UserDaoImpl();
}
}
在Spring配置文件中配置
<!-- 静态工厂初始化 -->
<bean id="userDao" class="com.qfedu.factory.StaticBeanFactory" factory-method="getUserDaoImpl"></bean>
测试
//演示通过静态工厂创建Bean
@Test
public void test1() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = (UserDao) context.getBean("userDao");
userDao.save();
}
工厂实例方法实例化
创建动态工厂
public class DynamicBeanFactory {
public UserDao getUserDao() {
return new UserDaoImpl();
}
}
在Spring配置文件中配置
<bean id="factory" class="com.qfedu.factory.DynamicBeanFactory"></bean>
<bean id="userDao" factory-bean="factory" factory-method="getUserDao"></bean>
测试同上
DI(依赖注入)
依赖注入:Dependency Injection ,指容器负责创建和维护对象之间的依赖关系,而不是通过对象本身负责自己的创建和解决自己的依赖。在当前类需要用到其他类的对象,由Spring为我们提供,我们只需要在配置中说明。
业务层和持久层的依赖关系,在使用 Spring 之后,就让 Spring 来维护了。
简单的说,就是坐等框架把持久层对象传入业务层,而不用我们自己去获取。
即service中的private UserDao userDao = new 对象()
,变成private UserDao userDao;
,就是省略new
构造方法注入
- 创建接口UserService和实现类UserServiceImpl
public interface UserService {
void save();
}
public class UserServiceImpl implements UserService {
//这里一定要有该属性,我们最终的目的是让该属性关联一个UserDaoImpl的对象
private UserDao userDao;
public UserServiceImpl() {
}
//一定要有该有参的构造方法,通过该方法完成依赖注入
public UserServiceImpl(UserDao userDao) {
this.userDao = userDao;
}
}
- 在Spring配置文件中配置
<bean id="userDao" class="com.qfedu.dao.impl.UserDaoImpl"></bean>
<bean id="userService" class="com.qfedu.service.impl.UserServiceImpl">
<!-- 构造方法注入,通过ref将id为“userDao”的bean传递给了UserServiceImpl构造方法的userDao形参 -->
<constructor-arg name="userDao" ref="userDao" />
</bean>
- 测试
@Test
public void test3() {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
UserService userService = (UserService)context.getBean("userService");
userService.save();
}
set方法注入(重点)
在UserServiceImpl中添加set方法
public class UserServiceImpl implements UserService {
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
在Spring配置文件中配置
<bean id="userService" class="UserServiceImpl">
<!-- set方法注入 -->
<property name="userDao" ref="userDao"></property>
</bean>
测试方法同上
p名称空间注入
p命名空间注入本质也是set方法注入,但比起上述的set方法注入更加方便,主要体现在配置文件中
引入P命名空间
xmlns:p="http://www.springframework.org/schema/p"
在Spring配置文件中配置
<!-- p名称空间注入 -->
<bean id="userService" class="com.qfedu.service.impl.UserServiceImpl" p:userDao-ref="userDao"/>
依赖注入其他类型
上面的案例,我们学习了如何注入引用类型的数据,除了引用数据类型,普通数据类型,集合数据类型也可以注入。
普通数据类型注入
创建Department实体类
//表示部门的实体类
public class Department {
private Integer id;//部门编号
private String name;//部门名称
private String desc;//部门描述
//set、get方法
//toString方法
}
- 在Spring配置文件中配置
<!--
通过Spring的IOC容器创建Department类的对象,并为其属性注入值
无参构造方法实例化
-->
<bean id="department" class="com.qfedu.entity.Department">
<!-- set方法注入
value:简单类型
-->
<property name="id" value="1" />
<property name="name" value="研发部" />
<property name="desc" value="项目研发" />
</bean>
测试
@Test
public void test6() {
//解析配置文件 -- 创建对象 -- 对象交给Spring的IOC容器进行管理
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//获取Department的对象
Department department = (Department)context.getBean("department");
//打印对象
System.out.println(department);
}
引用类型注入
- 创建实体类Department
//表示部门的实体类
public class Department {
private Integer id;//部门编号
private String name;//部门名称
private String desc;//部门描述
private Address address;//部门地址
//set、get
//toString
}
在Spring配置文件中配置
<bean id="address" class="com.qfedu.entity.Address">
<property name="province" value="山东省" />
<property name="city" value="青岛市" />
<property name="county" value="市北区" />
<property name="street" value="龙城路" />
<property name="no" value="31号" />
</bean>
<!--
通过Spring的IOC容器创建Department类的对象,并为其属性注入值
无参构造方法实例化
-->
<bean id="department" class="com.qfedu.entity.Department">
<!-- set方法注入
value:简单类型
-->
<property name="id" value="1" />
<property name="name" value="研发部" />
<property name="desc" value="项目研发" />
<!-- set方法注入
ref:引用类型
-->
<property name="address" ref="address" />
</bean>
测试同上
集合数据类型(List<String>
)的注入
创建Employee实体类
//表示员工的实体类
public class Employee {
private Integer id;//员工编号
private String name;//姓名
private Integer age;//年龄
private String gender;//性别
private List<String> hobby;//爱好
//set、get方法
//toString方法
}
- 在Spring配置文件中配置
<!--
通过Spring的IOC容器创建Employee类的对象,并为其属性注入值
无参构造方法实例化
-->
<bean id="e1" class="com.qfedu.entity.Employee">
<property name="id" value="1" />
<property name="name" value="zs" />
<property name="age" value="30" />
<property name="gender" value="男" />
<!-- 集合类型注入 -->
<property name="hobby">
<list>
<value>学习1</value>
<value>学习2</value>
<value>学习3</value>
</list>
</property>
</bean>
测试同上
集合数据类型(List<对象>
)的注入
- 修改Department实体类
//表示部门的实体类
public class Department {
private Integer id;//部门编号
private String name;//部门名称
private String desc;//部门描述
private Address address;//部门地址
private List<Employee> emps;//普通员工
//set、get方法
//toString方法
}
- 在Spring配置文件中配置
<bean id="e1" class="com.qfedu.entity.Employee">
<property name="id" value="1" /> <property name="name" value="zs" /> <property name="age" value="30" /> <property name="gender" value="男" />
<!-- 集合类型注入 -->
<property name="hobby"> <list> <value>学习1</value> <value>学习2</value> <value>学习3</value> </list> </property>
</bean>
<bean id="e2" class="com.qfedu.entity.Employee">
<property name="id" value="1" /> <property name="name" value="ls" /> <property name="age" value="31" /> <property name="gender" value="男" />
<!--集合类型注入 -->
<property name="hobby"> <list> <value>爬山</value> <value>游泳</value> <value>网游</value> </list> </property>
</bean>
<bean id="department" class="com.qfedu.entity.Department">
<!-- set方法注入
value:简单类型
-->
<property name="id" value="1" />
<property name="name" value="研发部" />
<property name="desc" value="项目研发" />
<!-- set方法注入
ref:引用类型
-->
<property name="address" ref="address" />
<property name="emps">
<list>
<ref bean="e1" />
<ref bean="e2" />
</list>
</property>
</bean>
测试同上
集合数据类型(Map<String>
)的注入
- 修改Department,添加属性
//表示部门的实体类
public class Department {
private Integer id;//部门编号
private Map<String, Employee> leader;//部门主管
//set、get
//toString
}
- 在Spring配置文件中配置
<bean id="e1" class="com.qfedu.entity.Employee">
<property name="id" value="1" />
</bean>
<bean id="e2" class="com.qfedu.entity.Employee">
<property name="id" value="2" />
</bean>
<bean id="department" class="com.qfedu.entity.Department">
<property name="id" value="1" />
<property name="leader">
<map>
<entry key="CEO" value-ref="e1" />
<entry key="CTO" value-ref="e2" />
</map>
</property>
</bean>
测试同上
集合数据类型(properties)的注入
创建实体类JdbcConfig,添加Properties
package com.qfedu.entity;
import java.util.Properties;
public class JdbcConfig {
private Properties config;
public Properties getConfig() {
return config;
}
public void setConfig(Properties config) {
this.config = config;
}
@Override
public String toString() {
return "JdbcConfig{" +
"config=" + config +
'}';
}
}
在Spring配置文件中配置
<bean id="jdbcConfig" class="com.qfedu.entity.JdbcConfig">
<!-- Properties类型的注入 -->
<property name="config">
<props>
<prop key="driverName">com.mysql.jdbc.Driver</prop>
<prop key="url">jdbc:mysql://localhost:3306/test</prop>
<prop key="username">root</prop>
<prop key="password">root</prop>
</props>
</property>
</bean>
测试
@Test
public void test8() {
//解析配置文件 -- 创建对象 -- 对象交给Spring的IOC容器进行管理
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//获取Employee的对象
JdbcConfig config = (JdbcConfig)context.getBean("jdbcConfig");
//打印对象
System.out.println(config);
}
引入其他配置文件
实际开发中,Spring的配置内容非常多,这就导致Spring配置很繁杂且体积很大,所以,可以将部分配置拆解到其他配置文件中,而在Spring主配置文件通过import标签进行加载。
<import resource="applicationContext-xxx.xml"/>
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)