spring-ioc-native&容器扩展点

原创
2016/08/15 16:11
阅读数 179

章节地址 ###7.6自定义一个bean的生命 (nature) ####7.6.1 lifecycle callbacks(生命周期回调) 与容器的bean的生命周期管理相关,你可用实现spring的InitializingBean和DisposableBean接口.对于前者,容器调用afterPropertiesSet()方法,对于后者,容器调用destroy()方法,允许你在bean的初始化和销毁中添加一些行为.

一般而来,spring框架使用BeanPostProcessor实现来处理任何它能发现的回调接口并调用正确的方法.如果你要自定义spring没有提供开箱即用的功能或其他生命周期行为,你可以自己实现一个 BeanPostProcessor接口.对于更多信息,查看Section 5.8,"Container Extension Points".

另外对于初始化和销毁回调,spring管理的对象也可以实现Lifecycle接口,这样这些对象可以作为容器生命周期的驱动参与启动和关闭过程.

生命周期回调会在这节描述

####初始回调 org.springframework.beans.factory.IntializingBean接口允许一个bean在bean的所有属性被容器设置之后参与初始化工作.InitializingBean接口指定了一个单独的方法:

	void afterPropertiesSet() throws Exception;

提醒一下,你没有必要使用InitializingBean接口,因为你没有必要同spring代码耦合.另外,你也可以使用@PostConstruct注解或者指定一个POJO初始化方法.这xml-based 的配置元数据下,你可以使用init-method属性来指定方法的名称,但该方法必须是无参的.在java配置下,你可以使用@Bean的initMethod属性.详情查看Recieving lifecycle callbacks. 例如:

	<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
	
           public class ExampleBean {

		public void init() {
			// do some initialization work
		}

	}

他等同于

	<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>

	public class AnotherExampleBean implements InitializingBean {

		public void afterPropertiesSet() {
			// do some initialization work
		}

	}

但上一个例子不会和spring耦合. ###Destruction callbacks (销毁回调) 实现org.springframework.beans.factory.DisposableBean接口可以允许一个bean在持有它的容器在销毁时得到一个回调.DisposableBean接口只申明了一个方法.

	void destroy() throws Exception;

你不需要使用DisposableBean回调接口,因为你没必要同spring代码耦合.另外,使用@PreDestroy注解或制定一个被该bean定义支持的泛型方法.在基于xml的配置元数据中,你可以使用<bean/>定义里的destroy-method属性.在java配置中,你可以使用@Bean的destroyMethod属性.查看Recieving lifecycle callbacks;

	<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
	public class ExampleBean {

		public void cleanup() {
			// do some destruction work (like releasing pooled connections)
		}

	}

等同于下面

	<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>

	public class AnotherExampleBean implements DisposableBean {

		public void destroy() {
			// do some destruction work (like releasing pooled connections)
		}

	}		

但上例无spring代码耦合.

  • 注意bean元素的destroy-method属性可以指明一个特别值(inferred),它可以指明spring去自动检测在特定的class下的一个公共的close or shutdown方法.(任何实现了java.lang.AutoCloseable 或者java.io.Closable 的类都可以匹配).这个特定值(inferred)还可以在<beans>标签的default-destroy-method属性上使用,使其可以应用到整个bean集合.记住这个java配置的默认行为.

####默认的初始化和销毁方法(Default initialization and destroy methods)

当你编写初始化和销毁方法回调而不使用spring特定的InitializingBean和DisposableBean回调接口时,一般而言,你需要将这些方法名为init,initialze,dispose等.一个项目中关于生命周期回调的方法名称最好标准化,这样所有的开发者可以使用它们并保持一致.

你可以配置spring容器使其去查找每一个bean上的名字相似的初始化和销毁回调方法.这意味着你作为一个应用开发者,可以编写自己的应用classes,并使用一个叫inti()的初始化回调方法,而不需要在再每个类定义上配置init-method="init".当这些bean被创建时,spring容器会调用这些方法(与之前提到的生命周期契约一致).这个功能强制要求初始化和销毁方法有一个同意的命名规范.

假设你的初始化回到方法都命名为init(),销毁回调方法命名为destroy(),你的如下组装你的类: public class DefaultBlogService implements BlogService {

		private BlogDao blogDao;

		public void setBlogDao(BlogDao blogDao) {
			this.blogDao = blogDao;
		}

		// this is (unsurprisingly) the initialization callback method
		public void init() {
			if (this.blogDao == null) {
				throw new IllegalStateException("The [blogDao] property must be set.");
			}
		}

	}

xml定义如下: <beans default-init-method="init">

		<bean id="blogService" class="com.foo.DefaultBlogService">
			<property name="blogDao" ref="blogDao" />
		</bean>

	</beans>

在高级的<beans/>元素里的default-init-method属性,可以使spring容器在Beans里查找一个叫init的方法来作为初始化方法回调.当一个bean被创建或装配,如果这个bean有该方法,它就会在合适的时间调用.

你可以在beans元素里通过default-destroy-method方法来配置销毁方法.

当已存在的bean有命名约定变种的回调方法时,你可以用<bean>属性里的init-method和destroy-method方法来指定他们的方法名称.

spring容器保证每一个bean被所有的依赖组装完之后,其配置的初始化回调可以被及时的调用.因此这些方法被原生的bean的引用调用,这意味着这意味着AOP拦截器没有对这些bean起作用.一个目标被完全创建,然后一个AOP代理以及相关的拦截器链就会被申请.如果这个目标bean和代理被定义为分离,你的代码甚至可以绕过代理,直接访问目标bean.因此,并不总是要用拦截器来调用初始化方法,因为这样的话会使目标类的生命周期和它的代理/拦截器耦合并在你的代码同原生目标类交互时留下奇怪的代码.

####Combining lifecycle mechanisms(组合生命周期机制) 在spring 2.5中,你有三个选项来控制生命周期行为:InitializingBean和DisposableBean回调接口,自定义init()和destroy()方法,@PostConstruct和@PreDestroy注解.你可以使用这些机制来控制一个特定的bean.

  • 如果给一个bean配置混合生命周期机制,且每个机制都配置了不同的方法,然后这些方法会依次被执行.

但是,如果配置了相同的方法名称.例如,对于一个叫init的初始化方法,对于多个生命周期机制,这个方法只被执行依次,原因以后章节介绍.

同一个bean混合生命周期机制中的不同的初始化方法,会如下调用

  • @PostConstruct注解的方法
  • 在InitialzingBean回调接口定义的afterPropertiesSet()方法
  • 自定义的配置的init()方法

销毁方法同上:

  • 首先@PreDestroy注解的方法
  • 其次在DisposableBean回调接口中定义的destroy()方法
  • 自定义的destroy()方法

####启动和关闭回调(Startup and shutdown callbacks) Lifecycle接口定义了任何有自己生命周期需求的对象的必要的方法(例如开始和结束一些后台进程)

	public interface Lifecycle {

		void start();

		void stop();

		boolean isRunning();

	}

所有的被spring管理的对象能实现该接口.然后,当ApplicationContext接受到启动或停止标志,比如在运行时停止/重启场景,它会级联调用这些被上下文定义的生命周期实现.这个操作是通过代理LifecycleProcessor来实现.

	public interface LifecycleProcessor extends Lifecycle {

		void onRefresh();

		void onClose();

	}

注意,LifecycleProcessor自身实现了对Lifecycle接口的扩展.它添加了两个方法来对应上下文的刷新和关闭.

注: 要明白org.springframework.context.lifeCycle接口这是一个对启动/停止通知的显示契约,并不意味着它会在上下文刷新时自动重启.可以考虑使用实现org.springframework.context.SmartLifecycle来替代对一个bean的自动启动方法更好控制.另外,请明确,停止通知不会先于销毁.在常规的停止中,所有的生命周期bean会在销毁回调信号传播之前先接受停止信号.但是,在一个上下文生命周期的热启动或者终止刷新尝试,只有销毁方法被调用.

启动和关闭调用的顺序非常重要.如果两个对象间有依赖关系,那么依赖的一方要晚于它的依赖启动,并且要早于它的依赖停止.但是,有时直接依赖是不可知的.你可能只知道一些类的对象要比其他类的对象先启动.很多情况下,SmartLifecycle接口定义了其他选项,一个继承了Phased接口的名为getPhase()方法.(phase:阶段,层次)

	public interface Phased {

		int getPhase();

	}

	public interface SmartLifecycle extends Lifecycle, Phased {

		boolean isAutoStartup();

		void stop(Runnable callback);

	}

当启动时,最低阶段的对象先启动,当停止时,这个顺序相反.因此,当一个实现了SmartLifecycle接口的对象,通过getPhase()方法返回了Integer.MIN_VALUE,它将是最先启动和最后关闭的.反之,如果阶段值是Integer.MAX_VALUE,则表明该对象是最后启动并最先停止(因为它依赖其他运行的进程).当思考阶段值时,对于一些没有实现SmartLifecycle接口的对象来默认值非常重要,默认值是0.所以,任何负值都表明一个对象应该在标准组件之前启动(在他们之后停止),当值为正数则反之.

如你所见SmartLifecycle定义的停止方法接受一个回调.任何实现在该实现完整的停止一个进程时,必须回调的run()方法.这样对于保持同步非常有必要,因为LifecycleProcessor接口的默认实现,DefaultLifecycleProcessor需要等待各个阶段对象集合要调用的超时值.默认的各个阶段的超时值是30秒.你可以在上下文里定义一个名叫"lifecycleProcessor"的 bean重写默认的生命周期处理器.如果你只想改变超时时间,可以按下面的例子:

	<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
		<!-- timeout value in milliseconds -->
		<property name="timeoutPerShutdownPhase" value="10000"/>
	</bean>

如上所示,LifecycleProcessor接口也定义了刷新和关闭上下文的方法回调.当上下文被关闭时,如果其stop()方法被明确调用,这将会简化关闭的过程.'refresh'回调保证了SmartLifecycle beans的另一个功能.当上下文被刷新(在所有的对象都被实例化和初始化后),这个回调会被调用,而且此时默认的生命周期处理器会检查每个SmartLifecycle对象的isAutoStartup()方法返回的boolean值.如果值为"true",那么该对象将会在此时启动,而不是等待一个上下文明确的调用或自己的start()方法(不同于上下文刷新,对于一个标准的上下文实现来说,上下文启动不会自动发生).不同依赖关系间的阶段值决定他们的启动顺序,和上文描述的相同.

####如何优雅的在non-web应用中停止spring ioc容器(Shutting down the Spring IOC container gracefully in non-web applications)

这个部分只适用于非web应用.spring的基于web的上下文实现早已有了当相关web应用停止时,如何优雅关闭spring IOc容器上下文的代码.

如果你在非web应用环境中使用spring ioc容器;例如,在一个非客户端桌面环境中,你注册了一个JVM关闭的钩子.这样做可以保证一个优雅的关闭,调用你所有单例bean的销毁方法所有所有的资源会得到释放.当然,你必须一直正确的配置和实现这些销毁回调.

要注册一个关闭钩子,你可以调用在ConfigurableApplicationContext接口上申明的registerShutdownHook()方法.

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

	public final class Boot {

		public static void main(final String[] args) throws Exception {

			ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext(
					new String []{"beans.xml"});

			// add a shutdown hook for the above context...添加钩子
			ctx.registerShutdownHook();

			// app runs here...app运行

			// main method exits, hook is called prior to the app shutting down...
			//如果存在main方法,app关闭时,钩子先调用
		}
	}

####7.6.2 ApplicationContextAware and beanNameAware 当一个ApplicationContext创建了一个实现了org.springframework.context.ApplicationContextAware接口的实现对象实例时,该实例将会配置指向该ApplicationContext的引用.

	public interface ApplicationContextAware{
	  void setApplicationContext(ApplicationContext applicationContext) throws beansException;
	}

这些beans可以用程序来操作创建它们的ApplicationContext,通过ApplicationContext接口,或者将其强转为该接口的一个已知的子类,例如ConfigurableApplicationContext(它会暴露其他的功能).一种用户就是可以程序式的获取其他beans.有时这个功能很有用,然而,你一般要避免使用它,因为这会造成与spring的代码耦合,也不符合控制反转的风格(协作对象将属性的形式提供给beans).ApplicationContext提供了其他功能:访问文件系统,发布应用事件,访问messageSourece.其他功能可查看Section 7.15 "Additional Capabilities of the Application context"

在spring2.5,自动注入是另一种方式获取ApplicationContext的引用.传统的constructor和byType注入模式将分别为构造器参数或setter方法属性提供一个ApplicationContext类型的依赖.为了更好的灵活性,也为了注入字段和混合参数方法,使用了基于注解的注入功能.如果你这么做,这个ApplicationContext会注入到期望ApplicationContext类型的字段,构造器参数,或者方法参数中,只要这个字段,构造器,或方法持有@Autowired注解.对于更多的信息,查看Section 7.9.2,"@Autowired".

当一个ApplicationContext创建了一个实现了org.springframework.beans.factory.BeanNameAware接口的类是,这个类会被提供了一个引用去设置它协作对象的命名定义.

	public interface BeanNameAware {

		void setBeanName(String name) throws BeansException;

	}

这个回调调用顺序:在正常bean属性被操作之后,在初始化之前(如InitializingBean的afterPropertiesSet方法或者一个自定义的init-method方法)

####7.6.3 Other Aware interfaces(其他感知接口) 除了以上讨论的applictionContextAware和BeanNameAware外,spring还提供了一些其他Aware接口,允许beans来指向容器,但他们需要一些基础依赖.非常重要的感知接口总结如下,你可用通过依赖的类型来区分它的名字

              name                			injected Dependency   
        ApplicationContextAware		 Declaring  ApplicationContext

         ApplicationEventPublisherAware     Event publisher of the enclosing ApplicationContext

	BeanClassLoaderAware 	 Class loader used to load the bean classes

	BeanFactoryAware		 Declaring BeanFactory
	BeanNameAware 			Name of the declaring bean
	BootsrapContextAware	 容器在资源适配器BootstrapContext中运行,一般只在JCA感知的ApplicationContext中获得
	LoadTimeWeaverAware  
	MessageSoureceAware   释放消息的配置策略,支持参数化和国际化
	NotificationPublisherAware    spring JMX 提醒发布者
	PortletConfigtAware    容器运行的当前PortletConfig,web应用专用
	PortlerContextAware		容器运行的当前PortletContext,web应用专用
	ResourceLoaderAware		低级别的资源访问的配置的加载器

	ServletConfigAware		容器运行的当前ServletConfig.只能在web应用中获取

	ServletContextAware		容器运行的当前ServletContext,只在web应用中获取

注意这些接口的使用会使你的代码和spring耦合,且不符合控制反转风格.因此,他们被推荐为需要对容器进行编程访问的基础组件Bean.

##7.8 Container Extension Points(容器扩展点) 一般而言,应用开发者不需要ApplicationContext实现的子类.替代的是,spring ioc容器可以通过插入特定的集成接口来扩展.本节描述这些集成接口. ###7.8.1 Customizing beans using a BeanPostProcessor BeanPostProcessor 接口可以定义一个回调方法,你可以实现来提供自己的(或重写容器默认的)实例化逻辑,依赖解决逻辑等等.如果你打算在spring 容器完成实例化,配置,初始化之后,你可以插入一两个BeanPostProcessor的实现.

你可以配置混合BeanPostProcessor实例,你可以通过设置order属性来控制这些BeanPostProcessor执行的顺序.不过只有当你实现了Ordered接口时才能设置该属性.所以你需要考虑是否实现Ordered接口.对于更多的细节,参考BeanPostProcessor和Odered接口文档.也可以查看programmatic registration of BeanPostProcessor

BeanPostProcessor操作bean的实例,也就是说Spring ioc容器实例化一个bean的实例,然后BeanPostProcessor进行他们的工作.

BeanPostProcessors 的作用域是每个容器.你正在使用容器层次结构时,这是唯一相关的.如果你在容器中定义一个BeanPostProcessor,那么容器中只有一个post-processbean.那么它们会后置处理容器里的bean.换句话说,一个容器里bean是不会被其他bean处理的,即使这两个容器部分结构相同.

要改变实际的bean定义(bean定义蓝色的印刷体),你需要使用7.8.2里描述的BeanFactoryPostProcessor.

org.springframework.beans.factory.config.BeanPostProcessor接口实际上由两个回调方法组成.当一个类被注册为容器的post-process时,每个bean的实例被容器创建时,该Post-process会在容器初始化方法之前(例如InitializingBean的afterPropertiesSet或其他初始化方法),bean自身初始化回调之后得到一个回调.这些后置处理器可以对bean实例做任何操作,包括完全忽略其他回调.一个后处理器的bean一般会检查回调接口或者将Bean装入一个代理里.一些spring AOP的组件类有时会作为 bean的后处理器来实现以提供相应代理注入逻辑.

一个ApplicationContext自动检测任何在配置元数据中定义的实现了BeanPostProcessor接口的bean.ApplicationContext将这些beans注册为后处理器,这样它们可以在其他bean创建之后回调.bean的后处理器可以在容器里像其他bean一样部署.

记住,当你使用@Bean的工厂方法在一个配置类里申明一个BeanPostProcessor时,这个工厂方法的返回类型应该是其本身的实现或至少是org.springframework.beans.factory.config.BeanPostProcessor接口,以清晰的指明其位一个后处理器.否则,ApplicationContext将无法在它完全创建之前通过类型自动检测到它.由于BeanProcessor需要尽早实例化已供上下文中其他bean实例化之后调用,所以尽早的类型检测很重要.

上文已说过BeanProcessor注册通过ApplicationContext来自动检测,你也可以在ConfigurableBeanFactory里使用addBeanPostProcessor方法来注册它们.你可以在注册之前来进行逻辑判断或你可以在层次结构里通过上下文复制一个post processors.虽然BeanPostProcessor可以程序化添加,但与Ordered接口无关.bean处理器启动的顺序和它注册的顺序一样.记住,通过程序注册的BeanPostProcessor的启动要早于自动检测发现的处理器,无关于具体的order值.

实现BeanPostProcessor接口的类都是特殊的,由容器区别对待.所有的bean后处理器和他们引用的bean会在容器启动就会作为ApplicationContext特定的启动阶段进行自动实例化.接下来,所有的bean后处理器都会被有序注册,并被容器里其他bean调用.因为AOP的自动代理是作为一个BeanPostProcessor实现的,不管BeanPostProcessor还是他们直接引用的bean都不适合自动代理.所以你不需要向它们注入切面.

对于这些bean,你应该看过这样一条信息日志消息:"bean foo没有被所有的BeanPostProcessor接口处理(例如,不适合动态代理)"

记住:如果注入到你的BeanPostProcessor的bean使用了autowiring或@Resource(可能会降级到autowiring),当spring进行类型匹配搜索时,可能会得到意外的beans,因此使他们不适合自动代理其他类的bean后处理.例如,如果你有一个@Resource的依赖,它的位置的field/setter没有同bean的名字相匹配,或者没有使用name属性,那么spring将会通过类型匹配来访问其他beans.

下面的例子展示如何在ApplicationContext中编写,注册,使用BeanPostProcessor.

###Example:Hello World,BeanPostProcessor-style

第一个例子展示了基本用法,介绍了一个BeanPostProcessor的实现容器创建时如何调用在每个bean的toString()方法,并将它们打印在系统控制台上.

查看下面的BeanProcessor的自定义实现;

package scripting;

import org.springframework.beans.factory.config.BeanPostProcessor;

		import org.springframework.beans.BeansException;

		public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {

			// simply return the instantiated bean as-is
			public Object postProcessBeforeInitialization(Object bean,
					String beanName) throws BeansException {
				return bean; // we could potentially return any object reference here...
			}

			public Object postProcessAfterInitialization(Object bean,
					String beanName) throws BeansException {
				System.out.println("Bean '" + beanName + "' created : " + bean.toString());
				return bean;
			}

		}
	<?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:lang="http://www.springframework.org/schema/lang"
			xsi:schemaLocation="http://www.springframework.org/schema/beans
				http://www.springframework.org/schema/beans/spring-beans.xsd
				http://www.springframework.org/schema/lang
				http://www.springframework.org/schema/lang/spring-lang.xsd">

			<lang:groovy id="messenger"
					script-source="classpath:org/springframework/scripting/groovy/Messenger.groovy">
				<lang:property name="message" value="Fiona Apple Is Just So Dreamy."/>
			</lang:groovy>

			<!--
			when the above bean (messenger) is instantiated, this custom
			BeanPostProcessor implementation will output the fact to the system console
			-->
			<bean class="scripting.InstantiationTracingBeanPostProcessor"/>

		</beans>

查看InstantiationTracingBeanPostProcessor是如何简化定义的. 他没有一个名字,但它可以像其他bean一样被依赖注入.(上述的配置被一个Groovy 脚本定义,spring动态语言支持查看35章)

下面是java应用如何执行代码和配置

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

	public final class Boot {

		public static void main(final String[] args) throws Exception {
			ApplicationContext ctx = new ClassPathXmlApplicationContext("scripting/beans.xml");
			Messenger messenger = (Messenger) ctx.getBean("messenger");
			System.out.println(messenger);
		}

	}

上面的应用输出如下:

	Bean 'messenger' created : org.springframework.scripting.groovy.GroovyMessenger@272961
	org.springframework.scripting.groovy.GroovyMessenger@272961		

####例子:RequiredAnnotationBeanPostProcessor 在联结一个自定义的BeanPostProcessor实现时使用回调接口或者注解是扩展spring ioc容器的一个通用方法.一个例子是Spring的RequiredAnnotationBeanPostProcessor,一个BeanPostProcessor的实现,可以保证每个被注解标注的javaBean的属性都会注入一个实际值.

###7.8.2 通过beanFactoryPostProcessor来自定义配置元数据 下一个扩展点是beanFactory后处理器(org.springframework.beans.factory.config.BeanFactoryPostProcessor).它在语义上同BeanPostProcessor相似,但有一个主要区别:BeanFactory后处理器可以读取配置元数据,即spring ioc容器允许一个BeanFactory后处理器来读取配置元数据并能够在容器实例化除了BeanFactoryPostProcessor之外的bean之前来改变元数据. 你可以配置混合的BeanFacotryPostProcessor,并通过order属性来控制这些BeanFacotryPostProcessor的执行顺序.不过,只有你实现了Ordered接口才能设置该属性.如果你要写你自己的BeanFactoryPostProcessor,你需要考虑是否实现Order接口.你可以通过查阅BeanFactoryProcessor和Order接口的文档来了解更多细节.

你要改变一个实际的bean实例,可以通过BeanPostProcessor来实现.但是如果你用BeanFactory来实例化Bean,会导致实例提前产生,这样会破坏原有的容器生命周期;这样还有其他负面影响,如绕过bean后处理.

另外,beanFactoryPostProcessor的定义域是容器级的.当你使用容器层次结构时,才与之相关.不能被其他容器使用.哪怕该容器有相同的结构.

一个Bean工厂后处理器在一个ApplicationContext里申明之后就会执行,用来改变定义容器的配置元数据.spring中包含了大量的已定义的bean工厂后处理器,例如PropertyOverrideConfigurer和PropertyPlaceholderConfigurer.例如,一个自定义的BeanFactoryPostProcessor也可以用来注册一个自定义属性编辑器.

应用上下文将会自动检测所有实现BeanFactoryPostProcessor接口的bean.它会在适合的时候将这些Bean当做bean工厂后处理器来使用.你可以像其他bean一样来部署他们.

实例化配置:你不需要将工厂后处理器设置为懒实例化.如果没有其他bean指向BeanPostProcessor,那么后处理器将不会实例化.而且,将它设为懒初始化会被忽略.即使你在beans元素里设置了deault-lazy-init属性为true,也没用.

####例子: 类名称属性提示配置器 你可以通过PropertyPlaceholderConfigurer来实例化bean定义的属性值,在一个独立的使用标准java属性格式的文件里.这样做可以保证用户通过自定义环境特定的属性(如数据库连接或密码)来部署一个应用,且没有修改容器里主要xml定义文件的风险和困难.

思考下面的基于xml的元数据配置中,数据库属性定部分.这个例子展示了通过实例化属性文件来配置属性.在运行时,一个PropertyPlaceholderConfigure将会用来替换DataSource数据里的部分属性.他们以${property-name}的形式来替换属性

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

	<bean id="dataSource" destroy-method="close"
			class="org.apache.commons.dbcp.BasicDataSource">
		<property name="driverClassName" value="${jdbc.driverClassName}"/>
		<property name="url" value="${jdbc.url}"/>
		<property name="username" value="${jdbc.username}"/>
		<property name="password" value="${jdbc.password}"/>
	</bean>

在其他文件里以标准java属性格式配置的实际值:

jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root

所以,这个字符串${jdbc.username}将会被替换为运行时的值'sa',其他的值也如此.另外,这个placeholder的前缀和后缀也可以自定义.

spring2.5里引入了context命名空间,它可以通过一个检测的配置元素来配置属性提醒器.一个或更多的位置可以通过location属性列表的形式来提供.

 <context:property-placeholder location="classpath:com/foo/jdbc.properties"/>

PropertyPlaceholderConfigurer不仅可以通过Properties来查找属行.默认的它还可以检测java系统属性,如果他没有在特定的属性文件里找到这些属性.你可以通过配置器的systemPropertiesMode属性来配置其行为,有以下三个值:

  • never(0);从来不检测系统属性;

  • fallback(1):如果在属性文件里发现则检测系统属性,此为默认.

  • override(2):在检测特定属性文件前,先检测系统属性.它允许系统属性来重写这些属性资源.

    查阅PropertyPlaceholderConfigurer的java文档来获取更多信息. 另一种配置形式; <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <value>classpath:com/foo/strategy.properties</value> </property> <property name="properties"> <value>custom.strategy.class=com.foo.DefaultStrategy</value> </property> </bean>

      <bean id="serviceStrategy" class="${custom.strategy.class}"/>	
    

在PropertyPlaceholderConfigurer的properties属性里添加配置值.<value>custom.strategy.class=com.foo.DefaultStrategy</value>,下面就可以通过${custom.strategy.class}来配置了. ####例子:属性重写配置器 the PropertyOverrideConfigurer 这个PropertyOverrideConfigurer,组成了PropertyPlaceholderConfigurer,却和后者不同.如果一个重写的属性文件里没有特定bean的属性值,那么原来的值还有用.

bean定义不会察觉哪个bean的属性重写,根据重写机制,如果一个属性有多个值,那么最后一个值会胜出.

属性配置文件单行写法如下:

beanName.property=value

例如:

            dataSource.driverClassName=com.mysql.jdbc.Driver
	dataSource.url=jdbc:mysql:mydb		

这样将会重写dataSource的Bean的driverClassName和url属性.

当然你可以这么定义:foo.fred.bob.sammy=123,前提是你的属性会被容器初始化.spring2.5引入了context命名空间,你可以这样配置属性重写器:

	<context:property-override location="classpath:override.properties"/>

###7.8.3 Customizing instantiation logic with a FactoryBean(自定义工厂bean的初始化逻辑) 实现了org.springframework.beans.factory.FactoryBean接口的都可以看做是自己的工厂类;

工厂类接口是spring ioc容器初始化逻辑中一个可插拔的点.在复杂的初始化代码上,相对于冗长的xml文件,如果你有更好的java代码表现,你可以创建自己的FactoryBean,将这些复杂的初始化代码写在类里,并将你的自动以FactoryBean推到容器里.

FactoryBean接口提供了三个方法:

  • Object getObject():返回实例,取决你返回的是单例还是原生;
  • boolean isSingleton():是单例返回true,否则则false;
  • Class getObjectType():返回getObject()方法返回的类型,如果不知道则返回null;

FactoryBean的概念和接口在spring很多地方使用,spring自己有超过50个FactoryBean实现;

当你需要一个工厂Bean的实例时,你需要使用ApplicationContext的getBean()方法,然后参数为bean的id加上&符号;如调用getBean("myBean")返回bean的实例,调用getBean("&myBean"),则返回其工厂Bean.

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部