• 控制反转(loC,Inversion of Control),是一个概念,是一种思想。指的是将传统上由程序代码直接操纵的对象调用权交给容器,通过容器来实现对象的装配和管理。控制反转就是对对象控制权的转移,从程序代码本身反转到了外部容器。
  • loC是一个概念,是一种思想,其实现方式多种多样。当前比较流行的实现方式有两种:依赖注入和依赖查找。依赖注入方式应用更为广泛。
    1、依赖查找:(Dependency Lookup,DL,容器提供回调接口和上下文环境给组件,程序代码则徐亚哦提供具体的查找方式。比较经典的是依赖于JNDI服务接口(Java Naming and Directory Interface)的查找。
    2、依赖注入:Dependency Injection,DI,程序代码不做定位查询,这些工作由容器自行完成。
  • 依赖注入DI是指程序运行过程中,若需要调用另一个对象协助时,无须在代码中创建被调用者,而是依赖于外部容器,由外部容器创建后传递给程序。
  • Spring的依赖注入对调用者与被调用者几乎没有任何要求,完全支持POJO之间依赖关系的管理。
  • 依赖注入是目前最优秀的解耦方式。依赖注入让Spring的Bean之间以配置文件的方式组织在一起,而不是以硬编码的方式耦合在一起。

    1 Spring的第一个程序

  • 在普通三层架构的基础上,将程序修改为Spring框架程序。
  • 举例:springDemo

    1.1 导入jar包

  • 首先,导入Spring程序开发的四个基本jar包。
    SSH框架之Spring4专题2:Spring与loC_第1张图片
  • 其次,导入日志相关的Jar包。
  • 在依赖库spring-framework-3.0.2.RELEASE-dependencies.zip解压目录下:\org.apache.commons\com.springsource.org.apache.commons.logging\1.1.1下的com.springsource.org.apache.commons.logging-1.1.1.jar文件。该文件只是日志记录的实现规范,并没有具体的实现,相当于的slf4j.jar作用。
  • 这里日志的实现使用log4j,故还需要log4j.jar。在依赖库解压目录下:\org.apache.log4j\com.springsource.org.apache.log4j\1.2.15中的com.springsource.org.apache.log4j-1.2.15.jar。
  • 最后,导入JUnit测试Jar包junit-4.9.jar即可。
  • Spring基本编程,共需要7个Jar包即可。

    1.2 定义接口和实体类

    package com.eason.spring4.service;
    public interface IStudentService {
    void some();
    }
    package com.eason.spring4.service.impl;
    import com.eason.spring4.service.IStudentService;
    public class StudentServiceImpl implements IStudentService {
    @Override
    public void some() {
        System.out.println("执行some()方法");
    }
    }

    1.3 创建Spring配置文件

  • spring配置文件的文件名可以随意,但是Spring建议的名称为applicationContext.xml。文件约束在%SPRING_HOME%\docs\spring-framework-reference\html\xsd-configuration.html文件中。
    
    
    
    
  • 注意,Spring配置文件中使用的约束文件为xsd文件。若Eclipse中没有自动提示功能,则需要将约束要查找的域名地址指向本地的xsd文件。相关的xsd文件在Spring框架解压目录下的schema目录的相关子目录中。
  • 这里需要的是spring-beans.xsd约束文件,故需要在beans子目录中查找相应版本的约束文件。
    SSH框架之Spring4专题2:Spring与loC_第2张图片
  • :用于定义一个实例对象。一个实例对应一个bean元素。
  • id:该属性是Bean实例的唯一标识,程序通过id属性访问Bean,Bean与Bean之间的依赖关系也是通过id属性关联的。
  • class:指定该Bean所属的类,注意这里只能是类,而不是接口。

    1.4 定义测试类

    @org.junit.Test
    public void test() {
        //获取容器
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        //从容器中获取对象
        IStudentService service = (IStudentService) context.getBean("studentService");
        service.some();
    }

    1.4.1 ApplicationContext接口容器

    • ApplicationContext用于加载Spring的配置文件,在程序中充当“容器”的角色。其实现类有两个。通过Ctrl + T查看:
      SSH框架之Spring4专题2:Spring与loC_第3张图片
      1、配置文件在类路径下:
  • 若Spring配置文件存放在项目的类路径下,则使用ClassPathXmlApplicationContext实现类进行加载。
    SSH框架之Spring4专题2:Spring与loC_第4张图片
    2、配置文件在本地目录中:
  • 若Spring配置文件存放在本地磁盘目录中,则使用FileSystemApplicationContext实现类进行加载。
    SSH框架之Spring4专题2:Spring与loC_第5张图片
    3、配置文件在项目根目录下:
  • 若Spring配置文件存放在项目的根目录下,同样使用FileSystemXmlApplicationContext实现类进行加载。
  • 下面是存放在项目根路径下的情况,该配置文件与src目录同级,而非在src中。
    SSH框架之Spring4专题2:Spring与loC_第6张图片
    SSH框架之Spring4专题2:Spring与loC_第7张图片

    2 Bean的装配

  • Bean的装配,即Bean对象的创建,容器根据代码要求创建Bean对象后再传递给代码的过程,称之为Bean的装配。

    2.1 默认装配方式

  • 代码通过getBean()方式从容器获取指定的Bean实例,容器首先会调用Bean类的无参构造器,创建空值的实例对象。

    2.2 动态工厂Bean

  • 有些时候,项目中需要工厂类来创建Bean实例,而不是像前面例子中似的,直接由Spring容器来装配Bean实例。使用工厂模式创建Bean实例,就会使得工厂类和要创建的Bean类耦合在一起。

    2.2.1 将动态工厂Bean作为普通Bean使用

  • 将动态工厂Bean作为普通Bean来使用是指,在配置文件中注册过动态工厂Bean后,测试类直接通过getBean()获取到工厂对象,再由工厂对象调用其相应方法创建相应的目标对象。配置文件中无需注册目标对象的Bean。因为目标对象的创建不由Spring容器来管理。
    1、定义工厂类:
    public class ServiceFactory {
    public IStudentService getStudentService() {
        return new StudentServiceImpl();
    }
    }

    2、定义配置文件:

    3、编写测试代码:

    @org.junit.Test
    public void test() {        
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        //从Spring容器中获取factory
        ServiceFactory factory =  (ServiceFactory) context.getBean("studentServiceFactory");
        IStudentService studentService = factory.getStudentService();
        studentService.some();
    }
  • 但是这样做的缺点是,不仅工厂类和目标类耦合到了一起,测试类和工厂类也耦合在了一起。

    2.2.2 使用Spring的动态工厂Bean

  • Spring对于使用动态工厂来创建的Bean,有专门的属性定义。factory-bean指定相应的工厂Bean,由factory-method指定创建所用的方法。此时配置文件中至少有两个Bean的定义:工厂类的Bean,与工厂类所要创建的目标类Bean。而测试类中不再需要获取工厂Bean对象,而是可以直接获取Bean对象,实现测试类和工厂类间的解耦。
    1、修改配置文件:
    
    

    2、编写测试代码:

    @org.junit.Test
    public void test() {        
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        //从Spring容器中获取factory
        IStudentService studentService =  (IStudentService) context.getBean("studentService");
        studentService.some();
    }

    2.3 静态工厂Bean

  • 使用工厂模式中的静态工厂来创建实例Bean。
  • 此时需要注意的是,静态工厂无需工厂实例,所以不需要定义静态工厂
  • 而对于工厂所要创建的Bean,其不是由自己的类创建的,所以无需定义自己的类。但是其是由工厂类创建的,所以需要指定所用的工厂类。故class属性指定的是工厂类而非自己的类。当然,还需要通过factory-method属性指定工厂方法。
  • 修改配置文件:

    2.4 容器中Bean的作用域

  • 当通过Spring容器创建一个Bean实例时,不仅可以完成Bean的实例化,还可以通过scope属性,为Bean指定特定的作用域。Spring支持5种作用域。
    1、singleton:单态模式。即在整个Spring容器中,使用singleton定义的Bean将是单例的,只有一个实例,默认为单态的。
    2、prototype:原型模式。即每次使用getBean方法获取的同一个的实例都是一个新的实例。
    3、request:对于每次HTTP请求,都将会产生一个不同的Bean实例。
    4、session:对于每次不同的HTTP session,都将产生一个不同的Bean实例。
  • 注意:
    1、对于scope的值request、session与global session,只有在Web应用中使用Spring时,该作用域才有效。
    2、对于scope为singleton的单例模式,该bean是在容器被创建时即被装配好的。
    3、对于scope为prototype的原型模式,Bean实例是在代码中使用该bean实例时才进行装配的。

    2.5 Bean后处理器

  • Bean后处理器是一种特殊的Bean,容器中所有的Bean在初始化时,均会自动执行该类的两个方法。由于该Bean是由其他Bean自动调用执行的,不是程序员手工调用,故此Bean无需id属性。
  • 需要做的是,在Bean后处理器类方法中,只要对Bean类与Bean类中的方法进行判断,就可以实现对指定的Bean的指定方法进行功能扩展和增强。方法返回的Bean对象,即是增强过的对象。
  • 代码中需要自定义Bean后处理器类。该类就是实现了接口BeanPostProcessor的类。该接口中包含两个方法,分别在目标Bean初始化完毕之前和之后执行。它们的返回值为:功能被扩展或者增强后的Bean对象。
  • Bean初始化完毕后一个标志:一个方法被执行。即当该方法被执行时,表示该Bean被初始化完毕。所以Bean后处理器中两个方法的执行,是在这个方法之前之后执行。这个方法在后面将会讲到。
  • public Object postProcessBeforeInitialization(Ojbect bean, String beanId) throws BeansException,该方法会在目标bean初始化完毕之前由容器自动调用。
  • public Object postProcessAfterInitialization(Object bean, String beanId) throws BeansException,第二个参数是该Bean实例的id属性值。若Bean没有id就是name属性值。
  • 举例:程序中有一个业务接口IService,其由两个业务方法some()与other()。有两个Bean:StudentServiceImpl与TeacherServiceImpl,均实现了IService接口。要求对StudentServiceImpl的some()方法进行增强,输出其开始执行时间和执行结束时间。
    public interface IService {
    void some();
    void other();
    }
    //此类为待增强的Bean,即目标类
    public class StudentServiceImpl implements IService {
    @Override
    public void some() {
        System.out.println(this.getClass().getSimpleName() + ",执行some()方法");
    }
    @Override
    public void other() {
        System.out.println(this.getClass().getSimpleName() + ",执行other()方法");
    }
    }
    public class TeacherServiceImpl implements IService {
    @Override
    public void some() {
        System.out.println(this.getClass().getSimpleName() + ",执行some()方法");
    }
    @Override
    public void other() {
        System.out.println(this.getClass().getSimpleName() + ",执行other()方法");   
    }
    }
    public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        //只对StudentServiceImpl实现类的some()方法进行加强,使用动态代理进行加强
        if("studentService".equals(beanName)) {
            Object proxy = Proxy.newProxyInstance(bean.getClass().getClassLoader(), bean.getClass().getInterfaces(),
                    new InvocationHandler() {               
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            if("some".equals(method.getName())) {
                                System.out.println("目标方法执行开始时间:" + System.currentTimeMillis());
                                //执行目标方法
                                Object result = method.invoke(bean, args);
                                System.out.println("目标方法执行结束时间:" + System.currentTimeMillis());
                                return result;
                            }
                            return method.invoke(bean, args);
                        }
                    });
            return proxy;
        }
        return bean;
    }
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        //即使不对Bean进行增强,也要是方法返回bean,不能够为默认的null值。
        //否则将抛出NullPointerException异常
        System.out.println("执行postProcessBeforeInitialization()");
        return bean;
    }
    }
     
    
    
    @org.junit.Test
    public void test() {        
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        IService studentService = (IService) context.getBean("studentService");
        studentService.some();
        studentService.other();
        IService teacherService = (IService) context.getBean("teacherService");
        teacherService.some();
        teacherService.other();
    }

    2.6 制定Bean的生命始末

  • 可以为Bean定制初始化后的生命行为,也可以为Bean定制销毁前的生命行为。
  • 举例:首先,这些方法需要在Bean类中事先定义好,是方法名随意的public void方法。
    public class StudentServiceImpl implements IService {
    @Override
    public void some() {
        System.out.println(this.getClass().getSimpleName() + ",执行some()方法");
    }
    @Override
    public void other() {
        System.out.println(this.getClass().getSimpleName() + ",执行other()方法");   
    }   
    public void setUp() {
        System.out.println("初始化完毕,执行后续工作...");
    }
    public void tearDown() {
        System.out.println("对象将销毁,进行资源释放...");
    }
    }
  • 其次,在配置文件中标签中增加如下属性:
    1、init-method:指定初始化方法的方法名;
    2、destroy-method:指定销毁方法的方法名;
     
  • 注意,若要看到Bean的destroy-method的执行结果,需要满足两个条件:
    1、Bean为singleton,即单例;2、要确保容器关闭。接口ApplicationContext没有close()方法,但是其实现类有。所以,可以将ApplicationContext强转为其实现类对象,或者直接创建的就是实现类对象。
    @org.junit.Test
    public void test() {        
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        IService studentService = (IService) context.getBean("studentService");  
        studentService.some();
        studentService.other();
        //关闭容器对象
        context.close();
    }

    2.7 Bean的生命周期

  • Bean实例从创建到最后销毁,需要经过很多过程,执行很多生命周期方法。
    1、调用无参构造器,创建实例对象。
    2、调用参数的setter,为属性注入值。
    3、若Bean实现了BeanNameAware接口,则会执行接口方法setBeanName(String beanId),使得Bean类可以获取其在容器中的id名称。
    4、若Bean实现了BeanFactoryAware接口,则执行接口方法setBeanFactory(BeanFactory factory),使得Bean类可以获取到BeanFactory对象。
    5、若定义并注册了Bean后处理器BeanPostProcessor,则执行接口方法postProcessBeforeInitialization()。
    6、若Bean实现了InitializingBean接口,则执行接口方法afterPropertiesSet()。该方法在Bean的所有属性的set方法执行完毕后执行,是Bean初始化结束的标志,即Bean实例化结束。
    7、若设置了init-method方法,则执行。
    8、若定义并注册了Bean后处理器BeanPostProcessor,则执行接口方法postProcessAfterInitialization()。
    9、执行业务方法。
    10、若Bean实现了DisposableBean接口,则执行接口方法destroy()。
    11、若设置了destroy-method方法,则执行。

    2.8 标签的id属性与name属性

  • 一般情况下,命名使用id属性,而不使用name属性。在没有id属性的情况下,name属性与id属性作用是相同的。但是当中含有一些特殊字符时,就需要使用name属性了。
  • id的命名需要满足XML对ID属性命名规范:必须以字母开头,可以包含字母、数字、下划线、连字符、冒号。
  • naem属性值则可以包含各种字符。

    3 基于XML的DI

    3.1 注入分类

  • Bean实例在调用无参构造器创建了空值对象后,就要对Bean对象的属性进行初始化。初始化时由容器自动完成的,称之为注入。根据注入方式的不同,常用的有两类:设值注入、构造注入。
  • 还有另外一种,实现特定接口注入。由于这种方式采用侵入式编程,会污染代码,所以几乎不用。

    3.1.1 设值注入

  • 设值注入指的是,通过setter方法传入被调用者的实例。这种注入方式简单、直观。因而在Spring的依赖注入中大量使用。
    public class Student {
    private String name;
    private int age;
    //setter and getter()
    @Override
    public String toString() {
        return "Student [name=" + name + ", age=" + age + "]";
    }
    }
        
            
            
        
        @org.junit.Test
        public void test() {        
            ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
            Student student =  (Student) context.getBean("student");  
            System.out.println(student.toString());
        }
  • 当指定bean的某属性值为另一个bean的实例时,通过ref指定它们间的引用关系。ref的值必须为某bean的id值。

    public class Student {
        private String name;
        private int age;
        private School school;
    
        @Override
        public String toString() {
            return "Student [name=" + name + ", age=" + age + ", school=" + school + "]";
        }
    }
    public class School {
        private String name;
        @Override
        public String toString() {
            return "School [name=" + name + "]";
        }
    }
            
                
                
            
                
                
                 
                
            
        @org.junit.Test
        public void test() {        
            ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
            Student student =  (Student) context.getBean("student");  
            System.out.println(student.toString());
        }
  • 对于其他对象的引用,除了标签的ref属性外,还可以使用标签。
            
                
                
                    
                    
                
            

    3.1.2 构造注入

  • 构造注入是指,在构造调用者实例的同时,完成被调用者的实例化。即,使用构造器设置依赖关系。
        public Student(String name, int age, School school) {
            super();
            this.name = name;
            this.age = age;
            this.school = school;
        }
            
                
                
            
                
                
                        
            
  • 标签中用于指定参数的属性有:
  • name:指定参数名称;
  • index:指明该参数对应着构造器的第几个参数,从0开始。不过,该属性不要也行,但是要注意,若参数类型相同,或者之间有包含关系,则需要保证赋值顺序要与构造器中的参数顺序一致。
  • 另外,type属性可用于指定其类型。基本类型直接写类型关键字即可,非基本类型需要写全限定性类名。

    3.2 集合属性注入

        public class Student {
            private String name;
    
            //getter and setter()
    
            @Override
            public String toString() {
                return "student [name=" + name + "]";
            }
        }
    public class MyCollections {
        private Student[] students;
        private List students2;
        private Set mySet;
        private Map myMap;
        private Properties properties;
    
        //getter and setter()
    }
    
        
        
    
        
        
    
    
        
            
                
                
            
        
        
            
                
                
            
        
        
            
                北京大学
                清华大学
            
        
        
            
                
                
            
        
        
            
                1008611
            
        
    
    @org.junit.Test
    public void test() {        
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        MyCollections cols = (MyCollections) context.getBean("myCollections");  
        Student[] students = cols.getStudents();
        List list = cols.getStudents2();
        Set mySet = cols.getMySet();
        Map myMap = cols.getMyMap();
        Properties properties = cols.getProperties();
        System.out.println(students[0]);
        System.out.println(list.size());
        System.out.println(mySet.isEmpty());
        System.out.println(myMap.get("weight"));
        System.out.println(properties.get("广州移动"));
    }

    3.3 对于域属性的自动注入

  • 对于域属性的注入,也可不在配置文件中显示的注入。可以通过标签设置autowire属性值,为域属性进行隐式自动注入。根据自动注入判断标准的不同,可以分为两种:
  • byName:根据名称自动注入;
  • byType:根据类型自动注入;

    3.3.1 byName方式自动注入

  • 当配置文件中被调用者Bean的id值与代码中调用者Bean类的属性名相同时,可使用byName方式,让容器自动将被调用者Bean注入给调用者Bean。容器是通过调用者的Bean类的属性名与配置文件的被调用者bean的id进行比较而实现自动注入的。
    public class Student {
        private String name;
        private School mySchool;
        //setter and getter
        @Override
        public String toString() {
            return "Student [name=" + name + ", mySchool=" + mySchool + "]";
        }
    }
    public class School {
    private String name;
    //setter and getter()
    @Override
    public String toString() {
        return "School [name=" + name + "]";
    }   
    }
    
        
    
     
        
    
    @org.junit.Test
    public void test() {        
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        Student student = (Student) context.getBean("myStudent");
        System.out.println(student);
    }

    3.3.2 byType方式自动

  • 使用byType方式自动注入,要求:配置文件中被调用者bean的class属性指定的类,要与代码中调用者Bean类的某域属性类型同源。即要么相同,要么有is-a关系(子类,或者是实现类)。但是这样的同源的被调用bean只能有一个。多于一个,容器就不知道该匹配哪一个了。
    
        
    
     
        
    

    3.4 使用SPEL注入

  • SPEL,Spring Expression Language,即Spring EL表达式语言。即,在Spring配置文件中为Bean的属性注入值时,可直接使用SPEL表达式计算的结果。SPEL表达式以#开头,后跟一对大括号。用法:。其文档中有其用法举例。在Spring框架解压目录\docs\spring-framework-reference\htmlsingle\index.html中。Ctrl+F,对SpEL进行检索。第一个检索结果中9.4.1所链接的位置即有用法举例。
    SSH框架之Spring4专题2:Spring与loC_第8张图片
    SSH框架之Spring4专题2:Spring与loC_第9张图片
  • 举例说明:
    public class Student {
    private String studentName;
    private int studentAge;
    //setter and getter()
    @Override
    public String toString() {
        return "Student [studentName=" + studentName + ", studentAge=" + studentAge + "]";
    }
    }
    public class Person {
    private String personName;
    private int personAge;
    //setter and getter()
    //业务方法,计算年龄(若年龄大于25岁,则按25岁计算)
    public int computeAge() {
        return personAge > 25 ? 25 : personAge;
    }
    @Override
    public String toString() {
        return "Person [personName=" + personName + ", personAge=" + personAge + "]";
    }
    }
    
        
        
    
    
        
        
    
    @org.junit.Test
    public void test() {        
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        Student student = (Student) context.getBean("myStudent");
        System.out.println(student);
    }
  • 其他用法:
    1、,引用另一个bean。指定school的值为另一个Bean实例mySchool。
    2、,使用指定属性,并使用其方法。指定schoolName值为mySchool的name属性值,并将其字母均转换成大写字母(toUpperCase()方法)。

    3.5 使用内部Bean注入

  • 若不希望代码直接访问某个bean,即,在代码中通过getBean方法获取该Bean实例,则可将该Bean的定义放入调用者bean定义的内部。
    
        
        
        
            
                
            
        
     

    3.6 使用同类抽象Bean注入

  • 当若干Bean实例同属于一个类,且这些实例的属性值有相同值时,可以使用抽象Bean,以此简化配置文件。
  • 抽象Bean是用于让其他bean继承的,这个bean在Bean类中是不能通过getBean方法获取的。设置abstract属性为true来指明该bean为抽象bean,默认值为false。不过,该bean不为抽象bean时,也可被继承。只不过在应用中,用于被继承的bean一般为抽象bean。

    public class Student {
    private String name;
    private double score;
    private String department;
    private String school;
    //setter and getter()
    @Override
    public String toString() {
        return "Student [name=" + name + ", score=" + score + ", department=" + department + ", school=" + school + "]";
    }
    }
    
    
    
    
    
    
    
    
    
    
    
    
    
    @org.junit.Test
    public void test() {        
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        Student student1 = (Student) context.getBean("myStudent1");
        System.out.println(student1);
        Student student2 = (Student) context.getBean("myStudent2");
        System.out.println(student2);
    }

    3.7 使用异类抽象Bean注入

  • 当若干不同的类对象具有相同的属性,且其值也相同时,可使用异类抽象Bean。

    public class Student {
    private String name;
    private double score;
    private String department;
    private String school;
    ·//setter and getter()
    @Override
    public String toString() {
        return "Student [name=" + name + ", score=" + score + ", department=" + department + ", school=" + school + "]";
    }
    }
    public class Teacher {
    private String name;
    private double score;
    private String department;
    private String school;
    //setter and getter()
    @Override
    public String toString() {
        return "Teacher [name=" + name + ", score=" + score + ", department=" + department + ", school=" + school + "]";
    }
    }
    
    
    
    
    
    
    
    
    
    
    
    
    
    @org.junit.Test
    public void test() {        
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        Student student = (Student) context.getBean("student");
        System.out.println(student);
        Teacher teacher = (Teacher) context.getBean("teacher");
        System.out.println(teacher);
    }

    3.8 为应用指定多个Spring配置文件

  • 在实际应用里,随着应用规模的增加,系统中Bean数量也大量增加,导致配置文件变得非常庞大、臃肿。为了避免这种情况的产生,提高配置文件的可读性和可维护性,可以将Spring配置文件分解成多个配置文件。

    3.8.1 平等关系的配置文件

  • 将配置文件分解为地位平等的多个配置文件,并将所有配置文件的路径定义为一个String数组,将其作为容器初始化参数出现。其将与可变参的容器构造器匹配。
    SSH框架之Spring4专题2:Spring与loC_第10张图片
  • 各配置文件间为并列关系,不分主次。
    SSH框架之Spring4专题2:Spring与loC_第11张图片
    @org.junit.Test
    public void test() {    
        String[] resourceFiles = {"com/eason/spring4/po/spring-base.xml",
                "com/eason/spring4/po/spring-student.xml", "com/eason/spring4/po/spring-teacher.xml"};
        ApplicationContext context = new ClassPathXmlApplicationContext(resourceFiles);
        Student student = (Student) context.getBean("student");
        System.out.println(student);
        Teacher teacher = (Teacher) context.getBean("teacher");
        System.out.println(teacher);
    }

    3.8.2 包含关系的配置文件

  • 各配置文件中有一个总文件,总配置文件将各其他子文件通过引入。在Java代码中只需要使用总配置文件对容器进行初始化即可。
    SSH框架之Spring4专题2:Spring与loC_第12张图片
    
    
    
    @org.junit.Test
    public void test() {    
        ApplicationContext context = new ClassPathXmlApplicationContext("com/eason/spring4/po/spring-total.xml");
        Student student = (Student) context.getBean("student");
        System.out.println(student);
        Teacher teacher = (Teacher) context.getBean("teacher");
        System.out.println(teacher);
    }
  • 也可以使用通配符。但是,此时要求父配置文件名不能满足所能匹配的格式,否则将出现循环递归包含。就本例而言,父配置文件不能匹配spring-*.xml的格式,即不能起名为spring-total.xml。

    4 基于注解的DI

  • 对于DI使用注解,将不再需要在Spring配置文件中声明Bean实例。Spring中使用注解,需要在原有Spring运行环境基础上再做一些改变,完成以下三个步骤。
    1、导入AOP的Jar包。因为注解的后台实现用到了AOP编程。
    SSH框架之Spring4专题2:Spring与loC_第13张图片
    2、需要更换配置文件头,即添加相应的约束。约束在%SPRING_HOME%\docs\spring-framework-reference\html\xsd-configuration.html文件中。
    SSH框架之Spring4专题2:Spring与loC_第14张图片
    3、需要在Spring配置文件中配置组件扫描器,用于在指定的基本包中扫描注解。
    
     
    
    

    4.1 定义Bean@Component

  • 需要在类上使用注解@Component,该注解的value属性用于指定该bean的id值。
    @Component("myStudent")
    public class Student {
    }
  • 另外,Spring还提供了3个功能基本和@Component等效的注解:
    1、@Repository:用于对DAO实现类进行注解;2、@Service:用于对Service实现类进行注解;3、@Controller:用于对Controller实现类进行注解;
  • 之所以创建这三个功能与@Component等效的注解,是为了以后对其进行功能上的扩展,使它们不再等效。

    4.2 Bean的作用域@Scope

  • 需要在类上使用注解@Scope,其value属性用于指定作用域,默认为singleton。
    @Scope("prototype")
    @Component("myStudent")
    public class Student {
    }

    4.3 基本类型属性注入@Value

  • 需要在属性上使用注解@Value,该注解的value属性用于指定要注入的值。
  • 使用该注解完成属性注入时,类中无需setter。当然,若属性有setter,则也可将其加到setter上。
    @Value("balabala")
    private String name;
    @Value("94.5")
    private double score;
    @Value("操作部")
    private String department;

    4.4 按照类型注入域属性@Autowired

  • 需要在域属性上使用注解@Autowired,该注解默认使用按照类型自动装配Bean的方式。
  • 使用该注解完成属性注入时,类中无需setter。当然,若属性有setter,则可以将其加到setter上。
    @Autowired
    private String school;

    4.5 按照名称注入域属性@Autowired与@Qualifier

  • 需要在域属性上联合使用注解@Autowired与@Qualifier。@Qualifier的value属性用于指定要匹配的Bean的id值。同样类中无需setter,也可加到setter上。
    SSH框架之Spring4专题2:Spring与loC_第15张图片
    @Qualifier("mySchool")
    @Autowired
    private School school;

    4.6 域属性注解@Resource

  • Spring提供了对JSR-250规范中定义@Resource标准注解的支持。@Resource注解既可以按照名称匹配的Bean,也可以按照类型匹配Bean。使用该注解,要求JDK必须是6以及以上版本。

    4.6.1 按照类型注入域属性

  • @Resource注解若不带任何参数,则会按照类型进行Bean的匹配注入。
    @Resource
    private School school;

    4.6.2 按照名称注入域属性

  • @Resource注解指定其name属性,则name的值即为按照名称进行匹配的Bean的id。
    @Resource(name="mySchool")
    private School school;

    4.7 Bean的生命始末@PostConstruct与@PreDestroy

  • 在方法上使用@PostConstruct,与原来的init-method等效。在方法上使用@PreDestroy,与destroy-method等效。
    @PostConstruct
    public void setUp() {
        System.out.println("Bean初始化后,执行...");
    }
    @PreDestroy
    public void tearDown() {
        System.out.println("Bean销毁前,执行...");
    }

    4.8 使用JUnit4测试Spring

  • 使用Spring的JUnit4对Spring代码进行测试,将不再需要在程序代码中直接写入创建Spring容器,以及从Spring容器中通过getBean()获取对象。这些工作将由JUnit4注解,配合着域属性的自动注入注解共同完成。

    4.8.1 导入Jar包

  • 除了junit-4.9.jar外,还需要导入Spring框架的解压目录中的Spring与JUnit4的整合Jar:spring-test-4.2.1.RELEASE.jar。
    SSH框架之Spring4专题2:Spring与loC_第16张图片

    4.8.2 定义实体类

    public class Student {
    private String name;
    private double score;
    private School school;
    //setter and getter()
    @Override
    public String toString() {
        return "Student [name=" + name + ", score=" + score + ", school=" + school + "]";
    }
    }
    public class School {
    private String name;
    //setter and getter()
    @Override
    public String toString() {
        return "School [name=" + name + "]";
    }
    }

    4.8.3 定义Spring配置文件

    
     
    
    
        
    
    
        
        
        
    
    
    

    4.8.4 定义测试类

    @RunWith(SpringJUnit4Cla***unner.class)
    @ContextConfiguration(locations="classpath:com/eason/spring4/application-context.xml")
    public class Test {
    @Autowired
    private Student student;    
    @org.junit.Test
    public void test() {    
        System.out.println(student);    
    }
    }
  • 在类头添加的两个注解:1、@RunWith(SpringJUnit4Cla***unner.class):拥有指定运行环境;2、@ContextConfiguration(locations=""):用于指定配置文件位置;

    4.9 注解与XML共同使用

  • 注解的好处是,配置方便,直观。但是其弊端也是显而易见的:以硬编码的方式写入到Java代码中,其修改时需要编译代码的。
  • XML配置方式的最大好处是,对其所作修改,无需编译代码,只需重启服务器即可将新的配置加载。
  • 若注解与XML同用,XML的优先级要高于注解。这样做的好处是,需要对某个Bean做修改时,只需要修改配置文件即可。当然,此时Bean类要有setter或者构造器。(注解的方式可以不需要setter或者构造器,直接编写在属性跟前即可。)