Gson切面日志实践
背景
应用切面做日志记录,记录操作实体详情时可以使用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"};// Serializationgson.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;}@Overridepublic boolean shouldSkipClass(Class<?> class1) {return false;}@Overridepublic 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);/** * 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); }}Gson gson = new GsonBuilder().setExcludeStrategy(ts).registerTypeAdapterFactory(HibernateProxyTypeAdapter.FACTORY).create();gson.toJson(teacher);
// get text for textarea, do not use text()var msg= $("#jsonText").val();// parse jsonmsg = JSON.parse(msg);// return format stringmsg = JSON.stringify(msg, null, 4);// get text for textarea, do not use text()var msg= $("#jsonText").val();// parse jsonmsg = JSON2.parse(msg);// return format stringmsg = JSON2.stringify(msg, null, 4);