Spring的工作原理(二)IOC-DI

 

目录

                   

Spring的工作原理(二)IOC-DI

 

一、什么是IOC,什么是DI?

1.1 IOC(Inversion of Control)-控制反转。

1.2 DI(Dependency Injection)-依赖注入。

二、IOC控制反转基础知识

2.1 IOC对bean的获取

2.2 IOC容器对Bean的管理


                 

                         Spring的工作原理(二)IOC-DI

由上一篇文章其实借着打印HelloWorld的这个例子已经简单理解了什么是控制反转。这里再一次详细讲解一下。

 

一、什么是IOC,什么是DI?

先把定义记住,后面才能有序的进行,否则就容易看不懂。

1.1 IOC(Inversion of Control)-控制反转。

控制反转与其定义为Spring中的一种技术,不如说是一种设计思想。在之前我们使用new的方式来进行创建对象,现在创建对象的权利交给了Spring来统一管理:即对对象的定义、作用域、生命周期、后置处理器、定义集成。

1.2 DI(Dependency Injection)-依赖注入。

        懂了控制反转之后,依赖注入就容易理解了,依赖注入的原理就是控制反转,因为管理权在Spring,如果对象与对象之间存在一定的联系,那么由Spring容器(或者叫IOC容器)动态的将某个依赖关系注入到组件当中,这个过程就是依赖注入。组件之间的依赖关系由容器运行期决定。

举个例子

在Spring框架下,当Bean实例 A运行过程中需要引用另外一个Bean实例B时,Spring框架会创建Bean的实例B,并将实例B通过实例A的构造函数、set方法、自动装配和注解方式注入到实例A,这种注入实例Bean到另外一个实例Bean的过程称为依赖注入。

二、IOC控制反转基础知识

        本节就是讲解IOC怎么进行控制反转的,并且通过哪一个类、或者哪一个接口实现的。它是怎么进行管理Bean对象的?管理了对象的哪一些地方?怎么进行创建?销毁时间是什么?下面就详细介绍一下:

2.1 IOC对bean的获取

首先,被Spring管理的类使用哪一个接口创建?那就需要了解一下Spring中的Spring BeanFactory容器和Sring ApplicationContext容器。

2.1.1 Spring BeanFactory容器

这个容器接口在org.springframework.beans.factory.BeanFactor中被定义。BeanFactory中有大量的接口被实现,最常用的就是XmlBeanFactory类。它可以从一个XML文件中读取元数据,获取Bean。

例如我们声明了一个Spring的xml配置文件为Beans.xml,实体类为HelloWorld.java

   <bean id="helloWorld" class="com.tutorialspoint.HelloWorld">
       <property name="message" value="Hello World!"/>
   </bean>

我们使用XmlBeanFactory进行读取Beans.xml,并创建HelloWorld的方式为:

XmlBeanFactory factory = new XmlBeanFactory(new ClassPathResource("Beans.xml"));
HelloWorld obj = (HelloWorld) factory.getBean("helloWorld"); //HelloWorld被Bean设置的Id为"helloWorld"

分析一下:

第一步就是利用Spring框架提供的XmlBeanFactory()API生成工厂Bean并利用 ClassPathResource() API 去加载在路径 CLASSPATH 下可用的 bean 配置文件。在此时XmlBeanFactory()初始化所有的Bean对象。

第二步就是利用第一步生成Bean工厂对象,利用getBean()方法获取所需要的Bean。该方法是通过配置文件中的Bean Id来返回一个真正的对象。

2.1.2 Spring ApplicationContext 容器

说到ApplicationContext容器,其实它是BeanFactory 的子接口。称为Spring上下文。

接口:org.springframework.context.ApplicationContext interface中被定义。

最常被使用的 ApplicationContext 接口实现:

  • FileSystemXmlApplicationContext:该容器从 XML 文件中加载已被定义的 bean。在这里,你需要提供给构造器 XML 文件的完整路径。

  • ClassPathXmlApplicationContext:该容器从 XML 文件中加载已被定义的 bean。在这里,你不需要提供 XML 文件的完整路径,只需正确配置 CLASSPATH 环境变量即可,因为,容器会从 CLASSPATH 中搜索 bean 配置文件。

  • WebXmlApplicationContext:该容器会在一个 web 应用程序的范围内加载在 XML 文件中已被定义的 bean。

例如我们声明了一个Spring的xml配置文件为Beans.xml,实体类为HelloWorld.java

   <bean id="helloWorld" class="com.tutorialspoint.HelloWorld">
       <property name="message" value="Hello World!"/>
   </bean>

我们使用FileSystemXmlApplicationContext进行读取Beans.xml,并创建HelloWorld的方式为:

ApplicationContext context = new FileSystemXmlApplicationContext("C:/Users/ZARA/workspace/HelloSpring/src/Beans.xml");
HelloWorld obj = (HelloWorld) context.getBean("helloWorld");

其他两个同理。不过是路径不同。

分析一下:

第一步生成工厂对象。加载完指定路径下 bean 配置文件后,利用框架提供的 FileSystemXmlApplicationContext API 去生成工厂 bean。FileSystemXmlApplicationContext 负责生成和初始化所有的对象,比如,所有在 XML bean 配置文件中的 bean。

第二步用第一步生成的上下文中的 getBean() 方法得到所需要的 bean。 这个方法通过配置文件中的 bean ID 来返回一个真正的对象。一旦得到这个对象,就可以利用这个对象来调用任何方法。

2.2 IOC容器对Bean的管理

由2.1 我们可以知道在Bean中定义了一个HelloWorld对象。就来详细研究一下,Bean的定义、作用域、生命周期。

2.2.1 SpringBean定义

属性描述
class这个属性是强制性的,并且指定用来创建 bean 的 bean 类。
name这个属性指定唯一的 bean 标识符。在基于 XML 的配置元数据中,你可以使用 ID 和/或 name 属性来指定 bean 标识符。
scope这个属性指定由特定的 bean 定义创建的对象的作用域。
constructor-arg基于构造函数的依赖注入。
properties基于设置函数的依赖注入。
autowiring mode自动装配实现
lazy-initialization mode延迟初始化的 bean 告诉 IoC 容器在它第一次被请求时,而不是在启动时去创建一个 bean 实例。
initialization 方法在 bean 的所有必需的属性被容器设置之后,调用回调方法。
destruction 方法当包含该 bean 的容器被销毁时,使用回调方法。

Spring和Bean之间的关系图:

2.2.2 配置数据元供给Spring

(1)基于 XML 的配置文件

(2)基于注解的配置

(3)基于 Java 的配置

让我们一一来看一下:

(1)基于 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-3.0.xsd">

   <!-- 基础的Bean对象加载方式 -->
   <bean id="..." class="...">
       <!-- collaborators and configuration for this bean go here -->
   </bean>

   <!-- Bean的懒加载方法 -->
   <bean id="..." class="..." lazy-init="true">
       <!-- collaborators and configuration for this bean go here -->
   </bean>

   <!-- Bean初始化方法 -->
   <bean id="..." class="..." init-method="...">
       <!-- collaborators and configuration for this bean go here -->
   </bean>
   <!-- Bean的销毁方法 -->
   <bean id="..." class="..." destroy-method="...">
       <!-- collaborators and configuration for this bean go here -->
   </bean>

   <!-- more bean definitions go here -->

</beans>

对于Spring Bean的定义,属于默认空间命名,没有空间名。

xmlns="http://www.springframework.org/schema/beans"

对于xsi来说,它是一个标准命名空间。这个命名空间为每个文档中指定相应的Schema文件,这是标准组织定义的标准命名空间。

 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

(2)基于注解的配置

自从Spring2.5之后,Spring就可以用配置注解的方式进行依赖注入。该种方式无需在xml中配置Bean引用。

注解配置默认在Spring中是关闭的,需要在配置文件中进行打开。(引入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: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-3.0.xsd">

    <context:annotation-config/>

</beans>

下面就可以使用注解,解释几个常用的重要注解:

  • @Required注解应用于bean属性的setter方法,用来检查是否已经设置了所有必需的属性。
  • @Autowired它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。
  • @Qualifier,通过指定确切的将被引用的bean,@Autowired@Qualifier注解可以用来删除混乱
  • SR-250 Annotations,Spring支持JSR-250的基础的注解,其中包括了@Resource@PostContruct@PreDestory注解

简介项目:

pom.xml文件中引入了

spring-core spring-beans spring-context junit

package com.mcb.spring.vo;
public class HelloWorld {
    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public void sayHello(){
        System.out.println(name+"说: Hello World");
    }
}
<?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:annotation-config />
    <bean id="hello" class="com.mcb.spring.vo.HelloWorld">
        <property name="name" value="Sum"/>
    </bean>
</beans>
import com.mcb.spring.vo.HelloWorld;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestSpring {
    @Test
    public void sayHello(){
        ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
        HelloWorld hello = (HelloWorld)context.getBean("hello");
        hello.sayHello();
    }
}

A. @Required

将该注解加到setter 方法中:

    @Required
    public void setName(String name) {
        this.name = name;
    }

此时如果我们将Beans.xml中name的<property>属性注释掉,我们会发现class报错。并提示"必须的参数缺失"。

如果我们将注解@Required去掉,并且同样注释参数,运行程序。运行成功,但是参数为属性name的值为 null。

B.@Autowired

1,setter方法中的@Autowired

我们稍微改造一下上面的工程,添加Person类

Person类:

package com.mcb.spring.vo;

public class Person {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

定义Beans.xml。将Person放在Spring进行管理。

    <context:annotation-config />
    <bean id="hello" class="com.mcb.spring.vo.HelloWorld">
        <!--<property name="name" value="Sum"/>-->
    </bean>

    <bean id="person" class="com.mcb.spring.vo.Person">
        <property name="name" value="Tom"/>
    </bean>

将Person对象通过setter方法进行注入。

package com.mcb.spring.vo;
import org.springframework.beans.factory.annotation.Autowired;
public class HelloWorld {
    private String name;
    public String getName() {
        return name;
    }
    @Autowired    // 将Person类注入进来
    public void setName(Person person) {
        this.name = person.getName();
    }
    public void sayHello(){
        System.out.println(name+"说: Hello World");
    }
}

运行程序,让我们看一下结果:

2,同理在属性中和构造方法中@Autowired  ,原理一致。都可以将Person对象注入到HelloWorld中,进行name输出。

    @Autowired    // 将Person类注入进来
    private Person person;
    @Autowired
    public HelloWorld(Person person) {
        this.name = person.getName();
    }

C.@Qualifier

结合使用@Qualifier@Autowired注解通过指定哪一个真正的bean将会被装配来消除混乱。

还是以上图工程为例:配置同一个Preson,两个不同对象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: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:annotation-config />
    <bean id="hello" class="com.mcb.spring.vo.HelloWorld">
        <!--<property name="name" value="Sum"/>-->
    </bean>
    <bean id="person1" class="com.mcb.spring.vo.Person">
        <property name="name" value="Tom"/>
    </bean>
    <bean id="person2" class="com.mcb.spring.vo.Person">
        <property name="name" value="Jim"/>
    </bean>
</beans>

在HelloWorld中进行注入

package com.mcb.spring.vo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
public class HelloWorld {
    @Autowired
    @Qualifier("person2")
    private Person person;
    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public void sayHello(){
        System.out.println(person.getName()+"说: Hello World");
    }
}

运行测试类,进行打印:

D. SR-250 Annotations

在此我们了解一下一个注解@Resource。

上图使用了 @Autowired 和Qualifier来进行了注入。

@Autowired
@Qualifier("person2")
private Person person;

我们还可以使用@Resource进行注入。

@Resource(name = "person2")
private Person person;

可以达到同样的效果。

他们的区别如下:

  • @Autowired注解为Spring提供的注解,只按照byType方式注入,默认情况下,它要求依赖对象必须存在,如果允许为null,可以设置它的required属性为false,如果我们想按照byName方式来装配,可以结合@Qualifier注解一起使用;
  • @Resource为J2EE提供的注解,它有两个重要的属性:name和type。而默认情况下,@Resource注解按照byName的方式来装配。@Resource的装配顺序是这样的: 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常。如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常。如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或是找到多个,都会抛出异常。如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配。

(3)基于 Java 的配置

Spring中为了减少XML配置,可以声明一个配置类类对bean进行配置,主要用到两个注解@Configuration和@bean

第一步:在Spring中开启包扫描。

<beans>
     <context:component-scan base-package="com.mcb.spring.vo"/>
</beans>

第二步:建立SpringConfig类。

package com.mcb.spring.config;

import com.mcb.spring.vo.HelloWorld;
import com.mcb.spring.vo.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SpringConfig {
    @Bean
    public HelloWorld helloWorld(Person person){
        HelloWorld helloWorld = new HelloWorld();
        helloWorld.setName(person.getName());
        return helloWorld;
    }
    @Bean
    public Person person() {
        Person person = new Person();
        person.setName("Jim");
        return person;
    }
}

第三步:测试类使用AnnotationConfigApplicationContext获取ApplicationContext类。

import com.mcb.spring.config.SpringConfig;
import com.mcb.spring.vo.HelloWorld;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestSpring {
    @Test
    public void sayHello(){
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        HelloWorld hello = (HelloWorld)context.getBean(HelloWorld.class);
        hello.sayHello();
    }
}

运行正常。

在第一步中,也可以用@ComponentScan注解代替xml的方式进行。

@Configuration
@ComponentScan(basePackages = "com.mcb.spring.config")
public class SpringConfig {
    ......
}

温馨提示:什么时候下使用什么样的注解呢?

2.2.3 Spring Bean的作用域
当Spring定义一个Bean时,必须声明该作用域的选项。目前支持五个作用域,分别是:singleton、prototype、request、session、global session。

用例: bean属性:scope

<bean id="..." class="..." scope="singleton">
    <!-- collaborators and configuration for this bean go here -->
</bean>

2.2.4 Spring Bean 生命周期

Bean的生命周期可以表达为: Bean的定义--> Bean的初始化--> Bean的使用 --> Bean的销毁

A。Bean的初始化回调函数。(实现InitializingBean)

import org.springframework.beans.factory.InitializingBean;
    public void afterPropertiesSet() throws Exception {
        System.out.println("我是Init回调函数");
    }

B。Bean的销毁回调函数。(DisposableBean)

import org.springframework.beans.factory.DisposableBean;
public void destroy() throws Exception {
        System.out.println("我是destroy回调函数");
}

C。Bean的初始化方法、销毁方法自定义。

public void destoryMy() {
    System.out.println("我是销毁方法");
}
public void init() {
        System.out.println("我是初始化方法");
}
<bean id="helloWorld" class="com.mcb.spring.vo.HelloWorld" init-method="init" destroy-method="destoryMy">

</bean>

将A、B、C同时配置后,结果如下:

我是Init回调函数
我是初始化方法
Jim说: Hello World
我是destroy回调函数
我是销毁方法

温馨提示:InitializingBean和DisposableBean的回调方法尽量不要使用,因为XML配置在命名方法上提供了极大的灵活性。

调用destory的时候,需要关闭hook的registerShutdownHook();

ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
HelloWorld hello = (HelloWorld)context.getBean("helloWorld");
hello.sayHello();
((ClassPathXmlApplicationContext) context).registerShutdownHook();

默认初始化和销毁方法(Beans 标签下)

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JAVA和人工智能

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值