求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
  
 
 
 
用 annotation 辅助 Json-lib 转换 JavaBean

2010-07-29 作者:吴 浩 来源:IBM

 

简介: 本文介绍使用 Json-lib 将 JavaBean 转换为 JSON 数据的方法。我们将使用 annotation 来灵巧地筛选 JavaBean 中的属性,并通过 annotation 来定义特殊的 JsonValueProcessor。

内容

概述

如今大量的 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 方法可以得到转换后的字符串。不过还有一些进一步的问题需要我们自己来解决。

清单 1. 使用 JSONSerializer
 
				
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: 需要有选择的提取 JavaBean 中的属性

清单 1 中的例子转换后的 JSON 数据中包含了 JavaBean 中的全部属性,可是我们常常需要有选择的提取 JavaBean 中的特定属性出来。例如:

  • 需要过滤掉循环引用的属性,这一点 json-lib 提供了 CycleDetectionStrategy 来处理,但是直接过滤掉更简单;
  • 不同的情况下只需要 JavaBean 中的部分属性:比如列表界面只需要显示 Bean 的几个重要属性,而详情界面则需要显示更多的 Bean 的属性;
  • 不同的用户权限限制用户只能获得某些属性数据;

问题 2: 需要自定义某些属性的转换方式

对于普通的 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 提供了自定义属性转换方式的接口。

清单 6. 注册 JsonValueProcessor
 
				
JsonConfig config = new JsonConfig();
config.registerJsonValueProcessor(java.util.Date.class, new JsDateJsonValueProcessor());

注册后 Json-lib 在遇到 java.uitl.Date 类型的属性时,会应用 JsDateJsonValueProcessor 的处理方法。

所以,只要实现自己的 JsonValueProcessor 就可以自定义各种 Object 的转换方式了。

根据上一节的讨论,Json-lib 在转换 Bean 属性之前,会将属性数据传递给 PropertyFilter 来判断是否需要过滤掉。因此,我们可以通过一个 Filter 对象获得 Bean 的属性的标注数据,并将它传递给特定的 Processor,Processor 根据得到的标注值知道应该怎么处理这个属性。下面以整型代码为例,说明处理的方法。

一般情况下,一个项目中会涉及许多种不同的代码,我们会为每一种代码定义一个主代码号(代码往往都是整型的),为它的子项定义几个子代码号。例如,我们定义性别的主代码号为 100,并定义男:1,女:2。

首先,需要一个代码标注(IntegerCode)及一个处理这种标注的 PropertyFilter。

清单 7. IntegerCode 及 IntegerCodeFilter
 
				
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface IntegerCode {
    public int value();
}

public class IntegerCodeFilter extends AbstractMethodFilter {
    // 代码处理器
    private IntegerCodeProcessor _processor;
    public IntegerCodeFilter(final IntegerCodeProcessor processor) {
        _processor = processor;
    }

    // 不过滤属性,但当发现IntegerCode标注时,将数据传递给Processor
    public boolean apply(final Method method) {
        if (_processor == null) {
            return false;                                // 表示没有特别的处理
        }
        if (method.isAnnotationPresent(IntegerCode.class)) {
            IntegerCode anno = method.getAnnotation(IntegerCode.class);
            int code = anno.value();
            _processor.setMainCode(code);                // 将code设置为主代码
        }
        return false;
    }
}

现在,我们需要一个 JsonValueProcessor 来处理 IntegerCode 数据。

清单 8. IntegerCodeProcessor
 
				
public class IntegerCodeProcessor implements JsonValueProcessor {

    private int _iMainCode;

    public void setMainCode(final int mainCode) { _iMainCode = mainCode; }

    public IntegerCodeProcessor() {
        super();
    } // END: IntegerCodeProcessor

    private void reset() {
        _iMainCode = -1;
    }

    public Object processArrayValue(Object value, JsonConfig jsonConfig) {
        return process(value, jsonConfig);
    } // END: processArrayValue

    public Object processObjectValue(
            String key, Object value, JsonConfig jsonConfig ) {
        return process( value, jsonConfig );
    } // END: processObjectValue

    private Object process(Object value, JsonConfig jsonConfig) {
        if (value == null) {
            return null;
        }
        String ret = null;
        if (value instanceof Integer && _iMainCode >= 0) {
            int code = value.intValue();
            switch (_iMainCode) {
                case 100:                    // 这里使用简单的case 处理不同的代码
                    if (code == 1) {         // 好一点的方式是从资源文件中读取对应值
                        ret = "man";
                    } else if (code == 2) {
                        ret = "woman";
                    } else {
                        ret = value.toString();
                    }
                    break;
                default:
                    ret = value.toString();
                    break;
            }
        } else {
            ret = value.toString();
        }
        reset();                             // 处理后重置,以免影响其他 Integer 属性

        return ret;
    } // END: process
}

现在,我们创建一个 JavaBean:Student 来测试这个 Processor。

清单 9. 测试 IntegerCodeProcessor
 
				
public class Student {
    private String name = "camry";
    private int gender = 1;
    
    // getters & setters
    public String getName() { return name; }
    @IntegerCode(100)                              // 性别主代码为 100
    public int getGender() { return gender; }}
}

...
IntegerCodeProcessor processor = new IntegerCodeProcessor();
IntegerCodeFilter filter = new IntegerCodeFilter(processor);
JsonConfig config = new JsonConfig();
config.setJsonPropertyFilter( filter );
config.registerJsonValueProcessor(Integer.class, processor);

System.out.println( JSONSerializer.toJSON( new Student(), config ) );
// prints {“gender”:”man”, "name":"camry"}

结束语

Json-lib 可以很方便的进行 JavaBean 到 JSON 数据的转换,再结合 annotation 为 JavaBean 的属性定义元数据,可以减少代码,提高效率。

参考资料

学习

  • JSON 入门指南”(developerWorks,2008 年 8 月):本文将快速讲解 JSON 格式,并通过代码示例演示如何分别在客户端和服务器端进行 JSON 格式数据的处理。
  • 掌握 Ajax,第 10 部分: 使用 JSON 进行数据传输”(developerWorks,2007 年 4 月):讨论另一种有用的数据格式 JSON,以及如何使用它更轻松地在应用程序中移动数据和对象。
  • 参考 JSON 主页,了解 JSON 数据格式的详细说明。
  • 参考“Json-lib 主页”,了解 Json-lib 的实现与使用。
  • 参考“Java 站点”,了解更多关于 annotation 的知识。
  • 技术书店:浏览关于这些和其他技术主题的图书。
  • developerWorks Java 技术专区:数百篇关于 Java 编程各个方面的文章。

讨论



Java 中的中文编码问题
Java基础知识的三十个经典问答
玩转 Java Web 应用开发
使用Spring更好地处理Struts
用Eclipse开发iPhone Web应用
插件系统框架分析
更多...   


Struts+Spring+Hibernate
基于J2EE的Web 2.0应用开发
J2EE设计模式和性能调优
Java EE 5企业级架构设计
Java单元测试方法与技术
Java编程方法与技术


Struts+Spring+Hibernate/EJB+性能优化
华夏基金 ActiveMQ 原理与管理
某民航公司 Java基础编程到应用开发
某风电公司 Java 应用开发平台与迁移
日照港 J2EE应用开发技术框架与实践
某跨国公司 工作流管理JBPM
东方航空公司 高级J2EE及其前沿技术
更多...