j2ee核心模式笔记二——迭代标签的使用及测试

迭代标签的使用及测试
 
(本文适合具有一定jsp/servlet开发基础的人阅读)
 
  初级的JSP开发人员往往在JSP中混杂诸多的逻辑内容,比如我们为了显示数据库的某个查询结果,经常会在页面中出现下列代码,而这些代码可能在许多地方重复出现
    <%
    while (rs.next()){
    {
        out.println("<td>"+rs.getName()+"</td>");
        out.println("<td>"+rs.getAuthor()+"</td>");
        .......
    }
    %>
 
这些在j2ee核心模式一书中可以被视作表现层的不佳实践,是因为多个视图都包含了同样的控制代码,而且看来极不雅观。比较好的一种方法就是使用标签对其进行重构,虽然你可以使用JSTL的标准标签库,但是为了揭示一些更本质的东西,我们这里采用了自定义标签来完成上述重构,在使用自定义标签之前,我们先构建完成上述实例的相关环境配置。
 
(1)oracle数据库
 
我们用在数据库里先建一个BOOKS的实例,然后启动该实例后用SYSTEM登录(可以使用sql plus),执行下列脚本:
 
----book.sql begin------
 
--以sys权限暂时连接,可以执行以下的GRANT权限
connect
sys/books@books as sysdba;
--删除用户BOOKS
drop user BOOKS cascade;
--创建用户BOOKS
CREATE USER "BOOKS" IDENTIFIED BY "BOOKS" PROFILE DEFAULT default tablespace users temporary tablespace temp ACCOUNT UNLOCK;
--赋予用户BOOKS系统中的默认角色CONNECT和RESOURCE
GRANT "CONNECT" TO "BOOKS";
GRANT "RESOURCE" TO "BOOKS";
--让BOOKS可以无限制地使用表空间,表空间的数据文件大小以及是否自动增长可以在企业版管理器中设置
GRANT UNLIMITED TABLESPACE TO "BOOKS";
--赋予用户创建表、执行表、修改表(包括其它模式)的权限
GRANT CREATE TABLE TO "BOOKS";
GRANT EXECUTE ANY PROCEDURE TO "BOOKS";
GRANT UPDATE ANY TABLE TO "BOOKS";
GRANT CREATE ANY INDEX TO "BOOKS";
--赋予用户可以运行SYS下四个包中相应系统函数的功能
GRANT EXECUTE ON "SYS"."UTL_FILE" TO "BOOKS";
GRANT EXECUTE ON "SYS"."DBMS_SQL" TO "BOOKS";
GRANT EXECUTE ON "SYS"."DBMS_JOB" TO "BOOKS";
GRANT EXECUTE ON "SYS"."DBMS_OUTPUT" TO "BOOKS";
GRANT EXECUTE ON "SYS"."DBMS_LOCK" TO "BOOKS";
--赋予用户的默认角色为所有角色(用户所具有的角色不一定完全开启,可以禁用,此处即开启所有角色)
ALTER USER "BOOKS" DEFAULT ROLE ALL;
--赋予BOOKS执行ALTER USER命令
GRANT ALTER USER TO "BOOKS";
COMMIT;
 
--创建序列为递增列做准备
create sequence BOOKS_BOOKID_SEQ
minvalue 1
maxvalue 999999999999999999999999999
start with 21
increment by 1
cache 20;
 
--创建触发器以实现数据递增
create or replace trigger books_i
  before insert on books
  for each row
declare
  -- local variables here
  next_book_id number;
begin
  SELECT BOOKS_BOOKID_SEQ.nextval into next_book_id FROM dual;
  :new.bookid :=next_book_id;
end books_i;
 
--创建表结构
 
create table BOOKS
(
  NAME      VARCHAR2(20),
  PRICE     VARCHAR2(20),
  AUTHOR    VARCHAR2(20),
  PUBLISHER VARCHAR2(20),
  BOOKID    NUMBER not null
)
tablespace USERS
  pctfree 10
  initrans 1
  maxtrans 255
  storage
  (
    initial 64
    minextents 1
    maxextents unlimited
  );
-- Create/Recreate primary, unique and foreign key constraints
alter table BOOKS
  add constraint BOOKID primary key (BOOKID)
  using index
  tablespace USERS
  pctfree 10
  initrans 2
  maxtrans 255
  storage
  (
    initial 64K
    minextents 1
    maxextents unlimited
  );
 
--插入数据
insert into books (NAME, PRICE, AUTHOR, PUBLISHER, BOOKID)
values ('j2ee核心模式', '45', 'yfhuang', '机械', 2);
 
insert into books (NAME, PRICE, AUTHOR, PUBLISHER, BOOKID)
values ('java程序设计', '45', 'fying', '机械', 3);
 
insert into books (NAME, PRICE, AUTHOR, PUBLISHER, BOOKID)
values ('agilejava', '30', 'jeff', '电子', 4);
 
COMMIT;
EXIT;
 
----books.sql end-------
 
到此,我们有了一个BOOKS数据库,里面有张books表
 
(2)Tomcat (此处为Tomcat 5.0.28为例,修改系统的server.xml文件)
 
添加JNDI全局资源,建立数据库连接池
 
<Resource name="jdbc/books" auth="Container" type="javax.sql.DataSource"/>
 <ResourceParams name="jdbc/books">
 <parameter>
  <name>factory</name>
  <value>org.apache.commons.dbcp.BasicDataSourceFactory</value>
 </parameter>
 <parameter>
  <name>username</name>
  <value>books</value>
 </parameter>
 <parameter>
  <name>password</name>
  <value>books</value>
 </parameter>
 <parameter>
  <name>driverClassName</name>
  <value>oracle.jdbc.driver.OracleDriver</value>
 </parameter>
 <parameter>
  <name>url</name>
  <value>jdbc:oracle:thin:@127.0.0.1:1521:books</value>
 </parameter>
 <parameter>
 <name>maxActive</name>
  <value>20</value>
 </parameter>
 <parameter>
  <name>maxIdle</name>
  <value>10</value>
 </parameter>
 <parameter>
  <name>maxWait</name>
  <value>10000</value>
 </parameter>
 </ResourceParams>
 
在j2ee web应用程序中添加到资源的引用,在server.xml中添加
 
<Context path="/j2ee" docBase="j2ee"
debug="0" privileged="true" reloadable="true" >
  <ResourceLink name="jdbc/books" global="jdbc/books"
                type="javax.sql.DataSource"/>
</Context>
 
通过以上两个步骤,你就可以在j2ee这个web应用程序中使用books数据库了
 
(3) 建立用来演示的jsp页面bookdetail.jsp页面
 
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib uri="
http://yfhuang.chap1lib" prefix="tag"%>
<html>
 <body bgcolor="white">
  <tag:generatebean jndisource="jdbc/books" beanname="booklist">
   select * from books
  </tag:generatebean>
  <table>
   <tr>
    <td>
     <b>
      自定义标签:使用TagSupport标签(靠的是IterationTag接口机制)进行迭代的示例,显示oracle数据库中的books表信息
     </b>
    </td>
   </tr>
  </table>
  <table border=2 cellspacing=3 cellpadding=3>
   
   <tag:recordheader/>
   <tag:recordsite bean="${booklist}">
    <tr>
     <tag:recordcontent />
    </tr>
   </tag:recordsite>
  </table>
 </body>
</html>
 
在该页面中,使用了(4)中用到的标签库,该标签库分成两部分,generatebean标签主要用来生成booklist,而recordsite主要用来展示booklist。这些自定义标签定义如(4).
 
(4) 建立自定义标签chap1lib.tld
 
<?xml version="1.0" encoding="UTF-8"?>
<taglib>
 <tlib-version>1.0</tlib-version>
 <jsp-version>1.2</jsp-version>
 <short-name>chap1lib</short-name>
 <uri>http://yfhuang.chap1lib</uri>
<!-- 自定义标签:一个使用自定义标签查询oracle数据库的综合示例-->
 <tag>
  <name>generatebean</name>
  <tag-class>chapter1.GenerateBean</tag-class>
  <body-content>jsp</body-content>
  <attribute>
   <name>jndisource</name>
   <required>true</required>
   <rtexprvalue>true</rtexprvalue>
  </attribute>
  <attribute>
   <name>beanname</name>
   <required>true</required>
   <rtexprvalue>true</rtexprvalue>
  </attribute>
 </tag>
 <tag>
  <name>recordsite</name>
  <tag-class>chapter1.RecordsIte</tag-class>
  <body-content>jsp</body-content>
  <attribute>
   <name>bean</name>
   <required>true</required>
   <rtexprvalue>true</rtexprvalue>
  </attribute>
 </tag>
 <tag>
  <name>recordheader</name>
  <tag-class>chapter1.RecordHeader</tag-class>
  <body-content>empty</body-content>
 </tag>
 <tag>
  <name>recordcontent</name>
  <tag-class>chapter1.RecordContent</tag-class>
  <body-content>empty</body-content>
 </tag>
</taglib>
 
(5) 标签处理核心类
 
----GenerateBean.java------
-----GenerateBean.java主要负责数据库连接参数的装配-------
 
package chapter1;
 
import java.util.ArrayList;
import java.util.Collection;
 
import javax.servlet.jsp.JspTagException;
import javax.servlet.jsp.tagext.BodyTagSupport;
 
public class GenerateBean extends BodyTagSupport implements DataEngine,DataBaseBean {
 private String jndisource;
 
 private String beanname;
 
 private String sql;
 
 private DataEngine dg;
 
 private Collection recordcollect;
 
 
 public GenerateBean(){
  dg = this;
  recordcollect=new ArrayList();
 }
 public int doStartTag() throws JspTagException {
  return EVAL_BODY_BUFFERED;
 }
 
 public int doEndTag() throws JspTagException {
  System.out.println("set dataengine");
  pageContext.setAttribute(beanname, this);
  String body = this.getBodyContent().getString();
  this.sql = body;
  return SKIP_BODY;
 }
 
 public String getJndisource() {
  return jndisource;
 }
 
 public void setJndisource(String jndisource) {
  System.out.println("set jndisource:"+jndisource);
  this.jndisource = jndisource;
 }
 
 public String getBeanname() {
  return beanname;
 }
 
 public void setBeanname(String beanname) {
  this.beanname = beanname;
 }
 
 public DataEngine getDg() {
  return dg;
 }
 
 public String getSql() {
  return sql;
 }
 
 public Collection getRecordcollect() {
  return recordcollect;
 }
 
 public void setSql(String sql) {
  this.sql = sql;
 }
 
}
 
------DataBaseBean.java和DataEngine.java是GenerateBean.java的两个辅助接口
----DataBaseBean.java-----
 
package chapter1;
 
import java.util.Collection;
 
public interface DataBaseBean {
 public DataEngine getDg();
 public String getSql();
 public Collection getRecordcollect();
}
 
----DataEngine.java----
 
package chapter1;
 
public interface DataEngine {
 public String getJndisource();
}
 
-----RecordsIte.java-------
-----RecordsIte主要根据Genarate的装配参数查询数据库,并通过标签生命周期方法doStartTag方
-----法doAfterBody方法对标签本体进行迭代,标签本体是RecordContent,显示单本书信息
 
package chapter1;
 
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Iterator;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspTagException;
import javax.servlet.jsp.tagext.TagSupport;
import javax.sql.DataSource;
 
public class RecordsIte extends TagSupport {
 
 private DataBaseBean bean;
 private Iterator iterator;
 private BookBean onebook;
 
 public void QueryDataBase() {
  try {
   System.out.println("Query DataBase");
   Context initCtx = new InitialContext();
   Context envCtx = (Context) initCtx.lookup("java:comp/env");
   DataSource ds = null;
   ds = (DataSource) envCtx.lookup(bean.getDg().getJndisource());
   if (ds != null) {
    Connection cn = ds.getConnection();
    if (cn != null) {
     Statement stmt = cn.createStatement();
     ResultSet rst = stmt.executeQuery(bean.getSql());
     bean.getRecordcollect().clear();
     while (rst.next()){
      BookBean abook=new BookBean();
      abook.setName(rst.getString("name"));
      abook.setAuthor(rst.getString("author"));
      abook.setPrice(rst.getString("price"));
      abook.setPublisher(rst.getString("publisher"));
      bean.getRecordcollect().add(abook);
     }
     rst.close();
     cn.close();
    }
   }else{
    System.out.println("不能找到jndi 数据源");
   }
  } catch (NamingException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } catch (SQLException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
 }
 
 public int doStartTag() throws JspTagException {
  QueryDataBase();
  iterator=bean.getRecordcollect().iterator();
  if (iterator.hasNext()) {
   System.out.println("RecordsIte dostarttag iterator");
   this.onebook = (BookBean) iterator.next();
   System.out.println(onebook.getAuthor());
   return EVAL_BODY_INCLUDE;
  } else {
   return SKIP_BODY;
  }
 }
 
 public int doAfterBody() throws JspException {
  System.out.println("do after body begin");
  if (iterator.hasNext()) {
   System.out.println("do after body iterator");
   this.onebook=(BookBean)iterator.next();
   return EVAL_BODY_AGAIN;
  } else {
   return SKIP_BODY;
  }
 }
 
 public DataBaseBean getBean() {
  return bean;
 }
 
 public void setBean(DataBaseBean bean) {
  this.bean = bean;
 }
 
 public BookBean getOnebook() {
  return onebook;
 }
 
}
 
------RecordContent.java------
package chapter1;
 
import java.io.IOException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.SimpleTagSupport;
 
public class RecordContent extends SimpleTagSupport{
 
 private BookBean onebook;
 
 public void doTag() throws JspException, IOException {
  System.out.println("RecordsContent dotag begin");
  JspWriter out = getJspContext().getOut();
  RecordsIte ancestorTag=(RecordsIte)this.getParent();
  if (ancestorTag == null) {
   System.out.println("没有父类");
  } else {
   //System.out.println(ancestorTag.test);
   BookBean onebook=ancestorTag.getOnebook();
   out.println(format(onebook.getName()));
   out.println(format(onebook.getPrice()));
   out.println(format(onebook.getAuthor()));
   out.println(format(onebook.getPublisher()));
  }
 }
 
 public static String format(String m) {
  return "<td>" + m + "</td>";
 }
 
 public static String format(int m) {
  return "<td>" + m + "</td>";
 }
}
 
(6) 标签处理的测试
 
在最初写这个页面迭代程序的时候,我在标签处理核心类RecordsIte的数据库查询部分是这样写的,
 
public void QueryDataBase() {
  try {
   System.out.println("Query DataBase");
   Context initCtx = new InitialContext();
   Context envCtx = (Context) initCtx.lookup("java:comp/env");
   DataSource ds = null;
   ds = (DataSource) envCtx.lookup(bean.getDg().getJndisource());
   if (ds != null) {
    Connection cn = ds.getConnection();
    if (cn != null) {
     Statement stmt = cn.createStatement();
     ResultSet rst = stmt.executeQuery(bean.getSql());
     bean.getRecordcollect().clear();
     BookBean abook=new BookBean();
     while (rst.next()){
      abook.setName(rst.getString("name"));
      abook.setAuthor(rst.getString("author"));
      abook.setPrice(rst.getString("price"));
      abook.setPublisher(rst.getString("publisher"));
      bean.getRecordcollect().add(abook);
     }
     rst.close();
     cn.close();
    }
   }else{
    System.out.println("不能找到jndi 数据源");
   }
  } catch (NamingException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } catch (SQLException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
 }
 
abook的生成放到了循环外,错误在哪里?昏了头了,找了半天还是没发现,不要紧的,逼我写了个测试,使用的是cactus的框架,测试如下:
 
//TestRecordIte.java
 
package test;
 
import java.util.Iterator;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.TagSupport;
import org.apache.cactus.JspTestCase;
import org.apache.cactus.WebResponse;
import chapter1.BookBean;
import chapter1.GenerateBean;
import chapter1.RecordsIte;
 
public class TestRecordIte extends JspTestCase {
 
 private RecordsIte tag;
 
 
 public void setUp() { 
  this.tag = new RecordsIte();
  GenerateBean genb = new GenerateBean();
  this.tag.setPageContext(this.pageContext);
  genb.setBeanname("booklist");
  genb.setJndisource("jdbc/books");
  genb.setSql("select * from books");
  this.tag.setBean(genb);
 }
 
 
 public void testQueryDatabase() throws Exception{
  
  this.tag.QueryDataBase();
  int number = 1;
  Iterator ite = this.tag.getBean().getRecordcollect().iterator();
  while (ite.hasNext()) {
   //System.out.print("the " + number + "is:");
   BookBean onebook = (BookBean) ite.next();
   System.out.println(onebook.getName());
   number++;
  }
 }
  
 public void testDoStartTag() throws Exception {
  int result = this.tag.doStartTag();
  assertEquals(TagSupport.EVAL_BODY_INCLUDE, result);
 }
 
 public void testDoAfterBody() throws JspException {
  int result = this.tag.doStartTag();
  assertEquals(TagSupport.EVAL_BODY_INCLUDE, result);

  boolean firsttime = true;
  int number=1;
  if (firsttime || result == TagSupport.EVAL_BODY_AGAIN) {
   result = this.tag.doAfterBody();
   firsttime = false;
   number++;
  }
  assertEquals(true,number==2);
 }
 }
 
该测试为taglib测试,采用cactus框架,可以测试taglib的行为,要使用该测试,必须导入cactus的相关lib,具体可见
 
要拷贝cactus中的jspRedirector.jsp(可在cactus下载包中找)至webapps目录下
并在j2ee web应用程序的webapps目录下添加这样的配置
 
<servlet>
  <servlet-name>JspRedirector</servlet-name>
  <jsp-file>/chap1/jspRedirector.jsp</jsp-file>
  </servlet>
 <servlet-mapping>
  <servlet-name>JspRedirector</servlet-name>
  <url-pattern>/JspRedirector</url-pattern>
 </servlet-mapping>
 
 
把该测试当作junit运行(测试有些在客户端运行,有些在服务器运行,具体参见cactus
对于客户端的Junit测试部分,需要一个配置文件cactus.properties(放在classpath中)
 
cactus.contextURL = http://127.0.0.1/j2ee
cactus.servletRedirectorName = JspRedirector
cactus.disableLogging=enable
 
测试虽然通过了(这边只是偷懒的测试,没有对记录的内容判断,只是打印出来),testQueryDatabase方法在Tomcat的控制台上把最后一条数据库记录(agilejava 30 jeff....)打印了三遍。
 
原来生成集合时就不对,肯定在bean.getRecordcollect().add(abook)发生了问题,
恍然大悟,终于发现BookBean abook=new BookBean()在while外面,每循环一次,abook还是那个abook,不过
内容变了,虽然里面加了三次abook,内容却是同一个abook,它对应于数据库的最后1条记录。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章