• 控制反转(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或者构造器,直接编写在属性跟前即可。)