第四章 在 IoC 容器中装配 Bean

4.1 Spring 配置概述

4.1.1 Spring 容器高层视图
  • Spring容器成功启动的必备条件

    • Spring 框架的类包都已经放到应用程序的类路径下
    • 应用程序为 Spring 提供完备的 Bean 配置信息
    • Bean 的类都已经放到应用程序的类路径下
  • Bean 在Spring中加载的过程

    • 1、Bean 配置信息定义了 Bean 的实现及依赖关系
    • 2、Spring 容器根据各种形式的 Bean 配置信息在容器内部建立 Bean 定义注册表
    • 3、然后根据注册表加载、实例化 Bean,并建立 Bean 和 Bean 的依赖关系
    • 4、最后将这些准备就绪到 Bean 放到 Bean 缓存池中,以供外层的应用程序进行调用
4.1.2 基于 XML 的配置
  • xml 文件头中声明的内容
    • 默认命名空间: 它没有空间名,用于Spring Bean 的定义
    • xsi命名空间: 这个命名空间用于为每个文档中命名空间指定相应的 Schema 样式文件,是标准组织定义的标准命名空间
    • aop命名空间: 这个命名空间是Spring 配置 AOP 的命名空间,是用户自定义的命名空间。

4.2 Bean 基本配置

4.2.1 装配一个Bean


	
	

4.2.2 Bean的命名
  • 配置一个 Bean 时,需要指定一个 id 或 name 属性作为 Bean 的名称
    id 和 name 都可以指定多个名字,之间用逗号、分号或者空格进行分割



  • 通过getBean(“car”)、getBean("#car1")、getBean(“123”)、getBean("$car") 、getBean(“com.baobaotao.simple.Car”)获取 Bean
    注意:
  • Spring配置文件不允许出现两个相同 id 的 < bean >;却可以出现两个相同 name 的 Bean;通过 getBean(beanName) 获取最后声明的 Bean,原因是后面的 Bean 覆盖前面 Bean。 故,尽量使用 id 而非 name 命名的 Bean。
  • 若没有 id 、name,则默认全路径为 Bean 命名。

4.3 依赖注入

4.3.1 属性注入
package com.baobaotao.ditype;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class Car {
	private int maxSpeed;
	private String brand;
	private double price;
	
}


	
	
		200
		红旗CA72
		20000.00
	

package com.baobaotao.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.baobaotao.ditype.Car;

public class ApplicationBeansTest {
	
	public static void main(String[] args) {
		ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
	   Car car = (Car) context.getBean("car");
	   System.err.println(car);
		
	}
}

注意:成员变量的命名规则 驼峰命名、全小写、首字母不可大写

4.3.2 构造函数注入
package com.baobaotao.ditype;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class Car {
	private int maxSpeed;
	private String brand;
	private double price;
	private String iDcode;
	
	//测试1
	public Car(String brand, double price) {
		this.brand = brand;
		this.price = price;
	}
	
	//测试2
	public Car(String brand, String corp, double price) {
		this.brand = brand;
		this.corp = corp;
		this.price = price;
	}
	//测试3
	public Car(String brand, String corp, int maxSpeed) {
		this.brand = brand;
		this.corp = corp;
		this.maxSpeed = maxSpeed;
	}
}
package com.baobaotao.ditype;

public class Office {

}
package com.baobaotao.ditype;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class Boss {
	private String name;
	private Car car;
	private Office office;
	
	//测试4
	public Boss(String name, Car car, Office office) {
		this.name = name;
		this.car = car;
		this.office = office;
	}
}


	
		20000
	
	
		红旗CA72
	


	

	
	
	




	
	
	




	
		John
	
	
		
	
	
		
	


	
	
	


  • 循环依赖问题
package com.baobaotao.ditype;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class Car {
	private String brand;
	private Boss boss;
	
	public Car() {}
	
	public Car(String beand, Boss boss) {
		this.brand = beand;
		this.boss = boss;
	}
}
package com.baobaotao.ditype;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class Boss {
	private String name;
	private Car car;
	
	public Boss(String name, Car car) {
		this.name = name;
		this.car = car;
	}
}

	
	



	
	

该种方法无法启动 Spring Ioc容器,因为循环依赖;解决方法:将构造函数注入方式调整为属性注入方式

4.3.3 工厂方法注入(大可不适用)
package com.baobaotao.ditype;

public class CarFactory {

	//非静态工厂方法
	public Car createhongQiCar() {
		Car car = new Car();
		car.setBrand("红旗CA72");
		return car;
	}
	
	//静态工厂方法
	public static Car createhongQiCarStatic() {
		Car car = new Car();
		car.setBrand("红旗CA72");
		return car;
	}
	
	public static void main(String[] args) {
		ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
		Car car = (Car) context.getBean("car5");
	   System.err.println(car);
	}
}









4.3.4 选择注入方式的考量
  • 构造函数可以保证一些重要的属性在Bean实例化时就设置好,避免了因为一些重要属性没有提供,导致一个无用Bean实例的情况;
  • 不需要为每个属性提供Setter方法,减少了类的方法个数
  • 可以更好的封装变量,不需要为每个属性指定Setter方法,避免外部错误的调用。更多的开发者可能更倾向于使用属性注入方式,他们反对构造函数注入的理由是:
    • 如果一个类的属性众多,构造函数的签名将变成一个庞然大物,可读性很差;
    • 灵活性不强,在有些属性是可选的情况下,如果通过构造函数注入,也需要为可选的参数提供一个null值
    • 如果有多个构造函数,需要考虑配置文件和具体构造函数匹配歧义的问题,配置上相对复杂
    • 构造函数不利于类的继承和扩展,因为子类需要引用到父类复杂的构造函数

4.4 注入参数详解

4.4.1 字面值

所谓"字面值"一般是指可用字符串表示的值,这些值可以通过< value >元素标签进行注入。

XMl特殊处理标签

  • XML的5个特殊字符(&、<、>、"、’、。)
  • 处理方法
    • 使XML解析器将标签中的字符串当作普通的文本对待
    • 使用XML转移序列特殊的符
特殊符号 转义序列
< & lt;
> & gt;
& & amp;
" & quot;
& apos;

注意:一般情况下,XML解析器会忽略内部字符串的前后空格,但Spring却不忽略元素标签内部字符串的前后空格。


	
		200
	
	
		
		红旗&CA72
	

4.4.2 引用其他 Bean
  • < ref> 元素可以通过一下三个属性引用容器中其他Bean
    • bean: 通过该属性可以引用同一容器或父容器的 Bean,这是最常见的形式
    • local: 通过改属性只能引用统一配置文件中定义的 Bean,它可以利用XML解析器自动检验引用的合法性,以便在开发编写配置时能够及时发现并纠正配置的错误
    • parent: 引用父容器中的 Bean,如< ref parnet=“car”>的配置说明car 的 Band 是父容器的中的Bean

beans1.xml



	
	
	

beans2.xml



	
	
	


	
		
		
		
	

package com.baobaotao.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.baobaotao.ditype.Boss;
import com.baobaotao.ditype.Car;
import com.baobaotao.ditype.CarFactory;

public class ApplicationBeansTest {
	
	public static void main(String[] args) {
		//父容器
		ClassPathXmlApplicationContext pFactory = new ClassPathXmlApplicationContext(new String[] {"beans1.xml"});
		//指定pFactory为该容器的父容器
		ApplicationContext factory = new ClassPathXmlApplicationContext(new String[] {"beans2.xml"}, pFactory);
		
		Boss boss = (Boss) factory.getBean("boss");
		System.err.println(boss);
	}
}
4.4.3 内部 Bean
  • 如果 car Bean 只被 boss Bean 引用而不被容器中任何其他的 Bean引用,则可以将 car 以内部的方式注入到 Boss 中

	
		
			
			
		
	

  • 内部 Bean 即使提供了 id、name、scope 属性,也会被忽略。即不能被其他 Bean 引用,只能在声明除为外部Bean提供实例注入
4.4.4 null值
  • 对 car 的 brand 属性注入一个 null 值

	
		
	

4.4.5 级联属性
package com.baobaotao.ditype;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class Boss {
	...
	private Car car = new Car();
}

	

  • Spring 没有对级联属性的层级数进行限制,只要配置的 Bean拥有对应于级联属性的类结构,就可以配置任意层级的级联属性;定义了具有三级结构的级联属性。
4.4.6 集合类型属性

java.util 包中的集合类是最常用的数据结构类型,主要包括 List、Set、Map、Properties,Spring 为这些集合类型属性提供了专门的配置元素标签

  • List、Set、Map、Properties 示例
package com.baobaotao.ditype;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class Boss {
	private List favorites = new ArrayList();
	private Map jobs = new HashMap();
	private Set hashSet = new HashSet();
	private Properties mails = new Properties();
	
}

	
		
			看报
			赛车
			高尔夫
		
	
	
		
			
				AM
				会见客户
			
			
				PM
				公司内部会议
			
		
	
	
		
			
			
		
	
	
		
			[email protected]
			[email protected]
		
	

  • 集合合并

	
		
			看报
			赛车
			高尔夫
		
	



	
		
			爬山
			游泳
		
	

通过 util 命名空间配置集合类型的 Bean
  • 如果你希望配置一个集合类型的 Bean,而非一个集合类型的属性,则可以通过 util 命名空间进行配置。首先需要在 Spring 配置文件头中引入 util 命名空间的声明


   
 

	dream1
	dream1
	dream1



	set1
	set2



	
		key1
		value1
	
	
		key2
		value2
	

4.4.7 简化配置方式
  • 字面值属性

    • 字面值属性 < property name=“maxSpeed” value=“200”>
    • 构造函数参数 < constructor-arg type=“java.lang.String” value=“红旗CA72”/>
    • 集合元素 < map>< entry key=“AM” value=“会见客户”/>
  • 引用对象属性

    • 字面值属性 < property name=“car” ref=“car”>
    • 构造函数参数 < constructor-arg ref=“car”/>
    • 集合元素 < map>< entry key-ref=“keyBean” value-ref=“valueBean”/>
  • 使用 p 命名空间



     	
     	
		
 
4.4.8 自动装配

< bean>元素提供了一个指定自动装配类型的属性:autowire="<自动装配类型>" Spring 提供了4种自动装配类型

自动装配类型 说明
byName 根据名称进行自动匹配。假设 Boss 有一个名为 car 的属性,如果容器中刚好有一个名为 car 的 Bean ,Spring 就会自动将其装配给 Boss 的 car 属性
byType 根据类型进行自动匹配。假设 Boss 有一个 Car 类型的属性,如果容器中刚好有一个 Car 类型的 Bean ,Spring 就会自动将其装配给 Boss 的 car 属性
constructor 于 byType 类似,只不过它是针对构造函数注入而言的,如果Boss有一个构造函数,构造函数包含一个 Car 类型的入参,如果容器中有一个 Car 类型的 Bean,则Spring 将自动把这个 Bean 作为 Boss 构造函数的入参,如果容器中没有找到和构造函数入参匹配类型的Bean,Spring 将抛出异常
autodetect 根据 Bean 的自省机制决定采用 byType 还是 constructor 进行自动装配:如果Bean提供了默认的构造函数,则采用 byType;否则采用constructor

4.5 方法注入

4.5.1 lookup 方法注入
package com.baobaotao.injectfun;

import com.baobaotao.ditype.Car;

public interface MagicBoss {

	Car getCar();
}

	

	

package com.baobaotao.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.baobaotao.ditype.Boss;
import com.baobaotao.ditype.Car;
import com.baobaotao.ditype.CarFactory;
import com.baobaotao.injectfun.MagicBoss;

public class ApplicationBeansTest {
	
	public static void main(String[] args) {
		ApplicationContext factory = new ClassPathXmlApplicationContext("beans.xml");
		MagicBoss car = (MagicBoss) factory.getBean("magicBoss");
		System.err.println(car.getCar());
	}
}
4.5.1 方法替换
  • 调用方法 使 Boss2 中的 方法 reimplement() 方法,替换 Boos1 中的 getCar() 方法
package com.baobaotao.injectfun;

import com.baobaotao.ditype.Car;

public class Boss1 {

	public Car getCar() {
		Car car = new Car();
		car.setBrand("宝马Z4");
		return car;
	}
}

package com.baobaotao.injectfun;

import java.lang.reflect.Method;

import org.springframework.beans.factory.support.MethodReplacer;

import com.baobaotao.ditype.Car;

public class Boss2 implements MethodReplacer{

	public Object reimplement(Object arg0, Method arg1, Object[] arg2) {
		Car car = new Car();
		car.setBrand("美洲豹");
		return car;
	}
}


	



4.6 < Bean >之间的关系

  • 不但可以通过 < ref > 引用另一个 Bean,建立起 Bean 和 Bean 之间的依赖关系,相似的,< bean >元素标签之间也可以建立类似的关系,完成一些特殊的功能
4.6.1 继承
  • 如果有多个类拥有相同的方法和属性,则我们可以引入一个父类,在父类中定义这些共同的方法和属性,以消除重复的代码。


	



4.6.2 依赖
  • 一般情况下,可以使用< ref >元素标签建立对其它 Bean 的依赖关系, Spring 负责管理这些 Bean 的关系,当实例化一个 Bean 时, Spring 保证该 Bean 所依赖的其他 Bean 已经初始化。
package com.baobaotao.entity;

public class SystemSettings {
	public static int SESSION_TIMEOUT = 30;
	public static int REFRESH_CYCLE = 60;	
}
package com.baobaotao.entity;

public class SysInit {

	public SysInit() {
		SystemSettings.SESSION_TIMEOUT = 10;
		SystemSettings.REFRESH_CYCLE = 100;
	}
}
package com.baobaotao.entity;

import java.util.Timer;
import java.util.TimerTask;

public class CacheManager {

	public void cacheManager() {
		System.err.println( SystemSettings.REFRESH_CYCLE);
	}
}
  • Spring 运行用户通过 depends-no 属性指定 Bean 前置依赖的 Bean,签之依赖的 Bean 会在本 Bean 实例化之前传值。如果前置依赖于多个Beans,则可以通过逗号,空格或分号的方式创建 Bean 的名称


4.6.3 引用
  • Spring 提供了一个< idref >元素标签,通过该标签引用另一个< bean > 的名字。


4.7 整合多个配置文件

  • 方法一:一个 XML 配置文件可以通过 < import> 组合多个外部的配置文件,resource 属性支持 Spring 标准的资源路径

  • beans1.xml



	
	

  • beans2.xml


	
	
	
	
	
	

方法二:通过 new String[]{“beans1.xml”,“beans1.xml”}

package com.baobaotao.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.baobaotao.ditype.Boss;
import com.baobaotao.ditype.Car;

public class ApplicationBeansTest {
	
	public static void main(String[] args) {
		ApplicationContext factory = new ClassPathXmlApplicationContext(new String[] {"beans1.xml", "beans2.xml"});
		Car car1 = (Car) factory.getBean("car1");
		Boss boss2 = (Boss) factory.getBean("boss2");
		System.err.println(car1);
		System.err.println(boss2);
	}
}

4.8 Bean 作用域

类 别 说 明
singleton 在 Spring IoC 容器中仅存在一个 Bean 实例, Bean 以单实例的方式存在
prototype 每次从容器中调用 Bean 时,都返回一个新的实例,即每次调用getBean()时,相当于执行 new XxxBean() 的操作
request 每次 HTTP 请求都会创建一个新的 Bean。该作用域仅适用于 WebApplicationContext环境
session 同一个 HTTP Session 共享一个
globalSession 在 Spring IoC 容器中仅存在一个 Bean 实例, Bean 以单实例的方式存在
4.8.1 singleton 作用域

无状态或者状态不可变的类适合使用单实例模式;

  • @1 处的 car Bean 声明为 singleton (因为默认是 singleton,所以可以显示不指定),在容器中有 3 个其他的 Bean 引用了 car Bean,如 @2、@3、@3所示。在容器内部 boss1、boss2、boss3 的 car 属性都指向同一个Bean
  • 任何通过容器的 getBean(“car”) 方法返回的实例也指向同一个 Bean.
   @1
 @2
 @3
 @4
  • Spring 的 ApplicationContext 容器在启动时,自动实例化所有singleton 的 Bean 并缓存于容器中。好出有二
    • 提前实例化,及早发现一些潜在的配置问题
    • Bean 以缓存的方式保存,当使用到该 Bean 时就无需再实例化,加快了效率。
  • 如果用户不希望再容器启动时提前实例化 singleton 的 Bean,可以通过lazy-init 属性控制
    • 当该 Bean 被其他需要提前实例化的 Bean 引用到,Spring 也将忽略延迟实例化的设置

4.8.2 prototype 作用域
  • boss1、boss2、boss3 所引用的都是一个新的 car 实例,每次通过容器的 getBean(“car”) 方法返回的也是新的Car 实例
  • 默认Spring 容器启动时不实例化 prototype 的 Bean。此外,Spring 容器将 prototype 的 Bean 交给调用者后,就不再管理它的生命周期
   @1
 @2
 @3
 @4
4.8.3 Web 应用环境相关的 Bean 作用域
  • 使用 Spring 的 WebApplicationContext,则还可以使用另外 3 种 Bean 的作用域:request、session和 globalSession。当然必须再 Web 容器中进行额外的配置。

  • Web 容器中进行配置



  	
  		org.springframework.web.context.request.RequestContextListener
  	
  

  • 再整合 Spring 容器时使用 ContextLoaderListener,它实现了 ServletContextListener 接听接口,ServletContextListener 只负责接听 Web 容器启动和关闭的事件。
  • RequestContextListener 实现 ServletReqeustListener 监听器接口,该监听器监听 HTTP 请求事件, Web 服务器接收的每一次请求都会通知该监听器。
  • Spring 容器启动和关闭操作由 Web 容器的启动和关闭事件触发,但如果 Spring 容器中的 Bean 需要 request、sessios 和 globalSession 作用域的支持,Spring容器本身就必须获取 Web 容器的 HTTP 请求事件,以 HTTP 请求的事件“驱动” Bean作用域的控制逻辑。

request 作用域

  • request 作用域的 Bean 对应一个 HTTP 请求和生命周期

  • 每次 HTTP 请求调用到 car Bean 时,Spring 容器创建一个新的 car Bean,请求处理完毕后,销毁这个 Bean。

session作用域


  • car Bean 的作用域横跨整个 HTTP Session, Session 中所有 HTTP 请求都共享一个 car Bean,当 HTTP Session 结束后,实例才被销毁。

globalSession 作用域


  • globalSession 作用域类似于 session 作用域,不过仅再 Portlet 的 Web 应用中使用。Portlet 规范定义了全局 Session 的概念,它被组成 portlet Web 应用的所有的子 Portet 共享。如果不在 Portet Web 应用环境下,globalSession 自然就等价于 session 作用域了。
4.8.4 作用域依赖问题


     xsi:schemaLocation="http://www.springframework.org/schema/beans
     	http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
     	http://www.springframework.org/schema/util
     	http://www.springframework.org/schema/util/spring-util-3.0.xsd
     	http://www.springframework.org/schema/aop
     	http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
	
	
		
	
	
		
	

  • car Bean 是 request 作用域,他被 singleton 作用域的 boss Bean 引用。 为了 boss能够从适当作用域中获取 car Bean 的引用,需要使用 Spring AOP 的配置标签,则需要再文档声明头中定义 aop 命名空间。
  • 当 boss Bean 再 Web 环境下,调用 car Bean 时, Spring AOP 将启用动态dialing智能判断 boss Bean 位于那个HTTP请求线程中,并从对应的HTTP请求线程域中获取对应的 car Bean
  • boss Bean 的作用域时 singleton,再Spring 容器中始终只有有个实例,而 car Bean 的作用域为request,所以每个调用到 car Bean 的那些HTTP请求都会创建一个car Bean。Spring 通过动态代理技术,能够让 boss Bean 应用到对应HTTP 请求的 car Bean。

4.9 FactoryBean

  • Spring 通过反射机制利用< Bean > 的class 属性指定实现类实例化 Bean。

  • 从Spring 3.0 开始,FactoryBean 开始支持泛型,即接口声明改为 FactoryBean< T>的形式;该接口中定义了以下3个接口方法

    • T getObject() 返回由FactoryBean 创建的 Bean 实例, 如果 isSingleton() 返回 true,则该实例会放到Spring 容器的单实例缓存池中。
    • boolean isSingleton() 确定由 FactoryBean 创建的 Bean的作用域时 singleton 还是prototype;
    • Class getObjectType() 返回 FactoryBean 创建 Bean 的类型。
package com.baobaotao.fb;

import org.springframework.beans.factory.FactoryBean;

import com.baobaotao.ditype.Car;

public class CarFactoryBean implements FactoryBean{

	private String carInfo;
	public String getCarInfo() {
		return carInfo;
	}
	
	//@1 接收逗号分割的属性设置信息
	public void setCarInfo(String carInfo) {
		this.carInfo = carInfo;
	}
	
	//@2 实例化 Car Bean
	@Override
	public Car getObject() throws Exception {
		Car car = new Car();
		String[] infos = carInfo.split(",");
		car.setBrand(infos[0]);
		car.setMaxSpeed(Integer.valueOf(infos[1]));
		car.setPrice(Double.valueOf(infos[2]));
		return car;
	}

	//@3 返回Car 的类型
	@Override
	public Class getObjectType() {
		return Car.class;
	}

	public boolean isSingleton() {
		return false;
	}
}

package com.baobaotao.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.baobaotao.ditype.Car;
import com.baobaotao.fb.CarFactoryBean;

public class ApplicationBeansTest {
	
	public static void main(String[] args) throws Exception {
		
		ApplicationContext factory = new ClassPathXmlApplicationContext("beans.xml");
		//该方法返回 Car Bean对象  getBean("car')
		//Car cfb = (Car) factory.getBean("car");
		
		//若希望获取CarFactoryBean 自身的实例 则需再 beanName 前加 ‘&’   getBean("&car')
		CarFactoryBean cfb = (CarFactoryBean) factory.getBean("&car");
		System.err.println(cfb.getCarInfo());
		Car car = cfb.getObject();
		System.err.println(car);
		System.err.println(cfb.getObjectType());
		System.err.println(cfb.isSingleton());
	}
}

4.10 基于注解的配置

4.10.1 使用注解定义 Bean
  • @Component:
  • @Repository:用于对DAO 实现类进行标注
  • @Service:用于对Service 实现类进行标注
  • @Contoller:用于对Controller 实现类进行标注
4.10.2 使用注解配置信息启动Spring 容器



	
	
	

  • 在@1 处声明context 命名空间,在@2 处即可通过context 命名空间的 component-scan 的base-package 属性指定一个扫描的基类包,Spring 容器将会扫描这个基类包里的所有类,并从类的注解信息中获取 Bean 的定义信息。
  • 如果仅希望扫描特定的类而非基包下的所有类,那么可以使用 resource-pattern 属性过滤出特定的类,如:

  • 这里我们将基包设置为 com.baobaotao,默认情况下 resource-pattern 属性的值为 “**/.class”,即基类包里的所有类。这里我们设置为 “anno/.class”,则Spring 仅会扫描基包里 anno 子包中的类。
  • 通过resource-pattern 属性仅可以按资源名称对几包中的类进行过滤,如果仅使用resource-pattern,你会发现很多时候它并不满足你的要求,如果仅过滤基类包中实现了XxxService 接口的类或标注了某个特定注解的类等。
    • 这些需求可以通过 < context:component-scan >的过滤子元素实现
    • < context:include-filter > 表示要包含的目标类,而< context:exclude-filter > 表示要排除在外的目标类。


	
	

类 别 示例 说 明
annotation com.baobaotao.XxxAnnotation 所有标注了 XxxAnnotation 的类。该类型采用目标类是否标注了某个注解进行过滤
assignable com.baobaotao.XxxService 所有继承或扩展 XxxService 的类。该类型采用目标类是否继承或扩展某个特定类进行过滤
aspectj com.baobaotao…*Service+ 所有类名以 Service 结束的类及继承或扩展他们的类。该类型采用 AspectJ 表达式经写过滤
regex com.baobaotao.anno…* 所有com.baobaotao.anno 类包下的类。该类型采用正式表达式根据目标类的类名进行过滤
custom com.baobaotao.XxxTypeFilter 采用 XxxTypeFile 通过代码的方式根据过滤规则。该类必须实现 org.springframework.core.type.TypeFilter 接口
4.10.3 自动装配 Bean
  • 使用@Autoeired 进行自动注入 其属性 required
package com.baobaotao.anno.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.baobaotao.anno.dao.LogDao;
import com.baobaotao.anno.entity.UserBo;

//@1 定义一个Service的Bean
@Service
public class LogonService {

	// @2 分别注入 LogDao 及 UserDao 的 Bean
	@Autowired(required=false)
	private LogDao logDao;
	
	public void logInUser() {
		UserBo user = new UserBo();
		System.out.println(user);
	}
}

  • @Autowired 默认按类型匹配的方式,在容器查找匹配的 Bean,当有且仅有一个匹配的 Bean 时,Spring 将其注入到@Autowired 标注的变量中
  • 如果容器中没有一个和标注变量类型匹配的 Bean,Spring 容器启动时将包 NoSuchBeanDefinitionException的异常。如果希望Spring 即使找不到匹配的 Bean 完成注入也不要报异常时,那么可以使用 @Autowired(required=false)

使用 @Qualifier 指定注入 Bean 的名称

  • 容器中有一个以上匹配的 Bean 时,则可以通过 Qualifier 注解限定 Bean 的名称
package com.baobaotao.anno.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

import com.baobaotao.anno.dao.LogDao;
import com.baobaotao.anno.dao.UserDao;

@Service
public class LogonService {

	@Autowired(required=false)
	private LogDao logDao;
	
	// 注入名为userDao,类型为 UserDao 的 Bean
	@Autowired
	@Qualifier("userDao")
	private UserDao userDao;
}

对类方法进行标注

  • @Autowired 可以对成员变量以及方法的入参进行标注
package com.baobaotao.anno.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

import com.baobaotao.anno.dao.LogDao;
import com.baobaotao.anno.dao.UserDao;

@Service
public class LogonService {
	private LogDao logDao;
	private UserDao userDao;
	
	//自动将LogDao 传给方法入参
	@Autowired
	public void setLogDao(LogDao logDao) {
		this.logDao = logDao;
		System.out.println("logDao:" +logDao);
	}
	
	//自动将名为 userDao 的 Bean 传给方法入参
	@Autowired
	@Qualifier("userDao")
	public void getUserInfo(UserDao userDao) {
		this.userDao = userDao;
		System.out.println("userDao:" + userDao);
	}

	//Spring 自动选择匹配入参类型的 Bean 进行注入
	@Autowired
	public void getUserInfo(UserDao userDao,  LogDao logDao) {
		this.userDao = userDao;
		System.out.println("userDao:" + userDao + " LogDao:" + logDao);
	}

	// 如果一个方法拥有多个入参,默认情况下,Spring 自动选择匹配入参类型的 Bean 进行注入, Sprig 允许对方法入参标注@Qualifier 以指定注入 Bean 的名称
	@Autowired
	public void getUserInfo(@Qualifier("userDao")UserDao userDao,  LogDao logDao) {
		this.userDao = userDao;
		System.out.println("userDao:" + userDao + " LogDao:" + logDao);
	}
}

对集合类进行标注

  • 如果对类中集合类的变量或方法入参进行@Autowired标注,Spring 会将容器中类型匹配的所有Bean 都自动注入进来
package com.baobaotao.anno.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class MyComponent {

	@Autowired(required=false)
	private List plugin;
	
	public List getPlugins(){
		return plugin;
	}
}
package com.baobaotao.anno.service;

public interface Plugin {
	
}
package com.baobaotao.anno.service.impl;

import org.springframework.stereotype.Component;

import com.baobaotao.anno.service.Plugin;

@Component
public class OnePlugin implements Plugin{

}
package com.baobaotao.anno.service.impl;

import org.springframework.stereotype.Component;

import com.baobaotao.anno.service.Plugin;

@Component
public class TwoPlugin implements Plugin{

}
4.10.4 Bean 作用范围及生命过程方法
  • 通过注解配置的 Bean 和通过 < bean >配置的 Bean 一样,默认的作用范围都是 singleton, Spring为注解配置提供了一个@Scope 的注解, 可通过它显示指定 Bean 的作用范围;
package com.baobaotao.ditype;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Scope("prototype")
@Component
public class Car {
	...
}
  • 使用< bean >进行配置时,我们可以通过 init-method 和 destory-method 属性指定 Bean 的初始化及容器销毁前执行的方法。
  • @PostConstruce 和 @PreDestroy注解,在Spring 中它们相当于 init-method 和 destory-method 属性的功能,不过使用注解时,可以在一个 Bean 中定义多个:
package com.baobaotao.anno.entity;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class Boss {
	private Car car;
	
	public Boss() {
		System.out.println("construce...");
	}

	@Autowired
	public void setCar(Car car) {
		System.out.println("execute in setCar");
		this.car = car;
	}
	
	@PostConstruct
	private void init1() {
		System.out.println("execute in init1");
	}
	
	@PostConstruct
	private void init2() {
		System.out.println("execute in init2");
	}
	
	@PreDestroy
	private void destory1() {
		System.out.println("execute in destory1");
	}
	
	@PreDestroy
	private void destory2() {
		System.out.println("execute in destory2");
	}
}
package com.baobaotao.anno;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {

	public static void main(String[] args) {
		ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
		((ClassPathXmlApplicationContext)ctx).destroy();
	}
}
  • 输出信息
    • construce …
    • execute in setCar
    • execute in init1
    • execute in init2
    • execute in destory1
    • execute in destory2
  • 说明 Spring 先调用 Boss 的构造函数示例化 Bean,再执行@Autowired 进行自动注入,然后分别执行标注了 @PostConstruct 的方法,当容器关闭时,则分别执行标注了 @PreDestroy 的方法

4.11 基于 Java 类的配置

4.11.1 使用 Java 类提供 Bean 定义信息
  • 普通的 POJO 只要标注 @Configuration 注解,就可以为 Spring 容器提供 Bean 定义的信息了,每个标注了 @Bean 的类方法都相当于提供一个Bean 的定义信息。
package com.baobaotao.conf;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.baobaotao.anno.dao.LogDao;
import com.baobaotao.anno.dao.UserDao;
import com.baobaotao.anno.dao.impl.LogDaoImpl;
import com.baobaotao.anno.dao.impl.UserDaoImpl;
import com.baobaotao.anno.service.LogonService;

//@1 将一个POJO标注为定义 Bean 的配置类
@Configuration
public class AppConf {

	//@2 以下两个方法定义了两个 Bean,并提供了 Bean 的示例化逻辑
	@Bean
	public UserDao userDao() {
		return new UserDaoImpl();
	}
	
	@Bean
	public LogDao logDao() {
		return new LogDaoImpl();
	}
	
	//@3 定义了 logonService 的 Bean
	@Bean
	public LogonService logonService() {
		LogonService logonService = new LogonService();
		//@4 将@2和@3处定义的 Bean 注入到logonService Bean 中
		logonService.setLogDao(logDao());
		logonService.getUserInfo(userDao(), logDao());
		return logonService;
	}
}
  • @1 处在AppConf 类的定义处标注了 @Configuration 注解,说明这个类可用于为Spring 提供Bean 的定义信息。类的方法处可以标注 @Bean 注解, Bean 的类型由方法返回值类型决定,名称默认和方法名相同,也可通过入参显示指定 Bean 名称,如@Bean(name=“userDao”)。之间在@Bean 所标注的方法中提供 Bean 的实例化逻辑。
  • 在@2 处 userDao() 和 logDao() 方法定义了一个 UserDao 和一个 LogDao的 Bean,他们的Bean名称分别是 userDao 和 logDao。 在@3 处,定义了一个logonService Bean,并且在@4处注入@2 处所定义的两个Bean。
package com.baobaotao.conf;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.baobaotao.anno.dao.LogDao;
import com.baobaotao.anno.dao.UserDao;
import com.baobaotao.anno.dao.impl.LogDaoImpl;
import com.baobaotao.anno.dao.impl.UserDaoImpl;

@Configuration
public class DaoConfig {

	@Bean
	public UserDao userDao() {
		return new UserDaoImpl();
	}
	
	@Bean
	public LogDao logDao() {
		return new LogDaoImpl();
	}
}
  • 由于 @Configuration 注解类本身已经标注了 @Component 注解,所以任何标注了 @Configuration 的类,本身也相当于标注了 @Component,即他们可以像普通的Bean 一样被注入到其他 Bean 中。DaoConfig 标注了 @Configuration 注解后就成为一个Bean,它可以被自动注入 ServiceConfig中:
package com.baobaotao.conf;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;

import com.baobaotao.anno.service.LogonService;

@Configuration
public class ServiceConfig {

	//@1 像普通 Bean 一样注入 DaoConfig
	@Autowired
	private DaoConfig daoConfig;
	
	public LogonService logonService() {
		LogonService logonService = new LogonService();
		//@2 像普通 Bean 一样,调用 Bean 相关的方法
		logonService.setLogDao(daoConfig.logDao());
		logonService.getUserInfo(daoConfig.userDao(), daoConfig.logDao());
		return logonService;
	}	
}
  • 调用 daoConfig 的 logDao() 和 userDao() 方法,就相当于将 DaoConfig 配置类中定义的 Bean 注入进来。Spring 会对配置类所有标注@Bean的方法进行AOP增强,将 daoConfig.userDao() 方法时,不是简单的执行 DaoConfig 类中定义的方法逻辑,而是从Spring 容器中返回相应 Bean 的单例。
  • 在@Bean 处,添加@Scope 注解以控制Bean 的作用范围。如果在@Bean 处标注了@Scope(“prototype”),则每次调用 daoConfig.logDao() 都会返回一个新的 LogDao Bean:
package com.baobaotao.conf;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

import com.baobaotao.anno.dao.LogDao;
import com.baobaotao.anno.dao.impl.LogDaoImpl;

@Configuration
public class DaoConfig2 {

	@Scope("prototype")
	@Bean
	public LogDao logDao() {
		return new LogDaoImpl();
	}
}
4.11.2 使用基于 Java 类的配置信息启动 Spring 容器
  • 直接通过@Configuration 类启动Spring 容器
package com.baobaotao.anno;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import com.baobaotao.anno.service.LogonService;
import com.baobaotao.conf.AppConf;

public class JavaConfigTest {

	public static void main(String[] args) {
		/*  //测试方法一:
		ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConf.class);
		LogonService logonService = ctx.getBean(LogonService.class);
		logonService.printHello();*/
		
		//测试方法二:
		AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
		ctx.register(DaoConfig.class);
		ctx.register(ServiceConfig.class);
		ctx.refresh();
		LogonService logonService = ctx.getBean(LogonService.class);
		logonService.printHello();
	}
}
  • 测试二 方法通过代码一个一个注册配置类,也可以通过@Import将多个配置类组装到一个配置类中,这样仅需要注册这个组装好的配置类就可以启动容器了:
package com.baobaotao.conf;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

import com.baobaotao.anno.service.LogonService;

@Configuration
@Import(DaoConfig.class)
public class ServiceConfig {

	//@1 像普通 Bean 一样注入 DaoConfig
	@Autowired
	private DaoConfig daoConfig;
	
	@Bean
	public LogonService logonService() {
		LogonService logonService = new LogonService();
		//@2 像普通 Bean 一样,调用 Bean 相关的方法
		logonService.setLogDao(daoConfig.logDao());
		logonService.getUserInfo(daoConfig.userDao(), daoConfig.logDao());
		return logonServ
	}	
}
  • 通过 XML 配置文件引用 @Configuration 的配置


	
	
	

  • 通过 Configuration 配置类引用 XML 配置信息


package com.baobaotao.conf;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;

import com.baobaotao.anno.dao.LogDao;
import com.baobaotao.anno.dao.UserDao;
import com.baobaotao.anno.service.LogonService;

@Configuration
@ImportResource("classpath:beans2.xml")
public class LogonAppConfig {

	@Bean
	@Autowired
	public LogonService logonService(UserDao userDao, LogDao logDao) {
		LogonService logonService = new LogonService();
		logonService.setLogDao(logDao);
		logonService.getUserInfo(userDao, logDao);
		return logonService;
	}
}

4.12 不同配置方式比较

基于 XML 配置 基于注解配置 基于Java 类配置
Bean 定义 在 XML 文件中同通过 < bean > 元素定义 Bean 如:< bean class=“com.hm.UserDao” > 在 Bean 实现类处通过标注 @component 或者衍型类(@Repository、@Service和@Controller)定义 Bean 在标注了 @Configuration 的Java类中,通过在类方法上标注 @Bean 定义一个Bean。方法必须提供 Bean 的实例化逻辑
Bean 名称 通过 < bean > 的id活name属性定义,如:< bean id=“userDao” class=“com.hm.UserDao” > 默认名称为:com.hm.UserDao#0 通过注解的 value 属性定义,如 @Component(“userDao”)。默认名称为小写字母打头的类名(不带包名): userDao 通过@Bean 的 name 属性定义,如 @Bean(“userDao”),默认名称为方法名
Bean 注入 通过 < property > 子元素或通过 p 命名空间的动态属性,如 p:userDao-ref="userDao"进行注入 通过在成员变量或方法入参处标注 @Autowired,按类型匹配自动注入。还可以配合使用 @Qualifief 按名称匹配方法注入 比较灵活,可以通过在方法处通过 @Autowired 使方法入参绑定 Bean,然后在方法中通过代码注入,还可以同通过调用配置类的 @Bean 方法进行注入
Bean 生命过程方法 通过 < bean > 的 init-method 和 destroy-method 属性指定 Bean 实现类的方法名。最多只能指定一个初始化反法和一个销毁方法 通过在目标方法上标注 @PostConstruce 和 @preDestroy 注解指定初始化或销毁方法,可以定义任意多个方法 通过 @Bean 的 initMethod 或 destoryMethod 指定一个初始化或销毁方法 对于初始化方法来说,可以直接在方法内部通过代码的方式灵活定义初始化逻辑
Bean 作用范围 通过 < bean > 的 scope 属性指定,如:< bean class=“com.hm.UserDao” scope=“prototype” > 通过在类定义处标注 @Scope 指定,如 @Scope(“prototype”) 通过在Bean方法定义处标注 @Scope 指定
Bean 延迟初始化 通过 < bean > 的 lazy-init 属性指定,默认为 default,继承于 < beans > 的default-lazy-init 设置,该值默认为 false 通过在类定义处标注 @Lazy 指定,如 @Lazy(true) 通过在 Bean 方法定义处标注 @lazy 指定

你可能感兴趣的:(Spring)