Spring IOC笔记——DI(依赖注入)的使用详解

Posted stormzhuo

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring IOC笔记——DI(依赖注入)的使用详解相关的知识,希望对你有一定的参考价值。

文章目录

😎前言

内容主要参考自《Spring实战》一书和尚硅谷-Spring5框架最新版教程,算是读书笔记或是原书的补充。

本文主要涉及IOC的部分,依照书中内容以及个人理解对Spring IOC容器进行了总结

😎IOC

🍉什么是IOC?

Spring IOC,我们又通常将其称为 IOC 容器,IOC 的2个实现方式分别为 依赖注入(DI) 依赖查找(DL)。由于依赖查找(DL)使用的很少,因此 IOC 也被叫做依赖注入。

🍉IOC 底层原理

xml 解析、工厂模式、反射

🍉Spring 提供 IOC 容器实现两种方式:

Spring容器并不是只有一个。Spring自带了多个容器实现,可以归为两种不同的类型。

bean 工厂(由org.springframework.beans. factory.beanFactory接口定义)

是最简单的容器,提供基本的DI支持。 是 Spring 内部的使用接口,不提供开发人员进行使用

注:加载配置文件时候不会创建对象,在获取对象(使用)才去创建对象

应用上下文(由org.springframework.context.Applicationcontext 接口定义)

基于BeanFactory构建,并提供应用框架级别的服务,例如从属性文件解析文本信息以及发布应用事件给感兴趣的事件监听者。一般由开发人员进行使用

注:加载配置文件时候就会把在配置文件对象进行创建

虽然我们可以在 bean工厂和应用上下文之间任选一种,但bean 工厂对大多数应用来说往往太低级了,因此,应用上下文要比 bean 工厂更受欢迎。

🍉使用应用上下文

Spring自带了多种类型的应用上下文。下面罗列的几个是最有可能遇到的。

  • AnnotationConfigApplicationContext:从一个或多个基于Java的配置类中加载 Spring应用上下文。
  • AnnotationConfigWebApplicationContext:从一个或多个基于Java的配置类中加载Spring Web应用上下文。
  • ClassPathXmlApplicationContext:从类路径下的一个或多个XML配置文件中加载上下文定义,把应用上下文的定义文件作为类资源。
  • FileSystemXmlApplicationContext:从文件系统下的一个或多个XML配置文件中加载上下文定义。
  • XmlWebApplicationContext:从Web应用下的一个或多个XML配置文件中加载上下文定义。

😎装配bean

Spring具有非常大的灵活性,它提供了三种主要的装配机制:

  • 在XML中进行显式配置;
  • 在Java中进行显式配置;
  • 隐式的bean发现机制和自动装配。

🍉自动化装配 bean

Spring从两个角度来实现自动化装配:

  • 组件扫描( component scanning ): Spring会自动发现应用上下文中所创建的bean
  • 自动装配( autowiring ): Spring自动满足bean之间的依赖

为了阐述组件扫描和装配,我们使用在mvc三层架构中使用的service层调用dao层的例子来说明

在装配bean之前,需要先创建可发现的bean

🍑创建可发现的bean

在传统的mvv架构中,我们是通过new的方式创建service层对象(bean),在使用spring后,只需通过一个注解即可实现对象的创建

创建User的service层接口以及实现类,如下

public interface UserService 
    // 添加一个用户
    void addUser();

@Service
public class UserServiceImpl implements UserService 
    @Override
    public void addUser() 
        System.out.println("service add...");
    

UserServiceImpl类上使用了@Service注解,这个简单的注解表明该类会作为组件类,并告知 Spring要为这个类创建bean

下面列出Spring 针对 Bean 管理中创建对象提供的注解

  • @Component
  • @Service
  • @Controller
  • @Repository

注:上面四个注解功能是一样的,只是起到标识作用,都可以用来创建 bean 实例

🌟为组件扫描的bean命名

Spring应用上下文中所有的bean都会给定一个默认的ID。也就是将类名的第一个字母变为小写。

如果想为这个bean设置不同的ID,有以下两种方式

  • 在创建bean的注解上添加value属性来为bean设置id
  • 使用@Named注解来为bean设置id

在创建bean的注解上添加value属性来为bean设置id

@Service(value = "userService")

使用@Named注解来为bean设置id

import javax.inject.Named;
@Named("userService")

Spring支持将@Named 作为@Component 注解的替代方案。两者之间有一些细微的差异,但是在大多数场景中,它们是可以互相替换的。

🌟设置组件扫描的基础包

组件扫描默认是不启用的。我们还需要显式配置一下 Spring,从而命令它去寻找带有@Service注解的类,并为其创建bean。

开启组件扫描有以下两种方式

  • 基于Java的配置
  • 基于xml的配置

基于Java的配置

@Configuration
@ComponentScan("com.zhuo.spring5.service")
public class SpringConfig 

SpringConfig类并没有显式地声明任何 bean,只不过它使用了@ComponentScan注解,这个注解能够在Spring中启用组件扫描

ok,到这里就可以来测试bean是否已创建,由于我们使用的是Java配置类,因此可以使用AnnotationConfigApplicationContext来加载应用上下文,如下

@Test
public void testService() 
    // 加载配置类
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("com.zhuo.spring5.config");
    UserServiceImpl userService = context.getBean("userService", UserServiceImpl.class);
    System.out.println(userService);
    userService.addUser();

下面来详细说下@ComponentScan注解的使用

若没有为@ComponentScan 设置任何属性,则它会以配置类所在的包作为基础包(base package)来扫描组件。

@ComponentScan()

如果你想更加清晰地表明你所设置的是基础包,那么你可以通过basePackages属性进行配置:

@ComponentScan(basePackages = "com.zhuo.spring5.service")

basePackages属性使用的是复数形式。因此还可以设置多个基础包,只需要将basePackages属性设置为要扫描包的一个数组即可:

@ComponentScan(basePackages = "com.zhuo.spring5.service", "com.zhuo.spring5.dao")

在上面的例子中,所设置的基础包是以string类型表示的。我认为这是可以的,但这种方法是类型不安全的。如果你重构代码的话,那么所指定的基础包可能就会被修改,因此会出现错误。可以通过下面的方法来解决

除了将包设置为简单的String类型之外,@ComponentScan还提供了另外一种方法,那就是将其指定为包中所包含的类或接口,可以使用basePackageClasses属性来实现

@ComponentScan(basePackageClasses = UserServiceImpl.class, UserDaoImpl.class)

基于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"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <context:component-scan base-package="com.zhuo.spring5.service"/>
</beans>

可以使用Spring context命名空间的<context :component-scan>元素,它会有与@componentScan注解相对应的属性和子元素。

现在测试基于xml的配置是否同样可以创建bean,由于使用的xml配置,因此可以使用ClassPathXmlApplicationContext来加载应用上下文

@Test
public void testService() 
    // 加载配置类
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
    UserServiceImpl userService = context.getBean("userService", UserServiceImpl.class);
    System.out.println(userService);
    userService.addUser();

🍑通过为bean添加注解实现自动装配

简单来说,自动装配就是让Spring自动满足bean依赖的一种方法,在满足依赖的过程中,会在Spring应用上下文中寻找匹配某个bean需求的其他 bean。为了声明要进行自动装配,我们可以通过以下五种注解实现

  • @Autowired:根据属性类型进行自动装配
  • @Qualifier:和上面@Autowired 一起使用,根据名称进行注入
  • @Resource:可以根据类型注入,可以根据名称注入
  • @Value:注入普通类型属性
  • @Inject:来源于Java 依赖注入规范,@Autowired替代方案

🌟Spring IOC容器中bean的两种注入方式

在spring ioc可以通过两种方式注入,如下

  • 第一种注入方式:使用 set 方法进行注入
  • 第二种注入方式:使用有参数构造进行注入

由于两种注入方式是类似的,所以下面在前面例子的基础上测试 set 方法进行注入

使用 set 方法进行注入

创建User的dao层接口以及实现类

public interface UserDao 
    // 插入一个用户
    void insertUser();

@Repository
public class UserDaoImpl implements UserDao 
    @Override
    public void insertUser() 
        System.out.println("dao add...");
    

@Autowired:根据属性类型进行自动装配

@Named("userService")
public class UserServiceImpl implements UserService 
    private UserDao userDao;
    @Autowired
    public void setUserDao(UserDao userDao) 
        this.userDao = userDao;
    
    @Override
    public void addUser() 
        System.out.println("service add...");
        userDao.insertUser();
    

在set方法上添加了@Autowired,它会把Spring容器中的UserDao对象注入(即通过属性类型)到User的service层对象的实例属性上

@Qualifier:根据名称进行注入


如果spring容器有两个或两个以上相同类型的属性的bean,这时不能单独使用@Autowired,因为它是根据属性类型进行注入的,所以可以和@Qualifier组合根据名称进行注入

@Resource:可以根据类型注入,可以根据名称注入


@Inject:@Autowired替代方案

补充:其实自动装配的注解可以直接使用在属性上,因为它对set方法进行了封装

🍉通过Java代码装配bean

尽管在很多场景下通过组件扫描自动装配实现Spring 的自动化配置是更为推荐的方式,但有时候自动化配置的方案行不通,因此需要明确配置Spring。比如说,你想要将第三方库中的组件装配到你的应用中,在这种情况下,是没有办法在它的类上添加@component@Autowired注解的,因此就不能使用自动化装配的方案了。

在这种情况下,我们可以采用显式装配的方式。

🍑创建配置类

在前面的程序中,我们第一次见识到JavaConfig,它使用了@Configuration注解来标识配置类,使用了@ComponentScan来开启组件扫描

但是在这里我们关注的是显示装配,因此需要把@ComponentScan去掉


移除了@ComponentScan注解,此时的@Component和@Autowired注解就没有任何作用了,因为没开启组件扫描,所以也就不会发现它

下面介绍如何在JavaConfig中声明bean来替换@Component

🍑声明简单的bean

要在JavaConfig中声明bean,我们需要编写一个方法,这个方法会创建所需类型的实例,然后给这个方法添加@Bean注解。比方说,下面的代码声明了UserDao bean:

@Configuration
public class SpringConfig 

    @Bean()
    public UserDao userDao() 
        return new UserDaoImpl();
    

@Bean注解会告诉Spring这个方法将会返回一个对象,该对象要注册为Spring IOC容器中的bean。方法体中包含了最终产生bean实例的逻辑。

默认情况下,bean的ID与带有@Bean注解的方法名是一样的,即userDao。如果你想为其设置成一个不同的名字的话,也可以通过name属性指定一个不同的名字</code

🍑借助JavaConfig实现注入

同样地,在JavaConfig 中装配bean的也有两种方式:set方法注入,构造器注入,下面分别演示

构造器注入

userService()的方法体与userDao()稍微有些区别。在这里并没有使用默认的构造器构建实例,而是调用了需要传入userDao对象的构造器来创建userService实例。

看起来,userService()是通过调用userDao()得到的,但情况并非完全如此。因为userDao()方法上添加了@Bean注解,Spring将会拦截所有对它的调用,并确保直接返回该方法所创建的同一个bean,而不是每次都对其进行实际的调用创建不同地bean,如下

set方法注入

🍉通过XML装配bean

到此为止,我们已经看到了如何让Spring自动发现装配 bean,还看到了如何进行手动干预,即通过JavaConfig 显式地装配bean。但是,在装配 bean 的时候,还有一种可选方案,即XML配置

不过,我不推荐使用XML,因为Spring 现在有了强大的自动化配置基于Java的配置,XML不应该再是你的第一选择了。

但是,鉴于已经存在那么多基于XML的Spring 配置,所以理解如何在 Spring中使用XML还是很重要的,用来帮助你维护已有的XML配置,在完成新的Spring工作时,希望你会使用自动化配置JavaConfig.

🍑创建XML配置规范

在使用XML 为Spring 装配 bean之前,你需要创建一个新的配置规范。在使用JavaConfig的时候,这意味着要创建一个带有@Configuration注解的类,而在XML配置中,这意味着要创建一个XML文件

最简单的spring xml配置如下

在配置文件的顶部声明多个XML模式(XSD)文件,这些文件定义了配置Spring 的XML元素

用来装配 bean的最基本的XML元素包含在spring-beans模式之中,在上面这个XML文件中,它被定义为根命名空间。<beans>是该模式中的一个元素,它是所有Spring配置文件的根元素

🍑声明一个简单的<bean>

要在基于XML的Spring 配置中声明一个bean,我们要使用spring-beans模式中的另外一个元素:<bean>。元素类似于JavaConfig中的@Bean注解

<bean class="com.zhuo.spring5.dao.impl.UserDaoImpl"></bean>

这里声明了一个很简单的bean,创建这个bean的类通过class属性来指定的,并且要使用全限定的类名。

因为没有明确给定ID,所以这个bean将会根据全限定类名来进行命名。在这里,bean的ID将会是“scom.zhuo.spring5.dao.impl.UserDaoImpl#0”。

其中,“#O”是一个计数的形式,用来区分相同类型的其他bean。如果你声明了另外一个UserDaoImpl,并且没有明确进行标识,那么它自动得到的ID将会是“scom.zhuo.spring5.dao.impl.UserDaoImpl#1”。

如果你想给bean命名,可以借助<bean>的id属性

<bean id="userDao" class="com.zhuo.spring5.dao.impl.UserDaoImpl"></bean>

在 bean 元素中有很多属性,下面介绍常用的属性

  • id 属性:唯一标识
  • class 属性:类全路径(包类路径)

注:创建对象时候,默认也是执行无参数构造方法完成对象创建

🍑借助构造器注入初始化 bean

在XML 中通过构造器声明DI时,有两种基本的配置方案可供选择

  • <constructor-arg>元素
  • 使用 Spring 3.0所引入的c-命名空间

🌟<constructor-arg>元素

通过<constructor-arg>在构造器注入 bean引用

当Spring遇到这个<bean>元素时,它会创建一个UserService实例。<constructor-arg>元素会告知Spring 要将一个ID为userDao的bean引用传递到UserService的构造器中

通过<constructor-arg>在构造器注入字面量

迄今为止,我们所做的DI通常指的都是类型的装配——也就是将对象的引用装配到依赖于它们的其他对象之中——-而有时候,我们需要做的只是用一个字面量值来配置对象。为了阐述这一点,我们需要要创建Order类如下所示:

public class Order 
    //属性
    private String oName;
    private String address;
    //有参数构造
    public Order(String oName, String address) 
        this.oName = oName;
        this.address = address;
    
    @Override
    public String toString() 
        return "Order" +
                "oName='" + oName + '\\'' +
                ", address='" + address + '\\'' +
                '';
    

在 spring xml配置文件中进行配置

<!--3 有参数构造注入属性-->
<bean id="order" class="com.zhuo.spring5.Order">
    <constructor-arg name="oName" value="电脑"/>
    <constructor-arg name="address" value="china"/>
</bean>

我们再次使用元素进行构造器参数的注入。但是这次我们没有使用“ref”属性来引用其他的bean,而是使用了value属性,通过该属性表明给定的值要以字面量的形式注入到构造器之中。

通过<constructor-arg>在构造器注入null值

通过<constructor-arg>在构造器注入集合

在构造器中可以注入包含字面量或引用的集合,下面以list举例

创建Course类,定义String类型属性,生成对应构造器方法

public class Course 
    private String courseName;
    public Course(String courseName) 
        this.courseName = courseName;
    
    @Override
    public String toString() 
        return "Course" +
                "courseName='" + courseName + '\\'' +
                '';
    

创建Student类,定义list、set 类型属性,生成对应构造器方法

public class Student 

    private List<String> lists;
    private List<Course> courses;

    public Student(List<String> lists, List<Course> courses) 
        this.lists = lists;
        this.courses = courses;
    

    @Override
    public String toString() 
        return "Student" +
                "lists=" + lists +
                ", courses=" + courses +
                '';
    

在 spring xml配置文件中进行配置

<list>元素是<constructor-arg>的子元素,这表明一个包含值的列表将会传递到构造器中。其中,<value>元素用来指定列表中的每个元素。

与之类似,我们也可以使用<ref>,实现 bean引用列表的装配。

🌟c-命名空间

c-命名空间是在 Spring 3.0中引入的,它是在 XML中更为简洁地描述构造器参数的方式。要使用它的话,必须要在XML的顶部声明其模式,如下所示:

通过c-命名空间在构造器注入 bean引用

c-命名空间有两种方式注入属性

通过构造器参数注入

下图描述了这个属性名是如何组合的

通过构造器参数索引注入

参数的名称替换成了“0”,也就是参数的索引。因为在XML中不允许数字作为属性的第一个字符,因此必须要添加一个下画线作为前缀。

通过c-命名空间在构造器注入字面量

可以看到,装配字面量与装配引用的区别在于属性名中去掉了“-ref”后缀。

🍑借助set方法注入初始化 bean

在XML 中通过set方法声明DI时,有两种基本的配置方案可供选择

  • 使用<property>元素
  • 使用 p-命名空间

🌟<property>元素

<property>元素为属性的Setter方法所提供的功能与<constructor-arg>元素为构造器所提供的功能是一样的

在Course类和Student类中提供属性的set方法

在 spring xml配置文件中进行配置

🌟p-命名空间

Spring 提供了更加简洁的p-命名空间,作为<property>元素的替代方案。为了启用p-命名空间,必须要在 XML文件中与其他的命名空间一起对其进行声明:


在 spring xml配置文件中进行配置


p-命名空间中属性所遵循的命名约定与c-命名空间中的属性类似,下图描述了p-命名空间属性是如何组成的

但需要注意的是,我们不能使用p-命名空间来装配集合,没有便利的方式使用 p-命名空间来指定一个值(或bean引用)的列表。但是,我们可以使用Spring util-命名空间中的一些功能来简化集合的注入

🌟util-命名空间

首先,需要在XML中声明util-命名空间及其模式:


util-命名空间所提供的功能之一就是<util:list>元素,它会创建一个列表的bean。借助<util:list>,我们可以将集合转移到单独的bean之中,如下所示:

<util:list>只是util-命名空间中的多个元素之一。下表列出了util-命名空间提供的所有元素。

元素描述
<util : constant>引用某个类型的public static域,并将其暴露为bean
<util : list>创建一个java.util.List类型的bean,其中包含值或引用
<util : map>创建一个java.util.Map类型的bean,其中包含值或引用
<util : properties>创建一个java.util.Properties类型的bean
<util : property-path>引用一个bean的属性(或内嵌属性),并将其暴露为bean
<util : set>创建一个java.util.Set 类型的bean,其中包含值或引用

😎总结

在本文中,我们看到了在Spring中装配 bean的三种主要方式:自动化配置基于Java的显式配置以及基于XML的显式配置。不管你采用什么方式,这些技术都描述了Spring应用中的组件以及这些组件之间的关系。

我同时建议尽可能使用自动化配置,以避免显式配置所带来的维护成本。但是,如果你确实需要显式配置Spring 的话,应该优先选择基于Java的配置,它比基于XML 的配置更加强大、类型安全并且易于重构。

以上是关于Spring IOC笔记——DI(依赖注入)的使用详解的主要内容,如果未能解决你的问题,请参考以下文章

Spring IOC笔记——DI(依赖注入)的使用详解

Spring IOC笔记——DI(依赖注入)的使用详解

Spring IOC笔记——DI(依赖注入)的使用详解

Web框架—Spring Framework学习笔记(IoC DI Bean)

Spring笔记2

spring 依赖注入(DI)与控制反转(IOC)