在Struts框架下使用時間類型

使用時間類型?這誰不會,不就是java.util下的幾個類嗎,在不加上java.sql和java.text下的幾個類,這會有什麼問題嗎?Struts要是連
時間都處理不了,那還能幹嘛?   在實際應用中,我就發現Struts確實連有些簡單的時間都處理不了(不知是我使用的方法不對還是Struts確
實沒有考慮到)。順便你也能瞭解Struts是怎麼把form裏的請求參數populate到ActionForm裏面的。
  今天下午同事告訴我把有java.util.Date類型屬性的類存入數據庫時出錯,把這個屬性刪除就沒有問題了。當時我就想到是
RequestProcessor在processPopulate()時出錯了,因此在它的這個方法設了斷點並跟蹤了進去。當然,它最先要調用ActionForm的reset()方
法,然後調用實際處理populate(將請求參數傳給ActionForm)的RequestUtils.populate()方法。RequestUtils的這個靜態方法最先是處理
Multipart的(即文件上傳等多部分)的方法,然後將所有的請求都放在叫properties的HashMap裏並循環處理它:
        names = request.getParameterNames();
        while (names.hasMoreElements()) {
            String name = (String) names.nextElement();
            String stripped = name;
            if (prefix != null) {
                if (!stripped.startsWith(prefix)) {
                    continue;
                }
                stripped = stripped.substring(prefix.length());
            }
            if (suffix != null) {
                if (!stripped.endsWith(suffix)) {
                    continue;
                }
                stripped = stripped.substring(0, stripped.length() - suffix.length());
            }
            if (isMultipart) {
                properties.put(stripped, multipartParameters.get(name));
            } else {
                properties.put(stripped, request.getParameterValues(name));
            }
        }

        實際處理它們的是下面的:BeanUtils.populate(bean, properties); 其中bean就是接受數據的ActionForm,而properties裏面則是
所有的請求的鍵-值對(鍵和值都是字符串,http協議的特點)。
        再看看BeanUtils的靜態(類)方法populate是怎麼處理的:
        // Loop through the property name/value pairs to be set
        Iterator names = properties.keySet().iterator();
        while (names.hasNext()) {

            // Identify the property name and value(s) to be assigned
            String name = (String) names.next();
            if (name == null) {
                continue;
            }
            Object value = properties.get(name);

            // Perform the assignment for this property
            setProperty(bean, name, value);

        }
        它是循環所有的請求參數,把實際的工作又交給了setProperty方法。呵呵,弄了半天,這幫人原來都是代理。
        這個方法還是代理嗎?計算了一下它有180行的代碼。這麼長應該是個實幹家了吧,錯!千萬不要被有些人的外表欺騙了!有些人一天
上班16個小時,可夠敬業的,可有8小時在打CS。這個類就是:一上來20多行都在一個if (log.isTraceEnabled()){}裏面。
        log在這說明一下。Struts中使用的是Jakarta Commons Logging的包,它使用的優先級是:Log4j(4念four好像比較有意義,大概是
Logger For Java的意思,我聽有的人年Log si J,感覺很彆扭,呵呵),Java 1.4 Logging API,Simple Logging。功能是依次減弱。
        建議在寫Action 的execute()或被execute()調用的業務方法中使用Commons Logging 來代替System.out.println()--當要你把成
百上千的System.out.println()去掉的時候你就會覺得Commons Logging是個多好的東東了。它的用法是:
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
        private/protected static Log log = LogFactory.getLog(DispatchAction.class);
        如果你用的是DispatchAction,那你就不要自己定義Log的實例了,因爲它已經有一個protected的Log實例,直接使用即可。
        使用方法是:
        if (log.isInfoEnabled()) {
             log.Info("some information.");
        }
        Logging把消息分爲6種級別,debug,error,fatal,info,trace,warn。比如,你想記錄一條消息,它只是爲了給用戶一個警告,則可
以使用warn。爲什麼在每個log.Info()前做一次判斷呢?難道如果log級別不允許Info,log.Info()仍然能Info嗎?當然不是。它的作用是提
高效率。
       比如有個消息是計算前一萬個自然數的和(這種消息可能少見)。用直接log.Info()
       int sum=0;
       for(int i=0;i<10000;i++){
          sum+=i;
       }
       log.Info("the sum of form 1 to 10000 is : "_sum);
       如果log.Info是不允許的,那求10000個數的和就白求的。當然如果你的計算機很快或和高斯一樣聰明,直接log.Info()也每什麼
問題。
      
       閒話少說,回到180多行的BeanUtils.setProperty()方法。這個方法先是處理nested屬性,也就是xxx.xxx的請求參數。我們只看看
處理簡單屬性的必須過程。下面這端代碼有點長,但它只做了一件事:將字符串的請求參數轉成ActionForm的類型。比如:你在ActionForm裏
有個Integer userAge;然後HTTP請求參數裏可能會有http://localhost:8080/xxx.do?userAge=21。傳人的是字符串,目標是專程Integer。
首先它當然會根據userAge這個字符串查找相應的ActionForm,如果這個ActionForm有個屬性也叫userAge,然後就會把這個userAge的類型存
到type裏,type的定義是:Class type = null; 得到type的代碼很長,這是因爲要它考慮很多情況,例如DynaActionForm。
      // Convert the specified value to the required type
        Object newValue = null;
        if (type.isArray() && (index < 0)) { // Scalar value into array
            if (value == null) {
                String values[] = new String[1];
                values[0] = (String) value;
                newValue = ConvertUtils.convert((String[]) values, type);
            } else if (value instanceof String) {
                String values[] = new String[1];
                values[0] = (String) value;
                newValue = ConvertUtils.convert((String[]) values, type);
            } else if (value instanceof String[]) {
                newValue = ConvertUtils.convert((String[]) value, type);
            } else {
                newValue = value;
            }
        } else if (type.isArray()) {         // Indexed value into array
            if (value instanceof String) {
                newValue = ConvertUtils.convert((String) value,
                                                type.getComponentType());
            } else if (value instanceof String[]) {
                newValue = ConvertUtils.convert(((String[]) value)[0],
                                                type.getComponentType());
            } else {
                newValue = value;
            }
        } else {                             // Value into scalar
            if ((value instanceof String) || (value == null)) {
                newValue = ConvertUtils.convert((String) value, type);
            } else if (value instanceof String[]) {
                newValue = ConvertUtils.convert(((String[]) value)[0],
                                                type);
            } else if (ConvertUtils.lookup(value.getClass()) != null) {
                newValue = ConvertUtils.convert(value.toString(), type);// Here is my program's break point
            } else {
                newValue = value;
            }
        }

        最後是:調用PropertyUtils的一些方法設置值。下面代碼的第一種情況是有索引的,即你在請求參數裏傳了field[0]=123之類的
參數,第二種是Map類型的,傳的是map(key)=value之類的參數,最一般的就是調用第三個方法。
            if (index >= 0) {
                PropertyUtils.setIndexedProperty(target, propName,
                                                 index, newValue);
            } else if (key != null) {
                PropertyUtils.setMappedProperty(target, propName,
                                                key, newValue);
            } else {
                PropertyUtils.setProperty(target, propName, newValue);
            }
         
            當然還可以在跟蹤下去,不過和這個主題沒什麼關係了。大概的流程是:
            setProperty()方法再調用setNestedProperty()方法(還是代理),在調用setSimpleProperty(),最後通過java.lang.reflect
包調用你在ActionForm裏寫的setXXX()方法,如setUserAge(Integer userAge)等。

            現在說說爲什麼不能populate java.util.Date類型的數據。關鍵是ConvertUtils.convert(),即上文有註釋的地方。如果這個方
法返回的是一個java.util.Date類型的對象,當然後面都不會有問題。但我發現實際運行的結果是,newValue還是String類型的,因此在後面
通過reflection調用setXXX時出錯。
            你或許會奇怪ConvertUtils包竟然連java.util.Date都不支持,我也覺得不可思異。我還以爲是我使用的不對,然後進入這個類
一看,確實是不支持:
/**
 * <p>Utility methods for converting String scalar values to objects of the
 * specified Class, String arrays to arrays of the specified Class.  The
 * actual {@link Converter} instance to be used can be registered for each
 * possible destination Class.  Unless you override them, standard
 * {@link Converter} instances are provided for all of the following
 * destination Classes:</p>
 * <ul>
 * <li>java.lang.BigDecimal</li>
 * <li>java.lang.BigInteger</li>
 * <li>boolean and java.lang.Boolean</li>
 * <li>byte and java.lang.Byte</li>
 * <li>char and java.lang.Character</li>
 * <li>java.lang.Class</li>
 * <li>double and java.lang.Double</li>
 * <li>float and java.lang.Float</li>
 * <li>int and java.lang.Integer</li>
 * <li>long and java.lang.Long</li>
 * <li>short and java.lang.Short</li>
 * <li>java.lang.String</li>
 * <li>java.sql.Date</li>
 * <li>java.sql.Time</li>
 * <li>java.sql.Timestamp</li>
 * </ul>
 *
 * <p>For backwards compatibility, the standard Converters for primitive
 * types (and the corresponding wrapper classes) return a defined
 * default value when a conversion error occurs.  If you prefer to have a
 * {@link ConversionException} thrown instead, replace the standard Converter
 * instances with instances created with the zero-arguments constructor.  For
 * example, to cause the Converters for integers to throw an exception on
 * conversion errors, you could do this:</p>
 * <pre>
 *   // No-args constructor gets the version that throws exceptions
 *   Converter myConverter =
 *    new org.apache.commons.beanutils.converter.IntegerConverter();
 *   ConvertUtils.register(myConverter, Integer.TYPE);    // Native type
 *   ConvertUtils.register(myConverter, Integer.class);   // Wrapper class
 * </pre>
 *
 * @author Craig R. McClanahan
 * @author Ralph Schaer
 * @author Chris Audley
 * @version $Revision: 1.12 $ $Date: 2003/01/15 21:59:38 $
 */
          另外,會不會即時是字符串的,org.apache.commons.beanutils.PropertyUtils.setProperty()也有能力處理呢?
          於是又寫了個小程序測試。
public class SetSimplePropertyTest {
  public SetSimplePropertyTest() {
  }
  public static void main(String[] args) {
    SetSimplePropertyTest setSimplePropertyTest1 = new SetSimplePropertyTest();
    String dateStr="2004-01-01 19:00:00";
    test.DataBean dataBean=new DataBean();
    try {
      org.apache.commons.beanutils.PropertyUtils.setProperty(dataBean,
          "receiveTime", dateStr);
    }
    catch (Exception e){
      e.printStackTrace();
    }
    System.out.println(dataBean.getReceiveTime().toString());
  }
  運行是拋出異常,證明處理不了。

          問題找到了,那該怎麼解決呢?當然最簡單的方法就是使用ConvertUtils能轉的java.sql.DateTime等,比較複雜一點的方法就是
自己寫一個ConvertUtils。當然,如果你把日前存成String,那更沒問題,但如果要將它存入數據庫,還得轉。尤其在使用DAO模式時,我們
可能用BeanUtils.CopyProperties()方法實現將一個ActionForm拷貝到一個DTO(or VO)對象中時會很麻煩。
          還有一個比較好的方法是,屬性定義成java.util.Date,但爲Struts提高另一個getter/setter方法。這種方法是在middlegen自動
生成的JSP頁面看到的。
          例如:
              private java.util.Date saveDate;
              //普通的set/get方法
              public void setSaveDate(java.util.Date saveDate){
                  this.saveDate=saveDate;
              }
              public java.util.Date getSaveDate(){
                  return this.saveDate;
              }
              //爲Struts準備的方法,時期的格式假定是 2000-12-31 23:59:59
              public void setSaveDateAsString(String saveDate){
                 java.text.DateFormat dateFormat =new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                 this.saveDate=dateFormat.parse(saveDate);
              }
              public String getSaveDateAsString(){
                 java.text.DateFormat dateFormat =new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                 return dateFormat.Format(this.saveDate);                
       }

              然後在JSP中使用:
              <html:form action="xxx.do">
                <html:text property="saveDateAsString"/>
              </html:form>

發佈了42 篇原創文章 · 獲贊 1 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章