Apache Struts常见异常信息和解决方法

file

以下所说的struts-config.xml和ApplicationResources.properties等文件名是缺省时使用的,如果你使用了多模块,或指定了不同的资源文件名称,这些名字要做相应的修改。

1、“No bean found under attribute key XXX”
在struts-config.xml里定义了一个ActionForm,但type属性指定的类不存在,type属性的值应该是Form类的全名。或者是,在Action的定义中,name或attribute属性指定的ActionForm不存在。

2、“Cannot find bean XXX in any scope”
在Action里一般会request.setAttribute()一些对象,然后在转向的jsp文件里(用tag或request.getAttribute()方法)得到这些对象并显示出来。这个异常是说jsp要得到一个对象,但前面的Action里并没有将对象设置到request(也可以是session、servletContext)里。
可能是名字错了,请检查jsp里的tag的一般是name属性,或getAttribute()方法的参数值;或者是Action逻辑有问题没有执行setAttribute()方法就先转向了。
还有另外一个可能,纯粹是jsp文件的问题,例如<logic:iterate>会指定一个id值,然后在循环里<bean:write>使用这个值作为name的值,如果这两个值不同,也会出现此异常。(都是一个道理,request里没有对应的对象。)

3、“Missing message for key "XXX"”
缺少所需的资源,检查ApplicationResources.properties文件里是否有jsp文件里需要的资源,例如:

<bean:message key="msg.name.prompt"/>

这行代码会找msg.name.prompt资源,如果AppliationResources.properties里没有这个资源就会出现本异常。在使用多模块时,要注意在模块的struts-config-xxx.xml里指定要使用的资源文件名称,否则当然什么资源也找不到,这也是一个很容易犯的错误。

4、“No getter method for property XXX of bean teacher”
这条异常信息说得很明白,jsp里要取一个bean的属性出来,但这个bean并没有这个属性。你应该检查jsp中某个标签的property属性的值。例如下面代码中的cade应该改为code才对:

<bean:write name="teacher" property="cade" filter="true"/>

5、“Cannot find ActionMappings or ActionFormBeans collection”
待解决。

6、“Cannot retrieve mapping for action XXX”
在.jsp的<form>标签里指定action='/XXX',但这个Action并未在struts-config.xml里设置过。

7、HTTP Status 404 - /xxx/xxx.jsp
Forward的path属性指向的jsp页面不存在,请检查路径和模块,对于同一模块中的Action转向,path中不应包含模块名;模块间转向,记住使用contextRelative="true"

8、没有任何异常信息,显示空白页面
可能是Action里使用的forward与struts-config.xml里定义的forward名称不匹配。

9、“The element type "XXX" must be terminated by the matching end-tag "XXX".”
这个是struts-config.xml文件的格式错误,仔细检查它是否是良构的xml文件,关于xml文件的格式这里就不赘述了。

10、“Servlet.init() for servlet action threw exception”
一般出现这种异常在后面会显示一个关于ActionServlet的异常堆栈信息,其中指出了异常具体出现在代码的哪一行。我曾经遇到的一次提示如下:

java.lang.NullPointerException
    at org.apache.struts.action.ActionServlet.parseModuleConfigFile(ActionServlet.java:1003)
    at org.apache.struts.action.ActionServlet.initModuleConfig(ActionServlet.java:955)

为解决问题,先下载struts的源码包,然后在ActionServlet.java的第1003行插入断点,并对各变量进行监视。很丢人,我竟然把struts-config.xml文件弄丢了,因此出现了上面的异常,应该是和CVS同步时不小心删除的。

11、“Resources not defined for Validator”
这个是利用Validator插件做验证时可能出现的异常,这时你要检查validation.xml文件,看里面使用的资源是否确实有定义,form的名称是否正确,等等。

上面这些是我在用Struts做项目时遇到过的问题,其中一些曾困绕我不少时间,其实大部分都是自己不细心造成的。希望这篇文章能对你的开发有所帮助,并欢迎继续补充。

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2004/08/02/29566.html

利用BeanUtils在对象间复制属性

commons-beanutils是jakarta commons子项目中的一个软件包,其主要目的是利用反射机制对JavaBean的属性进行处理。我们知道,一个JavaBean通常包含了大量的属性,很多情况下,对JavaBean的处理导致大量get/set代码堆积,增加了代码长度和阅读代码的难度(什么,你的薪水按代码行数计算?那千万别让老板看到此帖哦)

file

BeanUtils是这个包里比较常用的一个工具类,这里只介绍它的copyProperties()方法。该方法定义如下:

public static void copyProperties(java.lang.Object dest,java.lang.Object orig)
  throws java.lang.IllegalAccessException,
         java.lang.reflect.InvocationTargetException

如果你有两个具有很多相同属性的JavaBean,一个很常见的情况就是Struts里的PO对象(持久对象)和对应的ActionForm,例如Teacher和TeacherForm。我们一般会在Action里从ActionForm构造一个PO对象,传统的方式是使用类似下面的语句对属性逐个赋值:

//得到TeacherForm
TeacherForm teacherForm=(TeacherForm)form;
//构造Teacher对象
Teacher teacher=new Teacher();
//赋值
teacher.setName(teacherForm.getName());
teacher.setAge(teacherForm.getAge());
teacher.setGender(teacherForm.getGender());
teacher.setMajor(teacherForm.getMajor());
teacher.setDepartment(teacherForm.getDepartment());

//持久化Teacher对象到数据库
HibernateDAO=;
HibernateDAO.save(teacher);

而使用BeanUtils后,代码就大大改观了,如下所示:

//得到TeacherForm
TeacherForm teacherForm=(TeacherForm)form;
//构造Teacher对象
Teacher teacher=new Teacher();
//赋值
BeanUtils.copyProperties(teacher,teacherForm);
//持久化Teacher对象到数据库
HibernateDAO=;
HibernateDAO.save(teacher);

如果Teacher和TeacherForm间存在名称不相同的属性,则BeanUtils不对这些属性进行处理,需要程序员手动处理。例如Teacher包含modifyDate(该属性记录最后修改日期,不需要用户在界面中输入)属性而TeacherForm无此属性,那么在上面代码的copyProperties()后还要加上一句:

teacher.setModifyDate(new Date());

怎么样,很方便吧!除BeanUtils外还有一个名为PropertyUtils的工具类,它也提供copyProperties()方法,作用与BeanUtils的同名方法十分相似,主要的区别在于后者提供类型转换功能,即发现两个JavaBean的同名属性为不同类型时,在支持的数据类型范围内进行转换,而前者不支持这个功能,但是速度会更快一些!

BeanUtils支持的转换类型如下:

  • java.lang.BigDecimal
  • java.lang.BigInteger
  • boolean and java.lang.Boolean
  • byte and java.lang.Byte
  • char and java.lang.Character
  • java.lang.Class
  • double and java.lang.Double
  • float and java.lang.Float
  • int and java.lang.Integer
  • long and java.lang.Long
  • short and java.lang.Short
  • java.lang.String
  • java.sql.Date
  • java.sql.Time
  • java.sql.Timestamp

这里要注意一点,java.util.Date是不被支持的,而它的子类java.sql.Date是被支持的。因此如果对象包含时间类型的属性,且希望被转换的时候,一定要使用java.sql.Date类型。否则在转换时会提示argument mistype异常。

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2004/07/30/28607.html

[测试]将TestCase整合

上一篇贴子里,我简单介绍了如何写一个TestCase(MockStrutsTestCase是TestCase的一个子类),可以看到是十分简单的,基本上只要写一些testXXX方法就可以运行了。当我们选择运行这个TestCase的时候,实际上运行的是一个Test,Test是TestCase的接口,实现这个接口的还有TestSuite类,使用这个类可以把多个TestCase一起运行,从而更加自动化。

要写一个TestSuite更加简单,看一下下面的代码就明白了:

package edu.pku.cc.democenter.test;

import junit.framework.Test;
import junit.framework.TestSuite;

public class AllTests {

    public static Test suite() {
        TestSuite suite = new TestSuite("Test for democenter");
        //$JUnit-BEGIN$
        suite.addTest(TestTeacherAction.suite());
        suite.addTest(TestHibernateDAO.suite());
        //$JUnit-END$
        return suite;
    }
}

当运行这个TestSuite的时候,就会自动对这两个TestCase进行测试。你可能已经看出来了,我们前文中写的TestTeacherAction类中并没有声明suite方法,是的,因此这里就要增加这个静态方法,如下所示:

public static Test suite() {
    return new TestSuite(TestTeacherAction.class);
}

我们在这个方法里只是简单的返回一个TestSuite对象,JUnit会根据传递的参数(TestTeacherAction.class)找到这个TestCase中全部的testXXX()方法并运行。

上面这种suite()方法的写法被称为动态方式,即利用了java的反射机制。还可以写成静态方式,这就需要在TestCase里写两个方法了,如下:

public static Test suite() {
    TestSuite suite=new TestSuite();
    suite.addTest(new TestTeacherAction());
    return suite;    
}

protected void runTest() throws Throwable {
    testListTeacherAction();
    testEditTeacherAction();
    testSaveTeacherAction();
}

这种方式允许用户选择执行某些testXXX()方法,而且这些方法也不一定以test开头,反正只要在runTest()里指定的都给执行。而suite()方法与动态方式比也有变化。要注意的是,如果按动态方式写suite()就不要再覆盖runTest()方法了,我实验后发现,这样会造成runTest()中指定的方法被反复执行n次,其中n等于textXXX()方法的数目。

另外一点,关于JUnite对Test的计数,在动态方式下,JUnit是按照testXXX()的数目计数的;而在静态方式下,是按照TestCase的数目计数的。

还有一点很重要,动态方式下,setUp()和tearDown()这两个方法是在每个testXXX()方法的前后执行;而静态方式下,是在每个TestCase的前后执行,也就是说,同一个TestCase中两个测试方法之间可能不会经过tearDown()和setUp()的过程。

至于动态方式和静态方式的选择,可以根据上面所说的进行参考。不过先声明,以上都是我自己测试得到的结论,存在出现错误的可能性(欢迎告知),以及没有涉及到的方面。

我本人比较prefer动态方式,毕竟代码量小一些

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2004/07/29/28309.html

[Struts]使用StrutsTestCase对Action进行单元测试简介

目前,测试驱动开发正变得越来越流行,由于“存在的就是合理的”,这种开发方式必然有其优越之处。作为一个小小程序员,对新鲜技术的追求是工作的重要动力,相信大家都有同感吧。

测试驱动开发是极限编程(XP)的重要组成部分,从字面上就可以看出,它是先有测试再有代码的。这听起来似乎有点奇怪,实际上,可以把测试用例当作需求,程序员的工作就是写出满足这种需求的代码,即让这些测试都能够通过。在刚刚写好测试用例的时候,由于还没有实际代码,因此这时运行测试的结果一定不会通过,随着代码的增加,越来越多的测试得以通过,最后全部通过。这时,基本上可以说系统的每个功能单元都是正确的,剩下的工作就是集成测试了。而这些测试用例的使命并未结束,因为代码会不断修改,我们需要经常(例如每天)运行测试来检验目前的代码是否能够通过测试。很明显,这种检验是完全自动化的,既快速有保证质量。

在使用Struts构件的系统里,的每一个Action都可以看作一个功能单元,它们构成了系统的主体。(当然,你的业务逻辑并不一定都直接写在Action的execute方法中,但在该方法中会以一定方式调用这些逻辑。)StrutsTestCase 是JUnit的一个包装,它提供了非常方便的测试这些Action的方法。它提供两种测试方式:Mock(模拟对象)和Cactus(真实环境),目前我只试验了前者,发现效果很好。

由于我已经写了一些代码,因此我进行的不能算是真正的测试“驱动”开发,我主要是把StrutsTestCase作为一种自动化的测试工具来用,起到保证代码质量的作用。

举例来说,我有一个类名为SaveTeacherAction的Action,其所在模块名为teacher,访问路径为/save,与他相关联的是名为TeacherForm的ActionForm,(struts-config-teacher.xml)配置如下所示:

<action
    attribute="teacherForm"
    input="/form/teacher.jsp"
    name="teacherForm"
    path="/save"
    scope="request"
    type="edu.pku.cc.democenter.teacher.action.SaveTeacherAction">
    <forward name="success" path="/list.do" redirect="true" />
</action>

SaveTeacherAction类中的execute方法如下,其中HibernateDAO是我自己写的用来进行持久化操作的包装类,BeauUtils是Jakarta commons包中的一个实用工具,可以在两个Bean类型对象的相同属性之间进行复制:

public ActionForward execute(
    ActionMapping mapping,
    ActionForm form,
    HttpServletRequest request,
    HttpServletResponse response)
    throws Exception {

    HibernateDAO dao=HibernateDAO.getInstance(getServlet().getServletContext());
    TeacherForm teacherForm = (TeacherForm) form;
    Teacher t=null;

    if("Create".equals(teacherForm.getAction())){
        t=new Teacher();
    }
    if("Edit".equals(teacherForm.getAction())){
        t=(Teacher)dao.findByCode(Teacher.class,teacherForm.getCode());
    }

    BeanUtils.copyProperties(t,teacherForm);

    dao.saveOrUpdate(t);
    return mapping.findForward("success");
}

下面,我要写一个测试用例来对这个Action进行测试。在Eclipse里使用StrutsTestCase非常简单,只需要在工程的classpath里包含strutstest-2.1.2.jar这个包以及junit的包就可以开始编写了。我为这个类起名为TestSaveTeacherAction,即Action类名前加上Test字样,所在包也与实际代码分开,使用edu.pku.cc.democenter.test的名称(与之对比,SaveTeacherAction的包名为edu.pku.cc.democenter.teacher.action,democenter是我们这个项目的名称)。

现在来看一下TestSaveTeacherAction的内容:

package edu.pku.cc.democenter.test;

import servletunit.struts.MockStrutsTestCase;
import edu.pku.cc.democenter.teacher.form.TeacherForm;

public class TestTeacherAction extends MockStrutsTestCase{

    protected void setUp() throws Exception {
        super.setUp();
        setConfigFile("teacher","/WEB-INF/struts-config-teacher.xml");
    }

    protected void tearDown() throws Exception {
        super.tearDown();
    }

    public void testSaveTeacherAction_Create(){
        setRequestPathInfo("/teacher","/save");
        TeacherForm form=makeForm();
        form.setAction("Create");
        form.setCode("test.create");
        setActionForm(form);
        actionPerform();
        verifyForward("success");
    }

    public void testSaveTeacherAction_Edit(){
        setRequestPathInfo("/teacher","/save");
        TeacherForm form=makeForm();
        form.setAction("Create");
        form.setCode("test.edit");
        setActionForm(form);
        actionPerform();

        form.setAction("Edit");
        form.setBirthDate("1979-1-10");
        setActionForm(form);
        actionPerform();
        verifyForward("success");
    }

    private TeacherForm makeForm(){
        TeacherForm form=new TeacherForm();
        form.setAction("Create");
        form.setBirthDate("1979-1-1");
        form.setCode("test.001");
        form.setEmail("test@test.com");
        form.setName("test");
        form.setShortName("CS");
        form.setTel("62760000");
        return form;
    }
}

使用Mock方式的测试用例都继承servletunit.struts.MockStrutsTestCase这个类,setUp和tearDown方法分别是测试前后进行准备和善后工作的地方。要运行的测试方法都以test开头,系统会自动调用这些方法。

由于SaveTeacherAction根据request中的action参数有两种运行方式:Create和Edit,所以我写了两个test方法对它们分别进行测试。这里要注意的是,这些test方法运行的顺序是不确定的,不要认为可以先运行create的测试,再以此为基础对刚刚create的对象进行edit,看testSaveTeacherAction_Edit方法的写法。

我在setUp方法里指定了模块和对应的配置文件名称,如果没有模块可以省去这一步。在实际的testSaveTeacherAction_Create方法里,首先用setRequestPathInfo方法指定要测试的Action的路径和模块,如果没有模块可以用一个参数的同名方法。然后构造一个ActionForm,对于我的例子就是TeacherForm,为了简化代码,我写了一个makeForm方法来生成一个填好值的TeacherForm。用setActionForm将这个TeacherForm连接到Action,actionPerform方法通知执行Action操作。这时按照我们的设想,Action会将请求转发到一个名为success的Forward,所以我们使用verifyForward("success")方法验证是否进行了转发。在这一步如果失败,就表明此单元测试失败,否则为成功。

testSaveTeacherAction_Edit方法与其类似,只是要注意在这个方法里要自己建立对象再修改,而不能使用testSaveTeacherAction_Create方法里建立的对象,因为这两个方法的执行顺序是不确定的。

要在Eclipse里运行这个测试也很简单,先双击打开这个类,然后在Run菜单里选择Run As->JUnit Test就可以了,你会看到一个JUnit视图,里面有一个绿色的进度条,如果走到头还保持绿色表示所有的测试都成功,否则会变成红色,并在下面显示异常的堆栈信息。(成就感哦)

file

好了,今天对StrutsTestCase作了一个很简单的介绍,我也是刚刚开始使用它,今后肯定还会遇到问题的,敬请关注后续报道。

另,相关的一些文章可以在网上找到,作为入门很好,例如:

http://plateau.sicool.com/article/tdd/strutstestcast_junit_tdd.htm

我的感觉,遇到问题最好先找文档,养成习惯后不但解决问题快了,同时在看文档的同时还可以对其加深理解,何乐而不为呢。

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2004/07/29/28221.html

[Struts]学习日记3 – 在页面中显示条目列表

开发jsp/servlet最经常遇到的应用其中之一就是在页面上显示一个条目列表(例如用户列表、文章列表、商品列表等等),然后用户才好在浏览的基础上选择对某一个条目进行操作。现在就说一下怎样用struts实现这个功能。

一般来说,用struts开发的应用是不应该直接访问.jsp文件的,而是由action转发请求,jsp只是显示action传来的数据用。所以即使这样简单的一个应用,也还是需要一个action的。

1、还是使用easy struts的向导,在菜单里选择File->New->Other,然后选择Easy Action这一项。这就打开一个向导窗口,该向导只有两步,比学习日记1里的少了创建form bean的那一步。

2、我们给要创建的action起名叫listItems好了,也就是说,在user case框里输入listItems。Type最好根据需要修改一下包名称。按下一步继续。

3、增加一个名为success的forward,path为/form/listItems.jsp。按finish按钮完成整个向导。

4、下面,首先编辑刚刚生成的ListItemsAction.java文件,修改execute方法如下:

public ActionForward execute(ActionMapping mapping, ActionForm form,
    HttpServletRequest request, HttpServletResponse response) throws Exception {

    User user = new User();
    List items = new ArrayList();
    items.add(new Item("001", "Medicine"));
    items.add(new Item("002", "Ticket"));
    items.add(new Item("003", "Clothes"));
    user.setItems(items);
    request.getSession().setAttribute("user", user);

    return (mapping.findForward("success"));
}

这里的User和Item都是我们自己写的类,一个User可以拥有多个items,Item具有id和name两个属性。具体代码见后。

5、接下来在/form下创建名为listItems.jsp的文件,内容如下:

<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean"%>
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html"%>
<%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %>
<html>
 <head>
  <title>itemlist</title>
 </head>
 <body>
  <table>
  <logic:iterate name="user" property="items" id="item">
   <tr>
    <td><bean:write name="item" property="id" filter="true"/></td>
    <td><bean:write name="item" property="name" filter="true"/></td>
   </tr>
  </logic:iterate>
  </table>
 <body>
</html>

这里关键的就是这个标签,在这个例子中,它用来遍历session或request中key为user对应的对象的items属性,在遍历过程中可用item引用每个对象()。用于输出每个对象的不同属性,filter="true"表示把特殊字符进行转换,例如<转换为<等等。

6、这样,就可以通过 http://localhost:8080/struts-test/listItems.do 看到一个条目列表了,如下所示:

001 Medicine
002 Ticket
003 Clothes

7、我们可以修改struts-config.xml文件,让用户登录成功后直接转向条目列表页面。只需要把原来logon action的名为success的forward的path由/form/main.htm改为/listItems.do就可以了。

附:

User.java代码:

public class User {  
   
    private List items;  
   
    public User(){  
        items=new ArrayList();  
    }  
   
    public List getItems() {  
        return items;  
    }

    public void setItems(List list) {  
        items = list;  
    }
}

Item.java代码:

public class Item {  
   
    private String id;  
    private String name;

    public Item(String arg0, String arg1){  
        id=arg0;  
        name=arg1;  
    }

    public String getId() {  
        return id;  
    }

    public String getName() {  
        return name;  
    }

    public void setId(String string) {  
        id = string;  
    }

    public void setName(String string) {  
        name = string;  
    }
}

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2004/07/28/28074.html

[Struts]报告异常”Cannot find bean in any scope”之一解

问题描述

今天在开发中遇到一奇怪问题,有一个action,在该action里使用request.setAttribute()方法将一个List类型对象放在request中,然后forward到一个jsp文件,该文件的主要内容是使用<logic:iterate>标签将这个List对象中的条目列表显示。与它同样逻辑但位于另一模块(teacher)中的代码执行正常。但这个模块(xxgl)中的代码,本来很简单的逻辑,却总是提示:

org.apache.jasper.JasperException: Cannot find bean t in any scope 
at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:254) 
at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:295) 
at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:241) 
at javax.servlet.http.HttpServlet.service(HttpServlet.java:853) 
at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:684) 
at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:432) 
at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:356) 
at org.apache.struts.action.RequestProcessor.doForward(RequestProcessor.java:1069) 
at org.apache.struts.action.RequestProcessor.processForwardConfig(RequestProcessor.java:455) 
at org.apache.struts.action.RequestProcessor.process(RequestProcessor.java:279) 
at org.apache.struts.action.ActionServlet.process(ActionServlet.java:1482) 
at org.apache.struts.action.ActionServlet.doGet(ActionServlet.java:507) 
at javax.servlet.http.HttpServlet.service(HttpServlet.java:740) 
at javax.servlet.http.HttpServlet.service(HttpServlet.java:853) 
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:247) 
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:193) 
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:256) 
at org.apache.catalina.core.StandardPipeline$StandardPipelineValveContext.invokeNext(StandardPipeline.java:643) 
at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:480) 
at org.apache.catalina.core.ContainerBase.invoke(ContainerBase.java:995) 
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191) 
at org.apache.catalina.core.StandardPipeline$StandardPipelineValveContext.invokeNext(StandardPipeline.java:643) 
at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:480) 
at org.apache.catalina.core.ContainerBase.invoke(ContainerBase.java:995) 
at org.apache.catalina.core.StandardContext.invoke(StandardContext.java:2417) 
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:180) 
at org.apache.catalina.core.StandardPipeline$StandardPipelineValveContext.invokeNext(StandardPipeline.java:643) 
at org.apache.catalina.valves.ErrorDispatcherValve.invoke(ErrorDispatcherValve.java:171) 
at org.apache.catalina.core.StandardPipeline$StandardPipelineValveContext.invokeNext(StandardPipeline.java:641) 
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:172) 
at org.apache.catalina.core.StandardPipeline$StandardPipelineValveContext.invokeNext(StandardPipeline.java:641) 
at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:480) 
at org.apache.catalina.core.ContainerBase.invoke(ContainerBase.java:995) 
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:174) 
at org.apache.catalina.core.StandardPipeline$StandardPipelineValveContext.invokeNext(StandardPipeline.java:643) 
at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:480) 
at org.apache.catalina.core.ContainerBase.invoke(ContainerBase.java:995) 
at org.apache.coyote.tomcat4.CoyoteAdapter.service(CoyoteAdapter.java:193) 
at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:781) 
at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.processConnection(Http11Protocol.java:549) 
at org.apache.tomcat.util.net.TcpWorkerThread.runIt(PoolTcpEndpoint.java:589) 
at org.apache.tomcat.util.threads.ThreadPool$ControlRunnable.run(ThreadPool.java:666) 
at java.lang.Thread.run(Unknown Source) 

<logic:iterate>之间的代码去掉后就不会提示错误了,所以怀疑是<bean:write>中的代码有错误。相关文件ListSfzxJbxxAction.java中的execute方法如下:

public ActionForward execute(ActionMapping mapping, ActionForm form, 
   HttpServletRequest request, HttpServletResponse response) throws Exception {

   HibernateDAO dao = HibernateDAO.getInstance(getServlet().getServletContext()); 
   List sfzxjbxxs = dao.find("from " + SfzxJbxx.class.getName()); 
   SfzxJbxx tmp = new SfzxJbxx(); 
   tmp.setSfzxid("id"); 
   tmp.setXxdm("pku"); 
   sfzxjbxxs.add(tmp); 
   request.setAttribute("sfzxjbxxs", sfzxjbxxs); 
   return mapping.findForward("success"); 
} 

listsfzxjbxx.jsp内容如下:

<%@ page contentType="text/html;charset=UTF-8" language="java" %> 
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean"%> 
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html"%> 
<head> 
      <title>SfzxJbxxList</title> 
</head> 
<body> 
<logic:iterate  name="sfzxjbxxs" id="sfzxjbxx"> 
  <tr> 
    <td align="left"> 
      <bean:write name="sfzxjbxx" property="sfzxId" filter="true"/> 
    </td> 
    <td align="left"> 
      <bean:write name="sfzxjbxx" property="xxdm" filter="true"/> 
    </td> 
    <td> 
    </td> 
  </tr> 
</logic:iterate> 
<body> 
</html> 

模块配置文件中相关内容如下:

<action-mappings> 
    <action 
        attribute="Form" 
        input="/form/sfzxjbxx.jsp" 
        name="sfzxJbxxForm" 
        path="/saveSfzxJbxx" 
        type="edu.pku.cc.sfzx.xxgl.action.SaveSfzxJbxxAction" /> 
    <action path="/listsfzxjbxx" type="edu.pku.cc.sfzx.xxgl.action.ListSfzxJbxxAction"> 
        <forward name="success" path="/form/listsfzxjbxx.jsp"/> 
    </action> 

</action-mappings> 

问题解决

经过三个小时的检查,发现是listsfzxjbxx.jsp里缺少<logic:iterate>标签的声明,在前面增加上:

<%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic"%> 

一切OK!

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2004/07/28/28065.html

[Hibernate]关于ID的一个容易混淆的地方

用了这么久的Hibernate了,今天却遇到一个从未遇到的问题,幸好我思维敏捷,善于联想,才得以在短时间内发现并解决了问题。以下是具体描述。

我在HibernateDAO这个类里增加了一个方法如下:

public Object getById(Class clazz, String id) throws HibernateException{
   return session.find("from "+clazz.getName()+" o where o.id=?",id,Hibernate.STRING).get(0);
}

你知道,我的PO类的主键都是名为oid的。凑巧的是,有一些PO类除了具有oid属性外,还具有名为id的属性,用来表示业务编号,例如教师编号、文化程度的编号等等。这些类在使用这个方法时总报下面的异常:

java.lang.IndexOutOfBoundsException: Index: 0, Size: 0

郁闷了半个小时,终于想到,会不会是查询语句中的o.id,Hibernate认为这里的id表示的是主键oid呢?在debug里把参数按oid的值一改,发现果然如此!

解决方法:暂时还不知道有什么方法能起到转义的作用,不过id这个属性确实有点容易产生歧义,还是改名为code吧。

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2004/07/28/28064.html

[Hibernate]使用XDoclet生成hbm.xml

Hibernate真是受欢迎,有那么多工具为它服务,XDoclet、MiddleGen、各种插件。。。

用XDoclet生成hbm.xml就是在.java文件里写入一些元数据,XDoclet会从这些数据以及类本身得到足够的信息来生成目标文件。当然,除了用于hibernate,XDoclet还可以用于web、ejb等等很多用途。

XDoclet要从sourceforge上下载,包含了很多jar包、文档和例子,我觉得文档做得还是不错的,查起来比较方便。要使用XDoclet,一般要通过ant来完成,也就是在ant脚本里加入XDoclet的内容。

由于eclipse已经包含了ant支持,因此我没有专门去下载一个ant回来,而是直接使用eclipse带的,版本是1.5.3。

创建一个名为build.xml的脚本(其实应该换个名,比如gen-hbm.xml,看起来比较明白),内容如下:

<?xml version="1.0" encoding="ISO-8859-1"?>

<project name="XDoclet Examples" default="hibernate" basedir=".">
    <property name="xdoclet.root.dir" value="c:/xdoclet-1.2.1"/>
    <property name="xdoclet.lib.dir" value="${xdoclet.root.dir}/lib"/>
    <path id="myclasspath">
        <fileset dir="${xdoclet.lib.dir}">
            <include name="*.jar"/>
        </fileset>
    </path>
     <taskdef
        name="hibernatedoclet"
        classname="xdoclet.modules.hibernate.HibernateDocletTask"
        classpathref="myclasspath"
        />
    <target name="hibernate" description="Generate mapping documents">

        <echo>+---------------------------------------------------+</echo>
        <echo>|                                                   |</echo>
        <echo>| R U N N I N G   H I B E R N A T E D O C L E T     |</echo>
        <echo>|                                                   |</echo>
        <echo>+---------------------------------------------------+</echo>

        <hibernatedoclet
            destdir="./src"
            excludedtags="@version,@author,@todo,@see"
            addedtags="@xdoclet-generated at ${TODAY},@copyright The XDoclet Team,@author XDoclet,@version ${version}"
            force="false"
            verbose="true">

            <fileset dir="./src">
                <include name="org/haree/struts/form/UserForm.java"/>
            </fileset>

            <hibernate version="2.0"/>

        </hibernatedoclet>
    </target>
</project>

我曾经卡住的一个地方就是在taskdef里的classpathref属性。一开始我在eclipse的ant运行参数里设置了XDoclet相关的包,总是提示:

Can't create a hibernate element under hibernatedoclet. Make sure the jar file
containing the corresponding subtask class is on the classpath specified in the
<taskdef> that defined {2}.

后来如上设置了classpathref,即包含了XDoclet使用到的包,并将eclipse的ant里关于XDoclet的包都去掉,竟然就成功了。其实现在也不明白为什么会这样。。。

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2004/07/28/28072.html

[转载]HibernatePlugIn for Struts

这个Plugin的作用是在Struts应用程序启动时进行hibernate的初始化操作,原文请移步 HibernatePlugIn for Struts

步骤很简单:

1、在struts-config.xml里增加:

<plug-in className="org.haree.struts.HibernatePlugIn"> 
  <!-- 'path-to-config-file' is relative to the root of the class 
       path.  It MUST start with a '/'. The default is 
       "/hibernate.cfg.xml --> 
  <set-property property="configFilePath" value="path-to-config-file" /> 
  <set-property property="storeInServletContext" value="true-or-false" /> 
</plug-in> 

2、HibernatePlugIn.java的内容

package org.haree.struts; 

import java.net.URL; 

import javax.servlet.ServletContext; 
import javax.servlet.ServletException; 

import net.sf.hibernate.SessionFactory; 
import net.sf.hibernate.cfg.Configuration; 

import org.apache.commons.logging.Log; 
import org.apache.commons.logging.LogFactory; 
import org.apache.struts.action.ActionServlet; 
import org.apache.struts.action.PlugIn; 
import org.apache.struts.config.ModuleConfig; 

/** 
 * Implements the <code>PlugIn</code> interface to configure the Hibernate 
 * data persistence library.  A configured 
 * <code>net.sf.hibernate.SessionFactory</code> is stored in the 
 * <code>ServletContext</code> of the web application unless the property 
 * <code>storedInServletContext</code> is set to <code>false</code>. 
 * 
 * <p> 
 * <plugin class="net.sf.hibernate.plugins.struts.HibernatePlugIn"> 
 *   <set-property name="configFilePath&quot" 
 *                value="path-to-config-file"/> 
 *   <set-property name="storedInServletContext&quot" 
 *                value="true-or-false"/> 
 * </plugin> 
 * 
 * @author  <a href="mailto:bhandy@users.sf.net">Bradley M. Handy</a> 
 * @version 1.0 
 */ 
public class HibernatePlugIn implements PlugIn { 

    /** 
     * the key under which the <code>SessionFactory</code> instance is stored 
     * in the <code>ServletContext</code>. 
     */ 
    public static final String SESSION_FACTORY_KEY 
            = SessionFactory.class.getName(); 

    private static Log _log = LogFactory.getLog(HibernatePlugIn.class); 

    /** 
     * indicates whether the <code>SessionFactory</code> instance will be stored 
     * in the <code>ServletContext</code>, or not. 
     */ 
    private boolean _storedInServletContext = true; 

    /** 
     * the path to the xml configuration file.  the path should start with a 
     * '/' character and be relative to the root of the class path. 
     * (DEFAULT:  "/hibernate.cfg.xml") 
     */ 
    private String _configFilePath = "/hibernate.cfg.xml"; 

    private ActionServlet _servlet = null; 
    private ModuleConfig _config = null; 
    private SessionFactory _factory = null; 

    /** 
     * Destroys the <code>SessionFactory</code> instance. 
     */ 
    public void destroy() { 
        _servlet = null; 
        _config = null; 

        try { 
            _log.debug("Destroying SessionFactory"); 

            _factory.close(); 

            _log.debug("SessionFactory destroyed"); 
        } catch (Exception e) { 
            _log.error("Unable to destroy SessionFactory(exception ignored)", 
                    e); 
        } 
    } 

    /** 
     * Initializes the <code>SessionFactory</code>. 
     * @param servlet the <code>ActionServlet</code> instance under which the 
     *        plugin will run. 
     * @param config the <code>ModuleConfig</code> for the module under which 
     *        the plugin will run. 
     */ 
    public void init(ActionServlet servlet, ModuleConfig config) 
    throws ServletException { 
        _servlet = servlet; 
        _config = config; 

        initHibernate(); 
    } 

    /** 
     * Initializes Hibernate with the config file found at 
     * <code>configFilePath</code>. 
     */ 
    private void initHibernate() throws ServletException { 
        Configuration configuration = null; 
        URL configFileURL = null; 
        ServletContext context = null; 

        try { 
            configFileURL = HibernatePlugIn.class.getResource(_configFilePath); 

            context = _servlet.getServletContext(); 

            if (_log.isDebugEnabled()) { 
                _log.debug("Initializing Hibernate from " 
                        + _configFilePath + ""); 
            } 

            configuration = (new Configuration()).configure(configFileURL); 
            _factory = configuration.buildSessionFactory(); 

            if (_storedInServletContext) { 
                _log.debug("Storing SessionFactory in ServletContext"); 

                context.setAttribute(SESSION_FACTORY_KEY, _factory); 
            } 

        } catch (Throwable t) { 
            _log.error("Exception while initializing Hibernate."); 
            _log.error("Rethrowing exception", t); 

            throw (new ServletException(t)); 
        } 
    } 

    /** 
     * Setter for property configFilePath. 
     * @param configFilePath New value of property configFilePath. 
     */ 
    public void setConfigFilePath(String configFilePath) { 
        if ((configFilePath == null) || (configFilePath.trim().length() == 0)) { 
            throw new IllegalArgumentException( 
                    "configFilePath cannot be blank or null."); 
        } 

        if (_log.isDebugEnabled()) { 
            _log.debug("Setting 'configFilePath' to '" 
                    + configFilePath + "'"); 
        } 

        _configFilePath = configFilePath; 
    } 

    /** 
     * Setter for property storedInServletContext. 
     * @param storedInServletContext New value of property storedInServletContext. 
     */ 
    public void setStoredInServletContext(String storedInServletContext) { 
        if ((storedInServletContext == null) 
                || (storedInServletContext.trim().length() == 0)) { 
            storedInServletContext = "false"; 
        } 

        if (_log.isDebugEnabled()) { 
            _log.debug("Setting 'storedInServletContext' to '" 
                    + storedInServletContext + "'"); 
        } 

        _storedInServletContext 
                = new Boolean(storedInServletContext).booleanValue(); 
    } 

}

[Struts]分模块后文件命名的考虑

例如有一个模块名为teacher,包含对教师的列表、增、删、改等操作,因此应该有与这些操作相对应的Action。这样就涉及到它们的命名问题。

一开始我为他们的path起名为/listTeachers、/editTeacher和/saveTeacher(删除操作暂时没写),其中listTeachers用于列表,editTeacher判断request中的action参数产生新增或编辑的Form并跳转至Form页,saveTeacher则是在用户提交表单后执行实际的操作。

随后我发现,这样一来在IE路径中就有两个teacher了,比如:

http://localhost:8080/democenter/teacher/listTeachers.do 

如果去掉path中的teacher字样不就简明多了吗。因此我修改了path名,为统一起见,类名中也去掉了teacher字样。幸好目前的代码还很少,否则光这么一改就不知道要反复调试多少时间了。现在,我可以用下面的路径访问了:

http://localhost:8080/democenter/teacher/list.do 

问题又来了,我想到等模块多起来以后,按这样的命名方法,工程里就会有大量的EditAction.java等文件,虽然它们的包名不同,但在程序中看过去还是会有些费劲的,特别是使用eclipse的Ctrl+Alt+T打开类的时候,要在众多同名文件中根据包来选,远不如直接输入唯一的类名方便。

因此,我觉得比较好的命名方式是把path和类名分开,path使用省略包的命名方法(如/edit),而类名还保持EditTeacherAction的方式。

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2004/07/28/28063.html