首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >【Spring上】Spring超详细学习(IOC、DI)

【Spring上】Spring超详细学习(IOC、DI)

作者头像
超级苦力怕
发布2025-12-24 08:58:07
发布2025-12-24 08:58:07
2060
举报

通过知识点的讲解,帮助未入门的人快速掌握Spring的IOC和DI部分。

1. 总览与学习路径

  • 核心主线
    • IoC/DI:外部化创建对象与依赖管理,解耦与配置化/注解化驱动。
    • AOP:在不改动原始代码的前提下增强方法(日志、鉴权、监控、事务)。
    • 声明式事务:通过注解和事务管理器保障一系列数据库操作同成同败。
    • 整合:第三方数据源、MyBatis、单元测试(JUnit)。
  • 学习顺序:IoC/DI → Bean/容器/生命周期 → 外部化配置 → 注解开发 → 第三方 Bean 管理 → AOP → 事务 → 整合 MyBatis/JUnit → 实战与最佳实践。

2.Spring相关概念

Spring 是一个开源的 Java 企业级应用开发框架,它的核心目标是简化企业级应用开发的复杂性,提供一站式的解决方案。

2.1 Spring Framework 系统架构图

(1)核心层

  • Core Container:核心容器,这个模块是Spring最核心的模块,其他的都需要依赖该模块

(2)AOP层

  • AOP:面向切面编程,它依赖核心层容器,目的是在不改变原有代码的前提下对其进行功能增强
  • Aspects:AOP是思想,Aspects是对AOP思想的具体实现

(3)数据层

  • Data Access:数据访问,Spring全家桶中有对数据访问的具体实现技术
  • Data Integration:数据集成,Spring支持整合其他的数据层解决方案,比如Mybatis
  • Transactions:事务,Spring中事务管理是Spring AOP的一个具体实现,也是后期学习的重点内容

(4)Web层

  • 这一层的内容在SpringMVC框架,不做讲解

(5)Test层

  • Spring主要整合了JUnit来完成单元测试和集成测试
2.2 核心思想
2.2.1 快速理解

在Spring核心概念这部分内容中主要包含IoC/DIIoC容器Bean

概念

IoC (Inversion of Control) 控制反转:对象的创建控制权由程序转移到外部,这种思想称为控制反转。

Bean:在Spring中提供了IoC容器,而用于充当外部,负责对象的创建、初始化等工作,被创建或被管理的对象在IoC容器中的统称为Bean。

DI(Dependency Injection)依赖注入:在容器中建立bean与bean之间的依赖关系的整个过程,称为依赖注入

目标:充分解耦

  1. 使用IoC容器管理bean(IoC)
  2. 在IoC容器内将有依赖关系的bean进行关系绑定(DI)

最终效果:使用对象时不仅可以直接从IoC容器中获取,并且获取到的bean已经绑定了所有依赖关系

理解: 在没有学习IoC之前,我们写业务层示例如下

这导致了我们写新的数据层时,需要重新编译业务层,而我们通过外部提供对象,则不需要提供多个数据层,也不需要主要去new对象了。

在Spring中提供了IoC容器,存入了Service和Dao层的对象,并绑定依赖,当使用时,直接从IoC容器中获取,效果也是和上图一样的。

2.2.2 核心思想总结

什么IOC/DI思想?

  • IOC:控制反转,控制反转的是对象的创建权
  • DI:依赖注入,绑定对象与对象之间的依赖关系

什么是IOC容器?

Spring创建了一个容器用来存放所创建的对象,这个容器就叫IOC容器

什么是Bean?

容器中所存放的一个个对象就叫Bean或Bean对象

3.入门案例

3.1 IoC入门案例

思路分析

1.IoC容器管理什么? 答:主要管理项目中所使用到的类对象,比如(Service和Dao) 2.IoC容器如何将被管理的对象告知IoC容器? 答:使用配置文件 3.被管理的对象交给IoC容器,如何获取到IoC容器? 答:Spring框架提供相应的接口 4.IoC容器得到后,如何从容器中获取bean? 答:调用Spring框架提供对应接口中的方法 5.使用Spring导入哪些坐标? 答:用别人的东西,就需要在pom.xml添加对应的依赖

入门案例

1.导入Spring坐标

代码语言:javascript
复制
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>5.2.10.RELEASE</version>
</dependency>

2.定义Spring管理的类(接口)

代码语言:javascript
复制
public interface BookService {
    public void save();
}
代码语言:javascript
复制
public class BookServiceImpl implements BookService {
    private BookDao bookDao = new BookDaoImpl();

    public void save() {
        bookDao.save();
    }
}

3.创建Spring配置文件,配置对应类作为Spring管理的Bean

代码语言:javascript
复制
<?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.导入spring的坐标spring-context,对应版本是5.2.10.RELEASE-->

    <!--2.配置bean-->
    <!--bean标签标示配置bean
    id属性标示给bean起名字
    class属性表示给bean定义类型-->
    <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
</beans>

bean定义时id属性在同一个上下文中不能重复

4.初始化IoC容器(Spring核心容器/Spring容器),通过容器获取Bean

代码语言:javascript
复制
public class App2 {
    public static void main(String[] args) {
        //3.获取IoC容器
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        //4.获取bean(根据bean配置id获取)
        BookDao bookDao = (BookDao) ctx.getBean("bookDao");
        bookDao.save();
    }
}
3.2 DI入门案例

思路分析

1.要想实现依赖注入,必须要基于IoC管理Bean DI的入门案例要依赖于前面IoC的入门案例 2.Service中使用new形式创建的Dao对象是否保留? 答:需要删除掉,最终要使用IoC容器中的bean对象 3.Service中需要的Dao对象如何进入到Service中? 答:在Service中提供方法,让Spring的IoC容器可以通过该方法传入bean对象 4.Service与Dao间的关系如何描述? 答:使用配置文件

入门案例 1.删除使用new形式创建对象的代码

代码语言:javascript
复制
public class BookServiceImpl implements BookService {
    //删除业务层中使用new的方式创建的dao对象
    private BookDao bookDao;
	 //原代码
	 //private BookDao bookDao = new BookDaoImpl();
    public void save() {
        bookDao.save();
    }
}

2.提供依赖对象对应的setter方法

代码语言:javascript
复制
public class BookServiceImpl implements BookService {
    //提供对应的set方法
    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }
}

3.配置service与dao之间的关系(xml)

代码语言:javascript
复制
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
    <!--配置service与dao的关系-->
    <!--property标签表示配置当前bean的属性
    name属性表示配置哪一个具体的属性
    ref属性表示参照哪一个bean-->
    <property name="bookDao" ref="bookDao"/>
</bean>

4.IoC 相关内容

4.1 bean基础配置
4.11 bean基础配置(id和class)

bean的基础配置(前文使用过)

代码语言:javascript
复制
<bean id="" class=""/>

其中,bean标签的功能、使用方式以及id和class属性的作用,如图所示

在这里插入图片描述
在这里插入图片描述

重点:bean标签的id和class属性的使用 注意:class 不能写成接口名如 BookDao,应填写实现类的类全名(完全限定类名),因为接口无法创建对象

4.12 bean的name属性

问题:bean设置id时,id必须唯一,如果产生分歧如何解决?

答:可以通过bean的别名配置解决,别名的配置说明如下

请添加图片描述
请添加图片描述

步骤示例 1.配置别名 打开xml配置文件

代码语言:javascript
复制
<?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">

    <!--name:为bean指定别名,别名可以有多个,使用逗号,分号,空格进行分隔-->
    <bean id="bookService" name="service service4 bookEbi" class="com.itheima.service.impl.BookServiceImpl">
        <property name="bookDao" ref="bookDao"/>
    </bean>

    <!--scope:为bean设置作用范围,可选值为单例singleton,非单例prototype-->
    <bean id="bookDao" name="dao" class="com.itheima.dao.impl.BookDaoImpl" scope="prototype"/>
</beans>

步骤2:根据名称容器中获取bean对象

代码语言:javascript
复制
public class AppForName {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        //此处根据bean标签的id属性和name属性的任意一个值来获取bean对象
        BookService bookService = (BookService) ctx.getBean("service4");
        bookService.save();
    }
}

步骤3:运行程序即可

注意事项:

bean依赖注入的ref属性指定bean,必须在容器中存在,如果不存在,就会报错,报错为NoSuchBeanDefinitionException 同样,当看到这个错误,要意识到时id或者name无法获取

4.13 bean作用范围scope配置

bean的作用范围是bean属性配置的重点内容,它的作用时可以改变IoC容器中对象为单例或非单例。

在这里插入图片描述
在这里插入图片描述

使用方法: 在Spring的配置文件中,修改<bean>的scope属性

代码语言:javascript
复制
<bean id="bookDao" name="dao" class="com.itheima.dao.impl.BookDaoImpl" scope=""/>

将scope更改为singleton或prototype,前者代表单例,后者代表非单例

测试方法: 准备一个类,在main方法中验证

代码语言:javascript
复制
public class AppForScope {
    public static void main(String[] args) {
        ApplicationContext ctx = new 
            ClassPathXmlApplicationContext("applicationContext.xml");

        BookDao bookDao1 = (BookDao) ctx.getBean("bookDao");
        BookDao bookDao2 = (BookDao) ctx.getBean("bookDao");
        System.out.println(bookDao1);
        System.out.println(bookDao2);
    }
}

当scope为singleton单例时

代码语言:javascript
复制
com.itheima.dao.impl.BookDaoImpl@4c1d9d4b
com.itheima.dao.impl.BookDaoImpl@4c1d9d4b

当scope为prototype多例时

代码语言:javascript
复制
com.itheima.dao.impl.BookDaoImpl@4c1d9d4b
com.itheima.dao.impl.BookDaoImpl@7b227d8d

思考知识点

为什么bean默认为单例

bean为单例的意思是在Spring的IoC容器中只会有该类的一个对象 bean对象只有一个就避免了对象的频繁创建与销毁,达到了bean对象的复用,性能高

bean在容器中是单例的,会不会产生线程安全问题

如果对象是有状态对象,即该对象有成员变量可以用来存储数据的,因为所有的请求线程共用一个bean对象,会存在线程安全问题 如果对象是无状态对象,即该对象没有成员变量没有进行数据存储的,因方法中的局部变量在方法调用完成后会被销毁,所以不会存在线程安全问题。

哪些bean对象适合交给容器进行管理

表现层对象、业务层对象、数据层对象、工具对象

哪些bean对象不适合交给容器进行管理?

封装示例的域对象,因为会引发线程安全问题,所以不适合

4.14 bean基础配置总结
在这里插入图片描述
在这里插入图片描述
4.2 bean实例化

bean本质上就是对象,创建bean使用构造方法完成。

4.21 构造方法实例化

通过提供可访问的构造方法,并配置到Spring容器即可

示例: 提供可访问的构造方法

代码语言:javascript
复制
public class BookDaoImpl implements BookDao {

/*    public BookDaoImpl() {
        System.out.println("book dao constructor is running ....");
    }
    */
    public void save() {
        System.out.println("book dao save ...");
    }
}

配置

代码语言:javascript
复制
<bean
	id="bookDao"
	class="com.itheima.dao.impl.BookDaoImpl"
	/>

测试类

代码语言:javascript
复制
public class AppForInstanceBook {
    public static void main(String[] args) {
        ApplicationContext ctx = new 
            ClassPathXmlApplicationContext("applicationContext.xml");
        BookDao bookDao = (BookDao) ctx.getBean("bookDao");
        bookDao.save();
    }
}

注意:若无参构造方法不存在,会抛出异常BeanCreationException 默认提供无参构造;若仅保留有参构造,需显式声明无参构造或使用构造器注入

4.22 静态工厂实例化(了解)

静态工厂实例化通常是用来兼容早期的老系统,所以了解为主。

示例: 创建一个工厂类OrderDaoFactory并提供一个静态方法

代码语言:javascript
复制
//静态工厂创建对象
public class OrderDaoFactory {
    public static OrderDao getOrderDao(){
        return new OrderDaoImpl();
    }
}

配置

class:工厂类的类全名 factory-method:具体工厂类中创建对象的方法名

代码语言:javascript
复制
<bean
	id="orderDao" 
	class="com.itheima.factory.OrderDaoFactory"
 	factory-method="getOrderDao"
	 />

测试类

代码语言:javascript
复制
public class AppForInstanceOrder {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

        OrderDao orderDao = (OrderDao) ctx.getBean("orderDao");

        orderDao.save();

    }
}

拓展内容:

工厂静态方法除了new对象,还可以做一些其他的业务操作,以打印语句为例:

代码语言:javascript
复制
public class OrderDaoFactory {
    public static OrderDao getOrderDao(){
        System.out.println("factory setup....");//模拟必要的业务操作
        return new OrderDaoImpl();
    }
}
4.23 实例工厂与FactoryBean
实例工厂(了解)

实例工厂常规配置较繁琐,了解即可

示例: 实例化工厂

代码语言:javascript
复制
public class UserDaoFactory {
    public UserDao getUserDao(){
        return new UserDaoImpl();
    }
}

配置

第一行是创建实例化工厂对象 第二行是调用对象中的方法创建bean

代码语言:javascript
复制
   <bean id="userFactory" class="com.itheima.factory.UserDaoFactory"/>
   <bean id="userDao" factory-method="getUserDao" factory-bean="userFactory"/>

测试类

代码语言:javascript
复制
public class AppForInstanceUser {
    public static void main(String[] args) {
        ApplicationContext ctx = new 
            ClassPathXmlApplicationContext("applicationContext.xml");
        UserDao userDao = (UserDao) ctx.getBean("userDao");
        userDao.save();
    }
}
FactoryBean(掌握)

FactoryBean常被用在Spring整合框架中,所以需要理解掌握。

示例: 1.创建UserDaoFactoryBean的类,实现FactoryBean接口,重写方法

代码语言:javascript
复制
public class UserDaoFactoryBean implements FactoryBean<UserDao> {
    //代替原始实例工厂中创建对象的方法
    public UserDao getObject() throws Exception {
        return new UserDaoImpl();
    }
    //返回所创建类的Class对象
    public Class<?> getObjectType() {
        return UserDao.class;
    }
}

2.配置

代码语言:javascript
复制
<bean id="userDao" class="com.itheima.factory.UserDaoFactoryBean"/>

3.测试类(不需要变)

代码语言:javascript
复制
public class AppForInstanceUser {
    public static void main(String[] args) {
        ApplicationContext ctx = new 
            ClassPathXmlApplicationContext("applicationContext.xml");
        UserDao userDao = (UserDao) ctx.getBean("userDao");
        userDao.save();
    }
}

在FactoryBean接口中,有着三个方法

代码语言:javascript
复制
T getObject() throws Exception;

Class<?> getObjectType();

default boolean isSingleton() {
		return true;
}

方法一:getObject(),重写后,在方法中进行对象创建并返回 方法二:getObjectType(),重写后,主要返回的是被创建类的Class对象 方法三:已经给了默认值,若重写此方法,可以控制是否单例,改为false,为非单例

4.24 bean实例化总结

bean是如何创建的

答:构造方法

Spring的IOC实例化对象的三种方式分别是

答:构造方法,静态方法,实例方法(含FactoryBean),主要掌握构造方法FactoryBean 注:构造方法在类中默认会提供,但如果重写构造方法,默认的就会消失。因此,重写构造方法时,需要把默认的构造方法也重写。

4.3 bean的生命周期
4.31 概述

bean最后一个相关知识是生命周期,对于这个知识点,主要围绕着bean生命周期控制来讲解

生命周期:从创建到消亡的完整过程 bean生命周期:bean从创建到消亡的完整过程 bean生命周期控制:从bean创建后到销毁前做一些事情

4.32 生命周期设置

在这里有两种方法,第一种是自定义生命周期方法配置方式,第二种是实现接口的生命周期控制方式

自定义方法

有2个步骤,一步是提供生命周期控制方法,一步是配置生命周期控制方法 示例: 1.提供生命周期控制方法

代码语言:javascript
复制
public class BookDaoImpl implements BookDao {
    public void save() {
        System.out.println("book dao save ...");
    }
    //表示bean初始化对应的操作
    public void init(){
        System.out.println("init...");
    }
    //表示bean销毁前对应的操作
    public void destroy(){
        System.out.println("destroy...");
    }
}

2.配置生命周期控制方法

代码语言:javascript
复制
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl" init-method="init" destroy-method="destroy"/>

3.测试类

代码语言:javascript
复制
    public static void main( String[] args ) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

        BookDao bookDao = (BookDao) ctx.getBean("bookDao");
        bookDao.save();

运行结果

代码语言:javascript
复制
init...
book dao save ...
接口方法

Spring提供了两个接口完成生命周期的控制,可以不用再配置init-methoddestroy-method 实现方法:添加两个接口InitializingBeanDisposableBean并实现接口中的两个方法afterPropertiesSetdestroy即可

示例: 更改实现类,添加接口和方法

代码语言:javascript
复制
public class BookServiceImpl implements BookService, InitializingBean, DisposableBean {
    private BookDao bookDao;
    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }
    public void save() {
        System.out.println("book service save ...");
        bookDao.save(); 
    }
    public void destroy() throws Exception {
        System.out.println("service destroy");
    }
    public void afterPropertiesSet() throws Exception {
        System.out.println("service init");
    }
}

运行结果

代码语言:javascript
复制
init...
set .....
service init
book dao save ...
service destroy

拓:afterPropertiesSet,直译属性设置之后,在运行时,这个方法会在类种属性设置之后执行 拓:afterPropertiesSet,直译属性设置之后,在运行时,这个方法会在类中属性设置之后执行

4.33 关闭容器

在上述过程中,我们发现destroy方法并没有执行,这里涉及到如下知识点

Spring的IoC容器是运行在JVM中 获取main方法后,JVM启动,Spring加载配置文件生成IoC容器,获取bean对象并调方法执行 main方法执行完,JVM退出,但bean还没有销毁,无法执行destroy方法

那如何解决这个问题?我们可以通过close关闭容器或注册钩子关闭容器

close关闭容器

由于ApplicationContext种没有close方法,因此我们需要把测试类中的ApplicationContext更换成ClassPathXmlApplicationContext即可

close()最早在ConfigurableApplicationContext中,是ApplicationContext的子类

代码语言:javascript
复制
ClassPathXmlApplicationContext ctx = new 
    ClassPathXmlApplicationContext("applicationContext.xml");

随后我们便可以调用ctx.close()方法,主动关闭容器了。

测试类示例

代码语言:javascript
复制
    public static void main( String[] args ) {
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

        BookDao bookDao = (BookDao) ctx.getBean("bookDao");
        bookDao.save();
        //关闭容器
        ctx.close();
    }

运行结果

代码语言:javascript
复制
init...
set .....
service init
book dao save ...
service destroy
destroy...
注册钩子关闭容器

原理:在容器未关闭之前,提前设置回调函数,让JVM在退出之前回调函数来关闭容器

示例: 调用ctx的registerShutdownHook()方法

代码语言:javascript
复制
ctx.registerShutdownHook();

测试类

代码语言:javascript
复制
    public static void main( String[] args ) {
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

        BookDao bookDao = (BookDao) ctx.getBean("bookDao");
        bookDao.save();
        //注册关闭钩子函数,在虚拟机退出之前回调此函数,关闭容器
        ctx.registerShutdownHook();
    }
}

运行结果

代码语言:javascript
复制
init...
set .....
service init
book dao save ...
service destroy
destroy...
4.34 bean生命周期小结

关于Spring种对bean生命周期提供了几种方式?

答:两种方式

  • 在配置文件中的bean标签中添加init-methoddestroy-method属性
  • 类实现InitializingBeanDisposableBean接口,这种方式了解下即可。

对于bean的生命周期控制在bean的整个生命周期中所处的位置

答:

  • 初始化容器
    • 1.创建对象(内存分配)
    • 2.执行构造方法
    • 3.执行属性注入(set操作)
    • 4.执行bean初始化方法
  • 使用bean
    • 1.执行业务操作
  • 关闭/销毁容器
    • 1.执行bean销毁方法

关闭容器的两种方式

答:ConfigurableApplicationContext是ApplicationContext的子类

  • close()方法
  • registerShutdownHook()方法

5.DI相关内容

在了解相关知识点之前,有两个问题需要思考

向一个类中传递数据的方式有几种?

答:普通方法(set方法)和构造方法

依赖注入描述了在容器中建立bean与bean之间的依赖关系的过程,但bean运行如果需要数字或字符串呢?

可以将数字或字符串分成引用类型简单类型(基本数据结构和字符串)区分

Spring基于以上知识点,提供了两种注入方式,一种是setter注入,一种是构造器注入,他们又分为了注入简单类型和引用类型

5.1 setter注入
引用类型

引用类型在入门案例已经使用过,不过多讲解 示例 在bean中定义引用类型属性并提供可访问的set方法

代码语言:javascript
复制
public class BookServiceImpl implements BookService{
    private BookDao bookDao;
    //setter注入需要提供要注入对象的set方法
    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }
}

配置中使用property标签和ref属性注入引用类型对象

代码语言:javascript
复制
    <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
        <!--property标签:设置注入属性-->
        <!--name属性:设置注入的属性名,实际是set方法对应的名称-->
        <!--ref属性:设置注入引用类型bean的id或name-->
        <property name="bookDao" ref="bookDao"/>
    </bean>

输出结果

代码语言:javascript
复制
book service save ...
book dao save ...
user dao save ...
简单类型

在bean中定义引用类型属性并提供可访问的set方法

代码语言:javascript
复制
public class BookDaoImpl implements BookDao {

    private String databaseName;
    private int connectionNum;
    //setter注入需要提供要注入对象的set方法
    public void setConnectionNum(int connectionNum) {
        this.connectionNum = connectionNum;
    }
    //setter注入需要提供要注入对象的set方法
    public void setDatabaseName(String databaseName) {
        this.databaseName = databaseName;
    }

    public void save() {
        System.out.println("book dao save ..."+databaseName+","+connectionNum);
    }
}

配置中使用property标签value属性注入简单类型数据

代码语言:javascript
复制
    <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
        <!--property标签:设置注入属性-->
        <!--name属性:设置注入的属性名,实际是set方法对应的名称-->
        <!--value属性:设置注入简单类型数据值-->
        <property name="connectionNum" value="100"/>
        <property name="databaseName" value="mysql"/>
    </bean>

输出结果

代码语言:javascript
复制
book service save ...
book dao save ...mysql,100
user dao save ...
5.2 构造器注入
引用类型

在实现类中主要就是从set方法改为构造方法去传参,主要体现在配置不同 示例: 在Bean中定义引用类型属性并提供可访问的构造方法

代码语言:javascript
复制
public class BookServiceImpl implements BookService{
    private BookDao bookDao;
    private UserDao userDao;

    public BookServiceImpl(BookDao bookDao, UserDao userDao) {
        this.bookDao = bookDao;
        this.userDao = userDao;
    }

    public void save() {
        System.out.println("book service save ...");
        bookDao.save();
        userDao.save();
    }
}

配置中使用constructor-arg标签ref属性注入引用类型对象

代码语言:javascript
复制
     <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl"> 
           <!-- 根据构造方法参数名称注入-->
        <constructor-arg name="userDao" ref="userDao"/>
        <constructor-arg name="bookDao" ref="bookDao"/>
    </bean>

这里的ref指向的是参数,如果改成BookServiceImpl(BookDao bookDao1, UserDao userDao1),则配置中name指向userDao1和bookDao1

运行结果

代码语言:javascript
复制
book service save ...
book dao save ...
user dao save ...
简单类型

在实体类添加构造方法

代码语言:javascript
复制
public class BookDaoImpl implements BookDao {
    private String databaseName;
    private int connectionNum;

    public BookDaoImpl(String databaseName, int connectionNum) {
        this.databaseName = databaseName;
        this.connectionNum = connectionNum;
    }

    public void save() {
        System.out.println("book dao save ..."+databaseName+","+connectionNum);
    }
}

配置

代码语言:javascript
复制
    <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
        <constructor-arg name="databaseName" value="mysql"/>
        <constructor-arg name="connectionNum" value="666"/>
    </bean>
    <bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
    <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
        <constructor-arg name="bookDao" ref="bookDao"/>
        <constructor-arg name="userDao" ref="userDao"/>
    </bean>

输出结果

代码语言:javascript
复制
book service save ...
book dao save ...mysql,666
user dao save ...
拓展问题(了解)

当构造函数中方法的参数名变化后,配置文件中的name属性也要跟着变,这两块存在紧耦合,如何解决。

在这里插入图片描述
在这里插入图片描述

方式一:删除name属性,添加type属性,按照类型注入

代码语言:javascript
复制
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
    <constructor-arg type="int" value="10"/>
    <constructor-arg type="java.lang.String" value="mysql"/>
</bean>
  • 这种方式可以解决构造函数形参名发生变化带来的耦合问题
  • 但是如果构造方法参数中有类型相同的参数,这种方式就不太好实现了

方式二:删除type属性,添加index属性,按照索引下标注入,下标从0开始

代码语言:javascript
复制
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
    <constructor-arg index="1" value="100"/>
    <constructor-arg index="0" value="mysql"/>
</bean>
  • 这种方式可以解决参数类型重复问题
  • 但是如果构造方法参数顺序发生变化后,这种方式又带来了耦合问题

介绍完两种参数的注入方式,具体我们该如何选择呢?

  1. 强制依赖使用构造器进行,使用setter注入有概率不进行注入导致null对象出现
    • 强制依赖指对象在创建的过程中必须要注入指定的参数
  2. 可选依赖使用setter注入进行,灵活性强
    • 可选依赖指对象在创建过程中注入的参数可有可无
  3. Spring框架倡导使用构造器,第三方框架内部大多数采用构造器注入的形式进行数据初始化,相对严谨
  4. 如果有必要可以两者同时使用,使用构造器注入完成强制依赖的注入,使用setter注入完成可选依赖的注入
  5. 实际开发过程中还要根据实际情况分析,如果受控对象没有提供setter方法就必须使用构造器注入
  6. 自己开发的模块推荐使用setter注入
5.3 自动装配(autowire)

在配置文件的编写配置上较麻烦,我们可以通过自动装配去解决这个问题

什么是依赖自动装配?

IoC容器根据bean所依赖的资源在容器中自动查找并注入到bean中的过程称为自动装配

基于自动装配方式,我们可以分为按类型(常用)、按名称、按构造方法、不启用自动装配四种

使用方法:主要是通过改动配置文件 示例: Bean以之前的setter注入为例

代码语言:javascript
复制
public class BookServiceImpl implements BookService{
    private BookDao bookDao;

    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }

    public void save() {
        System.out.println("book service save ...");
        bookDao.save();
    }
}

在配置文件中更改,去除<property>,在<bean>中添加autowire属性

代码语言:javascript
复制
    <bean class="com.itheima.dao.impl.BookDaoImpl"/>
    <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl" autowire="byType"/>

autowire属性可以按照类型(byType),可以按照名称(byName) 使用按类型装配时(byType)必须保障容器中相同类型的bean唯一,推荐使用 使用按名称装配时(byName)必须保障容器中具有指定名称的bean,因变量名与配置耦合,不推荐使用 自动装配优先级低于setter注入与构造器注入,同时出现时自动装配配置失效

5.4 集合注入

这里比较简单,直接放示例 示例: 实现类

代码语言:javascript
复制
public class BookDaoImpl implements BookDao {

    private int[] array;

    private List<String> list;

    private Set<String> set;

    private Map<String,String> map;

    private Properties properties;

     public void save() {
        System.out.println("book dao save ...");

        System.out.println("遍历数组:" + Arrays.toString(array));

        System.out.println("遍历List" + list);

        System.out.println("遍历Set" + set);

        System.out.println("遍历Map" + map);

        System.out.println("遍历Properties" + properties);
    }
	//setter....方法省略,自己使用工具生成
}

配置中添加对应实现类和类型数据

代码语言:javascript
复制
  <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">

注入数组类型数据

代码语言:javascript
复制
<property name="array">
    <array>
        <value>100</value>
        <value>200</value>
        <value>300</value>
    </array>
</property>

注入List类型数据

代码语言:javascript
复制
<property name="list">
    <list>
        <value>itcast</value>
        <value>itheima</value>
        <value>boxuegu</value>
        <value>chuanzhihui</value>
    </list>
</property>

注入Set类型数据

代码语言:javascript
复制
<property name="set">
    <set>
        <value>itcast</value>
        <value>itheima</value>
        <value>boxuegu</value>
        <value>boxuegu</value>
    </set>
</property>

注入Map类型数据

代码语言:javascript
复制
<property name="map">
    <map>
        <entry key="country" value="china"/>
        <entry key="province" value="henan"/>
        <entry key="city" value="kaifeng"/>
    </map>
</property>

注入Properties类型数据

代码语言:javascript
复制
<property name="properties">
    <props>
        <prop key="country">china</prop>
        <prop key="province">henan</prop>
        <prop key="city">kaifeng</prop>
    </props>
</property>

测试类

代码语言:javascript
复制
    public static void main( String[] args ) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

        BookDao bookDao = (BookDao) ctx.getBean("bookDao");

        bookDao.save();
    }

运行结果

代码语言:javascript
复制
book dao save ...
遍历数组:[100, 200, 300]
遍历List[itcast, itheima, boxuegu, chuanzhihui]
遍历Set[itcast, itheima, boxuegu]
遍历Map{country=china, province=henan, city=kaifeng}
遍历Properties{country=china, province=henan, city=kaifeng}

property标签表示setter方式注入,构造方式注入constructor-arg标签内部也可以写<array><list><set><map><props>标签 List的底层也是通过数组实现的,所以<list><array>标签是可以混用 集合中要添加引用类型,只需要把<value>标签改成<ref>标签,这种方式用的比较少

6.结语

这篇文章到此就结束了,如果解决了你的问题,欢迎点赞+关注+评论,支持一下

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-11-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 总览与学习路径
  • 2.Spring相关概念
    • 2.1 Spring Framework 系统架构图
    • 2.2 核心思想
      • 2.2.1 快速理解
      • 2.2.2 核心思想总结
  • 3.入门案例
    • 3.1 IoC入门案例
    • 3.2 DI入门案例
  • 4.IoC 相关内容
    • 4.1 bean基础配置
      • 4.11 bean基础配置(id和class)
      • 4.12 bean的name属性
      • 4.13 bean作用范围scope配置
      • 4.14 bean基础配置总结
    • 4.2 bean实例化
      • 4.21 构造方法实例化
      • 4.22 静态工厂实例化(了解)
      • 4.23 实例工厂与FactoryBean
      • 4.24 bean实例化总结
    • 4.3 bean的生命周期
      • 4.31 概述
      • 4.32 生命周期设置
      • 4.33 关闭容器
      • 4.34 bean生命周期小结
  • 5.DI相关内容
    • 5.1 setter注入
    • 5.2 构造器注入
    • 5.3 自动装配(autowire)
    • 5.4 集合注入
  • 6.结语
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档