做过项目的人都知道,在构思完一个项目的功能之后,紧接着的事情就是考虑这些构思的功能如何实现,对于自己不熟悉的领域,要进行技术穿刺。我的穿刺方法为先查找有无比较好的开源组件可用,如果没有,就查找相关的文档,自己编写和测试代码。
在这一篇,我主要解决三个问题。 1、解决字符串加密的问题,在前面一篇中,我们设计用户模块的时候,准备将用户的密码字段以MD5加密的方式保存,因此,这里需要写一个对字符串加密生成MD5字符串的方法; 2、解决生成图像缩略图和生成验证码的问题; 3、解决url重写的问题,之所以要用到url重写,主要是为了让用户在访问自己的主页时,可以使用http://www./username或者http://username.这样的形式,而不是像http://www./index.jsp?username=xxx这样难看的形式。
需要说明的是,要解决上面的三个问题,不是没有开源的东西可用,而是我觉得每次都要整合不同的组件是在是太麻烦,而我们需要的功能也不是很复杂,我们不需要太通用的东西,只要能够解决这里特定的问题就行了,因此不如自己动手实现,同时还可以获得技术上的提高。
首先来看看MD5加密的问题,JDK中本来提供有数据加密的支持,其中java.security.MessageDigest类就可以实现MD5的加密,但是,加密后生成的数据是byte[]类型的,这里只需要写一个方法将它转换为字符串就行,代码如下:
package com.xkland.util;

import java.security.MessageDigest;
import java.lang.NullPointerException;
import java.security.NoSuchAlgorithmException;

 public class StringUtil {
 public static char [] num_chars = new char [] { ‘ 0 ‘ ,
‘ 1 ‘ , ‘ 2 ‘ , ‘ 3 ‘ , ‘ 4 ‘ , ‘ 5 ‘ , ‘ 6 ‘ , ‘ 7 ‘ , ‘ 8 ‘ ,
‘ 9 ‘ , ‘ A ‘ , ‘ B ‘ , ‘ C ‘ , ‘ D ‘ , ‘ E ‘ , ‘ F ‘ } ;
public static String toMD5String(String input)
 throws NullPointerException,NoSuchAlgorithmException {
if (input == null ) throw new NullPointerException();
char [] output = new char [ 32 ];
MessageDigest md = MessageDigest.getInstance( " MD5 " );
byte [] by = md.digest(input.getBytes());
 for ( int i = 0 ;i < by.length;i ++ ) {
output[ 2 * i] = num_chars[ (by[i] & 0xf0 ) >> 4 ];
output[ 2 * i + 1 ] = num_chars[ by[i] & 0xf ];
}
return new String(output);
}
}
下面是它的测试用例:
package com.xkland.util;

import junit.framework.TestCase;

 public class StringUtilTest extends TestCase {

 public void testToMD5String() {
 try {
System.out.println(StringUtil.toMD5String( " abc " ));
 } catch (Exception e) {
}
}
}
运行测试用例,输出结果为:
900150983CD24FB0D6963F7D28E17F72
再来说说关于图像缩略图生成的问题,我准备将它设置为一个可以让Spring管理的类,简单的说,可以利用Spring的配置文件来设置该类的一些属性,比如原图像保存的目录和目标图像保存的目录,生成的缩略图的大小,生成缩略图的方式。这里特别需要说明的就是这个生成缩略图的方式,我们即可以指定它只简单的执行缩放,也可以指定它进行剪裁以后再缩放。为什么要这么设计,请大家看看如下的效果图,对于下面这两张美女图:


如果我们只通过简单的缩放来生成缩略图,那么在网页上的布局效果为:

如果我们通过先剪切后缩放的效果来生成缩略图,那么在网页上布局的效果为:

可以看到通过第二种方式生成的缩略图布局要漂亮一些,但是会损失图片的信息。因此,两种方式各有优劣。所以在设计的时候就设计为能够让用户灵活配置。
对于有些网友反映的gif动画经过缩放以后就不能动了,这个问题的主要原因是因为Java SDK 1.4和1.5版本的ImageIO类只能读gif格式的文件,而不能写gif格式的文件,因此,对于gif格式的文件,生成的缩略图只能用png格式代替,在我的设计中,我准备让bmp格式的文件也让png格式代替,因为png格式生成的文件更小,而且也不损失图片质量。至于Java SDK 1.4和1.5版不支持写gif格式的文件,可以查看Java文档,下面是截图:

最新推出的Java SDK 6是可以写gif格式的文件的,因此如果要解决这个问题,可以使用最新的JDK,下面是文档截图:

下面是我写的生成缩略图和生成验证码的ImageUtil类的源代码:
package com.xkland.util;

import javax.imageio.ImageIO;

import java.awt.image.BufferedImage;
import java.io.File;
import java.awt.Image;
import java.awt.Graphics2D;
import java.util.Random;
import java.awt.Font;

 public class ImageUtil {
private String sourceDir; // 图片的存放路径
private String destinationDir; // 缩略图的存放路径
private String mode; // 生成缩略图的模式,可选ScaleOnly或ClipAndScale
private String width; // 缩略图的宽度
private String height; // 缩略图的高度
private String characterStorage; // 用来生成验证码的字符仓库
// 以下代码段是为了使用Spring注入属性
public void setCharacterStorage(String characterStorage) {
this .characterStorage = characterStorage;
}
public void setDestinationDir(String destinationDir) {
this .destinationDir = destinationDir;
}
public void setHeight(String height) {
this .height = height;
}
public void setMode(String mode) {
this .mode = mode;
}
public void setSourceDir(String sourceDir) {
this .sourceDir = sourceDir;
}
public void setWidth(String width) {
this .width = width;
}
// 生成缩略图的方法,默认缩略图的文件名和原图相同,存放路径不同
public void createMicroImage(String fileName)
 throws Exception {
// 判断sourceDir的格式是否为以"\"结尾,并生成完整的路径
String sourceFileName;
String destinationFileName;
 if (sourceDir.lastIndexOf( ‘ \\ ‘ ) != (sourceDir.length() - 1 )) {
sourceFileName = sourceDir + " \\ " + fileName;
destinationFileName = destinationDir + " \\ " + fileName;
 } else {
sourceFileName = sourceDir + fileName;
destinationFileName = destinationDir + fileName;
}
// 创建文件,并判断原文件是否存在
File sourceFile = new File(sourceFileName);
 if ( ! sourceFile.exists()) {
throw new Exception();
}
// 根据扩展名判断原文件的格式
String extension = fileName.substring(fileName.lastIndexOf( ‘ . ‘ ) + 1 );
if ( ! extension.equalsIgnoreCase( " jpg " ) && ! extension.equalsIgnoreCase( " bmp " )
 && ! extension.equalsIgnoreCase( " gif " ) && ! extension.equalsIgnoreCase( " png " )) {
throw new Exception();
}
// 判断缩略图的宽度和高度是否正确,如果不能正确解析则抛出异常
int destinationWidth = Integer.parseInt(width);
int destinationHeight = Integer.parseInt(height);
// 判断缩放模式是否正确,如果配置错误,则抛出异常
if ( ! mode.equalsIgnoreCase( " ScaleOnly " )
 && ! mode.equalsIgnoreCase( " ClipAndScale " )) {
throw new Exception();
}
// 读取图像文件,并创建BufferedImage对象,如果不能读取,则抛出异常
BufferedImage image = null ;
image = ImageIO.read(sourceFile);
 if (image == null ) {
throw new Exception();
}
// 获取原图像文件的高度和宽度
int sourceWidth = image.getWidth();
int sourceHeight = image.getHeight();
// 生成缩略图
if (mode.equalsIgnoreCase( " ScaleOnly " )) {
BufferedImage destinationImage;
 if (( float )sourceWidth / destinationWidth > ( float )sourceHeight / destinationHeight) {
Image tempImage = image.getScaledInstance(destinationWidth, ( int )(destinationWidth * (( float )sourceHeight / sourceWidth)), Image.SCALE_DEFAULT);
destinationImage = new BufferedImage(destinationWidth, ( int )(destinationWidth * (( float )sourceHeight / sourceWidth)),BufferedImage.TYPE_INT_RGB);
Graphics2D graphics = destinationImage.createGraphics();
graphics.drawImage(tempImage, 0 , 0 , null );
 } else {
Image tempImage = image.getScaledInstance(( int )(destinationHeight * (( float )sourceWidth / sourceHeight)), destinationHeight, Image.SCALE_DEFAULT);
destinationImage = new BufferedImage(( int )(destinationHeight * (( float )sourceWidth / sourceHeight)), destinationHeight,BufferedImage.TYPE_INT_RGB);
Graphics2D graphics = destinationImage.createGraphics();
graphics.drawImage(tempImage, 0 , 0 , null );
}
// 如果是bmp或者gif,则缩略图为png格式
if (extension.equalsIgnoreCase( " bmp " ) || extension.equalsIgnoreCase( " gif " )) {
extension = " png " ;
destinationFileName = destinationFileName.substring( 0 , destinationFileName.lastIndexOf( ‘ . ‘ )) + " . " + extension;
}
File destinationFile = new File(destinationFileName);
ImageIO.write(destinationImage, extension, destinationFile);
 } else {
BufferedImage destinationImage;
 if (( float )sourceWidth / destinationWidth > ( float )sourceHeight / destinationHeight) {
// 先裁减
int x = sourceWidth - ( int )(sourceHeight * (( float )destinationWidth / destinationHeight));
Image clipedImage = image.getSubimage(( int )( 0.5 * x), 0 , ( int )(sourceHeight * (( float )destinationWidth / destinationHeight)), sourceHeight);
// 后缩放
Image scaledImage = clipedImage.getScaledInstance(destinationWidth, destinationHeight, Image.SCALE_DEFAULT);
destinationImage = new BufferedImage(destinationWidth, destinationHeight,BufferedImage.TYPE_INT_RGB);
Graphics2D graphics = destinationImage.createGraphics();
graphics.drawImage(scaledImage, 0 , 0 , null );
 } else {
// 先裁减
int y = sourceHeight - ( int )(sourceWidth * (( float )destinationHeight / destinationWidth));
Image clipedImage = image.getSubimage( 0 , ( int )( 0.5 * y), sourceWidth, ( int )(sourceWidth * (( float )destinationHeight / destinationWidth)));
// 后缩放
Image scaledImage = clipedImage.getScaledInstance(destinationWidth, destinationHeight, Image.SCALE_DEFAULT);
destinationImage = new BufferedImage(destinationWidth, destinationHeight,BufferedImage.TYPE_INT_RGB);
Graphics2D graphics = destinationImage.createGraphics();
graphics.drawImage(scaledImage, 0 , 0 , null );
}
// 如果是bmp或者gif,则缩略图为png格式
if (extension.equalsIgnoreCase( " bmp " ) || extension.equalsIgnoreCase( " gif " )) {
extension = " png " ;
destinationFileName = destinationFileName.substring( 0 , destinationFileName.lastIndexOf( ‘ . ‘ )) + " . " + extension;
}
File destinationFile = new File(destinationFileName);
ImageIO.write(destinationImage, extension, destinationFile);
}
}
// 生成验证码的方法
public BufferedImage createValidateImage() {
BufferedImage validateImage = new BufferedImage( 80 , 20 ,BufferedImage.TYPE_INT_RGB);
Graphics2D graphics = validateImage.createGraphics();
// 从characterStorage中随机抽取四个字符生成验证码
int length = characterStorage.length();
char [] chars = new char [ 4 ];
Random rand = new Random();
 for ( int i = 0 ; i < 4 ; i ++ ) {
int index = rand.nextInt(length);
chars[i] = characterStorage.charAt(index);
}
String str = new String(chars);
graphics.setFont( new Font( " 宋体 " ,Font.BOLD, 18 ));
System.out.println(graphics.getFont());
graphics.drawString(str, 2 , 16 );
// 随机画干扰直线
for ( int i = 0 ; i < 5 ; i ++ ) {
int x1 = rand.nextInt( 80 );
int y1 = rand.nextInt( 20 );
int x2 = rand.nextInt( 80 );
int y2 = rand.nextInt( 20 );
graphics.drawLine(x1, y1, x2, y2);
}
return validateImage;
}
}
写得比较仓促,没有进行重构,所以比较难看一点。下面是测试用例的代码:
package com.xkland.util;

import junit.framework.TestCase;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;

 public class ImageUtilTest extends TestCase {

 public void testCreateMicroImage() throws Exception {
ImageUtil util = new ImageUtil();
util.setSourceDir( " E:\\ " );
util.setDestinationDir( " F:\\ " );
util.setWidth( " 100 " );
util.setHeight( " 100 " );
// 以仅缩放的形式生成缩略图
util.setMode( " ScaleOnly " );
// 横图像
util.createMicroImage( " 001.bmp " );
// 竖图像
util.createMicroImage( " 002.jpg " );
// 以先裁减后缩放的形式生成缩略图
util.setDestinationDir( " G:\\ " );
util.setMode( " ClipAndScale " );
// 横图像
util.createMicroImage( " 001.bmp " );
// 竖图像
util.createMicroImage( " 002.jpg " );
}
 public void testCreateValidateImage() throws Exception {
ImageUtil util = new ImageUtil();
util.setCharacterStorage( " ABCDEFGHIJKLMNOPQRSTUVWXYZ北冥有鱼其名为鲲鲲之大不知其几千里也化而为鸟其名为鹏鹏之背不知其几千里也怒而飞其翼若垂天之云是鸟也海运则将徙于南冥南冥者天池也 " );
BufferedImage image = util.createValidateImage();
ImageIO.write(image, " jpg " , new File( " F:\\validateImage.jpg " ));
}
}
运行该测试用例,可以成功的生成缩略图,并且可以生成验证码,生成的验证码如下图:
 把以上代码再修改再完善,就可以创建更漂亮一点的图形了。
为了把上面这个ImageUtil类让SpringSide管理起来,并进行灵活的配置,可以在src\main\resources\spring目录下建立beans.xml文件,并如下配置:
<? xml version="1.0" encoding="UTF-8" ?>
<! DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www./dtd/spring-beans-2.0.dtd" >
< beans >
< bean id ="imageUtil" class ="com.xkland.util.ImageUtil" >
< property name ="sourceDir" >
< value > E:\ </ value >
</ property >
< property name ="destinationDir" >
< value > F:\ </ value >
</ property >
< property name ="width" >
< value > 100 </ value >
</ property >
< property name ="height" >
< value > 100 </ value >
</ property >
< property name ="mode" >
< value > ScaleOnly </ value >
</ property >
< property name ="characterStorage" >
< value > ABCDEFGHIJKLMNOPQRSTUVWXYZ北冥有鱼其名为鲲鲲之大不知其几千里也化而为鸟其名为鹏鹏之背不知其几千里也怒而飞其翼若垂天之云是鸟也海运则将徙于南冥南冥者天池也 </ value >
</ property >
</ bean >
</ beans >
最后,我们再来看看url重写的问题。俗话说得好:“会者不难,难者不会”,刚开始我为了实现文章开头所说的url重写功能,尝试采用的是配置Servlet映射的方法,但是怎么都不成功,后来才想到使用Filter来实现。有时候开源的东西会直接影响人的思路,比如Struts 1.x采用的就是配置Servlet映射的方法,而到了2.0,也改成Filter了。
在Filter中实现url重写比较简单,无非就是分析字符串和替换字符串,这里我就不列代码了。只有想不到,没有做不到,想办法实现我们设计的功能,这便是技术穿刺的作用。
|