Ibatis with MapBean

用了两年的Hibernate之后,对ORM深有感触。一方面,ORM可以令到我们从OO角度来思考数据,屏蔽了数据库的差异(其实Hibernate被吹的最厉害就是那个分页,其实分页没什么技术含量,而且ROR都把分页从核心抽离了)。另一方面,性能问题,例如必须select才能update,必须select全部,必须update全部,cache带来的后遗症,javabean关联问题(一对多,多对一,多对多等),lazyload问题。当然,小型项目是很有优势的

Hibernate一直说,他生成的sql比一般程序员的要好,可以减少很多问题。但是,我倒是认为用Hibernate反而因为对程序员的要求更高导致更多问题。而且我现在认为,SQL才是王道,至少目前是。

于是在新项目中启用Ibatis,这一年来的使用感觉很不错,全程使用细粒度的SQL语句,虽然多写了很多SQL语句,但是感觉到项目在自己的控制中

我只用了Ibatis 40%不到的功能,可能大家会觉得不可思议。我没有用cache,没有用关联,没有用resultMap,甚至,连JavaBean也没有用

在大家都在讨论PO,VO,DTO的时候,我的系统里面一个JavaBean都没有

首先我问问,JavaBean是用来做什么的,存储数据,每一个PO,其实就约等于一个表里面的一行数据

我举一个真实一点的例子,一个user表

public class User {
  private int id;
  private String name;
 
  // 省略getter/setter一大段
}

不知道大家有没有用eclipse生成JavaBean getter/setter的痛苦,至少非常枯燥。整个JavaBean实现了什么功能?完全没有

好了我开谜底了,我用的是HashMap

什么,HashMap?是不是听错了

没听错,请问有什么事情是上面那个那么普通的JavaBean能做到的,而HashMap不能做到的呢

用HashMap没有具体属性的类型啊,那不是变成动态语言一样了,而且我要在JavaBean里面加逻辑怎么办

好吧,这是我想到的两个问题(如果你也有别的问题可以留意提出来)

首先,现在不同以前了,动态语言的优势慢慢提高了,像动态语言有什么不好,我从ROR里面学了很多不错的思想

其次是要加逻辑怎么办,这个问题,之前在Javaeye讨论充血模型还是贫血模型不可开交,最后还是没什么结论,目前还是一片贫血的情况,你可以翻一下你的项目里面的JavaBean,至少大多数的Bean都是没有意义的getter/setter。BO跟PO混杂在一起也很多人不建议的。至于逻辑,大可以写在util包里面,我都把整个model包去掉了,强化一下util不成么,呵呵

再次,数据库的列,跟JavaBean的属性两者,本来就是冗余的,如果我们修改数据库结构,就还要修改相应的JavaBean,或者影射文件。当然我这种做法更依赖数据库。另外,ROR的名字转换功能可以令到代码中的调用名字更好看一些,我觉得也不是非常有必要的实现

直接使用HashMap不太方便,尤其是类型转换上,于是我实现了一个MapBean的类,其实这个类很简单,关键是用HashMap代替JavaBean的思想

public class MapBean extends HashMap<String, Object> {
	public MapBean() {
	}
 
	public MapBean(Object... args) {
		put(args);
	}
 
	public int getInt(Object key) {
		return getInt(key, 0);
	}
 
	public int getInt(Object key, int defaultInt) {
		Integer i = (Integer) get(key);
		return i == null ? defaultInt : i;
	}
 
	public String getString(Object key) {
		return (String) get(key);
	}
 
	public String getString(Object key, String defaultValue) {
		String value = (String) get(key);
		return value == null ? defaultValue : value;
	}
 
	public Timestamp getTimestamp(Object key) {
		return (Timestamp) get(key);
	}
 
	public void put(Object... args) {
		for (int i = 1; i < args.length; i += 2) {
			put(String.valueOf(args[i - 1]), args[i]);
		}
	}
 
	public JSONObject toJson() {
		return JSONObject.fromObject(this);
	}
 
	public JSONObject toJson(String... keys) {
		xxx
	}
 
	public String toJsonString() {
		return toJson().toString();
	}
}

这个类主要是方便做类型转换,加入了getInt,getString等方法,另外因为我的系统里面大量使用了JSON,也有一些HashMap向JSON转换的辅助方法,还有一个特别处理过的put方法和构造器,有什么用呢,看下面的例子

MapBean params = new MapBean("id",userId,"name",username,"sex",0,"online",true);

如果你直接用HashMap实现以上功能,要写五行代码,就这个差别而已,呵呵

当然如果你用JavaBean的话,你也可以写一个对应的构造函数

接下来,我们在sql-map-config.xml中加入

<typeAlias alias="mapbean" type="xxxxxxx.MapBean" />

就可以用mapbean的别称来引用这个类了

例如具体的SQL xml是这样的

<select id="getWorkManagerSystemRole" resultClass="mapbean"
	parameterClass="mapbean">
	select * from work_manager_system_role where
	system_id=#system_id# and user_id=#user_id#
</select>

其实我已经把Ibatis当成SQL wrapper来用了,我曾经评估过Spring的JDBC Template,不过功能始终差少少,或许以后我把JDBC Template再强化一下来代替Ibatis吧

Posted in Uncategorized | 7 Comments

架设你的CruiseControl

说实在话,07年最大的感悟就是重新认识了TDD和持续集成的意义,这个下次有机会再说。这次要说的是部署一个持续集成服务器。

持续集成服务器的作用其实很简单,就是check out最新的代码,然后运行指定的script,然后把结果记录下来,还可以将结果(尤其是错误的结果)通过email等手段发给程序员。

事情倒是很简单,但是我们为什么要专门的软件呢,当然你也可以自己写bash script来做这些事情,做着做着你就会发现差不多实现一个CruiseControl出来了,呵呵,无谓重复发明轮子。

常用的持续集成服务器有CruiseControl、Luntbuild 和 Anthill等,相对来说CruiseControl比较老牌,支持度也比较好,不过实际使用的时候还是觉得不太好使(最近又发现了一个软件Hudson,Netbeans在使用)

下面是安装和配置过程,可能有一些细节不是很准确,因为已经是比较早之前安装的

从官方网站下载最新版的CruiseControl 2.7.1,我选用的是exe的版本,因为我的服务器是windows的,相对来说,exe版本有一些预设的参数,配置会简单些

运行安装程序,默认装在C:\Program Files\CruiseControl,可以不改动,而实际的项目文件可以放在其他盘上面的

安装成功之后运行cruisecontrol.bat,就能看到一个dos窗口运行,这个时候访问本机8080端口就可以看到一个示范项目

接下来修改配置文件

打开config.xml,可以看到示范例子的配置,把它都注释掉,不过其实我最后的配置也跟它差不多

直接贴我的配置吧,然后再来解释

<project name="xxx-xxx">
        <listeners>
            <currentbuildstatuslistener file="logs/${project.name}/status.txt"/>
        </listeners>
        <bootstrappers>
            <svnbootstrapper localWorkingCopy="D:/cc/${project.name}" />
        </bootstrappers>
        <modificationset quietperiod="30">
		<svn localWorkingCopy="D:/cc/${project.name}"/>
        </modificationset>
        <schedule interval="300">
            <ant anthome="apache-ant-1.7.0" buildfile="D:/cc/${project.name}/build.xml"
target="svnup test" />
        </schedule>
        <log>
		<merge dir="D:/cc/${project.name}/target/testresult"/>
        </log>
</project>

首先是项目的名字,这个是必须的,中途可以用${project.name}来引用

listeners是监听事件,这里加了一个构建状态监听。CruiseControl默认把构建状态保存在logs/${project.name}/status.txt下,但是,这仅仅是成功or失败的状态,而没有具体的信息,加上这个配置的话,就能在首页看到是否成功的显示

bootstrappers和modificationset启动监听,其实我已经分不清他们两个的具体作用了(不好意思了,以后再翻一下补上),配置到你的项目目录中,我这里用了D:/cc/${project.name}。另外,你应该预先用svn checkout一份项目在这个位置,因为CruiseControl还要在该目录进行svn update的操作,所以这个目录的svn信息必须完整的,比如有.svn等文件,简单来说,也就是你可以在该目录成功运行“svn update”的命令

schedule里面放的是实际运行的script,很明显,我这里用了ant,调用了它的svnup和test两个target,为什么有个svnup的东西?呵呵待会说

最后是log,这个是用来合并ant输出日志的,如果你在CruiseControl的构建页面里面没有看到任何出错的详细信息,那就可能是没有合并日志,而ant脚本也必须配置成把相应的日志输出到设定的目录

基本的配置就是这样,这里还miss了一个ant的build.xml的内容,待会再介绍它,现在我先来说说CruiseControl的工作流程

CruiseControl通过内置的svn支持,定期检查localWorkingCopy目录的svn状态变化,然后触发构建script,但是,这里有一点必须主意的是,CruiseControl并没有svn update的功能,他仅仅是用内置的svn支持去检查你的目录是否有更新,但是具体的更新是由你来做,于是便有了很奇怪的ant svnup的target,待会你会看到它的内容

说实在话我觉得这种模式非常奇怪。而且如果检查的频率不够的话,svnup的时候,非常有可能update了好几个changeset下来,也就是说构建不是按照changeset分割的,仅仅是按照时间分割的

接下来就是build.xml的内容,我只列出跟CruiseControl相关的部分

<target name="test" depends="testcompile">
	<junit printsummary="true" fork="true">
		<classpath>
			...
		</classpath>
		<batchtest todir="target/testresult">
			<fileset dir="target/test">
				<include name="**/*Test.*" />
			</fileset>
		</batchtest>
	</junit>
</target>
<target name="svnup">
	<exec executable="svn">
		<arg line="up" />
	</exec>
</target>

这里需要主意的是,把测试结果输出到目录,然后在CruiseControl合并。另外一个当然是奇怪的svnup

这里有一个问题,如果你用海龟之类的svn client checkout项目,而你的机器上面没有装svn命令行的话,你可能不能使用我这个方法,需要有少少改动。其实就是用ant的svn支持来代替命令行的svn操作,具体可以研究ant的配置手册

基本就是这样了,重启你的CruiseControl,然后提交一些changge上svn,看看有没有结果输出吧

Posted in Uncategorized | 7 Comments

优化你的Eclipse VM参数

最近dzone在讨论eclipse的VM参数
引用了一篇2004年的帖子

Here are the best options that I’ve found so far for my 2-processor Windows machine running JDK5.0 and Eclipse3.1:

-vmargs -XX:+UseParallelGC

2004年就用两个CPU都颇奢侈了吧

我现在的机器是双核+2G内存,于是我修改了一下eclipse的VM参数

打开eclipse.ini

修改或加入如下内容

-vmargs
-Xmx512m
-XX:+UseParallelGC

Posted in Uncategorized | Leave a comment

我与Mercurial

最初知道 Mercurial这个工具其实是因为去年6月份看了cyfdecyf的一篇blog分布式版本管理工具:git & mercurial(作者搬迁了blog,新地址是这里).然后断断续续有在使用,一直想把一些经验写出来.最近刚好遇到云风在研究分布式版本管理,还是动手把一些经验写下来吧

一直一来都是用CVS,然后是SVN来做版本管理,最初认识分布式版本管理,是一个叫SVK的工具,是一个台湾人的作品,本身基于SVN,令到SVN可以分布式工作.当时觉得安装非常麻烦就没有研究下去了,而且本身对这样的功能需求也不大.后来Linus因为不满现有版本管理软件的问题,自己花了两个星期写了一个分布式版本管理Git,现在Linux kernel等源代码已经完全工作在Git的管理之上了.但是Git本身非常晦涩难懂,而且只能在Linux下运行,我本身也主要用Windows来工作,因此也没有深入研究.

直至看到 cyfdecyf的文章,这篇文章有坚定我研究分布式版本管理的信心.集中式版本管理的缺陷, cyfdecyf和云风都有描述了一些例子,我认为那些例子都很典型.如果所有开发人员都在一个房间里面,集中式版本管理是最好的,最明显的例子就是公司的开发,大家都能直接访问到服务器,网络不会中断.这种情况下,使用分布式版本管理反而增加了复杂性.但是,如果开发人员不是在同一个房间,甚至在不同的国家.当然你也可以在Internet上假设一个SVN服务器.但是,开发人员不一定有网络,或者网络不一定很好.而每次提交,或者同步,都要花上好几分钟或者更长时间,这非常影响开发.又或者你只是私人的项目,而你有没有能力假设公开的SVN服务器.而且我只是一个人,为什么每次都要提交到外边的服务器.当然也可以在自己的机器上面架设一个SVN,但是如果你有两台以上的机器就会觉得很麻烦了.

所有的这些场景,其实你需要一个分布式版本管理.cyfdecyf的文章最后在选择Git的代替品,定了用Mercurial.那我也从Mercurial开始吧.不过Mercurial的中文资料非常少,我始终不理解分布式版本管理的原理.最后,非常搞笑的情况是,我其实是看了Git的一份中文资料才真正明白的,也推荐大家从Git的中文资料入手.

待续…

Posted in Uncategorized | Tagged | 10 Comments

如何用Java进行3DES加密解密

最近一个合作商提出使用3DES交换数据,本来他们有现成的代码,可惜只有.net版本,我们的服务器都是Linux,而且应用都是Java。于是对照他们提供的代码改了一个Java的版本出来,主要是不熟悉3DES,折腾了一天,终于搞定。

所谓3DES,就是把DES做三次,当然不是简单地DES DES DES就行了,中途有些特定的排列。这个我可不关心,呵呵,我的目的是使用它。

在网上搜索了一下3DES,找到很少资料。经过朋友介绍,找到GNU Crypto和Bouncy Castle两个Java扩充包,里面应该有3DES的实现吧。

从GNU Crypto入手,找到一个TripleDES的实现类,发现原来3DES还有一个名字叫DESede,在网上搜索TripleDES和DESede,呵呵,终于发现更多的资料了。

Java的安全API始终那么难用,先创建一个cipher看看算法在不在吧

Cipher cipher = Cipher.getInstance("DESede");

如果没有抛异常的话,就证明这个算法是有效的

突然想看看JDK有没有内置DESede,于是撇开Crypto,直接测试,发现可以正确运行。在jce.jar里面找到相关的类,JDK内置了。

于是直接用DES的代码来改&测试,最后代码变成这样

SecureRandom sr = new SecureRandom();
DESedeKeySpec dks = new DESedeKeySpec(PASSWORD_CRYPT_KEY.getBytes());
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DESede");
SecretKey securekey = keyFactory.generateSecret(dks);
Cipher cipher = Cipher.getInstance("DESede");
cipher.init(Cipher.ENCRYPT_MODE, securekey, sr);
return new String(Hex.encodeHex(cipher.doFinal(str.getBytes())));

需要留意的是,要使用DESede的Spec、Factory和Cipher才行

事情还没完结,合作商给过来的除了密钥之外,还有一个IV向量。搜索了一下,发现有一个IvParameterSpec类,于是代码变成这样

SecureRandom sr = new SecureRandom();
DESedeKeySpec dks = new DESedeKeySpec(PASSWORD_CRYPT_KEY.getBytes());
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DESede");
SecretKey securekey = keyFactory.generateSecret(dks);
IvParameterSpec iv = new IvParameterSpec(PASSWORD_IV.getBytes());
Cipher cipher = Cipher.getInstance("DESede");
cipher.init(Cipher.ENCRYPT_MODE, securekey, iv, sr);
return new String(Hex.encodeHex(cipher.doFinal(str.getBytes())));

但是,运行报错了

java.security.InvalidAlgorithmParameterException: ECB mode cannot use IV

ECB是什么呢?我的代码完全没有写ECB什么的

又上网搜索,结果把DES的来龙去脉都搞清楚了

http://www.tropsoft.com/strongenc/des.htm

ECB是其中一种字串分割方式,除了DES以外,其他加密方式也会使用这种分割方式的,而Java默认产生的DES算法就是用ECB方法,ECB不需要向量,当然也就不支持向量了

除了ECB,DES还支持CBC、CFB、OFB,而3DES只支持ECB和CBC两种

http://www.tropsoft.com/strongenc/des3.htm

CBC支持并且必须有向量,具体算法这里就不说了。合作商给的.net代码没有声明CBC模式,似乎是.net默认的方式就是CBC的

于是把模式改成CBC

Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");

成功运行了

后话:

搜索的过程中,找到一个不错的讨论

http://www.lslnet.com/linux/dosc1/21/linux-197579.htm

在CBC(不光是DES算法)模式下,iv通过随机数(或伪随机)机制产生是一种比较常见的方法。iv的作用主要是用于产生密文的第一个block,以使最终生成的密文产生差异(明文相同的情况下),使密码攻击变得更为困难,除此之外iv并无其它用途。因此iv通过随机方式产生是一种十分简便、有效的途径。此外,在IPsec中采用了DES-CBC作为缺省的加密方式,其使用的iv是通讯包的时间戳。从原理上来说,这与随机数机制并无二致。

看来,向量的作用其实就是salt

最大的好处是,可以令到即使相同的明文,相同的密钥,能产生不同的密文

例如,我们用DES方式在数据保存用户密码的时候,可以另外增加一列,把向量同时保存下来,并且每次用不同的向量。这样的好处是,即使两个用户的密码是一样的,数据库保存的密文,也会不一样,就能降低猜测的可能性

另外一种用法,就是类似IPsec的做法,两部主机互传数据,保证两部机的时钟同步的前提下(可以取样到分钟或更高的单位避免偏差),用时钟的变化值作为向量,就能增加被sniffer数据的解密难度

Posted in Uncategorized | Tagged , | 3 Comments

如何用Java进行DES加密解密

这篇其实是引子,直接贴代码,不多解释了

SecureRandom sr = new SecureRandom();
DESKeySpec dks = new DESKeySpec(PASSWORD_CRYPT_KEY.getBytes());
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
SecretKey securekey = keyFactory.generateSecret(dks);
Cipher cipher = Cipher.getInstance("DES");
cipher.init(Cipher.ENCRYPT_MODE, securekey, sr);
return new String(Hex.encodeHex(cipher.doFinal(str.getBytes())));
SecureRandom sr = new SecureRandom();
DESKeySpec dks = new DESKeySpec(PASSWORD_CRYPT_KEY.getBytes());
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
SecretKey securekey = keyFactory.generateSecret(dks);
Cipher cipher = Cipher.getInstance("DES");
cipher.init(Cipher.DECRYPT_MODE, securekey, sr);
return new String(cipher.doFinal(Hex.decodeHex(str.toCharArray())));
Posted in Uncategorized | Tagged , | 2 Comments

PageCacheFilter2

/**
 * 2006-5-1
 * @author Sparkle
 */
public class PageCacheFilter2 implements Filter {
	private Set<string> cacheUrlSet = new HashSet<string>();
	private Set<string> scacheUrlSet = new HashSet<string>();
	private String baseCachePath, contentType;
 
	public void init(FilterConfig config) throws ServletException {
		String cacheUrl = config.getInitParameter("cacheUrl");
		if (cacheUrl != null) {
			StringTokenizer tk = new StringTokenizer(cacheUrl);
			while (tk.hasMoreTokens()) {
				String str = tk.nextToken().trim().toLowerCase();
				if (str.endsWith("?")) {
					str = str.substring(0, str.length() - 1);
					scacheUrlSet.add(str);
				}
				cacheUrlSet.add(str);
			}
		}
		baseCachePath = config.getInitParameter("cachePath");
		contentType = config.getInitParameter("contentType");
	}
 
	private int urlHashCode(HttpServletRequest request) {
		String uri = request.getRequestURI();
		String qStr = request.getQueryString();
		if (qStr != null) {
			uri += ('?' + qStr);
		}
		return Math.abs(uri.hashCode());
	}
 
	private String cacheFilePath(HttpServletRequest request) {
		String uri = request.getRequestURI();
		int hash = urlHashCode(request);
		if (scacheUrlSet.contains(uri.toLowerCase())) {
			String entityId = request.getParameter("entityId");
			if (StringUtils.isNotBlank(entityId)) {
				return baseCachePath + "/article/" + subDir(entityId)
						+ '/' + entityId + '/' + hash + ".wml";
			}
		}
		return baseCachePath + "/other/" + subDir2(String.valueOf(hash))
				+ '/' + hash + ".wml";
	}
 
	private String subDir(String str) {
		int length = str.length();
		if (length > 4) {
			return str.substring(0, length - 4);
		}
		return "0";
	}
 
	private String subDir2(String str) {
		int length = str.length();
		if (length > 4) {
			return str.substring(length - 4, length);
		}
		return str;
	}
 
	private void clearCache(HttpServletRequest request, File cacheFile) {
		if (scacheUrlSet.contains(request.getRequestURI().toLowerCase())) {
			File cacheDir = cacheFile.getParentFile();
			for (File file : cacheDir.listFiles()) {
				file.delete();
			}
			cacheDir.delete();
		} else {
			cacheFile.delete();
		}
	}
 
	public void doFilter(ServletRequest req, ServletResponse res,
			FilterChain chain) throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) req;
		if (!cacheUrlSet.contains(request.getRequestURI().toLowerCase())) {
			chain.doFilter(req, res);
			return;
		}
 
		HttpServletResponse response = (HttpServletResponse) res;
 
		File cacheFile = new File(cacheFilePath(request));
		if (cacheFile.isFile()) {
			if (request.getParameter("refresh") != null) {
				clearCache(request, cacheFile);
			} else {
				try {
					BufferedReader reader = new BufferedReader(
							new FileReader(cacheFile));
					response.setContentType(contentType);
					String line;
					PrintWriter printWriter = response.getWriter();
					while ((line = reader.readLine()) != null) {
						printWriter.println(line);
					}
					printWriter.close();
					return;
				} catch (IOException e) {
					// ignore
					response.reset();
					System.out.println("read cache file error");
				}
			}
 
		}
 
		StringWriter strWriter = new StringWriter();
		boolean[] support = new boolean[1];
		support[0] = true;
		chain.doFilter(req, new ContentResponseWrapper(response, strWriter,
				support));
		if (support[0] && !response.isCommitted()) {
			String content = strWriter.getBuffer().toString();
 
			PrintWriter printWriter = response.getWriter();
			printWriter.println(content);
			printWriter.close();
 
			File cacheDir = cacheFile.getParentFile();
			if (!cacheDir.exists()) {
				cacheDir.mkdirs();
			}
			PrintWriter writer = new PrintWriter(cacheFile);
			writer.println(content);
			writer.close();
		}
 
	}
 
	public void destroy() {
	}
 
}

代码点评:这段代码的作用是,对指定的url进行cache,用了ContentRewriteFilter一样的做法来获取内容,因为具体的细节有些区别,没有继承ContentRewriteFilter而是直接编写代码。先判断是否有cache文件,如果有就输出给用户,但是如果连接上有refresh参数的话,就会清掉cache文件,如果没有cache文件,就会获取Servlet or JSP输出的内容,原原本本保存到磁盘,供下次用户访问使用。如果设置URL的时候,最后一位是问号,就会进入scacheUrlSet,这个是用于文章类型,主要的不同点是有分页,如果要refresh的话,应该要把所有分页的cache文件都清除。至于类名后面有个2,是因为这个是第二个版本。

(最近在考虑用Squid+Etag来实现一样的功能)

Posted in Uncategorized | Tagged , | Leave a comment

ContentRewriteFilter

    /**
     * 2006-4-30
     * @author Sparkle
     */
    public abstract class ContentRewriteFilter implements Filter {
	protected boolean wantRewrite(HttpServletRequest request) {
		return true;
	}
 
	protected abstract String rewrite(String content,
			HttpServletRequest request, HttpServletResponse response);
 
	public void doFilter(ServletRequest req, ServletResponse res,
			FilterChain chain) throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) req;
		if (!wantRewrite(request) || res.isCommitted()) {
			chain.doFilter(req, res);
			return;
		}
 
		HttpServletResponse response = (HttpServletResponse) res;
		StringWriter strWriter = new StringWriter();
		boolean[] support = new boolean[1];
		support[0] = true;
		chain.doFilter(req, new ContentResponseWrapper(response, strWriter,
				support));
		if (support[0] && !response.isCommitted()) {
			String content = strWriter.getBuffer().toString();
 
			content = rewrite(content, request, response);
 
			PrintWriter printWriter = response.getWriter();
			printWriter.println(content);
			printWriter.close();
		}
 
	}
 
	public void destroy() {
	}
 
	public void init(FilterConfig config) throws ServletException {
	}
    }
 
 
    public class ContentResponseWrapper extends HttpServletResponseWrapper {
	private Writer writer;
 
	private boolean[] support;
 
	public ContentResponseWrapper(HttpServletResponse response, Writer writer,
			boolean[] support) {
		super(response);
		this.writer = writer;
		this.support = support;
	}
 
	@Override
	public ServletOutputStream getOutputStream() throws IOException {
		support[0] = false;
		return super.getOutputStream();
	}
 
	@Override
	public PrintWriter getWriter() throws IOException {
		return new PrintWriter(writer);
	}
 
    }

代码点评:这段代码的作用是,获得Servlet or JSP输出的内容进行改写操作,这是一个基类,继承之后重载abstract rewrite即可获得输出的内容进行改写。这段代码在实际环境工作之后做了不少的修正,对response的commit状态,和是否成功获取内容都有较好的判断。中途用了一个[0]的技巧回传了一个boolean值。使用这个代码实现了动态插入广告,个人短消息等资料,还有sessionid自动补充(!)等等功能

Posted in Uncategorized | Leave a comment

CryptUtil

    /**
     * 2006-6-10
     * @author Sparkle
     */
    public class CryptUtil {
	/**
	 * 将一个数字转换成0-9a-zA-z的62进制
	 */
	public static String encodeLongToString(long i) {
		if (i == 0) {
			return "0";
		} else if (i < 0) {
			throw new IllegalArgumentException();
		}
		StringBuffer buffer = new StringBuffer();
		while (i > 0) {
			buffer.insert(0, encodeIntToChar((int) (i % 62)));
			i /= 62;
		}
		return buffer.toString();
	}
 
	/**
	 * 将0-9a-zA-z的62进制转换成一个数字
	 */
	public static long decodeStringToLong(String s) {
		long i = 0;
		for (int index = 0; index < s.length(); index++) {
			if (index > 0) {
				i *= 62;
			}
			i += decodeCharToInt(s.charAt(index));
		}
		return i;
	}
 
	/**
	 * 将数字作为顺序转换成单位的0-9a-zA-z<br />
	 * 0 -> '0'<br />
	 * 9 -> '9'<br />
	 * 10 -> 'a'<br />
	 * 35 -> 'z'<br />
	 * 36 -> 'A'<br />
	 * 61 -> 'Z'
	 */
	private static char encodeIntToChar(int i) {
		if (i >= 0 && i <= 9) {
			return (char) (i + 48);
		} else if (i >= 10 && i <= 35) {
			return (char) (i + 87);
		} else if (i >= 36 && i <= 61) {
			return (char) (i + 29);
		} else {
			throw new IllegalArgumentException();
		}
	}
 
	/**
	 * 将单位的0-9a-zA-z按顺序转换成数字<br />
	 * '0' -> 0<br />
	 * '9' -> 9<br />
	 * 'a' -> 10<br />
	 * 'z' -> 35<br />
	 * 'A' -> 36<br />
	 * 'Z' -> 61
	 */
	private static int decodeCharToInt(char c) {
		if (c >= '0' && c <= '9') {
			return c - 48;
		} else if (c >= 'a' && c <= 'z') {
			return c - 87;
		} else if (c >= 'A' && c <= 'Z') {
			return c - 29;
		} else {
			throw new IllegalArgumentException();
		}
	}
    }

代码点评:这段代码的作用是,转换出来的字符串会比16进制要短很多

Posted in Uncategorized | Leave a comment

Apache、Nginx、Lighttpd对比

Apache

* 经典的Web服务器
* 除了慢没有别的缺点了
* 对了,Apache2对fcgi支持并不好
* 非常好用的proxy和proxy_ajp(很多人用它作为tomcat的前端)
* 不支持epoll(这年头,epoll几乎是性能的必备)

Nginx

* 速度快,占用资源少
* 杀手级的proxy和rewrite
* 非常不错的静态文件能力
* 最适合作为整个网站的前端服务(将php、svn等不同请求发送往后端apache)
* 其他功能马马虎虎

Lighttpd

* 杀手级的静态文件能力
* 杀手级的fcgi能力
* 不稳定的proxy模块

总体来说,如果你不确定应该用什么服务器,那就应该用Apache
但是稍微可以配置多个服务的情况下,做一个Nginx在最前端,然后把需要的功能转发给Apache是最好的选择
如果你打算跑fcgi,Lighttpd是不二的选择
如果你打算做图片服务器,独立的Lighttpd也是很好的选择

Posted in 互联网 | Tagged , , | 3 Comments