概述
如今大量的 Web 站点应用了 AJAX 技术,通过更少的数据通讯,服务器能够更快的反馈用户请求,再通过 Javascript 的控制,让使用者有了更好的用户体验。 JSON 是一种轻量级的数据交换语言,它是 Javascript 的一个子集,又有良好的可读性,经常用于客户端和服务器间的数据交换。因此,在服务器端,常常需要将实体对象( JavaBean )转换为 JSON 格式数据。本文介绍了如何使用 Json-lib 转换 JavaBean 为 JSON 格式数据,并给出解决方法,以及利用 annotation 来增强 Json-lib 的两个功能:一是灵活的筛选 JavaBean 属性;二是通过 JsonValueProcessor 来自定义如何转换 JavaBean 属性到 JSON 数据。
JSON 数据格式
JSON 的数据格式简单易读,它存在于两种基本形式:
· 名值对(Collection ):名称与值用 ‘ : ’ 分开;名值对之间用 ‘ , ’ 分隔;整体用 ‘ {} '括起来。例如 {name1:value1, name2:value2}
· 值的有序队列(Array ):即数组,每个值之间用 ‘ , ’ 分隔;整体用 ‘ [] '括起来。例如: [value1, value2]
这两种形式的有机组合,就形成了 JSON 数据。
使用 Json-lib 转换 JavaBean 为 JSON 数据
Json-lib 是一个 java 工具库,它提供 api 来转换 JavaBean , Map , Collection 等对象为 JSON 数据,或反过来通过 JSON 数据得到 JavaBean 。
Json-lib 的使用很容易,只要使用 JSONSerializer 的 toJSON 方法就可以转换任意的 Java Object 为 JSON 对象了,再调用 JSON 对象的 toString 方法可以得到转换后的字符串。不过还有一些进一步的问题需要我们自己来解决。
import net.sf.json.JSONSerializer;
List list = new ArrayList(); list.add( "first" ); list.add( "second" ); JSON json = JSONSerializer.toJSON( list ); System.out.println( json.toString() ); // prints ["first","second"]
class MyBean{ private String name = "json"; private int pojoId = 1;
// getters & setters ... } json = JSONSerializer.toJSON( new MyBean() ); System.out.println( json.toString() ); // prints {"name":"json","pojoId":1}
|
清单 1 中的例子转换后的 JSON 数据中包含了 JavaBean 中的全部属性,可是我们常常需要有选择的提取 JavaBean 中的特定属性出来。例如:
· 需要过滤掉循环引用的属性,这一点 json-lib 提供了 CycleDetectionStrategy 来处理,但是直接过滤掉更简单;
· 不同的情况下只需要 JavaBean 中的部分属性:比如列表界面只需要显示 Bean 的几个重要属性,而详情界面则需要显示更多的 Bean 的属性;
· 不同的用户权限限制用户只能获得某些属性数据;
对于普通的 Object 类型(如 Long , String 等), json-lib 有缺省的值转换处理方式,但是对于一些特殊的类型,我们希望用自定义的方式来转换该属性的值。例如:
· 对于 java.util.Date 类型,我们希望直接转换成时间串: 2010-04-10 ,而不希望得到一个类似 {"year":"2010","month":"4","day":"10"} 这样的结果
· 对于常用到的代码数据(比如:性别),在定义时它也许是个 Integer (男: 1 ;女: 2 ),我们希望在转换后直接得到: {" 性别 ":" 男 ",...} ,而不是 {" 性别 ":"1",...}
Json-lib 已经预留出一些接口,让用户修改它的缺省行为。下面我们来看看如何利用 annotation 来配合 Json-lib 解决这两个问题。
使用 annotation 筛选 JavaBean 属性
JSONSerializer 提供了一个 toJSON 的重载方法,增加一个参数 JsonConfig ,可以通过这个参数对 Json-lib 的缺省方式做自定义的配置。
清单 2. 自定义 JSONSerializer 的属性过滤器
import net.sf.json.JSONSerializer; import net.sf.json.JsonConfig; import net.sf.json.util.PropertyFilter; // 定义属性过滤器 PropertyFilter filter = new PropertyFilter( public boolean apply(Object source, String name, Object value) { if ( name.equals( “pojoId” ) ) { return true; // 返回 true, 表示这个属性将被过滤掉 } return false; } ); // 注册属性过滤器 JsonConfig config = new JsonConfig(); config.setJsonPropertyFilter( filter );
System.out.println( JSONSerializer.toJSON( new MyBean(), config ) ); // prints {"name":"json"}
|
从清单 2 中可以看出来, Json-lib 通过 PropertyFilter 的 apply 方法进行属性过滤,可以象例子中一样,把所有需要过滤的属性名称写进去,但是这样做太烦琐,也不好维护,对不同的 Bean 要做不同的处理。下面让我们看看怎么利用 annotation 来更方便的处理。
首先,需要定义一个 annotation ,并给 MyBean 的 get 方法加上标注。
清单 3. 定义一个 annotation: Invisible
import java.lang.annotation.Target; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.ElementType; import java.lang.annotation.RetentionPolicy;
@Documented @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface Invisible { public String[] value(); }
// 为 myBean 中需要过滤的属性 get 方法(或者 is 方法)加上 Invisible 标注 public class MyBean{ private String name = "json"; private int pojoId = 1;
// getters & setters public String getName() { return name; } @Invisible(“LIST”) // 在 “LIST” 情况下不要这个属性 public int getPojoId() { return pojoId; } }
|
然后,我们需要一些能处理 annotation 的 PropertyFilter 类。
清单 4. 处理 annotation 的 PropertyFilter 类
import java.util.Map; import java.lang.reflect.Method; import net.sf.json.util.PropertyFilter; // 先实现一个 abstract 类,将读取 Bean 属性的 Method 找到并传递给子类处理 public abstract class AbstractMethodFilter implements PropertyFilter { // 这个方法留给子类实现,以便适应不同的过滤需求 public abstract boolean apply(final Method method);
public boolean apply(final Object source, final String name, final Object value) { if (source instanceof Map) { return false; } String propName = name.substring(0, 1).toUpperCase() + name.substring(1); Class clz = source.getClass(); String methodName = "get" + propName; Method method = null; try { method = clz.getMethod(methodName, (Class[]) null); // 寻找属性的 get 方法 } catch (NoSuchMethodException nsme) { String methodName2 = "is" + propName; // 也许是个 is 方法 try { method = clz.getMethod(methodName2, (Class[]) null); } catch (NoSuchMethodException ne) { // 没有找到属性的 get 或者 is 方法,打印错误,返回 true System.err.println(“No such methods: ” + methodName + “ or “ + methodName2); return true; } } return apply(method); } } // END: AbstractMethodFilter
public class InvisibleFilter extends AbstractMethodFilter { // 过滤条件,标注中有符合这个条件的 property 将被过滤掉 private String _sGUIID; public InvisibleFilter(final String guiid) { _sGUIID = guiid; }
public boolean apply(final Method method) { if (_sGUIID == null || _sGUIID.equals(“”)) { return false; // 表示不做限制 } if (method.isAnnotationPresent(Invisible.class)) { Invisible anno = method.getAnnotation(Invisible.class); String[] value = anno.value(); for (int i = 0; i < value.length; i++) { if (_sGUIID.equals(value[i])) { return true; } } } return false; } }
|
现在只要把这个 filter 注册到 JsonConfig 中,就实现了属性的过滤,请看清单 5 。
清单 5. 使用 InvisibleFilter 来过滤不需要的属性
JsonConfig config = new JsonConfig(); config.setJsonPropertyFilter( new InvisibleFilter(“LIST”)); //标注了 LIST 的属性将被过滤掉
System.out.println( JSONSerializer.toJSON( new MyBean(), config ) ); // prints {"name":"json"}
|
增加其他的 annotation 及 Filter 就可以实现不同的属性过滤方式了。
使用 annotation 自定义 Bean 属性的转换方式
Json-lib 通过 JsonConfig 提供了自定义属性转换方式的接口。
注册后 Json-lib 在遇到 java.uitl.Date 类型的属性时,会应用 JsDateJsonValueProcessor 的处理方法。
所以,只要实现自己的 JsonValueProcessor 就可以自定义各种 Object 的转换方式了。
根据上一节的讨论,Json-lib 在转换 Bean 属性之前,会将属性数据传递给 PropertyFilter 来判断是否需要过滤掉。因此,我们可以通过一个 Filter 对象获得 Bean 的属性的标注数据,并将它传递给特定的 Processor , Processor 根据得到的标注值知道应该怎么处理这个属性。下面以整型代码为例,说明处理的方法。
一般情况下,一个项目中会涉及许多种不同的代码,我们会为每一种代码定义一个主代码号(代码往往都是整型的),为它的子项定义几个子代码号。 例如,我们定义性别的主代码号为 100 ,并定义男: 1 ,女: 2 。
首先,需要一个代码标注(IntegerCode )及一个处理这种标注的 PropertyFilter 。
清单 7. IntegerCode 及 IntegerCodeFilter
JsonConfig config = new JsonConfig(); config.registerJsonValueProcessor(java.util.Date.class, new JsDateJsonValueProcessor());
|
@Documented @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface IntegerCode {
|