第21~22周Java主流框架入门-Spring 1SpringIoc容器与Bean管理

1.Spring IOC 与依赖注入课程笔记

课程简介

本节课介绍了 Spring 框架中的核心概念——IOC(控制反转)和 DI(依赖注入)。Spring 是 Java 生态中最重要的框架之一,几乎所有的 Java 项目开发都会使用它。理解 Spring 的 IOC 和 DI 是进入实际开发的关键。

课程重点

  • Spring 的快速入门:理解 Spring 框架与传统开发方式的区别。
  • Spring IOC 容器的配置:学习通过 XML 配置 Spring IOC 容器。
  • 对象实例化与依赖注入:理解如何实例化对象并实现依赖注入。
  • Spring 单元测试:如何在 Spring 中进行单元测试。

什么是 IOC(控制反转)

  • 定义:IOC(Inversion of Control,控制反转)是一种软件设计理念,它将对象的控制权交给第三方进行管理。
  • 现实案例:举例说明生活中的 IOC 控制反转,例如水果摊老板帮你挑选合适的苹果,这就是一种控制反转的应用场景。消费者不再直接控制对象的创建,而是通过代理人来获取对象。

核心概念

  1. 传统开发中的问题:消费者必须掌握每个对象的细节,并手动创建和管理对象。这增加了耦合度,导致代码复杂且不灵活。
  2. 引入代理人角色(IOC 容器):引入 IOC 后,对象的创建和管理由一个中间角色(如水果摊老板)负责。消费者只需通过这个中间人来获取对象。
  3. 容器管理对象:在 Spring 中,IOC 容器相当于一个“冷冻仓库”,负责存储和管理对象。对象的创建、销毁等生命周期管理都交由容器完成。

核心理念

  • 解耦:IOC 的目的是降低对象之间的耦合度,消费者与对象之间通过容器进行间接交互。
  • 对象灵活性:通过引入代理人,可以让对象的创建和管理变得更加灵活,消费者无需关心对象的创建细节。

什么是 DI(依赖注入)

  • 定义:DI(Dependency Injection,依赖注入)是具体实现 IOC 的技术手段。在程序运行时,将对象的依赖关系注入到对象中。
  • DI 与 IOC 的关系:IOC 是一种设计理念,而 DI 是实现这种理念的具体技术。DI 的本质是通过反射技术动态创建和设置对象的依赖关系。
  • Java 中的 DI 实现:在 Java 中,DI 是通过反射机制实现的,反射可以在运行时动态创建对象、设置属性等。

DI 的应用

  • 反射技术:DI 在 Java 中的底层实现依赖于反射技术,利用反射动态创建对象、设置属性并调用方法。
  • 跨语言支持:DI 是一种普遍的技术,不仅限于 Java,在其他语言(如 Python、.NET)中也有类似的实现技术。

IOC 与 DI 的区别与联系

  • IOC:宏观上的设计理念,强调控制权的反转。
  • DI:具体的实现手段,在代码层面实现 IOC 的设计理念。
  • Java 实现:在 Java 中,依赖注入的实现是通过反射技术。

课程总结

  • IOC 的核心理念:将对象的创建权从消费者转移到代理人(IOC 容器),降低了对象之间的耦合度。
  • 容器管理对象:IOC 容器(如 Spring 容器)负责管理所有对象的创建、销毁和生命周期管理。
  • DI 的实现:DI 是 IOC 的具体实现手段,在 Java 中通过反射技术动态管理对象的依赖关系。
  • Spring 框架中的应用:Spring 框架通过 IOC 和 DI 实现了灵活的对象管理和依赖注入,极大简化了开发工作。

在这里插入图片描述

<?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标签默认通过默认构造方法创建对象-->
    <bean id="apple1" class="com.imooc.spring.ioc.entity.Apple">

    </bean>
    <!--使用带参构造方法实例化对象-->
    <bean name="apple2" class="com.imooc.spring.ioc.entity.Apple">
        <constructor-arg name="title" value="红富士"/>
        <constructor-arg name="color" value="红色"/>
        <constructor-arg name="origin" value="欧洲"/>
        <constructor-arg name="price" value="19.8"/>
    </bean>

    <bean id="apple3" class="com.imooc.spring.ioc.entity.Apple">
        <constructor-arg index="0" value="红富士"/>
        <constructor-arg index="1" value="欧洲"/>
        <constructor-arg index="2" value="红色"/>
        <constructor-arg index="3" value="19.8"/>
    </bean>

    <!--利用静态工厂获取对象-->
    <bean id="apple4" class="com.imooc.spring.ioc.factory.AppleStaticFactory"
          factory-method="createSweetApple"/>

    <!--利用工厂实例方法获取对象-->
    <bean id="factoryInstance" class="com.imooc.spring.ioc.factory.AppleFactoryInstance"/>
    <bean id="apple5" factory-bean="factoryInstance" factory-method="createSweetApple"/>
</beans>
package com.imooc.spring.ioc;

import com.imooc.spring.ioc.entity.Apple;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringApplication {
    public static void main(String[] args) {
        String[] configLocations = new String[]{"classpath:applicationContext.xml","classpath:applicationContext-1.xml"};
        //初始化IoC容器并实例化对象
        ApplicationContext context = new ClassPathXmlApplicationContext(configLocations);
        /*Apple apple4 = context.getBean("apple4", Apple.class);
        System.out.println(apple4.getTitle());
        Apple apple3 = (Apple)context.getBean("apple3");
        System.out.println(apple3.getTitle());*/

        Apple apple2 = context.getBean("apple2", Apple.class);
        System.out.println(apple2.getTitle());
        Apple apple7 = context.getBean("apple7", Apple.class);
        System.out.println(apple7.getTitle());
        Apple apple = context.getBean("com.imooc.spring.ioc.entity.Apple", Apple.class);
        System.out.println(apple.getTitle());
    }
}

2.Spring IOC 容器中的依赖注入

2. 1. 依赖注入的概念

依赖注入是将对象之间的依赖关系通过Spring IOC容器动态注入,而不是通过代码中的 new 来创建依赖对象。

依赖注入的两种形式

  • 基于Setter方法注入:通过Setter方法为对象的属性注入依赖。
  • 基于构造方法注入:通过构造方法为对象注入依赖。

2.2. Setter方法注入对象

2.1 静态数值的注入

  • 使用 property 标签进行属性的静态值注入。

  • property 标签的两个属性:

    • name:表示类中属性的名称。
    • value:表示属性的值。

    例如,为 apple 对象的 title 属性赋值为 sweet apple

    <bean id="sweetApple" class="com.example.Apple">
        <property name="title" value="红富士" />
        <property name="color" value="红色" />
        <property name="origin" value="欧洲" />
    </bean>
    
  • 注意:静态数值也能作为对象注入,因为在Java中,String 和基本类型都有对应的包装类,可以作为对象处理。

2.2 测试Setter方法注入

在测试时,可以通过设置断点或在Setter方法中打印调试信息,验证Spring通过反射调用 set 方法为属性赋值。

例如:

public void setTitle(String title) {
    System.out.println("Title属性设置为:" + title);
    this.title = title;
}

3. Setter方法注入对象依赖

除了静态数值的注入,还可以通过Setter方法注入其他对象。

3.1 引用对象的注入

使用 ref 属性来注入已在IOC容器中定义的Bean。

例如,Lily喜欢吃甜苹果,Lily 对象依赖于 sweetApple 对象:

<bean id="lily" class="com.example.Child">
    <property name="name" value="Lily" />
    <property name="apple" ref="sweetApple" />
</bean>

3.2 测试对象之间的依赖注入

Child 类的 setApple 方法中打印调试信息,观察依赖注入的过程:

package com.imooc.spring.ioc;

import com.imooc.spring.ioc.entity.Apple;
import com.imooc.spring.ioc.entity.Child;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringApplication {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        Apple sweetApple = context.getBean("sweetApple", Apple.class);
//        System.out.println(sweetApple.getTitle());
        Child andy = context.getBean("andy", Child.class);
        andy.eat();
    }
}

通过Spring IOC实现对象解耦

1. 案例背景

通过一个书店应用案例,展示如何利用Spring IOC容器实现对象之间的解耦,进而在团队开发中实现成员之间的解耦。

2.Spring配置文件

创建两个不同的Spring配置文件:

  1. application.context-service.xml:用于保存所有的Service类(业务逻辑)。
  2. application.context-dao.xml:用于保存所有的DAO类(数据访问类)。

3. DAO层的实现

3.1 创建DAO接口

public interface BookDao {
    void insert();
}

3.2 实现DAO接口

public class BookDaoImpl implements BookDao {
    @Override
    public void insert() {
        System.out.println("向mysql表插入一条数据");
    }
}

3.3 DAO XML配置

<bean id="bookDao" class="com.imock.spring.ioc.dao.impl.BookDaoImpl" />

4. Service层的实现

4.1 创建Service类

public class BookService {
    private BookDao bookDao;

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

    public void purchase() {
        System.out.println("正在执行图书采购业务");
        bookDao.insert();
    }
}

4.2 Service XML配置

<bean id="bookService" class="com.imock.spring.ioc.service.BookService">
    <property name="bookDao" ref="bookDao" />
</bean>

5. IOC容器配置与应用程序入口

5.1 创建应用程序入口

public class BookshopApplication {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:application.context-*.xml");
        BookService bookService = (BookService) context.getBean("bookService");
        bookService.purchase();
    }
}

5.2 运行结果

运行后输出:

正在执行图书采购业务
向mysql表插入一条数据

6. 通过Spring IOC实现对象解耦

通过将DAO和Service分离管理,每个人可以负责自己的模块,互不干扰。利用Spring IOC容器自动注入依赖,实现了DAO和Service的解耦。

7. 案例扩展:数据库迁移

7.1 实现新的DAO类(Oracle数据库)

public class BookDaoOracleImpl implements BookDao {
    @Override
    public void insert() {
        System.out.println("向oracle数据库的book表插入一条数据");
    }
}

7.2 替换DAO配置

application.context-dao.xml 中将实现类更换为 BookDaoOracleImpl

<bean id="bookDao" class="com.imock.spring.ioc.dao.impl.BookDaoOracleImpl" />

7.3 运行效果

运行程序时,无需修改Service层代码,程序会自动将数据插入到Oracle数据库中,输出:

正在执行图书采购业务
向oracle数据库的book表插入一条数据

8. 结论

通过Spring IOC容器实现对象的解耦,使得DAO和Service的开发人员可以各司其职,互不干扰。当系统需求变化时,如数据库从MySQL迁移到Oracle,只需修改DAO层的实现,Service层无需改动,极大地提升了系统的可维护性和扩展性。这种解耦对于大型软件工程的团队协作具有重要意义,能大幅度节约时间成本。

Spring IOC中的集合注入

在前面的课程中,我们学习了如何基于IOC容器在运行时动态注入单个对象。本节将介绍如何使用IOC容器注入集合类型的对象,如 ListSetMapProperties

1. 注入List集合

关键点:

  • List 属性类型应该是 List
  • IOC容器可以通过 list 标签来为 List 设置数据。
  • 有两种方式为 List 属性注入数据:
    • 静态数值:使用 value 标签。
    • 引用其他对象:使用 ref 引用相应的 Bean。

示例:

<bean id="myBean" class="com.example.MyClass">
    <property name="myList">
        <list>
            <value>Value1</value>
            <ref bean="someOtherBean"/>
        </list>
    </property>
</bean>

2. 注入Set集合

关键点:

  • Set 属性类型应该是 Set
  • 使用 set 标签为 Set 属性注入数据。
  • Set 的数据不会重复,而 List 允许重复数据。

示例:

<bean id="myBean" class="com.example.MyClass">
    <property name="mySet">
        <set>
            <value>Value1</value>
            <ref bean="someOtherBean"/>
        </set>
    </property>
</bean>

3. 注入Map集合

关键点:

  • Map 是键值对集合,通常 key 是字符串,value 可以是静态数值或对象引用。
  • 使用 map 标签来定义键值对,entry 标签用于定义每个键值对。
    • 静态数值:使用 value
    • 对象引用:使用 ref

示例:

<bean id="myBean" class="com.example.MyClass">
    <property name="myMap">
        <map>
            <entry key="key1" value="value1" />
            <entry key="key2" value-ref="someOtherBean" />
        </map>
    </property>
</bean>

4. 注入Properties集合

关键点:

  • Properties 类型与 Map 类似,但 Propertieskeyvalue 必须是字符串。
  • 不能像 Map 一样在 value 部分引用其他对象。
  • 常用于保存配置文件,例如数据库连接信息等。

示例:

<bean id="myBean" class="com.example.MyClass">
    <property name="myProperties">
        <props>
            <prop key="username">admin</prop>
            <prop key="password">secret</prop>
        </props>
    </property>
</bean>
<?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="c1" class="com.imooc.spring.ioc.entity.Computer">
        <constructor-arg name="brand" value="联想"/>
        <constructor-arg name="type" value="台式机"/>
        <constructor-arg name="sn" value="8389283012"/>
        <constructor-arg name="price" value="3085"/>
    </bean>

    <bean class="com.imooc.spring.ioc.entity.Computer">
        <constructor-arg name="brand" value="微星"/>
        <constructor-arg name="type" value="台式机"/>
        <constructor-arg name="sn" value="8389280012"/>
        <constructor-arg name="price" value="3000"/>
    </bean>

    <bean class="com.imooc.spring.ioc.entity.Computer">
        <constructor-arg name="brand" value="华硕"/>
        <constructor-arg name="type" value="笔记本"/>
        <constructor-arg name="sn" value="9089380012"/>
        <constructor-arg name="price" value="6000"/>
    </bean>

    <bean id="company" class="com.imooc.spring.ioc.entity.Company">
        <property name="rooms">
            <set>
                <value>2001-总裁办</value>
                <value>2003-总经理办公室</value>
                <value>2010-研发部会议室</value>
                <value>2010-研发部会议室</value>
            </set>
        </property>
        <property name="computers">
            <map>
                <entry key="dev-88172" value-ref="c1"/>
                <entry key="dev-88173">
                    <bean class="com.imooc.spring.ioc.entity.Computer">
                        <constructor-arg name="brand" value="联想"/>
                        <constructor-arg name="type" value="笔记本"/>
                        <constructor-arg name="sn" value="1280258012"/>
                        <constructor-arg name="price" value="5060"/>
                    </bean>
                </entry>
            </map>
        </property>

        <property name="info">
            <props>
                <prop key="phone">010-12345678</prop>
                <prop key="address">北京市朝阳区XX路XX大厦</prop>
                <prop key="website">http://www.xxx.com</prop>
            </props>
        </property>
    </bean>
</beans>

3. 课程笔记:Spring IOC 中的 Scope 属性

3. 1. Scope 属性的概念

Scope 是 Spring IOC 容器中用来决定对象的创建时机和作用范围的属性。通过设置 scope 属性,可以影响容器内对象的数量以及它们的生命周期。

3. 2. 常见的 Scope 属性值

Spring 支持六种常见的 Scope 属性值:

  • singleton(单例模式):容器中的对象全局唯一,所有对该 bean 的引用指向同一个实例。是默认的 Scope 值。
  • prototype(多例模式):每次获取 bean 时都会创建一个新的实例。
  • request:每个 HTTP 请求都会创建一个新的实例。
  • session:每个用户会话拥有一个唯一实例。
  • globalSession:适用于 Portlet 环境中的全局会话。
  • webSocket:在每个 WebSocket 连接中创建唯一实例。

3. 3. singleton 和 prototype 的区别

singleton(单例模式)

  • 在 IOC 容器启动时即创建该对象。
  • 对象在容器中全局唯一,共享实例。
  • 存在线程安全问题,可能导致并发问题。
  • 适用于频繁使用、无需重复创建的对象。
  • 默认 Scope 值。

prototype(多例模式)

  • 每次调用 getBean() 方法时,创建一个新的对象实例。
  • 对象不共享,每个调用者获取的都是新的实例。
  • 不会出现线程安全问题,但会消耗更多的内存和 CPU 资源。
  • 适用于每次需要单独对象实例的场景。

3. 4. 其他 Scope

  • request:每个 HTTP 请求都会创建一个新的实例,不同请求的实例互不影响,生命周期与请求一致。
  • session:每个用户的会话中存在一个唯一实例,不同用户的会话拥有不同的实例。
  • globalSession:应用于 Portlet 环境,多个 Portlet 共享同一个会话实例。
  • webSocket:用于 WebSocket 连接,每个连接拥有一个独立的实例。

3. 5. singleton 的线程安全问题

在多线程环境下,单例模式可能会导致线程安全问题。比如,多个线程同时对同一个对象的属性进行修改时,会出现数据混乱的问题。可以通过同步机制(如加锁)来解决此问题,或者使用 prototype 来为每个线程创建独立的实例,从根本上避免并发问题。

4. 课程笔记:Spring IOC 容器中 Bean 的生命周期

4. 1. Bean 的生命周期概述

在 Spring IOC 容器中,Bean 的生命周期指的是从创建到销毁的整个过程。Bean 的生命周期可以分为五个主要阶段:实例化、属性注入、初始化、业务方法调用和销毁。下面我们详细介绍每个阶段。

4. 2. Bean 的生命周期阶段

1. 解析配置文件

首先,IOC 容器会解析 applicationContext.xml 文件,了解需要创建哪些对象以及为这些对象注入哪些属性。这是整个生命周期的第一步。

2. 实例化对象

IOC 容器通过反射机制根据配置文件中的定义来实例化对应的 Bean,并调用其构造方法。此时,Bean 还没有属性值。

3. 属性注入

容器实例化对象后,会根据配置文件为 Bean 注入属性。属性的注入会调用对应的 set 方法。

4. 初始化(init-method)

在 Bean 的属性注入完成后,IOC 容器会调用配置中的 init-method 方法。此方法用于在属性设置完毕后进行进一步的初始化操作。构造方法和 init-method 的区别在于:构造方法创建对象时 Bean 还没有属性值,而 init-method 是在属性已经注入后调用的。

5. 业务方法调用

IOC 容器初始化完毕后,业务代码就可以调用这个 Bean 来完成具体的业务操作。例如,通过 getBean() 方法获取实例并调用其方法。

6. 销毁(destroy-method)

当容器销毁时,会调用配置的 destroy-method 来释放与当前 Bean 相关的资源。这一步通常用于关闭文件、断开数据库连接等操作。

5. Spring 注解方式配置 IOC 容器

5. 1. Spring IOC 容器的三种配置方式

  • 在前面的课程中,我们已经学习了如何使用 XML 配置 方式来配置 Spring IOC 容器。
  • 除了 XML 配置,Spring 还支持 注解Java Config 两种配置方式。
  • 三种配置方式的底层原理是一样的,都是通过反射机制来动态实例化对象和注入属性。

5. 2. 注解的优势

  • 摆脱繁琐的 XML 配置,不再需要大量的 beanproperty 标签来进行配置。
  • 注解的配置更直观,可以直接在代码中通过注解声明类、属性和方法的依赖关系,提升开发体验。
  • 注解基于声明式的原则,更适合轻量级的现代企业应用开发。

5. 3. Spring 常见的三类注解

在 Spring 中,注解可以分为以下三类:

  1. 组件类型注解:声明类的功能和职责,告诉 IOC 容器应该如何管理这个类。
  2. 自动装配注解:根据属性特征自动注入对象或值。
  3. 元数据注解:细化辅助 IOC 容器管理对象。

3.1 组件类型注解

组件类型注解有四种,分别是:

  1. @Component:表示这是一个通用组件类,适用于不确定职责的类。
  2. @Service:用于标记业务逻辑层类,通常是业务服务类。
  3. @Controller:用于标记控制器类,在 MVC 模式中使用,处理用户请求和返回响应。
  4. @Repository:用于标记数据持久层类,通常是 DAO 类,用于与数据库交互。

这些注解用于告诉 Spring IOC 容器哪些类需要被管理,并根据类的功能自动注入相关的扩展模块。

3.2 @Component 注解

  • @Component 是一个通用的组件注解,适用于各种类,不区分具体职责。
  • 如果无法明确类的职责,可以使用这个注解。

3.3 @Service 注解

  • @Service 适用于业务逻辑层的类,用于实现系统的核心业务逻辑。

3.4 @Controller 注解

  • @Controller 适用于控制层的类,处理客户端的请求,调用业务逻辑,并返回响应。
  • 在学习 Spring MVC 时会更加详细地学习它的用法。

3.5 @Repository 注解

  • @Repository 适用于持久层的类,通常是 DAO 类,用于数据库的增删改查操作。

4. 开启组件扫描

  • 要让 Spring IOC 容器识别并管理这些带有注解的类,需要在 applicationContext.xml 中开启组件扫描。
  • 通过 context:component-scan 标签设置扫描的包路径,并且可以排除不希望扫描的类。
<context:component-scan base-package="com.example">
    <context:exclude-filter type="regex" expression="com.example.excluded.*"/>
</context:component-scan>

5.Spring IOC 注解中的自动装配

自动装配的两种方式
  • 按类型装配:通过属性的数据类型来完成自动注入。
  • 按名称装配:通过 bin 的名称来完成依赖注入。
自动装配注解分类
  1. 按类型装配

    • @Autowired:Spring 提供的按类型自动装配注解。
    • @Inject:JSR-330 提供的行业标准注解,与 @Autowired 功能相似。
  2. 按名称装配

    • @Named:JSR-330 提供的注解,配合 @Inject 使用,支持通过名称注入。
    • @Resource:JSR-250 的注解,支持通过名称优先匹配,如果名称不匹配则按类型进行注入。
@Autowired 示例
@Service
public class UserService {

    @Autowired
    private UserDao userDao;

    // Getter and Setter
}
  • @Autowired 放在属性上,Spring IOC 容器会根据属性的数据类型,自动寻找并注入相应的对象。
  • 如果放在方法上,则 Spring 会为方法的参数进行自动注入。
自动装配注解的执行方式差异
  • 在属性上使用 @Autowired:Spring 使用反射技术,将私有属性临时设为公共属性进行赋值。
  • 在方法上使用 @Autowired:会直接调用 set 方法,将依赖注入到方法参数中。
按类型注入的潜在问题
  • 当 IOC 容器中存在多个相同类型的 bean 时,Spring 不知道该选择哪个对象进行注入,容易出现 NoUniqueBeanDefinitionException

示例:

@Repository
public class UserDaoImpl implements IUserDao {
    // UserDao implementation
}

@Repository
public class UserOracleDaoImpl implements IUserDao {
    // Oracle specific UserDao implementation
}

当有两个 IUserDao 的实现时,按类型注入将会失败。

解决方案
  1. 去掉某个实现的 @Repository 注解:避免重复的 bean 存在。
  2. 使用 @Primary 注解:指定默认优先注入的实现类。
@Repository
@Primary
public class UserDaoImpl implements IUserDao {
    // Default UserDao implementation
}

按名称装配注解

1. 按名称自动装配注解

在实际项目中,优先推荐使用按名称自动装配注解。常见的按名称注解是 @Resource,这是基于 JSR 250 的标准,广泛应用于 JAVA 项目中。

2. @Resource 注解的介绍
  • @Resource 是一种智能的自动装配注解,它优先尝试按名称进行注入,当名称匹配不到时,才会按类型匹配,具有较高的灵活性。
  • 常用规则
    • 如果设置了 name 属性:按 name 指定的名称查找对应的 Bean
    • 如果没有设置 name 属性:会默认使用属性名作为 Bean 名称查找匹配。
    • name 匹配失败后,才会按类型进行匹配。
3. 示例代码

假设我们有一个 DepartmentService 类,它依赖于 UserDAO。我们来演示如何使用 @Resource 来注入 UserDAO

@Service
public class DepartmentService {

    // 定义一个需要注入的属性
    @Resource
    private IUserDao userDao;

    public void joinDepartment() {
        // 打印userDao的实际类型
        System.out.println(userDao.toString());
    }
}

SpringApplication 中调用这个方法:

public class SpringApplication {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

        // 获取departmentService实例
        DepartmentService departmentService = context.getBean(DepartmentService.class);
        departmentService.joinDepartment();
    }
}
4. @Resource 注解的装配规则
  • 按名称装配:默认情况下,@Resource 会使用属性名作为 Bean 名称进行匹配。如果 userDao 属性名称与 Bean 名称一致,那么它将会被注入。
  • 按类型装配:如果名称匹配失败,则会按类型进行装配。例如,如果有多个 UserDAO 实现类,比如 UserMySQLDAOUserOracleDAO,那么就会发生冲突。

Spring IOC注解元数据

本节我们学习了Spring IOC注解中的元数据注解。元数据注解的作用是为Spring IOC容器管理对象提供辅助信息。

常用的元数据注解

  1. @Primary:按类型装配时,如果出现多个相同类型的对象,则优先将@Primary注解的对象进行注入。

  2. @PostConstruct:相当于XML中配置的init-method,在IOC容器对属性进行注入后自动执行初始化方法。

  3. @PreDestroy:相当于XML中配置的destroy-method,用于在IOC容器销毁时执行销毁方法,释放资源。

  4. @Scope:用于设置bin的scope属性,决定对象的生存周期。例如,单例模式(Singleton)或多例模式(Prototype)。

  5. @Value:为属性注入静态数据,通常用于从配置文件中加载数据。

@Primary 注解

@Primary注解用于解决按类型装配时出现多个相同类型对象的冲突。它告诉Spring在有多个候选对象时优先选择标注了@Primary的对象进行注入。

@Primary
@Repository
public class UserOracleDao implements IUserDao {
    // 实现代码...
}

@PostConstruct 注解

@PostConstruct注解在对象创建并且依赖注入完成后,执行指定的初始化方法。相当于在XML中配置的init-method

@Service
public class UserService {
    
    @PostConstruct
    public void init() {
        System.out.println("UserService 初始化");
    }
}

@PreDestroy 注解

@PreDestroy注解在对象销毁时执行相应的资源释放方法,类似于在XML中配置的destroy-method

@PreDestroy
public void destroy() {
    System.out.println("UserService 销毁");
}

@Scope 注解

@Scope注解用于指定对象的作用域。可以设置为singleton(单例模式)或prototype(多例模式)。

@Service
@Scope("prototype")
public class UserService {
    // 该类将以多例的形式被创建
}

@Value 注解

@Value注解用于为属性注入静态值,通常结合配置文件使用。首先我们需要创建一个properties文件,比如config.properties

metadata=Imooc.com

然后在Spring的配置文件中加载该属性文件:

<context:property-placeholder location="classpath:config.properties"/>

接着,在类中使用@Value注解引入配置文件中的值:

@Service
public class UserService {
    
    @Value("${metadata}")
    private String metadata;

    @PostConstruct
    public void init() {
        System.out.println("Metadata: " + metadata);
    }
}

Spring IOC 使用 Java Config 配置方式

Java Config 介绍

  • Java Config 是 Spring 3.0 之后推出的一种配置方式,用于替代传统的 XML 配置。
  • 使用独立的 Java 类 来管理对象与依赖,完全摆脱 XML 的束缚。
  • 提供了对象的集中管理,避免注解分散在各个类中,便于大型工程中的管理。
  • 在编译时进行依赖检查,能够更早地发现配置问题。

主要注解

  1. @Configuration:用于标记一个类为配置类,代替 XML 文件的作用。
  2. @Bean:用于方法上,表示方法返回的对象将由 IOC 容器管理,默认 Bean ID 为方法名。
  3. @ImportResource:用于加载静态配置文件,常与 @Value 配合使用。
  4. @ComponentScan:用于扫描项目中的四种组件类型(如 Service、Controller),类似于 XML 中的 context:component-scan

示例演示

@Configuration
public class Config {

    @Bean
    public UserDao userDao() {
        return new UserDao();
    }

    @Bean
    public UserService userService() {
        UserService userService = new UserService();
        userService.setUserDao(userDao());
        return userService;
    }

    @Bean
    public UserController userController() {
        UserController userController = new UserController();
        userController.setUserService(userService());
        return userController;
    }
}
  • 解释
    • @Configuration:标记当前类为配置类,替代 XML 配置文件。
    • @Bean:每个带有该注解的方法返回的对象将由 IOC 容器管理,默认 Bean 的 ID 为方法名。

主程序入口

public class SpringApplication {

    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);

        // 打印所有 Bean 的名称
        String[] beanNames = context.getBeanDefinitionNames();
        for (String beanName : beanNames) {
            System.out.println("Bean Name: " + beanName + " - Object: " + context.getBean(beanName));
        }
    }
}
  • AnnotationConfigApplicationContext:用于基于注解配置的应用上下文,传入配置类 Config 来初始化 IOC 容器。

Spring IOC 使用 Java Config 进行依赖注入

Java Config 中的依赖注入

  • 在 Java Config 中,依赖注入的过程主要通过 方法参数 来完成。
  • 当一个对象(如 UserService)依赖另一个对象(如 UserDao)时,只需将依赖的对象作为方法的参数,Spring 会自动注入该对象。

示例讲解

  1. UserService 依赖于 UserDao

    • Config 类中定义 UserService 的方法时,添加 UserDao 作为参数:
      @Bean
      public UserService userService(UserDao userDao) {
          UserService userService = new UserService();
          userService.setUserDao(userDao);
          return userService;
      }
      
    • Spring 会自动将容器中的 UserDao 对象注入到 UserServicesetUserDao 方法中。
  2. Controller 依赖于 Service

    • 同理,在 UserController 中注入 UserService
      @Bean
      public UserController userController(UserService userService) {
          UserController userController = new UserController();
          userController.setUserService(userService);
          return userController;
      }
      
  3. 验证依赖注入过程

    • Config 类的各个方法中增加输出语句:
      System.out.println("已创建 UserDao 对象");
      System.out.println("已创建 UserService 对象,且注入了 UserDao");
      System.out.println("已创建 UserController 对象,且注入了 UserService");
      
    • 通过控制台输出,可以看到对象的创建顺序和注入过程。

依赖注入的方式

  1. 按名称注入

    • Spring 首先按 名称 注入依赖,方法参数名与 Bean ID 相同,则会根据名称匹配进行注入。
  2. 按类型注入

    • 当名称不匹配时,Spring 会尝试按 类型 进行注入。
    • 如果存在多个相同类型的 Bean,则会抛出错误。

避免类型冲突

  • 当存在多个相同类型的 Bean 时,可以使用 @Primary 注解来指定优先注入的 Bean。
    @Bean
    @Primary
    public UserDao userDao1() {
        return new UserDao();
    }
    

多例对象与注解兼容

  • 可以通过 @Scope("prototype") 来指定 Bean 为多例模式。

    @Bean
    @Scope("prototype")
    public UserController userController(UserService userService) {
        UserController userController = new UserController();
        userController.setUserService(userService);
        return userController;
    }
    
    • 在多例模式下,容器不会在启动时创建对象,而是在使用时才会创建。
  • 通过 @ComponentScan 兼容注解形式:

    @ComponentScan(basePackages = "com.imock.spring")
    
    • 容器启动时会自动扫描指定包路径下的组件并进行实例化。

XML 与 Java Config 的对比

  1. 优点

    • Java Config 提供了更好的开发体验,支持编译时检查,方便在开发阶段发现问题,适合快速迭代的项目。
    • XML 配置 适合团队协作,可以通过配置文件将模块职责分离,更具可维护性。
  2. 缺点

    • Java Config 的修改需要重新编译,适合敏捷开发。
    • XML 配置 虽然更加分离,但配置繁琐。

Spring Test 模块与 JUnit 整合

Spring Test 介绍

  • Spring Test 是 Spring 框架中专用于测试的模块,常用于与 JUnit 单元测试的整合。
  • 通过 Spring Test,可以在 JUnit 单元测试开始时自动初始化 IOC 容器,避免手动初始化 ApplicationContext 对象。

Spring 与 JUnit 4 的整合过程

  1. Maven 依赖

    • 引入 Spring Test 模块和 JUnit 4 的依赖:
      <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-test</artifactId>
          <version>5.2.6.RELEASE</version>
      </dependency>
      <dependency>
          <groupId>junit</groupId>
          <artifactId>junit</artifactId>
          <version>4.12</version>
          <scope>test</scope>
      </dependency>
      
  2. 测试类注解

    • 使用 @RunWith@ContextConfiguration 注解:
      • @RunWith:将 JUnit 的执行权交给 Spring,由 Spring 来管理测试用例的执行过程。
      • @ContextConfiguration:指定 Spring 容器加载的配置文件。
  3. 示例代码

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations = {"classpath:applicationContext.xml"})
    public class SpringTest {
    
        @Resource
        private UserService userService;
    
        @Test
        public void testCreateUser() {
            userService.createUser();
        }
    }
    

具体步骤

1. 引入依赖

  • pom.xml 中添加 Spring Test 和 JUnit 的依赖,确保测试模块和框架整合成功。

2. 创建测试用例类

  • src/test/java 目录下创建测试用例类,并在类上使用 @RunWith@ContextConfiguration 注解。
  • @RunWith(SpringJUnit4ClassRunner.class):将 JUnit 的运行权交给 Spring Test 模块。
  • @ContextConfiguration(locations = {"classpath:applicationContext.xml"}):指定 Spring 容器要加载的配置文件。

3. 属性注入与测试方法

  • 使用 @Resource 注解注入需要测试的 Service 对象。
  • 在测试方法中调用 Service 的方法,并通过断言或日志验证代码逻辑是否正确。

示例代码结构

  1. DAO 类

    public class UserDao {
        public void insert() {
            System.out.println("新增用户数据");
        }
    }
    
  2. Service 类

    public class UserService {
        private UserDao userDao;
    
        public void setUserDao(UserDao userDao) {
            this.userDao = userDao;
        }
    
        public void createUser() {
            System.out.println("调用创建用户业务代码");
            userDao.insert();
        }
    }
    
  3. applicationContext.xml 配置文件

    <beans>
        <bean id="userDao" class="com.imock.spring.ioc.dao.UserDao"/>
        <bean id="userService" class="com.imock.spring.ioc.service.UserService">
            <property name="userDao" ref="userDao"/>
        </bean>
    </beans>
    
  4. 测试用例类

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations = {"classpath:applicationContext.xml"})
    public class SpringTest {
    
        @Resource
        private UserService userService;
    
        @Test
        public void testCreateUser() {
            userService.createUser();
        }
    }
    

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/891591.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

RWKV-CHN模型部署教程

一、模型介绍 RWKV 语言模型&#xff08;用纯 100%RNN 达到 GPT 能力&#xff0c;甚至更强&#xff09;&#xff0c;该项目旨在通过为您自动化所有事情来消除使用大型语言模型的障碍。您需要的是一个只有几兆字节的轻量级可执行程序。此外&#xff0c;该项目还提供了一个接口兼…

python yolov8半自动标注

首先标注一部分图片&#xff0c;进行训练&#xff0c;生成模型&#xff0c;标注文件为xml方便后面统一做处理。 1、标注数据&#xff08;文件为xml, 转为txt用于训练&#xff0c;保留xml标签文件&#xff09; 2、模型训练&#xff08;训练配置、训练代码、&#xff09; 3、使用…

一个将.Geojson文件转成shapefile和kml文件的在线页面工具

最近需要读取.geojson格式的流域边界文件。在谷歌地球桌面版和globalMapper中均无法正常读取。下面我发现的一个在线的平台可以很好实现这一功能。 GeoJSON to SHP Converter Online - MyGeodata Cloud ❤️欢迎点赞收藏❤️

关于WPF(Windows Presentation Foundation)中Grid控件

本文将从Grid控件的基础概念开始&#xff0c;逐步深入探讨其特性、用法、实例代码&#xff0c;以及最佳实践。 1. WPF和布局简介 WPF是一种用于构建Windows桌面应用程序的UI框架&#xff0c;它通过XAML&#xff08;Extensible Application Markup Language&#xff09;使开…

Oracle中解决select into值集为空的报错情况

先看为空的情况 procedure test is n number; begin select 1 into n from CUX_2_OM_RELEASE_LIMIT_V cov where cov.Customer_Idnull; end; CUX_2_OM_RELEASE_LIMIT_V中没有id是空的&#xff0c;因此返回的结果一定是空集 运行结果: 有时候我…

QT事件与网络通信

闹钟 头文件 #ifndef MAINWINDOW_H #define MAINWINDOW_H#include <QMainWindow> #include <QTimer> #include <QTextToSpeech> // 添加此行以引入QTextToSpeech类QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } QT_END_NAMESPACEclass MainWin…

大数据ETL数据提取转换和加载处理

什么是 ETL&#xff1f; 提取转换加载&#xff08;英语&#xff1a;Extract, transform, load&#xff0c;简称ETL&#xff09;&#xff0c;用来描述将资料从来源端经过抽取、转置、加载至目的端的过程。ETL一词较常用在数据仓库&#xff0c;但其对象并不限于数据仓库。 ETL&…

下载Edge/Chrome浏览器主题的背景图片

当我们为Edge安装了心仪的主题后&#xff0c;希望把对应的背景图片下载保存要怎么做呢&#xff0c;以下图的“湖心小屋”主题为例。如下图&#xff0c;我们已经在应用商店中按照了该主题。 当打开新标签页后&#xff0c;可以欣赏这个主题内置的背景图片。 如果想要下载这个背景…

【人工智能/计算机工程/大数据】第五届人工智能与计算工程国际学术会议(ICAICE 2024,2024年11月8-10日)

The 5th International Conference on Artificial Intelligence and Computer Engineering 第五届人工智能与计算工程国际学术会议&#xff08;ICAICE 2024&#xff09; 会议官网&#xff1a;www.event-icaice.org The 5th International Conference on Artificial Intellige…

外包干了5天,技术明显退步

我是一名本科生&#xff0c;自2019年起&#xff0c;我便在南京某软件公司担任功能测试的工作。这份工作虽然稳定&#xff0c;但日复一日的重复性工作让我逐渐陷入了舒适区&#xff0c;失去了前进的动力。两年的时光匆匆流逝&#xff0c;我却在原地踏步&#xff0c;技术没有丝毫…

【Vue】Vue扫盲(二)指令:v-for 、v-if、v-else-if、v-else、v-show

【Vue】Vue扫盲&#xff08;一&#xff09;事件标签、事件修饰符&#xff1a;click.prevent click.stop click.stop.prevent、按键修饰符、及常用指令 文章目录 一、v-for遍历数组数组角标遍历对象&#xff1a;Key作用介绍 二、v-if、v-show基本用法&#xff1a;案例&#xff1…

Linux--多路转接之epoll

上一篇:Linux–多路转接之select epoll epoll 是 Linux 下多路复用 I/O 接口 select/poll 的增强版本&#xff0c;它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统 CPU 利用率。它是 Linux 下多路复用 API 的一个选择&#xff0c;相比 select 和 poll&#xff0c…

用于病理图像诊断的跨尺度多实例学习|文献速递-基于深度学习的医学影像分类,分割与多模态应用

Title 题目 Cross-scale multi-instance learning for pathological image diagnosis 用于病理图像诊断的跨尺度多实例学习 01 文献速递介绍 病理学是诊断炎症性肠病&#xff08;如克罗恩病&#xff09;的金标准&#xff08;Gubatan等&#xff0c;2021&#xff1b;Yeshi等…

机器学习:opencv--人脸检测以及微笑检测

目录 前言 一、人脸检测的原理 1.特征提取 2.分类器 二、代码实现 1.图片预处理 2.加载分类器 3.进行人脸识别 4.标注人脸及显示 三、微笑检测 前言 人脸检测是计算机视觉中的一个重要任务&#xff0c;旨在自动识别图像或视频中的人脸。它可以用于多种应用&#xff0…

Vxe UI vue vxe-table select 下拉框选项列表数据量超大过大时卡顿解决方法

Vxe UI vue vxe-table vxe-grid select 下拉框选项列表数据量超大过大时卡顿解决方法 查看 github vxe-table 官网 vxe-table 本身支持虚拟滚动&#xff0c;数据量大也是支持的&#xff0c;但是如果在可编辑表格中使用下拉框&#xff0c;下拉框的数据量超大时&#xff0c;可能…

int QSqlQuery::size() const

返回结果的大小&#xff08;返回的行数&#xff09; 或者返回-1 &#xff08;如果大小不能被决定 或者 数据库不支持报告查询的大小信息&#xff09; 注意&#xff1a;对于非查询语句&#xff0c;将返回-1&#xff08;isSelect()返回false&#xff09; 如果查询不是活跃的&…

JS 分支语句

目录 1. 表达式与语句 1.1 表达式 1.2 语句 1.3 区别 2. 程序三大流控制语句 3. 分支语句 3.1 if 分支语句 3.2 双分支 if 语句 3.3 双分支语句案例 3.3.1 案例一 3.3.2 案例二 3.4 多分支语句 1. 表达式与语句 1.1 表达式 1.2 语句 1.3 区别 2. 程序三大流控制语…

基于方块编码的图像压缩matlab仿真,带GUI界面

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1 编码单元的表示 4.2编码单元的编码 5.算法完整程序工程 1.算法运行效果图预览 (完整程序运行后无水印) 下图是随着方块大小的变化&#xff0c;图像的压缩率以及对应的图像质量指标PSN…

初始爬虫13(js逆向)

为了解决网页端的动态加载&#xff0c;加密设置等&#xff0c;所以需要js逆向操作。 JavaScript逆向可以分为三大部分&#xff1a;寻找入口&#xff0c;调试分析和模拟执行。 1.chrome在爬虫中的作用 1.1preserve log的使用 默认情况下&#xff0c;页面发生跳转之后&#xf…

echarts显示隐藏柱状图柱子的背景色

showBackground: true, //控制是否显示背景色backgroundStyle: {// color: rgba(180, 180, 180, 0.4) //背景色的颜色color: red} 关键代码是 showBackground: true, //控制是否显示背景色 设置为false或者直接而不写就是不显示背景色&#xff0c;默认是不显示背景色 true的时…