基于XML的依赖注入及循环依赖问题

IoC概念

IoC是一个概念,是一种思想,其实现方式多种多样。当前比较流行的实现方式之一是DI。

基于XML的DI

<?xml version="1.0" encoding="UTF-8"?>
<!-- 一个xml文件 只有一个标签,这个标签就是beans,所有的东西都放在这个标签里面 -->
<!-- 1. xmlns:xmlnamespace,后面接的是url,它与类的pakage一样,是唯一标示的id-->
<!-- 2. xmlns:xsi  : 全称是xmlns:XMLSchema-instance, 他是xmlns下面的子标签,它是用来校验我们xml的规范-->
<!-- 3.  xsi:schemaLocation 这个有两个值,前一个就是我们的唯一标示符,后面是一个可以访问的地址,它用来提供xsd文件
         xsd文件用来校验我们xml的书写规范,以及哪些标签可以写,哪些不可以写,一些属性必须有什么值等
         项目每次启动时都会校验一下我们的xml文件,如果我们本地没有这个文件它就会连网下载
         PluggableSchemaResolver专门的类来加载一系列的xsd文件的类 它是在Spring自带的jar包中:org/springframework/beans/factory/xml/PluggableSchemaResolver.java
         绝对路径:/Users/luca/Downloads/spring-framework-5.2.8.RELEASE/libs/spring-beans-5.2.8.RELEASE-sources.jar!/org/springframework/beans/factory/xml/PluggableSchemaResolver.java
         这个xsd文件被存储在:/Users/luca/Downloads/spring-framework-5.2.8.RELEASE/libs/spring-beans-5.2.8.RELEASE.jar!/META-INF
         -->
<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">

    <import resource="applicationContext-service.xml"/>

1. XML文件结构


<?xml version="1.0" encoding="UTF-8"?>
<!-- 一个xml文件 只有一个标签,这个标签就是beans,所有的东西都放在这个标签里面 -->
<!-- 1. xmlns:xmlnamespace,后面接的是url,它与类的pakage一样,是唯一标示的id-->
<!-- 2. xmlns:xsi  : 全称是xmlns:XMLSchema-instance, 他是xmlns下面的子标签,它是用来校验我们xml的规范-->
<!-- 3.  xsi:schemaLocation 这个有两个值,前一个就是我们的唯一标示符,后面是一个可以访问的地址,它用来提供xsd文件
         xsd文件用来校验我们xml的书写规范,以及哪些标签可以写,哪些不可以写,一些属性必须有什么值等
         项目每次启动时都会校验一下我们的xml文件,如果我们本地没有这个文件它就会连网下载
         PluggableSchemaResolver专门的类来加载一系列的xsd文件的类 它是在Spring自带的jar包中:org/springframework/beans/factory/xml/PluggableSchemaResolver.java
         绝对路径:/Users/luca/Downloads/spring-framework-5.2.8.RELEASE/libs/spring-beans-5.2.8.RELEASE-sources.jar!/org/springframework/beans/factory/xml/PluggableSchemaResolver.java
         这个xsd文件被存储在:/Users/luca/Downloads/spring-framework-5.2.8.RELEASE/libs/spring-beans-5.2.8.RELEASE.jar!/META-INF
     4.在容器启动后,bean被使用到的时候才被加载。可以将Lazy-init设置为true       
-->
<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">

    <import resource="applicationContext-service.xml"/>

    <bean id="lucy" lazy-init="true" class="com.luca.Person">
        <!--        构造器参数,我们可以将这些值注入到我们对象中,这就是依赖注入的体现,同时我们的PErson类依赖Food类,我们通过xml文件new出Food类,然后注入到person类中-->
        <!--        value是具体的值,ref是某一个bean的id,我们也可以指定type,默认是自动识别的-->
        <!--        这里传入了三个参数,所以我们必须在Person类中提供对应的构造器-->
        <constructor-arg name="name" value="maxiaosan"> </constructor-arg>
        <constructor-arg name="food" ref="rice"> </constructor-arg>
        <constructor-arg name="age" value="18"> </constructor-arg>
    </bean>


</beans>

为了防止一个文件中有太多的bean,我们可以将bean分类,放置在不同的xml文件中。不过不用担心,ClassPathXmlApplicationContext是可以一次加载多个文件的。

ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(
"applicationContext.xml","applicationContext-service.xml");

当然文件一多,就要读取很多文件,所以我们还可以在xml文件中import其他xml文件

<import resource="applicationContext-service.xml"/>

2. Spring ioc container


spring ioc container管理一个或多个bean,bean来自xml中对bean定义的元数据(configurationmetadata)

元数据信息

Class 类


Name,id 标识
Scope 作用域
Constructor arguments 构造器注入
Properties 属性注入
autowiring mode 自动装配
lazy-initialization mode 懒加载
initialization method 初始化
destruction method 销毁

2.1 构造器注入 constructor-arg

Person的构造器

public Person(String name, Integer age, Food food) {
    super();
    this.name = name;
    this.age = age;
    this.food = food;
}

我们可以通过构造器来向对象中注入属性值,可以对constructor-arg添各种属性,比如index,name,type。

<bean id="person" name="human,star" scope="prototype" lazy-init="false" class="com.luca.Person">
        <constructor-arg name="name" type = "int" value="maxiaosan"></constructor-arg>
        <constructor-arg name="age" index = "0" value="18"></constructor-arg>
        <constructor-arg name="food" ref="food"></constructor-arg>
</bean>

2.2 属性注入


constructor-arg有缺陷,它比较死板,要提供对应类的构造器,我们可以通过属性注入,这样比较灵活,也比较常用。底层Spring调用的是对象属性的Set方法,所以我们一定要提供这些属性的Set方法

<bean id="person" class="com.luca.Person">

    <property name="age" value="19"><\property>
    <property name="name" value="zhangsan"><\property>

<\bean>
使用java.util.Properties

在set方法中把properties

 private Properties gift;
    public Properties getGift() {
        return gift;
    }

    public void setGift(Properties gift) {
        this.gift = gift;
    }
<property name="gift">
            <value> attack = 200 defende = 400</value>
        </property>

3.3 对集合进行注入


我们使用常用的方法,也就是属性注入的方法,来对对象的集合属性进行注入

Array
// private String[] myArray;  该段为java属性
<property name="myArray">

    <array>

        <value>北京</value>
        <value>上海</value>

    </array>

</property>
Set
<property name="set">
            <array>
                <value>a</value>
                <value>b</value>
                <value>c</value>
            </array>
        </property>
List
<property name="list">
           <array>
               <value>a</value>
               <value>b</value>
               <value>c</value>
           </array>
        </property>
Map
       <property name="map">
            <map>
                <entry key="a" value="1"/>
                <entry key="3" value="3"/>
                <entry key="5" value="6"/>
            </map>
        </property>

3. 作用域


spring为bean提供了6种作用域:singleton 、prototype 、websocket、request、session、application;后面4种只有在web-aware的ApplicationContext种才有用。用户也可以创建自定义的作用域。后面四种也是单例的,是单例的一种

  1. singleton scope 单例作用域: 每一个类,在一个容器内只能产生一个实例

  2. prototype scope 原型作用域: 该bean每次被注入,或者使用getBean()方法获取时,都返回一个新的实例。

  3. Request scope: 该作用域的bean,在每个HTTP request都会新建一个实例,当一个request结束后,该实例也会被丢弃。

  4. Session scope: 某一个用户在一段时间内,会使用同一个session,session有超时时间,过了超时时间则session失效。不同用户使用不同的session。

  5. Application scope: 该作用域的bean,每一个application会创建一个

每一个<bean>都有一个属性scope,用来指定生成对象的作用域,默认为(singleton)单例

<bean id="person" name="human,star" scope="prototype" class="com.luca.Person">
...
</bean>

4. SpringMVC下的单例


4.1 SpringMVC三层架构示意图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lNB6usbC-1603671420900)(/Users/luca/MarkText-img-Support/2020-09-07-20-15-00-image.png)]

MVC其实是:

controller层:在web中他是专门在处理一些url的请求的,不同的请求生成不同的业务对象

modle层:这就是我们的业务层,它用来处理任务,并将结果交给view层

view层:就是我们的展示层,将处理的结果response给用户

在SSM(Spring,SpringMVC,Mybatis)框架中Spring就是来帮我们new 对象的,而且new出来的是单例模式的对象,如果不用Spring来创建单例就要写很多的代码,所以Spring框架是帮我们省了很多事的。

4.2 线程安全问题

  1. 业务对象并没有做线程的并发限制,因此不会出现各个线程之间的等待问题,或是死锁问题

  2. MVC中的实体bean不是单例的

  3. 在SpringMVC中,因为性能问题都是使用的单例模式,这就导致了多个业务线程使用的对象都是同一个对象,这就会出现线程安全问题,使用锁可以解决,但是加锁会导致性能下降,与我们一开始为了性能而使用单例的目的相斥,所以在单例类中不要有状态数据。

  4. 我们这些状态数据一般直接手动写进ThreadLocal中,不需要和别的线程共享,起到线程隔离的作用。这也就是ThreadLocal的作用,典型的例子就是JDBC。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c1arqwj9-1603671420903)(/Users/luca/MarkText-img-Support/2020-09-07-21-18-16-image.png)]

引用类型的成员

其中引用类型的成员变量即我们在controller中注入的service,在service中注入的dao,这里将其定义为成员变量主

要是为了实例化进而调用里面的业务方法,在这些类中一般不会有全局变量,因此只要我们的业务方法不含有独立的

全局变量即使是被多线程共享,也是线程安全的。

Controller service dao
层中的业务类是多线程共享的,但是每个线程在处理数据的时候具体处理的数据是在每个线程中各自有一份。

controller层

  • final类型 线程安全

  • 成员变量 有状态数据有线程安全问题

5. 循环依赖的bean


循环依赖的测试代码URL:/Users/luca/SpringStu/SpringCirculardependenciesTest

5.1 循环依赖测试结果


通过属性注入能否getbean:

  • 循环依赖的bean都是singleton 成功

  • 循环依赖的bean都是prototype 失败

  • 同时有singleton和prototype
    当先获取的那个bean是singleton时,就会成功,否则失败

5.2 循环依赖测试结果分析


因为存在循环依赖,我们知道GC是无法对它们回收的,如果这些对象是单例的话,还可以接收,因为单例对象都只有一个,就算无法被回收所占用的内存资源也比较有限,所以Spring允许这样的操作,但是非单例的对象会越来越多,又无法被回收,存在内存泄漏,很危险;所以Spring有一套机制,它会检测到这种对象作用域全是prototype的循环依赖,并且在允许时直接报错。

5.3 Spring对非单例模式的循环依赖检测的实现


Spring是如何实现对非单例模式的循环依赖的检测:

5.3.1 Spring对scope为prototype类实例化过程
  1. 当一个bean的scope是原型(prototype,即非单例),首先Spring不会将其实例化,而是先将其添加到一个注册标中。

  2. Spring容器中有一个对象注册标,这个注册标类似于一个map,key为bean的id/name,value为该bean中用到的其他的bean。

  3. 当我们get一个prototype的bean,Spring先会检查这张表,会将这个对象用到的对象先实例化,之后在实例化这个对象

如果存在循环依赖,则一个对象都实例化不出,所以会报错

5.3.2 Spring对单例类实例化过程

为什么单例对象存在循环依赖能get成功是怎么实现的呢?

对于单例对象,Spring采用了另外一种方法:如果是单例对象,Spring不会将其添加到注册标中,而是直接实例化这个对象;之后,如果该对象中有其他对象的引用再去set。

所以就算有循环依赖也能实例化成功

6. bean的实例化顺序

6.1 Spring bean的默认实例化顺序


bean的实例化顺序就是xml中的顺序,比如下面的实例化顺序就是a,b,c。

<bean id="a"  class="com.luca.A"> </bean>     
<bean id="b"  class="com.luca.B"> </bean>
<bean id="c"  class="com.luca.C"> </bean>

6.2 depends-on属性


测试URL:/Users/luca/SpringStu/SpringDepends-on

  • 在一些情况下,如果一个对象中要使用另外对象的属性,我们将这中情况称为“一个对象持有另一个对象的弱引用”。这种情况下,我们就能有必要手动干预对象实例化的顺序。

  • 与之相对应的就是5中的情况,一个对象中要使用到另一个对象,我们称之为:“一个对象持有另一个对象的强引用”。如果是强引用直接用ref就可,ref也会保证实例化的顺序。

  • 如果是弱引用的话我们可以使用depends-on属性,bean标签中有一个depends-on属性, 比如下面代码,a依赖于b,所以Spring会先实例化b,然后在实例化。

<bean id="a"  class="com.luca.A" depends-on="b"> </bean>     
<bean id="b"  class="com.luca.B"> </bean>

6.3 lazy-init 属性


单例的bean只要读取xml文件后就会被实例化,如果想让对象在使用的时候在实例化可以将bean标签的lazy-init属性设置为true

7. 自动注入

    <bean id="a"  class="com.luca.A" lazy-init="true">
        <property name="b" ref="b"> </property>
    </bean>

    <bean id="b" scope="prototype"  class="com.luca.B">
        <property name="c" ref="c"> </property>
    </bean>

    <bean id="c" scope="prototype" class="com.luca.C">
        <property name="a" ref="a"> </property>
    </bean>

上面的代码我们可以偷懒,我们可以使用autowire属性来帮我们自动装配

autowire有两种一个是byName,一个是byType

上面代码我们可以写成

<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"
autowire="byType" >
//可以直接写在beans里,就省的每一个bean都写autwire

    <bean id="a"  class="com.luca.A" lazy-init="true" autowire="byName">
        //byName:a对象中对b对象的引用的名字要与b对象bean的id一致
    </bean>

    <bean id="b_id" scope="prototype"  class="com.luca.B" autowire="byType">
        //byType 只要b中对c对象的引用是C类就行,但b对象中不能出现多个对c的引用
        //不然识别不了
    </bean>

    <bean id="c" scope="prototype" class="com.luca.C" autowire="default">
        //默认是byType
    </bean>

</beans>
public class A {
    private B b_id;
}

public class B {
    private C c;
}


public class C {
    private A a;
}

8.工厂方式注入


工厂方法的测试代码URL:/Users/luca/SpringStu/Spring-CarFactory

为满足更复杂的需求,Spring也提供了工厂方式来创建更加灵活的Bean。

留意观察工厂类和实现类的创建次数

动态工厂

抽象接口 Car

public interface Car {

     public String getName();

     public String getPrice();

}

实现类 BMW车

public class Bmw implements Car{



     public String getName() {

            // TODO Auto-generated method stub

            return "别摸我";

     }

     public String getPrice() {

            // TODO Auto-generated method stub

            return "500000RMB";

     }

}

汽车工厂类 CarFactory

public class CarFactory {

    public Car getCar(String name) throws Exception{

        if (name.endsWith("bmw")) {
            return new Bmw();
        }else {
                throw new Exception("car not fond");
        }
    }
}

Bean配置

<bean id="carFactory" class="com.msb.CarFactory"></bean>
<bean id="car" factory-bean="carFactory" factory-method="getCar" >
    <constructor-arg value="bmw"></constructor-arg>
</bean>

静态工厂

Bean配置

<bean id="carStatic" class="com.msb.CarFactoryStatic" factory-method="getCar">
 <constructor-arg value="bmw"></constructor-arg>
</bean>

工厂类

public class CarFactoryStatic {

    public static Car getCar(String name) throws Exception{

        if (name.endsWith("bmw")) {
            return new Bmw();
        }else {
                throw new Exception("car not fond");
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值