Spring IoC高级特性---Spring自动转化其他非String类型值的问题

来源:互联网 发布:nc工具for windows 编辑:程序博客网 时间:2024/04/28 07:37
前言
 
本节介绍了FactoryBean和PropertyEditor,二者都是为了注入属性而生。前者处理了如何注入那些不能通过new运算符产生实例的属性这一问题,后者则处理了如何通过只在配置文件中注入String类型的值,就能被Spring自动转化成其他非String类型值的问题。二者在使用上互有所长,学习之后建议比较。
 
使用FactoryBean
 
作用:可以注入那些无法直接用new运算符创建的以来关系。创建事务型代理。

特点:是一个可以扮演其他bean之工厂的bean;可以像其他bean一样被配置;使用时,不返回FactoryBean本身而是返回需要注入的实例。

解释:在java中,有些类可以通过new运算符生成一个它的新的实例,但是有些类则不然。例如Calender类,只能通过getInstance()方法来获取其实例。

实现:一个实现了FactoryBean接口类必须实现如下三个方法,

getObject(),获取FactoryBean创建的Object,此Object是传给(注入)给其他Bean的真正Object。

geObjectType(),告知Spring,你的FactoryBean所返回的Object的类型。“如果你事先已知,则可以为null”(原书原文,个人认为是可能在程序中进行人工转换,所以原书才会这么说)。如果指定了类型,Spring会自动把Object转换成该类型并且进行转换。

isSingleton(),告知Spring,FactoryBean是否管理着singleton实例。此处别把FactoryBean的<bean>标签中的singleton属性和这个搞混。这个方法是说FactoryBean所管理的Bean实例,而属性是说FactoryBean本身是否为singleton。
 
例程代码如下:
以原书例,(P114,代码清单5-29;P115,代码清单5-30;P116,代码清单5-31,代码清单5-32)
读者注意自己注意总结这些程序中类的关系。
 
1。实现了FactoryBean接口的“bean工厂”类
package com.apress.prospring.ch5.factory;
import java.security.MessageDigest;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
public class MessageDigestFactoryBean implements FactoryBean, InitializingBean {
 private String algorithmName = "MD5";
 
 private MessageDigest messageDigest = null;
 
 public Object getObject() throws Exception {
  return messageDigest.clone();
 }
 public Class getObjectType() {
  return MessageDigest.class;
 }
 public boolean isSingleton() {
  return true;
 }
 // ...
}
 
2。依赖于类1的MessageDigester类
package com.apress.prospring.ch5.factory;
import java.security.MessageDigest;
import sun.misc.BASE64Encoder;
public class MessageDigester {
 private MessageDigest digest1 = null;
 private MessageDigest digest2 = null;
 
 public void setDigest1(MessageDigest digest1) {
  this.digest1 = digest1;
 }
 public void setDigest2(MessageDigest digest2) {
  this.digest2 = digest2;
 }
 
 public void digest(String msg) {
  System.out.println("Using digest1");
  digest(msg,digest1);
  
  System.out.println("Using digest1");
  digest(msg,digest2);
 }
 
 private void digest(String msg, MessageDigest digest) {
  System.out.println("Using algorithm: " + digest.getAlgorithm());
  digest.reset();
  byte[] bytes = msg.getBytes();
  byte[] out = digest.digest(bytes);
  BASE64Encoder enc = new BASE64Encoder();
  System.out.println(enc.encode(out));
 }
 
}
 
3。bean配置文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "
http://www.springframework.org/dtd/spring-beans.dtd" >
<beans>
 <bean id="shaDigest"
  class="com.apress.prospring.ch5.factory.MessageDigestFactoryBean">
  <property name="algorithmName">
   <value>SHA1</value>
  </property>
 </bean>
 <bean id="defaultDigest"
  class="com.apress.prospring.ch5.factory.MessageDigestFactoryBean"/>
 <bean id="digester"
  class="com.apress.prospring.ch5.factory.MessageDigester">
  <property name="digest1">
   <ref local="shaDigest"/>
  </property>
  <property name="digest2">
   <ref local="defaultDigest"/>
  </property>
 </bean>
 
</beans>
从这里可以看出,尽管配置时候没有直接给MessageDigester类注入MessageDigest,而是注入FactoryBean,但是Spring会通过FactoryBean接口的getObject()方法直接返回MessageDigester类所需要的实例,而非直接注入FactoryBean实例。接下来,看看运行结果。
 
4。使用MessageDigester
package com.apress.prospring.ch5.factory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.FileSystemResource;
public class MessageDigestExample {
 public static void main(String[] args) {
  BeanFactory factory =
   new XmlBeanFactory(
    new FileSystemResource("com/apress/prospring/ch5/factory/factory.xml")
   );
  // 注意此处,尽管我们已经实现了getType()方法,但是还要转换,想想为什么?
  MessageDigester digester = (MessageDigester) factory.getBean("digester");
  digester.digest("Hello World!");
 }
}
可见,FactoryBean已经起了作用。
输出结果是:
Using digest1
Using algorithm: SHA1
Lve95gjOVATpfV8EL5X4nxwjKHE=
Using digest1
Using algorithm: MD5
7Qdih1MuhjZehB6Sv8UNjA==
与原书一致。
换成解析字符串Hello Kitty!,输出结果是:
Using digest1
Using algorithm: SHA1
blS3+vJkjY3SOwJtRoEl57JSkWQ=
Using digest1
Using algorithm: MD5
1l7h0w3Oa7h2DOgHNiRgxg==
 
//////////////////////////////
直接访问FactoryBean
//////////////////////////////
 
既然FactoryBean也是一个Bean,那么可否像其他Bean一样在Spring中被访问到呢?当然可以!
实现:在作为getBean()调用参数的bean名字前加上"&"符号即可。
例程代码:(P117,代码清单5-33)

package com.apress.prospring.ch5.factory;
import java.security.MessageDigest;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.FileSystemResource;
public class AccessingFactoryBeans {
 public static void main(String[] args) {
  BeanFactory factory =
   new XmlBeanFactory(
    new FileSystemResource("com/apress/prospring/ch5/factory/factory.xml")
   );
  MessageDigest digest =
   (MessageDigest) factory.getBean("shaDigest");
  
  MessageDigestFactoryBean factoryBean =
   (MessageDigestFactoryBean) factory.getBean("&shaDigest");
 }
}

特别注意啊,在getBean方法中的bean的名称前加或者不加&符号效果是不同的啊!仔细体会。

建议:尽量不要这么做,显然是进一步加大了耦合性,把程序耦合到一个具体的实现,而且这个实现将来可能改变,尽管FactoryBean在Spring本身中大量存在(它实际上作为Spring本身的底层API,支撑了Spring的基础设施)。
 
总结:原书在FactoryBean这里其实并没有讲太多东西,只要掌握了所给的例子,以及其中的关系也就够用了。
 
JavaBeans PropertyEditors(属性编辑器)
 
前文回忆:考虑以往的bean配置文件,其中很多bean的属性并非String类型,但是配置的时候传入的是String类型的值,却可以正常运行!

作用:允许把String类型的值转换成bean中对应属性的正确的类型值。

解释:注意对于Spring的PropertyEditors“们”,默认的转换只有七种类型,而我们平时在配置的时候发现还有一些原始类型也可以这样做,这实际上是Spring的IoC容器做的基本类型转换。对于一些高级的类型,则需要用属性编辑器来完成。所以,从这层意思上讲,个人觉得叫做值型转化器貌似更好。

基本类型:是指Spring默认提供的7中属性编辑器的类型,而非Java语言的基本数据类型。他们分别对应了Java语言中7种数据类型。如果是以下7种默认类型,则不需要显式配置对应的PropertyEditor,因为Spring会自动进行处理。否则要自定义相应的PropertyEditor。
 
1。ByteArrayPropertyEditor
对应类型:byte[]
配置举例:其中bytes是某个bean中的byte[]类型的属性
<property name="bytes">
 <value>hello world!</value>
</property>
说明:Spring会把String类型的hello world!字符串转换为byte[]。
 
2。ClassEditor
对应类型:Class
配置举例:其中class是某个bean中的Class类型的属性
<property name="class">
 <value>java.lang.String</value>
</property>
说明:Spring会把String类型的java.lang.String字符串转换为类对象Class类的实例。
 
3。FileEditor
对应类型:java.io.File
配置举例:其中file是某个bean中的java.io.File类型的属性
<property name="file">
 <value>d:/temp/test.txt</value>
</property>
说明:Spring会把String类型的d:/temp/test.txt字符串转换为类对象java.io.File实例。
 
4。LocaleEditor
对应类型:java.util.Locale
配置举例:其中locale是某个bean中的java.util.Locale类型的属性
<property name="locale">
 <value>en-GB</value>
</property>
说明:Spring会把String类型的en-GB字符串转换为Locale类型实例。
 
5。PropertiesEditor
对应类型:java.util.Properties
配置举例:其中 是某个bean中的java.util.Properties类型的属性
<property name="properties">
 <value>
  name=foo
  age=19
 </value>
</property>
说明:Spring会把String类型的name=foo和age=19字符串转换为java.util.Properties实例中的值。
 
6。StringArrayPropertyEditor
对应类型:String[]
配置举例:其中strings是某个bean中的String[]类型的属性
<property name="strings">
 <value>Bob,Rod,John,Roly</value>
</property>
说明:Spring会把String类型的Bob,Rod,John,Roly字符串转换为String[]实例。
 
7。URLEditor
对应类型:java.net.URL
配置举例:其中url是某个bean中的java.net.URL类型的属性
<property name="url">
 <value>http://www.sina.com</value>
</property>

说明:Spring会把String类型的http://www.sina.com字符串转换为url实例的值。
以上简单介绍了7种基本类型在bean配置文件中的用法,都是通过String值型注入的,之后Spring通过7种默认属性编辑器进行转化并且注入到bean中去——这一步是透明的,不需要开发人员去操作。如果不是默认类型该如何去处理呢?
 
////////////////////////////////////
自定义属性编辑器
////////////////////////////////////
 
作用:自定义属性编辑器来转换String到相应的属性类型

实现:通过扩展PopertyEditorSupport类来实现,该类只需要开发人员实现了一个方法setAsText()。

注册:扩展之后要通过注册通知Spring该对某个类型使用那种属性编辑器。

注册是最复杂的部分,通常有两种方法:
 
编程型:直接调用ConfigurableBeanFactory.registerCustomEditor()方法,传入编辑器使用的需要转换的类型和该编辑器本身的实例。

声明型:(推荐方法)在bean配置文件中顶一个一个CustomEditorConfigurer类型的bean,在该Bean的Map类型属性里指定编辑器。原书例程中就使用了这种方法。关于CustomEditorConfigurer到底是什么,详见本文末尾。
 
例程代码:P122,代码清单5-36

首先,类PatternPropertyEditor扩展了PropertyEditorSupport类,并且实现了它的setAsText()方法,并且是该类唯一的方法。

public void setAsText(String text) throws IllegalArgumentException {
 Pattern patter = Pattern.compile(text);
 this.setValue(patter);
}
 
其中setValue(Object)是PropertyEditorSupport类自身的方法,是设置Object的值。
 
其次,P122,Listing 5-37的代码中,类CustomEditorExample中,我们只关心两件事:
1。此Bean中有个searchPattern属性,是我们需要注入的,他的类型是Pattern
2。整个从获取工厂到实现查询的过程,参看如下代码
 
// 获取XmlBeanFactory,过程略...

CustomEditorConfigurer config =
 (CustomEditorConfigurer) factory.getBean("customEditorConfigurer");

config.postProcessBeanFactory(factory);
 
其中customEditorConfigurer注册了我们自定义的属性编辑器
然后通过config.postProcessBeanFactory(factory),修改了工厂配置,这个是关键所在。如果不运行这个,及时在CustomEditorConfigurer中定义了我们的自定义属性编辑器也不会被Spring所使用到。所以,在试图访问需要用自定义PropertyEditor的任何bean之前,一定要确保已经调用了这个方法。
 
最后,我们来看一下怎么配置。P123,Listing 5-38,部分
<bean name="customEditorConfigurer" 
       class="org.springframework.beans.factory.config.CustomEditorConfigurer">
        <property name="customEditors">
            <map>
                <entry key="java.util.regex.Pattern">
                    <bean                          class="com.apress.prospring.ch5.pe.PatternPropertyEditor"/>
                </entry>
            </map>
        </property>
</bean>
 
对于CustomEditorConfigurer类的固定属性customEditors,使用Map方法注入。其中对Pattern类型,定义了一个匿名bean,实现这个匿名bean的类就是刚刚我们自定义的属性编辑器PatternPropertyEditor类。
 
配置文件中,接着,
    <bean id="exampleBean" 
          class="com.apress.prospring.ch5.pe.CustomEditorExample">
        <property name="searchPattern">
            <value>(dog|fox)</value>
        </property>
        <property name="textToSearch">
            <value>The quick brown fox jumped over the lazy dog.</value>
        </property>
    </bean>
在CustomEditorExample中,针对那个Pattern类型的属性searchPattern,我们只给出了它的String类型的值。在运行中就自动被转换成了Pattern类型的值。
 
总之,使用过程归结起来如下:

1。创建扩展自PropertyEditorSupport的类,并且实现其中的setAsText(String text)方法
2。通过推荐的注册方法,在BeanFactory的配置文件中,通过Map方式注册这个步骤【1】中提到的自定义类。
3。在你的应用程序中,一定要保证在使用到任何带有需要用自定义属性编辑器处理属性的bean之前,首先获取CustomEditorConfigurer类的实例,并且调用它的postProcessBeanFactory(BeanFactory factory)方法。
4。之后就可在应用中自由的使用bean了。
 
////////////////////////////////////
遗留的探讨
////////////////////////////////////
 
1。CustomEditorConfigurer类是什么

回答这个问题之前先说BeanFactoryPostProcessor类。这个类能够在应用程序使用BeanFactory的配置之前修改该配置。postProcessBeanFactory(BeanFactory factory)方法就是它的方法之一。

再说CustomEditorConfigurer,它是BeanFactoryPostProcessor的一个示例,没说是实例,用英文原版的话讲:The CustomEditorConfigurer is an example of a BeanFactoryPostProcessor ……,虽然中文版没有进行详细的解释,但是个人认为,这个类更像是BeanFactoryPostProcessor的子类。

其他常见的BeanFactoryPostProcessor的示例还有PropertyPlaceholderConfigurer和PropertyOverrideConfigurer,具体使用参见Spring参考大全2.0版(已由中国Spring社区翻译完毕并发布)。
 
不过使用BeanFactoryPostProcessor和BeanFactory的缺点是,二者不能自动融合,所以我们必须显式调用那个名字很长的post……方法。如果使用ApplicationContext就不会有这麻烦事。
 
2。配置CustomEditorConfigurer应该注意些什么

 


首先,自定义属性编辑器是通过Map类型注入进去的。

其次,Map中的每个entry表示一个属性编辑器,entry的key属性表示属性编辑器应作用的类的名称。

最后,在Map的entry中使用了匿名bean的声明还是有好处的,因为一般情况下,这些匿名bean不为其他的bean所用。当然,情况总是在变化,根据实际需要来决定你的配置文件如何编写吧。
 
3。选用哪种注册方法好?

 


建议使用声明型的方法,好处不言而喻。每人愿意反复编译修改应编码的配置方式,而且也极易出错。

如果采用BeanFactory,编程型和声明型所需编写的代码量相当,但是声明型更好配置。
如果采用ApplicationContext,声明型占绝对优势。
 
4。使用时机

PropertyEditor不是万能的,也是有一定的局限性。
 
如果Object(被注入对象)的整个标识符和他的(值或其他)配置可以表示成字符串时,或者该配置可以通过标识的改变或派生获取时,可以使用PropertyEditor。
 
对于上一段话的前半句,就是Property存在的意义,即把String类型value转换成不同类型的值。后半中的“该配置”句大概是指那些类型相同id不同 或者 是通过bean配置中派生的bean(即只改变个别属性的值,参考spring IoC基础部分)。当然,后半句所述内容说白了还是在需要把String值转化其他类型的值时所遇到的情况,只不过此时Bean的表现形式不同而已。
本文转帖来自http://hi.baidu.com/looyea/blog/item/544978f0bbc9f1c37831aa62.html