Java出现Invalid XPath expression:的一种可能原因

相信dom4j是很多Java程序猿必备的jar包,最近跟某邮件系统集成时,使用dom4j读取对方的atom文件,无奈怎么调试系统都在报错“Invalid XPath expression:”,而我也很确定XPath表达式没有出错,经过查找资料及比对,发现造成这个问题的原因是少了一个Jar包——jaxen-1.1-beta-6.jar

加入此Jar包后,问题解决。

 

关于Java中redirect与forward的区别

今天遇到了一个超级郁闷的问题,在公司的门户上做了二开,理想的效果是用户通过我给定的URI提交数据之后,若数据正确跳转到首页,若错误则跳转到登录页。修改完成之后部署上,发现无论用户给出的凭据是正确还是错误都往错误页上跳转。
调试过程中我发现,用户输入正确的凭据以后系统确实往主页跳转了,但是最后的结果却是又返回到了登录页。仔细查看代码后发现,我在往主页跳转的时候,错误的使用了forward,造成不能将用户本地的cookie等凭证带到主页,令系统以为我已经退出了,故又返回了登录页。
关于redirect与forwad的区别,找到一篇博文,见下:(转载自:http://blog.csdn.net/tenor/article/details/4077079) 继续阅读“关于Java中redirect与forward的区别”

设计模式学习笔记之职责链模式

今天开始打算将设计模式相关的内容进行一下梳理,同时将一些Demo记录于此。

在开始今天的学习笔记之前,我想先讲这样一个故事:某公司到了一年一度调薪的日子,对于调薪的受理由不同的职位的领导进行处理。部门经理可以直接受理1000以内的调薪,总经理可以直接受理2000以内的调薪,董事长可以受理5000以内的调薪。目前,有三位童鞋申请调薪,他们分别是 Kaisir:3000 , Zhang:5000 , liu:1500 , X-Man:10000,如果使用程序编写,我们该如何处理这种情况呢?

在这种情况下,我们的请求沿着 “部门经理 -> 总经理 -> 董事长” 的顺序依次传递,每一级受理他们权限之内的请求,若超出权限,则继续向上级申请。除了调薪之外,请假,调薪也跟此情景类似,于是对于此种情景,我们可以使用职责链模式进行处理。

任何事物都有正反两面,这个职责链模式的优缺点在哪里呢?
优点:链上的每个节点都是彼此独立的,节点无需知道整个链条的内容,只需要知道他的后继节点即可,这样很大程度降低了类与类之间的耦合关系。此外可以很自由的增加新的处理节点,无需修改之前的节点,符合开放-封闭原则。

缺点:除目标链之外,其他的节点仅仅起到传递的作用,若链过长的话,系统将被传递用的节点占掉很多的资源。

具体事例代码如下:
抽象类 Manager

[java]

package com.kaisir.designpattern.chain;

import com.kaisir.designpattern.chain.model.Request;

/**
* Created with IntelliJ IDEA.
* User: Kaisir
* Date: 13-3-23
* Time: 下午2:32
* To change this template use File | Settings | File Templates.
*/
public abstract class Manager {
private Manager nextchain;

public Manager getNextchain() {
return nextchain;
}

public void setNextchain(Manager nextchain) {
this.nextchain = nextchain;
}

public abstract void doRequest(Request request);
}

[/java]

继续阅读“设计模式学习笔记之职责链模式”

记一则马虎引起的日期错误

Error

周末忙着做毕设,其中有从数据库中取出时间,然后进行了下格式化,结果发现格式化后的日期如上图,显然月份数据是不对的,怎么可能出来30月…

查阅代码,一开始竟也没有发现错误:

   1: public void setCreatetime(Date createtime) {

   2:     SimpleDateFormat df=new SimpleDateFormat("yyyy-mm-dd hh:mm:ss");

   3:     this.createtime = df.format(createtime).toString();

   4: }

看了以上代码,我想细心的同学已经发现了,月份的掩码应该是MM,而不是mm,哈哈 看来英雄难过MM关啊!(MM=网络用语,美眉)

 

附录:

常见的时间掩码

y 年 Year 1996; 96
M 年中的月份 Month July; Jul; 07
w 年中的周数 Number 27
W 月份中的周数 Number 2
D 年中的天数 Number 189
d 月份中的天数 Number 10
F 月份中的星期 Number 2
E 星期中的天数 Text Tuesday; Tue
a Am/pm 标记 Text PM
H 一天中的小时数(0-23) Number 0
k 一天中的小时数(1-24) Number 24
K am/pm 中的小时数(0-11) Number 0
h am/pm 中的小时数(1-12) Number 12
m 小时中的分钟数 Number 30
s 分钟中的秒数 Number 55
S 毫秒数 Number 978
z 时区 General time zone Pacific Standard Time; PST; GMT-08:00
Z 时区 RFC 822 time zone -0800

.Net与Java时间处理上的一点区别

这两天在做与某知名一卡通公司的单点登录集成(我们的产品使用Java编写,对方的程序使用.Net编写)其中有一项参数是时间戳。即当前时间相对于公元1970-1-1 00:00:00的秒数。按照他们给出的文档我完成了Java部分的编写,时间戳部分很简单,一句话:

[codesyntax lang=”php”]

    private String createTimestamp() //timestamp
    {
        return System.currentTimeMillis() / 1000 + "";
    }

[/codesyntax]

使用System.currentTimeMillis()方法就能得到自1970年1月1日0时0分0秒的毫秒数,除以1000自然是秒数。但是意外却发生了,我产生的时间戳跟他们公司产生的时间戳差了一大截,查阅资料后发现.Net跟Java在处理时间上有出入。

.Net产生的时间都是当前时区的当前时间,而Java的currentTimeMillis()方法得到的却是相对于GMT来的时间。中国所在时区是+8区,故时间整整差了8小时!

所以当童鞋们再遇到.Net跟Java时间不一致的时候,多往时区方面考虑考虑,说不定就能找到解决办法了 :)

当然我会给出解决方案:

方法一:JVM运行时增加参数,指定时区 -D user.timezone=GMT+08

方法二:直接在程序中设置时区。System.setProperty(“user.timezone”,”GMT +08″);

方法三:直接加上28800就好了(8hours*60min*60sec=28800)

Struts2学习笔记之(二)——验证及国际化

昨天通过一个简单的实例程序完成了Struts2的基本功能,但是昨天的那个程序那是相当的丑陋哇,前台部分代码量多不说,还都没加验证,输入用户名密码的地方留空点提交他也木有提示直接验证了,哎,作为一个程序员,怎么能忍受这样滴代码呢?而且现在软件国际化趋势明显,你的程序仅仅支持简体中文出门你都不好意思跟别的程序员打招呼,要是支持上英语、法语、韩语、日语、阿拉伯语、土耳其语……¥#&……*你瞧,倍儿有面子吧!

好啦,依然废话不多说,我们用代码说话。我们将昨天的登录页(index.jsp)使用Struts2中的标签进行改写,改写后的结果如下:

login.jsp

   1: <%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>

   2: <%@ taglib prefix="s" uri="/struts-tags"  %>

   3:  

   4: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

   5: <html>

   6:   <head>

   7:     <title>My JSP 'login.jsp' starting page</title>

   8:   </head>

   9:   

  10:   <body>

  11:       <s:form action="Login">

  12:           <s:textfield name="username" label="用户名"></s:textfield>

  13:           <s:textfield name="password" label="密码"></s:textfield>

  14:           <s:submit label="提交"></s:submit>

  15:       </s:form>

  16:   </body>

  17: </html>

大家可以看出,无非就是引入了一个taglib,并将表单啊,文本域啊按钮啊全部改写成了Struts2中的标签,怎么样,是不是简洁了不少?嘿嘿,这里只体现出了标签的一个优势,接下来,我们给昨天的登录页增加一个验证功能——若用户输入为空,则给出错误提示,禁止用户提交。在Struts2中,我们有两种方法来实现这种功能,1)实现validate()方法 2)编写验证脚本(*-validation.xml)。要注意的是,无论使用哪种验证方法,我们都必须让我们编写的Action基础ActionSupport父类对象。不然验证会出错的哟~~

在这里,我们着重说下方法2,毕竟这是我们在实际项目中会常常用到的方法,而且因为其验证字符串写于配置文件中,想要修改验证规则的时候,仅仅需要修改配置文件中的规则即可。

LoginAction-validation.xml

   1: <?xml version="1.0" encoding="UTF-8"?>

   2: <!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.2//EN"

   3: "http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd">

   4: <validators>

   5:     <field name="username">

   6:         <field-validator type="requiredstring">

   7:             <message key="user.required"/>

   8:         </field-validator>

   9:     </field>

  10:     <field name="password">

  11:         <field-validator type="requiredstring">

  12:             <message key="password.required"/>

  13:         </field-validator>

  14:     </field>

  15: </validators>

怎么样,这个xml结构是不是非常清晰易懂?使用<filed>段标识我们需要进行验证的字段,使用<filed-validator>中的type字段指定我们需要进行何种验证,而<message>部分则定义了出错时如何显示信息。这里要注意的是,当你使用了Struts2中的验证器之后,你需要给Action增加一个input的映射,此映射应该指向我们最初提交动作发生的页面,如实例中的Login这个Action,我们需要将input指向我们的login.jsp页面。修改后的struts.xml文件如下:

   1: <?xml version="1.0" encoding="UTF-8"?>

   2: <!DOCTYPE struts PUBLIC

   3:     "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"

   4:     "http://struts.apache.org/dtds/struts-2.0.dtd">

   5: <struts>

   6:     <package name="struts2" extends="struts-default">

   7:         <action name="Login" class="com.kaisir.www.struts2demo.LoginAction">

   8:             <result name="success">/welcome.jsp</result>

   9:             <result name="error">/error.jsp</result>

  10:             <result name="input">/login.jsp</result>

  11:         </action>

  12:     </package>

  13: </struts>

此时我们部署项目,并进行测试,会发现若不填用户名、密码,则系统会给出相应的提示。

小贴士:
当初实现验证的时候我以为不继承ActionSupport即可,但是发现不行,系统会报如下错误:
“严重: Validation error for password:password.required”
所以当你出现类似的错误时,应先检查是否忘记继承ActionSupport类。

说完了验证部分,我们来侃侃国际化部分,Struts2中对国际化的支持几乎是自动的,他会自动的检索你浏览器的首选语言,并按照首选语言来读取相应的语言文件。不说废话,上代码。

首先我们需要完成语言文件,在这里我以中文(zh_CN)及英文(en_US)作为实例语言,其语言文件如下:

messageResource_zh_CN.properties:

loginPage=u767Bu5F55u9875u9762

errorPage=u9519u8BEFu9875u9762

successPage=u767Bu5F55u6210u529F

failTip=u5BF9u4E0Du8D77uFF0Cu4F60u4E0Du80FDu767Bu5F55uFF01

successTip=u6B22u8FCE,{0},u4F60u5DF2u6210u529Fu767Bu5F55u3002

user.required=u7528u6237u540Du5FC5u586B

password.required=u5BC6u7801u5FC5u987Bu586Bu5199

username=u7528u6237u540D

password=u5BC6u7801

在这里,要注意的是,我使用了myeclipse编写配置文件,故他自动的将中文转义了,若是手动编写,需要手工调用native2ascii.exe文件来完成转义工作。

messageResource_en_US.properties

loginPage=LoginPage

errorPage=ErrorPage

successPage=SuccessPage

failTip=Sorry,You can't login in!

successTip=Welcome,{0},you has logged in!

user.required=Require Username!

password.required=Require Password!

username=Username

password=Password

光有语言文件还不够,我们还需要创建一个struts.properties文件来告诉Struts2我们有语言文件可用。

struts.properties

struts.custom.i18n.resources=messageResource

最后剩下的就是改写我们的jsp文件,让他调用语言文件了,主要使用<s:text name=””>这个标签~

login.jsp

<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>

<%@ taglib prefix="s" uri="/struts-tags"  %>

 

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>

  <head>

    <title><s:text name="loginPage"/></title>

  </head>

  

  <body>

      <s:form action="Login">

          <s:textfield name="username" label="%{getText('username')}"></s:textfield>

          <s:textfield name="password" label="%{getText('password')}"></s:textfield>

          <s:submit label="提交"></s:submit>

      </s:form>

  </body>

</html>

要注意的是,在这里Struts2中不允许标签的嵌套,所以想要在textfield标签中嵌套国际化支持的标签是不行了,但是我们可以变通一下,毕竟国际化的字符串就像全局的资源,故可以使用”%{getText(‘name’)}”这样的形式来调用国际化文件 :)

Ok,部署到Web容器中,看看怎么样?

Struts2 学习笔记之(一)——Hello Struts2

之前自学了Struts1,感觉用起来很方便,而对于Struts2的学习更多的是“赶鸭子上架”型学习,被项目逼着仓促的学了些,现在终于等到放假,打算写一系列日志用以总结归纳,同时留作学习笔记。本次自学我使用的参考书籍为《Struts2权威指南》及《struts-2-in-action》,有兴趣的童鞋可以下载下来详细阅读。

Ok,废话不多说,我们开始。

Struts2虽然跟Struts1就差一个数字,但是这之间是没什么联系的哈,如果想不通的可以参照王凯&王八,同样都是两个字,但是相差了一个字,这意思可就相差十万八千里咯~!Struts2更接近WebWork,是从WebWork扩展而来的。虽然这三个构架差别蛮大,但是他们都实现了共同的一个东西——MVC模型,具体的说应该是MVC2模型哈,详细的内容及区别大家可以参照我给出的参考书籍:)

想要配置Struts2非常简单, 这里我使用一个用户登录的例子来做演示。首先呢,我们需要一个让用户输入用户名密码的页面(index.jsp),以及一个登录成功的转入页(welcome.jsp)外加一个登录失败的提示页(error.jsp)。

index.jsp

 1: <%@page language="java" contentType="text/html; charset=UTF-8"%>
 2: <html>
 3: <head>
 4:     <title>User Login</title>
 5: </head>
 6: <body>
 7: <form action="Login.action" method="post">
 8:     <table>
 9:         <tr>
 10:             <td>用户登录</td>
 11:         </tr>
 12:         <tr>
 13:             <td>用户名:</td>
 14:             <td><input name="username" type="text"/></td>
 15:         </tr>
 16:         <tr>
 17:             <td>密码:</td>
 18:             <td><input name="password" type=password /></td>
 19:         </tr>
 20:         <tr>
 21:             <td><input type="submit" value="登录"/></td>
 22:         </tr>
 23:     </table>
 24: </form>
 25: </body>
 26: </html>

做好了基础的页面之后,我们需要写一个action用以处理用户的请求,这个action就是一个非常普通的POJO对象,他甚至不需要继承任何父类,也不需要实现任何接口,这为我们的测试以及重用,提供了非常大的便利。

LoginAction.Java

 1: package com.kaisir.www.struts2demo;
 2:
 3: public class LoginAction {
 4:     private String username;
 5:     private String password;
 6:     public String getUsername() {
 7:         return username;
 8:     }
 9:     public void setUsername(String username) {
 10:         this.username = username;
 11:     }
 12:     public String getPassword() {
 13:         return password;
 14:     }
 15:     public void setPassword(String password) {
 16:         this.password = password;
 17:     }
 18:
 19:     public String execute() throws Exception{
 20:         if (username.equals("Kaisir")&&password.equals("123456")) {
 21:             return "success";
 22:         }else{
 23:             return "error";
 24:         }
 25:     }
 26: }

可以看出,我们的Action中有两个字段,分别对应我们index.jsp中的两个文本框username及password。提交页面之后,系统会自动的获取我们这两个字段的值,并使用set方法填入到对应的变量之中。这样,我们在JavaBean中便获得了用户表单中输入的内容。

完成了Action之后,我们需要编写一个struts.xml文件,让系统知道我们当前都有哪些Action可用,以及根据Action的执行结果可以跳转到哪些View中进行结果的显示。

struts.xml

 1: <?xml version="1.0" encoding="UTF-8"?>
 2: <!DOCTYPE struts PUBLIC
 3:     "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
 4:     "http://struts.apache.org/dtds/struts-2.0.dtd">
 5: <struts>
 6:     <package name="struts2" extends="struts-default">
 7:         <action name="Login" class="com.kaisir.www.struts2demo.LoginAction">
 8:             <result name="success">/welcome.jsp</result>
 9:             <result name="error">/error.jsp</result>
 10:         </action>
 11:     </package>
 12: </struts>

在这个struts.xml文件中,我定义了一个action名为Login,他产生了两个输出跳转,一个跳转到了welcome.jsp,一个跳转到了error.jsp

最后,我们需要修改web.xml,以过滤器的形式加载Struts2。

 1: <?xml version="1.0" encoding="UTF-8"?>
 2: <web-app version="3.0"
 3:     xmlns="http://java.sun.com/xml/ns/javaee"
 4:     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 5:     xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
 6:     http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
 7:   <display-name></display-name>
 8:   <welcome-file-list>
 9:     <welcome-file>index.jsp</welcome-file>
 10:   </welcome-file-list>
 11:   <filter>
 12:       <!-- 定义核心Filter的名字 -->
 13:       <filter-name>Struts2</filter-name>
 14:       <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
 15:   </filter>
 16:   <filter-mapping>
 17:       <filter-name>Struts2</filter-name>
 18:       <url-pattern>/*</url-pattern>
 19:   </filter-mapping>
 20: </web-app>

要注意的是,在2.xx后期的版本中,这个过滤器的类已经变成了“org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter”如果还按照以前的过滤器来,当你部署到tomcat中时会出现莫名其妙的500错误。

当然,拷贝必要的类库也是必要的~我从Struts2自带的例子中提取了一份类库列表,应该是最简的列表了,即要实现最基本的Struts2必须引入下列jar包。

 1: lib目录下必须存在的包
 2: asm-3.3.jar
 3: asm-commons-3.3.jar
 4: asm-tree-3.3.jar
 5: commons-fileupload-1.2.2.jar
 6: commons-io-2.0.1.jar
 7: commons-lang-2.5.jar
 8: freemarker-2.3.18.jar
 9: javassist-3.11.0.GA.jar
 10: ognl-3.0.3.jar
 11: struts2-core-2.3.1.jar
 12: xwork-core-2.3.1.jar

好了,至此,将项目部署到你的tomcat,输入用户名Kaisir,密码123456,看看有没有成功跳转到Welcome页面吧 :)大家有任何问题欢迎与我交流,Webmaster#Kaisir.Com ,借用一句广告语“大家好,才是真的好!”

记一个很奇怪的火狐验证码问题(未解决)

今天闲来无事打算封装一个Java的验证码类,实现基本功能很容易,可是测试的时候却发现了一个奇怪的问题,而且暂时还未找到答案。要想说清楚这个问题,首先给大家看一张图片,请大家注意图中标注1,2,3处的验证码。

checkcode

问题描述:

我在java中写了一个servlet实现构造一个验证码,servlet负责返回java生成的一张图片(其实是jpeg的流),使用IE直接浏览这个生成地址正常,但是使用火狐浏览时却发现显示出来的验证码跟实际的验证码是不同的。后来我使用了firebug对访问情况进行跟踪,得到了上面的结果:

1处是用户看到的验证码;2处是当前实际的验证码;3处是系统生成的验证码。

这就好像火狐在请求完图像之后,又重新请求了一遍一样,但是使用FireBug却仅仅抓到了第二次请求。而且更奇怪的是,我将这个地址嵌入到<img>标签中,却又变得正常了,看到的跟实际请求的验证码又是相同的了。实在搞不清楚这是怎么回事了……

后续打算:

打算使用WireShark抓包试试看,看看是不是发起了两次请求。这个问题暂时无解,欢迎大家广泛讨论。

关于Hibernate出现illegal attempt to dereference collection..的错误处理

今天使用Hibernate查询某个实体时出现标题中所示的错误,百思不得其解,查阅资料后发现,这个问题是因为Hibernate的版本问题造成的,具体原因见这里

在我的项目中,两个实体类AppUser与AppRole,AppUser类中有一个List<AppRole>d的属性用来存放此用户的角色。我现在想要做的是通过AppRole的rname查询出这个权限下所有的AppRole。我定义了命名查询:

[codesyntax lang=”xml”]

  <query name="UserDao.findByRoleName">
        <![CDATA[
         select u
         from com.kaisir.tms.pojo.AppUser as u
         where u.AppRoles.rname=:rname
        ]]>
  </query>

[/codesyntax]

可是查询出现了标题中的错误,查阅资料,后期版本的Hibernate必须“显式”的指定子查询,不会再自动的添加隐含的查询了,故将定义修改如下:

[codesyntax lang=”xml”]

  <query name="UserDao.findByRoleName">
        <![CDATA[
         select u
         from com.kaisir.tms.pojo.AppUser as u inner join fetch u.appRoles r
         where r.rname=:rname
        ]]>
  </query>

[/codesyntax]

这样我显式的告诉Hibernate先去查询AppUser中的appRoles属性,然后再从属性中查询其rname为指定值的AppUser对象,这样问题就解决了 :)

Parameter index out of range (1 > number of parameters, which is 0).

今天调数据库课程设计(Java+MySql)的时候通过Tomcat测试的时候总是抛出“Parameter index out of range (1 > number of parameters, which is 0).”的异常,系统提示出错的行是如下标示“抛出异常”的行。

[codesyntax lang=”java” lines=”normal”]

public List<Book> searchBorrowed(String id) throws Exception {
	List<Book> allBooks = new ArrayList<Book>();
	PreparedStatement pstmt = null;
	ResultSet rs = null;
	String sql = "select b.name as name,b.author as author,b.category as category,borr.btime as btime,borr.rtime as rtime from borrowed borr,books b where borr.bid=b.id and isreturn=0 and pid=?";
	pstmt = conn.prepareStatement(sql);
	pstmt.setString(1, id);//抛出异常!
	rs = pstmt.executeQuery();
	while (rs.next()) {
		Book book = new Book();
		book.setName(rs.getString("name"));
		book.setAuthor(rs.getString("author"));
		book.setCategory(rs.getString("category"));
		book.setBtime(Long.getLong(rs.getString("btime")));
		book.setRtime(Long.getLong(rs.getString("rtime")));
		allBooks.add(book);
	}
	return allBooks;
}

[/codesyntax]

 

经过多次测试,甚至编写了一个测试用的类直接本地运行调用这个函数却没有出错,Google搜索了下,大家出现这个问题大部分都是ParparedStatement的用法有问题或者Sql语句写的有问题,查了很久,也没找到原因,而最后看到 这里 最后一段话,令我恍然大悟:

[codesyntax lang=”text”]

Please forget about it. I found why the error was occurring.
I was actually using one single PreparedStatement for two different
methods (silly me), and when the flow returned from the called method,
it still was the first preparedstatement rather than the new one,
hence it couldn't found the parameter in the proper indexed position.

[/codesyntax]

 

大体意思是说他使用同一个pstmt执行了两条查询以后出现了这个问题,仔细查看源代码,发现我使用了数据库连接之后没有释放,再次调用的时候,系统就没有再实例化新的pstmt而是直接把刚才用过的这个实例返回给我了,而那个测试用的类没有出现这个问题是因为他仅仅执行了一个SQL查询就退出了,不像我实际中使用的前面还执行了一个登录的过程。知道了问题所在解决起来也便简单的多了,使用代理模式重构代码,通过代理来访问这个具体的数据库操作,从代理中来对数据库的连接与关闭进行处理,这样即更好的解耦,又使得我不用去关注数据库的连接与关闭操作,直接交给代理完成就可以了 :)