【Spring学习笔记】Spring框架的IoC容器学习笔记

一、IoC容器概述

        IoC容器是一种面向接口编程,将接口的具体实现和对象的组装延后至编译后,并将这些配置从代码提取到配置文件中的一种编程方式。IoC容器通过JAVA提供的反射机制根据配置文件提供的信息选择实现类并装配实例化对象。

        IoC容器允许这样一种编程模式:首先定义一个接口,并且通过接口实现两个不同实现方式的实现类(比如对于存储数据这个接口,实现一个用操作系统文件方式存储的类和一个用数据库存储的类),然后某个业务类使用这个接口的实现来完成某项任务。在通常使用的编程模式中此时需要在业务类中创建接口的某个实现(即使用哪个实现类在代码中,并且在编译前必须决定),而IoC容器则将此过程延后,并将其提取到一个xml配置文件中(也可以使用注解的模式),如果在应用发布后需要修改某个接口的实现,只需要修改配置文件而无需对代码进行修改、重新编译和重新发布。

        IoC容器提供了这样的一种理念:按照清晰的功能模块来构造应用,用接口来描述模块功能,同个模块功能提供针对不同应用环境的不同实现,在部署时按照实际应用环境通过配置文件重新组装、优化应用。

二、IoC容器的基本底层实现

        IoC容器的底层实现主要依赖于JAVA的反射机制。通过反射机制和配置文件,容器载入相应的.class文件,实例化对象。使用java.reflect.Method类调用相应的set方法来设置对象属性。如下的配置文件:




	

1.容器通过某个XML解析器,解析配置文件;

2.在节点处,解析出class=com.sample.bean.Car属性,使用ClassLoader载入对应的.class文件,获得一个Class类型的对象;

3.通过Class.getDeclaredContructor成员函数获取Constructor类型的对象,这里没有配置子节点,所以获取使用空参数的构造函数对象;

4.使用Constructor.newInstance成员函数实例化com.sample.bean.Car对象;

5.解析p命名空间下的所有属性,并通过Class.getDeclaredMethod方法获取相应setXXX方法对应的Method对象,并且通过调用Method.invoke方法来实际执行Car.setXxx方法设置属性;

6.解析init-method属性,通过Class.getDeclaredMethod和属性的值来获得对应Car.init的Method对象,执行Method.invoke,实际效果为执行Car.init;

7.将生成完成的对象放入到IoC的Bean缓冲池中;

8.当IoC容器被释放时,通过解析destory-method属性,通过Class.getDeclaredMethod和属性的值来获得对应Car.destory的Method对象,执行Method.invoke,实际效果为执行Car.destory,完成相关资源的释放;

        通过以下代码即可获得配置文件中id为car的Car类实例对象,通过打印相关信息我们可以看到相应的属性已经被设置为配置文件中我们配置好的值。

public class MainEntry
{
	public static void main(String[] args)
	{
		ClassPathXmlApplicationContext actx = new ClassPathXmlApplicationContext("applicationContext.xml");
		Car car = (Car)actx.getBean("car");
		System.out.println(car);
		actx.close();
	}
}

三、相关XML文件概念

        在前面的配置文件中




	


在beans节点中,有xmlns和xsi:schemaLocation两个属性,这两个属性的语法意义如下文

1.xmlns 命名空间定义

        定义一个命名空间的语法为xmlns:namespace-prefix="namespaceURI",例如以上xml文件中的xmlns:p="http://www.springframework.org/schema/p"。namespace-prefix是命名空间的缩写,而后面的namespaceURI是命名空间的全称。在后面使用命名空间时只需要在节点或者属性的名字前加上“namesapce-prefix:”即可。作为命名空间全称的namespaceURI在形式上虽然是一个URI,但是这只是命名空间名字的一种约定俗成的命名方法,并不作为查找schema的路径或其他任何用处,这样做的好处只是为了有效避免命名空间的名字冲突。xml当中命名空间的用处和C++的名字空间以及java的包的用处是相同的,即避免名字冲突的作用。

        在这里我们看到有一个命名空间的定义省略了namespace-prefix部分,即xmlns="http://www.springframework.org/schema/beans",这定义了一个默认命名空间的名字,也就是说,下面不用省略namespace-prefix:nodeName标识的节点,如上面xml中的bean节点使用的是以上这个默认命名空间。

2.xsi:schemaLocation命名空间对应schema文件的URI

        这个属性定义了一个命名空间使用的schema文件路径,此属性告诉xml解析器当遇到命名空间时去哪里查找对应的schema文件。这里定义的格式为namespaceURI schemaFileURI。namespaceURI和schemaFileURI之间用空格、换行符隔开。

四、IoC容器的配置(XML和注解模式)

        Spring通过一个配置文件或者注解的方式来描述Bean(生命周期和属性值)和Bean之间的依赖关系,利用JAVA的反射机制实现Bean和Bean之间的依赖关系,并且通过BeanFactory或者ApplicationContext的实例将Bean提供给应用使用。

1.基本属性的配置

        基本属性是指JAVA中的基本数据类型,例如整型、浮点、字符等等。

a.XML配置

        在XML中我们使用p命名空间的属性来配置基本属性,配置的格式为p:<属性名>=值。实例如下






       在这个XML配置文件的实例中p:companyName会由容器使用JAVA的反射机制自动调用Car.setCompanyName("Chevrolet")这个访问器函数来实现属性值的设置。注意在xmlns和xsi:schemaLocation当中对于p命名空间的设置。

2.引用属性的注入

        引用属性是指,一个类当中的一个字段需要应用类一个类的一个实例,例如下面的代码

public class Car
{
	private String	companyName;
	private String	carName;
	private String	color;
	private int		maxSpeed;
        
        //set/geter
}

public class Person
{
	private Car car;

        //set/geter
}
在类Person当中有一个引用了Car实例的引用属性,在对这种类型的配置时有两种配置方法:

a.xml配置方法

使用xml配置时,我们依旧使用p命名空间,只是格式与基本属性配置略有不同,我们使用p:<属性名>-ref,例如:





			


b.使用annotation配置

        使用annotation配置Bean时,首先要通过一个注解告知容器某个类是一个Bean,用于告知容器某个类是一个Bean的注解一共有四个个:@Compoent、@Service、@Controller和@Repository。这四个注解从功能上讲完全一样,只是用来区分所标识的类在应用中所属的角色

         @Compoent:表示类在应用中属于领域角色,是组件类

         @Repository:表示类在应用中属于DAO层

         @Service:Service业务层的类

         @Controller:控制层的类

        在使用相应的注解标识完类为Bean以后就可以开始注入Bean属性的值了,默认情况下Spring利用类名来选择注入的引用实例,此时只需要使用@Autowired来标识属性即可。当对应的类在容器中配置有多个实例时需要配合使用@Qualifier和Bean的id属性来做限制。

        @Autowired中有一个可选的参数required。这个参数的作用是:当标注为Autowired的属性如果在容器中找不到相关的Bean时,如果设置为true时则抛出异常,否则设置为null。

        @Autowired可以设置在属性声明上,也可以设置在set访问器函数上。具体实例如下

@Component
@Scope(value="singleton")
public class Person
{
	@Autowired(required=false)
	@Qualifier("car")
	private Car car;
}

        在使用annotation进行属性注入时,我们需要在主配置文件中通过来告知Spring容器去哪里扫描那些被注解配置为Bean的类






3.容器属性的配置

a.xml配置

List容器:


	
		
			shopping
			music
		
	
Set容器


	
		
			shopping
			music
		
	
Map容器


	
		
			
				JAVAHOME
				jcommon
			
			
				ACE
				common
			
		
	
Property容器,该容器与Map容器相同,区别只是在该容器当中key必须为字符串,配置时也与Map容器完全相同,只需要把节点名字改成即可

b.使用annotation配置

使用annotation来配置集合类时,依旧使用@Autowired注解,注解的使用方式与配置引用属性时完全相同,需要注意的是,凡是容器模板参数中类型参数T的类型实例以及子类的实例均会被配置进容器中。

4.初始化和销毁方法

        Bean的初始化方法在Bean被创建并配置完所有属性后被调用,用于进行一些初始化操作,而销毁方法只会在Bean被配置为singleton生命周期时才会发生,他发生在Spring容器被销毁,Bean缓存清空时,他用于资源的回收和释放。

        配置这两个方法在xml中使用对应可以使用@PostConstruct和@PreDestory两个注解来配置。

5.继承

        在使用XML文件进行配置时,一个类可以被配置为多个id的实例,在这些实例中有些公共的属性会是相同的,因此,我们可以使用类似类继承的关系来描述这些Bean。要使用这个功能只需要在子Bean当中使用parent属性标识父Bean即可,子Bean可以覆盖父Bean当中属性的设置。另外,如果一个Bean要被设置为纯虚的父Bean的话只需要设置属性abstract="true"即可,这样他只能被继承而不能在应用中获取此Bean的实例。

6.依赖

        当一个类的创建、初始化依赖于另一个类的存在时,即当获取这个类的实例的实例时,另一个类的实例必须首先已经存在。此时依赖的类可以通过设置depends-on="beanId"即可,Spring容器保证在创建该Bean之前先创建id为beanId的Bean。

五、Bean的生命周期

        Spring当中的Bean生命周期可以用下面的图示来描述


1.当调用者通过getBean(beanName)向容器请求某一个Bean 时,如果容器注册org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor 接口,在实例化Bean 之前,将调用接口的postProcessBeforeInstantiation()方法;
2.根据配置情况调用Bean 构造函数或工厂方法实例化Bean;
3.如果容器注册了InstantiationAwareBeanPostProcessor 接口,在实例化Bean 之后,调用该接口的postProcessAfterInstantiation()方法,可在这里对已经实例化的对象进行一些“梳妆打扮”;
4.如果Bean 配置了属性信息,容器在这一步着手将配置值设置到Bean 对应的属性中,不过在设置每个属性之前将先调用InstantiationAwareBeanPostProcessor 接口的
postProcessPropertyValues()方法;
5.调用Bean 的属性设置方法设置属性值;
6.如果Bean 实现了org.springframework.beans.factory.BeanNameAware 接口,将调用setBeanName()接口方法,将配置文件中该Bean 对应的名称设置到Bean 中;
7.如果Bean 实现了org.springframework.beans.factory.BeanFactoryAware 接口,将调用 setBeanFactory()接口方法,将BeanFactory 容器实例设置到Bean 中;
8.如果BeanFactory 装配了org.springframework.beans.factory.config.BeanPostProcessor后处理器,将调用BeanPostProcessor 的Object postProcessBeforeInitialization(Object bean,String beanName)接口方法对Bean 进行加工操作。其中入参bean 是当前正在处理的Bean,而beanName 是当前Bean 的配置名,返回的对象为加工处理后的Bean。用户可以使用该方法对某些Bean 进行特殊的处理,甚至改变Bean 的行为,BeanPostProcessor 在Spring 框架中占有重要的地位,为容器提供对Bean 进行后续加工处理的切入点,Spring 容器所提供的各种“神奇功能”(如AOP,动态代理等)都通过BeanPostProcessor 实施;
9.如果Bean 实现了InitializingBean 的接口,将调用接口的afterPropertiesSet()方法;
10.如果在通过init-method 属性定义了初始化方法,将执行这个方法;
11.BeanPostProcessor 后处理器定义了两个方法:其一是postProcessBeforeInitialization()在第8 步调用;其二是Object postProcessAfterInitialization(Object bean, String beanName)方法,这个方法在此时调用,容器再次获得对Bean 进行加工处理的机会;
12.如果在中指定Bean 的作用范围为scope=“prototype”,将Bean 返回给调用者,调用者负责Bean 后续生命的管理,Spring 不再管理这个Bean 的生命周期。如果作用范围设置为scope=“singleton”,则将Bean 放入到Spring IoC 容器的缓存池中,并将Bean引用返回给调用者,Spring 继续对这些Bean 进行后续的生命管理;
13.对于scope=“singleton”的Bean,当容器关闭时,将触发Spring 对Bean 的后续生命周期的管理工作,首先如果Bean 实现了DisposableBean 接口,则将调用接口的
afterPropertiesSet()方法,可以在此编写释放资源、记录日志等操作;
14.对于scope=“singleton”的Bean,如果通过的destroy-method 属性指定了Bean的销毁方法,Spring 将执行Bean 的这个方法,完成Bean 资源的释放等操作。

六、使用属性文件配置IoC容器

        当我们使用一个Bean来保存连接数据库的相关信息时,我们会在配置的xml文件中写入jdbc驱动和相关的连接URI,当实际部署应用时很有可能因为实际数据服务器部署方式的不同需要修改这个配置文件。因为IoC容器的配置文件往往会根据模块分散在各个包的路径下,此时会给项目实施人员带来诸多不便。此时较好的解决方案是在一个特定的位置使用一个属性文件来配置这些信息,然后在IoC的配置文件中指定属性文件的扫描位置,然后使用属性来装配IoC容器中的Bean。使用属性文件的实际的IoC容器配置文件如下:



 
	
 	
	
	
1.使用节点来指定属性文件的位置,location属性指定了属性文件的路径,file-encoding指定了属性文件的编码格式。这里需要导入context名字空间。

2.在装配bean的属性时,使用${propertyName}的方式来引用属性。特别需要注意的时,需要保留""的使用

七、IoC容器的i18n(国际化)配置

        国际化就是使应用的一些字符根据应用所部属的计算机所属位置显示相应的语言。要确定一个国际化信息需要两个信息:语言信息和地区信息,语言信息根据ISO-639标准确定,地区信息根据ISO-3166标准确定。在Spring当中分别使用两组字母来确定国际化信息,通常是在属性文件名的最后加上后缀"-语言信息-地区信息",比如一个属性文件名字为global.properties,那么表示简体中文的属性文件会被命名为global-zh-CN.properties,台湾繁体中文会被命名为global-zh-TW.properties。

        要在Spring中使用国际化配置的功能,首先要按照国际化信息编写对应的properties文件。例如,我们要支持简体中文和英文两种国际化信息就需要配置以下的两个properties文件,并放在同一个路径下:

//global-en-US.properties
label="Welcome to use spring"
//global-zh-CN.properties
label="欢迎使用Spring"

        特别需要注意的是,非英文的配置文件中的字符必须使用UTF-8转义码的格式来进行保存,我在这里使用了MyEclipse的转码插件

        然后,在配置文件中增加对于org.springframework.context.support.ReloadableResourceBundleMessageSource类的装配信息


 		
 			
 				properties/global
 			
 		
 	
       这个Bean的id必须为messageSource。在属性的basenames中使用一个数组来配置,这里的值为包含国际化信息的属性文件路径前缀,即不包含语言信息、地区信息后缀的属性文件路径。

       最后是使用这些国际化信息配置过的值,这里需要使用ApplicationContext.getMessage方法,具体代码如下

public static void main(String[] args)
{
	ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
	System.out.println(ctx.getMessage("label",null,Locale.CHINA));
}
ApplicationContext.getMessage的第一个参数为国际化属性文件中的键名字,第三个参数是一个用Locale表示的国际化位置信息。

八、IoC容器事件

        Ioc容器的事件系统与其他框架的事件系统构成基本相同,事件系统由事件、监听器和发送者三者构成。

        作为IoC容器的事件,必须从ApplicationEvent继承,并且重写构造函数(构造函数的第一个参数,也是基类构造函数的唯一参数标识了事件的发送者),同时重写一些相应数据的get/set方法

public class BuyCarEvent extends ApplicationEvent
{
	private Car	car;
	
	public BuyCarEvent(Boss boss,Car car)
	{
		super(boss);
		this.car = car;
	}
	
	public Car getCar()
	{
		return car;
	}
}
         作为监听器,必须实现ApplicationEvent接口,同时实现onApplicationEvent(T event)方法,从而实现对事件的处理

public class BuyCarListener implements ApplicationListener
{
	@Override
	public void onApplicationEvent(BuyCarEvent event)
	{
		Boss boss = (Boss)event.getSource();
		Car car = event.getCar();
		System.out.println(boss.getName() + " buy a car" + car);
	}
	
}
         作为事件的发送者必须继承自ApplicationContextAware,通过实现setApplicationContext方法来获取容器使用的ApplicationContext对象,同时使用这个对象来发出事件

public class Boss implements ApplicationContextAware
{
	private String 	name;
	private Car		car;
	private ApplicationContext ctx;
	
	public Boss()
	{
		super();
		// TODO Auto-generated constructor stub
	}
	
	public void BuyCar()
	{
		ctx.publishEvent(new BuyCarEvent(this,car));
	}

	public String getName()
	{
		return name;
	}

	public void setName(String name)
	{
		this.name = name;
	}

	public Car getCar()
	{
		return car;
	}

	public void setCar(Car car)
	{
		this.car = car;
	}
	
	@Override
	public String toString()
	{
		String str = "Boss[" + name + "," + car + "]";
		return str;
	}

	@Override
	public void setApplicationContext(ApplicationContext applicationContext)
			throws BeansException
	{
		ctx = applicationContext;
	}
}


你可能感兴趣的:(JAVA)