分类: Java

保持健康

前一段时间体检,其中一项空腹血糖值达到了6.13mmol/L,超过了3.9-6.1的正常范围,医学上称之为空腹血糖受损,是指空腹血糖在≥6.1mmol/L-<7.0mmol/L之间,即既高于正常标准又低于糖尿病诊断标准之间的一种亚健康状态,空腹血糖受损还不是疾病,但已经有血糖调节异常,它是一种危险状态,很可能发展成糖尿病。另外我的血脂指标也不好,体重指数BMI值偏高,总的来说,健康已经出现了一些问题,给我敲响了警钟,虽然这些都是大家公认的城市病、富贵病。

由于工作性质,每天大量的时间都是坐着,很少活动,下班比较晚,回去也就懒得动了,加上饮食上高油脂、高热量、高糖份,健康不出问题才怪,其实近3年,每周我都要进行一次体育锻炼,是在健身房里跑步,但看来这个运动量远远不够,我现在除了每周固定的运动之外,已经拒绝高脂肪、高糖份的食品,餐饮结构以蔬果为主,配合一定的谷物主食,同时坚持每天晚上少吃,并在吃过饭后在小区内步行20分钟以上,希望通过坚持,把体重也减下来,相信在下次体检时能恢复正常。

前几天研究Java导出SPSS格式数据,在网上找了半天,找到了2个三方库,都叫SPSS Writer,一个在http://spss.pmstation.com,另一个在http://sourceforge.net/projects/spss-writer/,这两个包生成的.sav文件,都会产生一个Demo的列,原来pmstation的这个SPSS Writer是要收费的,估计买了License就不会生成Demo列,pmstation的这个SPSS Writer的这个包还有个问题,就是不支持中文,我用UTF-8的编码也不支持(发邮件给pmstation询问,也没有给我回复),sourceforge的到是支持中文,但是这个包没有源码也没有任何文档,总的来说这两个包都不理想,难道没有比较好的Java导出SPSS格式的三方包吗?

今日调查《关于阿里巴巴战略投资新浪微博》,欢迎各位参与,结果下期公布。

欢迎订阅我的微信公共帐号:“Laoer杂谈”,搜索微信号:laoertalk,或扫描页面右侧的二维码
新浪微博:@laoer
Twitter:@laoer

GET方式传递中文错误引发的思考

今天在“天乙社区”里,网友报了一个BUG,在版区内搜索中文的时候,分页会出现问题,我在社区里试了一下,果然有这样的问题,我记得这个问题在写代码的时候特意注意过,我做了URLEncoder和URLDecoder,但还是有问题,当时可能测试不够,把这个问题漏过了,这个问题出现的原因很简单,就是在GET方式传递中文的时候,由于编码的问题出现了字符的错误,解决的方法也有几种:

第一种,是不采用GET方式,采用POST方式,可以在页面上加入一个隐藏表单,把数据写进去,分页的时候,用JavaScript提交,POST方式发送到服务器,这样的做法可以很好的保护数据完整性,也比较安全,但是对于社区来说需要修改页面,比较麻烦。

第二种,就是将中文内容做一些编码,比如Base64,之后再做一次URLEncoder,这样应该就可以传递正确的信息,我就按照这个方法修改了一下程序,没有问题了。

这时候我突然想起了Taobao的奇怪URL编码方式,比如我在淘宝上搜索“尼康 镜头”,请求到的地址是

http://search1.taobao.com/browse/0/n-g,ytq37njax2243ny——-2———40–commend-0-all-0.htm?at_topsearch=1&ssid=e-p1-s5

注意其中的“ytq37njax2243ny”,在JavaEye里有位高人写了一篇关于淘宝URL编码规则的文章,着实很强,淘宝也用的是Base64,只不过用自己的码表,这样编码出来的字符串就像“ytq37njax2243ny”,乍看上去挺怪异的,但为什么要这么做,我似乎明白了一些,淘宝、阿里巴巴都采用他们自己的Webx框架,在其前端专门有模块是处理URL的,包括URL的生成、解析,其好处有很多,其中一个重要的原因应该是解决编码问题,附件是阿里巴巴webx的一个文档,大家可以看看,有什么启发。

阿里巴巴webx框架资料

Tomcat中request.getContextPath()值引发的问题

今天是生日,一大早上社区就发现了一个问题,是一个网友报的错误(http://bbs.laoer.com/main-read-15-ff808081205a54e001205d2565901041.html),我一看还真是,连接地址有错误,成了http://post.bbscs?action=re&bid=15&parentID=……,前面的服务器地址没有了,程序没有修改过,为什么会出现这样的问题呢,我看了一下代码,在BBSCSUtil.java里有个getActionMappingURL(String action, HttpServletRequest request)方法,其中要取request.getContextPath(),当应用根路径下运行的时候,request.getContextPath()的值为“/”,所以getActionMappingURL方法返回的值前面多了一个“/”,在Tomcat5.5中对于//uri的连接解析似乎不正确,而在用Resin时候却没有问题,看来在request.getContextPath()的问题上还是要多注意。

Java RSA研究

前几天我做了PHP RSA的测试,想想Java来做RSA应该更简单,就在网上搜了一下Java RSA,发现资料是有一些,但却不是很完整,也不是很严谨,都是转来转去,看来还是自己要测试一下,两个文件RSAUtil.java和EncryptException.java,注意,JDK里没有RSA的provider,所以我们要用第三方的provider,在http://www.bouncycastle.org/下载最新的包,加入工程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
import javax.crypto.Cipher;
import java.security.*;
import java.security.spec.RSAPublicKeySpec;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.io.*;
import java.math.BigInteger;
 
public class RSAUtil {
 
    /**
     *   * 生成密钥对
     *   * @return KeyPair
     *   * @throws EncryptException
     */
    public static KeyPair generateKeyPair() throws EncryptException {
        try {
            KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA",
                    new org.bouncycastle.jce.provider.BouncyCastleProvider());
            final int KEY_SIZE = 1024;//没什么好说的了,这个值关系到块加密的大小,可以更改,但是不要太大,否则效率会低
            keyPairGen.initialize(KEY_SIZE, new SecureRandom());
            KeyPair keyPair = keyPairGen.genKeyPair();
            return keyPair;
        } catch (Exception e) {
            throw new EncryptException(e.getMessage());
        }
    }
 
    /**
     *   * 生成公钥
     *   * @param modulus
     *   * @param publicExponent
     *   * @return RSAPublicKey
     *   * @throws EncryptException
     */
    public static RSAPublicKey generateRSAPublicKey(byte[] modulus, byte[] publicExponent) throws EncryptException {
        KeyFactory keyFac = null;
        try {
            keyFac = KeyFactory.getInstance("RSA", new org.bouncycastle.jce.provider.BouncyCastleProvider());
        } catch (NoSuchAlgorithmException ex) {
            throw new EncryptException(ex.getMessage());
        }
 
        RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(new BigInteger(modulus), new BigInteger(publicExponent));
        try {
            return (RSAPublicKey) keyFac.generatePublic(pubKeySpec);
        } catch (InvalidKeySpecException ex) {
            throw new EncryptException(ex.getMessage());
        }
    }
 
    /**
     *   * 生成私钥
     *   * @param modulus
     *   * @param privateExponent
     *   * @return RSAPrivateKey
     *   * @throws EncryptException
     */
    public static RSAPrivateKey generateRSAPrivateKey(byte[] modulus, byte[] privateExponent) throws EncryptException {
        KeyFactory keyFac = null;
        try {
            keyFac = KeyFactory.getInstance("RSA", new org.bouncycastle.jce.provider.BouncyCastleProvider());
        } catch (NoSuchAlgorithmException ex) {
            throw new EncryptException(ex.getMessage());
        }
 
        RSAPrivateKeySpec priKeySpec = new RSAPrivateKeySpec(new BigInteger(modulus), new BigInteger(privateExponent));
        try {
            return (RSAPrivateKey) keyFac.generatePrivate(priKeySpec);
        } catch (InvalidKeySpecException ex) {
            throw new EncryptException(ex.getMessage());
        }
    }
 
    /**
     *   * 加密
     *   * @param key 加密的密钥
     *   * @param data 待加密的明文数据
     *   * @return 加密后的数据
     *   * @throws EncryptException
     */
    public static byte[] encrypt(Key key, byte[] data) throws EncryptException {
        try {
            Cipher cipher = Cipher.getInstance("RSA", new org.bouncycastle.jce.provider.BouncyCastleProvider());
            cipher.init(Cipher.ENCRYPT_MODE, key);
            int blockSize = cipher.getBlockSize();//获得加密块大小,如:加密前数据为128个byte,而key_size=1024 加密块大小为127 byte,加密后为128个byte;因此共有2个加密块,第一个127 byte第二个为1个byte
            int outputSize = cipher.getOutputSize(data.length);//获得加密块加密后块大小
            int leavedSize = data.length % blockSize;
            int blocksSize = leavedSize != 0 ? data.length / blockSize + 1 : data.length / blockSize;
            byte[] raw = new byte[outputSize * blocksSize];
            int i = 0;
            while (data.length - i * blockSize > 0) {
                if (data.length - i * blockSize > blockSize)
                    cipher.doFinal(data, i * blockSize, blockSize, raw, i * outputSize);
                else
                    cipher.doFinal(data, i * blockSize, data.length - i * blockSize, raw, i * outputSize);
//这里面doUpdate方法不可用,查看源代码后发现每次doUpdate后并没有什么实际动作除了把byte[]放到ByteArrayOutputStream中,而最后doFinal的时候才将所有的byte[]进行加密,可是到了此时加密块大小很可能已经超出了OutputSize所以只好用dofinal方法。
 
                i++;
            }
            return raw;
        } catch (Exception e) {
            throw new EncryptException(e.getMessage());
        }
    }
 
 
 
    /**
     *   * 解密
     *   * @param key 解密的密钥
     *   * @param raw 已经加密的数据
     *   * @return 解密后的明文
     *   * @throws EncryptException
     */
    public static byte[] decrypt(Key key, byte[] raw) throws EncryptException {
        try {
            Cipher cipher = Cipher.getInstance("RSA", new org.bouncycastle.jce.provider.BouncyCastleProvider());
            cipher.init(cipher.DECRYPT_MODE, key);
            int blockSize = cipher.getBlockSize();
            ByteArrayOutputStream bout = new ByteArrayOutputStream(64);
            int j = 0;
 
            while (raw.length - j * blockSize > 0) {
                bout.write(cipher.doFinal(raw, j * blockSize, blockSize));
                j++;
            }
            return bout.toByteArray();
        } catch (Exception e) {
            throw new EncryptException(e.getMessage());
        }
    }
 
    public static String byte2hex(byte[] b) {
        String hs = "";
        String stmp = "";
        for (int i = 0; i < b.length; i++) {
            stmp = Integer.toHexString(b[i] & 0xFF);
            if (stmp.length() == 1) {
                hs += "0" + stmp;
            } else {
                hs += stmp;
            }
        }
        //return hs;
        return hs.toUpperCase();
    }
 
 
    public static byte[] hex2byte(String hex) throws IllegalArgumentException {
        if (hex.length() % 2 != 0) {
            throw new IllegalArgumentException();
        }
        char[] arr = hex.toCharArray();
        byte[] b = new byte[hex.length() / 2];
        for (int i = 0, j = 0, l = hex.length(); i < l; i++, j++) {
            String swap = "" + arr[i++] + arr[i];
            int byteint = Integer.parseInt(swap, 16) & 0xFF;
            b[j] = new Integer(byteint).byteValue();
        }
        return b;
    }
 
    /**
     *   *
     *   * @param args
     *   * @throws Exception
     */
    public static void main(String[] args) throws Exception {
 
        byte[] orgData = "test".getBytes();
        KeyPair keyPair = RSAUtil.generateKeyPair();
        RSAPublicKey pubKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey priKey = (RSAPrivateKey) keyPair.getPrivate();
 
        byte[] pubModBytes = pubKey.getModulus().toByteArray();
        System.out.println("PubKey Modulus:"+new BigInteger(pubModBytes).toString(16));
        byte[] pubPubExpBytes = pubKey.getPublicExponent().toByteArray();
        System.out.println("publicExponent:"+new BigInteger(pubPubExpBytes).toString(16));
        byte[] priModBytes = priKey.getModulus().toByteArray();
        byte[] priPriExpBytes = priKey.getPrivateExponent().toByteArray();
        RSAPublicKey recoveryPubKey = RSAUtil.generateRSAPublicKey(pubModBytes, pubPubExpBytes);
        RSAPrivateKey recoveryPriKey = RSAUtil.generateRSAPrivateKey(priModBytes, priPriExpBytes);
 
        byte[] raw = RSAUtil.encrypt(priKey, orgData);
        System.out.println("Encrypt:"+byte2hex(raw));
        byte[] data = RSAUtil.decrypt(recoveryPubKey, raw);
        System.out.println("Decrypt:"+new String(data));
 
    }
 
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class EncryptException extends Exception {
 
    public EncryptException() {
        super();
    }
 
    public EncryptException(String message) {
        super(message);
    }
 
    public EncryptException(String message, Throwable cause) {
        super(message, cause);
    }
 
    public EncryptException(Throwable cause) {
        super(cause);
    }
 
}

我们看一下运行结果:

1
2
3
4
PubKey Modulus:a907de8b5789b1df66c8a4ea90f99c9b00bbad520d487a7e218cd1ee2a1cafcaff2dd03a70cc61d8ccdfe0557b9132dd163a5a6c287d94790fc8b573a154ba0cd799e2cc73fc44c03083274760664125cafc33c647c44df300968665b6dbc9e553c59f8180de0ded3ae2163aab499c1ec0688ed7468fb816cdf05db501cbba19
publicExponent:10001
Encrypt:71521BBA91D871AAA8FF99D7E9D6E44DAE6218FAFDC07CE7C34ABACC1357854BF8F5D94F17FD106346B0916CC81A85B7031F9421810F1E5A568EE408E6DFDB219201CB1DE2AF259A516F3B930130D6AD4FFCB26072EB2BD9CFC11CA727B181A640311BC3023B2D1E54EBC52454C669389C24D0F7AD316B0B24257D53384E1446
Decrypt:test

使用已有的Modulus加密的话

1
2
3
4
5
6
7
8
String modulus = "D192471B8699640F931FE6F4FACC3E990B894F894CEA5BEE0DCBD7A4B76752F7345CF9B5F1271001B724F7A0ABF0A6E911E309536F4BE4749E92DCC531B8E36B95969D206649C9DD2371B413A8DFD9B92569660B1499A5CD310B86A8FDE24988E456897A416D2E7B0B649F0714F322C57EF92563B21A448D1072FF3806C34C75";
byte[] b_modulus = new BigInteger(modulus,16).toByteArray();
String publicExponent = "10001";
byte[] b_publicExponent = new BigInteger(publicExponent,16).toByteArray();
String text = "test";
RSAPublicKey recoveryPubKey = RSAUtil.generateRSAPublicKey(b_modulus, b_publicExponent);        
byte[] ss = RSAUtil.encrypt(recoveryPubKey,text.getBytes());
System.out.println(byte2hex(ss));

IDEA中使用Tomcat不能启动的问题

今天用IDEA做个Java的Web工程,想运行一下,在IDEA配置好Tomcat(8180端口),部署上去,运行,竟然报Address localhost:8180 is already in use,我检查了一下本地没有在8180上的服务,奇怪了,前一段用IDEA的时候没有这样的问题,我尝试将Tomcat换到其他的端口,依旧报错,还是Google一下吧,找到了http://www.notionzone.com/2008/11/19/intellij-idea-eclipse-tomcat-deploy-58.html,原来是NOD32的问题,我就是最近才换到NOD32的,将NOD32中“启用HTTP检查”关闭就好了。

Linux下切分Tomcat的catalina.out日志文件

随着Tomcat的运行,catalina.out文件会越来越大,虽然Tomcat每日会生成一个catalina.ymd.log的文件,但catalina.out主文件仍然不断增加,需要对catalina.out按日切分才好,在网上找了一下,看到一篇《rotating catalina.out in tomcat 5.5 using cronolog》,就用公司的Tomcat配置一下。

cronolog工具已经在服务器上装过,一个对日志切分的小工具,其主页在http://cronolog.org/,我们也用它来切分Apache的日志。

进入Tomcat的bin目录,打开catalina.sh文件,找到tomcat启动的相关行,或者你直接查找catalina.out,一般我们修改下面行中的内容(因为我们一般不会在-security条件下运行),

1
2
3
4
5
6
7
8
9
10
11
12
13
    else$_RUNJAVA$JAVA_OPTS $CATALINA_OPTS \
    -Djava.endorsed.dirs=”$JAVA_ENDORSED_DIRS-classpath$CLASSPATH” \
    -Dcatalina.base=”$CATALINA_BASE” \
    -Dcatalina.home=”$CATALINA_HOME” \
    -Djava.io.tmpdir=”$CATALINA_TMPDIR” \
    org.apache.catalina.startup.Bootstrap “$@” start  \
    >>$CATALINA_BASE/logs/catalina.out 2>&1 &
 
    if [ ! -z "$CATALINA_PID" ]; then
    echo $! > $CATALINA_PID
    fi
    fi

修改
org.apache.catalina.startup.Bootstrap “$@” start  \
>> “$CATALINA_BASE”/logs/catalina.out 2>&1 &

org.apache.catalina.startup.Bootstrap “$@” start 2>&1 \
| /usr/local/sbin/cronolog “$CATALINA_BASE”/logs/catalina.%Y-%m-%d.out >> /dev/null &
同时,上面有一行
touch “$CATALINA_BASE”/logs/catalina.out
可以注释掉,完成之后重起Tomcat就可以了,在logs目录下可以看到catalina.2009-02-18.out的日志,是按日生成的。

关于Struts2里使用EL或JSTL

最近在公司里做一些Java Web开发的培训,同时对已经做的一些工程做一些ReView,现在的工程里,工程师直接使用JSTL取得Action里的属性,这个用法我以前到真的没有用过,因为在我印象中,Struts2的这些Action属性,应该是在ValueStack中,而在某些情况下,从ValueStack取值是件挺麻烦的事情,在做天乙社区8时,我就参考Struts2的标记库,自己扩展标记库,从而取得ValueStack里的值,而JSTL应该是从Page、Request、Session和Application里顺序取值,莫非Struts2将ValueStack里的值也放入了Request?同时我们直接用EL标签也直接取出了Action的属性值,莫非真的放入了Request?但是打开Struts2的Debug,发现Request里并没有值。

带着这个疑问,我Google了一下,很快找到答案:

提问:在Struts2中,如何使用JSTL来读取Action中的变量?

这是一个历史悠久的问题。因为事实上,很多朋友(包括我在内)是不使用Struts2自身的标签库,而是使用JSTL的,可能因为JSTL标签库比较少,简单易用的原因吧。

我们知道,JSTL默认是从page,request,session,application这四个Scope逐次查找相应的EL表达式所对应 的对象的值。那么如果要使用JSTL来读取Action中的变量,就需要把Action中的变量,放到request域中才行。所以,早在 Webwork2.1.X的年代,我们会编写一个拦截器来做这个事情的。大致的原理是:在Action执行完返回之前,依次读取Action中的所有的变 量,并依次调用request.setAttribute()来进行设置。具体的整合方式,请参考以下这篇文档:http://wiki.opensymphony.com/display/WW/Using+WebWork+and+XWork+with+JSP+2.0+and+JSTL+1.1

不过随着时代的发展,上面的这种方式,已经不再被推荐使用了。(虽然如此,我们依然可以学习它的一个解决问题的思路)目前来说,自从 Webwork2.2以后,包括Struts2,都使用另外一种整合方式:对HttpServletRequest进行装饰。让我们来看一下源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
 public class StrutsRequestWrapper extends HttpServletRequestWrapper {
 
     /**
      * The constructor
      * @param req The request
      */
     public StrutsRequestWrapper(HttpServletRequest req) {
         super(req);
     }
 
     /**
      * Gets the object, looking in the value stack if not found
      *
      * @param s The attribute key
      */
     public Object getAttribute(String s) {
         if (s != null && s.startsWith("javax.servlet")) {
             // don't bother with the standard javax.servlet attributes, we can short-circuit this
             // see WW-953 and the forums post linked in that issue for more info
             return super.getAttribute(s);
         }
 
         ActionContext ctx = ActionContext.getContext();
         Object attribute = super.getAttribute(s);
 
         boolean alreadyIn = false;
         Boolean b = (Boolean) ctx.get("__requestWrapper.getAttribute");
         if (b != null) {
             alreadyIn = b.booleanValue();
         }
 
         // note: we don't let # come through or else a request for
         // #attr.foo or #request.foo could cause an endless loop
         if (!alreadyIn && attribute == null && s.indexOf("#") == -1) {
             try {
                 // If not found, then try the ValueStack
                 ctx.put("__requestWrapper.getAttribute", Boolean.TRUE);
                 ValueStack stack = ctx.getValueStack();
                 if (stack != null) {
                     attribute = stack.findValue(s);
                 }
             } finally {
                 ctx.put("__requestWrapper.getAttribute", Boolean.FALSE);
             }
         }
         return attribute;
    }
}

看到了嘛?这个类会在Struts2初始化的时候,替换HttpServletRequest,运行于整个Struts2的运行过程中,当我们试 图调用request.getAttribute()的时候,就会执行上面的这个方法。(这是一个典型的装饰器模式)在执行上面的方法时,会首先调用 HttpServletRequest中原本的request.getAttribute(),如果没有找到,它会继续到ValueStack中去查找, 而action在ValueStack中,所以action中的变量通过OGNL表达式,就能找到对应的值了。

在这里,在el表达式广泛使用的今天,JSTL1.1以后,也支持直接使用el表达式。注意与直接使用struts2的tag的区别,这里需要使用el的表示符号:${}

例如:${user.name}, <c:out value=”${department.name}” />

原来是这样,学无止境啊,很多东西需要仔细去研究。

3DES加密

DES加密算法是一种标准算法,应该可以通过不同程序语言实现,我在异构系统里,可能需要用不同的语言加密解密,整理不同语言的DES算法还是有必要的,先前我发过一个Java的3DES算法代码,我再发一遍

import java.security.*;
import javax.crypto.*;
import javax.crypto.spec.*;

public class DES {

public static int _DES = 1;
public static int _DESede = 2;
public static int _Blowfish = 3;

private Cipher p_Cipher;
private SecretKey p_Key;
private String p_Algorithm;

private void selectAlgorithm(int al) {
switch (al) {
default:
case 1:
this.p_Algorithm = “DES”;
break;
case 2:
this.p_Algorithm = “DESede”;
break;
case 3:
this.p_Algorithm = “Blowfish”;
break;
}
}

public DES(int algorithm) throws Exception {
this.selectAlgorithm(algorithm);
Security.addProvider(new com.sun.crypto.provider.SunJCE());
this.p_Cipher = Cipher.getInstance(this.p_Algorithm);
}

public byte[] getKey() {
return this.checkKey().getEncoded();
}

private SecretKey checkKey() {
try {
if (this.p_Key == null) {
KeyGenerator keygen = KeyGenerator.getInstance(this.p_Algorithm);
/*
SecureRandom sr = new SecureRandom(key.getBytes());
keygen.init(168, sr);*/
this.p_Key = keygen.generateKey();
}
}
catch (Exception nsae) {}
return this.p_Key;
}

public void setKey(byte[] enckey) {
this.p_Key = new SecretKeySpec(enckey, this.p_Algorithm);
}

public byte[] encode(byte[] data) throws Exception {
this.p_Cipher.init(Cipher.ENCRYPT_MODE, this.checkKey());
return this.p_Cipher.doFinal(data);
}

public byte[] decode(byte[] encdata, byte[] enckey) throws Exception {
this.setKey(enckey);
this.p_Cipher.init(Cipher.DECRYPT_MODE, this.p_Key);
return this.p_Cipher.doFinal(encdata);
}

public String byte2hex(byte[] b) {
String hs = “”;
String stmp = “”;
for (int i = 0; i < b.length; i++) {
stmp = Integer.toHexString(b[i] & 0xFF);
if (stmp.length() == 1) {
hs += “0” + stmp;
}
else {
hs += stmp;
}
}
return hs.toUpperCase();
}

public byte[] hex2byte(String hex) throws IllegalArgumentException {
if (hex.length() % 2 != 0) {
throw new IllegalArgumentException();
}
char[] arr = hex.toCharArray();
byte[] b = new byte[hex.length() / 2];
for (int i = 0, j = 0, l = hex.length(); i < l; i++, j++) {
String swap = “” + arr[i++] + arr[i];
int byteint = Integer.parseInt(swap, 16) & 0xFF;
b[j] = new Integer(byteint).byteValue();
}
return b;
}

public static void main(String[] args) throws Exception {

String info = “要加密的字串”;
System.out.println(“region string:” + info);
byte[] key; //密钥文件(byte)
DES des = new DES(DES._DESede); // 声明DES
key = des.getKey(); //获取随机生成的密钥
System.out.println(“encrypted key(byte):” + new String(key));
String hexkey = des.byte2hex(key); //生成十六进制密钥
System.out.println(“encrypted key(hex):” + hexkey);
byte[] enc = des.encode(info.getBytes()); //生成加密文件(byte)
System.out.println(“encrypted string(byte):” + new String(enc));

String hexenc = des.byte2hex(enc); //生成十六进制加密文件
System.out.println(“encrypted string(hex):” + hexenc);

byte[] dec = des.decode(enc, des.hex2byte(hexkey)); //解密文件,其中转换十六进制密钥为byte
System.out.println(“decrypted string:” + new String(dec)); //生成解密文件字符串, 与info相同
}

}

最近我在看PHP的时候,研究了一下PHP对于3DES的加密代码

<?php

function fmt3DESEncode($s,$base64key){

$key = base64_decode($base64key);

$td = mcrypt_module_open(MCRYPT_3DES, ”, MCRYPT_MODE_ECB, ”);

$iv_size = mcrypt_enc_get_iv_size($td);

$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);

mcrypt_generic_init($td, $key,$iv);

$encrypted_data = mcrypt_generic($td, $s);

mcrypt_generic_deinit($td);

mcrypt_module_close($td);

return $encrypted_data;

}

function PaddingPKCS7($data) {

$block_size = mcrypt_get_block_size(‘tripledes’, ‘ecb’);

$padding_char = $block_size – (strlen($data) % $block_size);

$data .= str_repeat(chr($padding_char),$padding_char);

return $data;

}

function fmt3DESDecode($s,$base64key) {

$key = base64_decode($base64key);

$td = mcrypt_module_open(MCRYPT_3DES, ”, MCRYPT_MODE_ECB, ”);

$iv_size = mcrypt_enc_get_iv_size($td);

$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);

mcrypt_generic_init($td, $key,$iv);

$p_t = mdecrypt_generic($td, $s);

mcrypt_generic_deinit($td);

mcrypt_module_close($td);

return $p_t;

}

function unPaddingPKCS7($data) {

$pad = ord($data{strlen($data)-1});

if ($pad > strlen($data)) {

return false;

}

if (strspn($data, chr($pad), strlen($data) – $pad) != $pad) {

return false;

}

$srcdata = substr($data, 0, -1 * $pad);

return $srcdata;

}

$S = “laoer”;

$str = BASE64_Encode(fmt3DESEncode(PaddingPKCS7($S),”XXXXXXXXX-BASE64-密钥”));

echo $str;

echo”<br/>”;

$str = fmt3DESDecode(base64_decode($str), “XXXXXXXXX-BASE64-密钥”);

echo unPaddingPKCS7($str);

?>

有几点注意,在Java里3DES默认ENCRYPT_MODE是ECB,并采用PKCS7补码,关于着两个概念,我不是专业人士,也解释不清楚,需要研究加密解密了,所以在PHP里也进行了相应的处理,还有就是Java代码里密钥和加密结果是用16进制编码了,PHP代码里用了Base64,大家用的时候相应改一下就可以了。

现在可以测试一下,JAVA和PHP的加密解密结果是否一致!

补:.NET下3DES的算法也没有问题,我不做这方面,但以前同事做过,都测试通过。

ApplicationContext研究

ApplicationContext是Spring的核心,Context我们通常解释为上下文环境,我想用“容器”来表述它更容易理解一些,ApplicationContext则是“应用的容器”了:P,Spring把Bean放在这个容器中,在需要的时候,用getBean方法取出,虽然我没有看过这一部分的源代码,但我想它应该是一个类似Map的结构。
在Web应用中,我们会用到WebApplicationContext,WebApplicationContext继承自ApplicationContext,先让我们看看在Web应用中,怎么初始化WebApplicationContext,在web.xml中定义:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>

<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<!– OR USE THE CONTEXTLOADERSERVLET INSTEAD OF THE LISTENER
<servlet>
<servlet-name>context</servlet-name>
<servlet-class>org.springframework.web.context.ContextLoaderServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
–>

可以看出,有两种方法,一个是用ContextLoaderListener这个Listerner,另一个是ContextLoaderServlet这个Servlet,这两个方法都是在web应用启动的时候来初始化WebApplicationContext,我个人认为Listerner要比Servlet更好一些,因为Listerner监听应用的启动和结束,而Servlet得启动要稍微延迟一些,如果在这时要做一些业务的操作,启动的前后顺序是有影响的。

那么在ContextLoaderListener和ContextLoaderServlet中到底做了什么呢?
以ContextLoaderListener为例,我们可以看到
public void contextInitialized(ServletContextEvent event) {
this.contextLoader = createContextLoader();
this.contextLoader.initWebApplicationContext(event.getServletContext());
}
protected ContextLoader createContextLoader() {
return new ContextLoader();
}
ContextLoader是一个工具类,用来初始化WebApplicationContext,其主要方法就是initWebApplicationContext,我们继续追踪initWebApplicationContext这个方法(具体代码我不贴出,大家可以看Spring中的源码),我们发现,原来ContextLoader是把WebApplicationContext(XmlWebApplicationContext是默认实现类)放在了ServletContext中,ServletContext也是一个“容器”,也是一个类似Map的结构,而WebApplicationContext在ServletContext中的KEY就是WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,我们如果要使用WebApplicationContext则需要从ServletContext取出,Spring提供了一个WebApplicationContextUtils类,可以方便的取出WebApplicationContext,只要把ServletContext传入就可以了。

上面我们介绍了WebApplicationContext在Servlet容器中初始化的原理,一般的Web应用就可以轻松的使用了,但是,随着Struts的广泛应用,把Struts和Spring整个起来,是一个需要面对的问题,Spring本身也提供了Struts的相关类,主要使用的有org.springframework.web.struts.ActionSupport,我们只要把自己的Action继承自ActionSupport,就是可以调用ActionSupport中getWebApplicationContext()的方法取出WebApplicationContext,但这样一来在Action中,需要取得业务逻辑的地方都要getBean,看上去不够简洁,所以Spring又提供了另一个方法,用org.springframework.web.struts.ContextLoaderPlugIn,这是一个Struts的Plug,在Struts启动时加载,对于Action,可以像管理Bean一样来管理,在struts-config.xml中Action的配置变成类似下面的样子
<action attribute=”aForm” name=”aForm” path=”/aAction” scope=”request”  type=”org.springframework.web.struts.DelegatingActionProxy”>
<forward name=”forward” path=”forward.jsp” />
</action>
注意type变成了org.springframework.web.struts.DelegatingActionProxy,之后我们需要建立action-servlet.xml这样的文件,action-servlet.xml符合Spring的spring-beans.dtd标准,在里面定义类似下面的
<bean name=”/aAction” class=”com.web.action.Aaction” singleton=”false”>
<property name=”businessService”>
<ref bean=”businessService”/>
</property>
</bean>

com.web.action.Aaction是Action的实现类,businessService是需要的业务逻辑,Spring会把businessService注入到Action中,在Action中只要写businessService的get和set方法就可以了,还有一点,action的bean是singleton=”false”,即每次新建一个实例,这也解决了Struts中Action的线程同步问题,具体过程是当用户做“/aAction”的HTTP请求(当然应该是“/aAction.do”),Struts会找到这个Action的对应类org.springframework.web.struts.DelegatingActionProxy,DelegatingActionProxy是个代理类,它会去找action-servlet.xml文件中“/aAction”对应的真正实现类,然后把它实例化,同时把需要的业务对象注入,然后执行Action的execute方法。

使用了ContextLoaderPlugIn,在struts-config.xml中变成类似这样配置
<plug-in className=”org.springframework.web.struts.ContextLoaderPlugIn”>
<set-property property=”contextConfigLocation” value=”/WEB-INF/applicationContext.xml,/WEB-INF/action-servlet.xml” />
</plug-in>
而在web.xml中不再需要ContextLoaderListener或是ContextLoaderServlet。

说到这里不知道大家会不会有这样的问题,如果使用ContextLoaderPlugIn,如果我们有些程序是脱离Struts的Action环境,我们怎么处理,比如我们要自定义标记库,在标记库中,我们需要调用Spring管理的业务层逻辑对象,这时候我们就很麻烦,因为只有在action中动态注入业务逻辑,其他我们似乎不能取得Spring的WebApplicationContext。

别急,我们还是来看一下ContextLoaderPlugIn的源码(源码不再贴出),我们可以发现,原来ContextLoaderPlugIn仍然是把WebApplicationContext放在ServletContext中,只是这个KEY不太一样了,这个KEY值为ContextLoaderPlugIn.SERVLET_CONTEXT_PREFIX+ModuleConfig.getPrefix()(具体请查看源代码),这下好了,我们知道了WebApplicationContext放在哪里,只要我们在Web应用中能够取到ServletContext也就能取到WebApplicationContext了:)

Struts2小结

天乙社区8.0(http://www.laoer.com)已经完全用Struts2实现,在使用Struts的过程中也遇到了一些问题,现在总结一下。

1、在web.xml中EncodingFilter的位置应该在Struts2的FilterDispatcher之前,道理很简单,要先调整字符集,再进入Action。

2、如果使用Urlrewrite,要指定filter-mapping的dispatcher方式,如下
<filter-mapping>
<filter-name>Struts2</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>

3、在做上传文件的时候,要在web.xml中增加ActionContextCleanUp这个filter,如果不增加,会发生第一次上传取不到文件的情况
<filter>
<filter-name>struts-cleanup</filter-name>
<filter-class>org.apache.struts2.dispatcher.ActionContextCleanUp</filter-class>
</filter>
<filter-mapping>
<filter-name>struts-cleanup</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
按照Struts2的API,filter的顺序是
struts-cleanup filter
SiteMesh filter
FilterDispatcher

4、在Apache+Resin的情况下,要在WEB-INF下增加resin-web.xml,该文件只针对Resin有效,作用是指定后缀与 Resin的Servlet引擎匹配,要不然从Apache转发过去的请求到Resin后会出现404的情况,resin-web.xml举例如下:
<web-app xmlns=”http://caucho.com/ns/resin”>
<servlet-mapping url-pattern=’*.bbscs’ servlet-name=’plugin_match’/>
</web-app>

5、在使用<s:url/>标签的时候,会出现将get或post数值带入url参数的情况,如果不需要这些参数,可以在struts.properties文件中设置
struts.url.includeParams=none
或是在<s:url/>标记中将includeParams属性设为none
另外还有两个值
all,是把get和post中的参数加入到url参数中
get,是只把get中的参数加入到url参数中

6、与webwork基本相同,Struts2提供了几种ui.theme,有xhtml、css_xhtml、simple等等,在 struts.properties中可以设置使用何种theme,这一点很关键,不同的theme,struts的tag会生成不同的html代码,而且在某些情况下这些theme不能满足页面要求,则需要自己进行扩展了,这些theme都是由freemarker写的,仿照这写就可以。

7、单个checkbox的标记库好像只能返回boolean的值,如果在数据库中设计为int型,则需要做一些转换,这一点我觉得不如Struts1.x的方便。

8、总体来说Struts2的标记库使用上比Struts1.x的方便,页面整体也比较简洁,Struts2采用stack的方式存取数据,与Struts1相比各有千秋吧。

Struts2主要延续自webwork,以前使用webwork的朋友转过来并不困难,Struts2的几个核心的部分,比如拦截器、Result Configuration、OGNL stack等等还是需要仔细的体会,深入了解,才能做出优秀的系统。

(这篇文章在JavaEye上发表过)