1调用静态工厂方法创建Bean
问题: 你打算调用一个静态工厂方法在Spring IoC容器中创建一个Bean,静态工厂方法的目的是在静态方法中封装对象创建过程。 解决方案: Spring支持调用一个静态工厂方法创建Bean,这个方法应该在factory-method属性中指定。 工作原理: factory-method public 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> @Test public 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-bean package 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> @Test public 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" /> @Test public 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 { @Override public void destroy() throws Exception { System.out.println("离职"); } public void vork(){ System.out.println("working"); } @Override public 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 { @PreDestroy public void destroy() throws Exception { System.out.println("离职"); } public void vork(){ System.out.println("working"); } @PostConstruct public 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中的常见感知接口 感知接口 目标资源 BeanNameAware IoC容器中配置的实例的Bean名称 BeanFactoryAware 当前的Bean工厂,通过它你可以调用容器的服务。 ApplicationContextAware* 当前应用上下文,通过他你可以调用容器的服务。 MessageSourceAware 消息资源,通过他可以解析文本消息。 ApplicationEventPublisherAware 应用事件发布者,通过他你可以发布应用事件。 ResourceLoaderAware 资源装载器,通过他可以加载外部资源。 你可以实现这些接口,然后试试看你都能获得什么。 工作原理: public class HelloBeanNameAware implements BeanNameAware { private String name; @Override public void setBeanName(String name) { this.name = name; } @Override public String toString() { return "HelloBeanNameAware [name=" + name + "]"; } } public class HelloBeanFactoryAware implements BeanFactoryAware { private BeanFactory beanFactory; @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { this.beanFactory = beanFactory; } @Override public String toString() { return "HelloBeanFactoryAware [beanFactory=" + beanFactory.getClass().getSimpleName() + "]"; } } public class HelloApplicationContextAware implements ApplicationContextAware { private ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } @Override public 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" /> @Test public 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; @Override public 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 { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println(beanName + " say hello world!"); return bean; } @Override public 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=test public class DBConfig { private String name; public void setName(String name) { this.name = name; } @Override public 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 beatu shopping.age={0} oh <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> <property name="basename"> <value>message</value> </property> </bean> @Test public 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 beatu 18 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.propertyeditors public 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 { @Override public String getAsText() { DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); User user = (User) getValue(); return user.getClass().getName() + "," + user.getUsername() + "," + dateFormat.format(user.getBirthday()); } @Override public 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> @Test public void testAware() throws InterruptedException{ System.out.println(applicationContext.getBean("userChild")); }