《Spring攻略》 第2章 高级Spring IoC容器

来源:互联网 发布:大数据与精准教学 编辑:程序博客网 时间:2024/05/21 15:06

1调用静态工厂方法创建Bean

问题:你打算调用一个静态工厂方法在Spring IoC容器中创建一个Bean,静态工厂方法的目的是在静态方法中封装对象创建过程。解决方案:Spring支持调用一个静态工厂方法创建Bean,这个方法应该在factory-method属性中指定。工作原理:factory-methodpublic class ProductCreator {public static Product createProduct(int productId){if(1 == productId){return new Product("xiaomei", 16);}else if(2 == productId){return new Product("xiaolang", 15);}throw new IllegalArgumentException("Unknown product");}}<bean id="productCreator1" class="com.partner4java.spring.factorymethod.ProductCreator"factory-method="createProduct"><constructor-arg value="1" /></bean><bean id="productCreator2" class="com.partner4java.spring.factorymethod.ProductCreator"factory-method="createProduct"><constructor-arg value="2" /></bean>@Testpublic void testFactoryMethod(){System.out.println(applicationContext.getBean("productCreator1"));System.out.println(applicationContext.getBean("productCreator2"));}


2调用一个实例工厂方法创建Bean

问题:你打算调用一个实例工厂方法在Spring IoC容器中创建一个Bean,目的是在另一个对象实例的一个方法中封装对象创建过程。请求对象的客户可以简单地调用这个方法,不需要了解创建的细节。解决方案:Spring支持调用实例工厂方法创建Bean。Bean实例在factory-bean属性中指定,而工厂方法应该在factory-method属性中指定。工作原理:factory-beanpackage com.partner4java.spring.factorybean;import java.util.Map;import com.partner4java.spring.factorymethod.Product;public class ProductCreator {private Map<String, Product> products;public void setProducts(Map<String, Product> products) {this.products = products;}public Product createProduct(String productId){Product product = products.get(productId);if(product != null){return product;}throw new IllegalArgumentException("Unknown product");}}<bean id="productCreator" class="com.partner4java.spring.factorybean.ProductCreator"><property name="products"><map><entry key="gaofumei"><bean class="com.partner4java.spring.factorymethod.Product"><constructor-arg value="gaofumei" name="name"/><constructor-arg value="100" name="price"/></bean></entry><entry key="xiaoneinv"><bean class="com.partner4java.spring.factorymethod.Product"><constructor-arg value="xiaoneinv" name="name"/><constructor-arg value="200" name="price"/></bean></entry></map></property></bean><bean id="gaofumei" factory-bean="productCreator" factory-method="createProduct"><constructor-arg value="gaofumei"/></bean><bean id="xiaoneinv" factory-bean="productCreator" factory-method="createProduct"><constructor-arg value="xiaoneinv"/></bean>@Testpublic void testFactoryMethod(){System.out.println(applicationContext.getBean("gaofumei"));System.out.println(applicationContext.getBean("xiaoneinv"));}


3从静态字段中声明bean

问题:你打算从一个静态字段中声明Spring IoC容器中的一个Bean。在Java中,常量值往往声明为静态字段。解决方案:为了从静态字段中声明Bean,你可以使用内建的工厂Bean FieldRetrievingFactoryBean,或者Spring 2.X中的<util:contant>标记。工作原理:public class ProductConstant {public static Product gaofumei = new Product("gaofumei", 100);public static Product xiaoneinv = new Product("xiaoneinv", 200);}<util:constant id="gaofumei"static-field="com.partner4java.spring.constant.ProductConstant.gaofumei" /><util:constant id="xiaoneinv"static-field="com.partner4java.spring.constant.ProductConstant.xiaoneinv" />@Testpublic void testFactoryMethod(){System.out.println(applicationContext.getBean("gaofumei"));System.out.println(applicationContext.getBean("xiaoneinv"));}


4从对象属性中声明bean

问题:你打算从一个对象属性或者嵌套的属性(也就是属性路径)中声明Spring IoC容器中的一个Bean。解决方案:为了从一种对象属性或者属性路径中声明Bean,可以使用内建的工厂Bean PropertyPathFactoryBean或者Spring 2.X中的<util:property-path>标记。工作原理:public class ProductProperty {private Product gaofumei;private Product xiaonennv;public Product getGaofumei() {return gaofumei;}public void setGaofumei(Product gaofumei) {this.gaofumei = gaofumei;}public Product getXiaonennv() {return xiaonennv;}public void setXiaonennv(Product xiaonennv) {this.xiaonennv = xiaonennv;}}<bean id="productProperty" class="com.partner4java.spring.property.ProductProperty"><property name="gaofumei"><bean class="com.partner4java.spring.factorymethod.Product"><constructor-arg name="name" value="gaofumei"/><constructor-arg name="price" value="100.1"/></bean></property><property name="xiaonennv"><bean class="com.partner4java.spring.factorymethod.Product"><constructor-arg name="name" value="xiaonennv"/><constructor-arg name="price" value="200.2"/></bean></property></bean><util:property-path id="gaofumei" path="productProperty.gaofumei"/><util:property-path id="xiaonennv" path="productProperty.xiaonennv"/>


5使用Spring表达式语言

问题:
你希望动态的评估一些条件或者属性,并且将其作为IoC容器中的配置值使用。
你也可能因为自定义范围的情况,必须将某些估值从设计时延迟到运行时。
或者你只是需要一种行为自己的应用添加强大的表达式语言。


解决方案:
使用Spring 3.0的Spring表达式语言(SpEL),这种语言提供了与JSF和JSP中的Unified EL或者对象图形导航语言(OGNL)相似的功能。
SpEL提供了易用的基础架构,可以在Spring容器之外使用。


工作原理:



6设置Bean作用域

问题:
当你在配置文件中声明Bean时,实际上定义了Bean创建的一个模板,而不是实际的Bean实例。
当getBean()方法或者其他的Bean的一个引用请求Bean时,Spring将根据Bean作用域(Scope)确定应该返回的Bean实例。
有时候,你必须为Bean设置正确的作用域而不是默认的作用域。( By default, a bean will be a singleton)


解决方案:
作用域 描述 
singleton 
 在每个Spring IoC容器中一个bean定义对应一个对象实例。
 
prototype 
 一个bean定义对应多个对象实例。
 
request 
 在一次HTTP请求中,一个bean定义对应一个实例;即每次HTTP请求将会有各自的bean实例, 它们依据某个bean定义创建而成。该作用域仅在基于web的Spring ApplicationContext情形下有效。
 
session 
 在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。
 
global session 
 在一个全局的HTTP Session中,一个bean定义对应一个实例。典型情况下,仅在使用portlet context的时候有效。该作用域仅在基于web的Spring ApplicationContext情形下有效。


7自定义Bean初始化和析构

问题 :许多现实世界中的组件在使用之前必须进行某种初始化任务。在组件的声明周期结束时,也必须要执行相应的任务。解决方案:除了注册Bean之外,Spring IoC容器还负责管理Bean的声明周期,允许你在他们的生命期特定时点执行自定义任务。你的任务应该封装在回调方法中,由Spring IoC容器在核实的时候调用。Spring IoC容器管理Bean周期的步骤:1、构造程序或者工厂方法创建Bean实例。2、向Bean属性设置值和Bean引用。3、调用初始化回调方法。4、Bean就绪。5、容器关闭时,调用析构回调方法。工作原理:方法一:实现约定接口public class Work implements InitializingBean,DisposableBean {@Overridepublic void destroy() throws Exception {System.out.println("离职");}public void vork(){System.out.println("working");}@Overridepublic void afterPropertiesSet() throws Exception {System.out.println("入职");}}<bean id="work" class="com.partner4java.spring.initdes.Work"scope="prototype" />方法二:配置文件声明public class Work1 {public void destroy() throws Exception {System.out.println("离职");}public void vork(){System.out.println("working");}public void afterPropertiesSet() throws Exception {System.out.println("入职");}}<bean id="work1" class="com.partner4java.spring.initdes.Work1"init-method="afterPropertiesSet" destroy-method="destroy" scope="prototype" />方法三:注解声明public class Work2 {@PreDestroypublic void destroy() throws Exception {System.out.println("离职");}public void vork(){System.out.println("working");}@PostConstructpublic void afterPropertiesSet() throws Exception {System.out.println("入职");}}<context:annotation-config/><bean id="work2" class="com.partner4java.spring.initdes.Work2"scope="prototype" />


8用Java Config简化XML配置

问题:
你欣赏DI容器的能力,但是希望覆盖一些配置,或者只是希望将更多的配置从XML格式中转移到Java中,可以更好的从重构和类型安全性中获益。


解决方案:
你可以使用Java Config。


工作原理:
Java Config支持强大的,代表了与其他通过XML或者注解的配置选项完全不同的工作方式。
重要的是,Java Config可以与现有方式混合使用。
启用Java配置的最简单方法是使用简单的XML配置文件。


@Configuration标记类:Spring将在类中寻找@Bean定义,指定这个标记@Bean的方法为一个Bean。(也就是指定某个方法返回为一个Bean)


@Lazy:将Bean的构造推迟到必须满足依赖或者应用上下文中显示的访问时。
@DependsOn:指定一个Bean的创建必须在其他Bean创建之后。
@Primary:指定相同接口的多个Bean。


@Import、@Value...


9使Bean感知容器

问题:一个精心设计的组件应该没有对容器的直接依赖。但是,有时候Bean有必要了解容器的资源。解决方案:Spring将通过一些接口定义的设置方法将对应资源注入到你的Bean中。Spring中的常见感知接口感知接口目标资源BeanNameAwareIoC容器中配置的实例的Bean名称BeanFactoryAware当前的Bean工厂,通过它你可以调用容器的服务。ApplicationContextAware*当前应用上下文,通过他你可以调用容器的服务。MessageSourceAware消息资源,通过他可以解析文本消息。ApplicationEventPublisherAware应用事件发布者,通过他你可以发布应用事件。ResourceLoaderAware资源装载器,通过他可以加载外部资源。你可以实现这些接口,然后试试看你都能获得什么。工作原理:public class HelloBeanNameAware implements BeanNameAware {private String name;@Overridepublic void setBeanName(String name) {this.name = name;}@Overridepublic String toString() {return "HelloBeanNameAware [name=" + name + "]";}}public class HelloBeanFactoryAware implements BeanFactoryAware {private BeanFactory beanFactory;@Overridepublic void setBeanFactory(BeanFactory beanFactory) throws BeansException {this.beanFactory = beanFactory;}@Overridepublic String toString() {return "HelloBeanFactoryAware [beanFactory=" + beanFactory.getClass().getSimpleName() + "]";}}public class HelloApplicationContextAware implements ApplicationContextAware {private ApplicationContext applicationContext;@Overridepublic void setApplicationContext(ApplicationContext applicationContext)throws BeansException {this.applicationContext = applicationContext;}@Overridepublic String toString() {return "HelloApplicationContextAware [applicationContext="+ applicationContext.getClass().getSimpleName() + "]";}}<bean id="helloBeanNameAware" class="com.partner4java.spring.aware.HelloBeanNameAware" /><bean id="helloBeanFactoryAware" class="com.partner4java.spring.aware.HelloBeanFactoryAware" /><bean id="helloApplicationContextAware" class="com.partner4java.spring.aware.HelloApplicationContextAware" />@Testpublic void testAware() throws InterruptedException{System.out.println(applicationContext.getBean("helloBeanNameAware"));System.out.println(applicationContext.getBean("helloBeanFactoryAware"));System.out.println(applicationContext.getBean("helloApplicationContextAware"));// 后台打印:// HelloBeanNameAware [name=helloBeanNameAware]// HelloBeanFactoryAware [beanFactory=DefaultListableBeanFactory]// HelloApplicationContextAware// [applicationContext=ClassPathXmlApplicationContext]}


10加载外部资源

问题:有时候,你的应用可能需要从不同位置(例如文件系统、classpath或者URL)读取外部资源(例如文本文件、XML文件、属性文件或者图像文件)。通常,你必须处理用于从不同位置加载资源的不同API。解决方案:Spring的资源装载器提供统一的getResoure()方法,按照资源路径读取外部资源。你可以为路径指定不同的前缀从不同位置加载资源。为了从文件系统加载资源,使用file前缀。从classpath加载资源则使用classpath前缀。你还可以在资源路径中指定一个URL。Resource是Spring中代表外部资源的通用接口。Spring提供Resource接口的多个实现。资源装载器的getResource()方法根据资源路径决定实例化哪一个Resource实现。工作原理:方式一:实现接口ResourceLoaderAware(感知接口)public class HelloResourceLoader implements ResourceLoaderAware {private ResourceLoader resourceLoader;@Overridepublic void setResourceLoader(ResourceLoader resourceLoader) {this.resourceLoader = resourceLoader;}public void showResource() throws IOException {Resource resource = resourceLoader.getResource("file:D:/cc.txt");BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(resource.getInputStream()));String s = null;while((s = bufferedReader.readLine()) != null){System.out.println(s);}}}<bean id="helloResourceLoader" class="com.partner4java.spring.resource.HelloResourceLoader"init-method="showResource" />获取bean,会注入感知接口的资源,并执行初始化方法。方式二:简单的指定这个Resource属性的资源路径,Spring将使用预先注册属性编辑器ResourceEditor将这个属性转换为一个Resource对象,然后注入你的Bean中。public class HelloSimpleResourceLoader {private Resource resource;public void setResource(Resource resource) {this.resource = resource;}public void showResource() throws IOException {BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(resource.getInputStream()));String s = null;while((s = bufferedReader.readLine()) != null){System.out.println(s);}}}<bean id="helloSimpleResourceLoader"class="com.partner4java.spring.resource.HelloSimpleResourceLoader"init-method="showResource"><property name="resource"><value>classpath:com/partner4java/spring/App.class</value></property></bean>


11创建Bean后处理器

问题:你希望在Spring IoC容器中注册自己的插件,在构造期间处理Bean实例。解决方案:Bean后处理器允许在初始化回调方法前后进行附加的Bean处理。Bean后处理器的主要特性是逐个处理IoC容器中的所有Bean实例,而不是单个Bean实例。一般,Bean后处理器用于检查Bean属性有效性,或者根据特殊条件修改Bean属性。Bean后处理器的基本要求是实现BeanPostProcessor接口。你可以实现postProcessBeforeInitialization()和postProcessAfterInitialization()方法,在初始化回调方法前后处理所有Bean。然后,Spring将在调用初始化回调方法前后向这两个方法传递每个Bean实例。步骤如下:1、构造程序或者工厂方法创建Bean实例。2、为Bean属性设置值和Bean引用。3、调用感知接口中定义的设置方法。4、将Bean实例传递给每个Bean前置处理器中的postProcessBeforeInitialization方法。5、调用初始化回调方法。6、讲Bean实例传递给每个Bean后处理器中的postProcessAfterInitialization方法。7、Bean准备就绪,可以使用。8、容器关闭时,调用析构回调方法。使用Bean工厂为IoC容器时,Bean后处理器只能编程注册,更准确的讲是通过addBeanPostProcessor()方法注册。但是,如果你使用一个应用上下文,注册将很简单,只要在Bean配置文件中声明处理器实例,他就会自动注册。工作原理:public class LogBeanPostProcessor implements BeanPostProcessor {@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName)throws BeansException {System.out.println(beanName + " say hello world!");return bean;}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName)throws BeansException {System.out.println(beanName + " say good buy!");return bean;}}<!-- 要在应用上线文中注册一个Bean后处理器,只要在Bean配置文件中声明他的一个实例就可以了。 应用上下文能够自动检测谁实现了BeanPostProcessor接口,并且注册他一处理容器中的所有其他Bean实例 --><bean class="com.partner4java.spring.postprocess.LogBeanPostProcessor" /><bean id="helloResourceLoader" class="com.partner4java.spring.resource.HelloResourceLoader"init-method="showResource" />如果以配置文件的格式设置init-method,对BeanPostProcesser的执行没有什么威胁,BeanPostProcesser还是会先执行。但是如果,以@PreDestroy和@PostConstruct的形式,BeanPostProcesser讲不能正常工作,因为BeanPostProcesser的默认优先级低于CommonAnnotationBeanPostProcesser。不过可以同时实现PriorityOrdered接口来指定执行顺序。


12外部化Bean配置

问题:在配置文件中配置Bean时,你必须记住,讲部署细节如文件路径、服务器地址、用户名和密码与Bean配置混在一起是不好的做法。通常,Bean配置由应用开发人员编写,而部署细节则是部署人员或者系统管理员的事情。解决方案:Spring有一个名为PropertyPlaceholderConfigurer的Bean工厂后处理器,用来将部分Bean配置外部化为一个属性文件。你可以在Bean配置文件中使用${var}形式的变量,PropertyPlaceholderConfigurer讲从属性文件中加载属性并且用他们替代变量。Bea工厂后处理器与Bean后处理器之间的不同在他的目标是IoC容器--Bean工厂或者应用上下文,而不是Bean实例。Bean工厂后处理器将在IoC容器加载Bean配置之后、Bean实例创建之前生效,他的典型作用是在Bean实例化之前修改Bean配置。Spring有多个Bean工厂后处理器供你使用。在实战中,你很少与必要编写自己的Bean工厂后处理器。工作原理:方式一:具体指定外部Bean配置类DBConfig.properties:DB.name=testpublic class DBConfig {private String name;public void setName(String name) {this.name = name;}@Overridepublic String toString() {return "DBConfig [name=" + name + "]";}}<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"><property name="location"><value>classpath:DBConfig.properties</value></property></bean><bean id="dbConfig" class="com.partner4java.spring.config.DBConfig"><property name="name" value="${DB.name}"/></bean>第二种:简单的方式<context:property-placeholder location="classpath:DBConfig.properties"/>


13解析文本消息

问题:对于支持国际化的应用来说,为不同地区解析文本消息的能力是必要的。解决方案:Spring应用上下文能够按照关键字为目标地区解析文本消息。一般来说,一个地区的消息应该存储在一个独立的属性文件中,这个属性文件称作资源包(Resource bundle)。MessageSource是定义多种消息解析方法的接口。ApplicationContext接口扩展了这个接口,使用所有应用上下文能够解析文本消息。应用上下文将消息解析委派给名为messageSource的Bean。ResourceBundleMessageSource最常见的MessageSource实现,他从资源包中解析不同地区的消息。工作原理:方式一:message_en_US.properties:shopping.wife=gaofumei beatushopping.age={0} oh<bean id="messageSource"class="org.springframework.context.support.ResourceBundleMessageSource"><property name="basename"><value>message</value></property></bean>@Testpublic void testAware() throws InterruptedException{System.out.println(applicationContext.getMessage("shopping.wife", null, Locale.US));System.out.println(applicationContext.getMessage("shopping.age", new Object[]{18}, Locale.US));}后台打印:gaofumei beatu18 oh


14使用应用事件进行通信

问题:
在组件之间的典型通信模式中,发送者必须定位接受者,以便调用接受者之上的方法。
在这种情况下,发送者组件必须了解接收者组件。这种通信直接而简单,但是发送者和接受者组件紧密耦合。


使用IoC容器时,你的组件可以通过接口而不是实现进行通信。这种通信模式有助于减少耦合。
但是,只有在发送者组件必须与一个接受者通信时有效。当发送者必须与多个接受者通信时,必须逐个调用接收者。


解决方案:
Spring的应用上下文支持基于事件的Bean间通信。
在基于事件的通信模式中,发送者组件只要发布一个事件而不需要知道接收者。
实际上,可以有多于一个接收者组件。
而且,接收者不需要知道是谁发布了事件,可以同时监听不同发送者的多个事件。
这样,发送者和接收者组件是低耦合的。


在Spring中,所有事件类都必须扩展ApplicationEvent类。这样,任何Bean都可以调用应用时间发布者的publishEvent()方法,发布一个事件 。
对于监听某些事件的Bean来说,必须实现ApplicationListener接口,并在onApplicationEvent()方法中处理事件。
实际上,Spring将通知所有事件的监听者,这样你必须自己过滤事件。但是,如果使用类属,Spring将只分发匹配类属参数的消息。


工作原理:
(jdk里面的观察者模式也挺成熟的)


15在Spring中注册属性编辑器

问题:属性编辑器是JavaBeans API的一项功能,用于属性值与文本值互相转换。每个属性编辑器仅用于某一类属性。你可以希望采用属性编辑器来简化Bean配置。解决方案:Spring IoC容器支持使用属性编辑器帮助Bean配置。例如,使用java.net.URL类型的属性编辑器,可以指定用于URL类型属性的URL字符串。Spring会自动的将这个URL字符串转换为一个URL对象,注入你的属性中。Spring自带多种用于转换常见类型Bean属性的属性编辑器。一般来说,你应该在Spring IoC容器中注册属性编辑器,然后才能使用它。CustomEditorConfigurer是作为Bean工厂后处理器来实现的,用于在任何Bean实例化之前注册你的自定义属性编辑器。工作原理:就是先写一个编辑器,然后注册,然后自动使用。除了CustomDateEditor之外,Spring自带多个转换常见数据类型的属性编辑器,例如CustomNumberEditor、ClassEditor、FileEditor、LocaleEditor、StringArrayPropertyEditor和URLEditor。可查看包:org.springframework.beans.propertyeditorspublic class User {private String username;private Date birthday;...<bean id="dateEditor"class="org.springframework.beans.propertyeditors.CustomDateEditor"><!-- DateFormat对象作为其第一个构造程序参数 --><constructor-arg><bean class="java.text.SimpleDateFormat"><constructor-arg value="yyyy-MM-dd" /></bean></constructor-arg><!-- 表示该编辑器是否允许空值 --><constructor-arg value="true"/></bean><!-- 必须进行注册 --><bean class="org.springframework.beans.factory.config.CustomEditorConfigurer"><property name="customEditors"><map><entry key="java.util.Date"><ref local="dateEditor"/></entry></map></property></bean><bean id="user" class="com.partner4java.spring.editor.User"><property name="username" value="gaofumei"/><property name="birthday" value="2012-10-10"/></bean>


16创建自定义属性编辑器

问题:除了注册内建的属性编辑器之外,你可能希望编写自定义的属性编辑器,转换你自定义数据类型。解决方案:你可以实现java.beans.PropertyEditor实例或者扩展便利的支持类java.beans.PropertyEditorSupport,编写自定义的属性编辑器。工作原理:首先实现自己的编辑器,然后注册编辑器public class UserEditor extends PropertyEditorSupport {@Overridepublic String getAsText() {DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");User user = (User) getValue();return user.getClass().getName() + "," + user.getUsername() + ","+ dateFormat.format(user.getBirthday());}@Overridepublic void setAsText(String text) throws IllegalArgumentException {String[] parts = text.split(",");DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");try {User user = (User)Class.forName(parts[0]).newInstance();user.setUsername(parts[1]);user.setBirthday(dateFormat.parse(parts[2]));setValue(user);} catch (Exception e) {e.printStackTrace();}}}<!-- 必须进行注册 --><bean class="org.springframework.beans.factory.config.CustomEditorConfigurer"><property name="customEditors"><map><entry key="com.partner4java.spring.editor.User"><bean class="com.partner4java.spring.editor.UserEditor"/></entry></map></property></bean><bean id="userChild" class="com.partner4java.spring.editor.UserChild"><property name="user"><value>com.partner4java.spring.editor.User,gaofumei,2012-01-02</value></property></bean>@Testpublic void testAware() throws InterruptedException{System.out.println(applicationContext.getBean("userChild"));}


原创粉丝点击