本来在第四篇要说完的,但是写着写着,我觉得内容有点多起来了,所以就另开这篇,在这里专门讲述Token的定义,JSP自定义标签以及如何用Parameterized的来做单元测试。
1,新建包com.vanceinfo.javaserial.handlerinterceptors,新加类TokenHandler,这个类就是Token的Helper类了,包含三个方法:
generateGUID当进入页面加载时,产生一个GUID,分别存入Session和Constant里,说明一下,Constant是用于页面的hidden值保存用的。。。。,顺便打个预防针的是sesseion里面存的是map对象,使用的key叫SPRINGMVC.TOKEN,map里面的一条对象以"springMVC_token.GUID:GUID"形式保存。而客户端的hidden框的name使用的是小写的springMVC_token
getInputToken获取客户端hidden里面的guid值。
validToken这个方法用于验证客户端Hidden里的guid里值,与服务端Session里面对应的值是否一致,完全相同而返回true,否则返回false, 并且先会remove掉session里面的对应的这条token值。
源码如下:
package com.vanceinfo.javaserial.handlerinterceptors;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.apache.log4j.Logger;
import org.springframework.ui.ModelMap;
import com.vanceinfo.javaserial.constants.Constant;
public class TokenHandler {
private static Logger LOGGER = Logger.getLogger(TokenHandler.class);
static Map<String, String> springmvc_token = new HashMap<String, String>();
/**
* generate the unique token, and store into both server, client side.
*
* @param session
* @return
*/
@SuppressWarnings("unchecked")
public synchronized static String generateGUID(HttpSession session, ModelMap map) {
String token = "";
try {
Object obj = session.getAttribute("SPRINGMVC.TOKEN");
if (obj != null) {
springmvc_token = (Map<String, String>) session.getAttribute("SPRINGMVC.TOKEN");
}
token = new BigInteger(165, new Random()).toString(36).toUpperCase();
springmvc_token.put(Constant.DEFAULT_TOKEN_NAME + "." + token, token);
session.setAttribute("SPRINGMVC.TOKEN", springmvc_token);
Constant.TOKEN_VALUE = token;
} catch (IllegalStateException e) {
LOGGER.error("generateGUID() mothod find bug,by token session...");
}
return token;
}
/**
* validate the form token value and session token value.
*
* @param request
* @return true if both token value are the same,otherwise false
*/
@SuppressWarnings("unchecked")
public static boolean validToken(HttpServletRequest request) {
String inputToken = getInputToken(request);
if (inputToken == null) {
LOGGER.warn("token is not valid!inputToken is NULL");
return false;
}
HttpSession session = request.getSession();
Map<String, String> tokenMap = (Map<String, String>) session.getAttribute("SPRINGMVC.TOKEN");
if (tokenMap == null || tokenMap.size() < 1) {
LOGGER.warn("token is not valid!sessionToken is NULL");
return false;
}
String sessionToken = tokenMap.get(Constant.DEFAULT_TOKEN_NAME + "." + inputToken);
if (!inputToken.equals(sessionToken)) {
LOGGER.warn("token is not valid!inputToken='" + inputToken + "',sessionToken = '" + sessionToken + "'");
return false;
}
tokenMap.remove(Constant.DEFAULT_TOKEN_NAME + "." + inputToken);
session.setAttribute("SPRINGMVC.TOKEN", tokenMap);
return true;
}
/**
* Get the token value from the form. assume it store in the hidden field
*
* @param request
* @return
*/
@SuppressWarnings("unchecked")
public static String getInputToken(HttpServletRequest request) {
Map<String, String[]> params = request.getParameterMap();
if (!params.containsKey(Constant.DEFAULT_TOKEN_NAME)) {
LOGGER.warn("Could not find token name in params.");
return null;
}
String[] tokens = (String[]) (String[]) params.get(Constant.DEFAULT_TOKEN_NAME);
if ((tokens == null) || (tokens.length < 1)) {
LOGGER.warn("Got a null or empty token name.");
return null;
}
return tokens[0];
}
}
TokenHandler.java
通过上面的描述,聪明你可以很自然的猜到Constant.java里面会有两个常量值
public static String DEFAULT_TOKEN_NAME = "springMVC_token";
public static String TOKEN_VALUE;
Interceptor里面其实编写代码起来要容易的多,实现HandlerInterceptor接口,其实主要是在preHandler和postHandler就好了。
/* (non-Javadoc)
* @see org.springframework.web.servlet.HandlerInterceptor#postHandle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, java.lang.Object, org.springframework.web.servlet.ModelAndView)
*/
public void postHandle(HttpServletRequest request, HttpServletResponse response,
Object handler, ModelAndView modelAndView) throws Exception {
if ("get".equalsIgnoreCase(request.getMethod())) {
TokenHandler.generateGUID(request.getSession(), modelAndView.getModelMap());
}
}
/* (non-Javadoc)
* @see org.springframework.web.servlet.HandlerInterceptor#preHandle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, java.lang.Object)
*/
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
if ("get".equalsIgnoreCase(request.getMethod())) {
return true;
} else {
if (!TokenHandler.validToken(request)) {
response.sendRedirect("index.do");
return false;
}
return true;
}
}
preHandler_PostHandler
以上代码是接合我的一些情况,如果二次请求,我是跳转至index.do这个方法的。
2, 将刚写的这个interceptor注册至spring-mvc.xml里面。
<mvc:interceptors>
<!-- use Token to void multi post when user press F5 or Ctrol+F5 -->
<mvc:interceptor>
<mvc:mapping path="/**/*.do" />
<bean
class="com.vanceinfo.javaserial.handlerinterceptors.TokenHandlerInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
mvc:interceptors
3, 接下来就是要写tag了。4步实现自定义tag:
a,先定义一个BaseTag,让其继承于TagSupport,这是最简单的实现方式
package com.vanceinfo.javaserial.tags;
import javax.servlet.jsp.tagext.TagSupport;
public class BaseTag extends TagSupport {
private static final long serialVersionUID = -4886769810825854364L;
protected String name;
protected String type;
protected String id;
protected String onclick;
protected String onfocus;
protected String onblur;
protected String onchange;
protected String cssStyle;
protected String cssClass;
protected String size;
public void generateAttribute(StringBuilder sb) {
if (id != null) {
sb.append(" id='").append(id).append("'");
}
if (onclick != null) {
sb.append(" οnclick='").append(onclick).append("'");
}
if (onfocus != null) {
sb.append(" οnfοcus='").append(onfocus).append("'");
}
if (onblur != null) {
sb.append(" οnblur='").append(onblur).append("'");
}
if (onchange != null) {
sb.append(" οnchange='").append(onchange).append("'");
}
if (cssStyle != null) {
sb.append(" style='").append(cssStyle).append("'");
}
if (cssClass != null) {
sb.append(" class='").append(cssClass).append("'");
}
if (size != null) {
sb.append(" size='").append(size).append("'");
}
}
/**
* @return the name
*/
public String getName() {
return name;
}
/**
* @param name the name to set
*/
public void setName(String name) {
this.name = name;
}
/**
* @return the type
*/
public String getType() {
return type;
}
/**
* @param type the type to set
*/
public void setType(String type) {
this.type = type;
}
/**
* @return the id
*/
public String getId() {
return id;
}
/**
* @param id the id to set
*/
public void setId(String id) {
this.id = id;
}
/**
* @return the onclick
*/
public String getOnclick() {
return onclick;
}
/**
* @param onclick the onclick to set
*/
public void setOnclick(String onclick) {
this.onclick = onclick;
}
/**
* @return the onfocus
*/
public String getOnfocus() {
return onfocus;
}
/**
* @param onfocus the onfocus to set
*/
public void setOnfocus(String onfocus) {
this.onfocus = onfocus;
}
/**
* @return the onblur
*/
public String getOnblur() {
return onblur;
}
/**
* @param onblur the onblur to set
*/
public void setOnblur(String onblur) {
this.onblur = onblur;
}
/**
* @return the onchange
*/
public String getOnchange() {
return onchange;
}
/**
* @param onchange the onchange to set
*/
public void setOnchange(String onchange) {
this.onchange = onchange;
}
/**
* @return the cssStyle
*/
public String getCssStyle() {
return cssStyle;
}
/**
* @param cssStyle the cssStyle to set
*/
public void setCssStyle(String cssStyle) {
this.cssStyle = cssStyle;
}
/**
* @return the cssClass
*/
public String getCssClass() {
return cssClass;
}
/**
* @param cssClass the cssClass to set
*/
public void setCssClass(String cssClass) {
this.cssClass = cssClass;
}
/**
* @return the size
*/
public String getSize() {
return size;
}
/**
* @param size the size to set
*/
public void setSize(String size) {
this.size = size;
}
}
BaseTag
自定义的TokenTag 继承于BaseTag
package com.vanceinfo.javaserial.tags;
import java.io.IOException;
import javax.servlet.jsp.JspException;
import com.vanceinfo.javaserial.constants.Constant;
public class TokenTag extends BaseTag {
private static final long serialVersionUID = 1495609370076247263L;
/* (non-Javadoc)
* @see javax.servlet.jsp.tagext.TagSupport#doStartTag()
*/
@Override
public int doStartTag() throws JspException {
StringBuilder sb = new StringBuilder();
sb.append("<input type='text' name='").append(
Constant.DEFAULT_TOKEN_NAME).append("'").append(" value='").append(Constant.TOKEN_VALUE).append("'");
sb.append(" /> ");
try {
pageContext.getOut().print(sb.toString());
} catch (IOException e) {
e.printStackTrace();
}
return EVAL_BODY_INCLUDE;
}
}
TokenTag
b,接着就去WEB-INF下面创建标签库描述文件,注意这个文件只能放在这个文件夹下,当然为了区分,你可以在其下自建一个文件夹,至于放其它地方会出错的原因还不明。
<?xml version="1.0" encoding="UTF-8"?>
<taglib version="2.0" xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee web-jsptaglibrary_2_0.xsd">
<tlib-version>1.0</tlib-version>
<jsp-version>2.0</jsp-version>
<short-name>token</short-name>
<uri>/mytaglib</uri>
<tag>
<name>token</name>
<tag-class>com.vanceinfo.javaserial.tags.TokenTag</tag-class>
<body-content>empty</body-content>
<attribute>
<name>user</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
</taglib>
/tlds/Token.tld
至于c、d两步,我在第四篇中已经讲述,故不在重复,不清楚的可以返回上篇查阅一下。
以上就是关于自定义类Struts2的Token的完整过程,Struts2是经历了很多人的检验的,我们的自造的,为了表示出我们的信心和诚意,自然对我们的Interceptor进行单元测试喽。当然,从浏览器现象来看个现象也是好的,所谓有图有真相有说服力
4,关于单元测试,我们利用Parameterized参数化集中测试,没弄这种单元测试之前,我也很悚这种模式,不过一来二去之后,对其还是持拥抱态度的。其实,单元测试针对的是单元,看你对单元的单位是如何定义的,一般来讲一个方法就是一个单元来测最合适。因为我是这样子认同的,所以,对于每一个方法,我们需要创建一个JUnit4的Test Case.并且勾选setUpBeforeClass,tearDownAfterClass,setUp,tearDown,这4个方法一般用于记一些描述性步骤等内容,下面是TokenHandlerInterceptorTest_postHandleTest里的一个示例。
/**
* @throws java.lang.Exception
*/
@BeforeClass
public static void setUpBeforeClass() throws Exception {
LOGGER.debug("Starting test class : " + TokenHandlerInterceptorTest_postHandleTest.class.getName());
}
/**
* @throws java.lang.Exception
*/
@AfterClass
public static void tearDownAfterClass() throws Exception {
LOGGER.debug("Ending test class : " + TokenHandlerInterceptorTest_postHandleTest.class.getName());
}
/**
* @throws java.lang.Exception
*/
@Before
public void setUp() throws Exception {
LOGGER.debug("Starting test: " + testName);
}
/**
* @throws java.lang.Exception
*/
@After
public void tearDown() throws Exception {
LOGGER.debug("Ending test: " + testName);
}
JUnit Test Case 四大金刚前后执行方法
还有一个约定成俗的定义一个变量,用于对每次执行一时输出你的test case名字出来。
private String testName;
除了这个约定以外,如果你的测试方法的入参也请列在private列表里面,作为变量,也就是参数。最后,测试方法的返回值也请以参数的形式定义。这一次,我举TokenHandlerInterceptor_preHandleTest作为例子,因为他是有返回值的。
private String testName;
private HttpServletRequest httpRequest;
private HttpServletResponse httpResponse;
private boolean returnBoolean;
入参和出参列表
然后,你还要定义一个带所有参数的构造函数出来。例如:
public TokenHandlerInterceptor_preHandleTest(String testName, HttpServletRequest httpRequest,
HttpServletResponse httpResponse, Boolean returnBoolean) {
this.testName = testName;
this.httpRequest = httpRequest;
this.httpResponse = httpResponse;
this.returnBoolean = returnBoolean;
}
带全部入出参数的构造函数
最后,准备测试数据,写在testData()函数里面,一定要以这个命名,返回Collection<Object[]>,并且打上@org.junit.runners.Parameterized.Parameters标签,
准备完这些之后中,在class前面也一定不要忘了打上@RunWith(Parameterized.class)标签。
一个完整的TokenHandlerInterceptor_preHandleTest如下:
package com.vanceinfo.javaserial.handlerinterceptors;
import static org.junit.Assert.*;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.log4j.Logger;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockHttpSession;
@RunWith(Parameterized.class)
public class TokenHandlerInterceptor_preHandleTest {
private static final Logger LOGGER = Logger.getLogger(TokenHandlerInterceptor_preHandleTest.class);
private String testName;
private HttpServletRequest httpRequest;
private HttpServletResponse httpResponse;
private boolean returnBoolean;
public TokenHandlerInterceptor_preHandleTest(String testName, HttpServletRequest httpRequest,
HttpServletResponse httpResponse, Boolean returnBoolean) {
this.testName = testName;
this.httpRequest = httpRequest;
this.httpResponse = httpResponse;
this.returnBoolean = returnBoolean;
}
@Parameters
public static Collection<Object[]> testData() throws Exception {
// *******************
// test case 1
// *******************
String testNameTC1 = "Get Method";
MockHttpServletRequest httpRequest1 = new MockHttpServletRequest("get", "http://testRequestUrl");
httpRequest1.addParameter("htid", "1001");
MockHttpServletResponse httpResponse1 = new MockHttpServletResponse();
// *******************
// test case 2
// *******************
String testNameTC2 = "Post Method unvalid: no param(client) token";
MockHttpServletRequest httpRequest2 = new MockHttpServletRequest("post", "http://testRequestUrl");
httpRequest2.addParameter("htid", "1001");
MockHttpServletResponse httpResponse2 = new MockHttpServletResponse();
// *******************
// test case 3
// *******************
String testNameTC3 = "Post Method unvalid: has param(client) token but is null";
MockHttpServletRequest httpRequest3 = new MockHttpServletRequest("post", "http://testRequestUrl");
httpRequest3.addParameter("htid", "1001");
httpRequest3.addParameter("springMVC_token", new String[] {});
MockHttpServletResponse httpResponse3 = new MockHttpServletResponse();
// *******************
// test case 4
// *******************
String testNameTC4 = "Post Method unvalid: has param(client) token but no session token";
MockHttpServletRequest httpRequest4 = new MockHttpServletRequest("post", "http://testRequestUrl");
httpRequest4.addParameter("htid", "1001");
httpRequest4.addParameter("springMVC_token", new String[] { "abcdef" });
MockHttpServletResponse httpResponse4 = new MockHttpServletResponse();
// *******************
// test case 5
// *******************
String testNameTC5 = "Post Method unvalid: has param(client) token and session token,but not the same value";
MockHttpServletRequest httpRequest5 = new MockHttpServletRequest("post", "http://testRequestUrl");
httpRequest5.addParameter("htid", "1001");
httpRequest5.addParameter("springMVC_token", new String[] { "abcdef" });
MockHttpSession session5 = new MockHttpSession();
Map<String, String> tokenMap5 = new HashMap<String, String>();
tokenMap5.put("springMVC_token.abcdef", "abcdefg");
session5.putValue("SPRINGMVC.TOKEN", tokenMap5);
httpRequest5.setSession(session5);
MockHttpServletResponse httpResponse5 = new MockHttpServletResponse();
// *******************
// test case 6
// *******************
String testNameTC6 = "Post Method valid";
MockHttpServletRequest httpRequest6 = new MockHttpServletRequest("post", "http://testRequestUrl");
httpRequest6.addParameter("htid", "1001");
httpRequest6.addParameter("springMVC_token", new String[] { "abcdef" });
MockHttpSession session6 = new MockHttpSession();
Map<String, String> tokenMap6 = new HashMap<String, String>();
tokenMap6.put("springMVC_token.abcdef", "abcdef");
session6.putValue("SPRINGMVC.TOKEN", tokenMap6);
httpRequest6.setSession(session6);
MockHttpServletResponse httpResponse6 = new MockHttpServletResponse();
return Arrays
.asList(new Object[][] { { testNameTC1, httpRequest1, httpResponse1, true },
{ testNameTC2, httpRequest2, httpResponse2, false },
{ testNameTC3, httpRequest3, httpResponse3, false },
{ testNameTC4, httpRequest4, httpResponse4, false },
{ testNameTC5, httpRequest5, httpResponse5, false },
{ testNameTC6, httpRequest6, httpResponse6, true } });
}
/**
* @throws java.lang.Exception
*/
@BeforeClass
public static void setUpBeforeClass() throws Exception {
LOGGER.debug("Starting test class : " + TokenHandlerInterceptor_preHandleTest.class.getName());
}
/**
* @throws java.lang.Exception
*/
@AfterClass
public static void tearDownAfterClass() throws Exception {
LOGGER.debug("Ending test class : " + TokenHandlerInterceptor_preHandleTest.class.getName());
}
/**
* @throws java.lang.Exception
*/
@Before
public void setUp() throws Exception {
LOGGER.debug("Starting test: " + testName);
}
/**
* @throws java.lang.Exception
*/
@After
public void tearDown() throws Exception {
LOGGER.debug("Ending test: " + testName);
}
/**
* Test method for
* {@link com.expedia.lux.drr.handlerinterceptors.TokenHandlerInterceptor#preHandle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, java.lang.Object)}
* .
*
* @throws Exception
*/
@Test
public void testPreHandle() {
TokenHandlerInterceptor pre = new TokenHandlerInterceptor();
try {
boolean actual = pre.preHandle(httpRequest, httpResponse, new Object());
assertEquals(this.returnBoolean, actual);
} catch (Exception e) {
fail("Should not throw exception!");
}
}
}
TokenHandlerInterceptor_preHandleTest
注意我在上面设计了6个case. 大部分期望的是返回false,也就是负case的测试。而assert 的时候,只需写一次就可以了。
这就是参数化单元测试的好处了。耗时就耗在准备数据上面。
希望把自定义标签的制作说明白了。