Spring学习笔记(二):实例解读 IoC 和 DI(接续上篇)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Jin_Kwok/article/details/82783946

概述

在上一篇文章:《Spring学习笔记(一):眼见为实,先上一个简单例子》中提到了 “容器” 的概念,对于初学者来说,这个词可能并不好理解,本着 “逢山开路,遇水架桥” 的原则,本文趁热打铁,不留疑惑,着重介绍Spring核心概念之一:IoC容器。

一、什么是 IoC?

控制反转 IoC(Inversion of Control),是一种设计思想,而不是什么技术。通常,对象的创建和对象间的依赖关系完全硬编码在程序中,对象的创建是由程序控制,如new一个实例,控制权实际是由程序控制。如下所示,硬编码创建对象:

IBookDAO temp = new BookDAO();

通过硬编码创建对象有很多缺陷,不够灵活,鉴于此,IoC设计思想应运而生。"控制反转" 的本质是指new实例工作不由程序员来做,而是交给Spring容器,容器负责实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。说白了,控制反转就是将创建对象(实例)的方式改变了:程序员硬编码  —>> 改为容器根据配置文件或元数据创建。

IoC是Spring框架的核心内容,使用多种方式完美的实现了IoC,可以使用XML配置,也可以使用注解,新版本的Spring也可以零配置实现IoC。Spring容器在初始化时先读取配置文件,根据配置文件或元数据创建与组织对象存入容器中,程序使用时再从Ioc容器中取出需要的对象。整体流程如下图所示。

Spring容器根据XML配置文件创建对象的例子

1. 配置bean的XML文件,此处命名为 IOCBeans01.xml,文件中配置了一个bean,id “bookdao”作为唯一标识,class指明该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" 
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
    
    <bean id="bookdao" class="com.yingshulan.spring.spring_learning.BookDAO"></bean>
</beans>

特别说明:类BookDAO源文件如下,这个类很简单,没有属性,因此使用默认的构造方法创建对象即可。对于有属性的类,通常,在配置文件中还需配置属性,后面将介绍。

public class BookDAO implements IBookDAO
{
    public String addBook(String bookname)
    {
        return "添加图书" + bookname + "成功!";
    }
}

2. Spring容器通过XML配置文件创建对象

// 第一步: 使用框架 API ClassPathXmlApplicationContext() 来创建应用程序的上下文。
// 这个 API 加载 beans 的配置文件并最终基于所提供的 API,它处理创建并初始化所有的对象,
// 即在配置文件中提到的 beans。
ApplicationContext ctx = new ClassPathXmlApplicationContext("IOCBeans01.xml");

// 第二步是使用已创建的上下文的 getBean() 方法来获得所需的 bean。
// 这个方法使用 bean 的 ID 返回一个最终可以转换为实际对象的通用对象。一旦有了对象,
// 你就可以使用这个对象调用任何类的方法。
bookDAO = (IBookDAO) ctx.getBean("bookdao");

3. 通过对象,调用它的方法,通过id,从容器中获得对象,通过对象就可以访问它的方法,从而实现某种业务。

通过上面的例子,相信读者已经对IoC的思想有了基本的认识,接下来我们一起来看一下什么是DI。


二、什么是 DI?

DI(Dependency Injection),即依赖注入 :它是实现 IoC 的一种方法,在容器创建对象后,负责处理对象的依赖关系。

更直白的说法:Java类通常是包含域的(如果一个类没有域,只有方法,一般不必实例化,设置为静态类更合适),在对象实例化时,需要将域对应的数据注入到对象中,才能真正实例化(如果不为域注入数据,那就只能是默认值),这些 “刚需” 的数据就是 “依赖”,而将这些 “依赖” 注入到对象中的过程就是 “依赖注入”。

一个没有完成依赖注入的对象,只是行尸走肉,如下例子:采用默认的构造器创建BookDAO类对象,由于没有为域注入数据,这个对象采用的只是默认值,算不上真正实例化,如同行尸走肉。

public class BookDAO
{
    private String agentName;
    private double x;

    public static void main(String[] args)
    {
        BookDAO temp = new BookDAO();
        System.out.println(temp.agentName + "  " + temp.x);
    }
}

运行结果:null  0.0

依赖注入spring的注入方式有四种:set注入方式;基于注解的方式;构造方法注入方式;静态工厂注入方式。

 构造方法注入方式实例

1. 创建一个BookDAO类,与上面的例子不同,这里为BookDAO增加了两个属性和构造方法,如下:

/**
 * 图书数据访问实现类,访问数据库不是本文的重点,为简单起见,这里只是模拟访问数据库
 * 并没有真正存储图书信息到数据库中。
 */
public class BookDAO implements IBookDAO
{
    // 经办人
    private String agentName;

    // 库房信息
    private StoreInfo storeInfo;

    BookDAO(String agentName, StoreInfo storeInfo)
    {
        this.agentName = agentName;
        this.storeInfo = storeInfo;
    }

    public String addBook(String bookname)
    {
        return "经办人:" + agentName + "添加图书:" + bookname + "仓库信息:" + storeInfo.getStoreInfo();
    }
}

其中,属性StoreInfo的定义如下:

public class StoreInfo
{
    // 库房id
    private int id;
    // 书架编号
    private String bookshelfNum;

    StoreInfo(int id, String bookshelfNum)
    {
        this.id = id;
        this.bookshelfNum = bookshelfNum;
    }

    public String getStoreInfo()
    {
        return "id: " + id + "bookshelfNum: " + bookshelfNum;
    }
}

2. 配置bean的XML文件,命名为 IOCBeans01.xml,其中定义了两个bean,如下所示。很明显,和前面的配置很不一样,由于类BookDAO和StoreInfo都是有属性的,构造器也带有参数,容器根据配置文件创建对象,自然是需要注入参数的,如何注入呢?本例中采用构造器注入的方式,即 “constructor-arg” 标签所示,对于非引用类型,name对应属性名,value指定其值(这只是参数解析方式之一,还可以通过type、index来传递构造器的参数)。

需要特别注意的是:BookDAO类的属性之一storeInfo,它依赖一个StoreInfo类对象,因此,为了创建实例化BookDAO,需要实例化它的依赖StoreInfo。因此,XML配置文件中定义了两个bean,分属这两个类,在bean “bookdao”中,其构造器注入参数有一个引用 “ref标签”,它引用的是另一个bean “storeInfo”。Spring 容器会根据配置文件创建bean(对象),并处理它们的依赖关系,这个过程也就是所谓的依赖注入——DI(Dependency Injection)。

<?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:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
    
    <bean name="storeInfo" class="com.yingshulan.spring.spring_learning.StoreInfo">
    	<constructor-arg name="id" value="129"></constructor-arg>
    	<constructor-arg name="bookshelfNum" value="Noth-A1-002"></constructor-arg>
    </bean>  
    
    <bean id="bookdao" class="com.yingshulan.spring.spring_learning.BookDAO">
    	<constructor-arg name="agentName" value="Jack Ma"></constructor-arg>
    	<constructor-arg ref="storeInfo"></constructor-arg>
    </bean>
</beans>

3. Spring容器通过XML配置文件创建对象

public class BookService
{

    private IBookDAO bookDAO;
    
    public static void main(String[] args)
    {
        BookService bookservice = new BookService();
        bookservice.storeBook("《深入浅出理解 Spring 3.X 精要》");
    }

    public BookService()
    {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("IOCBeans01.xml");
        bookDAO = (IBookDAO) ctx.getBean("bookdao");
    }

    /**
     * Service层: 调用DAO层的方法写信息到数据库
     * @param bookname
     */
    public void storeBook(String bookname)
    {
        System.out.println("增加图书信息到数据库");
        String result = bookDAO.addBook(bookname);
        System.out.println(result);
    }
}

运行结果:

增加图书信息到数据库
经办人:Jack Ma添加图书:《深入浅出理解 Spring 3.X 精要》仓库信息:id: 129bookshelfNum: Noth-A1-002

三、set注入方式,基于注解的方式

上面介绍了注入方式之一——基于构造器注入的方式,本节介绍另外几种注入方式:set注入方式;基于注解的方式

3.1 set注入方式

除了构造器,bean的属性也可以通过set方法(Java中称为设置器)来设置(注入)。对第二节中的例子进行修改,如下所示:为属性agentName添加了一个set方法。

public class BookDAO implements IBookDAO
{
    // 经办人
    private String agentName;
    
    public void setAgentName(String agentName)
    {
        this.agentName = agentName;
    }

    public String addBook(String bookname)
    {
        return "经办人:" + agentName + "添加图书:" + bookname;
    }
}

对应的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:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd"> 
    
    <bean id="bookdao" class="com.yingshulan.spring.spring_learning.BookDAO">
    	<property name="agentName" value="Jack Ma"></property>
    </bean>
</beans>

配置文件中使用了property标签,Spring是这样解释的:Bean definitions can have zero or more properties. Property elements correspond to JavaBean setter methods exposed by the bean classes. Spring supports primitives, references to other beans in the same or related factories, lists, maps and properties. 简言之,property 标签定义的元素对应的是bean 类的设置器,这种对应关系默认以编程规范为基础:set方法,即所谓设置器,命名需遵循规范。

3.2 基于注解的方式

前面的例子使用的是传统的xml配置完成IOC的,如果内容比较多则配置需花费很多时间,通过注解可以减轻工作量,但注解后修改要麻烦一些,偶合度会增加,应该根据需要选择合适的方法。

1. 修改BookDAO类,加入注解,如下所示

@Component("bookdaoObj")
public class BookDAO implements IBookDAO
{
    public String addBook(String bookname)
    {
        return "添加图书:" + bookname;
    }
}

在类上增加了一个注解Component,在类的开头使用了@Component注解,它可以被Spring容器识别,启动Spring后,会自动把它转成容器管理的Bean,并且Bean的ID为 “bookdaoobj”。

除了@Component外,Spring提供了3个功能基本和@Component等效的注解,分别对应于用于对DAO,Service,和Controller进行注解。

  1. @Repository 用于对DAO实现类进行注解。
  2. @Service 用于对业务层注解,但是目前该功能与 @Component 相同。
  3. @Constroller用于对控制层注解,但是目前该功能与 @Component 相同。

2. 修改XML配置文件,相比之前的例子,这里增加了命名空间与模式约束“xmlns:context="http://www.springframework.org/schema/context”,同时增加了注解扫描的范围,指定了一个包。

<?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:p="http://www.springframework.org/schema/p"
    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-4.3.xsd">
        <context:component-scan base-package="com.yingshulan.spring.spring_learning"></context:component-scan>
</beans>

特别说明:可以通过属性设置更加精确的指定扫描范围,如:

<context>标记常用属性配置:
resource-pattern:对指定的基包下面的子包进行选取
<context>子标记:
include-filter:指定需要包含的包
exclude-filter:指定需要排除的包

<!-- 自动扫描com.zhangguo.anno.bo中的类进行扫描 --> 
<context:component-scan base-package="com.zhangguo.anno" resource-pattern="bo/*.class" />

<context:component-scan base-package="com.zhangguo.anno" >

  <context:include-filter type="aspectj“ expression="com.zhangguo.anno.dao.*.*"/>
  <context:exclude-filter type=“aspectj” expression=“com.zhangguo.anno.entity.*.*”/>

</context:component-scan>

3. 修改BookService类,将原来构造方法中的代码直接写在了storeBook方法中,避免循环加载的问题。

@Component
public class BookService
{

    private IBookDAO bookDAO;
    
    public static void main(String[] args)
    {
        BookService bookservice = new BookService();
        bookservice.storeBook("《深入浅出理解 Spring 3.X 精要》");
    }


    public void storeBook(String bookname)
    {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("IOCBeans01.xml");
        
        bookDAO = (IBookDAO) ctx.getBean("bookdaoObj");
        System.out.println("增加图书信息到数据库");
        String result = bookDAO.addBook(bookname);
        System.out.println(result);
    }
}

运行结果:

增加图书信息到数据库
添加图书:《深入浅出理解 Spring 3.X 精要》

猜你喜欢

转载自blog.csdn.net/Jin_Kwok/article/details/82783946