关闭

Spring的控制反转(IOC)

标签: springIoCDI控制反转依赖注入
254人阅读 评论(0) 收藏 举报
分类:

        IoC 全称 (Inverse Of Control) ,中文译为控制反转, Spring框架的核心基于控制反转原理。

        控制反转是一种将组件依赖关系的创建和管理置于程序外部的技术。由容器控制程序之间的关系,而不是由代码直接控制。由于控制权由代码转向了容器,所以称为反转。

        对象与对象之间的关系可以简单的理解为对象之间的依赖关系:在 A 类需要类 B 的一个实例来进行某些操作,比如在类 A 的方法中需要调用类 B 的方法来完成功能,叫做 A 类依赖于 B 类。

        一个需要特定的依赖的组件一般会涉及一个依赖对象,在 IOC 的概念中叫做目标 (target) 。换句话说, IOC 提供了这样的服务,使一个组件能够在它的整个生命周期中访问它的依赖和服务,用这种方法与它的依赖进行交互。总的来说, IOC 能够被分解为两种子类型:依赖查找和依赖注入。

        一、依赖查找

        比如使用 JNDI 注册一个数据库连接池时,代码中从注册处获得依赖关系的 JNDI 查找 (JNDI lookups) :

initContext = new InitialContext();
// 获取数据源
DataSource ds = (DataSource) initContext.lookup("java:comp/env/jdbc/mysql");

        二、依赖注入

        Dependency Injection简称DI:两个对象之间的依赖关系在程序运行时由外部容器动态的注入依赖行为方式称为依赖注入。在理解依赖注入之前,看如下这个问题在各种社会形态里如何解决:

        一个人(Java实例,调用者)需要一把斧子(Java实例,被调用者):

        (1)原始社会里,几乎没有社会分工。需要斧子的人(调用者)只能自己去磨一把斧子(被调用者)。对应的情形为:Java程序里的调用者自己创建被调用者。必然要求被调用的Java类出现在调用者的代码里。无法实现二者之间的松耦合。

        (2)进入工业社会,工厂出现。斧子不再由普通人完成,而在工厂里被生产出来,此时需要斧子的人(调用者)找到工厂,购买斧子,无须关心斧子的制造过程。对应Java程序的简单工厂的设计模式。调用者无须关心被调用者具体实现过程,只需要找到符合某种标准(接口)的实例即可使用。此时调用的代码面向接口编程,可以让调用者和被调用者解耦,这也是工厂模式大量使用的原因。但调用者需要自己定位工厂,调用者与特定工厂耦合在一起。

        (3)进入“按需分配”社会,需要斧子的人不需要找到工厂,坐在家里发出一个简单指令:需要斧子。斧子就自然出现在他面前。对应Spring的依赖注入。调用者无须自己定位工厂,程序运行到需要被调用者时,系统自动提供被调用者实例。事实上,调用者和被调用者都处于Spring的管理下,二者之间的依赖关系由Spring提供。

        所谓依赖注入,是指程序运行过程中,如果需要调用另一个对象协助时,无须在代码中创建被调用者,而是依赖于外部的注入。Spring的依赖注入对调用者和被调用者几乎没有任何要求,完全支持对POJO之间依赖关系的管理。有四种方式,分别是:

1、Setter注入方式

        这是最常用的注入方式,假设有一个SpringService类需要调用SpringDao类进行数据库操作,通常需要实例化(new一个)SpringDao对象。而Setter注入方式则可以定义一个private的SpringDao类的成员变量,然后创建该SpringDao的set方法。例如:

public class SpringService{   
    private SpringDao springDao;   
    public void setSpringDao(SpringDao springDao){  
        this.springDao = springDao;  
    }  
    public void operate(){  
        springDao.update();  
    }  
}  

        随后需要配置spring的xml文件,<bean>中的name属性是bean的一个别名,class属性是类的全名,因为在SpringService中有一个成员变量SpringDao,所以要在<bean>标签中创建一个<property>标签指定SpringDao的实例。<property>标签中的name就是SpringService类中的SpringDao的实例名,ref映射相对应定义的SpringDao对象:<bean name="springDao" class="...">,这样其实是将SpringDao对象实例化并且调用SpringService的setSpringDao方法将SpringDao实例注入

<bean name="springService" class="com.mytest.service.SpringService">   
    <property name="springDao" ref="springDao"></property>  
</bean>  
<bean name="springDao" class="com.mytest.dao.SpringDao"></bean>  

2、Constructor注入方式

        这种注入方式是指带有参数的构造方法注入,假如有类型分别是SpringDao和User的成员变量,但是并未设置对象的set方法,所以就不能用第一种注入方式。但可以在SpringService的构造方法中注入,也就是说在创建SpringService对象时将SpringDao和User类型的两个实列参数值传进来:

public class SpringService{  
    private SpringDao springDao;  
    private User user;  

    public SpringService(SpringDao springDao,User user){  
        this.springDao = springDao;  
        this.user = user;  
    }  

    public void operate(){  
        user.setName("xxx");  
        springDao.update(user);
    }  
}  

        在xml文件中不用<property>的形式,而是使用<constructor-arg>标签,ref属性同样指向对应定义的<bean>标签的name属性:

<bean name="springService" class="com.mytest.service.SpringService">  
    <constructor-arg ref="springDao"></constructor-arg>  
    <constructor-arg ref="user"></constructor-arg>  
</bean>  
<bean name="springDao" class="com.mytest.dao.SpringDao"></bean>  
<bean name="user" class="com.mytest.service.User"></bean> 

        解决构造方法参数的不确定性,你可能会遇到构造方法传入的两参数是同类型的,为了分清哪个是第一个参数哪个是第二个参数,则需要进行一些小处理。下面设置index标识参数位置:

<bean name="springService" class="com.mytest.service.SpringService">  
    <constructor-arg index="0" ref="springDao"></constructor-arg>  
    <constructor-arg index="1" ref="user"></constructor-arg>  
</bean> 

        另外还可以有其他属性,比如:

            type是指该属性所对应的类型;

            value是当注入的不是依赖自定义类,而是基本数据类型时,就用value;

3、注解注入方式

        就是在Java代码中使用注解的方式(主要是@Resource或@Autowired)进行装配。前提是需要在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:tx="http://www.springframework.org/schema/tx"
     xmlns:context=http://www.springframework.org/schema/context
     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
     http://www.springframework.org/schema/context
     http://www.springframework.org/schema/context/spring-context-3.2.xsd
     http://www.springframework.org/schema/tx 
     http://www.springframework.org/schema/tx/spring-tx-3.2.xsd">
     <context:annotation-config></context:annotation-config>或者<context:component-scan base-package="com.xxx,com.yyy"></context:component-scan>
</beans>

           只有配置了xmlns:context=http://www.springframework.org/schema/context以及<context:annotation-config></context:annotation-config>或者<context:component-scan base-package="com.xxx,com.yyy"></context:component-scan>才可以引入注解的命名空间,否则报错。以上的配置隐式地注册了多个对注释进行解析的处理器:AutowiredAnnotationBeanPostProcessor、CommonAnnotationBeanPostProcessor、PersistenceAnnotationBeanPostProcessor 等。

        <context:annotation-config> 和 <context:component-scan>的区别在于:<context:annotation-config>是用于激活那些已经在spring容器里注册过的bean上面的注解,而<context:component-scan>除了具有<context:annotation-config>的功能外,还可以在指定package下扫描并注册bean。当<context:annotation-config>和<context:component-scan>同时存在时前者被忽略。并且注解只会被注入一次,哪怕是手动注册了多个处理器,spring仍然只会处理一次。

        1><context:annotation-config> 方式:

        java:

public class SpringService{   
    private SpringDao springDao; 
    @Autowired或@Resource(name="springDao")
    public void setSpringDao(SpringDao springDao){  
        this.springDao = springDao;  
    }  
    public void operate(){  
        springDao.update();  
    }  
}  

        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:tx="http://www.springframework.org/schema/tx"
     xmlns:context="http://www.springframework.org/schema/context"
     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
     http://www.springframework.org/schema/context
     http://www.springframework.org/schema/context/spring-context-3.2.xsd
     http://www.springframework.org/schema/tx 
     http://www.springframework.org/schema/tx/spring-tx-3.2.xsd">
    <context:annotation-config></context:annotation-config>
    <bean name="springDao" class="com.mytest.dao.SpringDao"></bean>  
</beans>

        2><context:component-scan>方式:

        java:

@Component
public class SpringService{   
    private SpringDao springDao; 
    @Autowired或@Resource(name="springDao")
    public void setSpringDao(SpringDao springDao){  
        this.springDao = springDao;  
    }  
    public void operate(){  
        springDao.update();  
    }  
} 

        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:tx="http://www.springframework.org/schema/tx"
     xmlns:context="http://www.springframework.org/schema/context"
     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
     http://www.springframework.org/schema/context
     http://www.springframework.org/schema/context/spring-context-3.2.xsd
     http://www.springframework.org/schema/tx 
     http://www.springframework.org/schema/tx/spring-tx-3.2.xsd">
    <context:component-scan base-package="com.xxx"/>
</beans>

        虽然@Resource和@Autowired都可以来完成注入依赖,但它们之间是有区别和联系的:

        a.@Resource默认是按照名称来装配注入的,只有当找不到与名称匹配的bean才会按照类型来装配注入;

        b.@Autowired默认是按照类型装配注入的,如果想按照名称来转配注入,则需要结合@Qualifier一起使用;

        c.@Resource注解是又J2EE提供,而@Autowired是由spring提供,故减少系统对spring的依赖建议使用前者;  

        d.@Resource和@Autowired都可以标注字段或者该字段的setter方法之上。

4、自动装配注入方式

        Spring中提供了自动装配依赖对象的机制,但是在实际应用中并不推荐使用自动装配,因为自动装配会产生未知情况,开发人员无法预见最终的装配结果。自动装配是在配置文件中实现的,配置如下:

<bean id="xxx" class="xxx" autowire="byType">

        只需要配置一个autowire属性即可完成自动装配,不用再配置文件中写<property>,但是在类中还是要生成依赖对象的setter方法。

        Autowire的属性值有如下几种:

        1>byType:按属性类型装配,在容器中寻找该类型匹配的bean,如有多个,则会抛出异常,如果没有找到,则属性值为null;

        2>byName:按属性名称装配,在容器中查询与该属性名称相同的bean,如果没有找到,则属性值为null;

        3>constructor:与byType方式相似,不同之处在与它应用于构造器参数,如果在容器中没有找到与构造器参数类型一致的bean,那么将抛出异常;

        4>autodetect:通过bean类的自省机制(introspection)来决定是使用constructor还是byType的方式进行自动装配。如果没发现匹配的构造器,那么将使用byType的方式。

              


参考:http://blog.sina.com.cn/s/blog_833273af01018xkd.html

           http://blog.csdn.net/qh_java/article/details/44044441

           http://www.cnblogs.com/leiOOlei/p/3713989.html

           http://blog.csdn.net/baple/article/details/17891755

           http://blog.sina.com.cn/s/blog_99201d8901012dzu.html


0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:55339次
    • 积分:992
    • 等级:
    • 排名:千里之外
    • 原创:45篇
    • 转载:42篇
    • 译文:0篇
    • 评论:2条
    文章分类
    最新评论