3.4 自定义转换器
对于大部分应用而言,我们使用JSF内置的各种转换器就足够了;但对于一些极端的情形,我们可能需要开发自定义转换器,从而可以将用户输入的数据转换为各种自定义类型。
3.4.1 实现转换器类
除了可以使用JSF内建支持的类型转换之外,JSF也允许用户自定义类型转换器,自定义类型转换器可以将数据转换成任何其他数据类型。当用户开发了自定义类型转换器之后,开发者必须在组件上使用<f:converter…/>标签显式注册自定义类型转换器。
总体来说,如果需要在应用中创建并使用自定义转换器,我们必须完成如下3件事情:
实现自己的转换器类。
在faces-config.xml文件中注册类型转换器。
在JSF页面中通过组件的converter属性或者内嵌<f:converter…/>标签来使用类型转换器。
JSF规范为转换器提供了一个javax.faces.convert.Converter接口,所有转换器都应该实现该接口。实现该接口还需要实现如下两个方法:
getAsObject(FacesContext context, UIComponent component, java.lang.String value):该方法实现从字符串类型到目标类型的转换。
getAsString(FacesContext context, UIComponent component, java.lang.Object value):该方法实现目标类型到字符串类型的转换。
假如有如下页面:
程序清单:codes\03\3.4\ClassRegist\add.jsp
<h1>添加用户</h1>
<h:form>
用户名:<h:inputText value="#{userBean.name}"/><br/>
儿子:<h:inputText value="#{userBean.son}">
</h:inputText>(请按"姓名:身高:年龄"的格式输入)
<h:commandButton value="添加" action="#{userBean.add}"/>
</h:form>
上面<h:inputText…/>标签的值被绑定到userBean的son属性,该属性是org.crazyit.jsf.Son类型,它是一个自定义类型,显然JSF没法对该类型的值进行类型转换。Son的代码比较简单,其类代码如下:
程序清单:codes\03\3.4\ClassRegist\WEB-INF\src\org\crazyit\jsf\Son.java
public class Son { private String name; private double height; private int age; //无参数的构造器 public Son() { } //初始化全部属性的构造器 public Son(String name , double height , int age) { this.name = name; this.height = height; this.age = age; } //省略3个属性的setter和getter方法 … }
为此我们需要为上面的Son类提供一个自定义类型转换器,该类型转换器的代码如下:
程序清单:codes\03\3.4\ClassRegist\WEB-INF\src\org\crazyit\converter\SonConverter.java
public class SonConverter implements Converter { //实现从字符串类型向目标类型转换的方法 public Object getAsObject(FacesContext context, UIComponent component, String value) { String[] values = value.split(":"); Son son = new Son(values[0] , Double.parseDouble(values[1]) , Integer.parseInt(values[2])); return son; } //实现从目标类型向字符串类型转换的方法 public String getAsString(FacesContext context, UIComponent component, Object value) { Son son = (Son)value; return "Son[name=" + son.getName() + ", height=" + son.getHeight() + ", age=" + son.getAge() + "]"; } }
实现上面转换器时实现了两个方法:getAsObject方法负责完成字符串到目标类型的转换;getAsString完成从目标类型到字符串的转换。实现该转换器之后,接下来就注册该转换器了。
3.4.2 注册转换器
在JSF中注册转换器通过faces-config.xml文件完成,在该文件中通过根元素下的<converter…/>元素即可成功注册转换器。
JSF配置文件中<converter…/>元素的内部结构如图3.22所示。
图3.22 <converter…/>元素的内部结构
从图3.22可以看出,在<converter…/>元素中可以指定如下必需的子元素:
<converter-id…/>或<converter-for-class…/>其中之一:指定转换器的名称或为某个类型注册转转器。
<converter-class…/>:指定自定义转换器的实现类。
其实上面两个互斥的子元素代表了JSF对自定义转换器支持的两种注册方式:
类型注册:使用<converter-for-class…/>完成注册。
名称注册:使用<converter-id…/>为转换器指定名称。
如果采用类型注册的方式来注册自定义类型转换器,则JSF会自动利用该转换器为指定类型数据执行转换;如果使用名称注册的方式来注册自定义类型转换器,则还需要在JSF页面中使用<f:converter…/>标签来显式引用某个转换器。
下面我们先使用类型注册的方式来注册该自定义类型转换器,在faces-config.xml文件中添加如下代码即可完成注册。
程序清单:codes\03\3.4\ClassRegist\WEB-INF\faces-config.xml
<!-- 注册转换器 --> <converter> <!-- 指定对Son类型使用该自定义类型转换器 --> <converter-for-class>org.crazyit.jsf.Son</converter-for-class> <converter-class>org.crazyit.converter.SonConverter</converter-class> </converter>
上面配置片段指定对Son自定义类使用SonConverter转换器。上面页面中有一个<h:inputText…/>组件的值被绑定到Son类型的属性,因此该转换器将会对该属性起作用。
如果在输入页面按要求格式在“儿子”输入框填入数据,提交表单后将看到如图3.23所示的页面
图3.23 自定义类型转换器
如果使用类型注册的方式来注册自定义转换器,则注册之后无须额外工作,自定义转换器将自动对指定类型的属性起作用;如果使用名称注册的方式来注册自定义类型转换器,则还需要在页面显式使用自定义类型转换器。
在faces-config.xml文件中增加如下代码片段完成名称注册。
程序清单:codes\03\3.4\IDRegist\WEB-INF\faces-config.xml
<!-- 注册转换器 -->
<converter>
<!-- 为自定义类型转换器指定名称 -->
<converter-id>sonConverter</converter-id>
<converter-class>org.crazyit.converter.SonConverter</converter-class>
</converter>
上面注册方式指定了该转换器的名称是sonConverter,这就允许开发者通过该名称来引用该自定义类型转换器。
这里我们对前面提供的自定义类型转换器做一些改进:JSF自定义类型转换器转换失败时会提示一定的错误信息,这对实际应用非常有帮助——当自定义转换器的某个转换方法抛出Converter Exception异常时,JSF转换机制里的消息机制就会起作用了,为此我们将前面的JSF自定义转换器的代码改为如下形式。
程序清单:codes\03\3.4\IDRegist\WEB-INF\src\org\crazyit\converter\SonConverter.java
public class SonConverter
implements Converter
{
//实现从字符串类型向目标类型转换的方法
public Object getAsObject(FacesContext context,
UIComponent component, String value)throws ConverterException
{
try
{
String[] values = value.split(":");
Son son = new Son(values[0]
, Double.parseDouble(values[1])
, Integer.parseInt(values[2]));
return son;
}
//捕获所有自定义异常
catch (Exception ex)
{
ex.printStackTrace();
//抛出ConverterException异常
throw new ConverterException("无法转换!");
}
}
//实现从目标类型向字符串类型转换的方法
public String getAsString(FacesContext context,
UIComponent component, Object value)
{
Son son = (Son)value;
return "Son[name=" + son.getName()
+ ", height=" + son.getHeight()
+ ", age=" + son.getAge() + "]";
}
}
上面粗体字代码捕捉了转换过程中抛出的原生异常,并且将该异常重新包装成ConverterException异常,这样就可以利用JSF类型转换过程中的消息机制了。
由于上面类型转换器的注册方式是名称注册,因此必须在页面中显式引用该自定义类型转换器,在页面中通过<f:converter…/>标签来显式引用自定义类型转换器。使用<f:converter…/>标签时可指定如下两个属性:
converterId:该属性指定某个类型转换器的名称。
binding:该属性用于将类型转换器绑定到Bean属性。
页面代码如下所示。
程序清单:codes\03\3.4\IDRegist\IDRegist.jsp
<h1>添加用户</h1> <h:form> 用户名:<h:inputText value="#{userBean.name}"/><br/> 儿子:<h:inputText value="#{userBean.son}" id="son" converterMessage="您为儿子属性输入的字符串格式不正确!"> <f:converter converterId="sonConverter"/> </h:inputText>(请按"姓名:身高:年龄"的格式输入) <h:message for="son" style="color:red;font-weight:bold"/><br/> <h:commandButton value="添加" action="#{userBean.add}"/> </h:form>
上面页面代码中粗体字代码分别为转换失败时指定了错误消息,显式指定了使用sonConverter转换器,并使用<h:message…/>标签来输出转换失败的错误消息。
如果用户输入了满足格式的数据,单击“提交”按钮将看到如图3.24所示的效果。
图3.24 以名称注册方式注册的自定义类型转换器
将图3.23与图3.24放在一起对比不难发现:当采用类型注册的方式注册时,该转换器将自动对Son类型的属性起作用,包括将字符串转换成Son对象和将Son对象转换成字符串;当采用名称注册的方式注册时,必须显式引用转换器才能让它起作用。因为输入页面(add.jsp)显式使用<f:converter…/>标签来引用该转换器,因此它可将用户输入的字符串转换成Son对象;但显示页面(show.jsp)未显式引用该转换器,因此它将不再起作用了。
如果用户输入的字符串不能成功转换为Son对象,自定义转换器将会抛出ConverterException异常,该异常将会让页面呈现转换失败的错误提示,如图3.25所示。
图3.25 自定义类型转换器转换失败的错误提示
3.4.3 使用自定义转换器
当以名称注册的方式完成自定义类型转换器的注册之后,除了可使用<f:converter…/>标签来显式引用某个转换器之外,还可通过UI组件的converter属性来引用某个转换器。例如,我们将页面代码改为如下形式也是允许的。
程序清单:codes\03\3.4\Converter\IDRegist.jsp
<h1>添加用户</h1> <h:form> 用户名:<h:inputText value="#{userBean.name}"/><br/> 儿子:<h:inputText value="#{userBean.son}" id="son" converterMessage="您为儿子属性输入的字符串格式不正确!" converter="sonConverter"> </h:inputText>(请按"姓名:身高:年龄"的格式输入) <h:message for="son" style="color:red;font-weight:bold"/><br/> <h:commandButton value="添加" action="#{userBean.add}"/> </h:form>
3.4.4 绑定到Bean属性的转换器
前面介绍<f:converter…/>标签时已经指出,可通过该标签的binding属性将转换器本身绑定到托管Bean的某个属性,这样即可让托管Bean获得对转换器的全部控制,从而灵活地操作转换器。
当然,要将某个转换器绑定到托管Bean的某个属性时,该Bean属性的类型应该是Converter或其实现类。下面托管Bean中的converter属性可绑定到某个转换器。
程序清单:codes\03\3.4\bindingConverter\WEB-INF\src\org\crazyit\jsf\UserBean.java
public class UserBean { private String name; private Son son; private Converter converter; //无参数的构造器 public UserBean() { } //初始化全部属性的构造器 public UserBean(String name , Son son) { this.name = name; this.son = son; } //省略name属性的setter和getter方法 … //省略son属性的setter和getter方法 … //converter属性的setter和getter方法 public void setConverter(Converter converter) { this.converter = converter; } public Converter getConverter() { //采用匿名内部类实现一个转换器 return new Converter() { //实现从字符串类型向目标类型转换的方法 public Object getAsObject(FacesContext context, UIComponent component, String value) throws ConverterException { try { String[] values = value.split(":"); Son son = new Son(values[0] , Double.parseDouble(values[1]) , Integer.parseInt(values[2])); return son; } //捕获所有自定义异常 catch (Exception ex) { ex.printStackTrace(); //抛出ConverterException异常 throw new ConverterException("无法转换!"); } } //实现从目标类型向字符串类型转换的方法 public String getAsString(FacesContext context, UIComponent component, Object value) { Son son = (Son)value; return "Son[name=" + son.getName() + ", height=" + son.getHeight() + ", age=" + son.getAge() + "]"; } }; } //编写处理导航的方法 public String add() { return "success"; } }
在上面UserBean代码中,getConverter()方法采用匿名内部类的方式返回了一个自定义转换器,这意味着使用faces-config.xml文件配置该托管Bean之后,即可在页面中将转换器本身绑定到该属性。页面代码如下所示。
程序清单:codes\03\3.4\bindingConverter\add.jsp
<h1>添加用户</h1>
<h:form>
用户名:<h:inputText value="#{userBean.name}"/><br/>
儿子:<h:inputText value="#{userBean.son}" id="son"
converterMessage="您为儿子属性输入的字符串格式不正确!">
<f:converter binding="#{userBean.converter}"/>
</h:inputText>(请按"姓名:身高:年龄"的格式输入)
<h:message for="son" style="color:red;font-weight:bold"/><br/>
<h:commandButton value="添加" action="#{userBean.add}"/>
</h:form>
上面页面代码中粗体字代码实现了将转换器本身绑定到托管Bean的属性,这样就可让托管Bean更灵活地控制转换器了。