Spring IoC浅析

 

对象的三种注入方式:

1、接口注入:接口注入因为强制对象实现不必要的接口,带有侵入性 

2、构造注入:构造注入在同类型构造参数列表的情况下处理和维护会很困难,构造方法不能被继承且无法设置默认值 

3、setter方法注入:setter注入侵入性低,缺点是无法在对象构造后马上使用

 

 

Spring两种类型容器:

BeanFactory:基础类型IoC容器,提供完整的IoC服务,默认采用延迟加载策略,只有当客户端对象访问容器内对象时才会对客户端对象进行注入操作,容器启动较快,需要资源较少

ApplicationContext:在BeanFactory基础上构建,提供里事件发布、国际化等其它高级特性,在容器启动后默认全部初始化(Singleton)并绑定完成,相对于BeanFactory,启动时需要更对的资源

 

BeanFacotry和ApplicationContext继承关系如图:

 

BeanFactory作为Spring提供的基本IoC容器,可以完成对象的注册和对象间依赖关系的绑定,BeanFacotry接口有如下声明

public interface BeanFactory {
	/**
	 * 
	 */
	String FACTORY_BEAN_PREFIX = "&";
	/**
	 * 根据名称获取对象
	 */
	Object getBean(String name) throws BeansException;
	/**
	 * 根据名称和Class获取对象
	 */
	<T> T getBean(String name, Class<T> requiredType) throws BeansException;
	/**
	 * 根据Class获取对象
	 */
	<T> T getBean(Class<T> requiredType) throws BeansException;
	/**
	 * 根据名称和参数获取对象
	 */
	Object getBean(String name, Object... args) throws BeansException;
	/**
	 * 查询对象是否存在与容器中
	 */
	boolean containsBean(String name);
	/**
	 * 查询对象是否是单例
	 */
	boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
	/**
	 * 查询对象是否是原型
	 */
	boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
	/**
	 * 是否匹配名称和类型
	 */
	boolean isTypeMatch(String name, Class<?> targetType) throws NoSuchBeanDefinitionException;
	/**
	 * 根据名称获得Class
	 */
	Class<?> getType(String name) throws NoSuchBeanDefinitionException;
	/**
	 * 获取别名
	 */
	String[] getAliases(String name);
}

原始方式:业务对象需要自己去pull所依赖的业务对象

IoC方式:业务对象需要依赖什么让BeanFacotry push过来

 

BeanFactory的注册与依赖绑定方式,编码实现

//POJO对象
public class Person {
	private String name;
	private Integer age;

	public Person() {
		super();
	}
	public Person( String name ) {
		super();
		this.name = name;
	}
	@Override
	public String toString() {
		return "Person [name=" + name + ", age=" + age + "]";
	}
	public Integer getAge() {
		return age;
	}
	public void setAge( Integer age ) {
		this.age = age;
	}
}
//BeanFactory注册/绑定测试
	@Test
	public void testDefaultListableBeanFactory() {
		//BeanFactotry定义了如何访问容器内管理的bean的API,具体实现类负责bean的注册及管理
		DefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory();
		BeanFactory container = bindViaCode(beanRegistry);
		Person person = (Person) container.getBean("person");
		System.err.println(person);
	}

	public BeanFactory bindViaCode( BeanDefinitionRegistry registry ) {
	//创建Person对应的BeanDefinition,每一个受管理的对象在容器中都会有一个BeanDefinition实例与之对应
		//就像Java中每一个对象都是Class的实例一样,BeanDefinition实例负责保存对象的基本信息
		//如:对象的Class类型、是否是抽象类、构造方法参数、类中属性
		AbstractBeanDefinition personDefinition = new RootBeanDefinition(Person.class);
		//将Person对应的BeanDefinition注册到容器中,容器可以通过BeanDefinition中保存的信息构建Person
		registry.registerBeanDefinition("person", personDefinition);
		//通过构造方式为Person注入name属性
		ConstructorArgumentValues argumentValues = new ConstructorArgumentValues();
		argumentValues.addIndexedArgumentValue(0, "AAA");
		personDefinition.setConstructorArgumentValues(argumentValues);
		//通过setter方式为Person注入age注入
		MutablePropertyValues propertyValues = new MutablePropertyValues();
		propertyValues.addPropertyValue(new PropertyValue("age", 11));
		personDefinition.setPropertyValues(propertyValues);
		return (BeanFactory) registry;
	}

 BeanFacotry、BeanDefinitionRegistry、DefaultListableBeanFactory关系如图

 

除了编码方式管理bean,SpringIoC容器支持Properties和XML配置文件方式管理bean的注册和依赖绑定,根据不同的BeanDefinitionReader(解析配置文件、装配BeanDefinition)实现类读取配置文件并映射到BeanDefinition,然后将映射后的BeanDefinition注册到BeanDefinitionRegistry中。PropertiesBeanDefinitionReader是读取properties文件,XmlBeanDefinitionReader读取xml配置文件。

在Spring2.5版本中增加了注解的方式,但需要JDK1.5以上版本才能支持,使用注解还需要配置包扫描

<context:component-scan base-package="..." />

 具体xml配置见Spring part 1:IoC和DI有详细的配置

Spring容器自动绑定方式:

no:容器默认方式,不采用任何形式的绑定,完全依赖手工明确配置

byName:类中声明的实例变量与xml配置文件中<bean>标签的id对应

byType:根据bean的类型寻找依赖对象, 根据byType装配时如果找到多个时无法区分具体需要哪种类型

constructor:byName和byType都是针对property的自动绑定,constructor是针对构造方法参数进行绑定,也是byType模式,找多个类型时也无法区分具体使用哪个

autodetect:对象拥有无参数的构造方法,容器会优先使用byType方式,否则使用contructor方式,如果通过constructor后还有对象没有被绑定,会对剩余对象属性进行byType方式绑定

注意事项:1、手工绑定覆盖自动绑定 2、自动绑定对String、Classes、数组、基本类型无效

 

Spring容器Bean的Scope:

singleton:在容器内保证singleton的bean只存在一个 共享实例,与Gof Singleton模式不同,该模式保证在同一个Classload中只能有一个bean实例

prototype:容器在接到该对象类型请求时会每次都重新创建新的返回给请求方,请求方需自己负责该对象后续的声明周期管理工作。

request:XmlWebApplicationContext会为每个http请求创建一个新的ReqeustProcessor供当前请求使用,请求结束后该对象声明周期结束

session:容器会为每个独立的session创建术语自己的对象

global:protlet中使用,在servlet中会被作为普通session处理

 

BeanFactoryPostProcessor实现

Spring容器加载bean的两个阶段:

1、容器启动阶段:加载配置文件、分析配置信息、装备到BeanDefinition、其它后处理

2、Bean实例化阶段:实例化对象、装配依赖、声明周期回调、对象其它处理、注册回调接口

Spring提供了一种叫做BeanFactoryPostProcessor的容器扩展机制,允许在容器在启动阶段的最后,对容器内的BeanDefinition所保存的信息进行修改。

PropertyPlaceholderConfigurer:

允许在XML文件中使用Placeholder,并将这些Placeholder所代表的资源单独配置到properties文件中进行加载

	<bean id="placeholder" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
		<property name="locations">
			<list>
				<value>classpath:properties/jdbc.properties</value>
				<value>classpath:properties/config.properties</value>
			</list>
		</property>
	</bean>

当BeanFactory在第一阶段加载完所遇配置信息后,BeanFacotry中保存的对象的信息还是占位符形式,如${jdbc.url}、${jdbc.driver},当PropertyPlaceholderConfigurer作为BeanFactoryPostProcessor被应用后,会使用properties中的信息替换BeanDefinition中的占位符所表示的属性值,在Bean实例化第二阶段完成替换。PropertyPlaceholderConfigurer不仅会从配置的properties文件中加载配置,同时还会检查Java的Properties,这也是PropertyPlaceholderConfigurer默认的加载方式

PropertyOverrideConfigurer:

对容器中任何你想处理的bean定义的property信息进行覆盖替换

CustomEditorConfigurer:

PropertyPlaceholderConfigurer、PropertyOverrideConfigurer这两个BeanFactoryPostPorcessor都是通过对BeanDefinition中的数据进行变更达到某种目的,CustomEditorConfigurer对BeanDefinition没有任何修改。不管对象是什么类型在通过xml或properties配置文件表示时都是String类型,需要通过程序把String类型转换成相对应的类型,要想完成String到具体类型的转换,都需要具体的转换规则,CustomEditorConfigurer帮我们传达了这种信息。

JDK中String内部使用java.beans.PropertyEditor来帮助String类进行转换工作,只要为每种类型都提供特定的PropertyEditor,就可以根据对象的类型取得String到特定对象的类型。

Spring也提供了部分PropertyEditor:

StringArrayPropertyEditor:将CVS格式的字符串根据“,”分割为String[],类似的还有ByteArrayPropertyEditor、CharArrayPropertyEditor

ClassEditor:根据String类型的class名称直接将起转换为Class对象,相当于Class.forName(String)

FileEditor:对java.io.File类型的支持,对资源进行定位的还有InputStreamEditor、 URLEditor

LocalEditor:针对java.lang.Local类型的PropertyEditor

PatternEditor:针对与正则的PropertyEditor

CustomDateEditor:针对与日期的PropertyEditor

以上这些PropertyEditor会默认被容器加载,如果我们想自定义PropertyEditor就需要通过CustomEditorConfigurer告知容器。

Spring2.0前提倡使用CustomEditorConfigurer的customEditors属性来配置

	<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
		<property name="customEditors">
			<map>
				<entry key="java.util.Date" value-ref="自定义PropertyEditor"></entry>
			</map>
		</property>
	</bean>

 Srping2.0后提倡使用propertyEditorRegistrars属性配置

	<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
		<property name="propertyEditorRegistrars">
			<list>
				<ref bean="自定义PropertyEditor"/>
			</list>
		</property>
	</bean>

 

Bean的实例化过程如图

1、BeanWrapper

Spring采用策略设计模式为Bean实例化,通常有两种方式反射和CGLIB动态字节码。InstantiationStrategy定义了实例化Bean策略的接口,SimpleInstantiationStrategy实现类可以通过反射实例化bean,但不支持方法注入,CglibSubclassingInstantiationStrategy继承SimpleInstantiationStrategy通过CGLIB的动态字节码生成对象,但返回的是BeanWarrper包裹后的对象。

对比如下用BeanWrapper和Java反射分别操作对象的的方式,BeanWrapper更简单些,省去了很多异常处理的代码,Spring默认使用CglibSubclassingInstantiationStrategy。

		//BeanWrapper操作
		Object object = Class.forName("bean.Person").newInstance();
		BeanWrapper wrapper = new BeanWrapperImpl(object);
		wrapper.setPropertyValue("name", "AAA");
		wrapper.setPropertyValue("age", 11);
		System.out.println(wrapper.getWrappedInstance() instanceof Person);
		System.out.println(wrapper.getPropertyValue("name"));
		System.out.println(wrapper.getPropertyValue("age"));
		//反射操作
		Object object = Class.forName("bean.Person").newInstance();
		Class clazz = object.getClass();
		Field nameFiled = clazz.getDeclaredField("name");
		Field ageFiled = clazz.getDeclaredField("age");
		nameFiled.set(object, "aa");
		ageFiled.set(object, 11);
		System.out.println(nameFiled.get(object));
		System.out.println(ageFiled.get(object));
 2、Aware接口

 对象实例化完成并且设置相关属性及依赖后,Spring容器会检查该对象是否实现XXXAware接口,如果有将这些XXXAware接口中定义的依赖注入给当前对象

BeanNameAware:注入自己对象实例的Bean定义对应的beanName设置到当前对象实例

BeanClassLoaderAware:注入当前bean的Classloader。默认加载org.springframework.util.ClassUtils类的Classloader

BeanFactoryAware:BeanFactory容器会将自身注入到bean中

3、BeanPostProcessor

BeanPostProcessor用于容器对象实例化对象阶段,而BeanFactoryPostProcessor用于容器启动阶段,BeanFactoryPostProcessor处理的是BeanDefinition,BeanPostProcessor处理的是实例对象,BeanPostProcessor接口只有两个方法,在Bena实例化之前进行处理和Bean实例化之后进行处理

public interface BeanPostProcessor {
	Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;

	Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}

 通过BeanPostProcessor给对象注入属性

//标记接口
public interface IStringMark {
	public String getString();
	public void setString( String string );
}
//实现
@Service
public class StringServiceImpl implements IStringMark {
	private String string;

	public String getString() {
		return string;
	}

	public void setString( String string ) {
		this.string = string;
	}
}
//BeanPostProcessor
@Component
public class StringBeanPostProcessor implements BeanPostProcessor {
	public Object postProcessBeforeInitialization( Object bean, String beanName ) throws BeansException {
		if ( bean instanceof IStringMark ) {
			IStringMark strMark = (IStringMark) bean;
			strMark.setString("AAAA");
		}
		return bean;
	}

	public Object postProcessAfterInitialization( Object bean, String beanName ) throws BeansException {
		return bean;
	}

}

SpringAOP更多的使用BeanPostProcessor生成代理对象,与ApplicationContext相关的Aware接口也是用BeanPostProcessor方式处理注入 

 4、InitializingBean和init-method

InitializingBean接口是个容器内部使用的一个对象声明周期标记接口,只有一个方法,执行时间点在容器实例化对象过程中调用BeanPostProcessor前置处理后会检测是否实现了InitializingBean接口,如果实现了该接口就调用afterPropertiesSet()。

public interface InitializingBean {
	void afterPropertiesSet() throws Exception;
}

如果业务对象实现该接口则显得Spring容器对业务对象具有侵入性,Spring提供了另外一种方式,XML配置<beam>标签中的init-method属性

 

统一资源加载策略

Spring使用Resource作为所有资源的抽象和访问接口,ResourceLoader作为资源的定位、加载。

Resource接口可以根据资源的不同类型或资源的不同位置给出不同的具体实现,这些实现类在org.springframework.core.io包下,也可以通过继承AbstractResource类来实现自定义的Resource接口。

ByteArrayResource:将byte数组提供的数据作为资源进行封装,比如功过InputStream访问的资源

ClassPathResource:从Java的Classpath中加载资源并进行封装,可以使用置顶的ClassLoader

FileSystemResource:对java.io.File类进行封装,以文件或URL形式对资源进行访问,只要能跟File打交道的资源几乎都能和FileSystemResource打交道

UrlResource:通过url进行资源的查找定位

InputStreamResource:将给定的InputStream视为一种资源

 

ResourceLoader接口可以查找和定位资源,相当于资源定位器,该接口有个默认实现DefaultResourceLoader

 

public interface ResourceLoader {
	String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
	Resource getResource(String location);
	ClassLoader getClassLoader();
}


 

ApplicationContext也继承了ResourcePatternResolver,所以间接实现了ResourceLoader接口,所以ApplicationContext实际也是一个ResourceLoader,这就是Spring内容易资源加载的逻辑。从实现的角度来看AbstractApplicationContext extends DefaultResourceLoader。


 有了这种实现ApplicatonContext加载任何Spring支持的Resource类型,当一个Bean需要ResourceLoader是可以使用Spring提供的Aware接口,比如ResourceLoaderAware、ApplicatonContextAware。

 

国际化信息支持

JavaSE提供的国际化支持使用Local代表不同国家和地区,比如代表中国

Locale.CHINA;
Locale.CHINESE;
Locale china = new Locale("zh", "CN");==Locale.CHINA;

ResourceBundle用于加载国家化properties文件

 

猜你喜欢

转载自mvplee.iteye.com/blog/2290028