- 控制反转(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包。
- 其次,导入日志相关的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文件中。
<?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">
<!-- bean definitions here -->
</beans>
- 注意,Spring配置文件中使用的约束文件为xsd文件。若Eclipse中没有自动提示功能,则需要将约束要查找的域名地址指向本地的xsd文件。相关的xsd文件在Spring框架解压目录下的schema目录的相关子目录中。
- 这里需要的是spring-beans.xsd约束文件,故需要在beans子目录中查找相应版本的约束文件。
<bean id="studentService" class="com.eason.spring4.service.impl.StudentServiceImpl"></bean>
- <bean/>:用于定义一个实例对象。一个实例对应一个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查看:
1、配置文件在类路径下:
- 若Spring配置文件存放在项目的类路径下,则使用ClassPathXmlApplicationContext实现类进行加载。 2、配置文件在本地目录中:
- 若Spring配置文件存放在本地磁盘目录中,则使用FileSystemApplicationContext实现类进行加载。 3、配置文件在项目根目录下:
- 若Spring配置文件存放在项目的根目录下,同样使用FileSystemXmlApplicationContext实现类进行加载。
- 下面是存放在项目根路径下的情况,该配置文件与src目录同级,而非在src中。
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、定义配置文件:
<bean id="studentServiceFactory" class="com.eason.spring4.service.ServiceFactory"></bean>
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、修改配置文件:
<bean id="studentServiceFactory" class="com.eason.spring4.service.ServiceFactory"></bean>
<bean id="studentService" factory-bean="studentServiceFactory" factory-method="getStudentService"></bean>
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/>。
- 而对于工厂所要创建的Bean,其不是由自己的类创建的,所以无需定义自己的类。但是其是由工厂类创建的,所以需要指定所用的工厂类。故class属性指定的是工厂类而非自己的类。当然,还需要通过factory-method属性指定工厂方法。
- 修改配置文件:
<bean id="studentService" class="com.eason.spring4.service.ServiceFactory"
factory-method="getStudentService"></bean>
2.4 容器中Bean的作用域
- 当通过Spring容器创建一个Bean实例时,不仅可以完成Bean的实例化,还可以通过scope属性,为Bean指定特定的作用域。Spring支持5种作用域。 1、singleton:单态模式。即在整个Spring容器中,使用singleton定义的Bean将是单例的,只有一个实例,默认为单态的。 2、prototype:原型模式。即每次使用getBean方法获取的同一个<bean/>的实例都是一个新的实例。 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;
}
}
<bean id="studentService" class="com.eason.spring4.service.impl.StudentServiceImpl"/>
<bean id="teacherService" class="com.eason.spring4.service.impl.TeacherServiceImpl"/>
<bean class="com.eason.spring4.service.impl.MyBeanPostProcessor"></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("对象将销毁,进行资源释放...");
}
}
- 其次,在配置文件中<bean/>标签中增加如下属性: 1、init-method:指定初始化方法的方法名; 2、destroy-method:指定销毁方法的方法名;
<bean id="studentService" class="com.eason.spring4.service.impl.StudentServiceImpl"
init-method="setUp" destroy-method="tearDown"/>
- 注意,若要看到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 <bean/>标签的id属性与name属性
- 一般情况下,命名<bean/>使用id属性,而不使用name属性。在没有id属性的情况下,name属性与id属性作用是相同的。但是当<bean/>中含有一些特殊字符时,就需要使用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 + "]";
}
}
<bean id="student" class="com.eason.spring4.po.Student">
<property name="name" value="bilibili"></property>
<property name="age" value="22"></property>
</bean>
@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 + "]";
}
}
<bean id="mySchool" class="com.eason.spring4.po.School">
<property name="name" value="广州大学"></property>
</bean>
<bean id="student" class="com.eason.spring4.po.Student">
<property name="name" value="bilibili"></property>
<property name="age" value="22"></property>
<property name="school" ref="mySchool">
</property>
</bean>
@org.junit.Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Student student = (Student) context.getBean("student");
System.out.println(student.toString());
}
- 对于其他对象的引用,除了<bean/>标签的ref属性外,还可以使用<ref/>标签。
<bean id="student" class="com.eason.spring4.po.Student">
<property name="name" value="bilibili"></property>
<property name="age" value="22"></property>
<property name="school">
<ref bean="mySchool"/>
</property>
</bean>
3.1.2 构造注入
- 构造注入是指,在构造调用者实例的同时,完成被调用者的实例化。即,使用构造器设置依赖关系。
public Student(String name, int age, School school) {
super();
this.name = name;
this.age = age;
this.school = school;
}
<bean id="mySchool" class="com.eason.spring4.po.School">
<property name="name" value="广州大学"></property>
</bean>
<bean id="student" class="com.eason.spring4.po.Student">
<constructor-arg name="name" value="张三"></constructor-arg>
<constructor-arg name="age" value="21"></constructor-arg>
<constructor-arg name="school" ref="mySchool"></constructor-arg>
</bean>
- <constructor-arg/>标签中用于指定参数的属性有:
- 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<Student> students2;
private Set<String> mySet;
private Map<String, String> myMap;
private Properties properties;
//getter and setter()
}
<bean id="student1" class="com.eason.spring4.po.Student">
<property name="name" value="bilibili"></property>
</bean>
<bean id="student2" class="com.eason.spring4.po.Student">
<property name="name" value="balabala"></property>
</bean>
<bean id="myCollections" class="com.eason.spring4.po.MyCollections">
<property name="students">
<array>
<ref bean="student1"/>
<ref bean="student2"/>
</array>
</property>
<property name="students2">
<list>
<ref bean="student1"/>
<ref bean="student2"/>
</list>
</property>
<property name="mySet">
<set>
<value>北京大学</value>
<value>清华大学</value>
</set>
</property>
<property name="myMap">
<map>
<entry key="height" value="180cm"></entry>
<entry key="weight" value="80kg"></entry>
</map>
</property>
<property name="properties">
<props>
<prop key="广州移动">1008611</prop>
</props>
</property>
</bean>
@org.junit.Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
MyCollections cols = (MyCollections) context.getBean("myCollections");
Student[] students = cols.getStudents();
List<Student> list = cols.getStudents2();
Set<String> mySet = cols.getMySet();
Map<String, String> 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 对于域属性的自动注入
- 对于域属性的注入,也可不在配置文件中显示的注入。可以通过<bean/>标签设置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 + "]";
}
}
<bean id="mySchool" class="com.eason.spring4.po.School">
<property name="name" value="清华大学"></property>
</bean>
<bean id="myStudent" class="com.eason.spring4.po.Student" autowire="byName">
<property name="name" value="balabala"></property>
</bean>
@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只能有一个。多于一个,容器就不知道该匹配哪一个了。
<bean id="mySchool" class="com.eason.spring4.po.School">
<property name="name" value="清华大学"></property>
</bean>
<bean id="myStudent" class="com.eason.spring4.po.Student" autowire="byType">
<property name="name" value="balabala"></property>
</bean>
3.4 使用SPEL注入
- SPEL,Spring Expression Language,即Spring EL表达式语言。即,在Spring配置文件中为Bean的属性注入值时,可直接使用SPEL表达式计算的结果。SPEL表达式以#开头,后跟一对大括号。用法:<bean id="abc" value="#{...}"/>。其文档中有其用法举例。在Spring框架解压目录\docs\spring-framework-reference\htmlsingle\index.html中。Ctrl+F,对SpEL进行检索。第一个检索结果中9.4.1所链接的位置即有用法举例。
- 举例说明:
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 + "]";
}
}
<bean id="myPerson" class="com.eason.spring4.po.Person">
<property name="personName" value="balabala"></property>
<property name="personAge" value="#{T(java.lang.Math).random() * 30}"></property>
</bean>
<bean id="myStudent" class="com.eason.spring4.po.Student">
<property name="studentName" value="#{myPerson.personName}"></property>
<property name="studentAge" value="#{myPerson.computeAge()}"></property>
</bean>
@org.junit.Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Student student = (Student) context.getBean("myStudent");
System.out.println(student);
}
- 其他用法: 1、<property name="school" value="#{mySchool}"/>,引用另一个bean。指定school的值为另一个Bean实例mySchool。 2、<property name="schoolName" value="#{mySchool.name.toUpperCase()"/>,使用指定属性,并使用其方法。指定schoolName值为mySchool的name属性值,并将其字母均转换成大写字母(toUpperCase()方法)。
3.5 使用内部Bean注入
- 若不希望代码直接访问某个bean,即,在代码中通过getBean方法获取该Bean实例,则可将该Bean的定义放入调用者bean定义的内部。
<bean id="myStudent" class="com.eason.spring4.po.Student">
<property name="name" value="balabala"></property>
<property name="age" value="21"></property>
<property name="school">
<bean class="com.eason.spring4.po.School">
<property name="name" value="清华大学"></property>
</bean>
</property>
</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 + "]";
}
}
<bean id="myBase" class="com.eason.spring4.po.Student" abstract="true">
<property name="department" value="计算机工程"></property>
<property name="school" value="清华大学"></property>
</bean>
<bean id="myStudent1" parent="myBase">
<property name="name" value="balabala"></property>
<property name="score" value="94.5"></property>
</bean>
<bean id="myStudent2" parent="myBase">
<property name="name" value="bilibili"></property>
<property name="score" value="95.5"></property>
</bean>
@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 + "]";
}
}
<bean id="myBase" abstract="true">
<property name="department" value="计算机工程"></property>
<property name="school" value="清华大学"></property>
</bean>
<bean id="student" class="com.eason.spring4.po.Student" parent="myBase">
<property name="name" value="balabala"></property>
<property name="score" value="94.5"></property>
</bean>
<bean id="teacher" class="com.eason.spring4.po.Teacher" parent="myBase">
<property name="name" value="bilibili"></property>
<property name="score" value="95.5"></property>
</bean>
@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数组,将其作为容器初始化参数出现。其将与可变参的容器构造器匹配。
- 各配置文件间为并列关系,不分主次。
@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 包含关系的配置文件
- 各配置文件中有一个总文件,总配置文件将各其他子文件通过<Import/>引入。在Java代码中只需要使用总配置文件对容器进行初始化即可。
<import resource="classpath:com/eason/spring4/po/spring-base.xml"/>
<import resource="classpath:com/eason/spring4/po/spring-student.xml"/>
<import resource="classpath:com/eason/spring4/po/spring-teacher.xml"/>
@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编程。 2、需要更换配置文件头,即添加相应的约束。约束在%SPRING_HOME%\docs\spring-framework-reference\html\xsd-configuration.html文件中。 3、需要在Spring配置文件中配置组件扫描器,用于在指定的基本包中扫描注解。
<?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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- bean definitions here -->
<context:component-scan base-package="com.eason.spring4.po"></context:component-scan>
</beans>
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上。
@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。
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配置文件
<?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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- bean definitions here -->
<bean id="mySchool" class="com.eason.spring4.School">
<property name="name" value="清华大学"></property>
</bean>
<bean id="myStudent" class="com.eason.spring4.Student">
<property name="name" value="balabala"></property>
<property name="score" value="21"></property>
<property name="school" ref="mySchool"></property>
</bean>
<context:component-scan base-package="com.eason.spring4"></context:component-scan>
</beans>
4.8.4 定义测试类
@RunWith(SpringJUnit4ClassRunner.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(SpringJUnit4ClassRunner.class):拥有指定运行环境;2、@ContextConfiguration(locations=""):用于指定配置文件位置;
4.9 注解与XML共同使用
- 注解的好处是,配置方便,直观。但是其弊端也是显而易见的:以硬编码的方式写入到Java代码中,其修改时需要编译代码的。
- XML配置方式的最大好处是,对其所作修改,无需编译代码,只需重启服务器即可将新的配置加载。
- 若注解与XML同用,XML的优先级要高于注解。这样做的好处是,需要对某个Bean做修改时,只需要修改配置文件即可。当然,此时Bean类要有setter或者构造器。(注解的方式可以不需要setter或者构造器,直接编写在属性跟前即可。)