<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    Springweb 表現層集成技術


    13.1. 簡介

    Spring之所以出色的一個原因就是將表現層從MVC的框架中分離出來。例如,通過配置就可以讓Velocity或者XSLT來代替已經存在的JSP頁面。本章介紹和Spring集成的一些主流表現層技術,并簡要描述如何集成新的表現層。這里假設你已經熟悉第 12.5 節 “視圖與視圖解析”,那里介紹了將表現層集成到MVC框架中的基本知識。
    13.2. 和JSP & JSTL一起使用Spring

    Spring 為JSP和JSTL提供了一組方案(順便說一下,它們都是最流行的表現層技術之一)。使用JSP或JSTL需要使用定義在 WebApplicationContext里的標準的視圖解析器。此外,你當然也需要寫一些JSP頁面來顯示頁面。這里描述一些Spring為方便 JSP開發而提供的額外功能。
    13.2.1. 視圖解析器

    就象和Spring集成的其他表現層技術一樣,對于JSP頁面你需要一個視圖解析器來解析。最常用的JSP視圖解析器是InternalResourceViewResolver和ResourceBundleViewResolver。它們被定義在WebApplicationContext里:

    # The ResourceBundleViewResolver:
    <bean id="viewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
        <property name="basename"><value>views</value></property>
    </bean>

    # And a sample properties file is uses (views.properties in WEB-INF/classes):
    welcome.class=org.springframework.web.servlet.view.JstlView
    welcome.url=/WEB-INF/jsp/welcome.jsp

    productList.class=org.springframework.web.servlet.view.JstlView
    productList.url=/WEB-INF/jsp/productlist.jsp

    你可以看到ResourceBundleViewResolver需要一個屬性文件來把視圖名稱映射到 1)類和 2) URL。 通過ResolverBundleViewResolver,你可以用一個解析器來解析兩種類型的視圖。

    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass"><value>org.springframework.web.servlet.view.JstlView</value></property>
        <property name="prefix"><value>/WEB-INF/jsp/</value></property>
        <property name="suffix"><value>.jsp</value></property>
    </bean>

    InternalResourceBundleViewResolver可以配置成使用JSP頁面。作為好的實現方式,強烈推薦你將JSP文件放在WEB-INF下的一個目錄中,這樣客戶端就不會直接訪問到它們。
    13.2.2. 普通JSP頁面和JSTL

    當你使用Java標準標簽庫(Java Standard Tag Library)時,你必須使用一個特殊的類,JstlView,因為JSTL在使用象I18N這樣的功能前需要一些準備工作。
    13.2.3. 其他有助于開發的標簽

    正如前面幾章所提到的,Spring可以將請求參數綁定到命令對象上。為了更容易地開發含有數據綁定的JSP頁面,Spring定義了一些專門的標簽。所有的Spring標簽都有HTML轉義功能來決定是否使用字符轉義。

    標簽庫描述符(TLD)和庫本身都包含在spring.jar里。更多有關標簽的信息可以訪問http://www.springframework.org/docs/taglib/index.html.
    13.3. Tiles的使用

    Tiles象其他表現層技術一樣,可以集成在使用Spring的Web應用中。下面大致描述一下過程。
    13.3.1. 所需的庫文件

    為了使用Tiles,你必須將需要的庫文件包含在你的項目中。下面列出了這些庫文件。

        *

          struts version 1.1
        *

          commons-beanutils
        *

          commons-digester
        *

          commons-logging
        *

          commons-lang

    這些文件以從Spring中獲得。
    13.3.2. 如何集成Tiles

    為了使用Tiles,你必須用定義文件(definition file)來配置它(有關于定義(definition)和其他Tiles概念,請參考http://jakarta.apache.org/struts)。在Spring中,這些都可以使用TilesConfigurer在完成。下面是ApplicationContext配置的片段。

    <bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles.TilesConfigurer">
        <property name="factoryClass">
            <value>org.apache.struts.tiles.xmlDefinition.I18nFactorySet</value>
        </property>
        <property name="definitions">
            <list>
                <value>/WEB-INF/defs/general.xml</value>
                <value>/WEB-INF/defs/widgets.xml</value>
                <value>/WEB-INF/defs/administrator.xml</value>
                <value>/WEB-INF/defs/customer.xml</value>
                <value>/WEB-INF/defs/templates.xml</value>
            </list>
        </property>
    </bean>

    你可以看到,有五個文件包含定義,它們都存放在WEB-INF/defs目錄中。當初始化WebApplicationContext時,這些文件被讀取,并且初始化由factoryClass屬性指定的定義工廠(definitons factory)。在這之后,你的Spring Web應用就可以使用在定義文件中的tiles includes內容。為了使用這些,你必須得和其他表現層技術一樣有一個ViewResolver。有兩種可以選擇,InternalResourceViewResolver和ResourceBundleViewResolver。
    13.3.2.1.  InternalResourceViewResolver

    InternalResourceViewResolver用viewClass屬性指定的類實例化每個它解析的視圖。

    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="requestContextAttribute"><value>requestContext</value></property>
        <property name="viewClass">
            <value>org.springframework.web.servlet.view.tiles.TilesView</value>
        </property>
    </bean>

    13.3.2.2.  ResourceBundleViewResolver

    必須提供給ResourceBundleViewResolver一個包含viewnames和viewclassess屬性的屬性文件。

    <bean id="viewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
        <property name="basename"><value>views</value></property>
    </bean>

        ...
        welcomeView.class=org.springframework.web.servlet.view.tiles.TilesView
        welcomeView.url=welcome (<b>this is the name of a definition</b>)
            
        vetsView.class=org.springframework.web.servlet.view.tiles.TilesView
        vetsView.url=vetsView (<b>again, this is the name of a definition</b>)
           
        findOwnersForm.class=org.springframework.web.servlet.view.JstlView
        findOwnersForm.url=/WEB-INF/jsp/findOwners.jsp
        ...

    你可以發現,當使用ResourceBundleViewResolver,你可以使用不同的表現層技術。
    13.4. Velocity

    Velocity是Jakarta項目開發的表現層技術。有關與Velocity的詳細資料可以在http://jakarta.apache.org/velocity找到。這一部分介紹如何集成Velocity到Spring中。
    13.4.1. 所需的庫文件

    在使用Velocity之前,你需要在你的Web應用中包含兩個庫文件,velocity-1.x.x.jar 和commons-collections.jar 。一般它們放在WEB-INF/lib目錄下,以保證J2EE服務器能夠找到,同時把它們加到你的classpath中。當然假設你也已經把spring.jar放在你的WEB-INF/lib目錄下!最新的Velocity和commnons collections的穩定版本由Spring框架提供,可以從/lib/velocity和/lib/jakarta-commons目錄下獲取。
    13.4.2. 分發器(Dispatcher Servlet)上下文

    你的Spring DispatcherServlet配置文件(一般是WEB-INF/[servletname]-servlet.xml)應該包含一個視圖解析器的bean定義。我們也可以再加一個bean來配置Velocity環境。我指定DispatcherServlet的名字為‘frontcontroller’,所以配置文件的名字反映了DispatcherServlet的名字

    下面的示例代碼顯示了不同的配置文件

    <!-- ===========================================================-->
    <!-- View resolver. Required by web framework.                  -->
    <!-- ===========================================================-->
    <!--
      View resolvers can be configured with ResourceBundles or XML files.  If you need
      different view resolving based on Locale, you have to use the resource bundle resolver,
      otherwise it makes no difference.  I simply prefer to keep the Spring configs and
      contexts in XML.  See Spring documentation for more info.
    -->
    <bean id="viewResolver" class="org.springframework.web.servlet.view.XmlViewResolver">
        <property name="cache"><value>true</value></property>
        <property name="location"><value>/WEB-INF/frontcontroller-views.xml</value></property>
    </bean>

    <!-- ===========================================================-->
    <!-- Velocity configurer.                                       -->
    <!-- ===========================================================-->
    <!--
      The next bean sets up the Velocity environment for us based on a properties file, the
      location of which is supplied here and set on the bean in the normal way.  My example shows
      that the bean will expect to find our velocity.properties file in the root of the
      WEB-INF folder.  In fact, since this is the default location, it's not necessary for me
      to supply it here.  Another possibility would be to specify all of the velocity
      properties here in a property set called "velocityProperties" and dispense with the
      actual velocity.properties file altogether.
    -->
    <bean
        id="velocityConfig"
        class="org.springframework.web.servlet.view.velocity.VelocityConfigurer"
        singleton="true">
        <property name="configLocation"><value>/WEB-INF/velocity.properties</value></property>         
    </bean>

    13.4.3. Velocity.properties

    這個屬性文件用來配置Velocity,屬性的值會傳遞給Velocity運行時。其中只有一些屬性是必須的,其余大部分屬性是可選的 - 詳細可以查看Velocity的文檔。這里,我僅僅演示在Spring的MVC框架下運行Velocity所必需的內容。
    13.4.3.1. 模版位置

    大部分屬性值和Velocity模版的位置有關。Velocity模版可以通過classpath或文件系統載入,兩種方式都有各自的優缺點。從 classpath載入具有很好的移植性,可以在所有的目標服務器上工作,但你會發現在這種方式中,模版文件會把你的java包結構弄亂(除非你為模版建立獨立樹結構)。從classpath載入的另一個重要缺點是在開發過程中,在源文件目錄中的任何改動常常會引起WEB-INF/classes下資源文件的刷新,這將導致開發服務器重啟你的應用(代碼的即時部署)。這可能是令人無法忍受的。一旦完成大部分的開發工作,你可以把模版文件存在在jar中,并把它放在WEB-INF/lib目錄下中。
    13.4.3.2. velocity.properties示例

    這個例子將Velocity模版存放在文件系統的WEB-INF下,客戶瀏覽器是無法直接訪問到它們的,這樣也不會因為你開發過程中修改它們而引起Web應用重啟。它的缺點是目標服務器可能不能正確解析指向這些文件的路徑,尤其是當目標服務器沒有把WAR模塊展開在文件系統中。Tomcat 4.1.x/5.x,WebSphere 4.x和WebSphere 5.x支持通過文件系統載入模版。但是你在其他類型的服務器上可能會有所不同。

    #
    # velocity.properties - example configuration
    #


    # uncomment the next two lines to load templates from the
    # classpath (i.e. WEB-INF/classes)
    #resource.loader=class
    #class.resource.loader.class=org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader

    # comment the next two lines to stop loading templates from the
    # file system
    resource.loader=file
    file.resource.loader.class=org.apache.velocity.runtime.resource.loader.FileResourceLoader


    # additional config for file system loader only.. tell Velocity where the root
    # directory is for template loading.  You can define multiple root directories
    # if you wish, I just use the one here.  See the text below for a note about
    # the ${webapp.root}
    file.resource.loader.path=${webapp.root}/WEB-INF/velocity


    # caching should be 'true' in production systems, 'false' is a development
    # setting only.  Change to 'class.resource.loader.cache=false' for classpath
    # loading
    file.resource.loader.cache=false

    # override default logging to direct velocity messages
    # to our application log for example.  Assumes you have
    # defined a log4j.properties file
    runtime.log.logsystem.log4j.category=com.mycompany.myapplication

    13.4.3.3. Web應用的根目錄標記

    上面在配置文件資源載入時,使用一個標記${webapp.root}來代表Web應用在文件系統中的根目錄。這個標記在作為屬性提供給Velocity之前,會被Spring的代碼解釋成和操作系統有關的實際路徑。這種文件資源的載入方式在一些服務器中是不可移植的。如果你認為可移植性很重要,可以給VelocityConfigurer定義不同的 “appRootMarker”,來修改根目錄標記本身的名字。Spring的文檔對此有詳細表述。
    13.4.3.4. 另一種可選的屬性規范

    作為選擇,你可以用下面的內嵌屬性來代替Velocity配置bean中的“configLocation”屬性,從而直接指定Velocity屬性。

    <property name="velocityProperties">
        <props>
            <prop key="resource.loader">file</prop>
            <prop key="file.resource.loader.class">org.apache.velocity.runtime.resource.loader.FileResourceLoader</prop>
            <prop key="file.resource.loader.path">${webapp.root}/WEB-INF/velocity</prop>
            <prop key="file.resource.loader.cache">false</prop>
        </props>
    </property>

    13.4.3.5. 缺省配置(文件資源載入)

    注意從Spring 1.0-m4起,你可以不使用屬性文件或內嵌屬性來定義模版文件的載入,你可以把下面的屬性放在Velocity配置bean中。

    <property name="resourceLoaderPath"><value>/WEB-INF/velocity/</value></property>

    13.4.4. 視圖配置

    配置的最后一步是定義一些視圖,這些視圖和Velocity模版一起被顯示。視圖被定義在Spring上下文文件中。正如前面提到的,這個例子使用XML文件定義視圖bean,但是也可以使用屬性文件(ResourceBundle)來定義。視圖定義文件的名字被定義在WEB-INF/frontcontroller-servlet.xml文件的ViewResolver的bean中。

    <!--
      Views can be hierarchical, here's an example of a parent view that
      simply defines the class to use and sets a default template which
      will normally be overridden in child definitions.
    -->
    <bean id="parentVelocityView" class="org.springframework.web.servlet.view.velocity.VelocityView">
        <property name="url"><value>mainTemplate.vm</value></property>       
    </bean>

    <!--
      - The main view for the home page.  Since we don't set a template name, the value
      from the parent is used.
    -->
    <bean id="welcomeView" parent="parentVelocityView">
      <property name="attributes">
          <props>
              <prop key="title">My Velocity Home Page</prop>
          </props>
      </property>    
    </bean> 

    <!--
      - Another view - this one defines a different velocity template.
    -->
    <bean id="secondaryView" parent="parentVelocityView">
        <property name="url"><value>secondaryTemplate.vm</value></property> 
        <property name="attributes">
            <props>
                <prop key="title">My Velocity Secondary Page</prop>
            </props>
        </property>   
    </bean>

    13.4.5. 創建Velocity模版

    最后,你需要做的就是創建Velocity模版。我們定義的視圖引用了兩個模版,mainTemplate.vm和secondaryTemplate.vm。屬性文件velocity.proeprties定義這兩個文件被放在WEB-INF/velocity/下。如果你在velocity.properties中選擇通過classpath載入,它們應該被放在缺省包的目錄下,(WEB-INF/classes),或者WEB-INF/lib下的jar文件中。下面就是我們的‘secondaryView’看上去的樣子(簡化了的HTML文件)。

    ## $title is set in the view definition file for this view.
    <html>
        <head><title>$title</title></head>
        <body>
            <h1>This is $title!!</h1>

            ## model objects are set in the controller and referenced
            ## via bean properties o method names.  See the Velocity
            ## docs for info

            Model Value: $model.value
            Model Method Return Value: $model.getReturnVal()

        </body>
    </html>

    現在,當你的控制器返回一個ModelAndView包含“secondaryView”時,Velocity就會工作,將上面的頁面轉化為普通的HTML頁面。
    13.4.6. 表單處理

    Spring提供一個標簽庫給JSP頁面使用,其中包含了<spring:bind>標簽。這個標簽主要使表單能夠顯示在web層或業務層中的Validator驗證時產生的出錯消息。這種行為可以被Velocity宏和其他的Spring功能模擬實現。
    13.4.6.1. 驗證錯誤

    通過表單驗證而產生的出錯消息可以從屬性文件中讀取,這有助于維護和國際化它們。Spring以它自己的方式處理這些,關于它的工作方式,你可以參考MVC指南或javadoc中的相關內容。為了訪問這些出錯消息,需要把RequestContext對象暴露給VelocityContext中的Velocity模版。修改你在views.properties或views.xml中的模版定義,給一個名字到它的attributes里(有了名字就可以被訪問到)。

    <bean id="welcomeView" parent="parentVelocityView">
        <property name="requestContextAttribute"><value>rc</value></property> 
        <property name="attributes">
            <props>
                <prop key="title">My Velocity Home Page</prop>
            </props>
        </property>    
    </bean>

    在我們前面例子的基礎上,上面的例子將RequestContext屬性命名為rc。這樣從這個視圖繼承的所有Velocity視圖都可以訪問$rc。
    13.4.6.2. Velocity的宏

    接下來,需要定義一個Velocity宏。既然宏可以在幾個Velocity模版(HTML表單)中重用,那么完全可以把宏定義在一個宏文件中。創建宏的詳細信息,參考Velocity文檔。

    下面的代碼應該放在你的Velocity模版根目錄的VM_global_library.vm文件中。

    #*
     * showerror
     *
     * display an error for the field name supplied if one exists
     * in the supplied errors object.
     *
     * param $errors the object obtained from RequestContext.getErrors( "formBeanName" )
     * param $field the field name you want to display errors for (if any)
     *
     *#
    #macro( showerror $errors $field )
        #if( $errors )
            #if( $errors.getFieldErrors( $field ))
                #foreach($err in $errors.getFieldErrors( $field ))
                    <span class="fieldErrorText">$rc.getMessage($err)</span><br />
                #end
            #end
        #end
    #end     

    13.4.6.3. 將出錯消息和HTML的域關聯起來

    最后,在你的HTML表單中,你可以使用和類似下面的代碼為每個輸入域顯示所綁定的出錯消息。

    ## set the following variable once somewhere at the top of
    ## the velocity template
    #set ($errors=$rc.getErrors("commandBean"))
    <html>
    ...
    <form ...>
        <input name="query" value="$!commandBean.query"><br>
        #showerror($errors "query")
    </form>
    ...
    </html>       

    13.4.7. 總結

    總之,下面是上面那個例子的文件目錄結構。只有一部分被顯示,其他一些必要的目錄沒有顯示出來。文件定位出錯很可能是Velocity視圖不能工作的主要原因,其次在視圖中定義了錯誤的屬性也是很常見的原因。

    ProjectRoot
      |
      +- WebContent
          |
          +- WEB-INF
              |
              +- lib
              |   |
              |   +- velocity-1.3.1.jar
              |   +- spring.jar
              |
              +- velocity
              |   |
              |   +- VM_global_library.vm
              |   +- mainTemplate.vm
              |   +- secondaryTemplate.vm
              |
              +- frontcontroller-servlet.xml
              +- frontcontroller-views.xml
              +- velocity.properties

    13.5. XSLT視圖

    XSLT 一種用于XML文件的轉換語言,作為web應用的一種表現層技術非常流行。如果你的應用本身需要處理XML文件,或者你的數據模型很容易轉換成XML文件,XSLT就是一個不錯的選擇。下面介紹如何生成XML文檔用作模型數據,以及如何在Spring應用中使用XSLT轉換它們。
    13.5.1. My First Words

    這個Spring應用的例子在控制器中創建一組單詞,并把它們加到數據模型的映射表中。這個映射表和我們XSLT視圖的名字一起被返回。關于Spring中Controller的詳細信息,參考第 12.3 節 “控制器” 。 XSLT視圖會把這組單詞生成一個簡單XML文檔用于轉換。
    13.5.1.1. Bean的定義

    對于一個簡單的Spring應用,配置是標準的。DispatcherServlet配置文件包含一個ViewResolver,URL映射和一個控制器bean..

    <bean id="homeController"class="xslt.HomeController"/>

    ..它實現了我們單詞的產生“邏輯”。
    13.5.1.2. 標準MVC控制器代碼

    控制器邏輯被封裝在AbstractController的子類中,包含象下面這樣的處理器方法。

    protected ModelAndView handleRequestInternal(
        HttpServletRequest req,
        HttpServletResponse resp)
        throws Exception {
           
        Map map = new HashMap();
        List wordList = new ArrayList();
           
        wordList.add("hello");
        wordList.add("world");
          
        map.put("wordList", wordList);
         
        return new ModelAndView("home", map);
    }

    到目前為止,我們沒有做任何XSLT特定的東西。模型數據的創建方式和其他Spring的 MVC應用相同。現在根據不同的應用配置,這組單詞被作為請求屬性交給JSP/JSTL處理,或者作為VelocityContext里的對象,交給 Velocity處理。為了使XSLT能處理它們,它們必須得轉換成某種XML文檔。有一些軟件包可以自動DOM化一個對象圖,但在Spring中,你可以用任何方式把你的模型轉換成DOM樹。這樣可以避免使XML轉換決定你模型數據結構,這在使用工具管理DOM化過程的時候是很危險的。
    13.5.1.3. 把模型數據轉換成XML文檔

    為了從我們的單詞列表或其他模型數據中創建DOM文檔,我們繼承org.springframework.web.servlet.view.xslt.AbstractXsltView。同時,我們必須實現抽象方法createDomNode()。傳給它的第一個參數就是我們的數據模型的Map。下面是我們這個應用中HomePage類的源程序清單 - 它使用JDOM來創建XML文檔,然后在轉換成所需要的W3C節點,這僅僅是因為我發現JDOM(和Dom4J)的API比W3C的API簡單。

    package xslt;

    // imports omitted for brevity

    public class HomePage extends AbstractXsltView {

        protected Node createDomNode(
            Map model, String rootName, HttpServletRequest req, HttpServletResponse res
        ) throws Exception {
           
            org.jdom.Document doc = new org.jdom.Document();
            Element root = new Element(rootName);
            doc.setRootElement(root);

            List words = (List) model.get("wordList");
            for (Iterator it = words.iterator(); it.hasNext();) {
                String nextWord = (String) it.next();
                Element e = new Element("word");
                e.setText(nextWord);
                root.addContent(e);
            }

            // convert JDOM doc to a W3C Node and return
            return new DOMOutputter().output( doc );
        }

    }

    13.5.1.3.1. 添加樣式表參數

    你的視圖子類可以定義一些name/value組成的參數,這些參數將被加到轉換對象中。參數的名字必須符合你在XSLT模版中使用<xsl:param name="myParam">defaultValue</xsl:param>格式定義的參數名。為了指定這些參數,可以從AbstractXsltView中重載方法getParameters(),并返回包含name/value組合的Map。
    13.5.1.3.2. 格式化日期和貨幣

    不象JSTL和Velocity,XSLT對和本地化相關的貨幣和日期格式化支持較弱。Spring為此提供了一個幫助類,讓你在createDomNode()中使用,從而獲得這些支持。詳細請參考org.springframework.web.servlet.view.xslt.FormatHelper的javadoc。
    13.5.1.4. 定義視圖屬性

    下面是單視圖應用的屬性文件views.properties(如果你使用基于XML的視圖解析器,比如上面例子中的Velocity,它等價于XML定義),如我們的“My First Words”..

    home.class=xslt.HomePage
    home.stylesheetLocation=/WEB-INF/xsl/home.xslt
    home.root=words

    這兒,你可以看到視圖是如何綁定在由屬性“.class”定義的HomePage類上的,HomePage類處理數據模型的DOM化操作。屬性 “stylesheetLocation”指定了將XML文檔轉換成HTML文檔時所需要的XSLT文件,而最后一個屬性“.root”指定了XML文檔根節點的名字。它被上面的HomePage類作為第二個參數傳遞給createDomNode方法。
    13.5.1.5. 文檔轉換

    最后,我們定義了XSLT的代碼來轉換上面的XML文檔。在views.properties文件中指定了這個XSLT文件home.xslt存放在war文件里的WEB-INF/xsl下。

    <?xml version="1.0"?>

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
        <xsl:output method="text/html" omit-xml-declaration="yes"/>

        <xsl:template match="/">
            <html>
                <head><title>Hello!</title></head>
                <body>

                    <h1>My First Words</h1>
                    <xsl:for-each select="wordList/word">
                        <xsl:value-of select="."/><br />
                    </xsl:for-each>

                </body>
            </html>
        </xsl:template>

    </xsl:stylesheet>

    13.5.2. 總結

    下面的WAR文件結構簡單列了一些上面所提到的文件和它們在WAR文件中的位置。

    ProjectRoot
      |
      +- WebContent
          |
          +- WEB-INF
              |
              +- classes
              |    |
              |    +- xslt
              |    |   |
              |    |   +- HomePageController.class
              |    |   +- HomePage.class
              |    |
              |    +- views.properties
              |
              +- lib
              |   |
              |   +- spring.jar
              |
              +- xsl
              |   |
              |   +- home.xslt
              |
              +- frontcontroller-servlet.xml

    當然,你還需要保證XML解析器和XSLT引擎在classpath中可以被找到。JDK 1.4會缺省提供它們,并且大多數J2EE容器也會提供它們,但是這也是一些已知的可能引起錯誤的原因。
    13.6. 文檔視圖 (PDF/Excel)
    13.6.1. 簡介

    HTML頁面并不總是向用戶顯示數據輸出的最好方式,Spring支持從數據動態生成PDF或Excel文件,并使這一過程變得簡單。文檔本身就是視圖,從服務器以流的方式加上內容類型返回文檔,客戶端PC只要運行電子表格軟件或PDF瀏覽軟件就可以瀏覽。

    為了使用Excel電子表格,你需要在你的classpath中加入‘poi’庫文件,而對PDF文件,則需要iText.jar文件。它們都包含在Spring的主發布包中。
    13.6.2. 配置和安裝

    基于文檔的視圖的處理方式和XSLT視圖幾乎完全相同,下面的部分將以前面的例子為基礎,演示了XSLT例子中的控制器是如何使用相同的數據模型生成PDF文檔和Excel電子表格(它們可以在Open Office中打開或編輯)。
    13.6.2.1. 文檔視圖定義

    首先,讓我們來修改一下view.properties文件(或等價的xml定義),給兩種文檔類型都添加一個視圖定義。加上剛才XSLT視圖例子的內容,整個文件如下。

    home.class=xslt.HomePage
    home.stylesheetLocation=/WEB-INF/xsl/home.xslt
    home.root=words

    xl.class=excel.HomePage

    pdf.class=pdf.HomePage

    如果你添加你的數據到一個模版電子表格,必須在視圖定義的‘url’屬性中指定模版位置。
    13.6.2.2. 控制器代碼

    我們用的控制器代碼和前面XSLT例子中用的一樣,除了視圖的名字。當然,你可以干得巧妙一點,使它基于URL參數或者其他邏輯-這證明了Spring的確在分離視圖和控制器方面非常出色!
    13.6.2.3. 用于Excel視圖的視圖子類化

    正入我們在XSLT例子中做的,為了在生成輸出文檔的過程中實現定制的行為,我們將繼承合適的抽象類。對于Excel,這包括提供一個org.springframework.web.servlet.view.document.AbstractExcelView的子類,并實現buildExcelDocument方法。

    下面是一個我們Excel視圖的源程序清單,它在電子表格中每一行的第一列中顯示模型map中的單詞。

    package excel;

    // imports omitted for brevity

    public class HomePage extends AbstractExcelView {

        protected void buildExcelDocument(
            Map model,
            HSSFWorkbook wb,
            HttpServletRequest req,
            HttpServletResponse resp)
            throws Exception {
       
            HSSFSheet sheet;
            HSSFRow sheetRow;
            HSSFCell cell;

            // Go to the first sheet
            // getSheetAt: only if wb is created from an existing document
            //sheet = wb.getSheetAt( 0 );
            sheet = wb.createSheet("Spring");
            sheet.setDefaultColumnWidth((short)12);

            // write a text at A1
            cell = getCell( sheet, 0, 0 );
            setText(cell,"Spring-Excel test");

            List words = (List ) model.get("wordList");
            for (int i=0; i < words.size(); i++) {
                cell = getCell( sheet, 2+i, 0 );
                setText(cell, (String) words.get(i));

            }
        }
    }

    如果你現在修改控制器使它返回xl作為視圖的名字(return new ModelAndView("xl", map);),并且運行你的應用,當你再次對該頁面發起請求時,Excel電子表格被創建,自動下載。
    13.6.2.4. 用于PDF視圖的視圖子類化

    單詞列表的PDF版本就更為簡單了。這次,需要象下面一樣繼承org.springframework.web.servlet.view.document.AbstractPdfView,并實現buildPdfDocument()方法。

    package pdf;

    // imports omitted for brevity

    public class PDFPage extends AbstractPdfView {

        protected void buildPdfDocument(
            Map model,
            Document doc,
            PdfWriter writer,
            HttpServletRequest req,
            HttpServletResponse resp)
            throws Exception {
           
            List words = (List) model.get("wordList");
           
            for (int i=0; i<words.size(); i++)
                doc.add( new Paragraph((String) words.get(i)));
       
        }
    }

    同樣修改控制器,使它通過return new ModelAndView("pdf", map);返回一個pdf視圖;并在你的應用中重新載入該URL。這次就會出現一個PDF文檔,顯示存儲在模型數據的map中的單詞。
    13.7. Tapestry

    Tapestry是Apache Jakarta項目(http://jakarta.apache.org/tapestry)下的一個面向組件的web應用框架。Spring框架是圍繞輕量級容器概念建立的J2EE應用框架。雖然Spring它自己的web表現層功能也很豐富,但是使用Tapestry作為web表現層,Spring容器作為底層構建的J2EE應用有許多獨特的優勢。這一節將詳細介紹使用這兩種框架的最佳實現。這里假設你熟悉Tapestry和Spring框架的基礎知識,這里就不再加以解釋了。對于Tapestry和Spring框架的一般性介紹文檔,可以在它們的網站找到。
    13.7.1. 架構

    一個由Tapestry和Spring構建的典型分層的J2EE應用包括一個上層的Tapestry表現層和許多底部層次構成,它們存在于一個或多個Spring應用上下文中。

        *

          用戶界面層:

          - 主要關注用戶界面的內容

          - 包含某些應用邏輯

          - 由Tapestry提供

          - 除了通過Tapestry提供的用戶界面,這一層的代碼訪問實現業務層接口的對象。實現對象由Spring應用上下文提供。
        *

          業務層:

          - 應用相關的“業務”代碼

          - 訪問域對象,并且使用Mapper API從某種數據存儲(數據庫)中存取域對象

          - 存在在一個或多個Spring上下文中

          - 這層的代碼以一種應用相關的方式操作域模型中的對象。它通過這層中的其它代碼和Mapper API工作。這層中的對象由某個特定的mapper實現通過應用上下文提供。

          - 既然這層中的代碼存在在Spring上下文中,它由Spring上下文提供事務處理,而不自己管理事務。
        *

          域模型:

          - 問題域相關的對象層次,這些對象處理和問題域相關的數據和邏輯

          - 雖然域對象層次被創建時考慮到它會被某種方式持久化,并且為此定義一些通用的約束(例如,雙向關聯),它通常并不知道其它層次的情況。因此,它可以被獨立地測試,并且在產品和測試這兩種不同的mapping實現中使用。

          - 這些對象可以是獨立的,也可以關聯Spring應用上下文以發揮它的優勢,例如隔離,反向控制,不同的策略實現,等等。
        *

          數據源層:

          - Mapper API(也稱為Data Access Objects):是一種將域模型持久化到某種數據存儲(一般是數據庫,但是也可以是文件系統,內存,等等)的API。

          - Mapper API實現:是指Mapper API的一個或多個特定實現,例如,Hibernate的mapper,JDO的mapper,JDBC的mapper,或者內存mapper。

          - mapper實現存在在一個或多個Spring應用上下文中。一個業務層對象需要應用上下文的mapper對象才能工作。
        *

          數據庫,文件系統,或其它形式的數據存儲:

          - 在域模型中的對象根據一個或多個mapper實現可以存放在不止一個數據存儲中

          - 數據存儲的方式可以是簡單的(例如,文件系統),或者有它域模型自己的數據表達(例如,一個數據庫中的schema)。但是它不知道其它層次的情況。

    13.7.2. 實現

    真正的問題(本節所需要回答的),是Tapestry頁面是如何訪問業務實現的,業務實現僅僅是定義在Spring應用上下文實例中的bean。
    13.7.2.1. 應用上下文示例

    假設我們以xml格式定義的下面的應用上下文:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
            "http://www.springframework.org/dtd/spring-beans.dtd">
     
    <beans>
     
        <!-- ========================= GENERAL DEFINITIONS ========================= -->
     
        <!-- ========================= PERSISTENCE DEFINITIONS ========================= -->
     
        <!-- the DataSource -->
        <bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
            <property name="jndiName"><value>java:DefaultDS</value></property>
            <property name="resourceRef"><value>false</value></property>
        </bean>
     
        <!-- define a Hibernate Session factory via a Spring LocalSessionFactoryBean -->
        <bean id="hibSessionFactory"
            class="org.springframework.orm.hibernate.LocalSessionFactoryBean">
            <property name="dataSource"><ref bean="dataSource"/></property>
        </bean>
     
        <!--
         - Defines a transaction manager for usage in business or data access objects.
         - No special treatment by the context, just a bean instance available as reference
         - for business objects that want to handle transactions, e.g. via TransactionTemplate.
         -->
        <bean id="transactionManager"
            class="org.springframework.transaction.jta.JtaTransactionManager">
        </bean>
     
        <bean id="mapper"
            class="com.whatever.dataaccess.mapper.hibernate.MapperImpl">
            <property name="sessionFactory"><ref bean="hibSessionFactory"/></property>
        </bean>
      
        <!-- ========================= BUSINESS DEFINITIONS ========================= -->
     
        <!-- AuthenticationService, including tx interceptor -->
        <bean id="authenticationServiceTarget"
            class="com.whatever.services.service.user.AuthenticationServiceImpl">
            <property name="mapper"><ref bean="mapper"/></property>
        </bean>
        <bean id="authenticationService"
            class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
            <property name="transactionManager"><ref bean="transactionManager"/></property>
            <property name="target"><ref bean="authenticationServiceTarget"/></property>
            <property name="proxyInterfacesOnly"><value>true</value></property>
            <property name="transactionAttributes">
                <props>
                    <prop key="*">PROPAGATION_REQUIRED</prop>
                </props>
            </property>
        </bean> 
     
        <!-- UserService, including tx interceptor -->
        <bean id="userServiceTarget"
            class="com.whatever.services.service.user.UserServiceImpl">
            <property name="mapper"><ref bean="mapper"/></property>
        </bean>
        <bean id="userService"
            class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
            <property name="transactionManager"><ref bean="transactionManager"/></property>
            <property name="target"><ref bean="userServiceTarget"/></property>
            <property name="proxyInterfacesOnly"><value>true</value></property>
            <property name="transactionAttributes">
                <props>
                    <prop key="*">PROPAGATION_REQUIRED</prop>
                </props>
            </property>
        </bean> 
     
     </beans>

    在Tapestry應用中,我們需要載入這個應用上下文,并允許Tapestry頁面訪問authenticationService和 userService這兩個bean,它們分別實現了AuthenticationService接口和UserService接口。
    13.7.2.2. 在Tapestry頁面中獲取bean

    在這點上,web應用可以調用Spring的靜態工具方法WebApplicationContextUtils.getApplicationContext(servletContext)來獲取應用上下文,參數servletContext是J2EE Servlet規范定義的標準ServletContext。因此,頁面獲取例如UserService實例的一個簡單方法就象下面的代碼:

        WebApplicationContext appContext = WebApplicationContextUtils.getApplicationContext(
            getRequestCycle().getRequestContext().getServlet().getServletContext());
        UserService userService = appContext.getBean("userService");
        ... some code which uses UserService

    這個方法可以工作。將大部分邏輯封裝在頁面或組件基類的一個方法中可以減少很多冗余。然而,這在某些方面違背了Spring所倡導的反向控制方法,而應用中其它層次恰恰在使用反向控制,因為你希望頁面不必向上下文要求某個名字的bean,事實上,頁面也的確對上下文一無所知。

    幸運的是,有一個方法可以做到這一點。這是因為Tapestry已經提供一種方法給頁面添加聲明屬性,事實上,以聲明方式管理一個頁面上的所有屬性是首選的方法,這樣Tapestry能夠將屬性的生命周期作為頁面和組件生命周期的一部分加以管理。
    13.7.2.3. 向Tapestry暴露應用上下文

    首先我們需要Tapestry頁面組件在沒有ServletContext的情況下訪問ApplicationContext;這是因為在頁面/組件生命周期里,當我們需要訪問ApplicationContext時,ServletContext并不能被頁面很方便地訪問到,所以我們不能直接使用WebApplicationContextUtils.getApplicationContext(servletContext)。一個方法就是實現一個特定的Tapestry的IEngine來暴露它:

    package com.whatever.web.xportal;
    ...
    import ...
    ...
    public class MyEngine extends org.apache.tapestry.engine.BaseEngine {
     
        public static final String APPLICATION_CONTEXT_KEY = "appContext";
     
        /**
         * @see org.apache.tapestry.engine.AbstractEngine#setupForRequest(org.apache.tapestry.request.RequestContext)
         */
        protected void setupForRequest(RequestContext context) {
            super.setupForRequest(context);
        
            // insert ApplicationContext in global, if not there
            Map global = (Map) getGlobal();
            ApplicationContext ac = (ApplicationContext) global.get(APPLICATION_CONTEXT_KEY);
            if (ac == null) {
                ac = WebApplicationContextUtils.getWebApplicationContext(
                    context.getServlet().getServletContext()
                );
                global.put(APPLICATION_CONTEXT_KEY, ac);
            }
        }
    }

    這個engine類將Spring應用上下文作為“appContext”屬性存放在Tapestry應用的“Global”對象中。在Tapestry應用定義文件中必須保證這個特殊的IEngine實例在這個Tapestry應用中被使用。例如,

    file: xportal.application:
     
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE application PUBLIC
        "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
        "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
    <application
        name="Whatever xPortal"
        engine-class="com.whatever.web.xportal.MyEngine">
    </application>

    13.7.2.4. 組件定義文件

    現在在我們的頁面或組件定義文件(*.page或*.jwc)中,我們僅僅添加property-specification元素從ApplicatonContext中獲取bean,并為這些bean創建頁面或組件屬性。例如:

        <property-specification name="userService"
                                type="com.whatever.services.service.user.UserService">
            global.appContext.getBean("userService")
        </property-specification>
        <property-specification name="authenticationService"
                                type="com.whatever.services.service.user.AuthenticationService">
            global.appContext.getBean("authenticationService")
        </property-specification>

    在property-specification中定義的OGNL表達式使用上下文中的bean來指定屬性的初始值。整個頁面定義文件如下:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE page-specification PUBLIC
        "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
        "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
        
    <page-specification class="com.whatever.web.xportal.pages.Login">
     
        <property-specification name="username" type="java.lang.String"/>
        <property-specification name="password" type="java.lang.String"/>
        <property-specification name="error" type="java.lang.String"/>
        <property-specification name="callback" type="org.apache.tapestry.callback.ICallback" persistent="yes"/>
        <property-specification name="userService"
                                type="com.whatever.services.service.user.UserService">
            global.appContext.getBean("userService")
        </property-specification>
        <property-specification name="authenticationService"
                                type="com.whatever.services.service.user.AuthenticationService">
            global.appContext.getBean("authenticationService")
        </property-specification>
      
        <bean name="delegate" class="com.whatever.web.xportal.PortalValidationDelegate"/>
     
        <bean name="validator" class="org.apache.tapestry.valid.StringValidator" lifecycle="page">
            <set-property name="required" expression="true"/>
            <set-property name="clientScriptingEnabled" expression="true"/>
        </bean>
     
        <component id="inputUsername" type="ValidField">
            <static-binding name="displayName" value="Username"/>
            <binding name="value" expression="username"/>
            <binding name="validator" expression="beans.validator"/>
        </component>
      
        <component id="inputPassword" type="ValidField">
            <binding name="value" expression="password"/>
           <binding name="validator" expression="beans.validator"/>
           <static-binding name="displayName" value="Password"/>
           <binding name="hidden" expression="true"/>
        </component>
     
    </page-specification>

    13.7.2.5. 添加抽象訪問方法

    現在在頁面或組件本身的Java類定義中,我們所需要做的是為我們定義的屬性添加抽象getter方法。當Tapestry真正載入頁面或組件時,Tepestry會對類文件作一些運行時的代碼處理,添加已定義的屬性,掛接抽象getter方法到新創建的域上。例如:

        // our UserService implementation; will come from page definition
        public abstract UserService getUserService();
        // our AuthenticationService implementation; will come from page definition
        public abstract AuthenticationService getAuthenticationService();

    這個例子的login頁面的完整Java類如下:

    package com.whatever.web.xportal.pages;
     
    /**
     *  Allows the user to login, by providing username and password.
     *  After succesfully logging in, a cookie is placed on the client browser
     *  that provides the default username for future logins (the cookie
     *  persists for a week).
     */
    public abstract class Login extends BasePage implements ErrorProperty, PageRenderListener {
     
        /** the key under which the authenticated user object is stored in the visit as */
        public static final String USER_KEY = "user";
      
        /**
         * The name of a cookie to store on the user's machine that will identify
         * them next time they log in.
         **/
        private static final String COOKIE_NAME = Login.class.getName() + ".username"; 
        private final static int ONE_WEEK = 7 * 24 * 60 * 60;
     
        // --- attributes
     
        public abstract String getUsername();
        public abstract void setUsername(String username);
     
        public abstract String getPassword();
        public abstract void setPassword(String password);
     
        public abstract ICallback getCallback();
        public abstract void setCallback(ICallback value);
       
        public abstract UserService getUserService();
     
        public abstract AuthenticationService getAuthenticationService();
     
        // --- methods
     
        protected IValidationDelegate getValidationDelegate() {
            return (IValidationDelegate) getBeans().getBean("delegate");
        }
     
        protected void setErrorField(String componentId, String message) {
            IFormComponent field = (IFormComponent) getComponent(componentId);
            IValidationDelegate delegate = getValidationDelegate();
            delegate.setFormComponent(field);
            delegate.record(new ValidatorException(message));
        }
     
        /**
         *  Attempts to login.
         *
         *  <p>If the user name is not known, or the password is invalid, then an error
         *  message is displayed.
         *
         **/
        public void attemptLogin(IRequestCycle cycle) {
        
            String password = getPassword();
     
            // Do a little extra work to clear out the password.
     
            setPassword(null);
            IValidationDelegate delegate = getValidationDelegate();
     
            delegate.setFormComponent((IFormComponent) getComponent("inputPassword"));
            delegate.recordFieldInputValue(null);
     
            // An error, from a validation field, may already have occured.
     
            if (delegate.getHasErrors())
                return;
     
            try {
                User user = getAuthenticationService().login(getUsername(), getPassword());
               loginUser(user, cycle);
            }
            catch (FailedLoginException ex) {
                this.setError("Login failed: " + ex.getMessage());
                return;
            }
        }
     
        /**
         *  Sets up the {@link User} as the logged in user, creates
         *  a cookie for their username (for subsequent logins),
         *  and redirects to the appropriate page, or
         *  a specified page).
         *
         **/
        public void loginUser(User user, IRequestCycle cycle) {
        
            String username = user.getUsername();
     
            // Get the visit object; this will likely force the
            // creation of the visit object and an HttpSession.
     
            Map visit = (Map) getVisit();
            visit.put(USER_KEY, user);
     
            // After logging in, go to the MyLibrary page, unless otherwise
            // specified.
     
            ICallback callback = getCallback();
     
            if (callback == null)
                cycle.activate("Home");
            else
                callback.performCallback(cycle);
     
            // I've found that failing to set a maximum age and a path means that
            // the browser (IE 5.0 anyway) quietly drops the cookie.
     
            IEngine engine = getEngine();
            Cookie cookie = new Cookie(COOKIE_NAME, username);
            cookie.setPath(engine.getServletPath());
            cookie.setMaxAge(ONE_WEEK);
     
            // Record the user's username in a cookie
     
            cycle.getRequestContext().addCookie(cookie);
     
            engine.forgetPage(getPageName());
        }
      
        public void pageBeginRender(PageEvent event) {
            if (getUsername() == null)
                setUsername(getRequestCycle().getRequestContext().getCookieValue(COOKIE_NAME));
        }
    }

    13.7.3. 小結

    在這個例子中,我們用聲明的方式將定義在Spring的ApplicationContext中業務bean能夠被頁面訪問。頁面類并不知道業務實現從哪里來,事實上,也很容易轉移到另一個實現,例如為了測試。這樣的反向控制是Spring框架的主要目標和優點,在這個Tapestry應用中,我們在J2EE棧上自始至終使用反向控制。

    posted on 2007-06-13 09:53 chenguo 閱讀(745) 評論(0)  編輯  收藏 所屬分類: Spring Dev

    <2025年5月>
    27282930123
    45678910
    11121314151617
    18192021222324
    25262728293031
    1234567

    導航

    統計

    留言簿

    隨筆分類(1)

    文章分類(52)

    好友 小山的博客

    最新隨筆

    最新評論

    主站蜘蛛池模板: 欧洲亚洲国产精华液| 久久久久亚洲AV无码去区首| 精品国产污污免费网站入口| 亚洲中文无码av永久| 91av免费观看| 亚洲视频一区二区三区四区| 野花高清在线观看免费完整版中文| 亚洲国产超清无码专区| 男人的好免费观看在线视频| 亚洲AV成人影视在线观看| 性做久久久久久久免费看| 亚洲日本一线产区和二线 | 成人国产精品免费视频| 亚洲中文字幕在线乱码| 久操免费在线观看| 欧洲 亚洲 国产图片综合| 在线观看亚洲免费视频| jizz免费观看| 亚洲一区二区三区首页| 青青久在线视频免费观看| 在线观看亚洲免费视频| 亚洲午夜未满十八勿入网站2| 99久久人妻精品免费二区| 亚洲a级片在线观看| 免费大片在线观看网站| 日韩免费电影网站| 亚洲中文字幕无码一去台湾 | 免费va人成视频网站全| 久久国产精品免费一区| 亚洲黄色免费电影| 国产免费无遮挡精品视频| 国产三级在线免费观看| 亚洲精品一区二区三区四区乱码 | 久久精品无码专区免费| 91天堂素人精品系列全集亚洲 | 美女裸身网站免费看免费网站| 亚洲欧美成人一区二区三区| 中文字幕亚洲综合久久菠萝蜜| 亚洲国产精品免费观看| 青青青视频免费观看| 亚洲卡一卡2卡三卡4麻豆|