2011年9月30日 星期五

apache tiles 2 學習筆記

Apache tiles是一個版型模組化的工具.

通常網頁至少會將版型切割為4個區塊, 例如下圖:


tiles 的定義檔如下:
<tiles-definitions>
  <definition name="indexTemplate" template="/WEB-INF/views/tiles/indexTemplate.jsp">
    <put-attribute name="title" value="Welcome" />
    <put-attribute name="header" value="/WEB-INF/views/tiles/header.jsp" />
    <put-attribute name="category" value="/WEB-INF/views/tiles/category.jsp" />
    <put-attribute name="browse" value="/WEB-INF/views/tiles/browse.jsp" />
    <put-attribute name="footer" value="/WEB-INF/views/tiles/footer.jsp" />
  </definition>
</tiles-definitions>


而頁面變更的時候, 通常都只是更換整頁中的某一個區塊, 其它區塊不需要變化.

例如登入時, 只需要將右邊的區塊變更為登入頁.


登入頁 tiles 的設定如下, 只要更換 title 及 browse 二個變數的定義即可.
<!-- Login Page -->
<definition name="login" extends="indexTemplate">
    <put-attribute name="title" value="Login Page" />
    <put-attribute name="browse" value="/WEB-INF/views/tiles/login.jsp" />
</definition>


但考慮更複雜一點的情況呢?

例如電子商務中常見的, 點擊商品目錄.

此時商品目錄會展開, 且右邊會列出該目錄下的所有商品.

示意圖如下:


這時候就得在同一個 controller 裡處理商品及目錄的邏輯.
然後分別放入不同的 tiles 變數中.

等一下下...似乎有點奇怪, 目錄跟商品雖然有關係, 但畢竟各自有不同的處理邏輯.
放在同一個 controller 內處理似乎怪怪的...

如果再回頭看一下登入頁的例子, 目錄的頁面雖然不需變動, 但此時目錄頁應呈現第一層目錄結構.

第一層目錄結構的邏輯, 跟登入有什麼關係呢 ?


是否有辦法將各區塊的內容獨立處理, 讓彼此不相干的邏輯不要混在一起呢?


還好, tiles 提供了 View Preparer 來處理這種情況.


2011年9月28日 星期三

Spring MVC 的怪事一椿

今天遇到怪事一椿, 其實已經困擾我一陣子了, 只是今天才發現原因所在.

Spring MVC 的 @RequestMapping 註解可用來標示 Url, 例如:


@RequestMapping(value={"index"}, method=RequestMethod.POST)
public ModelAndView index(HttpSession session) {
    ...
}


如果同時有二個相同的註解, spring 會報錯, 訊息如下:

2318 [main] ERROR org.springframework.web.context.ContextLoader - Context initialization failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping#0': Initialization of bean failed; nested exception is java.lang.IllegalStateException: Cannot map handler 'xxxxController' to URL path [/index]: There is already handler of type [class yyyyController$$EnhancerByCGLIB$$349da4f8] mapped.
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:527)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:456)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:295)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:292)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:580)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:900)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:455)
at org.springframework.web.context.ContextLoader.createWebApplicationContext(ContextLoader.java:294)
at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:215)
at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:47)
at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4206)
at org.apache.catalina.core.StandardContext.start(StandardContext.java:4705)
at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1057)
at org.apache.catalina.core.StandardHost.start(StandardHost.java:840)
at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1057)
at org.apache.catalina.core.StandardEngine.start(StandardEngine.java:463)
at org.apache.catalina.core.StandardService.start(StandardService.java:525)
at org.apache.catalina.core.StandardServer.start(StandardServer.java:754)
at org.apache.catalina.startup.Catalina.start(Catalina.java:595)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:289)
at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:414)


但有一個例外, 就是 / , 一般看到網站的 / 會參考 tomcat 的 web.xml中 welcome-file 的設定.

若是下面的寫法, spring 不會報錯, welcome-file也不會生效, 但會連到那個 URL 就不知道了...


@RequestMapping(value={"/", "index"})
public ModelAndView index(HttpSession session) {
...
}


@RequestMapping(value={"/", "login"})
public ModelAndView login(HttpSession session) {
...
}

2011年9月20日 星期二

動態載入圖示 Animated Logo

今天才知道, 原來常見的動態載入小圖示, 如圖:

Throbber





有個名字, 叫做 Throbber

還有網站可以幫忙製作呢.


搭配 jQuery 的 BlockUI Plugin
可以很快做出不錯的效果.

範例如下:

$.blockUI({ message: '<h3><img src="/images/ajax-loader.gif" /> Please wait...</h3>' });


實際操作的時候, 發現圖常常出不來, 應是圖片載入過慢, 需將圖檔做預載(pre-load)處理, 作法如下:

$(document).ready(function() {
throbber = new Image() ;
throbber.src = "/images/ajax-loader.gif" ;
}) ;




2011年9月5日 星期一

tomcat jsessionid url problem

Spring MVC 可以 cache static content, 例如圖檔, js, css...

參考文件

但我之前遇到一個問題, 就是原本應顯示為 /appbased/static-1.0.0/images/xxx 的 URL

第一次執行時, URL 卻會變成 /appbased/static-1.0.0?jsessionid=481AFDE46F818357ED2096824F250AFF/images/xxx

需重新 reload 一次才回復正常.

當時我的作法是, 在首次登入頁面的 jsp 前面加一行
<% request.getSession(true).invalidate(); %>
即可.


但最近又遇到另一個問題, 就是首頁有可能是登入狀態, 也有可能是未登入狀態, 總不能老是把 session 抓出來幹掉裝作沒事, 故查了一下解法.