Spring(4)-IOC-Spring的核心机制:依赖注入(面向接口)

Spring框架的核心功能有两个:
1>Spring容器作为超级大工厂,负责创建管理所有的java对象,这些java对象被称为Bean;
2>Spring容器管理容器中Bean之间的依赖关系,Spring使用一种被称为依赖注的方式来管理Bean之间的依赖关系;
一,依赖注入
A对象调用B对象,在传统模式下有如下两种做法:
1>原始做法:调用者主动创建被依赖对象,然后在调用被依赖对象的方法;
2>简单工厂模式:调用者先找到被依赖对象的工厂,然后主动通过工厂去获取被依赖对象,最后在调用被依赖对象的方法;
对于简单工厂方式,大致需要把握三点:
1>将被依赖对象的创建交给工厂完成;
2>调用者面向被依赖对象的接口编程;
3>调用者通过工厂来获得被依赖的组件;
通过这三点改造,可以保证调用者只须与被依赖对象的接口耦合,这就避免了类层次的硬编码耦合,但是缺点是调用者组件需要主动通过工厂去获取被依赖对象,这就会带来调用组件与被依赖对象工厂的耦合;
依赖注入通常有如下两种:
1>设值注入:IoC容器使用成员变量的setter方法来注入被依赖对象;
2>构造注入:IoC容器使用构造器来注入被依赖对象;
1,设值注入:
Spring推荐面向接口编程,这样可以更好的让规范和实现分离,从而提供更好的解耦,对于一个JavEE应用,不管是DAO组件,还是业务逻辑组件,都应该先定义一个接口,该接口定义了该组件应该实现的功能,但功能的实现则由其实现类提供,以便于后期的升级和维护,下面的示例更加规范:
<span style="font-size:18px;">package com.anlw.service;

public interface Person {
    //定义一个使用斧头的方法
    public void useAxe();
}</span>

<span style="font-size:18px;">package com.anlw.service;

public interface Axe {
    //Axe接口定义一个chop()的方法
    public String chop();
}</span>

<span style="font-size:18px;">package com.anlw.service.impl;

import com.anlw.service.Axe;

public class StoneAxe implements Axe {

    @Override
    public String chop() {
        // TODO Auto-generated method stub
        return "斧头砍柴好慢~";
    }
}</span>

<span style="font-size:18px;">package com.anlw.service.impl;

import com.anlw.service.Axe;
import com.anlw.service.Person;

public class Chinese implements Person {
    private Axe axe;
    //设值注入所需的setter方法
    public void setAxe(Axe axe){
        this.axe = axe;
    }
    //实现Perosn接口的useAxe()方法
    @Override
    public void useAxe() {
        //调用axe的chop()方法
        //表明Person对象依赖axe对象
        System.out.println(axe.chop());
    }
}</span>

<span style="font-size:18px;"><?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">

    <!--配置chinese实例,其实现类是Chinese类 -->
    <bean id="chinese" class="com.anlw.service.impl.Chinese">
        <!--驱动调用chinese的setAxe方法将容器中的stoneAxe作为参数传入 -->
        <property name="axe" ref="stoneAxe"></property>
    </bean>
    <!--配置stoneAxe实例,其实现类是StoneAxe  -->
    <bean id="stoneAxe" class="com.anlw.service.impl.StoneAxe"></bean>
</beans></span>

Spring配置文件配置bean实例通常会指定两个属性:
1>id:指定该Bean的唯一标识,Spring会根据id属性值来管理bean,程序通过id属性值来访问该bean实例;
2>class:指定该bean的实现类,此处不可再用接口,必须使用实现类,Spring容器会根据xml解析器读取该属性值,并利用反射来创建该实现类的实例;
测试方法:

<span style="font-size:18px;">import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.anlw.service.Person;
import com.anlw.service.impl.Chinese;

public class Test {
    public static void main(String[] args) {
        //创建Spring容器
        ApplicationContext  ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        //下面两个是重载方法,获取id为person的Bean
        Person c = (Person) ctx.getBean("chinese");
        //Person c = ctx.getBean("chinese", Person.class);
        //调用方法
        c.useAxe();
    }
}</span>

Spring IoC容器的三个基本要点:
1>应用程序的各组件面向接口编程。面向接口编程可以将组件之间的耦合关系提升到接口层次,从而有利于项目后期的扩展;
2>应用程序的各组件不再由程序主动创建,而是由Spring容器负责产生并初始化;
3>Spring采用配置文件或注解来管理bean的实现类、依赖关系,Spring容器则根据配置文件或注解,利用反射来创建实例,并为之注入依赖关系;


2,构造注入:
通过setter方法为目标bean注入依赖关系的方式被称为设值注入,另外还有一种注入方式,这种方式在构造实例时,已经为其完成了依赖关系的初始化,这种利用构造器来设置依赖关系的方式,被称为构造注入;
<bean>元素总是默认驱动Spring调用无参构造器来创建对象,那怎么驱动Spring调用有参构造器去创建对象呢?答案是<constructor-arg>子元素,每个<constructor-arg>子元素代表一个构造器参数,如果<bean>元素包含N个<constructor-arg>子元素,就会驱动Spring调用带N个参数定的构造器来创建对象;
对上面的设值注入的示例做两步更改:
1>

<span style="font-size:18px;">package com.anlw.service.impl;

import com.anlw.service.Axe;
import com.anlw.service.Person;

public class Chinese implements Person {
    private Axe axe;
    //构造注入所需的带参数的构造器
    public Chinese(Axe axe){
        this.axe = axe;
    }
    //实现Perosn接口的useAxe()方法
    @Override
    public void useAxe() {
        //调用axe的chop()方法
        //表明Person对象依赖axe对象
        System.out.println(axe.chop());
    }
}</span>

2>

<span style="font-size:18px;"><?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">

    <!--配置chinese实例,其实现类是Chinese类 -->
    <bean id="chinese" class="com.anlw.service.impl.Chinese">
        <!--下面只有一个Chinese子元素驱动Spring调用Chinese带一个参数的构造器来创建对象 -->
        <constructor-arg ref="stoneAxe"></constructor-arg>
    </bean>
    <!--配置stoneAxe实例,其实现类是StoneAxe  -->
    <bean id="stoneAxe" class="com.anlw.service.impl.StoneAxe"></bean>
</beans></span>

上面是构造注入的示例;
其反射代码如下:

<span style="font-size:18px;">String idStr = "chinese";//解析<bean>元素得到id属性值为chinese
String refStr = "stoneAxe";//解析<constructor-arg>元素得到的ref属性值为stoneAxre
Object paramBean = container.get("refStr");
//Spring会用反射的方式执行下面的代码,此处为了降低阅读难度,该行代码没有使用反射
Object obj = new com.anlw.service.impl.Chinese(paramBean);
//container代表Spring容器
container.put(idStr,obj);</span>

由此可以看出,使用了有参构造器创建实例,当Bean实例被创建完成后,该Bean的依赖关系已经设置完成,与设值注入的区别在于:创建Person实例中Axe属性的时机不同,设置注入是先通过无参数的构造器创建一个Bean实例,然后调用对应的setter方法注入依赖关系;而构造注入则直接调用有参构造器,当bean实例创建完成后,已经完成了依赖关系的注入;
配置<constructor-arg>元素时可以指定一个index属性,用于指定该构造参数值将作为第几个构造参数值,例如index="0"表明该构造参数值将作为第一个构造参数值;

注意几点:
1>
如果配置文件为:
<!-- 定义名为bean1的Bean,对应的实现类是lee.Test1-->
<bean id="bean1" class="lee.Test1">
    <constructor-arg value="hello"/>
    <constructor-arg value="23"/>
</bean>
上面的粗体字代码相当于让Spring调用如下代码:(Spring底层用反射执行该代码)
Object bean1 = new lee.Test("hello","23");
由于Spring本身提供了功能强大的类型转换机制,因此如果lee.Test1只包含一个Test1(String,int)构造器,那么上面配置文件相当于让Spring执行如下代码:(Spring底层用反射执行该代码)
Object bean1 = new lee.Test("hello",23);
那么如果lee.Test1既含有Test1(String,int)构造器,又含有Test1(String,String)构造器,肯定指定Test1(String,String)这个,因为23毕竟是字符串,正确匹配;
为了明确指定数据类型,Spring为<constructor-arg>元素指定了一个type属性,例如<constructor-arg value="23" type="int"/>

两种注入方式的对比:
相比之下,设值注入有如下优点;
1>与传统的JavaBean的写法更相似,程序开发人员更容易理解,接受,通过setter方法设定依赖关系显得更加直观,自然;
2>对于复杂的依赖关系,如果采用构造注入,会导致构造器非常臃肿,难以阅读,Spring在创建Bean实例时,需要同时实例化其依赖的全部实例,因而导致性能下降,而使用设置注入则能避免这些问题;
3>尤其在某些成员变量可选的情况下,多参数的构造器更加笨重;

某些特定的场景下,构造注入有如下优势:
1>构造注入可以在构造器中决定依赖关系的注入顺序,优先依赖的优先注入,例如组件中其他依赖关系的注入,常常需要依赖DataSource的注入。采用构造注入,可以在代码中清晰的决定注入顺序;
2>对于依赖关系无需变化的Bean,构造注入更有用处,因为没有setter方法,所有的依赖关系全部在构造器内设定,因此无需担心后续的代码对依赖关系产生破环;
3>依赖关系只能在构造器中设定,则只有组件的创建者才能改变组件的依赖关系,对组件的调用者而言,组件内部的依赖关系完全透明,更符合高内聚的原则;
建议采用以设值注入为主,构造注入为辅的注入策略;
对于依赖关系无需变化的注入,尽量采用构造注入;
而其他依赖关系的注入,则考虑采用设值注入;




评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值