空间站

北极心空

  BlogJava :: 首页 :: 联系 :: 聚合  :: 管理
  15 Posts :: 393 Stories :: 160 Comments :: 0 Trackbacks

http://fly-m.javaeye.com/blog/183534

struts2乱码与json插件(1)

    最近要用struts2做一个体育类的系统,由于系统本身的原因(要求可操作性强,用户交互性高),所以不可避免地要用到ajax.在我的前一篇文章里已经提到了有关在struts2中运用ajax的一些东西了.这次重新再拾起来,仔细对比下其中的差别.
    在前一个例子性系统中,由于所有的编码都是以UTF-8进行编码的,所以也没有碰到过有关乱码的问题.普通调用和ajax调用都很正常地运行了.而在新的系统中,要求所有的页面(包括数据库)都要求运用GBK编码.这样,一些有关于乱码的问题就出现了,乱码...与struts2有关. 
    大家都知道,在struts2的配置文件中,有一个配置项直接跟编码有关."struts.i18n.encoding"这个配置项表示struts2将对数据进行何种形式的编码操作,默认的编码为utf-8,这也是为什么在前一个新闻系统中没有乱码的原因,因为它本身都和系统默认编码一致.而在新的系统中,由于要求必须将编码改成GBK,配置如下:

 

Xml代码 复制代码
  1. <constant name="struts.i18n.encoding" value="GBK"/>  

   在这种情况下,加上在tomcat serve.xml中修改URIEncoding=GBK,保证传送到服务器的数据都是GBK格式.实际测试中,这种方式是正确的,编码正确.
   然而好境不长,当运用到ajax时,问题出现了.我最先采用的是struts2的json插件,看它的要求,它要求工程编码应该为UTF-8,而我的工程编码实际为GBK,这是不可能改回去的事情.先不管它,在页面中配置完成,进行ajax调用.果然,数据乱码.在action中,对数据进行了测试,发现数据在进行action时已经是乱码.对数据进行转码操作,发现将数据按

Java代码 复制代码
  1. x = new String(x.getBytes("GBK"),"UTF-8");  

 时,数据正常了.这就表示数据是按照utf-8格式进行传送的,而在sturts中可能将数据又转回了gbk,导致乱码产生.一开始从js入手,因为我用的prototype.js采用post进行传送.设置的encoding为"UTF-8",修改js的encoding为"GBK",发现并没有按照想像的方向发展,仍然乱码.而采用get方式发送时,数据仍然是乱码.
     只有从java方向入手了,处理方向有两种,就像我在前面两篇有关jsp乱码中提到一样.第一种是加拦截器,拦截到ajax请求时,将编码重新转码操作.第二种就是像过滤器一样,在进行参数编码前设置正确的编码.在struts2中采用的是第二种方法.其实struts.i18n.encoding采用的也是这种方法.相应的代码如下:

Java代码 复制代码
  1. public void prepare(HttpServletRequest request, HttpServletResponse response) {    
  2.         String encoding = null;    
  3.         if (defaultEncoding != null) {    
  4.             encoding = defaultEncoding;    
  5.         }   
  6.   
  7.         Locale locale = null;   
  8.   
  9.         if (defaultLocale != null) {   
  10.             locale = LocalizedTextUtil.localeFromString(defaultLocale, request.getLocale());   
  11.         }   
  12.         if (encoding != null) {    
  13.             try {    
  14.                 request.setCharacterEncoding(encoding);    
  15.             } catch (Exception e) {    
  16.                 LOG.error("Error setting character encoding to '" + encoding + "' - ignoring.", e);    
  17.             }   
  18.         }    
  19.         if (locale != null) {    
  20.             response.setLocale(locale);    
  21.         }    
  22.         if (paramsWorkaroundEnabled) {    
  23.             request.getParameter("foo"); // simply read any parameter (existing or not) to "prime" the request    
  24.         }    
  25.     }  

 这个方法这是org.apache.struts2.dispatcher.Dispatcher中而被FilterDispatcher调用,后都大家都知道吧,是struts2的标准过滤器.它调用的地方如下:

Java代码 复制代码
  1. protected HttpServletRequest prepareDispatcherAndWrapRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException {    
  2.     
  3.         Dispatcher du = Dispatcher.getInstance();    
  4.     
  5.         // Prepare and wrap the request if the cleanup filter hasn't already, cleanup filter should be    
  6.         // configured first before struts2 dispatcher filter, hence when its cleanup filter's turn,    
  7.         // static instance of Dispatcher should be null.    
  8.         if (du == null) {    
  9.     
  10.             Dispatcher.setInstance(dispatcher);    
  11.     
  12.             // prepare the request no matter what - this ensures that the proper character encoding    
  13.             // is used before invoking the mapper (see WW-9127)    
  14.             dispatcher.prepare(request, response);    
  15.         } else {    
  16.             dispatcher = du;    
  17.         }    
  18.             
  19.         try {    
  20.             // Wrap request first, just in case it is multipart/form-data    
  21.             // parameters might not be accessible through before encoding (ww-1278)    
  22.             request = dispatcher.wrapRequest(request, getServletContext());    
  23.         } catch (IOException e) {    
  24.             String message = "Could not wrap servlet request with MultipartRequestWrapper!";    
  25.             LOG.error(message, e);    
  26.             throw new ServletException(message, e);    
  27.         }    
  28.     
  29.         return request;    
  30.     }  

 由上可以看出,filter在实例化dispatcher后,调用其的prepare方法,而prepare方法中,好像涉及到的其他相关操作不多,只是操作request 和 response的,在prepare方法中,判断encoding是不是空,如果不为空则将其设置编码入request中.而在defaultEncoding的设置上,可以看出这个参数是跟struts.i18n.encoding相关的,相关代码如下:

Java代码 复制代码
  1. @Inject(StrutsConstants.STRUTS_I18N_ENCODING)    
  2.     public static void setEncoding(String val) {    
  3.         encoding = val;    
  4.     }  

 在上面这个方法中,将把struts.i18n.encoding注入到encoding中,也就是说,如果我们设置encoding为GBK,无论在何种条件下,它就是GBK编码了.
      尝试修改这种方式,因为直接影响的就是

Java代码 复制代码
  1. prepareDispatcherAndWrapRequest(HttpServletRequest request, HttpServletResponse response)   

这个方法,于是直接修改如为如下:

Java代码 复制代码
  1. public class TextFilter extends FilterDispatcher{   
  2.     private static final Log log = LogFactory.getLog(TextFilter.class);   
  3.     private FilterConfig filterConfig;   
  4.   
  5.     private static String defaultEncoding;   
  6.     private static String defaultLocale;   
  7.     private static String paramsWorkaroundEnabled = "false";   
  8.   
  9.     @Inject(org.apache.struts2.StrutsConstants.STRUTS_DISPATCHER_PARAMETERSWORKAROUND)   
  10.     public static void setParamsWorkaroundEnabled(String enabled) {   
  11.         paramsWorkaroundEnabled = enabled;   
  12.     }   
  13.   
  14.     @Inject(StrutsConstants.STRUTS_I18N_ENCODING)   
  15.     public static void setEncoding(String encoding) {   
  16.         defaultEncoding = encoding;   
  17.     }   
  18.   
  19.     @Inject(value = StrutsConstants.STRUTS_LOCALE, required = false)   
  20.     public static void setLocale(String locale) {   
  21.         defaultLocale = locale;   
  22.     }   
  23.   
  24.     public void init(FilterConfig filterConfig) throws ServletException {   
  25.         super.init(filterConfig);   
  26.         this.filterConfig = filterConfig;   
  27.     }   
  28.   
  29.     protected HttpServletRequest prepareDispatcherAndWrapRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException {   
  30.         Dispatcher du = Dispatcher.getInstance();   
  31.   
  32.         if(du == null) {   
  33.             Dispatcher.setInstance(dispatcher);   
  34.             prepare(request, response);   
  35.         } else {   
  36.             dispatcher = du;   
  37.         }   
  38.   
  39.         try {   
  40.             request = dispatcher.wrapRequest(request, getServletContext());   
  41.         } catch(IOException e) {   
  42.             String message = "Could not wrap servlet request with MultipartRequestWrapper!";   
  43.             log.error(message, e);   
  44.             throw new ServletException(message, e);   
  45.         }   
  46.   
  47.         return request;   
  48.     }   
  49.   
  50.     private void prepare(HttpServletRequest request, HttpServletResponse response) {   
  51.         String encoding = request.getCharacterEncoding();   
  52.         if(encoding == null) {   
  53.             encoding = defaultEncoding;   
  54.         }   
  55.   
  56.         Locale locale = null;   
  57.         if(defaultLocale != null) {   
  58.             locale = LocalizedTextUtil.localeFromString(defaultLocale, request.getLocale());   
  59.         }   
  60.   
  61.         if(encoding != null) {   
  62.             try {   
  63.                 request.setCharacterEncoding(encoding);   
  64.             } catch(Exception e) {   
  65.                 log.error("Error setting character encoding to '" + encoding + "' - ignoring.", e);   
  66.             }   
  67.         }   
  68.   
  69.         if(locale != null) {   
  70.             response.setLocale(locale);   
  71.         }   
  72.   
  73.         if(isParamsWorkaroundEnabled()) {   
  74.             request.getParameter("foo");   
  75.         }   
  76.     }   
  77.   
  78.     public boolean isParamsWorkaroundEnabled() {   
  79.         ServletContext servletContext = filterConfig.getServletContext();   
  80.         return servletContext != null && servletContext.getServerInfo() != null && servletContext.getServerInfo().indexOf("WebLogic") >= 0 ||   
  81.                 paramsWorkaroundEnabled.equalsIgnoreCase("true");   
  82.     }   
  83. }   
  84.    

 可以看出,整个就是继承filterDispatcher,再修改其中的一个方法.将dispatcher.prepare()这一名改成filter.prepare的形式,实际上效果是一样的.惟一让人感觉不爽的就是,由于在filter中设置的各种变量都是私有而且是是静态的(我尝试用反射都不能得到它的值),导致直接不能得到父类的属性值,没办法,只有再加变量了.在上面的prepare中,判断request中的编码是不是为空,一般来说,从jsp页面传送的编码都是空的.当由js进行传送时,由于已经设置传送编码为UTF-8,故getCharacterEncoding()不为空,则不再进行设置编码了.其他由设置传送编码为defaultEncoding(即sturts.i18n.encoding).
在上面的有一句

Java代码 复制代码
  1. if(isParamsWorkaroundEnabled()) {           request.getParameter("foo");        }  

专为weblogic设置,由于没有接触过,略过.值得不提的是,request.getParamter("foo").这一句,在tomcat里面是直接将编码固定化,即只要调用了这一句,都将使得request(在tomcat的实现中)不能再接受其他编码(参数已经被转化了).
最后,将filter设置在struts.xml中,以便窗口将参数@inject注入到filter中.

Java代码 复制代码
  1. <bean class="m_ylf.cs.sicau.struts2.TextFilter" static="true"/>  

 上面一句必须要,不然相应的静态参数都没有被注入,是会产生NullPointerException的哦.
先解决这一个问题,下一个问题将介绍struts2的json插件,及改进方法.

struts2与json插件(2)

 

在前一篇中<struts2与json插件(1)> ,讲到了解决在struts2中出现的一点点乱码问题,就想看看json中的数据处理方式.由struts2的处理流程来看,主要处理result的代码如下在defaultActionInvocation中:

Java代码 复制代码
  1. private void executeResult() throws Exception {   
  2.         result = createResult();   
  3.   
  4.         String timerKey = "executeResult: "+getResultCode();   
  5.         try {   
  6.             UtilTimerStack.push(timerKey);   
  7.             if (result != null) {   
  8.                 result.execute(this);   
  9.             } else if (resultCode != null && !Action.NONE.equals(resultCode)) {   
  10.                 throw new ConfigurationException("No result defined for action " + getAction().getClass().getName()    
  11.                         + " and result " + getResultCode(), proxy.getConfig());   
  12.             } else {   
  13.                 if (LOG.isDebugEnabled()) {   
  14.                     LOG.debug("No result returned for action "+getAction().getClass().getName()+" at "+proxy.getConfig().getLocation());   
  15.                 }   
  16.             }   
  17.         } finally {   
  18.             UtilTimerStack.pop(timerKey);   
  19.         }   
  20.     }  

 
如上所示,result就是返回的result,而resultCode就是我们通用的String返回类型了.而json插件中,采用默认值Action.Success来得到json类型,而在result中处理,即调用上面的result.invoke(this).json中的主要处理代码如下(省略中间一些代码):

Java代码 复制代码
  1. public void execute(ActionInvocation invocation) throws Exception {   
  2.     ActionContext actionContext = invocation.getInvocationContext();   
  3.     HttpServletRequest request = (HttpServletRequest) actionContext   
  4.         .get(StrutsStatics.HTTP_REQUEST);   
  5.     HttpServletResponse response = (HttpServletResponse) actionContext   
  6.         .get(StrutsStatics.HTTP_RESPONSE);   
  7.   
  8.     try {   
  9.         String json;   
  10.         Object rootObject;   
  11.         if (this.enableSMD) {   
  12.             //generate SMD   
  13.             rootObject = this.writeSMD(invocation);   
  14.         } else {   
  15.             // generate JSON   
  16.             if (this.root != null) {   
  17.                 ValueStack stack = invocation.getStack();   
  18.                 rootObject = stack.findValue(this.root);   
  19.             } else {   
  20.                 rootObject = invocation.getAction();   
  21.             }   
  22.         }   
  23.         json = JSONUtil   
  24.             .serialize(rootObject, this.excludeProperties, ignoreHierarchy, enumAsBean);   
  25.   
  26.         boolean writeGzip = enableGZIP && JSONUtil.isGzipInRequest(request);   
  27.           
  28.         JSONUtil.writeJSONToResponse(response, this.defaultEncoding,   
  29.             isWrapWithComments(), json, false, writeGzip);   
  30.   
  31.     } catch (IOException exception) {   
  32.         log.error(exception);   
  33.         throw exception;   
  34.     }   
  35. }  

 

可以看出,json插件的功能(在处理json上),就是将invocation中的action对象转化成json对象,再输出到页面上.
    在上面的应用上来看,用json插件必须注册json的resultType,而且,返回类型一定是json,也就是说,你必须给每一个json调用定义一个json的返回类型,而实际上这个类型就是转向jsonResult上,和一般的调用不一样,它不返回任何视图.即json这个返回类型感觉上完全是多余的,除了标明是json返回之外没有其他作用.而且对于解析来说,它是解析一个action对象,所就是说,如果你的action是富对象,里面有很多的属性,而实际你需要的不多时,那这个解析就完全失去了作用了.
     话说这么多,改进方法,从根本上来说,就是改进这种解析机制.是不解析当前的action对象,而是像spring一样,解析只需要解析的对象一样.如返回a,就只需要解析a.查看上面的executeResult()方法,其中对result和resultCode进行了处理.其中有这么一句

Java代码 复制代码
  1. if (result != null) {   
  2.                 result.execute(this);   
  3.             } else if (resultCode != null && !Action.NONE.equals(resultCode)) {   
  4.                 throw new ConfigurationException("No result defined for action " + getAction().getClass().getName()    
  5.                         + " and result " + getResultCode(), proxy.getConfig());   
  6.             } else {   
  7.                 if (LOG.isDebugEnabled()) {   
  8.                     LOG.debug("No result returned for action "+getAction().getClass().getName()+" at "+proxy.getConfig().getLocation());   
  9.                 }   
  10.             }  

 

第一句,也就是一般我们进行处理的地方,调用result进行最后的处理.而第二句,则是对resultCode进行判断,而第三句什么都不做.所以,对于其他的处理,可以从resultCode下手.方法有两种,一是返回无类型,即void类型,二是返回Action.NONE.当是这两种类型的时候,struts2就不会对result进行主动处理了.详细可参见struts2对Action.NONE的说明,如下:


Java代码 复制代码
  1. /**  
  2.      * The action execution was successful but do not  
  3.      * show a view. This is useful for actions that are  
  4.      * handling the view in another fashion like redirect.  
  5.      */  
  6.     public static final String NONE = "none";  

 

 

 即我们只需要在action方法中,处理ajax调用,而返回void或者"none"就行了.参考方法调用就如下:


Java代码 复制代码
  1. public void showCategoryListAjax2() throws Exception {   
  2.         this.category = this.service.getCategory(this.id);   
  3.         /** 如果该类别为空 **/  
  4.         if(this.category == null) {   
  5.             AjaxResponse.sendAjaxText(null, ServletActionContext.getResponse());   
  6.             return;   
  7.         }   
  8.   
  9.         this.categoryList = this.service.getCategoryListBySuperCategory(this.category);   
  10.   
  11.         AjaxResponse.sendAjaxText(this.categoryList, ServletActionContext.getResponse());   
  12.     }  

 

 

 上面的返回类别是一种取巧的方法,更好的方法是返回Action.NONE.这样在struts.xml配置就像这样:

Java代码 复制代码
  1. <action name="taa" class="articleAction" method="topArticleAjax"/>  

 是不是更好,惟一要做的就是在action方法处理json转换.不过这不是主要问题,在参考json的转换模式上,我对json转换进行了改进.在Fly_m的注解上如下:

Java代码 复制代码
  1. public @Interface Fly_m{   
  2.       String name() default "";    
  3.       String format() default "";    
  4.       boolean exclude() default false;    
  5. }  

 支持如json一样的名称和format以及exclude(如果为真则不进行转换).另外,支持排除指定类(如果转换对象类型和排除类一致而不进行转换)和指定名称的对象(如果对象名称和排除对象名称一致,则被忽略,只限于对象和map.这对于hibernate这种相互调用的持久层对象最好了).如对象a.b.c的形式.默认的json转换是完全转换,在json插件上,如果在b上设定json(exclude),则无论如何b都不会被转换,这种方法太过固定,不支持临时配置.而在改进中,只需要加一个"a.b"形式的excludeProperties参数就可以了.如果想转换时,把参数去掉就行了.相应的转换方法如下:

Java代码 复制代码
  1. public static String convertToJson(Object obj, String... excludeProperties) {   
  2.         return convertToJson(obj, nullnull, excludeProperties);   
  3.     }  

  

Java代码 复制代码
  1. public static String convertToJson(Object obj, Format format, Class[] excludeClasses, String... excludeProperties) {   
  2.         JsonHandle jsonHandle = new JsonHandle();   
  3.         jsonHandle.excludeProperties = excludeProperties;   
  4.         jsonHandle.excludeClasses = excludeClasses;   
  5.         if(format != null)   
  6.             jsonHandle.defaultFormat = format;   
  7.         return jsonHandle.convertToJson(null0, obj);   
  8.     }  

  而在jsonHandle中是这样处理的.

Java代码 复制代码
  1. public String convertToJson(String s, int depth, Object obj) {   
  2.             if(obj == null || obj.getClass().getName().indexOf("$$EnhancerByCGLIB$$") != -1 || contains(excludeClasses, obj.getClass())) {   
  3.                 sb.append("null");   
  4.             } else if(isSimpleType(obj.getClass())) {   
  5.                 if(obj instanceof Character) {   
  6.                     sb.append("'").append(obj).append("'");   
  7.                 } else {   
  8.                     sb.append(obj);   
  9.                 }   
  10.             } else  
  11.             if(String.class.isAssignableFrom(obj.getClass()) || StringBuffer.class.isAssignableFrom(obj.getClass())   
  12.                     || StringBuilder.class.isAssignableFrom(obj.getClass())) {   
  13.                 sb.append('"');   
  14.                 CharacterIterator it = new StringCharacterIterator(obj.toString());   
  15.   
  16.                 for(char c = it.first(); c != CharacterIterator.DONE; c = it.next()) {   
  17.                     if(c == '"') {   
  18.                         sb.append("\\\"");   
  19.                     } else if(c == '\\') {   
  20.                         sb.append("\\\\");   
  21.                     } else if(c == '/') {   
  22.                         sb.append("\\/");   
  23.                     } else if(c == '\b') {   
  24.                         sb.append("\\b");   
  25.                     } else if(c == '\f') {   
  26.                         sb.append("\\f");   
  27.                     } else if(c == '\n') {   
  28.                         sb.append("\\n");   
  29.                     } else if(c == '\r') {   
  30.                         sb.append("\\r");   
  31.                     } else if(c == '\t') {   
  32.                         sb.append("\\t");   
  33.                     } else if(Character.isISOControl(c)) {   
  34.                         sb.append(unicode(c));   
  35.                     } else {   
  36.                         sb.append(c);   
  37.                     }   
  38.                 }   
  39.   
  40.                 sb.append('"');   
  41.             } else if(obj instanceof Collection) {   
  42.                 boolean hibernateFlag;   
  43.                 try {   
  44.                     ((Collection) obj).size();   
  45.                     hibernateFlag = true;   
  46.                 } catch(Exception ex) {   
  47.                     hibernateFlag = false;   
  48.                 }   
  49.   
  50.                 if(hibernateFlag) {   
  51.                     sb.append("[");   
  52.   
  53.                     for(Iterator iterator = ((Collection) obj).iterator(); iterator.hasNext();) {   
  54.                         convertToJson(s, depth, iterator.next());   
  55.   
  56.                         if(iterator.hasNext()) {   
  57.                             sb.append(",\n");   
  58.                         }   
  59.                     }   
  60.   
  61.                     sb.append("]");   
  62.                 } else {   
  63.                     sb.append("null");   
  64.                 }   
  65.   
  66.             } else if(obj.getClass().isArray()) {   
  67.                 sb.append("[");   
  68.   
  69.                 int max = java.lang.reflect.Array.getLength(obj);   
  70.   
  71.                 for(int i = 0; i < max; i++) {   
  72.                     if(i > 0) {   
  73.                         sb.append(",");   
  74.                     }   
  75.                     convertToJson(s, depth, java.lang.reflect.Array.get(obj, i));   
  76.                 }   
  77.   
  78.                 sb.append("]");   
  79.             } else if(java.util.Map.class.isAssignableFrom(obj.getClass())) {   
  80.                 if(sb.length() > 0 && sb.lastIndexOf(",") != -1) {   
  81.                     sb.insert(sb.lastIndexOf(",") + 1"\n");   
  82.                 }   
  83.                 sb.append("{");   
  84.   
  85.                 for(Map.Entry e : ((Map<?, ?>) obj).entrySet()) {   
  86.                     if(!(e.getKey() instanceof String))   
  87.                         continue;   
  88.                     if(contains(excludeProperties, e.getKey().toString())) {   
  89.                         continue;   
  90.                     }   
  91.                     if(sb.length() > 0 && sb.charAt(sb.length() - 1) == ',' && sb.charAt(sb.length() - 2) == '}') {   
  92.                         sb.insert(sb.length(), "\n");   
  93.                     }   
  94.                     sb.append("\"").append(e.getKey()).append("\"").append(":");   
  95.   
  96.                     if(depth <= DEFAULT_DEPTH) {   
  97.                         convertToJson(add(s, e.getKey().toString()), depth + 1, e.getValue());   
  98.                     } else {   
  99.                         sb.append("undefined");   
  100.                     }   
  101.   
  102.                     sb.append(",");   
  103.                 }   
  104.   
  105.                 if(sb.length() > 3) {   
  106.                     sb.deleteCharAt(sb.length() - 1);   
  107.                 }   
  108.   
  109.                 sb.append("}");   
  110.             } else {   
  111.                 Map map = null;   
  112.                 try {   
  113.                     map = getPropertiesByReflect(this, obj);   
  114.                 } catch(Exception ex) {   
  115.                     ex.printStackTrace();   
  116.                 }   
  117.   
  118.                 convertToJson(s, depth, map);   
  119.             }   
  120.             return sb.toString();   
  121.         }   
  122.     }  

 

 相关的转换方法都参照了其他的处理方式,具体解析方法请参照附件中的JsonUtils类.

 这篇文章只是说明了一种对于json的一种新的处理方式,并不是对于json插件的不满,当然我还是喜欢自己的处理方式.加上前面对乱码的处理,算是对struts2的一种补充吧.

  • JsonUtil.rar (2.1 KB)
  • 描述: JsonUtils.java 源代码.
  • 下载次数: 53
posted on 2008-11-20 17:04 芦苇 阅读(3009) 评论(0)  编辑  收藏 所属分类: Struts

只有注册用户登录后才能发表评论。


网站导航: