如何使用IOC容器进行对象的管理和创建?

简介: 如何使用IOC容器进行对象的管理和创建?

Spring Framework系统架构:

IOC容器与依赖注入的引入:

IOC:Inversion of Control—反转控制

为什么要使用它呢?

我们先来看下图的代码实现方式,我们通常将业务处理和数据处理写在不同的类中:

但如果业务发生改变, 所对应的实现类不再是BookDaoImpl了,那么代码需要重新编写,测试和编译等,这样一来所产生的成本大大提高,不仅如此,由于我们在类中写了其他功能的实现,就导致我们的代码耦合度偏高,而java追求的则是“高内聚,低耦合”,因为低耦合的模块便于进行单元测试,且便于维护,代码关系过于紧密,往往改一小段代码,需要整个项目做很大的改动,所以在实际开发中应该尽量避免过高的耦合


那么要如何解决这种问题呢?


既然是由于我们在类中写了其他功能的实现,那么我们不写这个实现,耦合度不就低了吗?但我们如果仅仅是不写,耦合度是降低了,但是代码无法运行了呀,因为没有实现类的对象了


那么这个对象我们不能自己写,但又不能没有,由此对于上述的这种问题,解决方案为:使用对象时,在程序中不要主动使用new产生对象,转换为由外部提供对象,这也就是Ioc思想:将对象的创建控制权由程序转移到外部


那么这种思想的目的或者说效果到底是什么呢?


其实很简单,就是为了解耦


那么这种思想我们如何去实现呢?


就是通过我们接下来要学习的spring技术,spring技术提供了一个Ioc容器,用来充当Ioc思想的“外部”


那么也就是说,我们上述提到的导致耦合度偏高的代码,可以放在这个容器中实现,Ioc容器负责对象的初始化创建等工作,被创建或管理的对象在Ioc容器中被统称为Bean


但这似乎是有问题的,如下所示,此时的Ioc容器中有service对象和dao对象,假设我们现在需要使用service对象,那么直接从Ioc容器中拿取,但这似乎不太行,因为service对象的使用需要依赖dao对象,而Ioc此时我们只是拿到了service对象

既然service需要依赖dao对象才能运行,而这两个对象又都在Ioc容器中,由此Ioc又替我们做了一个工作,就是将二者进行绑定,使其在容器中也包含有这种依赖关系,而这项工作被称为依赖注入

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

无论是我们上述谈到的Ioc容器还是依赖注入,引入它们想要实现的共同目标即为充分解耦,使用Ioc容器管理Bean,在Ioc容器内将所有依赖关系的bean进行关系绑定(DI),而最终呈现的效果就是:使用对象时不仅可以直接从Ioc容器中获取,并且获取到的bean已经绑定了所有的依赖关系


在IDE中实现spring的入门应用:

首先创建一个新的项目:

创建类:

package xysfxy;
public class Hello_spring {
    public void showMessage(){
        System.out.println("hello,spring");
    }
}

由于我们可通过指定获取IOC容器,因此这里spring配置文件的名字可自行命名我们是基于XML来管理bean,因此在spring-xml文件中,我们需要配置bean标签,编写spring配置文件:

applicantsContext.xml:

id是每个bean对象的唯一标识符,名称可以自行创建class为上述创建的对象,注意:这里必须写全类名[包名+类名]

<?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">
<bean id="hellospring" class="xysfxy.Hello_spring"></bean>
</beans>

pom.xml文件中导入依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.example</groupId>
    <artifactId>SSM6</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <maven.compiler.source>18</maven.compiler.source>
        <maven.compiler.target>18</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties><dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.10.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.1</version>
        <scope>test</scope>
    </dependency>
</dependencies>
</project>

编写测试类:

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import xysfxy.Hello_spring;
public class Hello_springtext {
    @Test
    public void test(){
    //ApplicationContext是Spring中的核心接口和容器,允许容器通过应用程序上下文环境创建、获取、管理bean
    //ClassPathXmlApplicationContext:可以加载类路径下的配置文件,要求配置文件必须在类路径之下[将该xml文件放在resources目录下]
       //获取IOC容器
        ApplicationContext ioc=new ClassPathXmlApplicationContext("applicantsContext.xml");
        //获取bean对象--通过id获取,需要将其转换为对应的类型
        Hello_spring helloSpring=(Hello_spring) ioc.getBean("hellospring");
        helloSpring.showMessage();
    }
}

消息成功输出,对象获取成功!

使用IOC 进行对象的管理和创建时,无参构造不能缺失:

创建实体类:

package xysfxy;
public class student {
    private  Integer sid;
    private  String sname;
    private  Integer age;
    private  String gender;
    public Integer getSid() {
        return sid;
    }
    public void setSid(Integer sid) {
        this.sid = sid;
    }
    public String getSname() {
        return sname;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    public void setSname(String sname) {
        this.sname = sname;
    }
    public String getGender() {
        return gender;
    }
    public void setGender(String gender) {
        this.gender = gender;
    }
    @Override
    public String toString() {
        return "studnet{" +
                "sid=" + sid +
                ", sname=" + sname +
                ", age=" + age +
                ", gender='" + gender + '\'' +
                '}';
    }
    public student(Integer sid, String name, Integer age, String  gender ) {
        this.age=age;
        this.sname=sname;
        this.gender=gender;
        this.sid = sid;
    }
}

编写对应的spring.xml文件:

applicantsContext.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="student1" class=" xysfxy.student"></bean>
</beans>

此时编译器就已经报错了:没有匹配的构造器…

测试类:

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import xysfxy.student;
public class student_text {
    @Test
    public void test(){
        ApplicationContext ioc=new ClassPathXmlApplicationContext("applicantsContext.xml");
        //通过id获取bean对象,需要将获取到的对象强制转化为实体类的
       student student=(student) ioc.getBean("student1");
        System.out.println(student);
    }
}

下述报错:缺少无参构造

点开XML文件,我们发现报错为:没有一个匹配的构造器在student类中

上述报错原因如下:

通过IOC进行对象管理,在底层使用的是工厂模式,我们在没有学习spring之前创建对象使用的是new构造方法,那么spring是如何创建构造方法的呢?我们要想获取IOC容器是通过配置文件中的bean标签的class属性对象的对象类型,这个类型并不是确定的,因为spring.xml文件中可以配置多个bean,在类型不确定的情况下,我们应通过反射创建对象,而在IOC容器中,正好是通过反射+工厂模式进行对象的管理和创建,我们知道了类型之后,通过反射中的class.forName,再通过class中的newInstace方法来创建实例化对象,而这个过程正要用到无参构造,无参构造是固定的,但有参构造并不是,在有参构造中,包含的是成员变量的赋值,而这一过程可以进行重载,一个类可以重载很多个有参构造,因此,我们需要知道大部分使用反射进行对象创建的都需要使用无参构造


解决办法,加上无参构造:

成功输出,由于我们并没有给成员变量赋值,因此它输出的是默认值

获取bean的三种方式和注意事项:

通过IOC容器获取bean时,我们会发现可提供的方法有很多,我们可以通过id,类型,id+类型等


上述的案例中,我们就是根据bean的id值获取bean对象的,这里就不过多重复了,主要说一下其他的方法,需要注意的就是通过id获取到的bean对象需要通过强制类型转换为实体类对象,而根据类型获取就不存在这个问题

2:根据类型获取:

 //根据类型获取的bean对象就是我们的实体类对象,因此并不需要强制转换类型
 student student1= ioc.getBean(student.class);
 //输出获取到的对象值
 System.out.println(student1);

与通过id获取到的结果完全一样!


上述我们说到id是每一个bean对象唯一的标识符,那么IOC进行获取的时候,能够通过id值准确无误的找到我们要获取的bean对象,但我们也说过,一个beans子标签中可以出现多个bean标签,那么当我们在spring.xml文件中的beans标签中写了多个bean标签,并且他们的类型都是student,那么IOC容器通过类型获取bean对象时,会获取到哪一个呢?


如下所示:

<bean id="student1" class=" xysfxy.student"></bean>
<bean id="student2" class=" xysfxy.student"></bean>

测试结果如下:

IOC并没有获取到他们其中的一个,而是直接给我们报错,报错内容为:不唯一的bean定义异常, 其实早在学习mybatis框架时,我们就说过,如果当使用框架遇到模棱两可,我们都想不清楚到底是哪一个的时候, 框架一定会报错,因为它也想不清楚到底是哪个

由此我们可以得出一个结论,根据类型获取bean时,要求IOC容器中只有一个类型匹配的bean


上述我们是由于有多个类型匹配的bean而引发的异常,但如果一个都没有呢?还会报错吗?


一试便知!


我们将配置文件中有关student类的bean标签删除,再次运行程序!


测试结果如下,编译器报错:没有找到匹配的bean对象


小结:

若没有一个类型匹配的bean,此时抛出异常:NoSuchBeanDefinitionException
若有多个类型匹配的bean,此时抛出异常:NoUniqueBeanDefinitionException

3:根据类型+id值获取:

测试类中:

student student2=ioc.getBean("student1", student.class);
 System.out.println(student2);

输出如下所示,这种获取方式也能够正确的获取到bean对象:

说到这里,我们已经介绍了三种获取bean对象的方式:


第一种通过id获取,确保能够准确无误的获取到bean对象,但它需要强制转换成我们实体类类型


第二种通过class获取,不需要强制转换类型,但可能会出现一个都未匹配到或者匹配到多个的异常发生


第三种通过id和class进行获取,既不需要强制转换类型,也不会出现多个bean对象类型都匹配的问题,那么这三种方式到底那种是我们所推荐的呢?


其实是第二种根据bean的类型获取,原因是:上述我们在进行第二种方式获取bean对象的测试中,那种现象其实是几乎不会发生的,因为在IOC容器中一个类型的bean只需要配置一个,因为bean既可以单例也可以多例,下述为出现多个同类型bean的问题代码:

<bean id="student1" class=" xysfxy.student"></bean>
<bean id="student2" class=" xysfxy.student"></bean>

该代码是想通过第一个bean标签获取一个student类的一个对象,又想通过第二个bean标签获取一个新的student类的对象,这样显然是多余的,我们可以根据bean标签的scope属性去设置它的单例和多例,如下所示:


我们没有必要给一个类设置多个bean,因此上述三种获取bean的方式中,根据类型获取bean对象的方式是我们最常用的

扩展:

Question1:bean标签中的类可以写接口吗?

答案:不能

因为一个bean对应一个对象该对象将来是要实例化的,而接口并不能进行实例化因此在bean标签中的class属性我们只能写一个具体的类型,而不是接口

Question2:如果组件类实现了接口,根据接口类型可以获取bean吗?

答案:可以,但前提是bean必须唯一

组件类其实就是bean所管理的对象, 一个由bean所管理的对象就叫一个组件类

举例:

编写person接口:

package xysfxy;
public interface person {
}

让student类实现该接口!

在测试类中获取该bean对象:

 //方式1:通过类型获取bean对象,再将该对象进行上转型至接口
 person student1= ioc.getBean(student.class);
 System.out.println(student1);
 //方式2:通过该类实现的接口或者继承的父类而获取bean对象
 person person=ioc.getBean( person.class);
 System.out.println(person);

无论上述那种方式均可以!

Question3:如果一个接口有多个实现类,这些实现类都配置了bean,根据接口类型可以获取bean吗?

答案:不可以,因为bean不唯一


这种情况就是我们上述在学习根据类获取bean对象时,所描述的那种特殊情况,spring也不知道要找哪一个


小结:根据类型来获取bean时,在满足bean唯一性的前提下,其实只是看:对象 instanceof 指定的类型[判断一个对象是否属于指定类型的操作符。它返回一个布尔值,如果对象是指定类型或其子类的实例,则返回true] 的返回结果,只要返回的是true就可以认为和类型匹配,能够获取到,也就是说通过bean的类型,bean所继承的类类型,bean所实现的接口的类型都可以获取到bean

相关文章
|
1月前
|
Java 测试技术 开发工具
ApplicationArguments读取应用程序参数并注入到IOC容器
ApplicationArguments读取应用程序参数并注入到IOC容器
ApplicationArguments读取应用程序参数并注入到IOC容器
|
2月前
|
XML Java 数据格式
Spring IoC容器初始化过程(xml形式)
Spring IoC容器初始化过程(xml形式)
46 0
|
1天前
|
XML Java 数据格式
手写spring第八章-定义标记类型Aware接口,实现感知容器对象
手写spring第八章-定义标记类型Aware接口,实现感知容器对象
4 0
|
1月前
|
存储 前端开发 Java
springboot中的第二个IOC容器BootstrapContext
springboot中的第二个IOC容器BootstrapContext
springboot中的第二个IOC容器BootstrapContext
|
1月前
|
XML Java 数据格式
Spring 的奇幻起源:从 IoC 容器到 Bean 的魔法世界 (下)
Spring 的奇幻起源:从 IoC 容器到 Bean 的魔法世界
|
1月前
|
XML Java 数据格式
Spring 的奇幻起源:从 IoC 容器到 Bean 的魔法世界 (上)
Spring 的奇幻起源:从 IoC 容器到 Bean 的魔法世界 (上)
|
3月前
|
索引 Python 容器
Python容器专题 - deque(队列)--双向队列对象
Python容器专题 - deque(队列)--双向队列对象
35 0
|
3月前
|
XML Java 数据格式
Spring5源码(15)-IoC容器启动过程简析及XmlBeanFactory初始化
Spring5源码(15)-IoC容器启动过程简析及XmlBeanFactory初始化
31 1
|
3月前
|
XML Java 数据格式
Spring5源码(12)-IoC容器简介
Spring5源码(12)-IoC容器简介
32 1
|
3月前
|
XML Java 数据格式
Spring之容器:IOC(3)
【1月更文挑战第14天】 11、实验十:bean的作用域 12、实验十一:bean生命周期 13、实验十二:FactoryBean 14、实验十三:基于xml自动装配
42 1

热门文章

最新文章