2015年9月29日 星期二

攔截器 (Struts2.3.x 十)

※系統內鍵的攔截器

HelloInterceptor.java
public class HelloInterceptor extends ActionSupport{
    public void Hello(){
        System.out.println("Hello Interceptor!");
    }
}
和一般action一樣
struts.xml
<action name="hello" class="action.HelloInterceptor">
    <interceptor-ref name="timer" />
</action>
因為package有extends="struts-default",這個檔案在struts2-core-2.x.x.jar裡, 所以name可以是timer,還有很多,可以參考這裡
測試時,在瀏覽器的網址列最後打上hello!Hello.action, 記得struts.xml要加上<constant name="struts.enable.DynamicMethodInvocation" value="true" />

※自訂攔截器

TestInterceptor.java
public class TestInterceptor extends AbstractInterceptor {
    @Override
    public String intercept(ActionInvocation invocation) throws Exception {
        System.out.println("攔截成功!");
        return invocation.invoke();
    }
}
繼承一個叫AbstractInterceptor的抽象類別,並覆寫方法,invocation.invoke()表示繼續執行

struts.xml
<interceptors>
    <interceptor name="testICR" class="interceptor.TestInterceptor" />
</interceptors>
<action name="hello" class="action.HelloInterceptor">
    <interceptor-ref name="timer" />
    <interceptor-ref name="testICR" />
</action>
可以有很多攔截器,這個例子有兩個,要注意interceptors要寫在action上面,錯誤會有提示

※多個攔截器設定

也就是上面的struts.xml可以改成以下的寫法

<interceptors>
    <!-- interceptor可以很多 -->
    <interceptor name="testICR" class="interceptor.TestInterceptor" />
    
    <interceptor-stack name="xxx">
        <interceptor-ref name="timer" />
        <interceptor-ref name="testICR" />
    </interceptor-stack>
</interceptors>

<action name="hello" class="action.HelloInterceptor">
    <interceptor-ref name="xxx" />
</action>
把interceptor-ref搬上去後,用interceptor-stack包起來,然後取個名字,下面就使用新的名字即可

※全域攔截器設定

<interceptors>
    <interceptor name="testICR" class="interceptor.TestInterceptor" />
    
    <interceptor-stack name="xxx">
        <interceptor-ref name="timer" />
        <interceptor-ref name="testICR" />
    </interceptor-stack>
    <default-interceptor-ref name="xxx" />
</interceptors>

<action name="hello1" class="action.HelloInterceptor1" />
<action name="hello2" class="action.HelloInterceptor2" />
<action name="hello3" class="action.HelloInterceptor3" />

default-interceptor-ref設定完,所有的actin在執行之前都會攔截

※defaultStack

攔截器有個defaultStack,主要是執行struts2的方法
<interceptor-ref name="defaultStack" />
不加時,會執行setContainer()-->execute()
加上後,會執行setContainer()-->validate()-->hasErrors()-->execute()
所以如果想用validate方法,就要設這一行

※模擬一個登入攔截

登入時,如果帳號或密碼沒輸入,就再原網頁不動

login.jsp
<form action="login.action" method="post">
    <input type="text" name="user.userName" /><br /> 
    <input type="password" name="user.userPassword" /><br /> 
    <input type="reset" />
    <input type="submit" />
</form>

struts.xml
<interceptors>
    <interceptor name="login" class="interceptor.LoginInterceptor" />
    <interceptor-stack name="stack">
        <interceptor-ref name="login" />
    </interceptor-stack>
</interceptors>

<action name="login" class="login.LoginAction" method="execute">
    <interceptor-ref name="stack" />
    <result name="login">/struts2/login.jsp</result>
    
    <!-- 方法一 -->
    <result name="success">/struts2/success.jsp</result>
    <!-- 方法二,只是測試redirectAction -->
    <result name="success" type="redirectAction">xxx.action</result>
</action>
<!-- 此action方法二才需要 -->
<action name="xxx" class="login.LoginAction" method="success">
    <result name="success">/struts2/success.jsp</result>
</action>

LoginInterceptor.java的intercept
HttpServletRequest req = ServletActionContext.getRequest();
User user = new User();
user.setUserName(req.getParameter("user.userName"));
user.setUserPassword(req.getParameter("user.userPassword"));

// 方法一
Map<String, Object> map = invocation.getInvocationContext()
        .getSession();
// 方法二
// Map<String, Object> map = ActionContext.getContext().getSession();
if (user.getUserName().isEmpty() || user.getUserPassword().isEmpty()) {
    //方法一
    HttpServletResponse res = ServletActionContext.getResponse();
    res.sendRedirect("/Struts2Demo/struts2/login.jsp");
    return null;
    //方法二
    // return ActionSupport.LOGIN;
} else {
    map.put("user", user);
    return invocation.invoke();
}

LoginAction.java,記得繼承ActionSupport
private User user;
//setter/getter
@Override
public String execute() throws Exception {
    return SUCCESS;
}
//此方法為struts.xml的方法二才需要
public String success() {
    return SUCCESS;
}

User.java
private String userName;
private String userPassword;
//setter/getter

success.jsp
<%@ taglib prefix="s" uri="/struts-tags"%>
<%@ page isELIgnored="false"%>

OGNL:
<ul>
    <li>帳號:<s:property value="#session.user.userName"/>
    <li>密碼:<s:property value="#session.user.userPassword"/>
</ul>

EL:
<ul>
    <li>帳號:${user.userName}
    <li>密碼:${user.userPassword}
</ul>

※用攔截器防止頁面重覆提交

以登入的例子修改struts.xml
<constant name="struts.custom.i18n.resources" value="Message" />
<interceptors>
    <interceptor name="login" class="interceptor.LoginInterceptor" />
    
    <interceptor-stack name="stack">
        <interceptor-ref name="login" />
        <interceptor-ref name="basicStack" />
        <interceptor-ref name="token" />
    </interceptor-stack>
</interceptors>
<action name="login" class="login.LoginAction" method="execute">
    <interceptor-ref name="stack" />
    <result name="login">/struts2/login.jsp</result>
    <result name="success">/struts2/success.jsp</result>
    <result name="invalid.token">/struts2/login.jsp</result>
</action>
注意invalid.token要設定

login.jsp增加兩行
<form action="login.action" method="post">
    <s:token name="ooo" />
    <s:actionerror />
    <input type="text" name="user.userName" /><br /> 
    <input type="password" name="user.userPassword" /><br /> 
    <input type="reset" />
    <input type="submit" />
</form>
s:token的name可以隨便打,網頁原始碼可看出會變成兩行hidden
s:actionerror為針測到重覆提交時所產生的訊息

Message.properties 訊息定義在這個檔裡,struts.xml要設定這個檔名
struts.messages.invalid.token=\u8868\u55AE\u91CD\u8986\u63D0\u4EA4\uFF01

可參考這裡
如果把jsp的s:token放在form外面,不用重覆提交,一次就出現Message.properties的訊息了,而且在控制台會出「Could not find token name in params.」的錯

2015年9月28日 星期一

流程 (客製化JSP Tag 三)

 <<interface IterationTag>>
public static final int EVAL_BODY_AGAIN = 2;

<<interface Tag>>
public static final int SKIP_BODY = 0;
public static final int EVAL_BODY_INCLUDE = 1;
public static final int SKIP_PAGE = 5;
public static final int EVAL_PAGE = 6;

0、1給doStartTag()回傳
回傳0就調用doEndTag()
回傳1就調用doAfterBody()

0、2給doAfterBody()回傳
回傳0就調用doEndTag()
回傳2就調用自己,一直到回傳0為止

5、6給doEndTag()回傳
回傳5表示立即停止,忽略之後的頁面,但會將目前的輸出回傳到瀏覽器
回傳6表示正常結束


release():將資源全部釋放,修改後不重啟瀏覽器,過一下子就會直接調用


※SKIP_BODY、EVAL_BODY_INCLUDE

※AttrTag.java

public class AttrTag extends TagSupport {
    private String scope;
    private String content;
    // 這兩個屬性都給setter即可
    
@Override
public int doStartTag() throws JspException {
    System.out.println("doStartTag");
    Object value = null;
    if ("P".equals(scope)) {
        value = pageContext.getAttribute(content, PageContext.PAGE_SCOPE);
    }
    if ("R".equals(this.scope)) {
        value = pageContext.getAttribute(content, PageContext.REQUEST_SCOPE);
    }
    if ("S".equals(this.scope)) {
        value = pageContext.getAttribute(content, PageContext.SESSION_SCOPE);
    }
    if ("A".equals(this.scope)) {
        value = pageContext.getAttribute(content, PageContext.APPLICATION_SCOPE);
    }
    
    if (value == null) {
        return SKIP_BODY;
    } else {
        return EVAL_BODY_INCLUDE;
    }
    
}

※attr.tld

<tag>
    <name>displayScope</name>
    <tag-class>attr.AttrTag</tag-class>
    <body-content>JSP</body-content>
    <attribute>
        <name>scope</name>
        <required>true</required>
        <rtexprvalue>true</rtexprvalue>
    </attribute>
    <attribute>
        <name>content</name>
        <required>true</required>
        <rtexprvalue>true</rtexprvalue>
    </attribute>
</tag>

body content變成JSP


※attr.jsp

<%
    String scope = "P";
    if ("P".equals(scope)) {
        pageContext.setAttribute("ooo", scope);
    } else if ("R".equals(scope)) {
        request.setAttribute("ooo", scope);
    } else if ("S".equals(scope)) {
        session.setAttribute("ooo", scope);
    } else if ("A".equals(scope)) {
        application.setAttribute("ooo", scope);
    }
%>
    
<bruce:displayScope scope="<%=scope%>" content="ooo">
    <h1>範圍:<%=scope%></h1>
    <h1>pageScope內容是${pageScope.ooo}</h1>
    <h2>requestScope內容是${requestScope.ooo}</h2>
    <h3>sessionScope內容是${sessionScope.ooo}</h3>
    <h4>applicationScope內容是${applicationScope.ooo}</h4>
</bruce:displayScope>

※如果scope不輸入PRSA其中一個,就會調用SKIP_BODY



※SKIP_BODY、EVAL_BODY_AGAIN


※jsp

<%
    List<String> list = new ArrayList<String>();
    list.add("A1");
    list.add("B2");
    list.add("C3");
    request.setAttribute("ooo", list);
%>
    
<bruce:iterate content="ooo" id="abc">
    ${abc}<br/>
</bruce:iterate>




※tld

<tag>
    <name>iterate</name>
    <tag-class>attr.AttrHaveContent2</tag-class>
    <body-content>JSP</body-content>
    <attribute>
        <name>content</name>
        <required>true</required>
        <rtexprvalue>true</rtexprvalue>
    </attribute>
    <attribute>
        <name>id</name>
        <required>true</required>
        <rtexprvalue>true</rtexprvalue>
    </attribute>
</tag>




※java

public class AttrHaveContent extends TagSupport {
    private String content;
    private Iterator<?> it;
    
    public void setContent(String content) {
        this.content = content;
    }
    
    @Override
    public int doStartTag() throws JspException {
        System.out.println("doStartTag");
        Object value = pageContext.getAttribute(content, PageContext.REQUEST_SCOPE);
    
        System.out.println("value=" + value);
        if (value == null) {
            return SKIP_BODY;
        } else {
            List<?> x = ((List<?>) value);
            // Set<String> x = ((Map<String, Object>) value).keySet();
            // Collection<Object> x = ((Map<String, Object>) value).values();
            it = x.iterator();
    
            if (it.hasNext()) {
                pageContext.setAttribute(id, it.next());
                return EVAL_BODY_INCLUDE;
            }
            return SKIP_BODY;
        }
    }
    
    @Override
    public int doEndTag() throws JspException {
        System.out.println("doEndTag");
        return SKIP_PAGE;
    }
    
    @Override
    public int doAfterBody() throws JspException {
        System.out.println("doAfterBody");
    
        if (it.hasNext()) {
            pageContext.setAttribute(id, it.next());
            return EVAL_BODY_AGAIN;
        } else {
            return SKIP_BODY;
        }
    }
}

※TagSupport.java裡有個屬性是id,是protected,所以可以不用宣告id,但要用時還是要在tld裡定義



※setValue、getValues

List<?> x = ((List<?>) value);
for (int i = 0; i < x.size(); i++) {
    String s = (String) x.get(i);
    setValue(s.substring(1), "x");
}
    
enu = getValues();
if (enu.hasMoreElements()) {
    pageContext.setAttribute(id, enu.nextElement());
    return EVAL_BODY_INCLUDE;
}

※TagSupport還有個Hashtable叫values,但它是private,但有setValue和getValues可以塞進去,getValues回傳值是Enumeration,和Iterator很像

※doAfterBody也改成Enumeration就可以取到值了,取到的是setValue的Key值



※SKIP_PAGE、EVAL_PAGE

※jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@taglib uri="attrHaveContent" prefix="bruce"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
    <head></head>
    <body>
        <bruce:endTagTest />
        ooo
    </body>
</html>



※web.xml

<jsp-config>
    <taglib>
        <taglib-uri>attrHaveContent</taglib-uri>
        <taglib-location>/WEB-INF/AttrHave.xml</taglib-location>
    </taglib>
</jsp-config>



※tld

<taglib version="2.1" xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd">
    <tlib-version>1.0</tlib-version>
    <short-name>ht</short-name>
    
    <tag>
        <name>endTagTest</name>
        <tag-class>attr.AttrHaveContent</tag-class>
        <body-content>empty</body-content>
    </tag>
</taglib>



※java

public class AttrHaveContent extends TagSupport {
    @Override
    public int doEndTag() throws JspException {
        System.out.println("doEndTag");
    
        JspWriter out = this.pageContext.getOut();
        try {
            out.println("xxx");
        } catch (IOException e) {
            e.printStackTrace();
        }
        return SKIP_PAGE;
    }
}

※回傳SKIP_PAGE時,不會印出ooo


繼承圖 (客製化JSP Tag 二)

※繼承圖



※各個Type的屬性、方法、建構子

<<JspTag>>

空的

<<Tag>>

static int EVAL_BODY_INCLUDE
static int EVAL_PAGE
static int SKIP_BODY
static int SKIP_PAGE

int   doEndTag()
int   doStartTag()
Tag   getParent()
void  release()
void  setPageContext(PageContext pc)    
void  setParent(Tag t)


<<SimpleTag>>

void doTag()
JspTag getParent()
void setJspBody(JspFragment jspBody)
void setJspContext(JspContext pc)
void setParent(JspTag parent)


SimpleTagSupport

實作SimpleTag的五個方法
static JspTag findAncestorWithClass(JspTag from, Class klass)
protected JspFragment getJspBody()
protected JspContext  getJspContext()


<<Iteration Tag>>

static int EVAL_BODY_AGAIN

int doAfterBody()


<<BodyTag>>

static int EVAL_BODY_BUFFERED
static int EVAL_BODY_TAG

void doInitBody()
void setBodyContent(BodyContent b)


TagSupport

protected String id
protected PageContext pageContext

TagSupport()

實作Tag的六個方法
實作Iteration Tag的一個方法(doAfterBody)
static Tag findAncestorWithClass(Tag from, Class klass)
String    getId()
Object    getValue(String k)
Enumeration getValues()
void    removeValue(String k)
void    setId(String id)
void    setValue(String k, Object o)


BodyTagSupport

protected  BodyContent bodyContent

BodyTagSupport()

實作Iteration Tag的一個方法(doAfterBody)
int    doEndTag()
void    doInitBody()
int    doStartTag()
BodyContent getBodyContent()
JspWriter   getPreviousOut()
void    release()
void    setBodyContent(BodyContent b)

2015年9月27日 星期日

空標籤 (客製化JSP Tag 一)

※無屬性Tag

EmptyTag.java
public class EmptyTag extends TagSupport {
    @Override
    public int doStartTag() throws JspException {
        try {
            pageContext.getOut().println("<h1>tag success</h1>");
        } catch (IOException e) {
            e.printStackTrace();
        }
        // return TagSupport.SKIP_BODY
        return super.doStartTag();
    }
}

要繼承TagSupport並覆寫doStartTag()

 helloTag.tld
<?xml version="1.0" encoding="UTF-8"?>
<taglib version="2.1" xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd">
    <tlib-version>1.0</tlib-version>
    <short-name>ht</short-name>
    
    <tag>
        <name>helloTag</name>
        <tag-class>empty.EmptyTag</tag-class>
        <body-content>empty</body-content>
    </tag>
</taglib>

tld可以安裝jboss tools,它可以幫助產生taglib裡面的屬性,short-name必填

 empty.jsp
<%@taglib uri="WEB-INF/helloTag.tld" prefix="bruce" %>
<body>
    <bruce:helloTag />
</body>

※prefix寫什麼就用什麼開頭,「:」後面是tld有個tag,裡面的name
這樣就可以產生一個自訂的空標籤了

※因為是空標籤,所以<bruce:helloTag></bruce:helloTag>裡面不能放東西

※還可以配合web.xml使用,這樣uri就不用指定路徑了,只要指定web.xml的taglib-uri即可
web.xml
<jsp-config>
    <taglib>
        <taglib-uri>http://javabruce.blogspot.tw/empty</taglib-uri>
        <taglib-location>/WEB-INF/helloTag.tld</taglib-location>
    </taglib>
</jsp-config>

由web.xml連接tld,uri可以隨便打
而jsp的taglib,uri就要改成隨便打的字,如下:
<%@taglib uri="http://javabruce.blogspot.tw/empty" prefix="bruce" %>

※有屬性Tag

做一個數字格式化的範例
AttrTag.java
public class AttrTag extends TagSupport {
    private String format;
    private Double dou;
    public void setFormat(String format) {
        this.format = format;
    }
    public void setDou(Double dou) {
        this.dou = dou;
    }

    @Override
    public int doStartTag() throws JspException {
        NumberFormat formatter = new DecimalFormat(this.format);
        try {
            pageContext.getOut().write(formatter.format(dou));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return super.doStartTag();
    }
}

只要給setter就可以了

 attr.tld
<tag>
    <name>displayNumber</name>
    <tag-class>attr.AttrTag</tag-class>
    <body-content>empty</body-content>
    <attribute>
        <name>format</name>
        <required>true</required>
        <rtexprvalue>true</rtexprvalue>
    </attribute>
    <attribute>
        <name>dou</name>
        <required>true</required>
        <rtexprvalue>true</rtexprvalue>
    </attribute>
</tag>

attr.jsp
<%@taglib uri="http://javabruce.blogspot.tw/attr" prefix="bruce" %>
<bruce:displayNumber dou="12345.6789" format="#,###,###.###"/>

attribute的name會找到對應的setter

 web.xml
<jsp-config>
    <taglib>
        <taglib-uri>http://javabruce.blogspot.tw/empty</taglib-uri>
        <taglib-location>/WEB-INF/helloTag.tld</taglib-location>
    </taglib>
    <taglib>
        <taglib-uri>http://javabruce.blogspot.tw/attr</taglib-uri>
        <taglib-location>/WEB-INF/attr.tld</taglib-location>
    </taglib>
</jsp-config>

這裡的web.xml連剛剛的無屬性Tag一起show出來
我用兩個jsp-config會變成404,所以看起來只能有一個jsp-config,然後對應多個taglib
可以參考這個網站

tld的required是true,表示用此標籤時,一定要加這個屬性
rtexprvalue是true時,支援表達式語言,如下:
<%
    pageContext.setAttribute("d", 54321.87654d);
    String f = "#,###,###.###";
%>
<bruce:displayNumber dou="${d}" format="<%=f%>"/>

${}就是表達式語言,<%=%>就不是,所以為false時,就會出500,According to TLD or attribute directive in tag file, attribute xxx does not accept any expressions


轉換器 (Struts2.3.x 九)

轉換器就是類型轉類型的工具,Struts2.x 四有介紹自動轉換,但有些要自己寫,現在就寫個String轉Locale的工具,順便結合國際化,在頁面有兩個radio,選中文就出中文字,選英文就出英文字
convert.jsp
<h1>${displayLang}</h1>
<form action="convert!getLangAndDisplay.action" method="post">
    Language: 
    <input type="radio" value="zh_TW" name="loc"
        checked="checked" />Chinese Traditional 
    <input type="radio" value="en_US" name="loc" />English 
    <input type="submit" value="display">
</form>

struts.xml
<constant name="struts.enable.DynamicMethodInvocation" value="true" />
<constant name="struts.custom.i18n.resources" value="TestLanguage" />
<constant name="struts.devMode" value="true" />

<package name="basicstruts2" extends="struts-default"
    namespace="/struts2">
    <action name="convert" class="convert.ConvertAction">
        <result name="success">/struts2/convert.jsp</result>
        <result name="input">/struts2/fail.jsp</result>
    </action>
</package>

ConvertAction.java
private Locale loc;
//setter即可

public String getLangAndDisplay() {
    HttpServletRequest request = ServletActionContext.getRequest();
    String lang = LocalizedTextUtil.findDefaultText("test.lang", this.loc);
    request.setAttribute("displayLang", lang);
    System.out.println("loc=" + loc);
    return ActionSupport.SUCCESS;
}

TestConvert.java,StrutsTypeConverter是Struts2內鍵的class
public class TestConvert extends StrutsTypeConverter {
    @Override
    public Object convertFromString(Map arg0, String[] arg1, Class arg2) {
        Locale loc = null;
        if (arg2.equals(Locale.class)) {
            String lang[] = arg1[0].split("_");
            loc = new Locale(lang[0], lang[1]);
        }
        return loc;
    }

    @Override
    public String convertToString(Map arg0, Object arg1) {
        return null;
    }
}

xwork-conversion.properties,一定要這樣取
java.util.Locale=convert.TestConvert

TestLanguage_zh_TW.properties
test.lang=\u6B61\u8FCE\uFF01

TestLanguage_en_US.properties
test.lang=welcome!

可以參考這裡
xwork-conversion.properties可以改成不是全域的,在Action同層建一個ConvertAction-conversion.properties,因為我的Action叫ConvertAction
內容改為

loc=convert.TestConvert

loc是ConvertAction的變數名稱



這篇文章有說properties的執行順序,我試不出來,只有用最後一招的global有用,這裡用的就是global

2015年9月19日 星期六

驗證框架 (Struts2.3.x 八)

Struts2的驗證框架在xwork-core-2.x.x.jar的com.opensymphony.xwork2.validator.validators,有一個default.xml,官方文件在,Examples有很多可以參考,這裡用最基本的Basic Validation
User.java
private String userName;
private String userPassword;
private Integer userNo;
private Date userRegisterDate;
//setter/getter...

LoginAction.java,Step2完全沒有覆寫
private User user = new User();
public User getUser() {
    return user;
}

struts.xml,Step4說不用method
<action name="login" class="login.LoginAction">
    <result name="success">/struts2/login.jsp</result>
    <result name="input">/struts2/login.jsp</result>
</action>

login.jsp
${fieldErrors}
<s:property value="displayMessage" />
<form action="login.action">
    帳號:<input type="text" name="user.userName" />${FieldErrors["user.userName"][0]}<br />
    密碼:<input type="password" name="user.userPassword" />${FieldErrors["user.userPassword"][0]}<br />
    學號:<input type="text" name="user.userNo" />${FieldErrors["user.userNo"][0]}<br />
    註冊日期:<input type="text" name="user.userRegisterDate" />${FieldErrors["user.userRegisterDate"][0]}<br />
    <input type="reset" />
    <input type="submit" />
</form>

LoginAction-validation.xml,Step3要注意命名方式,放在Action同層
<field name="user.userName">
    <field-validator type="requiredstring">
        <message>You must enter a userName</message>
    </field-validator>
    <field-validator type="stringlength">
        <param name="minLength">6</param>
        <param name="maxLength">10</param>
        <message>帳號長度必需在${minLength}到${maxLength}之間</message>
    </field-validator>
    
</field>
<field name="user.userPassword">
    <field-validator type="requiredstring">
        <message>密碼必須輸入</message>
    </field-validator>
</field>
<field name="user.userNo">
    <field-validator type="requiredstring" short-circuit="true">
        <message>學號必須輸入</message>
    </field-validator>
    <field-validator type="int">
        <param name="min">1</param>
        <param name="max">1000</param>
        <message>學號必需介於${min}到${max}之間</message>
    </field-validator>
</field>
<field name="user.userRegisterDate">
    <field-validator type="requiredstring">
        <message>註冊日期必須輸入</message>
    </field-validator>
    <field-validator type="date">
        <param name="min">01/01/1990</param>
        <param name="max">01/01/2000</param>
        <message>註冊日期必需介於${min}到 ${max}之間</message>
    </field-validator>
</field>

short-circuit="true",表示如有錯,就不往下驗證了
我試的結果,只有必輸有起作用,int和date會出現「Invalid field value for field "XXX".」
但聽說正式開發很少人用這個框架

找到了這個後,我新增一個LoginAction.properties,放在Action同一層
invalid.fieldvalue.user.userNo=\u5B78\u865F\u5FC5\u9700\u4ECB\u65BC 1 \u5230 1000 \u4E4B\u9593
invalid.fieldvalue.user.userRegisterDate=\u8A3B\u518A\u65E5\u671F\u5FC5\u9700\u4ECB\u65BC 1990-01-01 \u5230 2000-01-01 \u4E4B\u9593

然後LoginAction-validation.xml不改也是可以,只是永遠不會顯示就對了,但整行刪除會報錯,所以還是要有的空殼
最後,int和date雖然可以正常顯示要驗證的訊息,但驗證還是有問題,譬如日期明明已經輸入裡面的範圍了,但它老大還是認為不對
數字也是介於裡面的值了,就給我報學號必需輸入,所以我每個都加了short-circuit="true",但還是沒用
還有properties增加{0},也是一點用都沒有
{0}會全部印出來
{min}會消失,看起來這才是對的,但就是不知道塞值進去的方法

動態方法(DMI)調用與通配符映射 (Struts2.3.x 七)

新寫一支Action,就叫CRUDAction.java好了
public class CRUDAction extends ActionSupport {
    public String insert() {
        System.out.println("增");
        return null;
    }

    public void delete() {
        System.out.println("刪");
    }

    public void update() {
        System.out.println("改");
    }

    public List<String> query() {
        System.out.println("查");
        return null;
    }
}
注意不用覆寫execute()了,而要動態調用裡面的方法,方法不能有參數
struts.xml
<action name="crudI" class="action.CRUDAction" method="insert"/>
<action name="crudD" class="action.CRUDAction" method="delete"/>
<action name="crudU" class="action.CRUDAction" method="update"/>
<action name="crudQ" class="action.CRUDAction" method="query"/>

此時在網址列上的最後面打
.crudI.action可以調用到insert方法
.crudD.action可以調用到delete方法
.crudU.action可以調用到update方法
.crudQ.action可以調用到query方法

但這樣子寫太累了,所以有一種動態的調用方法出來了
想使用動態方法,必須增加這一行,預設是false,所以要改成true,和package同層
<constant name="struts.enable.DynamicMethodInvocation" value="true" />

然後將action修改如下
<action name="crud*" class="action.CRUDAction" method="{1}"/>

此時在網址列上的最後面打
.crud!insert.action可以調用到insert方法
.crud!delete.action可以調用到delete方法
.crud!update.action可以調用到update方法
.crud!query.action可以調用到query方法

{0}代表URL本身,還有{1}~{9}可以用,一個星號對應一個{n}
所以假設crud!insert.action,*變成了insert,所以{1}也變成insert,所以就去調用insert方法了
更詳細可到這裡參考

2015年9月18日 星期五

s標籤取值注意事項 (Struts2.3.x 六)

使用s標籤,必須增加<%@ taglib prefix="s" uri="/struts-tags" %>

※直接取得

在LoginAction.java增加以下程式碼測試
private String message;
public String getMessage() {
    return message;
}

@Override
public String execute() throws Exception {
    HttpServletRequest request = ServletActionContext.getRequest() ;
    
    this.message = "顯示";
    request.setAttribute("test", "test");
    
    return SUCCESS;
}

login.jsp
<s:property value="message"/>
${message}
<s:property value="test"/>
${test}
<s:property value="#request.test"/>

※s標籤必須在Action裡定義field才能取得到; 而EL都可以,不過聽說較早版本取不到field
※如果s標籤要取內置對象屬性,可以用#,因為它是用OGNL,而OGNL的語法就是#開頭
※如message是field,而test不是,所以第三行印不出來,必須改成第五行才可以

※在collections裡取得

LoginAction.java
public String execute() throws Exception {
    HttpServletRequest request = ServletActionContext.getRequest() ;
    
    List<User> list = new ArrayList<User>() ;
    for (int i = 0 ; i < 3 ; i ++) {
        User user = new User() ;
        user.setUserName("name" + i);
        user.setUserPassword("password" + i);
        list.add(user) ;
    }
    request.setAttribute("users", list);
    return SUCCESS;
}

login.jsp
<ul>
<s:if test="#request.users != null">
    <s:iterator value="#request.users">
        <li><s:property value="userName"/>
        <li><s:property value="userPassword"/>
        <li>${userName}
        <li>${userPassword}
    </s:iterator>
</s:if>
</ul>

※在迴圈裡,s標籤就不用#開頭了,而且用EL也可以

修改驗證和內置對象 (Struts2.3.x 五)

※修改驗證

ActionSupport其實早就有validate()了,可以利用它來做驗證,會比較漂亮 struts.properties
login.success=login success
login.fail=login fail

LoginAction
private User user = new User();
public User getUser() {
    return user;
}

@Override
public String execute() throws Exception {
    return SUCCESS;
}

@Override
public void validate() {
    if (user.getUserName().trim().isEmpty()) {
        super.addFieldError("s", super.getText("login.success"));
    }

    if (user.getUserPassword().trim().isEmpty()) {
        super.addFieldError("f", super.getText("login.fail"));
    }
}


struts.xml
<action name="login" class="login.LoginAction" method="execute">
    <result name="success">/struts2/login.jsp</result>
    <result name="input">/struts2/login.jsp</result>
</action>

login.jsp
${fieldErrors}
<s:property value="displayMessage" />
<form action="login.action">
    <input type="text" name="user.userName" />${FieldErrors["s"][0]}<br />
    <input type="password" name="user.userPassword" />${FieldErrors["f"][0]}<br />
    <input type="reset" />
    <input type="submit" />
</form>

User.java
private String userName;
private String userPassword;
//setter/getter...
在jsp的${fieldErrors},主要是印出來,才知道是陣列,也才會知道要用${FieldErrors["s"][0]}

※內置對象

Servlet有四個內置對象:request、response、session、application,
可在execute()加程式碼,驗證成功就會印出
HttpServletRequest request = ServletActionContext.getRequest() ;
HttpServletResponse response = ServletActionContext.getResponse() ;
HttpSession session = request.getSession() ;
ServletContext context = ServletActionContext.getServletContext() ;

System.out.println("虛擬路徑=" + request.getContextPath());
System.out.println("編碼=" + response.getCharacterEncoding());
System.out.println("Session ID=" + session.getId());
System.out.println("真實路徑=" + context.getRealPath("/"));

2015年9月12日 星期六

在java做排序

※範例一

SortedSet<Date> set = new TreeSet<Date>();
set.add(new Date(102, 1, 1));
set.add(new Date());
set.add(new Date(103, 1, 1));
set.add(new Date(100, 1, 1));
set.add(new Date(101, 1, 1));
System.out.println("第一筆=" + set.first());
System.out.println("最後一筆=" + set.last());
System.out.println("排序結果=" + set);

※範例二

List<Date> list = new ArrayList<Date>();
list.add(new Date(102, 1, 1));
list.add(new Date());
list.add(new Date(103, 1, 1));
list.add(new Date(100, 1, 1));
list.add(new Date(101, 1, 1));
System.out.println("排序前=" + list);

Collections.sort(list, new Comparator<Date>() {
    @Override
    public int compare(Date o1, Date o2) {
        if (o1.before(o2)) {
            return 1;
        } else if (o1.after(o2)) {
            return -1;
        } else {
            return 0;
        }
    }
});
System.out.println("排序後=" + list);
※如果假資料是陣列,可用Arrays.sort
※倒序正序可以將-1和1互換

※範例三

class Student {
    private String name;
    private Date learningDate;
    private Integer learningNo;
    //setter/getter
}

List<Student> list = new ArrayList<Student>();
Student stu1 = new Student();
stu1.setName("circle");
stu1.setLearningDate(new Date(101, 1, 1));
stu1.setLearningNo(10);

Student stu2 = new Student();
stu2.setName("bruce");
stu2.setLearningDate(new Date(100, 2, 2));
stu2.setLearningNo(20);

Student stu3 = new Student();
stu3.setName("apple");
stu3.setLearningDate(new Date(102, 3, 3));
stu3.setLearningNo(30);

list.add(stu1);
list.add(stu2);
list.add(stu3);

System.out.println("list1=");
for (Student stu : list) {
    System.out.println(stu.getName());
    System.out.println(stu.getLearningDate());
    System.out.println(stu.getLearningNo());
}

Collections.sort(list, new Comparator<Student>() {
    @Override
    public int compare(Student o1, Student o2) {
        if (o1.getLearningDate().before(o2.getLearningDate())) {
            return -1;
        } else if (o1.getLearningDate().after(o2.getLearningDate())) {
            return 1;
        } else {
            return 0;
        }
    }
});

System.out.println("\nlist2=");
for (Student stu : list) {
    System.out.println(stu.getName());
    System.out.println(stu.getLearningDate());
    System.out.println(stu.getLearningNo());
}
類似從資料庫撈出來的效果

※範例四

class Student {
    private String name;
    private Date learningDate;
    private Integer learningNo;
    //setter/getter
}

class StudentComparator implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        if (o1.getLearningDate().before(o2.getLearningDate())) {
            return 1;
        } else if (o1.getLearningDate().after(o2.getLearningDate())) {
            return -1;
        } else {
            return 0;
        }
    }
}

List<Student> list = new ArrayList<Student>();
Student stu1 = new Student();
stu1.setName("circle");
stu1.setLearningDate(new Date(101, 1, 1));
stu1.setLearningNo(10);

Student stu2 = new Student();
stu2.setName("bruce");
stu2.setLearningDate(new Date(100, 2, 2));
stu2.setLearningNo(20);

Student stu3 = new Student();
stu3.setName("apple");
stu3.setLearningDate(new Date(102, 3, 3));
stu3.setLearningNo(30);

list.add(stu1);
list.add(stu2);
list.add(stu3);

System.out.println("list1=");
for (Student stu : list) {
    System.out.println(stu.getName());
    System.out.println(stu.getLearningDate());
    System.out.println(stu.getLearningNo());
}

Collections.sort(list, new StudentComparator());

System.out.println("\nlist2=");
for (Student stu : list) {
    System.out.println(stu.getName());
    System.out.println(stu.getLearningDate());
    System.out.println(stu.getLearningNo());
}
和範例三很像,只是新增一個class,然後將覆寫的方法寫在裡面