SpringDI

       DI也就是依赖注入。在传统的程序设计过程中,调用者需要直接使用new关键字创建被调用者的实例,调用者和被调用者之间的耦合度很高,要由调用者亲自创建被调用者的实例对象,这样不利于软件的移植与维护。但在Spring中创建被调用者的工作不再由调用者来完成,SpringIOC容器系统运行时会动态的向某个对象提供它所需要的其他对象,不像以前需要new一个出来。那么如何提供呢?Spring的DI依赖注入实现了该功能。其实,说的通俗点,依赖注入就是对对象中的属性进行赋值的过程,只是现在这个过程不需要我们自己来做,而交由SpringIOC容器来做。

注入依赖

1、构造器注入

基于构造器的DI通过调用带参数的构造器来实现,每个参数代表着一个依赖。例如下面的学生类中有 姓名 年龄属性以及自行车的属性
package org.spring;
public class Student {
    private String name;
    private int age;
    private Bicycle bicycle;
    public Student(String name, int age, Bicycle bicycle) {
        this.name = name;
        this.age = age;
        this.bicycle = bicycle;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public Bicycle getBicycle() {
        return bicycle;
    }
    public void setBicycle(Bicycle bicycle) {
        this.bicycle = bicycle;
    }
}

用构造器对Student的属性进行注入有以下两种配置方式,其效果是等同的

1.1、根据构造参数类型进行匹配




    
        
     

用type属性来显式指定那些构造参数的类型

1.2、根据构造参数索引进行匹配

对于上面的Student类,Spring通过type属性指定的参数类型就可以知道xiyangyang对应的是String类型的参数,而23对应int类型的参数,但是如果Student类中有两个类型相同的属性,如:
public Student(String name, String address, Bicycle bicycle) {
    this.name = name;
    this.address = address;
    this.bicycle = bicycle;
}

因为name与address的类型都是String,所以Spring无法确定type为String的到底对应的是name还是address,这时 我们就需要通过index这个属性来确定参数了。




    
        
      

现在Spring就知道我们的第一个参数对应的是name,第二个参数对应的是address了。index属性来显式指定构造参数的索引, 注意index从0开始。

测试代码:
public class DITest {
    @Test
    public void test() {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        
        Student student = (Student) context.getBean("student");
        
        System.out.println(student.getName() + " : " + student.getAge() + " : " + student.getBicycle());
    }
}

控制台输出结果为:
xiyangyang: 23 : org.spring.Bicycle@109de5b
可见我们已经通过spring的配置文件为Student的所有属性注入了值。

2、Setter注入

通过调用无参构造器实例化bean之后,调用该bean的setter方法,即可实现 基于setter的DI。
此时的Student类
package org.spring;
public class Student {
    private String name;
    private int age;
    private Bicycle bicycle;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public Bicycle getBicycle() {
        return bicycle;
    }
    public void setBicycle(Bicycle bicycle) {
        this.bicycle = bicycle;
    }
}

注意用setter方式注入,需要该bean提供一个无参的构造器,此处有一个默认的构造器。 如果还有其他带参数的构造器,则需要显示的定义默认的构造器,否则Spring容器无法初始化该类。
setter方法注入的配置文件



    
    
    

测试代码不变,控制台输出 huitailang : 23 : org.spring.Bicycle@88df60,得知我们用setter注入方式也成功得为Student类的所有属性注入了值。

        这里需要注意的是property属性中name的值指的是在student类中是否有一个setName()方法,至于是否有name这个属性变量则不作要求(根据Java规范一般都会提供属性)。如果没有setName()方法,则程序报错。如 <property name = "name" value = "huitailang"   /> 则检测student类中是否有一个setName()方法。

        Spring会在容器被创建时验证容器中每个bean的配置,包括验证那些bean所引用的属性是否指向一个有效的bean(即被引用的bean也在容器中被定义)。然而, 在bean被实际创建之前,bean的属性并不会被设置。 对于那些singleton类型和被设置为提前实例化的bean(比如ApplicationContext中的singleton bean)而言,bean实例将与容器同时被创建。而另外一些bean则会在需要的时候被创建,伴随着bean被实际创建,作为该bean的依赖bean以及依赖bean的依赖bean(依此类推)也将被创建和分配。
         通常情况下,Spring会在容器加载时发现配置错误(比如对无效bean的引用以及循环依赖)。Spring会在bean创建时才去设置属性和依赖关系(只在需要时创建所依赖的其他对象)。这意味着即使Spring容器被正确加载,当获取一个bean实例时,如果在创建bean或者设置依赖时出现问题,仍然会抛出一个异常。因为一些配置问题而导致潜在的可见性被延迟,所以在默认情况下,ApplicationContext实现中的bean采用提前实例化的singleton模式。在实际需要之前创建这些bean将带来时间与内存的开销。而这样做的好处就是ApplicationContext被加载的时候可以尽早的发现一些配置的问题。不过用户也可以根据需要采用延迟实例化来替代默认的singleton模式。
       当协作bean被注入到依赖bean时,协作bean必须在依赖bean之前完全配置好。例如bean A对bean B存在依赖关系,那么Spring IoC容器在调用bean A的setter方法之前,bean B必须被完全配置,这里所谓完全配置的意思就是bean将被实例化(如果不是采用提前实例化的singleton模式),相关的依赖也将被设置好。
        循环依赖比如说,一个类A,需要通过构造器注入类B,而类B又需要通过构造器注入类A。如果为类A和B配置的bean被互相注入的话,那么Spring IoC容器将检测出循环引用,并抛出 BeanCurrentlyInCreationException异常。

public Student(String name, int age, Bicycle bicycle) {
    this.name = name;
    this.age = age;
    this.bicycle = bicycle;
}

public Bicycle(Student student) {
    this.student = student;
}

配置文件如下:

    


    
           
        

当启动Spring Ioc容器时,因为student与bicycle两个类互相引用,所以Spring容器无法成功启动,这时,我们就需要将构造函数的注入方式调整为属性注入方式。

依赖配置

idref元素

idref元素用来将容器内其它bean的id传给元素,同时提供错误验证功能。

 

    
        
    

上述bean定义片段完全地等同于(在运行时)以下的片段:



    

第一种形式比第二种更可取的主要原因是,使用idref标记允许容器在部署时验证所被引用的bean是否存在。而第二种方式中,传给client bean的targetName属性值并没有被验证。任何的输入错误仅在client bean实际实例化时才会被发现(可能伴随着致命的错误)。如果client bean 是prototype类型的bean,则此输入错误(及由此导致的异常)可能在容器部署很久以后才会被发现。

此外,如果被引用的bean在同一XML文件内,且bean名字就是bean id,那么可以使用local属性,此属性允许XML解析器在解析XML文件时对引用的bean进行验证。

   
   

引用bean的方式

       在前面的例子可以可以看到在元素内部可以使用ref元素。该元素用来将bean中指定属性的值设置为对容器中的另外一个bean的引用。如前所述,该引用bean将被作为依赖注入,而且在注入之前会被初始化(如果是singleton bean则已被容器初始化)。尽管都是对另外一个对象的引用,但是通过id/name指向另外一个对象却有三种不同的形式,不同的形式将决定如何处理作用域及验证。
 
       第一种形式也是最常见的形式是通过使用标记指定bean属性的目标bean,通过该标签可以引用同一容器或父容器内的任何bean(无论是否在同一XML文件中)。XML 'bean'元素的值既可以是指定bean的id值也可以是其name值。
       第二种形式是使用ref的local属性指定目标bean,它可以利用XML解析器来验证所引用的bean是否存在同一文件中。local属性值必须是目标bean的id属性值。如果在同一配置文件中没有找到引用的bean,XML解析器将抛出一个例外。如果目标bean是在同一文件内,使用local方式就是最好的选择(为了尽早地发现错误)。

       第三种方式是通过使用ref的parent属性来引用当前容器的父容器中的bean。parent属性值既可以是目标bean的id值,也可以是name属性值。而且目标bean必须在当前容器的父容器中。使用parent属性的主要用途是为了避免引用父容器中的bean,该bean在子容器中与其相同的bean id值或name值(例如,子上下文中的一个bean定义覆盖了他的父bean)。


    



 
      
            
      
    
 

集合装配

通过元素可以定义和设置与Java Collection类型对应List、Set、Map及Properties的值。相对来说,这些难度并不大,主要是相关标签的使用
collection类用来装配集合属性
package org.spring.collection;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import org.spring.di.Student;

public class Collection {
    private List list;
    private Set set;
    private Map map;
    private Properties properties;
    private Student student;
    public List getList() {
        return list;
    }
    public void setList(List list) {
        this.list = list;
    }
    public Set getSet() {
        return set;
    }
    public void setSet(Set set) {
        this.set = set;
    }
    public Map getMap() {
        return map;
    }
    public void setMap(Map map) {
        this.map = map;
    }
    public Properties getProperties() {
        return properties;
    }
    public void setProperties(Properties properties) {
        this.properties = properties;
    }
    public Student getStudent() {
        return student;
    }
    public void setStudent(Student student) {
        this.student = student;
    }
}

配置文件



    
    
    


    
        
            list : one
            list : two
            
        
    
    
    
        
            set : one
            set : two
            
        
    
    
    
        
            
                
                    key1
                
                one
            
            
            
                
            
        
    
    
    
        
            one
            two
            three
        
    

测试类


package org.spring.collection;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class CollectionTest {
    @Test
    public void test() {
        ApplicationContext context = new ClassPathXmlApplicationContext(
                "applicationContext.xml");
        Collection collection = (Collection) context.getBean("collection");
        System.out.println("============list集合============");
        for (Object value : collection.getList()) {
            System.out.println(value);
        }
        System.out.println("============set集合============");
        for (Object value : collection.getSet()) {
            System.out.println(value);
        }
        System.out.println("============map集合============");
        for (Object key : collection.getMap().keySet()) {
            System.out.println(key + " = " + collection.getMap().get(key));
        }
        System.out.println("============properties集合============");
        for (Object key : collection.getProperties().keySet()) {
            System.out.println(key + " = "
                    + collection.getProperties().getProperty((String) key));
        }
    }
}

测试结果
============list集合============
list : one
list : two
org.spring.di.Student@13c6641
============set集合============
set : one
set : two
org.spring.di.Student@13c6641
============map集合============
key1=one
key2=two
key3=org.spring.di.Student@13c6641
============properties集合============
key3 = three
key2 = two
key1 = one

Spring注解注入

        Spring的DI功能使我们不需要再显示的构造对象,而只需在配置文件中配置对象之间的依赖关系即可,然后将这些对象交由Spring容器来管理,可是一个企业应用是由成千上百个bean装配在一起协同工作的,在 每个bean中又都有一些属性,如果把所有bean的属性都配置在配置文件中,那么这时候我们的Spring配置文件会非常庞大,而且维护起来也不方便。 从Spring2.5以后,Spring开始全面支持注解方式配置,大大的简化了xml配置文件的书写 。使用 注解方式时,必须在spring配置文件的schema中添加注解的命名空间如下:


           

可以看到 配置文件中增加了命名空间

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

与schemaLocation

http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd"
我们知道注释本身是不会做任何事情的,它仅提供元数据信息。要使元数据信息真正起作用,必须让负责处理这些元数据的处理器工作起来,所以我们还需要 在spring配置文件中注册注解处理器,这样Spring容器才对会bean之间的依赖进行注入,该标签需要在 元素之前添加

完成以上步骤后,我们只需在配置文件配置bean即可,而不用再配置对象之间的依赖关系。



注解注入的方式

@Autowired注解

@Autowired注解可以应用于属性 仅对引用类型的属性注入有效)上面

@Autowired
private Bicycle bicycle;

也可以应用于setter方法上面

@Autowired
public void setBicycle(Bicycle bicycle) {
    this.bicycle = bicycle;
}

甚至可以标注在构造器上
@Autowired
public Student(Bicycle bicycle) {
    this.bicycle = bicycle;
}

以上三种方式都会将bicycle对象注入到Student中, @Autowired注解是按类型装配依赖对象,默认情况下它要求依赖对象必须存在,如果允许null值,可以设置它的required属性为false。

@Autowired(required=false)
private Bicycle bicycle;

@Qualifier注解

       @Autowired 注解 默认是按照对象的类型进行自动装配的,在容器中,当且仅有一个匹配的bena时,Spring将其注入到@Autowired标注的变量中,在这边, Spring会 在运行时自动将类型为 Bicycle 的对象注入进来。 如果 spring配置文件中存在多个 Bicycle 类型的bean时,


则Spring报错,因为Spirng不知道我们要注入的是哪个bean对象,这时就需要通过 @Qualifier来指定bean的名称让容器找到需要的bean

@Autowired
@Qualifier("bicycle2")
private Bicycle bicycle;

这样Spring容器就知道我们获取的是id为bicycle2的bean。如果没有指定@Qualifier的字符串值,则默认获取配置文件中id值为bicycle的bean
@Qualifier的标注对象是成员变量、方法入参、构造函数入参。如
@Autowired
@Qualifier("bicycle")
private Bicycle bicycle;

@Autowired
public void setBicycle(@Qualifier("bicycle")Bicycle bicycle) {
    this.bicycle = bicycle;
}

@Autowired
public Student(@Qualifier("bicycle") Bicycle bicycle) {
    this.bicycle = bicycle;
}

JSR-250 的注解

@Resource注解

        @Resource可以按类型(type)注入,也可以按名称(name)注入, 默认按名称策略自动注入名称可以通过@Resource的name属性指定,如果没有指定name属性, 当注解标注在字段上,即默认取字段的名称作为bean名称寻找依赖对象, 当注解标注在属性的setter方法上,即默认取属性名作为bean名称寻找依赖对象。 Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。所以如果使用name属性,则使用byName的自动注入策略,并且只会按照 名称进行装配 而使用type属性时则使用byType自动注入策略。如果既不指定name也不指定type属性,这时将转为使用byName自动注入策略。

@Resource装配顺序
1、如果同时指定了name和type,则从Spring上下文中(配置文件)找到唯一匹配的bean进行装配,找不到则抛出异常
2、如果指定了name,则从上下文中查找名称(id或name)匹配的bean进行装配,找不到则抛出异常(只会按照byName策略注入)
3、如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常
4、如果既没有指定name( 默认为字段名称),又没有指定type( 默认为javabean类型),则自动按照byName方式进行装配;如果没有匹配, 则回退为一个原始类型进行匹配,如果匹配则自动装配;

@Resource注解通过标注在字段、方法上面进行属性的注入
@Resource
private Bicycle bicycle;
此时按照装配顺序中的第4点进行装配

如果指定了name值,则 按照 装配顺序中的第2点进行装配
@Resource(name="bicycle")
private Bicycle bicycle;

如果指定type值,则 按照 装配顺序中的第3点进行装配


@Resource(type=org.spring.annotation.Bicycle.class)
private Bicycle bicycle;

如果同时指定type与name值,则 按照 装配顺序中的第1点进行装配


@Resource(type=org.spring.annotation.Bicycle.class,name="bicycle")
private Bicycle bicycle;

@Autowired一样, @Resource也可以标注在setter方法上

@Resource
public void setBicycle(Bicycle bicycle) {
    this.bicycle = bicycle;
}

这时 默认取属性名(bicycle)进行装配, 当找不到与名称匹配的bean时按照类型进行装配

@PostConstruct 和 @PreDestroy

       在SpringIOC一文中我们通过 元素的init-method/destroy-method属性指定初始化之后 / 销毁之前调用的操作方法。 JSR-250 为初始化之后/销毁之前方法的指定定义了两个注释类,分别是 @PostConstruct 和 @PreDestroy,这两个注释只能应用于方法上。标注了 @PostConstruct 注释的方法将在类实例化后调用,而标注了 @PreDestroy 的方法将在类销毁之前调用。  还是应用 SpringIOC一文中用到的例子
public class Person {
    public Person() {
        System.out.println("构造器");
    }
    
    @PostConstruct
    public void init() {
        System.out.println("washing......");
    }
    
    public void eat() {
        System.out.println("eatting......");
    }
    
    @PreDestroy
    public void destroy() {
        System.out.println("wape mouth.....");
    }
}
    

现在我们在init与destroy方法上面进行了 @PostConstruct 和 @PreDestroy标注,则在配置文件中只需配置bean即可,不用再指定 init-method destroy-method了。不要忘了在配置文件中加入context命名空间。与xml配置方式不同的是, @PostConstruct 和 @PreDestroy可以指定多个

public class Person {
    public Person() {
        System.out.println("构造器");
    }
    
    @PostConstruct
    public void init1() {
        System.out.println("washing......1");
    }
    
    @PostConstruct
    public void init2() {
        System.out.println("washing......2");
    }
    
    public void eat() {
        System.out.println("eatting......");
    }
    
    @PreDestroy
    public void destroy1() {
        System.out.println("wape mouth.....1");
    }
    
    @PreDestroy
    public void destroy2() {
        System.out.println("wape mouth.....2");
    }
}

组件注解

       虽然我们可以通过 @Autowired 或 @Resource 在 Bean 类中使用自动注入功能,但是 Bean 还是在 XML 文件中通过 进行定义 —— 也就是说,在 XML 配置文件中定义 Bean,通过 @Autowired 或 @Resource 为 Bean 的成员变量、方法入参或构造函数入参提供自动注入的功能。能否也通过注释定义 Bean,从 XML 配置文件中完全移除 Bean 定义的配置呢?答案是肯定的,spring2.5为我们引入了组件自动扫描机制,它可以在类路径底下寻找标注了@Component、@Service、@Controller、@Repository注解的类,并把这些类纳入进spring容器中管理。它的作用和在xml文件中使用bean节点配置组件是一样的。同样,要使用@Component注解,Spring容器必须启用类扫描机制以启用注释驱动 Bean 定义和注释驱动 Bean 自动注入的策略。
其中base-package为需要扫描的包(含子包)。

这时Student类
@Component
public class Student {
    @Resource
    private Bicycle bicycle;
    public Bicycle getBicycle() {
        return bicycle;
    }
}

Bicycle类

@Component
public class Bicycle {
}

测试代码

public class AnnotationTest {
    @Test
    public void test() {
        ApplicationContext context = new ClassPathXmlApplicationContext("org/spring/annotation/applicationContext.xml");
        Student student = (Student) context.getBean("student");
        System.out.println(student.getBicycle());
    }
}

控制台打印org.spring.annotation.Bicycle@c01e99,说明属性已经注入成功。

使用@Component注解定义的Bean,默认的名称(id)是小写开头的非限定类名。如这里定义的Bean名称就是student。你也可以指定Bean的名称: @Component(stu), @Component是所有受Spring管理组件的通用形式,Spring还提供了更加细化的注解形式:@Repository、@Service、@Controller,它们分别对应存储层Bean,业务层Bean,和展示层Bean。 当组件不好归类的时候,我们可以使用@Component 注解进行标注。 目前版本(2.5)中,这些注解与@Component的语义是一样的,完全通用,在Spring以后的版本中可能会给它们追加更多的语义。所以,我们推荐使用@Repository、@Service、@Controller来替代@Component。

你可能感兴趣的:(Spring,spring,框架,DI)