java

org.springframework.dao.IncorrectResultSizeDataAccessException

今天在写一个游戏接口的时候,以为数据库中就一条记录,想当然的写下了

select appid, code, name, url from hall_games where code=:code

测试机上一运行就出现这个异常。
org.springframework.dao.IncorrectResultSizeDataAccessException: Incorrect result size: expected 0 or 1, actual 8: com.xiaonei.in.dao.LoginUserDAO#getHallGameInfo

于是查看文档。
Data access exception thrown when a result was not of the expected size, for example when expecting a single row but getting 0 or more than 1 rows。
当期望返回的结果记录是1时,如果返回值为0或者>1会抛此异常。于是修改sql,当需要一条数据的时候,还是老实加上limit 1比较好。

select appid, code, name, url from hall_games where code=:code limit 1

HttpClient模拟操作https网站

在日常工作中,我们常常需要抓取一些网站的信息为自己所用,比如自动抓取天气预报啊,抓取sina的股票数据,某些图片网站的美图等等,这就需要spider出场了,其实每种语言都有自己的写法,比如在Linux下,第一眼想到的就是wget,这是个强大的工具,可以递归地抓取网站,有windows下的版本,在本文章中还是说下Java的方式,那就是著名的开源软件HttpClient,在使用HttpClient的时候,我们需要知道网站的运行方式,可能在中间跳转了好几次,为了看到这些信息,我们还需要一点工具,Firefox的话直接中Httpfox就可以了,在IE下可以使用Httpwatch,官方下载地址是这个http://www.httpwatch.com,具体的使用很简单,不会的可以参考这个:http://www.cnblogs.com/mayingbao/archive/2007/11/30/978530.html
完事具备了,我们来看看怎么使用Httpclient

public static HttpMethod loginXN() throws IOException {
                PostMethod post = new PostMethod("http://passport.renren.com/PLogin.do");
		NameValuePair user = new NameValuePair("email",
				"zhangsan@hotmail.com");
		NameValuePair pwd = new NameValuePair("password", "12345");
		NameValuePair domain = new NameValuePair("domain", "renren.com");
		post.setRequestBody(new NameValuePair[] { user, pwd, domain});
		return post;
}

如果是https的请求,略微麻烦一点,需要使用SSLProtocolSocketFactory,在new HttpClient实例的时候,需要指定一下。

public static String httpGet(String url,Header header) {
        Protocol myhttps = new Protocol("https", new SSLProtocolSocketFactory(), 443);
        String result = "";
        HttpClient httpClient = new HttpClient();
        GetMethod getMethod = new GetMethod(url);
        getMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
                new DefaultHttpMethodRetryHandler());
        int statusCode = 1000;//默认代码Connection refused
        try {
            httpClient.getHostConfiguration().setHost("passport.dudusp.com",443,myhttps);
            getMethod.addRequestHeader(header);            
            statusCode = httpClient.executeMethod(getMethod);//执行getMethod
            if (statusCode == HttpStatus.SC_OK) {
                result = getMethod.getResponseBodyAsString();
            }
        } catch (Exception e) {
            log.info(e.getMessage(), e);
        } finally {
            getMethod.releaseConnection(); //释放连接
        }
        return result;
    }

公司的passport验证原理

最近为了安全考虑,出了套验证机制。passport处理未登录用户的过程大致为:
1.用户访问应用服务器A
2.如果用户未登录A,自动重定向到passport服务器
3.passport验证登录成功后,返回验证票
4.应用服务器A使用验证票到passport服务器进行二次验证
5.passport二次验证成功后,返回用户名

根据上述处理过程,passport的使用方法为:
1.passport的请求地址为:passport.no.xxxx.com;
2.应用服务器请求passport页面时,需提供forward跳转参数;
3.passport接收请求,验证正确的情况下,返回验证票(ticket参数);
4.应用服务器二次验证的过程为:
1)构造HTTP REFERER请求头;
2)携带HTTP REFERER请求头,远程读取passport.no.xxxx.com/verify.php?t=$ticket(该$ticket为 passport返回的ticket)地址的内容;
3)如果passport验证成功,返回用户名,否则返回空串。

passport的使用示例如下(以MM系统为例http://mm.renren.com/index.php):
1.MM请求访问passport的地址:https://passport.no.xxxx.com/login.php?forward=http://mm.renren.com/index.php
2.passport验证成功后,将地址重定向到http://mm.renren.com/index.php/index.php?ticket=nscr3omh80rn367h5pu0dq76u0
3.MM系统处理passport验证票($ticket参数)的过程为:

   function auth_check_valid($ticket){
       if($ticket == "")
           return false;

       $opts=array(
           'http'=>array(
               'header'=>"Referer :".$_SERVER['REQUEST_URI']
           )
       );

       $context = stream_context_create($opts);//构造HTTP REFERER头   
       $url = "https://passport.no.xxxx.com/verify.php?t=".$ticket;
       $user_id = file_get_contents($url,false,$context);//二次验证,远程请求用户名       
       return $user_id;//返回登录用户名,需进行后续判断是否为空串
   }

在接下来的文章中,我们会进一步讲解如果使用程序模拟登录验证系统,该怎么处理。

Use jpype in python scripts

JPype is an effort to allow python programs full access to java class libraries. This is achieved not through re-implementing Python, as Jython/JPython has done, but rather through interfacing at the native level in both Virtual Machines.
JPype是一个高效的python库,它允许python程序和java类互通。它并非是一个重新实现的Python,就像Jython和JPython那样,而是通过在虚拟机native层面上实现。
Eventually, it should be possible to replace Java with python in many, though not all, situations. JSP, Servlets, RMI servers and IDE plugins are all good candidates.
实际上,它应该可以在许多的场景下使用python来替换Java,当然了并非所有的都能替换。像 JSP, Servlets, RMI servers and IDE plugins都是很好的替换对象。
—————————————–强行分割一下吧—————————————-
最近学习写一些用来处理日志log的python脚本,但是到连接到线上数据库的时候出现了点小小的麻烦,因为众所周知,renren的代码是java的,连接数据库都被java做了封装,不能显示的看到具体那台数据库【实际上开发者也无需关心,我们只需要一个配置文件,具体指向什么地方,后面是一台机器还是多台,都不在操心。】就想找到一个python能直接访问java程序的解决方案,翻来翻去就找到JPype了,它的源码好久没更新,不过最近更新了一下,最新的版本是0.5.4.1,可以通过这个链接下载
安装很简单,直接直接python setup.py install就可以了(安装过程中显示的warning可以不必理会)。用于测试是否安装成功的Hello World代码如下:

from jpype import *
startJVM(getDefaultJVMPath())
java.lang.System.out.println("hello world")
shutdownJVM()

运行结果如下:

[root@SJSWT45-26 wei]# python testp2j.py
hello world
JVM activity report     :
        classes loaded       : 30
JVM has been shutdown

2.  启动JVM

依靠startJVM这个函数来完成,一个使用的例子是这样的:

vmPath = jpype.getDefaultJVMPath()
jpype.startJVM(vmPath, "-Xms32m", "-Xmx256m", "-mx256m", "-Djava.class.path=/home/some-lib.jar:")

startJVM的第一个参数是JVM库所在的路径(和JAVA_HOME不是一回事儿),通常可以用jpype.getDefaultJVMPath()来自动获取系统默认JVM的路径。如果系统中安装了多个JDK,希望从中选择一个,则可以手动注明这个路径。比如Mac OSX下可以写成“/System/Library/Frameworks/JavaVM.framework/Libraries/libjvm_compat.dylib”

剩下的都是发送给JVM的启动参数,每个逗号见是一个参数。因为这里是不支持带空格的参数写法的,所以例子里特意把classpath参数写成了-Djava.class.path=…的形式。注意这里需要手工保证参数的正确性,jpype是不会对错误的参数给出提示的,它的反应很简单,就是在后面用到这个JVM的时候报一些怎么也想不明白的错误……所以,使用jpype遇到任何问题,首先检查传给startJVM的各参数正确性。

3.  如何调用一个Java函数

主要靠JPackage语句来实现,比如

Document = jpype.JPackage('org').w3c.dom.Document

可以把Java里面的org.w3c.dom.Document映射给Python里面的Document变量。
java和javax两个包不需要以这种方式来调用,直接类似jpype.java.lang.System.out.println()这样就可以了。

有时候我们会遇到类似TypeError: Package org.w3c.dom.Document is not Callable”这样的错误。通常这时用到的Java指令在jar里面,而这个jar没有被正确导入,所以JVM找不到它。也就是说,遇到这种错误时,要去检查startJVM函数中的-Djava.class.path=参数的设置,通常都是因为这里的路径写错了造成的。

4.  如果捕捉Java异常

可以在Python里使用 jpype.JavaException指代所有的Java异常,比如像下面这样:

import jpype
jpype.startJVM(jpype.getDefaultJVMPath())
try:
    jpype.java.lang.Integer("x1")
except jpype.JavaException, ex:
    print ex.javaClass(), ex.message()
    print ex.stacktrace()    
jpype.shutdownJVM()

如果要捕获特定的Java异常呢,则需要用到jpype.JException,比如像下面这样捕获的就是java.lang.NumberFormatException

import jpype
jpype.startJVM(jpype.getDefaultJVMPath())
try:
    jpype.java.lang.Integer("x1")
except jpype.JException(jpype.java.lang.NumberFormatException), ex:
    print ex.javaClass(), ex.message()
    print ex.stacktrace()    
jpype.shutdownJVM()

5.  如何处理Java的函数多态

Java里面是允许参数格式不同的多个同名函数的,Python里面则不允许。这样在通过jpype调用Java api里面的函数时,有时会因为参数的类型乱掉而报错。那么怎么能调用到Java里面的特定函数呢?没办法,做强制类型转换吧。

比如jpype.java.lang.System.out.println(1)实际会调用println(int),那么如果我们想调用println(byte)%,则可以写成jpype.java.lang.System.out.println(JByte(1))这样。。

6.  如何重启JVM

jpype提供的shutdownJVM()方法实际调用的是JNI接口的unload实现,但是Sun对unload的实现有点问题,造成的结果就是jpype调用shutdownJVM()以后就没法再startJVM()了(会报错)。什么?您问关掉JVM干嘛还要重新开启它,这个折腾个什么劲?答案很简单:因为有时在未知的黑暗角落隐藏着邪恶的源头——内存泄露。。

既然jpype没法重启JVM,那么只好把jpype放到processing里面来用,需要重启时,就杀掉当前进 程,重新启动一个新进程好了。。(processing安装很简单,Python 2.6官方发行版已经带了,之前的版本则可以easy_install processing)

下面给出一个processing下用jpype的例子:

import jpype
import processing

def java_loop(pipe, id):
    jpype.startJVM(jpype.getDefaultJVMPath())
    while True:
        jpype.java.lang.System.out.println(pipe.recv() + ' ' + id)
        pipe.send(None)

head1, head2 = processing.Pipe()
p = processing.Process(target = java_loop, args = [head2, '(JVM 1)'])
p.setDaemon(True)
p.start()
head1.send("Hello message from")
head1.recv()
p.terminate()

head1, head2 = processing.Pipe()
p = processing.Process(target = java_loop, args = [head2, '(JVM 2)'])
p.setDaemon(True)
p.start()
head1.send("Hello message from")
head1.recv()

这里用到的pipe.recv()要小心,一旦阻塞可能会与twisted之类的框架产生冲突。一个可能的解决办法是在recv()之前用pipe.poll()函数检测一下管道里面是否有待接收数据,如果没有就等一会重新poll()就是了。[poll()自称是非阻塞的,因为它只阻塞当前线程;而recv()则会阻塞当前进程,于是twisted就不干了]

很茫然也很兴奋

写代码时间久了,总是感觉接触面越来越窄,到现在估计只剩下Java还能写点东西了,PHP算作业余时间搞的,好在是弱语言类型的,每次都是现学现卖。一口气写下来,在本地debug也快,出现错误就地改正就是了,尝试过用框架,结果都是半途而废了,估计水平也就这样子了,自己搞点小玩意儿还是可以的,帮朋友做几个个人网站也行。
近半年来,总算是有点目标,学习还是马马虎虎的,最近公司的事情好多啊,只能见缝插针的学习点,感觉提高的也不快,任何事情总得动手才行。
淘宝客的一堆东西基本上快研究透彻了,想到了几个点子,准备抽时间给实现了。
剩下的学习任务很很重,搞下PHPRPC,丢掉的C++想尽可能的给捡回来,搞好英语。

学习memcached的安装和java通讯

memcache需要libevent进行网络的通讯才能正常工作,所以安装memcache之前需要首先安装libevent. libevent可以从这儿下载,memcache需要从google code下载。
现在万事具备,开始安装的基本过程,如下所示:

#yum install gcc  //确认gcc已经安装,可以gcc -v确认

#wget http://monkey.org/~provos/libevent-1.4.5-stable.tar.gz  //下载最新的稳定版本
#tar xvzf libevent-1.4.4-stable.tar.gz  //解压缩
#cd libevent-1.4.4-stable 
#./configure -prefix=/usr/local/libevent //开始编译
#make
#make install

接着安装memcache.

#cd /home/mysoft/
#tar -zxvf  memcached-1.4.5.tar.gz
#cd  memcached-1.5.0
#./configure --prefix=/usr/local/memcached --with-libevent=/usr/
#make #make install
#ls -al /usr/local/memcached/bin //验证安装

如果没有问题,现在就可以正常启动memcached了,命令如下:

/usr/local/memcached/bin/memcached -d -m 100 -u root -l 192.168.1.101 -p 11211 -c 256 -P /tmp/memcached.pid 

-d选项是启动一个守护进程,
-m是分配给Memcache使用的内存数量,单位是MB,我这里是100MB,
-u是运行Memcache的用户,我这里是root,
-l是监听的服务器IP地址,如果有多个地址的话,我这里指定了服务器的IP地址192.168.1.100,
-p是设置Memcache监听的端口,我这里设置了11211,最好是1024以上的端口,我们这里统一使用11211-c选项是最大运行的并发连接数,默认是1024,我这里设置了256,按照自己服务器的负载量来设定。
-P是设置保存Memcache的pid文件,我这里是保存在/tmp/memcached.pid
常见的启动失败信息有:
AA.启动时找不到libevent库
memcached: error while loading shared libraries: libevent-1.4.so.2: cannot open shared object file: No such file or directory

解决办法1:将libevent库所在路径加入LIBRARY_PATH,在/etc/profile中加入
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/libevent/lib

解决办法2:
ln -s /libevent安装路径/libevent/lib/libevent-1.3b.so.1 /usr/lib/libevent-1.3c.so.1
详细的处理过程如下:
>locate libevent-1.4.so.2
libevent-1.4.so: /usr/local/lib/libevent-1.4.so.2

> ldd /usr/local/bin/memcached
linux-gate.so.1 => (0xb7fa4000)
libevent-1.4.so.2 => not found
libpthread.so.0 => /lib/libpthread.so.0 (0x0086e000)
libc.so.6 => /lib/libc.so.6 (0x006f7000)
/lib/ld-linux.so.2 (0x006d9000)

> LD_DEBUG=libs ./memcached -v
找到默认路径 /usr/lib/
>ln -s /usr/local/lib/libevent-1.4.so.2 /usr/lib/libevent-1.4.so.2

>ldd /usr/local/bin/memcached
linux-gate.so.1 => (0xb7ffd000)
libevent-1.4.so.2 => /usr/lib/libevent-1.4.so.2 (0xb7fdc000)
libpthread.so.0 => /lib/libpthread.so.0 (0x0086e000)
libc.so.6 => /lib/libc.so.6 (0x006f7000)
libnsl.so.1 => /lib/libnsl.so.1 (0x009f8000)
librt.so.1 => /lib/librt.so.1 (0×00887000)
libresolv.so.2 => /lib/libresolv.so.2 (0x00b29000)
/lib/ld-linux.so.2 (0x006d9000)
收工
AB.第二个可能的问题是编译memcache期间出现的,解决方案可以参考这儿
或者 在./configure 时加入参数–build=i686-pc-linux-gnu
AC.如果客户端连接不上,可能是服务器防火墙的原因,选择关闭或者增加相应的iptables文件如下:
#永久性生效,重启后不会复原
开启: chkconfig iptables on
关闭: chkconfig iptables off
#即时生效,重启后复原
开启: service iptables start
关闭: service iptables stop
#修改/etc/sysconfig/iptables文件,
添加-A RH-Firewall-1-INPUT -m state –state NEW -m tcp -p tcp –dport 11211 -j ACCEPT

使用的方式可以有很多种,常见的语言api通过官方网站都能找到,比如php的参考这儿,需要memcached插件
Java的可以选择这个spymemcached或者java memcached,其他语言的可以从这个列表中查找。

commons lang组件之自动生成toString()内容

问题

希望能自动生成toString()方法

解决方案

使用Commons Lang的ReflectionToStringBuilder或ToStringBuiler,配合ToStringBuilder可生成toString()方法
.下面的代码显示了如何使用反射生成器(reflection builder)生成toString()方法.

import org.apache.commons.lang.builder.ReflectionToStringBuilder;

public String toString(){
return ReflectionToStringBuilder.toString(this);
}

让toString()的内容与不断变化的类保持一致,是一项让人心烦又容易遗忘的工作.Commons Lang带有一个非常易用的工具类,
通过反射自动完成这件麻烦事.ToStringBuilder类及其派生类ReflectionToStringBuilder能把原本臃肿的toString()方法
浓缩成一行.更重要的是ReflectionToStringBuilder反映了对象模型未来的变化趋势.

在有限的时间和预算条件下,面对含有上百个实体的对象模型,要保证toString()方法能实时更新几乎就是天方夜谭.如果你的类
拥有含义明确的toString()方法,在诊断程序时将会受益非浅.通过使用ReflectionToStringBuilder类,能确保输出是正确的.
依靠开发者手动维护toString()方法会很不可靠.

native2ascii工具使用

在做Java开发的时候,常常会出现一些乱码,或者无法正确识别或读取的文件,比如常见的validator验证用的消息资源(properties)文件就需要进行Unicode重新编码。原因是java默认的编码方式为Unicode,而我们的计算机系统编码常常是GBK等编码。需要将系统的编码转换为 java正确识别的编码问题就解决了。

1、native2ascii简介:
native2ascii是sun java sdk提供的一个工具。用来将别的文本类文件(比如*.txt,*.ini,*.properties,*.java等等)编码转为Unicode编码。为什么要进行转码,原因在于程序的国际化。Unicode编码的定义:Unicode(统一码、万国码、单一码)是一种在计算机上使用的字符编码。它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。1990年开始研发,1994年正式公布。随着计算机工作能力的增强,Unicode也在面世以来的十多年里得到普及。
2、获取native2ascii:
安装了jdk后,假如你是在windows上安装,那么在jdk的安装目录下,会有一个bin目录,其中native2ascii.exe正是。

3、native2ascii的命令行的命名格式:
native2ascii -[options] [inputfile [outputfile]]

说明:
-[options]:表示命令开关,有两个选项可供选择
-reverse:将Unicode编码转为本地或者指定编码,不指定编码情况下,将转为本地编码。
-encoding encoding_name:转换为指定编码,encoding_name为编码名称。

[inputfile [outputfile]] inputfile:表示输入文件全名。
outputfile:输出文件名。如果缺少此参数,将输出到控制台。

4、最佳实践: More