#
背景:
加密的cookie信息中帶有特殊字符(“=”),導致讀cookie的時候,特殊符號丟失,解密失敗
看了同事“關于cookie特殊字符”的說明郵件,和網上對cookie特殊字符問題的解釋:
我們在實際使用Cookie過程中要注意一些問題:
1. Cookie的兼容性問題
Cookie的格式有2個不同的版本,第一個版本,我們稱為Cookie Version 0,是最初由Netscape公司制定的,也被幾乎所有的瀏覽器支持。而較新的版本,Cookie Version 1,則是根據RFC 2109文檔制定的。為了確保兼容性,JAVA規定,前面所提到的涉及Cookie的操作都是針對舊版本的Cookie進行的。而新版本的Cookie目前還不被Javax.servlet.http.Cookie包所支持。
2. Cookie的內容
同樣的Cookie的內容的字符限制針對不同的Cookie版本也有不同。在Cookie Version 0中,某些特殊的字符,例如:空格,方括號,圓括號,等于號(=),逗號,雙引號,斜杠,問號,@符號,冒號,分號都不能作為Cookie的內容。這也就是為什么我們在例子中設定Cookie的內容為“Test_Content”的原因。
雖然在Cookie Version 1規定中放寬了限制,可以使用這些字符,但是考慮到新版本的Cookie規范目前仍然沒有為所有的瀏覽器所支持,因而為保險起見,我們應該在Cookie的內容中盡量避免使用這些字符。
摘自:
http://swingchen.bokee.com/6200015.html
類似這樣的解釋,搜索出來的結果,挺多。
但是,我去看了RFC2109(
http://www.faqs.org/rfcs/rfc2109.html),其說明如下:

value中的token,是有一組非特殊字符,非空白字符。而它是在RFC 2068(
http://www.faqs.org/rfcs/rfc2068.html)中制定的 (是對Header的規范),請看:
也就是說,所謂的Cookie1,同樣有特殊字符的限制。
同樣,在Cookie2(RFC2965)中,也如此。
想想也是啊,如果沒有特殊字符的限制,解析Header的時候,還不亂套了?
看了RFC之后,我們再來看看Tomcat中的實現(6.0.29版本),請看:
org.apache.tomcat.util.http.Cookies
1.類注釋:
A collection of cookies - reusable and tuned for server side performance.
Based on RFC2965 ( and 2109 )
是基于RFC2965/RFC2109規范來實現的
2.特殊字符的定義
/*
List of Separator Characters (see isSeparator())
Excluding the '/' char violates the RFC, but
it looks like a lot of people put '/'
in unquoted values: '/': ; //47
'\t':9 ' ':32 '\"':34 '(':40 ')':41 ',':44 ':':58 ';':59 '<':60
'=':61 '>':62 '?':63 '@':64 '[':91 '\\':92 ']':93 '{':123 '}':125
*/
public static final char SEPARATORS[] = { '\t', ' ', '\"', '(', ')', ',',
':', ';', '<', '=', '>', '?', '@', '[', '\\', ']', '{', '}' };
根據規范,定義了特殊字符。除了“/”這個符號。因為大多數人會直接使用“/”。
3.針對“=”特殊處理
/**
* If true, cookie values are allowed to contain an equals character without
* being quoted.
*/
public static final boolean ALLOW_EQUALS_IN_VALUE;
static {
ALLOW_EQUALS_IN_VALUE = Boolean.valueOf(System.getProperty(
"org.apache.tomcat.util.http.ServerCookie.ALLOW_EQUALS_IN_VALUE",
"false")).booleanValue();
}
可以在catalina.properties中,添加這個配置項 (或者啟動過程中加上-D參數),使得cookie value中允許存在“=”符號。
所以本文開頭提到的問題,可以使用這個方法得到解決
4.解析過程
/**
* Parses a cookie header after the initial "Cookie:"
* [WS][$]token[WS]=[WS](token|QV)[;|,]
* RFC 2965
* JVK
*/
public final void processCookieHeader(byte bytes[], int off, int len){
//詳細代碼,省略
}
備注:
RFC沒有仔細看(時間有限,并且看E文挺累的),如理解有誤,請告知。
摘要: 會開車了,也慢慢不規矩起來了,于是乎,違章信息也慢慢多起來了,但是無法第一時間通知到自己。
雖說,有個網站:http://www.hzti.com/service/qry/violation_veh.aspx?pgid=&type=1&node=249
可以查詢非現場違章情況,
不過:
1.我是懶人,主動去查詢的時候不太樂意做
2.車輛識別碼,永遠記不住
3.每次輸驗證...
閱讀全文
原文地址:
http://weblogs.java.net/blog/caroljmcdonald/archive/2009/09/17/some-java-concurrency-tips
大綱:
Prefer immutable objects/data
盡可能使用不變對象/數據
Threading risks for Web applications
注意web應用的線程安全問題
Hold Locks for as short a time as possible
持有鎖的時間盡可能短
Prefer executors and tasks to threads
盡可能使用JDK并發工具包提供的Executor框架,進行多線程操作
Prefer Concurrency utilities to wait and notify
盡可能使用JDK并發工具包提供的工具進行同步(等待和通知)
- Concurrent Collections
- ConcurrentMap
- ConcurrentHashMap
- COncurrentLinkedQueue
- CopyOnWriteArrayList
- BlockingQueue Implementations
- ArrayBlockingQueue
- LinkedBlockingQueue
- PriorityBlockingQueue
Producer Consumer Pattern
了解生產者消費者模式
Synchronizers
同步器
- Semaphore
- CountDownLatch
- CyclicBarrier
- Exchanger
Multithreaded Lazy Initialization is tricky
多線程環境下,lazy init是一件棘手的事情
Prefer Normal initialization
盡可能使用正常的初始化(盡可能不要使用lazy init)
摘要: java反射效率到底如何,花了點時間,做了一個簡單的測試.供大家參考.
測試背景:
1. 測試簡單Bean(int,Integer,String)的set方法
2. loop 1億次
3. 測試代碼盡可能避免對象的創建,復發方法的調用,僅僅測試set方法的耗時
測試結果:
場景
&...
閱讀全文
在說另類思路之前,先說下傳統的測試方法:
0.準備一個干凈的測試數據庫環境
這個是前提
1.測試數據準備
使用文本,excel,或者wiki等,準備測試sql以及測試數據
利用dbfit,dbutil等工具將準備的測試數據導入到數據庫中
2.執行dao方法
執行被測試的dao方法
3.測試結果斷言
利用dbfit,dbutil等工具,斷言測試結果數據和預計是否一致
4.所有數據回滾
其實,對于這個流程來說,目前的dao測試框架,支持的已經比較完美了
但是此類測試方法,也有明顯的缺點(或者不能叫缺點,叫使用比較麻煩的地方)
如下:
1.背上了一個數據庫環境.
不輕量
這是一個共享環境,誰也無法確保環境數據是否真正的干凈
2.測試數據準備是一件麻煩的事情
新表,10幾個字段毫不為奇;老表,50幾個字段甚至百來個字段,也偶有可見;無論是使用文本,excel,wiki,準備工作量,都是巨大的.
準備的數據,部分字段內容可以是無意義的,部分字段內容又是需要符合測試意圖(testcase設計目的),部分字段還是其他表的關聯字段.從而導致后續維護人員無法了解準備數據意圖.
(實踐中,也出現過,一同事在維護他人單元測試時,由于無法了解測試數據準備意圖,寧可重新刪除,自己準備一份)
3.預計結果數據準備也是一件麻煩的事情
理由如上
所以,理論上是完美的測試方案,在實踐過程中,卻是一件麻煩的事情.導致DAO單元測試維護困難.
分析了現狀,我們再來分析下,IBatis下DAO,程序員主要做了哪些編碼:
1. 寫了一份sqlmap.xml配置文件
2. 通過
getSqlMapClientTemplate.doSomething($sqlID,$param), 執行語句
(當然,沒有使用spring的同學,也是使用了類似sqlMapClient.doSomething($sqlID,$param)方法)
而步驟2其實是框架替我們做了的事情,按照MOCK的思想,其實這部分代碼可以被MOCK的,那么我們是否可以做如下假設:
只要sqlmap.xml中配置信息(主要包括resultmap和statement)是正確的,那么執行結果也應該是正確的.
而我所謂的另類思路,就是基于這個假設,得出的:
IBatis下,DAO單元測試,我們拋棄背負的數據庫環境,只要根據不同的條件,斷言不同的sql即可.
于是乎,封裝了一個IbatisSqlTester,可以根據sqlmap中的statement和傳入的條件參數,生成sql語句.
那么,DAO單元測試就簡單了,脫離下數據庫環境:
public class ScoreDAOTest extends TestCase {
@SpringBeanByName
private IbatisSqlTester ibatisSqlTester; //通過spring配置,需要注入sqlmapclient對象
@Test
public void testListTpScores() {
Map<String, Object> param = new HashMap<String, Object>(1);
param.put("memberIds", new String[] { "stone", "stone2083" });
SqlStatement sql = ibatisSqlTester.test("MS-LIST-SCORES", param);
// sql全部匹配
SqlAssert.isEqual("select * from score where member_id in ('stone','stone2083')", sql.toString());
// sql包含member_id,athena2002,stone關鍵詞
SqlAssert.keyWith(sql.toString(), "member_id", "stone", "stone2083");
// sql符合某個 正則
SqlAssert.regexWith(".* where member_id in .*", sql.toString());
//其中,SqlAssert也可以換 成want.string()中的方法.
}
}
優勢:
脫離了數據庫環境
脫離了表結構數據準備
脫離了預計結果數據準備
讓單元測試變成sql的斷言,編寫相對更簡單
缺點:
row mapper過程無法被測試
最后,附上兩個核心的代碼類(還未完成),供大家參考:
SqlStatement.java
/**
* <pre>
* SqlStatement:Sql語句對象.
* 包含:
* 1.sql語句,類似 select * from offer where id = ? and member_id = ?
* 2.參數值,類似 [1,stone2083]
*
* toString方法,返回執行的sql語句,如:
* select * from offer where id = '1' and member_id = 'stone2083'
* </pre>
*
* @author Stone.J 2010-8-9 下午02:55:36
*/
public class SqlStatement {
//sql
private String sql;
//sql參數
private Object[] param;
/**
* <pre>
* 輸出最終執行的sql內容.
* 將sql和param進行merge,產生最終執行的sql語句
* </pre>
*/
@Override
public String toString() {
return merge();
}
/**
* <pre>
* 將sql進行格式化.
*
* 目前只是簡單進行格式化.去除前后空格,已經重復空格
* TODO:請使用統一格式化標準規,建議使用SqlFormater類,進行處理
* </pre>
*
* @param sql
* @return
*/
protected String format(String sql) {
if (sql == null) {
return null;
}
return sql.toLowerCase().trim().replaceAll("\\s{1,}", " ");
}
/**
* <pre>
* 將sql和param進行merge.
* TODO:請嚴格按照SQL標準,進行merge sql內容
* </pre>
*/
protected String merge() {
if (param == null || param.length == 0) {
return this.sql;
}
String ret = sql;
for (Object p : param) {
ret = ret.replaceFirst("\\?", "'" + p.toString() + "'");
}
return ret;
}
public String getSql() {
return sql;
}
public void setSql(String sql) {
this.sql = format(sql);
}
public Object[] getParam() {
return param;
}
public void setParam(Object[] param) {
this.param = param;
}
}
IbatisSqlTester.java
/**
* <pre>
* IBtatis SQL 測試
* 一般IBatis DAO單元測試,主要就是在測試ibatis的配置文件.
* IbatisSqlTester將根據提供的Sql Map Id 和 對應的參數,返回 {@link SqlStatement}對象,提供最終執行的sql語句
* 通過外部SqlAssert對象,將預計Sql和實際產生的Sql進行對比,判斷是否正確
* </pre>
*
* @author Stone.J 2010-8-9 下午02:58:46
*/
public class IbatisSqlTester {
// sqlMapClient
private ExtendedSqlMapClient sqlMapClient;
/**
* 根據提供的SqlMap ID,得到 {@link SqlStatement}對象
*
* @param sqlId: sql map id
* @return @see {@link SqlStatement}
*/
public SqlStatement test(String sqlId) {
//得到MappedStatement對象
MappedStatement ms = sqlMapClient.getMappedStatement(sqlId);
if (ms == null) {
//TODO:建議封轉自己的異常對象
throw new RuntimeException("can't find MappedStatement.");
}
//按照Ibatis代碼,得到Sql和Param信息
RequestScope request = new RequestScope();
ms.initRequest(request);
Sql sql = ms.getSql();
String sqlValue = sql.getSql(request, null);
//組轉返回對象
SqlStatement ret = new SqlStatement();
ret.setSql(sqlValue);
return ret;
}
/**
* 根據提供的SqlMap ID和對應的param信息,得到 {@link SqlStatement}對象
*
* @param sqlId: sql map id
* @param param: 參數內容
* @return @see {@link SqlStatement}
*/
public SqlStatement test(String sqlId, Object param) {
//得到MappedStatement對象
MappedStatement ms = sqlMapClient.getMappedStatement(sqlId);
if (ms == null) {
//TODO:建議封轉自己的異常對象
throw new RuntimeException("can't find MappedStatement.");
}
//按照Ibatis代碼,得到Sql和Param信息
RequestScope request = new RequestScope();
ms.initRequest(request);
Sql sql = ms.getSql();
String sqlValue = sql.getSql(request, param);
Object[] sqlParam = sql.getParameterMap(request, param).getParameterObjectValues(request, param);
//組轉返回對象
SqlStatement ret = new SqlStatement();
ret.setSql(sqlValue);
ret.setParam(sqlParam);
return ret;
}
/**
* 設置SqlMapClient對象
*/
public void setSqlMapClient(ExtendedSqlMapClient sqlMapClient) {
this.sqlMapClient = sqlMapClient;
}
/**
* <pre>
* 不推薦使用
* 推薦使用: {@link IbatisSqlTester#setSqlMapClient(ExtendedSqlMapClient)}
* TODO:請去除這個方法,或者增加初始化的方式
* </pre>
*
* @param sqlMapConfig sqlMapConfig xml文件
*/
public void setSqlMapConfig(String sqlMapConfig) {
InputStream in = null;
try {
File file = ResourceUtils.getFile(sqlMapConfig);
in = new FileInputStream(file);
this.sqlMapClient = (ExtendedSqlMapClient) SqlMapClientBuilder.buildSqlMapClient(in);
} catch (Exception e) {
throw new RuntimeException("sqlMapConfig init error.", e);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
}
}
}
}
}
最后的最后附上所有代碼(通過單元測試代碼,可以看如何使用).歡迎大家的討論.
sqltester
builder
很早之前,為了簡化配置信息,自己寫了一坨代碼,基于classpath掃描類信息,加載.
其實,在spring中,已經提供了類似組件(后知后覺了...):
org.springframework.core.io.support.PathMatchingResourcePatternResolver 資源解析器(基于路徑的正則表達式)
org.springframework.core.type.classreading.MetadataReader ClassMeta信息解讀器
于是乎,代碼就非常簡單了:
1 public class Test {
2
3 /* 資源路徑 */
4 private static final String PATH = "classpath*:com/alibaba/javalab/t*/**/*.class";
5 /* 資源解析器 */
6 private static final ResourcePatternResolver RESOLVER = new PathMatchingResourcePatternResolver();
7 /* Meta信息Reader Factory.用于創建MetaReader */
8 private static final MetadataReaderFactory READER_FACTORY = new SimpleMetadataReaderFactory();
9
10 public static void main(String[] args) throws Exception {
11 //根據正則表達式,得到資源列表
12 Resource[] resources = RESOLVER.getResources(PATH);
13 for (Resource res : resources) {
14 //通過 MetadataReader得到ClassMeta信息,打印類名
15 MetadataReader meta = READER_FACTORY.getMetadataReader(res);
16 System.out.println(meta.getClassMetadata().getClassName());
17 }
18 }
19 }
輸出結果:
com.alibaba.javalab.tool.fetion.protocol.Config
com.alibaba.javalab.tool.fetion.protocol.Fetion
com.alibaba.javalab.tool.fetion.protocol.FetionHelper
com.alibaba.javalab.tool.fetion.protocol.LoginSession
com.alibaba.javalab.tool.trace.TimeTrace
...
挺好使的一個工具 :)
一直來只知道ThreadLocal,直到最近看slf4j MDC實現代碼的時候,才認識了InheritableThreadLocal.
InheritableThreadLocal顧名思義,可繼承的ThreadLocal.
看類描述:
This class extends <tt>ThreadLocal</tt> to provide inheritance of values
* from parent thread to child thread: when a child thread is created, the
* child receives initial values for all inheritable thread-local variables
* for which the parent has values.
測試代碼:
1 public class Test {
2
3 public static void main(String[] args) {
4 //使用ThreadLocal,父子線程之間,不共享Value
5 final ThreadLocal<String> tl = new ThreadLocal<String>();
6 tl.set("ThreadLocal-VAL");
7 System.out.println("Main-1:" + tl.get());
8 new Thread() {
9 public void run() {
10 System.out.println("Child-1:" + tl.get());
11 };
12 }.start();
13
14 //使用InheritableThreadLocal,父線程Value可讓子線程共享
15 final ThreadLocal<String> itl = new InheritableThreadLocal<String>();
16 itl.set("InheritableThreadLocal-VAL");
17 System.out.println("Main-2:" + itl.get());
18 new Thread() {
19 public void run() {
20 System.out.println("Child-2:" + itl.get());
21 };
22 }.start();
23
24 }
25 }
輸出內容:
Main-1:ThreadLocal-VAL
Main-2:InheritableThreadLocal-VAL
Child-1:null
Child-2:InheritableThreadLocal-VAL
......分隔符號......
順帶著簡單說下MDC.(Mapped Diagnostic Context). 中文直譯太惡心了,我理解的意思是,和環境相關的上下文信息.
比如在web應用中,我們可以把用戶的ip,訪問url等放入到這個上下文中,log打印的時候,就能得到這個信息.
在slf4j BasicMDCAdapter實現中,就是用了InheritableThreadLocal
1 public class BasicMDCAdapter implements MDCAdapter {
2
3 private InheritableThreadLocal inheritableThreadLocal = new InheritableThreadLocal();
4
5 //
.
6
7 }
背景:讓應用在一個環境下,以多實例的方法運行.
Log問題,可以通過Log4j占位符實現(見前文:
http://www.tkk7.com/stone2083/archive/2010/07/01/324935.html)
其他Java組件代碼依賴了本地環境資源,怎么解決呢?
對于使用Spring的組件來說,PropertyPlaceholderConfigurer能幫我們解決這一問題.
PropertyPlaceholderConfigurer除了支持配置的properties文件外,還支持系統屬性(System.getProperties()).當然,它有三種模式:
1/** Never check system properties. */
2public static final int SYSTEM_PROPERTIES_MODE_NEVER = 0;
3
4/**
5 * Check system properties if not resolvable in the specified properties.
6 * This is the default.
7 */
8public static final int SYSTEM_PROPERTIES_MODE_FALLBACK = 1;
9
10/**
11 * Check system properties first, before trying the specified properties.
12 * This allows system properties to override any other property source.
13 */
14public static final int SYSTEM_PROPERTIES_MODE_OVERRIDE = 2;
對于使用本地環境資源的bean來說,只要配置:
1 <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
2 <property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
3 <property name="locations">
4 <list>
5 <value>classpath*:spring/env.properties</value> <!--無需配置node-->
6 </list>
7 </property>
8 </bean>
9
10 <bean id="javaBean" class="com.alibaba.javalab.spring.JavaBean">
11 <property name="lockFile" value="/home/stone/base/${node}/lock" />
12 </bean>
在啟動腳本中,只要加入-Dnode=instanceX即可.
總結:
PropertyPlaceholderConfigurer支持properties文件和系統屬性.并且存在三種覆蓋策略.
轉自:
http://blog.renren.com/blog/226112318/452978694
- 初次訪問發生在幾點幾分?
- 完全打開首頁花費多少時間?
- 是否瀏覽完整個首頁后再去找login入口?
- 找login入口花了多少時間?
- 是否在服務器提示下找到入口?
- 在找到真正login頁面之前,是否誤入后臺login頁面?
- 是否使用XX助手找到入口?
- 輸錯了幾次密碼后成功登陸?
- 在第一次成功登陸的時候,是否使用https(安全連接)?
- 登陸之前購買了多少份https證書?
- 是否由于服務器帶寬太小導致登入很慢?
- 登陸成功后服務器是否發出提示音?
- 登陸之后產生了多少PV才最終下單?
- 服務器是否在初次下單后給出紅色回執?
- 下單之后留在處于登陸狀態幾分鐘才離開?
- 整個訪問過程一共產生了幾個session?
- 平均session時長是幾分鐘?
- 當日訪問的cookie類型是cookie2.2還是3.1?
- 當日一共下了多少單?
- 在之后的30日內的活躍度類型(5次-18次屬于中度活躍度)
實踐經驗:
1. 對于封閉式題目(回答是與否),比較沒勁:問問題的人描述了半天,回答者只回答一個是否者否.對于此類問題,要做改進
2. 有些問題,都不好意思問出口
3. 千萬要根據新人的性格,決定是否是否這套模板,切忌切忌
摘要: 背景:
大學里學java,老師口口聲聲,言之鑿鑿,告誡我們,Java千萬別用異常控制業務流程,只有系統級別的問題,才能使用異常;
(當時,我們都不懂為什么不能用異常,只知道老師這么說,我們就這么做,考試才不會錯 :) )
公司里,有兩派.異常擁護者說,使用業務異常,代碼邏輯更清晰,更OOP;反之者說,使用異常,性能非常糟糕;
(當然,我是擁護者)
論壇上,爭論得更多,仁者見仁智者見智,口...
閱讀全文