#
背景:
加密的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.blogjava.net/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;反之者说,使用异常,性能非常糟糕;
(当然,我是拥护者)
论坛上,争论得更多,仁者见仁智者见智,口...
阅读全文