1. 程式人生 > >徹底解決SSH架構中的Hibernate懶載入問題

徹底解決SSH架構中的Hibernate懶載入問題

       在客戶端使用AJAX框架,服務端採用Struts2+Spring+Hibernate的架構中,經常會遇到Hibernate懶載入的問題 ,異常為:

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role


      相信大家經常可以在網上看到相關的技術文章,大致是在web.xml中加上openSessionFilter,程式碼如下:

  <filter>
    <filter-name>hibernateFilter</filter-name>
    <filter-class>org.springframework.orm.hibernate4.support.OpenSessionInViewFilter</filter-class>
  </filter>

        如果你真的親自去寫程式碼了,你會發現這個方法根本不管用,原因在於Struts2的JSON外掛使用反射技術,會呼叫關聯物件的get方法獲取物件的例項,從而觸發Hibernate查詢資料,導致本異常。

       本文的系統所採用的架構為Hibernate4、Spring4、Struts2,客戶端使用Ext JS4作為WEB介面框架,Ext JS4和Struts2互動使用JSON格式的資料,故使用了struts_json外掛,Struts2的配置則採用convention外掛,即註解的配置方式,Hibernate4採用JPA註解方式配置物件關係對映 ,先看一下POJO的關係對映

     1、MenuPanel,一的一方:

package com.mcs.user.pojo;

import java.util.ArrayList;
import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.OneToMany;
import javax.persistence.Table;

import org.hibernate.annotations.Columns;

import com.mcs.pojo.base.GenericObject;

@Entity
@Table(name = "MENUPANEL")
public class MenuPanel extends GenericObject {
	private String text;
	private List<Menu> menus=new ArrayList<Menu>();

	public MenuPanel() {
	}

	public MenuPanel(String text) {
		this.text = text;
	}
	public MenuPanel(long id,String text) {
		this.text = text;
		super.setId(id+"");
	}

	public String getText() {
		return text;
	}

	public void setText(String text) {
		this.text = text;
	}

	@OneToMany(cascade = {CascadeType.ALL}, fetch = FetchType.LAZY,mappedBy="menuPanel")
	public List<Menu> getMenus() {
		return menus;
	}

	public void setMenus(List<Menu> menus) {
		this.menus = menus;
	}

}

      2、menu多的一方:

package com.mcs.user.pojo;

import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import static org.hibernate.annotations.CascadeType.SAVE_UPDATE;
import org.hibernate.annotations.Cascade;

import com.mcs.pojo.base.GenericObject;


@Entity
@Table(name="MENU")
public class Menu extends GenericObject{
	private String text;
	private String url;
	private boolean leaf=true;//預設是葉子節點
	private MenuPanel menuPanel;
	private List<Menu> children;
	private Menu menu;
	
	
	public Menu() {
	}

	public Menu(String text, String url) {
		super();
		this.text = text;
		this.url = url;
	}
	public Menu(long id,String text, String url,MenuPanel menuPanel) {
		super();
		super.setId(id+"");
		this.text = text;
		this.url = url;
		this.menuPanel=menuPanel;
	}
	public String getText() {
		return text;
	}

	public void setText(String text) {
		this.text = text;
	}

	public String getUrl() {
		return url;
	}

	public void setUrl(String url) {
		this.url = url;
	}

	@ManyToOne(cascade={CascadeType.PERSIST,CascadeType.REMOVE}, targetEntity=MenuPanel.class)
	@JoinColumn(name="menuPanelId",referencedColumnName="id",insertable=true,updatable=true)
	public MenuPanel getMenuPanel() {
		return menuPanel;
	}

	public void setMenuPanel(MenuPanel menuPanel) {
		this.menuPanel = menuPanel;
	}

	@Column(length=1000)
	public boolean isLeaf() {
		return leaf;
	}

	public void setLeaf(boolean leaf) {
		this.leaf = leaf;
	}

	@OneToMany(cascade=CascadeType.ALL,fetch=FetchType.LAZY,mappedBy="menu")
	public List<Menu> getChildren() {
		return children;
	}

	public void setChildren(List<Menu> children) {
		this.children = children;
	}

	@ManyToOne(cascade={CascadeType.PERSIST,CascadeType.MERGE}, targetEntity=Menu.class)
	@JoinColumn(name="parentId",referencedColumnName="id",insertable=true,updatable=true)
	public Menu getMenu() {
		return menu;
	}

	public void setMenu(Menu menu) {
		this.menu = menu;
	}
	
	
}

       manuPanel和menu是一對多的關係,manuPanel是一的一方,menu是多的一方,採用懶載入的方式,Struts2使用JSON外掛進行JSON資料轉換,配置如下

@Action(value="queryMenu",results = { @Result(type = "json",params={"root","pageBean"})})
	public String queryMenu() {
		
		menuPanels=menuPanelService.queryMenuPanel();
		this.pageBean.setRows(menuPanels);
		this.pageBean.setTotal(menuPanels.size());//不使用分頁
		return "success";
	}

        讓Struts2支援Hibernate懶載入有三種方式,第一種:配置JSON外掛需要排除的欄位,第二種:使用DTO或者VO物件,可以解決懶載入的問題,並且可以有效減少不相關的呼叫,當然,在專案中使用DTO或VO物件,取決於專案架構的,我個人比較推崇使用DTO或者VO,但發現在專案組很難實行,第三種:修改JSON轉換外掛,可以徹底解決懶加的問題。咱們先介紹第一種:

@Action(value="queryMenu",results = { @Result(type = "json",params={"root","pageBean","excludeProperties","^.*menus$,^.*children$"})})
	public String queryMenu() {
		
		menuPanels=menuPanelService.queryMenuPanel();
		this.pageBean.setRows(menuPanels);
		this.pageBean.setTotal(menuPanels.size());//不使用分頁
		return "success";
	}

        上面這個程式碼我們用的是正則表示式進行欄位匹配,要排除多個欄位,用逗號隔開。好了,現在不會報錯了,資料也能載入了,但是如果我們有很多POJO類都使用懶載入,每次都要這麼配置,簡直就是一件痛苦的事,有沒有一勞永逸的解決辦法呢,答案是當然有,我們通過對JSON外掛進行修改,讓Struts2_JSON外掛支援Hibernate懶載入,這樣,就不用每次在Action中配置排除屬性了,

      首先,在protected void bean(Object object)方法中的Object value = accessor.invoke(object);後面加入以下程式碼

					Object value = accessor.invoke(object);
					//如果是PersistentBag型別的例項,則是Hibernate的持久化物件
					if (value instanceof PersistentBag) {
						// 判斷bag物件是否已經初始化,如果沒有初始化,則是懶載入的方式
						PersistentBag bag=(PersistentBag) value;
						boolean b=Hibernate.isInitialized(bag);
						if(b==false)
						{
							continue;							
						}
					}
					if (baseAccessor.isAnnotationPresent(JSONFieldBridge.class)) {....


     然後將protected void value(Object object, Method method)方法中的LOG.debug("Cyclic reference detected on " + object);改為

					//將object改為取class類名,避免Hibernate懶載入錯誤
					LOG.debug("Cyclic reference detected on " + clazz.getName());

     完整程式碼如下:

/*
 * $Id: JSONWriter.java 1436290 2013-01-21 11:37:16Z lukaszlenart $
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.struts2.json;

import com.opensymphony.xwork2.util.logging.Logger;
import com.opensymphony.xwork2.util.logging.LoggerFactory;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.beanutils.PropertyUtilsBean;
import org.apache.struts2.json.annotations.JSON;
import org.apache.struts2.json.annotations.JSONFieldBridge;
import org.apache.struts2.json.annotations.JSONParameter;
import org.apache.struts2.json.bridge.FieldBridge;
import org.apache.struts2.json.bridge.ParameterizedBridge;
import org.apache.velocity.texen.util.PropertiesUtil;
import org.hibernate.Hibernate;
import org.hibernate.collection.internal.PersistentBag;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.text.CharacterIterator;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.text.StringCharacterIterator;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.regex.Pattern;

/**
 * <p>
 * Serializes an object into JavaScript Object Notation (JSON). If cyclic
 * references are detected they will be nulled out.
 * </p>
 */
public class JSONWriter {

	private static final Logger LOG = LoggerFactory.getLogger(JSONWriter.class);

	/**
	 * By default, enums are serialised as name=value pairs
	 */
	public static final boolean ENUM_AS_BEAN_DEFAULT = false;

	private static char[] hex = "0123456789ABCDEF".toCharArray();

	private static final ConcurrentMap<Class<?>, BeanInfo> BEAN_INFO_CACHE_IGNORE_HIERARCHY = new ConcurrentHashMap<Class<?>, BeanInfo>();
	private static final ConcurrentMap<Class<?>, BeanInfo> BEAN_INFO_CACHE = new ConcurrentHashMap<Class<?>, BeanInfo>();

	private StringBuilder buf = new StringBuilder();
	private Stack<Object> stack = new Stack<Object>();
	private boolean ignoreHierarchy = true;
	private Object root;
	private boolean buildExpr = true;
	private String exprStack = "";
	private Collection<Pattern> excludeProperties;
	private Collection<Pattern> includeProperties;
	private DateFormat formatter;
	private boolean enumAsBean = ENUM_AS_BEAN_DEFAULT;
	private boolean excludeNullProperties;

	/**
	 * @param object
	 *            Object to be serialized into JSON
	 * @return JSON string for object
	 * @throws JSONException
	 */
	public String write(Object object) throws JSONException {
		return this.write(object, null, null, false);
	}

	/**
	 * @param object
	 *            Object to be serialized into JSON
	 * @return JSON string for object
	 * @throws JSONException
	 */
	public String write(Object object, Collection<Pattern> excludeProperties,
			Collection<Pattern> includeProperties, boolean excludeNullProperties)
			throws JSONException {
		this.excludeNullProperties = excludeNullProperties;
		this.buf.setLength(0);
		this.root = object;
		this.exprStack = "";
		this.buildExpr = ((excludeProperties != null) && !excludeProperties
				.isEmpty())
				|| ((includeProperties != null) && !includeProperties.isEmpty());
		this.excludeProperties = excludeProperties;
		this.includeProperties = includeProperties;
		this.value(object, null);

		return this.buf.toString();
	}

	/**
	 * Detect cyclic references
	 */
	protected void value(Object object, Method method) throws JSONException {
		if (object == null) {
			this.add("null");

			return;
		}

		if (this.stack.contains(object)) {
			Class clazz = object.getClass();

			// cyclic reference
			if (clazz.isPrimitive() || clazz.equals(String.class)) {
				this.process(object, method);
			} else {
				if (LOG.isDebugEnabled()) {
					//將object改為取class類名,避免Hibernate懶載入錯誤
					LOG.debug("Cyclic reference detected on " + clazz.getName());
				}
				this.add("null");
			}

			return;
		}

		this.process(object, method);
	}

	/**
	 * Serialize object into json
	 */
	protected void process(Object object, Method method) throws JSONException {
		this.stack.push(object);

		if (object instanceof Class) {
			this.string(object);
		} else if (object instanceof Boolean) {
			this.bool((Boolean) object);
		} else if (object instanceof Number) {
			this.add(object);
		} else if (object instanceof String) {
			this.string(object);
		} else if (object instanceof Character) {
			this.string(object);
		} else if (object instanceof Map) {
			this.map((Map) object, method);
		} else if (object.getClass().isArray()) {
			this.array(object, method);
		} else if (object instanceof Iterable) {
			this.array(((Iterable) object).iterator(), method);
		} else if (object instanceof Date) {
			this.date((Date) object, method);
		} else if (object instanceof Calendar) {
			this.date(((Calendar) object).getTime(), method);
		} else if (object instanceof Locale) {
			this.string(object);
		} else if (object instanceof Enum) {
			this.enumeration((Enum) object);
		} else {
			processCustom(object, method);
		}

		this.stack.pop();
	}

	/**
	 * Serialize custom object into json
	 */
	protected void processCustom(Object object, Method method)
			throws JSONException {
		this.bean(object);
	}

	/**
	 * Instrospect bean and serialize its properties
	 */
	protected void bean(Object object) throws JSONException {
		this.add("{");

		BeanInfo info;

		try {
			Class clazz = object.getClass();

			info = ((object == this.root) && this.ignoreHierarchy) ? getBeanInfoIgnoreHierarchy(clazz)
					: getBeanInfo(clazz);

			PropertyDescriptor[] props = info.getPropertyDescriptors();

			boolean hasData = false;
			for (PropertyDescriptor prop : props) {
				String name = prop.getName();
				Method accessor = prop.getReadMethod();
				Method baseAccessor = findBaseAccessor(clazz, accessor);

				if (baseAccessor != null) {
					if (baseAccessor.isAnnotationPresent(JSON.class)) {
						JSONAnnotationFinder jsonFinder = new JSONAnnotationFinder(
								baseAccessor).invoke();

						if (!jsonFinder.shouldSerialize())
							continue;
						if (jsonFinder.getName() != null) {
							name = jsonFinder.getName();
						}
					}
					// ignore "class" and others
					if (this.shouldExcludeProperty(prop)) {
						continue;
					}
					String expr = null;
					if (this.buildExpr) {
						expr = this.expandExpr(name);
						if (this.shouldExcludeProperty(expr)) {
							continue;
						}
						expr = this.setExprStack(expr);
					}

					Object value = accessor.invoke(object);
					//如果是PersistentBag型別的例項,則是Hibernate的持久化物件
					if (value instanceof PersistentBag) {
						// 判斷bag物件是否已經初始化,如果沒有初始化,則是懶載入的方式
						PersistentBag bag=(PersistentBag) value;
						boolean b=Hibernate.isInitialized(bag);
						if(b==false)
						{
							continue;							
						}
					}
					if (baseAccessor.isAnnotationPresent(JSONFieldBridge.class)) {
						value = getBridgedValue(baseAccessor, value);
					}

					boolean propertyPrinted = this.add(name, value, accessor,
							hasData);
					hasData = hasData || propertyPrinted;
					if (this.buildExpr) {
						this.setExprStack(expr);
					}
				}
			}

			// special-case handling for an Enumeration - include the name() as
			// a property */
			if (object instanceof Enum) {
				Object value = ((Enum) object).name();
				this.add("_name", value, object.getClass().getMethod("name"),
						hasData);
			}
		} catch (Exception e) {
			throw new JSONException(e);
		}

		this.add("}");
	}

	protected BeanInfo getBeanInfoIgnoreHierarchy(final Class<?> clazz)
			throws IntrospectionException {
		BeanInfo beanInfo = BEAN_INFO_CACHE_IGNORE_HIERARCHY.get(clazz);
		if (beanInfo != null) {
			return beanInfo;
		}
		beanInfo = Introspector.getBeanInfo(clazz, clazz.getSuperclass());
		BEAN_INFO_CACHE_IGNORE_HIERARCHY.put(clazz, beanInfo);
		return beanInfo;
	}

	protected BeanInfo getBeanInfo(final Class<?> clazz)
			throws IntrospectionException {
		BeanInfo beanInfo = BEAN_INFO_CACHE.get(clazz);
		if (beanInfo != null) {
			return beanInfo;
		}
		beanInfo = Introspector.getBeanInfo(clazz);
		BEAN_INFO_CACHE.put(clazz, beanInfo);
		return beanInfo;
	}

	protected Object getBridgedValue(Method baseAccessor, Object value)
			throws InstantiationException, IllegalAccessException {
		JSONFieldBridge fieldBridgeAnn = baseAccessor
				.getAnnotation(JSONFieldBridge.class);
		if (fieldBridgeAnn != null) {
			Class impl = fieldBridgeAnn.impl();
			FieldBridge instance = (FieldBridge) impl.newInstance();

			if (fieldBridgeAnn.params().length > 0
					&& ParameterizedBridge.class.isAssignableFrom(impl)) {
				Map<String, String> params = new HashMap<String, String>(
						fieldBridgeAnn.params().length);
				for (JSONParameter param : fieldBridgeAnn.params()) {
					params.put(param.name(), param.value());
				}
				((ParameterizedBridge) instance).setParameterValues(params);
			}
			value = instance.objectToString(value);
		}
		return value;
	}

	protected Method findBaseAccessor(Class clazz, Method accessor) {
		Method baseAccessor = null;
		if (clazz.getName().contains("$$EnhancerByCGLIB$$")) {
			try {
				baseAccessor = Thread.currentThread().getContextClassLoader()
						.loadClass(
								clazz.getName().substring(0,
										clazz.getName().indexOf("$$")))
						.getMethod(accessor.getName(),
								accessor.getParameterTypes());
			} catch (Exception ex) {
				LOG.debug(ex.getMessage(), ex);
			}
		} else if (clazz.getName().contains("$$_javassist")) {
			try {
				baseAccessor = Class.forName(
						clazz.getName().substring(0,
								clazz.getName().indexOf("_$$"))).getMethod(
						accessor.getName(), accessor.getParameterTypes());
			} catch (Exception ex) {
				LOG.debug(ex.getMessage(), ex);
			}
		} else {
			return accessor;
		}
		return baseAccessor;
	}

	/**
	 * Instrospect an Enum and serialize it as a name/value pair or as a bean
	 * including all its own properties
	 */
	protected void enumeration(Enum enumeration) throws JSONException {
		if (enumAsBean) {
			this.bean(enumeration);
		} else {
			this.string(enumeration.name());
		}
	}

	protected boolean shouldExcludeProperty(PropertyDescriptor prop)
			throws SecurityException, NoSuchFieldException {
		String name = prop.getName();
		return name.equals("class") || name.equals("declaringClass")
				|| name.equals("cachedSuperClass") || name.equals("metaClass");
	}

	protected String expandExpr(int i) {
		return this.exprStack + "[" + i + "]";
	}

	protected String expandExpr(String property) {
		if (this.exprStack.length() == 0) {
			return property;
		}
		return this.exprStack + "." + property;
	}

	protected String setExprStack(String expr) {
		String s = this.exprStack;
		this.exprStack = expr;
		return s;
	}

	protected boolean shouldExcludeProperty(String expr) {
		if (this.excludeProperties != null) {
			for (Pattern pattern : this.excludeProperties) {
				if (pattern.matcher(expr).matches()) {
					if (LOG.isDebugEnabled()) {
						LOG.debug("Ignoring property because of exclude rule: "
								+ expr);
					}
					return true;
				}
			}
		}

		if (this.includeProperties != null) {
			for (Pattern pattern : this.includeProperties) {
				if (pattern.matcher(expr).matches()) {
					return false;
				}
			}
			if (LOG.isDebugEnabled()) {
				LOG
						.debug("Ignoring property because of include rule:  "
								+ expr);
			}
			return true;
		}
		return false;
	}

	/**
	 * Add name/value pair to buffer
	 */
	protected boolean add(String name, Object value, Method method,
			boolean hasData) throws JSONException {
		if (excludeNullProperties && value == null) {
			return false;
		}
		if (hasData) {
			this.add(',');
		}
		this.add('"');
		this.add(name);
		this.add("\":");
		this.value(value, method);
		return true;
	}

	/**
	 * Add map to buffer
	 */
	protected void map(Map map, Method method) throws JSONException {
		this.add("{");

		Iterator it = map.entrySet().iterator();

		boolean warnedNonString = false; // one report per map
		boolean hasData = false;
		while (it.hasNext()) {
			Map.Entry entry = (Map.Entry) it.next();
			if (excludeNullProperties && entry.getValue() == null) {
				continue;
			}

			Object key = entry.getKey();
			if (key == null) {
				LOG.error("Cannot build expression for null key in #0",
						exprStack);
				continue;
			}

			String expr = null;
			if (this.buildExpr) {
				expr = this.expandExpr(key.toString());
				if (this.shouldExcludeProperty(expr)) {
					continue;
				}
				expr = this.setExprStack(expr);
			}
			if (hasData) {
				this.add(',');
			}
			hasData = true;
			if (!warnedNonString && !(key instanceof String)) {
				if (LOG.isWarnEnabled()) {
					LOG
							.warn(
									"JavaScript doesn't support non-String keys, using toString() on #0",
									key.getClass().getName());
				}
				warnedNonString = true;
			}
			this.value(key.toString(), method);
			this.add(":");
			this.value(entry.getValue(), method);
			if (this.buildExpr) {
				this.setExprStack(expr);
			}
		}

		this.add("}");
	}

	/**
	 * Add date to buffer
	 */
	protected void date(Date date, Method method) {
		JSON json = null;
		if (method != null)
			json = method.getAnnotation(JSON.class);
		if (this.formatter == null)
			this.formatter = new SimpleDateFormat(JSONUtil.RFC3339_FORMAT);

		DateFormat formatter = (json != null) && (json.format().length() > 0) ? new SimpleDateFormat(
				json.format())
				: this.formatter;
		this.string(formatter.format(date));
	}

	/**
	 * Add array to buffer
	 */
	protected void array(Iterator it, Method method) throws JSONException {
		this.add("[");

		boolean hasData = false;
		for (int i = 0; it.hasNext(); i++) {
			String expr = null;
			if (this.buildExpr) {
				expr = this.expandExpr(i);
				if (this.shouldExcludeProperty(expr)) {
					it.next();
					continue;
				}
				expr = this.setExprStack(expr);
			}
			if (hasData) {
				this.add(',');
			}
			hasData = true;
			this.value(it.next(), method);
			if (this.buildExpr) {
				this.setExprStack(expr);
			}
		}

		this.add("]");
	}

	/**
	 * Add array to buffer
	 */
	protected void array(Object object, Method method) throws JSONException {
		this.add("[");

		int length = Array.getLength(object);

		boolean hasData = false;
		for (int i = 0; i < length; ++i) {
			String expr = null;
			if (this.buildExpr) {
				expr = this.expandExpr(i);
				if (this.shouldExcludeProperty(expr)) {
					continue;
				}
				expr = this.setExprStack(expr);
			}
			if (hasData) {
				this.add(',');
			}
			hasData = true;
			this.value(Array.get(object, i), method);
			if (this.buildExpr) {
				this.setExprStack(expr);
			}
		}

		this.add("]");
	}

	/**
	 * Add boolean to buffer
	 */
	protected void bool(boolean b) {
		this.add(b ? "true" : "false");
	}

	/**
	 * escape characters
	 */
	protected void string(Object obj) {
		this.add('"');

		CharacterIterator it = new StringCharacterIterator(obj.toString());

		for (char c = it.first(); c != CharacterIterator.DONE; c = it.next()) {
			if (c == '"') {
				this.add("\\\"");
			} else if (c == '\\') {
				this.add("\\\\");
			} else if (c == '/') {
				this.add("\\/");
			} else if (c == '\b') {
				this.add("\\b");
			} else if (c == '\f') {
				this.add("\\f");
			} else if (c == '\n') {
				this.add("\\n");
			} else if (c == '\r') {
				this.add("\\r");
			} else if (c == '\t') {
				this.add("\\t");
			} else if (Character.isISOControl(c)) {
				this.unicode(c);
			} else {
				this.add(c);
			}
		}

		this.add('"');
	}

	/**
	 * Add object to buffer
	 */
	protected void add(Object obj) {
		this.buf.append(obj);
	}

	/**
	 * Add char to buffer
	 */
	protected void add(char c) {
		this.buf.append(c);
	}

	/**
	 * Represent as unicode
	 * 
	 * @param c
	 *            character to be encoded
	 */
	protected void unicode(char c) {
		this.add("\\u");

		int n = c;

		for (int i = 0; i < 4; ++i) {
			int digit = (n & 0xf000) >> 12;

			this.add(hex[digit]);
			n <<= 4;
		}
	}

	public void setIgnoreHierarchy(boolean ignoreHierarchy) {
		this.ignoreHierarchy = ignoreHierarchy;
	}

	/**
	 * If true, an Enum is serialized as a bean with a special property
	 * _name=name() as all as all other properties defined within the enum.<br/>
	 * If false, an Enum is serialized as a name=value pair (name=name())
	 * 
	 * @param enumAsBean
	 *            true to serialize an enum as a bean instead of as a name=value
	 *            pair (default=false)
	 */
	public void setEnumAsBean(boolean enumAsBean) {
		this.enumAsBean = enumAsBean;
	}

	protected static class JSONAnnotationFinder {
		private boolean serialize = true;
		private Method accessor;
		private String name;

		public JSONAnnotationFinder(Method accessor) {
			this.accessor = accessor;
		}

		public boolean shouldSerialize() {
			return serialize;
		}

		public String getName() {
			return name;
		}

		public JSONAnnotationFinder invoke() {
			JSON json = accessor.getAnnotation(JSON.class);
			serialize = json.serialize();
			if (serialize && json.name().length() > 0) {
				name = json.name();
			}
			return this;
		}
	}

}


      在你的工程中新建org.apache.struts2.json包,把以上程式碼複製到你的工程裡就可以了,要注意的是,如果是在WEBLOGIC中部署,最好把這個類重新打包到struts2-json-plugin-2.3.15.3.jar包中,避免不必要的麻煩。