背景
应用切面做日志记录,记录操作实体详情时可以使用JSON格式,这里是我使用Gson包做切面日志的一些实践,总结了遇到的问题。
软件包和学习方法
这里使用google的Gson包做JSON转换, 这里是项目的地址,可以查看上面的API和User Guide。务必将源码和API配置好,文档中的记录不是很完善,很多时候需要查看源码。
注意,我使用的是2.2版,因为较早的1.4版本的FieldAttributes类中没有getDeclaringClass()这个方法,这个方法是获取field所属的类,在我的排除策略中会用到。
排除策略
最简单的gson转换可以是这样的,但却没有多少实际的作用。切面日志时,一个实体和其他实体存在关联,这时候就需要通过自定义排除策略决定如何转换关联对象,否则可能出现“爆炸式”的json字符串。
下面是我定义的一个排除策略的类,能基本满足需求,从内网搬过来的,未测试
使用的时候是这样的
HibernateProxy异常处理
在使用Hibernate时,那么很可能遇到 这样的错误:
java.lang.UnsupportedOperationException: Attempted to serialize java.lang.Class: org.hibernate.proxy.HibernateProxy. Forgot to register a type adapter?
因为gson在转换时是使用的反射机制,当获取的实体对象还在hibernate代理的时候,例如刚通过Id获取到,这时候获取到的便是代理对象HibernateProxy。这和直接调用实体对象的get方法不同,获取对象的属性就不能起作用。
解决的方法便是将代理对象实例化,见下面的代码
使用的时候将该TypeAdapter的Factory注册到GsonBuilder,上面的代码变为
Javascript格式化
默认情况下,在页面上显示json字符串时是连在一起的,格式化一下可以便于阅读:
这里要注意的是IE8在转换的时候会出现问题,所以需要引入 json.js,将里面的JSON换位JSON2来使用,这时候代码便是这样的
应用切面做日志记录,记录操作实体详情时可以使用JSON格式,这里是我使用Gson包做切面日志的一些实践,总结了遇到的问题。
软件包和学习方法
这里使用google的Gson包做JSON转换, 这里是项目的地址,可以查看上面的API和User Guide。务必将源码和API配置好,文档中的记录不是很完善,很多时候需要查看源码。
注意,我使用的是2.2版,因为较早的1.4版本的FieldAttributes类中没有getDeclaringClass()这个方法,这个方法是获取field所属的类,在我的排除策略中会用到。
排除策略
最简单的gson转换可以是这样的,但却没有多少实际的作用。切面日志时,一个实体和其他实体存在关联,这时候就需要通过自定义排除策略决定如何转换关联对象,否则可能出现“爆炸式”的json字符串。
- Gson gson = new Gson();
- int[] ints = {1, 2, 3, 4, 5};
- String[] strings = {"abc", "def", "ghi"};
- // Serialization
- gson.toJson(ints); ==> prints [1,2,3,4,5]
- gson.toJson(strings); ==> prints ["abc", "def", "ghi"]
下面是我定义的一个排除策略的类,能基本满足需求,从内网搬过来的,未测试
- package com.lingceng.magic.logutil;
- import org.apache.commons.lang.ArrayUtils;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import com.google.gson.ExclusionStrategy;
- import com.google.gson.FieldAttributes;
- public class TargetStrategy implements ExclusionStrategy {
- private static Logger log = LoggerFactory.getLogger(TargetStrategy.class);
- private Class<?> target;
- private String[] fields;
- private Class<?>[] clazz;
- private boolean reverse;
- public TargetStrategy(Class<?> target) {
- super();
- this.target = target;
- }
- @Override
- public boolean shouldSkipClass(Class<?> class1) {
- return false;
- }
- @Override
- public boolean shouldSkipField(FieldAttributes fieldattributes) {
- Class<?> owner = fieldattributes.getDeclaringClass();
- Class<?> c = fieldattributes.getDeclaredClass();
- String f = fieldattributes.getName();
- boolean isSkip = false;
- if (owner == target) {
- if (ArrayUtils.contains(fields, f)) {
- log.debug("fitler field:{} for class:{}", f, owner);
- isSkip = true;
- }
- if (ArrayUtils.contains(clazz, c)) {
- log.debug("fitler class:{} for class:{}", c, owner);
- isSkip = true;
- }
- if (reverse) {
- isSkip = !isSkip;
- }
- }
- return isSkip;
- }
- public void setFields(String[] fields) {
- this.fields = fields;
- }
- public void setClazz(Class<?>[] clazz) {
- this.clazz = clazz;
- }
- public void setReverse(boolean reverse) {
- this.reverse = reverse;
- }
- }
使用的时候是这样的
- TargetStrategy ts = new TargetStrategy(Student.class);
- //这里表示仅转换Student中的id和name属性
- ts.setFields(new String[] {"id", "name"});
- ts.setReverse(true);
- Gson gson = new GsonBuilder().setExcludeStrategy(ts).create();
- gson.toJson(teacher);
HibernateProxy异常处理
在使用Hibernate时,那么很可能遇到 这样的错误:
java.lang.UnsupportedOperationException: Attempted to serialize java.lang.Class: org.hibernate.proxy.HibernateProxy. Forgot to register a type adapter?
因为gson在转换时是使用的反射机制,当获取的实体对象还在hibernate代理的时候,例如刚通过Id获取到,这时候获取到的便是代理对象HibernateProxy。这和直接调用实体对象的get方法不同,获取对象的属性就不能起作用。
解决的方法便是将代理对象实例化,见下面的代码
- /**
- * This TypeAdapter unproxies Hibernate proxied objects, and serializes them
- * through the registered (or default) TypeAdapter of the base class.
- */
- public class HibernateProxyTypeAdapter extends TypeAdapter<HibernateProxy> {
- public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
- @Override
- @SuppressWarnings("unchecked")
- public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
- return (HibernateProxy.class.isAssignableFrom(type.getRawType()) ? (TypeAdapter<T>) new HibernateProxyTypeAdapter(gson) : null);
- }
- };
- private final Gson context;
- private HibernateProxyTypeAdapter(Gson context) {
- this.context = context;
- }
- @Override
- public HibernateProxy read(JsonReader in) throws IOException {
- throw new UnsupportedOperationException("Not supported");
- }
- @SuppressWarnings({"rawtypes", "unchecked"})
- @Override
- public void write(JsonWriter out, HibernateProxy value) throws IOException {
- if (value == null) {
- out.nullValue();
- return;
- }
- // Retrieve the original (not proxy) class
- Class<?> baseType = Hibernate.getClass(value);
- // Get the TypeAdapter of the original class, to delegate the serialization
- TypeAdapter delegate = context.getAdapter(TypeToken.get(baseType));
- // Get a filled instance of the original class
- Object unproxiedValue = ((HibernateProxy) value).getHibernateLazyInitializer()
- .getImplementation();
- // Serialize the value
- delegate.write(out, unproxiedValue);
- }
- }
使用的时候将该TypeAdapter的Factory注册到GsonBuilder,上面的代码变为
- Gson gson = new GsonBuilder().setExcludeStrategy(ts)
- .registerTypeAdapterFactory(HibernateProxyTypeAdapter.FACTORY)
- .create();
- gson.toJson(teacher);
Javascript格式化
默认情况下,在页面上显示json字符串时是连在一起的,格式化一下可以便于阅读:
- // get text for textarea, do not use text()
- var msg= $("#jsonText").val();
- // parse json
- msg = JSON.parse(msg);
- // return format string
- msg = JSON.stringify(msg, null, 4);
这里要注意的是IE8在转换的时候会出现问题,所以需要引入 json.js,将里面的JSON换位JSON2来使用,这时候代码便是这样的
- // get text for textarea, do not use text()
- var msg= $("#jsonText").val();
- // parse json
- msg = JSON2.parse(msg);
- // return format string
- msg = JSON2.stringify(msg, null, 4);