分享

SpringSide开发实战(五):兵马未动,粮草先行...

 nbtymm 2007-01-15
做过项目的人都知道,在构思完一个项目的功能之后,紧接着的事情就是考虑这些构思的功能如何实现,对于自己不熟悉的领域,要进行技术穿刺。我的穿刺方法为先查找有无比较好的开源组件可用,如果没有,就查找相关的文档,自己编写和测试代码。

在这一篇,我主要解决三个问题。
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的配置文件来设置该类的一些属性,比如原图像保存的目录和目标图像保存的目录,生成的缩略图的大小,生成缩略图的方式。这里特别需要说明的就是这个生成缩略图的方式,我们即可以指定它只简单的执行缩放,也可以指定它进行剪裁以后再缩放。为什么要这么设计,请大家看看如下的效果图,对于下面这两张美女图:
32.jpg

33.jpg


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

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

可以看到通过第二种方式生成的缩略图布局要漂亮一些,但是会损失图片的信息。因此,两种方式各有优劣。所以在设计的时候就设计为能够让用户灵活配置。

对于有些网友反映的gif动画经过缩放以后就不能动了,这个问题的主要原因是因为Java SDK 1.4和1.5版本的ImageIO类只能读gif格式的文件,而不能写gif格式的文件,因此,对于gif格式的文件,生成的缩略图只能用png格式代替,在我的设计中,我准备让bmp格式的文件也让png格式代替,因为png格式生成的文件更小,而且也不损失图片质量。至于Java SDK 1.4和1.5版不支持写gif格式的文件,可以查看Java文档,下面是截图:
30.JPG

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

下面是我写的生成缩略图和生成验证码的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 " ));
    }


}


运行该测试用例,可以成功的生成缩略图,并且可以生成验证码,生成的验证码如下图:
40.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重写比较简单,无非就是分析字符串和替换字符串,这里我就不列代码了。只有想不到,没有做不到,想办法实现我们设计的功能,这便是技术穿刺的作用。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约