JdonFramework使用开发指南

 

板桥里人(banq  J http://www.jdon.com)

6.2版本: 20091223

 

 

JdonFramework下载地址:http://www.jdon.com/jdonframework/download.html

技术支持论坛:http://www.jdon.com/jivejdon/forum.jsp?forum=61

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Jdon框架能解决什么问题?

搞过数据库系统的人都知道,数据库系统中大量的基本功能无非是数据表的CRUD增删改查和批量分页查询,Jdon框架结合对象设计和J2EE/JEE设计理念将这个看似简单功能开发过程抽象出来,放在框架中,并且随着应用程序一起运行,提供优化性能提升等。

日常企业信息化过程中,经常会一些表单数据输入数据库,使用Jdon框架可以在一两个小时内完成这样的应用,当你只需要快速建立一个软件应用系统,Jdon框架正好适合你,而且拥有灵活的拓展性和方便的维护性。

如果你希望马上手一个Hello World的案例,可直接到“开发一个简单Jdon应用系统”章节。

请注意:Jdon框架不是面向数据库的;而是面向模型分析设计(OOA/OOD)。Jdon框架是基于如下一个软件生产环节中:

需求Use case分析;使用RoseTogetherUML工具;

面向对象的模型提取,域建模的确立,可使用UML的类图表达。

模型CRUD等基础功能快速完成以及构件/组件库组装;如果在这一过程中使用Jdon框架可提前保质保量完成,Jdon之类快速开发框架是基于真正松耦合的设计架构,这样在以后迭代过程中才能应需求改变。软件松耦合和快速性讨论见:http://www.jdon.com/artichect/coupling.htm

反馈给用户确认;根据用户需求修改模型或界面或数据库或逻辑,因为有Jdon等框架形成架构的松耦合性,修改一处不会影响太多无关部分,具有好的可维护和可拓展,再次给用户确认;进入下一个循环。

 

快速开发框架和代码生成工具的区别:

软件生产是一个产品生成过程,只要是产品就要有质量要求,如果质量单纯靠人工保证是不行的,必须靠自动化的生产设备,而开发框架和组件或构件库则就是可以保证质量的生产设备;当然只有生产设备也是不行的,必须有良好的产品设计,必须符合客户要求,符合市场需求,那么产品设计就是域建模设计,域建模专家就象工厂的产品设计师或工艺师一样。

那么除了生产设备和产品图纸是不是就可以生长出高质量软件?不是,必须有不同级别软件人员,以及对他们的管理,也即软件工程管理,软件工程管理是和生产设备是互补的,如果生产设备自动化程度高,对人员要求低,管理就相对容易一些。

所以,目前对于软件生产这样一个产业,最重要的是摆脱依赖人工的局面,形成生产设备高度自动化。

当然,过分重视生产设备自动化的极端是必须指出的,这也是代码生成工具的产生原因,在目前,第一个阶段“松耦合”还没有完成,就想进入共产主义,显然有些急躁。

开发框架是基于软件最大追求松耦合基础上诞生,实现真正高质量;而代码生成工具则可能生成好或坏的代码。

软件产品生产=产品设计图(域建模)+自动化生产设备(开发框架或组件库)+不同级别的开发人员+软件工程管理。开发框架需要人员参与编程;而代码生成工具夸大生产设备作用。

如果你希望马上手看一个Hello World的案例,可直接到“开发一个简单Jdon应用系统”章节。

Jdon框架安装说明

 

Jdon框架源码包中的dist目录下,有下列几个包,版本不同可能不相同,以实际dist目录下包为主:

jdonFramework.jar

Jdon框架核心包

必须需要

aopalliance.jar

AOPAlliance

必须需要

jdom.jar

读取XMLJDOM

必须需要

picocontainer-1.1.jar

Picocontainer

必须需要

commons-pool-1.2.jar

Apache对象池包

必须需要

log4j.jar

Log4j 调试记录跟踪包

可选

 

 

 

struts.jar …

struts驱动包,支持struts1.2

可选

         

JBoss中安装

安装步骤:

1. 确保已经安装J2SE 1.4 以上版本,然后设置操作系统的环境变量 JAVA_HOME=你的J2SE目录

2. 下载JBoss 3.X/JBoss 4.x

3. 安装Jdon框架驱动包:将Jdon框架源码包中的dist目录下除log4j.jarsrcclasses目录下log4j.properties以外的包拷贝到jboss/server/default/lib目录下。

4. 安装struts驱动包,下载struts 1.2,将jar包拷贝到jboss/server/default/lib

或者使用Jdon框架例程samplesSimpleJdonFrameworkTest项目的lib目录。将该目录下jar包拷贝到jboss/server/default/lib

 

对于具体J2EE应用系统,需要配置Jboss的数据库连接池JNDI

1.配置JBoss的数据库连接:

  将数据库驱动包如MYSQLmysql-connector-java-3.0.14-production-bin.jarOracleclass12.jar拷贝到jboss/server/default/lib

2. 选择JBoss的数据库JNDI定义文件:

jbossdocs目录下寻找你的数据库的配置文件,如果是MySQL,则是mysql-ds.xml;如果是Oracle;则是oracle-ds.xml。下面以MySQL为例子。

mysql-ds.xml拷贝到jboss/server/default/deploy目录下。

3. 修改配置数据库定义文件:

打开jboss/server/default/deploy/mysql-ds.xml,如下:

 <datasources>

  <local-tx-datasource>

    <jndi-name>DefaultDS</jndi-name> <!—JNDI名称  应用程序中使用java:/DefaultDS调用 -->

  <connection-url>jdbc:mysql://localhost:3306/test?useUnicode=true&amp;characterEncoding=UTF-8

</connection-url>

    <driver-class>com.mysql.jdbc.Driver</driver-class>

    <user-name>root</user-name><!--  MySQL数据库访问用户和密码,缺省是root  -->

    <password></password>

  </local-tx-datasource>

  </datasources>

 4.启动JBoss

打开jboss/server/default/log/server.log 如果没有错误,一切OK,一般可能是数据库连接错误,检查mysql-ds.xml配置,查取相关资料,弄懂每行意义。

至此,可以将基于Jdon框架开发的J2EE应用程序部署到JBoss中。

一般是将*.ear*.war拷贝到jboss/server/default/deploy目录下即可。

 

日志输出:

当运行具体应用系统时,打开jboss/server/default/log/server.log时,会看到很多Jdon框架本身的输出,如果你觉得非常混乱,可以关闭这些输出,在jboss/server/default/conf/log4j.xml中加入如下配置行:

<category name="com.jdon.aop">

     <priority value="ERROR"/>

   </category>

<category name="com.jdon.container">

     <priority value="ERROR"/>

</category>

   <category name="com.jdon.model">

     <priority value="ERROR"/>

   </category>

   <category name="com.jdon.controller">

     <priority value="ERROR"/>

   </category>

   <category name="com.jdon.security">

     <priority value="ERROR"/>

   </category>

   <category name="com.jdon.bussinessproxy">

     <priority value="ERROR"/>

   </category>

   <category name="com.jdon.strutsutil">

     <priority value="ERROR"/>

   </category>

  

  

  

Weblogic等服务器中安装

只要将Jdon框架包和struts 1.2包安装到服务器的库目录下即可,或者配置在系统的classpath中即可。如果你的服务器没有log4j包,那么还需要log4j.jar,并将log4j.properties放置在系统classpath中。

1. strutsJdonFramework所有驱动包拷贝到Weblogiccommon/lib目录下。

2. weblogic的启动文件中加入如下命令:将jar包加入系统的classpath

set CLASSPATH=%CLASSPATH%;%WL_HOME%\common\lib\log4j.jar;

%WL_HOME%\common\lib\mysql-connector-java-3.0.14-production-bin.jar;

set CLASSPATH=%CLASSPATH%;%WL_HOME%\common\lib\jdonFramework.jar;

%WL_HOME%\common\lib\jdom.jar;%WL_HOME%\common\lib\commons-pool-1.2.jar;

%WL_HOME%\common\lib\aopalliance.jar;%WL_HOME%\common\lib\picocontainer-1.1.jar

set CLASSPATH=%CLASSPATH%;%WL_HOME%\common\lib\struts.jar;

%WL_HOME%\common\lib\jakarta-oro.jar;%WL_HOME%\common\lib\commons-validator.jar;

%WL_HOME%\common\lib\antlr.jar;%WL_HOME%\common\lib\commons-beanutils.jar;

%WL_HOME%\common\lib\commons-collections.jar;%WL_HOME%\common\lib\commons-digester.jar

3. 在具体Web项目打包部署时,需要将log4j.properties加到WEB-INF/classess目录下,更改log4j.properties中配置,使之日志输出到你自己指定的一个文件中,注意:当时部署log4j日志不会激活log4j,必须重新启动Weblogic即可(项目必须在Weblogic中)。

 

Tomcat中安装

Jdon框架在Tomcat下安装主要问题是log4j问题,下面是安装步骤:

struts驱动包和Jdon框架包(包括log4j.jar)拷贝到tomcat/ common/lib目录下。

Jdon框架源码包dist目录下的log4j. properties拷贝到tomcat/common/classes目录。

配置Tomcat中运行log4j的关键是:检查commons-logging.jar log4j.jar 文件在common/lib 目录,struts驱动包中已经包含commons-logging.jar包。

上面步骤都正常了,可以启动Tomcat,但是你会发现tomcat/logs下没有输出记录,因为我们已经使用新的log4j,所以为了使得tomcat运行信息输出文件便于调试,编辑/common/classeslog4j. properties

1. log4j.rootLogger=INFO, A1前面加#log4j.rootLogger=DEBUG, R前去除#

log4j.rootLogger=DEBUG, R

#log4j.rootLogger=INFO, A1

2. #log4j.appender.R.File=D:/javaserver/jakarta-tomcat-5.0.28/logs/tomcat.log一行前面的#删除,文件目录是绝对路径,更改为你自己的目录和文件。

3.  配置Jdon框架运行过程输出,在log4j. properties中下面一行:

log4j.logger.com.jdon=DEBUG

该配置将会显示Jdon框架的主要运行信息,如果你要关闭,只要更改如下:

log4j.logger.com.jdon=ERROR

重新启动Tomcat,这时可以从tomcat.log看到输出记录。

 

如何判断安装启动成功

当启动Jdon框架任何一个应用程序,后台日志出现:

<========  Jdon Framework started successfully! =========>

表示你的系统配置和启动Jdon框架成功。

 


快速入门ABC

一步到位

如果使用Jdon框架 6.0以上,可以直接使用Jdon框架的Annotation一部到位。

举例如下:

HelloServiceImpl加入注解:

@Service("helloService")

public class HelloServiceImpl implements HelloService {

  private UserRepository userRepository;

   

   public HelloServiceImpl(UserRepository userRepository) {

       this.userRepository = userRepository;

    }

 

    public String hello(String name) {

       User user = userRepository.findUser(name);

       System.out.print("call ok");

        return "Hello, " + user.getName();

    }

}

HelloServiceImpl中并没有对接口UserRepository实例化,只需在接口UserRepository的子类中加入注解@Component,如下:

 

@Component

public class UserRepositoryInMEM implements UserRepository {

 

  }

 

客户端调用代码:

HelloService helloService = (HelloService) WebAppUtil.getService("helloService", req);

String result = helloService.hello(myname);

输出:Hello  XXXX

 

原理图:

 

  客户端代码中也没有将接口UserRepository子类UserRepositoryInMEM实例化创建后,赋给HelloServiceImpl,这一切都是由Jdon框架悄悄在背后实现了。

  具体全部代码见JdonFramework源码包目录下examples/testWeb项目。

  更详细的Annotation注解使用见 6版更新说明

  关于AOP拦截Annotation使用见6.2版本更新说明

详细讲解

如果你完全使用Annotation来使用Jdon框架,可能无需看这一篇,但是XMLAnnotation各有优缺点,XML有时更像水泥,可以将很多砖头联接在一起,无需重新编译源码,这样,在运行现场就可以通过更改XML中一些参数,达到修改程序运行功能的目的。

Jdon框架有一个配置文件叫jdonframework.xml,其中配置的是我们编写的Java类,格式如下:

 

<pojoService name="给自己类取的名称" class="完整类的名称"/>

相当于

@Service("给自己类取的名称")

 

 

配置有两个基本项:nameclassclass中写全POJO的全名;name是供代码中调用这个服务的名称。

假如我们编写了一个类TestServicePOJOImp,代码简要如下:

 

public class TestServicePOJOImp implements TestService{

   private JdbcDAO jdbcDao;

   public TestServicePOJOImp(JdbcDAO jdbcDao) {

         this.jdbcDao = jdbcDao;

   }

   public void createUser(EventModel em) {

      ....

   }

 }

 

接口TestService代码:

public interface TestService {

   void createUser(EventModel em);

}

上面TestServicePOJOImp代码创建完成后,我们在源码目录需要创建一个叫jdonframework.xml配置文件,内容如下:

 

 

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

<!DOCTYPE app PUBLIC "-//JDON//DTD Framework //EN" "http://www.jdon.com/jdonframework.dtd">

<app>

   <services>

<pojoService name="testService" class="com.jdon.framework.test.service.TestServicePOJOImp"/>

   </services>

</app>

 

这样,在servletjspstrutsaction等客户端代码中,我们可以使用如下代码调用TestServicePOJOImp,注意:以下代码没有具体TestServicePOJOImp类:

 

 

TestService testService = (TestService) WebAppUtil.getService("testService ", request);

testService.createUser(em);

 

 

以上步骤,只是简单展示框架的一个简要步骤,你可能没有觉得多了一个jdonframework.xml以后,好像比平常代码没什么不同,关键是:如果我们需要使用AnotherTestServicePOJOImp更换原来的TestServicePOJOImp类,只需要更改jdonframework.xml文件,而无须更改客户端代码,也无须重新编译项目了。

 

当然,还有另外一个优点,就是Ioc/DI依赖注射,细心的人已经注意到TestServicePOJOImp有一个构造参数如下:

   public TestServicePOJOImp(JdbcDAO jdbcDao) {

         this.jdbcDao = jdbcDao;

   }

如果不传入JdbcDAO实例,我们如何能在客户端代码中直接创建TestServicePOJOImp实例呢?原来只要我们在jdonframework.xml中再配置一个JdbcDAO类,概时框架就会自动帮我们创建JdbcDAO实例,并且传入TestServicePOJOImp实例中。

新的jdonframework.xml内容如下:

 

 

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

<!DOCTYPE app PUBLIC "-//JDON//DTD Framework //EN" "http://www.jdon.com/jdonframework.dtd">

<app>

   <services>

     <pojoService name="testService" class="com.jdon.framework.test.service.TestServicePOJOImp"/>

 

      <!-- 新增加的配置:jdbcDAO是被TestServiceImp调用的,是为其服务的。 -->

      <component name="jdbcDAO" class="com.jdon.framework.test.dao.JdbcDAO"/>

      <!-- 相当于@Component  -->

   </services>

</app>

 

 

 

再进一步,如果我们经常遇到一些类中需要一些常量或参数定义,那么可以使用如下配置:

 

 

<component name="jdbcDAO" class="com.jdon.framework.test.dao.JdbcDAO">

     <constructor value="java:/TestDS"/>

</ component >

<!--  如果组件带有配置参数,就不能用@Component 必须用XML配置,体现XML优势  -->

 

 

这时,要求JdbcDAO构造参数有一个字符串和参数,这样constructor的值java:/TestDS就在JdbcDAO被创建时注射到它的实例中。 JdbcDAO代码如下:

 

public class JdbcDAO{

 

......

   public JdbcDAO(String jndiName){

         System.out.println("jndiname" = jndiName);

         ......

 

   }

 

......

}

 

启动Jdon框架

如果我们没有使用XML配置,全部使用Annotation,就无需启动Jdon框架,这一章可跳过。

当配置了jdonframework.xml,我们需要在项目中启动它,有两种启动方式:一个是结合strutsstruts-config.xml配置方式;另外一个是不结合strutsweb.xml配置方式。

 

第一:web.xml配置方式:

如果你不使用Struts,可以通过web.xml下列配置来启动Jdon框架。

 

<context-param>

   <param-name> modelmapping-config </param-name>

   <param-value> jdonframework.xml </param-value>

</context-param>

……

<listener>

   <listener-class>com.jdon.container.startup.ServletContainerListener</listener-class>

</listener>

 

 

第二:结合struts配置方式(需要struts基础知识)

struts-config.xml(或其它struts模块配置文件如struts-config-admin.xml等等)中配置Plugin实现子类:

 

<plug-in className="com.jdon.strutsutil.InitPlugIn">

   <set-property property="modelmapping-config" value="jdonframework.xml" />

</plug-in>

 


 


技术背景

Java EE/J2EE基本概念

J2EE可以说指Java在数据库信息系统上实现,数据库信息系统从早期的dBase、到Delphi/VBC/S结构,发展到B/SBrowser浏览器/Server服务器)结构,而J2EE主要是指B/S结构的实现。

J2EE又是一种框架和标准,框架类似API、库的概念,但是要超出它们。

J2EE是一个虚的大的概念,J2EE标准主要有三种子技术标准:WEB技术、EJB技术和JMS,谈到J2EE应该说最终要落实到这三个子概念上。

这三种技术的每个技术在应用时都涉及两个部分:容器部分和应用部分,Web容器也是指Jsp/Servlet容器,你如果要开发一个Web应用,无论是编译或运行,都必须要有Jsp/Servlet库或API支持(除了JDK/J2SE以外)。

Web技术中除了Jsp/Servlet技术外,还需要JavaBeansJava Class实现一些功能或者包装携带数据,所以Web技术最初简称为Jsp/Servlet+JavaBeans系统。

谈到JavaBeans技术,就涉及到组件构件技术(component),这是Java的核心基础部分,很多软件设计概念(设计模式)都是通过JavaBeans实现的。

JavaBeans不属于J2EE概念范畴中,如果一个JavaBeans对象被Web技术(也就是Jsp/Servlet)调用,那么JavaBeans就运行在J2EEWeb容器中;如果它被EJB调用,它就运行在EJB容器中。

EJB(企业JavaBeans)是普通JavaBeans的一种提升和规范,因为企业信息系统开发中需要一个可伸缩的性能和事务、安全机制,这样能保证企业系统平滑发展,而不是发展到一种规模重新更换一套软件系统。

J2EE集群原理: http://www.jdon.com/jive/article.jsp?forum=121&thread=22282

至此,JavaBeans组件发展到EJB后,并不是说以前的那种JavaBeans形式就消失了,这就自然形成了两种JavaBeans技术:EJBPOJOPOJO完全不同于EJB概念,指的是普通JavaBeans,而且这个JavaBeans不依附某种框架,或者干脆可以说:这个JavaBeans是你为这个应用程序单独开发创建的。

J2EE应用系统开发工具有很多:如JBuilderEclipse等,这些IDE首先是Java开发工具,也就是说,它们首要基本功能是可以开发出JavaBeansJava class,但是如果要开发出J2EE系统,就要落实到要么是Web技术或EJB技术,那么就有可能要一些专门模块功能,最重要的是,因为J2EE系统区分为容器和应用两个部分,所以,在任何开发工具中开发J2EE都需要指定J2EE容器。

J2EE容器分为WEB容器和EJB容器,Tomcat/ResinWeb容器;JBossEJB容器+Web容器等,其中Web容器直接使用Tomcat实现的。所以你开发的Web应用程序可以在上面两种容器运行,而你开发的Web+EJB应用则只可以在JBoss服务器上运行,商业产品Websphere/Weblogic等和JBoss属于同一种性质。

J2EE容器也称为J2EE服务器,大部分时它们概念是一致的。

如果你的J2EE应用系统的数据库连接是通过JNDI获得,也就是说是从容器中获得,那么你的J2EE应用系统基本与数据库无关,如果你在你的J2EE应用系统耦合了数据库JDBC驱动的配置,那么你的J2EE应用系统就有数据库概念色彩,作为一个成熟需要推广的J2EE应用系统,不推荐和具体数据库耦合,当然这其中如何保证J2EE应用系统运行性能又是体现你的设计水平了。

高质量的Java企业系统

衡量J2EE应用系统设计开发水平高低的标准就是:解耦性;你的应用系统各个功能是否能够彻底脱离?是否不相互依赖,也只有这样,才能体现可维护性、可拓展性的软件设计目标。

为了达到这个目的,诞生各种框架概念,J2EE框架标准将一个系统划分为WEBEJB主要部分,当然我们有时不是以这个具体技术区分,而是从设计上抽象为表现层、服务层和持久层,这三个层次从一个高度将J2EE分离开来,实现解耦目的。

因此,我们实际编程中,也要将自己的功能向这三个层次上靠,做到大方向清楚,泾渭分明,但是没有技术上约束限制要做到这点是很不容易的,因此我们还是必须借助J2EE具体技术来实现,这时,你可以使用EJB规范实现服务层和持久层,Web技术实现表现层;

EJB为什么能将服务层从Jsp/Servlet手中分离出来,因为它对JavaBeans编码有强制的约束,现在有一种对JavaBeans弱约束,使用Ioc模式实现的(当然EJB 3.0也采取这种方式),在Ioc模式诞生前,一般都是通过工厂模式来对JavaBeans约束,形成一个服务层,这也是是Jive这样开源论坛设计原理之一。

由此,将服务层从表现层中分离出来目前有两种可选架构选择:管理普通JavaBeansPOJO)框架(SpringJdonFramework)以及管理EJBEJB框架,因为EJB不只是框架,还是标准,而标准可以扩展发展,所以,这两种区别将来是可能模糊,被纳入同一个标准了。

但是,通常标准制定是为某个目的服务的,总要牺牲一些换取另外一些,所以,这两种架构会长时间并存。

前面谈了服务层框架,使用服务层框架可以将JavaBeansJsp/Servlet中分离出来,而使用表现层框架则可以将Jsp中剩余的JavaBeans完全分离,这部分JavaBeans主要负责显示相关,一般是通过标签库(taglib)实现,不同框架有不同自己的标签库,Struts是应用比较广泛的一种表现层框架。

这样,表现层和服务层的分离是通过两种框架达到目的,剩余的就是持久层框架了,通过持久层的框架将数据库存储从服务层中分离出来是其目的,持久层框架有两种方向:直接自己编写JDBCSQL语句(如iBatis);使用O/R Mapping技术实现的HibernateJDO技术;当然还有EJB中的实体Bean技术。

持久层框架目前呈现百花齐放,各有优缺点的现状,所以正如表现层框架一样,目前没有一个框架被指定为标准框架,当然,表现层框架现在又出来了一个JSF,它代表的页面组件概念是一个新的发展方向,但是复杂的实现让人有些忘而却步。

最后,你的J2EE应用系统如果采取上面提到的表现层、服务层和持久层的框架实现,基本可以在无需深刻掌握设计模式的情况下开发出一个高质量的应用系统了。

还要注意的是: 开发出一个高质量的J2EE系统还需要正确的业务需求理解,那么域建模提供了一种比较切实可行的正确理解业务需求的方法,相关详细知识可从UML角度结合理解。

当然,如果你想设计自己的行业框架,那么第一步从设计模式开始吧,因为设计模式提供你一个实现JavaBeans或类之间解耦参考实现方法,当你学会了系统基本单元JavaBeans或类之间解耦时,那么系统模块之间的解耦你就可能掌握,进而你就可以实现行业框架的提炼了,这又是另外一个发展方向了。

以上理念可以总结为一句话:

J2EE开发三件宝: Domain Model(域建模)、patterns(模式)和framework(框架)。

 

 

组件框架比较

 

 

EJB2/EJB3

Spring Framework

Jdon Framework

灵活性
(
松耦合)

EJB3EJB2更具灵活性,EJB3支持应用系统POJO

支持应用系统POJO,框架基础功能不能替换

支持应用系统POJO,框架本身可分离配置,定制性更强

功能完整性

全面,支持异步JMS 分布式事务

较为全面。有自己的表现层和持久层模板,可支持异步

基本完整,表现层借助Struts实现。有自己简单的持久层模板

单台性能

一般,批量查询等大数据量业务处理须小心,存在本地不透明缺陷。

一般,框架本身组件无性能提升极致,应用程序可配置cache/Pool

好,框架本身组件使用缓存提升性能,应用程序可配置cache/Pool,批量查询专门优化,适合实时性并发性要求较高应用

可伸缩性

可支持多台服务器分布式计算。

不支持,可依靠EJB实现

不支持,可依靠EJB实现

开发效率

学习曲线长,导致熟练掌握难。借助商业开发工具可加快熟练者的开发速度。

较为复杂,可挑选只适合自己的功能实现。当组件很多时,需要照顾这些组件之间调用关系。

简单快速,表现层编码很少。当组件个数很多时,无需照顾它们之间的调用关系

系统规模

EJB2适合大型系统;EJB3适合中大型系统

适合中小型系统

适合小中型系统,和EJB无缝结合,可借助EJB支持中大型系统

重量级别

重量,正在减肥

轻量偏重,有可能继续增肥

最轻量,恪守简单快速原则

详细文章见:http://www.jdon.com/artichect/java_ee_architecture.htm

 

自动注射的Ioc框架

代码案例

假设有调用者B和被调用者A代码如下:

调用者B

 package test;

 public class B{   

          AInfterface a;

public B(AInfterface a){ 

this.a = a

     }

          public void invoke(){

                a.myMethod();

          }

 }

被调用者A类:

package test;

 public class A  implements AInfterface {

          public void myMethod(){

               System.out.println("hello");

          }

 }

生成B类实例代码如下:

B b = new B(new A());

创建B的实例要逐个照顾到B类中涉及到所有其他类(如A类)的实例化,给编程者带来代码编写的琐碎工作,无法提高效率。

使用Jdon框架的Ioc模式后,B类生成实例代码如下:

B b =  (B)  WebAppUtil.getService(“b”);

b. invoke();

无需首先照顾其他类如A类的实例生成。B的实例生成再也与其他类如A类没有任何关系了,实现松耦合。如果B类中涉及不只是A类,还有CDEF等类,那么生成B类实例时,我们就无需关心这些类的创建了。

实现上述调用效果,需另外进行一下jdonframework.xml配置如下:

<app>

   <services>

                 <pojoService name="b"  class="test.B"/>

                   <pojoService name="a"  class="test.A"/>

     </services>

   ……

</app>

革命性优点

两个革命性优点:

1. 颠覆对象使用之前必须创建的基本定律,正象无需关心对象销毁一样,您可以无需关心对象创建。

Java编程中类创建成实例的过程简化:(Class -> Instance

使用JdonIoc框架前:
编程者需要自己逐个解决这个Class相关涉及的其他Class的实例化。

使用JdonIoc框架后:

无需编程者自己实现这种级联式、琐碎的实例化过程。

2. 松耦合;更换各种子类方便。面向对象编程之父Grady Booch 说:对象最伟大之处在于其可被替代。而Jdon框架伟大之处是帮助你替代这些对象,甚至包括Jdon框架本身。

上例中,如果Ainterface有另外一个实现子类AA类,只要将jdonframework.xml中:

<pojoService name="a"  class="test.A"/>

更换为:

<pojoService name="a"  class="test.AA"/>

 


Jdon框架架构

Jdon框架是一个真正轻量级别的开发框架,设计简单巧妙,适合快速开发各种架构的J2EE应用系统。它是一套符合当前国际水平的、面向构件开发的、国人拥有自主产权的中间件产品。

面向对象编程之父Grady Booch 说:对象最伟大之处在于其可被替代。The great thing about objects is they can be replaced.

Jdon框架伟大之处是帮助你替代这些对象,甚至包括Jdon框架本身。另外一个优点是:颠覆对象使用之前必须创建的基本定律,正象无需关心对象销毁一样,您可以无需关心对象创建。

面向DDD的架构

为了更好地突出应用需求为主,Jdon框架采取面向DDD的主要设计思想,将应用架构分为业务和技术两个部分:

@Model 领域模型,包括实体模型 值对象和领域服务,与技术架构无关。相当于鱼;生存空间是缓冲器中

 

@Component 技术组件架构,用以支撑领域模型在计算机系统运行的支撑环境,相当于鱼生活的水。空间在Context container,例如ServletContext中。

 

两者以Domain Events模式交互:异步 命令。

 

 

 

 

 

注射和拦截器AOP特点

(一)@Model:模型中可以通过字段的@Inject将其他类注射进入,包括@Component类。被注射的类如果有@Introduce,将再次引入拦截器。

 

 

(二)@Component:技术架构中组件可以通过构造器直接注射,被注射的类如果有@Introduce,将再次引入拦截器。

 

可彻底分离的组件管理

Jdon框架可以实现几乎所有组件可配置、可分离的管理,这主要得益于Ioc模式的实现,Jdon框可以说是一个组件(JavaBeans)管理的微容器。

Jdon框架中,有三种性质的组件(JavaBeans):框架基础组件;AOP拦截器组件和应用服务组件。三种性质的组件都是通过配置文件实现可配置、可管理的,框架应用者替换这三种性质组件的任何一个。

 

 

框架基础组件是Jdon框架最基本的组件,是实现框架基本功能的组件,如果框架应用者对Jdon框架提供的功能不满意或有意替换,可以编写自己的基础功能组件替代,从而实现框架的可彻底分离或管理。Jdon框架功能开发基本思路是:当有新的功能加入Jdon框架时,总是让该功能组件实现可配置和可更换,以使得该功能代表的一类一系列其他功能有加入拓展的余地。

应用服务组件是框架应用者针对具体项目设计的组件,如用户管理AccountService、订单服务OrderService等都属于应用服务组件。

AOP拦截器组件主要是指一些应用相关的通用基础功能组件,如缓存组件、对象池组件等。相当于应用服务组件前的过滤器(Filter),在客户端访问应用服务组件之前,必须首先访问的组件功能。

这三种性质组件基本函括了应用系统开发大部分组件,应用服务组件是应用系统相关组件,基本和数据库实现相关,明显特征是一个DAO类;当应用服务组件比较复杂时,我们就可以从中重整Refactoring出一些通用功能,这些功能可以上升为框架基础组件,也可以抽象为AOP拦截器组件,主要取决于它们运行时和应用服务组件的关系。当然这三种性质框架组件之间可以相互引用(以构造方法参数形式),因为它们注册在同一个微容器中。

使用Jdon框架,为应用系统开发者提炼行业框架提供了方便,框架应用者可以在Jdon框架基本功能基础上,添加很多自己行业特征的组件,从而实现了框架再生产,提高应用系统的开发效率。

JdonFramework中,所有组件都是在配置文件中配置的,框架的组件是在container.xmlaspect.xml中配置,应用系统组件是在jdonframework.xml中配置,应用系统组件和框架内部或外部相关组件都是在应用系统启动时自动装载入J2EE应用服务器中,它们可以相互引用(以构造器参数引用,只要自己编写的普通JavaBeans属于构造器注射类型的类就可以),好似是配置在一个配置文件中一样。

因此,组件配置主要有三个配置文件:应用服务组件配置container.xmlAOP拦截器组件aspect.xml和应用服务组件配置jdonframework.xml 另外也有一套Annotation来起到和这些XML同样作用的配置。

 

 

 

适合哪些人

Jdon框架主要是面向J2EE程序员,对于程序员要求并不很高,只要具备以下技术背景之一就可以尝试学习使用Jdon框架:

拥有Jsp/Servlet JavaBeans J2EEWeb编程经验的程序员

最好有一些Struts感性认识和少量编程经验。

 

领域模型应用

Evans DDD是目前软件分析设计的主要方法,看如下各位大师的解释:

Jdon,com关于DDD的专题讨论:http://www.jdon.com/jivejdon/tags/272

 当我们接到一个新项目时,使用UML工具,通过面向对象DDD的分析设计方法将其变成领域模型图,如下:

 

这是一个典型的DDD建模图,这个模型图可以直接和Java代码对应,比如其中Cargo模型的代码如下,两者是完全一一对应,可以使用together等建模工具直接转换,Jdon框架的@Model就是针对Cargo这样模型,将其运行在Java平台中,:

 

package ship;

 

 

 

/**

 * @stereotype thing

 */

 

public class Cargo {

        

         private String id;

 

         /**

          * @link aggregation

          */

         private ship.DeliveryHistory lnkDeliveryHistory;

         /**

          * @link aggregation

          */

         private ship.DeliverySpec lnkDeliverySpec;

        

        

        

        

          public Cargo(String trackingId, DeliverySpec deliverySpec) {

                  this.id = trackingId;

                  this.lnkDeliverySpec = deliverySpec;

         }

 

         public String getId() {

                  return id;

         }

 

         public void setId(String id) {

                  this.id = id;

         }

 

         public void changeDestination(final Location newDestination) {

                   lnkDeliverySpec.setDestination(newDestination);

         }

          

          //跟踪货物位置

          public Location lastKnownLocation() {

                      final HandlingEvent lastEvent = this.getLnkDeliveryHistory().lastEvent();

                      if (lastEvent != null) {

                        return lastEvent.getLocation();

                      } else {

                        return null;

                      }

                    }

          

          //当货物在运输抵达目的地点时

          public boolean hasArrived() {

                      return lnkDeliverySpec.getDestination().equals(lastKnownLocation());

                    }

 

          //跟踪顾客货物的关键装卸事件

          public boolean isUnloadedAtDestination() {

                      for (HandlingEvent event : this.getLnkDeliveryHistory().eventsOrderedByCompletionTime()) {

                        if (HandlingEvent.Type.UNLOAD.equals(event.getType())

                          && hasArrived()) {

                          return true;

                        }

                      }

                      return false;

                    }

          

         public ship.DeliveryHistory getLnkDeliveryHistory() {

                  return lnkDeliveryHistory;

         }

 

         public void setLnkDeliveryHistory(ship.DeliveryHistory lnkDeliveryHistory) {

                  this.lnkDeliveryHistory = lnkDeliveryHistory;

         }

 

         public ship.DeliverySpec getLnkDeliverySpec() {

                  return lnkDeliverySpec;

         }

 

         public void setLnkDeliverySpec(ship.DeliverySpec lnkDeliverySpec) {

                  this.lnkDeliverySpec = lnkDeliverySpec;

         }

        

          

          

}

当领域模型Cargo出来以后,下一步就是使用Jdon框架来将其运行起来,因为Jdon框架分为领域模型和组件技术等两个部分,Cargo无疑属于@Model模型架构,我们只要给模型加上@Model,就能让Cargo的对象生活在内存缓存中。

 

@Model

public class Cargo {

}

 

没有领域设计的坏设计

以订单为例子,如果不采取DDD设计,而是通常朴素的数据表库设计,将订单设计为订单数据表,那么带来的问题是:

将实体的职责分离到不同限定场景 ,比如订单中有OrderItemId, OrderId, ProductId Qty,这是合乎逻辑的最初订单,后来有 MinDeliveryQty PendingQty字段,是和订单交货有关,这其实是两个概念,订单和订单的交货,但是我们把这些字段都混合在一个类中了。

混淆在一起的问题就是将来难以应付变化,因为实体的职责是各自变化的。

领域不是把实体看成铁板一块,一开始就把它分解到各种场景。下订单和订单交货交付是两个场景,它们应该有彼此独立的接口,由实体来实现。这就能够让实体和很多场景打交道,而彼此不影响,这就是组合模型composite model的一个关键优点。

在数据库中它们是一个,也就是说,从ER模型上看,它们是一个整体,但是从domain model领域模型角度看,它们是分离的。

下图是基于Jdon框架开发的JiveJdon中领域模型,中间的领域模型可能是继承等关系组成的对象群,或者成为聚合群Aggregation,但是对应的数据表可能是一张表,由此可见,领域模型能够更准确反映业务需求。

实体模型

根据DDD方法,需求模型分为实体模型 值对象和领域服务三种,实际需求经常被划分为不同的对象群,如Cargo对象群就是Cargo为根对象,后面聚合了一批与其联系非常紧密的子对象如值对象等,例如轿车为根对象,而马达则是轿车这个对象群中的一个实体子对象。

Jdon框架中,实体根模型通常以@Model标识,并且要求有一个唯一的标识主键,你可以看成和数据表的主键类似,它是用来标识这个实体模型为唯一的标志,也可以使用Java对象的HashCode来标识。

Jdon框架是实体模型的主键标识有两个用处:

首先是用来缓存实体模型,目前缓冲器是使用EHcache,可以无缝整合分布式云缓存Terracotta来进行伸缩性设计。

只要标识@Model的实体,在表现层Jsp页面再次使用时,将自动直接从缓存中获得,因为在中间业务层和表现层之间Jdon框架存在一个缓存拦截器CacheInterceptor,见框架的aspect.xml中配置。

为了能够在业务层能够使用缓存中的模型,需要在业务层后面的持久层或Repository中手工加入缓存的Annotation标签,如下:

实体模型的缓存架构如下:

注意:这里可能是Jdon框架 6.2的一个创新或重要点,在JF6.2之前实际上没有对模型进行突出的支持,就象画个圈,圈子外面基本都就绪,圈子里面留白,这个圈子就是Domain Model,原来因为考虑持久层HibernateORM巨大影响力,就连Spring也是将Model委托给Hibernate处理,自己不做模型层,但是当NoSQL运动蓬勃发展,DDD深入理解,6.2则找到一个方式可以介入模型层,同时又不影响任一持久层框架的接入。

Jdon框架6.2通过在持久层的获得模型对象方法上加注释的办法,既将模型引入了内存缓存,又实现了向这个模型注射必要的Domain Events(见下一章)。

Jdon框架使用实体模型的主键标识另外一个用处就是实现实体模型的表现层增删改查简化开发,见下面CRUD快速开发专门章节,架构图如下, 图中业务层就是Domain层:

 

Domain Events

 Evans DDD实现过程中,经常会碰到实体和服务Service以及Repository交互过程,这个交互过程的实现是一个难点,也是容易造成失血贫血模型的主要途径。

领域模型中只有业务,没有计算机软件架构和技术。不要将和技术相关的服务和功能组件注射到实体模型中,例如数据库Dao等操作。由领域模型通过Domain Events机制指挥Domain 服务和其他功能组件,包括数据库操作。http://jonathan-oliver.blogspot.com/search/label/DDD

 

Domain Events提出解决方案, JF提供的异步观察者模式为Domain Event实现提供更优雅的解决方案。详细文章见:http://www.jdon.com/jivejdon/thread/37289

 

 

Domain Events异步消息的使用如下,分三步:

1.    创建模型类如UserModel,在UserModel引入自己的UserDomainEvents

@Inject

       private UserDomainEvents userDomainEvents;

 

2.    创建UserDomainEvents

3.    创建com.jdon.domain.message.MessageListener实现子类

如下图所示步骤:

该案例代码可在http://www.jdon.com/jdonframework/SimpleJdonFrameworkTest.rar下载。

 

 

使用Domain Events 可实现异步懒加载机制,对模型中任何字段值根据需要从数据库加载,即用即取,这种方式有别于Hibernate的惰加载lazy load机制,更加灵活。

如下图,我们为了实现UserCount值对象中count数据的懒加载,将其封装到一个值对象中,这个值对象其他字段和方法都是为懒加载服务的。

我们看看getCountByAsync方法内部:

 

public int getCountByAsync() {

                  if (count == -1) { // lazy load

                          if (ageAsyncResult == null)

                                   ageAsyncResult = user.getUserDomainEvents().computeCount(user);

                          else

                                   count = (Integer) ageAsyncResult.getEventResult();

                  }

                  return count;

         }

Count初始值是-1,如果该方法第一次调用,count必为-1,那么ageAsyncResult也应该为空,这时就激活computeCount这个耗时耗CPU的任务,当第二次访问getCountByAsync时,ageAsyncResult应该不为空,因为耗时任务应该计算完毕,如果还没有,会在这里等待。一旦获得计算结果,count就不会为-1

UserCount值对象随同UserModel生活在内存中,因此,这样耗时任务计算只需要一次,以后,基本直接从对象的字段count直接获得。

调用顺序图如下:

 

深入Dmain Events

领域模型中只有业务,没有计算机软件架构和技术。

不要将和技术相关的服务和功能组件注射到实体模型中,例如数据库Dao等操作。容易造成多点多处耦合。由领域模型通过Domain Events机制指挥Domain 服务和其他功能组件,包括数据库操作。

Evans DDD实现过程中,经常会碰到实体和服务Service以及Repository交互过程,这个交互过程的实现是一个难点,也是容易造成失血贫血模型的主要途径。

因为实体的业务方法需要和服务或Reposirtoy打交道,如果把这个业务方法放入服务,就容易造成实体的贫血;但是如果把服务注射到实体中,也非常丑陋。这里提出一个中间处理模式:Domain Event,领域事件模式,这个模式也曾经被MF在文章Domain Event(http://martinfowler.com/eaaDev/DomainEvent.html)专门章节提到。

 

    详细文章见:http://www.jdon.com/jivejdon/thread/37289

            http://www.jdon.com/jivejdon/thread/37712

 

§      将领域模型和技术架构通过松耦合的异步消息解耦。

§      可以避免Hibernate的懒加载LazyInitializationExceptions,无需使用Open Session in View Close Session in View

§      可以在MessageListener处集成入JMSJMS是可伸缩性并带有分布式事务方案,可以保证持久化等事务精确完成。

 

案例

Account中有一个计算该用户发帖总数字段messageCount

public class Account{

 

   private int messageCount;

  

   public int getMessageCount(){

       return messageCount;

   }

 

}

 

这个messageCount是通过查询该用户所有发帖数查询出来的,也许你会问,为什么不将用户发帖总数设置为数据表一个字段,这样用户发帖时,更新这个字段,这样只要直接查询这个字段就可以得到messageCount

 

没有设立专门持久字段的原因如下:

1. 从模型设计角度看:messageCount字段归属问题,messageCount其实是一个表示AccountMessage两个实体的关系关联统计字段,但是这种关联关系不属于高聚合那种组合关系的关联,不是AccountMessage的必备属性,根据DDD的高聚合低关联原则,能不要的关联就不要,因此放在哪里都不合适。

2.从性能可伸缩性角度看:如果我们将messageCount放在第三方专门关联表中,那么用户增加新帖子,除了对Message对应的表操作外,还需要对这个关联表操作,而且必须是事务的,事务是反伸缩性的,性能差,如果象messageCount各种统计字段很多,跨表事务边界延长,这就造成整体性能下降。

3.当然,如果将messageCount硬是作为Account表字段,由于整个软件的业务操作都是Account操作的,是不是将其他业务统计如threadCount等等都放到Account表中呢?这会造成Account表变大,最终也会影响性能。

 

那么messageCount每次都通过查询Message对应表中用户所有发帖数获得,这也会导致性能差,表中数据越多,这种查询就越费CPU

使用缓存,因为Account作为模型被缓存,那么其messageCount值将只有第一次创建Account执行查询,以后就通过缓存中Account可直接获得。

所以,根据DDD,在AccountRepositoryAccountFactory实现数据表和实体Account的转换,Account中的值都是在这个类中通过查询数据表获得的。

当访问量增加时,这里又存在一个性能问题,虽然一个Account创建时,messageCount查询耗时可能觉察不出,但是如果是几十几百个Account第一次创建,总体性能损耗也是比较大的,鉴于我们对可伸缩性无尽的追求,这里还是有提升余地。

从设计角度看,由于messageCount不是Account的必备字段,因此,不是每次创建Account时都要实现messageCount的赋值,可采取即用即查方式。所以,我们需要下面设计思路:

 

public class Account{

 

   private int messageCount = -1;

  

   public int getMessageCount(){

       if(messageCount == -1)

           //第一次使用时即时查询数据表

       return messageCount;

   }

 

}

 

怎么实现这个功能呢?使用Hibernate的懒加载?使用Lazy load需要激活Open Session In View,否则如果Session关闭了,这时客户端需要访问messageCount,就会抛lazy Exception错误,但是OSIV只能在一个请求响应范围打开,messageCount访问可能不是在这次请求中访问,有可能在后面请求或其他用户请求访问,所以,这个懒加载必须是跨Session,是整个应用级别的.

实际上,只要Account保存在缓存中,对象和它的字段能够跨整个应用级别,这时,只要在messageCount被访问即时查询数据表,就能实现我们目标,其实如此简单问题,因为考虑HibernateORM框架特点反而变得复杂,这就是DDD一直反对的技术框架应该为业务设计服务,而不能成为束缚和障碍,这也是一山不容二虎的一个原因。

这带来一个问题,如何在让Account这个实体对象中直接查询数据库呢?是不是直接将AccountRepositoryAccountDao注射到Account这个实体呢?由于AccountDao等属于技术架构部分,和业务Account没有关系,只不过是支撑业务运行的环境,如果将这么多计算机技术都注射到业务模型中,弄脏了业务模型,使得业务模型必须依赖特定的技术环境,这实际上就不是POJO了,POJO定义是不依赖任何技术框架或环境。

POJOMartin Fowler提出的,为了找到解决方式,我们还是需要从他老人家方案中找到答案,模型事件以及Event模式也是他老人家肯定的,这里Account模型只需要向技术环境发出一个查询Event指令也许就可以。

那么,我们就引入一个Domain Events对象吧,以后所有与技术环境的指令交互都通过它来实现,具体实现中,由于异步Message是目前我们已知架构中最松耦合的一种方案,所以,我们将异步Message整合Domain Events实现,应该是目前我们知识水平能够想得到的最好方式之一,当然不排除以后有更好方式,目前JdonFramework 6.2已经整合了Domain Events + 异步消息机制,我们就可以直接来使用。

这样,AccountmessageCount即用即查就可以使用Domain Events + 异步消息实现:

 

 

public int getMessageCount(){

                  if (messageCount == -1) {

                          if (messageCountAsyncResult == null) {

                        //向技术环境发出查询获得messageCount值的命令,

                       //这个命令是在另外新线程实现,因此结果不一定立即返回                           

                        messageCountAsyncResult =

                              domainEvents.computeAccountMessageCount(account.getUserIdLong());

                          } else {

                             //当客户端再次调用本方法时,可以获得查询结果,

            //如果查询过程很慢,还是没有完成,会在这里堵塞等待,但概率很小

                                   messageCount = (Integer) messageCountAsyncResult.getEventResult();

                          }

                  }

}

 

messageCount最后获得,需要通过两次调用getMessageCount方法,第一次是激活异步查询,第二次是获得查询结果,在B/S架构中,一般第二次查询是由浏览器再次发出请求,这浏览器服务器一来一回的时间,异步查询一般基本都已经完成,这就是充分利用B/S架构的时间差,实现高效率的并行计算。

所以,并不是一味死用同步就能提高性能,可伸缩性不一定是指单点高性能,而是指整个系统的高效率,利用系统之间传送时间差,实现并行计算也是一种架构思路。这种思考思路在实际生活中也经常会发生。

最后,关于messageCount还有一些有趣结尾,如果浏览器不再发第二次请求,那么浏览器显示AccountmessageCount就是-1,我们可以做成不显示,也就看不到Account的发帖总数,如果你的业务可以容忍这个情况,比如目前这个论坛就可以容忍这种情况存在,AccountmessageCount第一次查询会看不到,以后每次查询就会出现,因为Account一直在缓存内存中。

如果你的业务不能容忍,那么就在浏览器中使用AJAX再次发出对getMessageCount的二次查询,那么用户就会每次

都会看到用户的发帖总数,JiveJdon这个论坛的标签关注人数就是采取这个技术实现的。这样浏览器异步和服务器端异步完美结合在一起,整个系统向异步高可伸缩性迈进一大步。

更进一步,有了messageCount异步查询,如何更新呢?当用户发帖时,直接对内存缓存中Account更新加一就可以,这样,模型操作和数据表操作在DDD + 异步架构中完全分离了,数据表只起到存储作用(messageCount甚至没有专门的存储数据表字段),这和流行的NoSQL架构是同一个思路。

由于针对messageCount有一些专门操作,我们就不能直接在Account中实现这些操作,可以使用一个专门值对象实现。如下::

public class AccountMessageVO {

 

         private int messageCount = -1;

 

         private DomainMessage messageCountAsyncResult;

 

         private Account account;

 

         public AccountMessageVO(Account account) {

                  super();

                  this.account = account;

         }

 

         public int getMessageCount(DomainEvents domainEvents) {

                  if (messageCount == -1) {

                          if (messageCountAsyncResult == null) {

                                   messageCountAsyncResult =

domainEvents.computeAccountMessageCount(account.getUserIdLong());

                          } else {

                                   messageCount = (Integer) messageCountAsyncResult.getEventResult();

                          }

                  }

                  return messageCount;

         }

 

         public void update(int count) {

                  if (messageCount != -1) {

                          messageCount = messageCount + count;

                  }

 

}

}

 

 

 

调用顺序图如下:

 

 

 

即用即加载

比如图片,只有显示图片时才会需要图片的二进制数据,这个数据比较大,所以,一般从持久层加载图片时,只加载图片其他核心文字信息,如图片ID,名称等,当需要显示时,再加载二进制数据输出真正图片。

public byte[] getData() {

                  byte[] bytes = super.getData();

                  if (bytes != null)

                          return bytes;

                  preloadData();

                  bytes = (byte[]) imgDataAsyncResult.getEventResult();

                  this.setData(bytes);

                  return bytes;

         }

 

//预加载 可在JSP即将显示图片之前发出事件激活该方法

public void preloadData() {

    if (imgDataAsyncResult == null && domainEvents != null)

         imgDataAsyncResult = this.domainEvents.loadUploadEntity(this);

}

 

传统意义上,懒加载和异步都是好像不被人接受的,会带来比较差的性能,高延迟性,属于边缘技术,这其实是被误导了:并发策略可以解决延迟

懒加载和异步代表的并发策略实际是一种潮流趋势,特别是作为并行计算语言Scalaerlang的新亮点:函数式编程functional programming的特点

而最新发布JiveJdon3.9使用传统Java,基于Jdon框架6.2实现了领域模型层的懒加载和异步,可以完全克服HibernateORM框架带来的Lazy懒加载错误问题。

 

 

 

模型注射

领域模型中不应该含有技术组件架构的元素,这是一种理想目标境界,但是在实际操作过程中,可能还是需要一些技术组件功能需要引入到模型中,Jdon框架也提供了这种模型注射功能,一般可以将DomainEvents对象注射进入当前模型,这样,可以在当前模型的方法中,直接使用DomainEvents中的消息发送方法,反之,如果将当前模型和DomainEvents合二为一,也是可以的,但是本模型中方法就不能调用标注@Send的方法,这个方法只能供缓存以外的访问者调用了。

 

上图中,不但可以注射MyModelDomainEvent到模型MyModel中,偶尔也可以将一个领域服务MyModelService注射到MyModel中,这种方式相比DomainEvents的异步,这是同步方式。

组件应用

本章节讲述Jdon框架的基本配置和一些基本设计,是使用Jdon框架之前需要了解的章节,通过这些基本概念说明,可以让程序员对Jdon框架有一个大体概念上的轮廓。

本章节对于初学者不是必读,其中很多用法可在后面案例中学习,因此,本章节也可以直接跳过去,进入下一个章节阅读。

应用服务配置

 

jdonframework.xml是应用服务组件配置文件,文件名可自己自由定义,jdonframework.xml中主要是定义Model(模型)和Service(服务)两大要素。

jdonframework.xml最新定义由http://www.jdon.com/jdonframework.dtd规定。

<models>段落是定义应用系统的建模,一个应用系统有哪些详细具体的模型,可由Domain Model分析设计而来。<models>中的详细配置说明可见 数据模型增、删、改、查章节。

<services>段落是定义服务组件的配置,目前有两种主要服务组件配置:EJBPOJO

EJB服务组件配置如下:

<ejbService name="newsManager">

      <jndi name="NewsManager" />

      <ejbLocalObject class="news.ejb.NewsManagerLocal"/>

</ejbService>

每个ejbService组件有一个全局唯一的名字,如newsManager,有两个必须子定义:该EJBJNDI名称和其Localremote接口类。

POJO服务组件配置如下:

<pojoService name="userJdbcDao" class="news.container.UserJdbcDao">

       <constructor value="java:/NewsDS"/>

</pojoService>

POJO服务也必须有一个全局唯一名称,如userJdbcDao,以及它的class类定义。如果该POJO构造器有字符串变量,可在这里定义其变量的值,目前Jdon框架只支持构造器字符串变量注射。

如果该POJO服务需要引用其它服务,例如UserPrincipalImp类的构造器如下:

public UserPrincipalImp(UserDao userDao){

    this.userDao = userDao;

}  ……

UserPrincipalImp构造器需要引用UserDao子类实现,只需在jdonframework.xml中同时配置这两个服务组件即可,Jdon框架会自动配置它们之间的关系:

<pojoService name="userJdbcDao" class="news.container.UserJdbcDao">

       <constructor value="java:/NewsDS"/>

</pojoService>

<pojoService name="userPrincipal" class="news.container.UserPrincipalImp"/>

上面配置中news.container.UserJdbcDao是接口UserDao的子类实现,这样,直接通过userPrincipal这个名称可获得UserPrincipalImp的实例。

6.0版本以后可使用@Service(myName)替代以上XML配置。

基础组件配置说明

container.xmlJdon框架基础组件配置文件,container.xml中包含的组件将由Jdon框架在启动时向微容器(PicoContainer)中注册,至于这些组件之间的依赖关系由微容器解决,称为Ioc模式。

container.xml内容主要由每行配置组成,每行格式如下:

<component name="组件名称" class="POJO类名称" />

<component name="modelHandler" class="com.jdon.model.handler.XmlModelHandler" />

代表组件com.jdon.model.handler.XmlModelHandler,其名称为modelHandler,如果需要在程序中调用XmlModelHandle实例,只需要以modelHandler为名称从微容器中获取即可。

组件配置也可以带有参数,例如下行:

   <component name="cache" class="com.jdon.controller.cache.LRUCache" >

      <constructor value="cache.xml"/>

   </component>

LRUCache的类代码如下:

public class LRUCache implements Cache {

  public LRUCache(String configFileName) {

   PropsUtil propsUtil = new PropsUtil(configFileName);

    cache = new UtilCache(propsUtil);   

  }

    ……..

}

这样LRUCache 中的configFileName值就是cache.xml,在cache.xml中定义了有关缓存的一些设置参数。目前Jdon框架只支持构造器是纯字符串型,可多个字符串变量,但不能字类型和其它类型混淆在一起作为一个构造器的构造参数。如果需要多个类型作为构造参数,可新建一个包含字符串配置的类,这个类就可和其它类型一起作为一个构造器的构造参数了。

一般在container.xml中的组件是框架基本功能的类,不涉及到具体应用系统。

6.0版本以后可使用@Component(“myName”)替代以上XML配置。

拦截器组件配置说明

本篇为高级应用,初学者可以后再深入研究。

aspect.xml是关于拦截器组件配置,有两个方面:advice(拦截器Interceptor)和pointcut(切入点)两个方面配置,有关AOP的基本概念可见:http://www.jdon.com/AOPdesign/aspectJ.htm

Jdon AOP的设计目前功能比较简单,不包括标准AOP中的MixinIntroduction等功能;Pointcut不是针对每个class和方法,而是针对一组class(如POJO组或EJB组),拦截粒度最粗,与许多复杂完整的AOP框架(如AspectJSpring)不同的是:Jdon AOP在粒度方面是最粗的,AspectJ最细,Spring中等,如果你需要粒度细腻的AOP功能,还是推荐使用SpringAspectJ。目前这样设计是主要有两个原因:

每个类在运行时刻都实现动态拦截,在性能上有所损失,这如同职责链模式缺点一样。

在实际应用中,可以通过代理模式Proxy、装饰模式Decorator实现一些细腻度拦截,结合容器的Ioc特性,这两个代理模式使用起来非常方便,运行性能有一定提高。

Jdon AOP主要针对拦截器Interceptor设计,它可以为所有jdonframework.xml中定义的Service提供拦截器;所有的拦截器被放在一个拦截器链InterceptorsChain中。

Jdon AOP并没有为每个目标实例都提供拦截器配置的功能,在JdonAOP中,目标对象是以组为单位,而非每个实例,类似Cache/Pool等这些通用拦截器都是服务于所有目标对象。

JdonAOP拦截器目标对象组有三种:全部目标服务;EJB服务;POJO服务(EJB服务和POJO服务是在JdonFramework.xml中定义的ejbServicepojoService)。从而也决定了Pointcut非常简单。以下是aspect.xml中的配置:

<interceptor name="cacheInterceptor"

class="com.jdon.aop.interceptor.CacheInterceptor"

pointcut="services" />

其中pointcut有三种配置可选:services ; pojoServicesejbServices

拦截器配置也可以如组件配置一样,带有constructor参数,以便指定有关拦截器设置的配置文件名。

拦截器的加入不只是通过配置自己的aspect.xml可以加入,也可以通过程序实现,调用WebAppUtiladdInterceptor方法即可,该方法只要执行一次即可。

如何实现自己的拦截器?

本篇为高级应用,初学者可以后再深入研究。

以对象池拦截器PoolInterceptor为例,对象池是使用Aapche Commons Pool开源产品,对象池主要是为了提高POJO Service的运行性能,在没有对象池的情形下,POJO Service每次被访问时,要产生一个新的实例,如果并发访问用户量很大,JVM将会频繁创建和销毁大量对象实例,这无疑是耗费性能的。

使用对象池则可以重复使用一个先前以前生成的POJO Service实例,这也是Flyweight模式一个应用。

对象池如何加入到Jdon框架中呢?有两种方式:1.替代原来的POJO Service实例创建方式,属于PojoServiceFactory实现(SpringTargetSource实现);2.通过拦截器,拦截在原来的PojoServiceFactory实现之前发生作用,同时屏蔽了原来的PojoServiceFactory实现。Jdon框架采取的这一种方式。

首先,为拦截器准备好基础组件。 对象池拦截器有两个:对象池com.jdon.controller.pool.CommonsPoolAdapter和对象池工厂com.jdon.controller.pool.CommonsPoolFactory,这两个实现是安装Apache Pool要求实现的。

第二步,需要确定拦截器的Pointcut范围。前面已经说明,在Jdon框架中有三个Pointcut范围:所有服务、所有EJB服务和所有POJO服务,这种划分目标的粒度很粗糙,我们有时希望为一些服务群指定一个统一的拦截器,例如,我们不想为所有POJO服务提供对象池,想为指定的一些目标服务(如访问量大,且没有状态需要保存的)提供对象池,那么如何实现呢?这实际是如何自由划分我们自己的目标群(或单个目标实例)的问题

我们只要制作一个空接口Poolable,其中无任何方法,只要将我们需要对象池的目标类实现这个接口即可,例如com.jdon.security.web.AbstractUserPrincipal多继承一个接口Poolable,那么AbstractUserPrincipal所有的子类都将被赋予对象池功能,所有子类实例获得是从对象池中借入,然后自动返回。

5.6版本以后,增加Annotation @Poolable替代接口Poolable

这种通过编程而不是配置实现的Pointcut可灵活实现单个目标实例拦截或一组目标实例拦截,可由程序员自由指定划分,非常灵活,节省了琐碎的配置,至于Pointcut详细到类方法适配,可在拦截器中代码指定,如缓存com.jdon.aop.interceptor.CacheInterceptor只拦截目标服务类中的getXXXX方法,并且该方法的返回结果类型属于Model子类,为了提高性能,CacheInterceptor将符合条件的所有方法在第一次检查合格后缓存起来,这样,下次无需再次检查,省却每次检查。

第三步,确定拦截器在整个拦截器链条中的位置。这要根据不同拦截器功能决定,对象池拦截器由于是决定目标服务实例产生方式,因此,它应该最后终点,也就是在拦截器链中最后一个执行,aspect.xml中配置拦截器是有先后的:

  <interceptor name="cacheInterceptor"

class="com.jdon.aop.interceptor.CacheInterceptor" pointcut="services" />

 

  <interceptor name="poolInterceptor"

class="com.jdon.aop.interceptor.PoolInterceptor" pointcut="pojoServices" />

拦截器链中排序是根据Interceptorname值排序的,cacheInterceptor第一个字母是c,而poolInterceptor第一个字母是p,按照字母排列顺序,cacheIntercepotr排在poolInterceptor之前,在运行中cacheInterceptor首先运行,在以后增加新拦截器时,要注意将poolInterceptor排在最后一个,name值是可以任意指定的,如为了使PoolInterceptor排在最后一个,可命名为zpoolInterceptor,前面带一个z字母。

第四步,确定拦截器激活行为是在拦截点之前还是之后,或者前后兼顾,这就是advice的三种性质:BeforeAfterAround,这分别是针对具体拦截点jointcut位置而言。

虽然Jdon框架没有象Spring那样提供具体的BeforeAdviceAfterReturningAdvice等接口,其实这些都可以有程序员自己直接实现。

Jdon框架的拦截器和Spring等遵循aopallianceAOP框架继承同一个接口MethodInterceptor,也就是说,一个拦截器在这些不同AOP框架之间可以通用,具体实现方式不同而已。

例如,一个AroundAdvice实现如下代码,它们都只需要完成invoke方法内尔:

public class AroundAdvice   implements MethodInterceptor

{

   public Object invoke( MethodInvocation invocation)  throws Throwable

   {

      System.out.println("Hello world! (by " + this.getClass().getName() + ")");

      invocation.proceed();

      System.out.println("Goodbye! (by " + this.getClass().getName() + ")");

      return null;

   }

}

beforeAdvice实现代码如下:

public class BeforeAdvice   implements MethodInterceptor

{

   public Object invoke( MethodInvocation invocation)  throws Throwable

   {

      System.out.println("Hello world! (by " + this.getClass().getName() + ")");

      invocation.proceed();

   }

}

由此可以注意到,invocation.proceed()类似一个joincut点,这个方法类似400接力比赛中的传接力棒,将接力棒传到下一个拦截器中,非常类似与servletFilter中的chain.doFilter(request, response);拦截器一些更详细说明可参考Spring相关文档,如下面网址:http://www.onjava.com/pub/a/onjava/2004/10/20/springaop2.html,和Jdon拦截器原理基本一致。

考察对象池拦截器功能,它实际是一个around advice,在joincut之前需要从对象池借用一个目标服务实例,然后需要返回对象池。com.jdon.aop.interceptor.PoolInterceptor主要核心代码如下:

Pool pool = commonsPoolFactory.getPool(); //获得对象池

Object poa = null;

Object result = null;

 try {

    poa = pool.acquirePoolable(); //借用一个服务对象

    Debug.logVerbose(" borrow a object:" + targetMetaDef.getClassName()

                    + " from pool", module);

    //set the object that borrowed from pool to MethodInvocation

    //so later other Interceptors or MethodInvocation can use it!

    proxyMethodInvocation.setThis(poa);  //放入invocation,以便供使用

    result = invocation.proceed();

} catch (Exception ex) {

    Debug.logError(ex, module);

 } finally {

    if (poa != null) {

      pool.releasePoolable(poa);  //将服务对象归还对象池

      Debug.logVerbose(" realease a object:" + targetMetaDef.getClassName()

                        + " to pool", module);

    }

 }

 return result;

经过上述四步考虑,我们基本可以在Jdon框架动态plugin自己的拦截器,关于对象池拦截器有一点需要说明的,首先EJB服务因为有EJB容器提供对象池功能,因此不需要对象池了,在POJO服务中,如果你的POJO服务设计成有状态的,或者你想让其成为单例,就不能使用对象池,只要你这个POJO服务类不继承Poolable或没有@Poolable注释,它的获得是通过组件实例方式获得,参考后面“组件实例和服务实例”章节。

自己实现的拦截器是在myaspect.xml中配置,至于如何将myaspect.xml告诉Jdon框架,可见启动自定义框架组件一节。

6.2版本以后可使用@Interceptor(“myName”)替代以上XML配置。

 

组件替代

我们前面说过,Jdon框架的一个最大特点是实现了对象的可替代性,Jdon框架应用系统组件和框架本身组件都是可以更换的,这样,程序员可以根据自己开发的具体特点,通过编制自己的组件,丰富Jdon框架的一些功能。

那么自己编制的组件如何放入Jdon框架,让Jdon框架识别呢?

通过前面介绍已经可能知道,通过修改container.xmlaspect.xml这样的配置文件即可。

一般情况下,container.xml是包含在Jdon框架的压缩包jdonFramework.jar中,不便于修改,我们可以用winrar等解压工具将jdonFramework.jarMETA-INF目录下的container.xmlaspect.xml解压出来,解压出来的container.xml也必须放置在系统的classpath中,当然,你修改container.xml以后,再将修改后的container.xml通过winrar工具再覆盖jdonFramework.jar中原来的container.xml也可以。

具体修改办法,以container.xml下面一句为例子:

   <component name="sessionContextSetup" class="com.jdon.security.web.HttpRequestUserSetup" />

其中com.jdon.security.web.HttpRequestUserSetupSessionContextSetup接口的子类,如果我们要实现自己的SessionContextSetup子类,例如为MyHttpRequestUserSetup,那么编制好MyHttpRequestUserSetup后,将配置中替代为MyHttpRequestUserSetup即可:

   <component name="sessionContextSetup" class="com.mylib.MyHttpRequestUserSetup" />

注意,我们自己子类MyHttpRequestUserSetup也必须放置在系统的classpath,最简单的办法是:将MyHttpRequestUserSetup等类也打包成.jar包,和jdonFramework.jar一起部署。

如果你需要增加自己的组件,那么只要定义mycontainer.xmlmyaspect.xml即可,然后将这两个配置通过启动配置告诉Jdon框架。

 

 

组件实例和服务实例

Jdon框架中, POJO实例分为两种性质:组件实例和服务实例。

组件实例是指那些注册在容器中的普通Javabeans,它们是单例的,也就是你每次获得的组件实例都是同一个实例,也就是说是单态的。

组件POJO获得方法是通过WebAppUtilgetComponentInstance方法,例如在container.xml中有如下组件定义:

<component name="modelManager" class="com.jdon.model.ModelManagerImp" />

在程序中如果要获得ModelManagerImp组件实例的方法是:

ModelManager modelManager =

(ModelManager)WebAppUtil.getComponentInstance(“modelManager”, sc);

组件实例获得的原理实际是直接在微容器中寻找以前注册过的那些POJO,相当于直接从XML配置中直接读取组件实例配置。

服务实例是指在jdonframework.xml中定义的ejbServicepojoService,当然它们也可以组件实例方式获得,但是如果以组件实例方式获得,AOP功能将失效;而以服务实例方式获得的话,在aspect.xml中定义的拦截器功能将激活。

EJB服务一定是通过服务实例方式获得,只有普通JavaBeansPOJO)才可能有这两种方式。

因此,除非特殊需要,一般推荐在应用系统中,通过获得服务实例方式来获得jdonframework.xml中定义的服务实例。

如在jdonframework.xml中有如下服务组件定:

<pojoService name="userJdbcDao" class="news.container.UserJdbcDao">

       <constructor value="java:/NewsDS"/>

</pojoService>

获得服务实例的方法代码如下:

UserDao ud = (UserDao)WebAppUtil.getService(“userJdbcDao”, request);

UserDaoUserJdbcDao的接口。注意,这里必须getService结果必须下塑为接口类型,不能是抽象类或普通类,这也是与getComponentInstance不同所在。

如果你在aspect.xmlpojoServices都配置以对象池Pool拦截器,那么上面代码将是从对象池中获取一个已经事先生成的实例。

如何获得一个POJO服务实例?

在上面章节说明了服务实例和组件实例的区别,在jdonframework.xml中配置POJO既可以组件实例获得,也可以服务实例获得。

首先,我们需要在jdonframework.xml中定义自己的POJO服务,例如在jdonframework.xml有如下一行定义:

<pojoService name="userJdbcDao" class="news.container.UserJdbcDao">

那么在应用程序中需要访问UserJdbcDao 实例有以下两种方式:

第一. 通过WebAppUtil工具类的getService方法获得服务实例,如:

 

UserDao ud = (UserDao)WebAppUtil.getService(“userJdbcDao”, request);

 

getService方法每次返回的一个新的服务实例对象,相当于new一对象。如果对象池拦截器被配置,那么这里返回的就是从对象池中借用的一个对象。

 

第二. 通过WebAppUtil工具类的getComponentInstance方法获得组件实例,这也是获得UserJdbcDao一个实例,与服务实例不同的是,每次获得组件实例是同一个对象,因此,如果这个服务中如果包含上次访问的状态或数据,下次访问必须使用到这些状态和数据,那么就必须使用getComponentInstance方法获得服务实例。

注意,以上方式是假定你获得一个POJO实例,是为了使用它,也就是说,是为了访问它的方法,如访问userJdbcDaogetName方法,就要使用上述方式。

如果你不是为了使用它,而是作为别的POJO服务的输入参数,如构造器的输入参数,那么完全不必通过上述方式,你只要直接使用上述方式获得那个POJO服务的实例就可以,因为容器自动完成它们的匹配。

还有一点要求注意的是:使用getService获得服务实例,必须该服务类有一个接口,这样才能将getService downcasting下塑为其接口,否则只能是一个普通Object,你获得后无法使用它。当然getComponentInstance没有这样限制。

如何编写一个POJO 类?

既然在Jdon框架中获得POJO服务这么方便,那么POJO服务类的编写是否有特殊规定,回答是没有,就是一个普通的Java类,当然如果你需要在这个类引用其他类,最好将其他类作为构造器参数,如A类中引用B类,A类的写法如下:

class A {

    private B b;

    public A(B b){

        this.b = b;

}

    ….

}

这样,在jdonframework.xml中配置如下两行:

<pojoService name=" a" class="A">

<pojoService name=" b" class="B">

 

如果你希望你的POJO服务能够以对象池形式被访问,那么你的类需要implements com.jdon.controller.pool.Poolable或者在类前加入 @Poolable元注解Annotation

例如 上面的A继承了Poolable或加入了@Poolable元注解Annotation,那么当有并发两个以上客户端访问服务器时,对象池将同时提供两个以上A实例为客户端请求服务,提高了并发访问量。

注意,当A的实例同时为两个以上时,A引用的B是否也是每个A实例拥有一个B实例呢?也就是说,B实例是否也是两个以上?

这取决于AB的调用方式,如果AB都在jdonframework.xml中配置,也就是都被注册到Jdon容器中,那么此时B永远只有一个,也就是单例的。

如果B的创建是在A内部,如下:

class A {

    private B b  = new B();

    ….

}

这样,B实例的创建是跟随A实例一起创建,因此,A有多少个,B就有多少个。

 

如何获得一个POJO服务的运行结果

在应用系统中,我们不但可以通过“如何获得一个POJO服务实例获得一个POJO服务实例,然后在通过代码调用其方法,获得其运行结果,例如写入代码:

 

UserDao ud = (UserDao)WebAppUtil.getService(“userJdbcDao”, request);

userJdbcDao.getName(); //获得一个POJO服务的运行结果

 

除此之外,Jdon框架还可以直接获得POJO服务的运行结果,只要你告诉它POJO类名、需要调用的方法名和相关方法参数类型和值,借用Java Method Relection机制,Jdon框架可以直接获得运行结果。

实现这个功能,只要和接口com.jdon.controller.service.Service打交道即可:

public interface Service {

   

    public Object execute(String name,

                                       MethodMetaArgs methodMetaArgs,

                                       HttpServletRequest request)  throws Exception;

   

   

    public Object execute(TargetMetaDef targetMetaDef,

                          MethodMetaArgs methodMetaArgs,

                          HttpServletRequest request)  throws Exception;

 

 

}

Service提供了两种获得某个POJO服务运行结果的方法。一个是以Jdonframework.xml中配置的POJO服务名称为主要参数,这是经常使用的一个情况。

MethodMetaArgs是包含调用的方法名、方法参数类型和方法参数值。

这种调用方式适合于POJO服务配置式调用,也就是说,通过编写自己的XML配置文件也可以实现如同写代码一样的服务调用和运行。

 

如何获得一个EJB服务实例?

Jdon框架不只可以支持普通JavaBeans,也就是POJO结构,也支持EJB架构,使用EJB的好处是能够获得可伸缩的、分布式的强大计算性能,当然EJB的开发需要借助JBuilder之类商业开发工具的图形开发功能才能方便快速实现。

使用Jdon框架可以开发出基于Struts+Jdon+EJB的标准J2EE架构系统,Jdon框架在标准的J2EE架构中所处位置如下图的红色标记:

从上图底部向上看,是J2EE架构从抽象到具体技术的演变划分,J2EE是一个中间件系统,那么中间层包括哪些部分呢?主要区分Web层和EJB层,Web层主要是完成用户交互操作功能,Jdon框架主要部分就位于这个Web层最後端,直接和EJB层打交道;同时Jdon框架有一部分JdonDAO运行在EJB容器中。

如何在表现层如Struts中获得一个EJB实例呢?

如同获得POJO实例一样,也是通过WebAppUtil工具类的getService方法获得,如果在jdonframework.xml中有如下配置:

<ejbService name="newsManager">

      <jndi name="NewsManager" />

      <ejbLocalObject class="news.ejb.NewsManagerLocal"/>

</ejbService>

那么,通过下面代码就可以访问NewsManagerLocal

NewsManagerLocal nm = (NewsManagerLocal)WebAppUtil.getService(“newsManager”, request);

这样就访问nm这个EJB对象的方法了。

如何编写一个EJB类?

Jdon框架对于一个EJB类的编写没有任何约束和规定。

但是,如果你原来使用POJO服务实现你的服务层,想无缝迁移到EJB服务,那么此时你的Session Bean需要继承implements 原来POJO服务的接口,同时在jdonframework.xmlejbService加入interface配置,指定原来POJO服务接口类,这样才能保证原来代码中通过getService方法获得服务实例的调用代码无需改变:

<ejbService name="testService2" >

      <jndi name="TestEJB" />

      <ejbLocalObject class="com.jdon.framework.test.ejb.TestEJBLocal"/>

      <interface class="com.jdon.framework.test.service.TestService" />

    </ejbService>     

  

如何获得EJB服务运行结果?

使用方式和获得POJO服务一样。

这种方式运行原理简要如下:当知道一个EJB/POJO的接口,通过Proxy.newProxyInstance生成一个动态代理实例(InvocationHandler的实现子类)即可,以后对EJB/POJO的调用,实际由这个动态代理实例的invoke自动激活,从而使用Method Reflection实现EJB/POJO的调用。

 


 

AOP拦截器

Jdon框架拦截器在6.2版本以后得到加强和细化,不过和AspectJAOP框架有些区别,主要是方便性方面,这些AOP框架有专门的复杂Ponitcut表达脚本,需要再学习,而Jdon框架则还是使用Java完成复杂的拦截方式。

有两种拦截器写法,一种是写在被拦截的类代码中,而拦截器则无特别写法,解放了拦截器;另外一种是拦截语法写在拦截器中,解放了被拦截者,实际中可根据自己的情况灵活选用。

 

@Introduce

@Introduce(名称) 为当前类引入拦截器,可以用在@Model@Component中,名称是拦截器的@Component(名称).   例如@Introduce("c") --- @Component(“c”)

在当前类具体方法上使用@Before @After@Around三种拦截器激活的方式。注意使用@Around对拦截器有特定写法要求,其他无。

被引入的拦截器c的代码如下,没有特别要求,只要配置@Component(“c”)就可以,或者使用XML配置:<component name=”c” class=”xxxxxx” />也可以。

@Component("c")

public class C {

 

   //被拦截器的@Input参数将注射到inVal

    public Object testOne(Object inVal) {

           ….

    }     //testOne 方法return 结果将被注射到被拦截器的@return

 

 

   //被拦截器的方法myMethod2返回值将被引入此方法的inVale

   public Object testWo(Object inVal) {

          ……

   }

 

}

 

 

@Introduce@Around则是一个特殊情况,因为around表示在被拦截点周围,实际是Before + After综合写法,@Around需要对拦截器写法有一些特殊要求,如下:

@Interceptor(aroundAdvice)//1. 需要表明自己是Inteceptor

public class AroundAdvice implements MethodInterceptor {

 

     //2.需要实现 org.aopalliance.intercept.MethodInterceptor接口

     //3.完成接口的Invoke方法

    public Object invoke(MethodInvocation invocation) throws Throwable

       Object o = invocation.proceed();//在此方法前后写包围around代码。

   }

 

 

@Interceptor

@Interceptor是将拦截定义在拦截器这边的语法,和AspectJ Spring写法类似,但是没有他们的复杂pointcut专门表达式。

 

//1. 指定被拦截的类是@Component(a)@Component(c)

@Interceptor(name = "myInterceptor", pointcut = "a,c")

public class MyInterceptor implements MethodInterceptor {

 

               //2.需要实现 org.aopalliance.intercept.MethodInterceptor接口

               //3.完成接口的Invoke方法

     public Object invoke(MethodInvocation methodInvocation) throws java.lang.Throwable {

    

}

 

如果直接写@Interceptor(“myName”) 那么就表示该拦截器的拦截点pointcut是针对所有ServicePOJOServices,相当于在aspectj.xml中的配置

 

@Interceptor中的pointcut是指被拦截的类名称,可以是多个类一起拦截。

 

 

 

 

 


增删改查(CRUD)快速开发

Jdon框架提供了基于Struts的快速开发,可以快速开发出数据的增删改查(CRUD)的MVC配置以及批量查询等功能。

本章是与Jdon框架其它功能分离的,你可以只使用Jdon框架的组件管理和AOP功能,而不必使用基于Struts的快速开发功能,随着新的表现层技术出现,Jdon框架将推出基于的技术如JSF的快速开发框架。

关于为什么使用jdon框架的CRUD功能,除了在开发速度上有极大提高以外,在设计上也有很多优点:

边界类,控制类和业务接口的关系:

http://www.jdon.com/jive/article.jsp?forum=91&thread=21245

J2EE中几种业务代理模式的实现和比较:

http://www.jdon.com/artichect/businessproxy.htm

 

 

struts基础简述

Jdon框架的CRUD功能是基于struts实现,主要工作是表现层的配置,那么是否需要程序员熟悉struts呢?其实完全不必。

但是,需要了解struts的一些基础:struts主要有一个配置文件,在Web目录的WEB-INFstruts-config.xml,其中主要有两部分配置:ActionFormAction

ActionForm配置:主要是配置你设计的ActionForm子类;例如:

<struts-config>

  <form-beans>

    <form-bean name="accountForm"

type="com.jdon.framework.samples.jpetstore.presentation.form.AccountForm"/>

  </form-beans>

  …..

</struts-config>

Action配置:相当于一个servlet,也需要在struts-config.xmaction-mappings l配置,例如:

<action-mappings>

         <action name="accountForm" path="/shop/newAccountForm"

type="com.jdon.strutsutil.ModelViewAction" scope="request">

      <forward name="create" path="/account/NewAccountForm.jsp" />

    </action>

</action-mappings>

action中配置简要说明:

nameActionForm名称,是前面ActionForm名称;

path是该action在浏览器中调用的url名称,使用http://xxxx/web名称/shop/newAccountForm.do就可以调用这个action,在path值后面加一个.do即可,.do是在web.xml中配置的:

  <servlet-mapping>

    <servlet-name>action</servlet-name>

    <url-pattern>*.do</url-pattern>

  </servlet-mapping>

你可以改变*.do配置,改为*.shtml,那么上面action(Servlet)调用变成http://xxxx/web名称/shop/newAccountForm.shtml

type是你继承strutsAction实现子类:如com.jdon.strutsutil.ModelViewActionJdon框架的实现子类。

Scope是值你的ActionForm生存周期,一般有requestsession,相当于JspuseBean中的scope,表示ActionForm实例有效范围,request表示用户发出一个请求周期;session表示是从用户登陆到退出为止,这个ActionForm实例一直存在,你可以引用同一个实例。

<forward name="create" path="/account/NewAccountForm.jsp" />

这表示Action执行完成后,需要推出的Jsp页面,name值是你在Action执行子类中定义的名称;path是你的jsp页面相对路径。

所以,编写一个传统意义上的Jsp页面,在struts变成下面几步:

编写一个ActionForm实现子类

编写一个Action实现子类,完成其中方法execute内容。

配置struts-config.xml

使用struts标签编写Jsp页面,这时的Jsp页面和传统的Jsp相比:已经没有Java代码,完全Html语法。

由以上可见,struts在实现设计优化目的同时,实际增加编程复杂性,Jdon框架试图保证struts优点的前提下,简化标程复杂性,CRUD的功能对上述步骤优化如下:

ActionFormModel代码复制;

无需编写Action实现子类;

模板化配置struts-config.xml,参考别的配置,稍微修改即可。

简化Jsp页面编写数量;四个功能只需编写一个Jsp

strtus学习资源:http://www.jdon.com/idea/strutsapp/04005.htm

CRUD流程原理

传统的直接基于Strut CRUD实现方式:增删改查是四个操作在传统表现层中需要分四次流程,每次流程中有大部分开发或配置类似,这种低层次的重复劳动降低开发者的积极性。

Jdon框架抽象出传统增删改查四个流程中的共同部分,并将其配置固化,实现一定开发模式限制,从而大大提高开发效率。具体设计原理如下:

1)一般情况下,一个系统的操作用户(以下简称用户)新增或修改数据,首先要推送给他一个Jsp页面,如果是新增页面,就是一个空白表单的Jsp页面;如果是修改页面,则先到数据库中查询获得原来的数据,然后推出一个有数据表单的Jsp页面,用户才能在原来的数据上修改或编辑。

由于在MVC模式中,Jsp页面只是一个页面输出,或者说不能有任何Java功能实现,因此上面修改页面推出前需要查询数据库这个需要Java实现的功能不能在Jsp页面中实现,只能在Jsp页面前加一个Action,这样,修改页面的推出流程变为不是直接调用Jsp页面,而是:action.do ---> Jsp页面,首先调用Action;然后才由Action推出Jsp页面。

这个Action实现我们称为ViewAction,专门用于控制输出Jsp界面,新增Jsp页面的推出前我们也加上这个ViewAction,这样无论是新增Jsp页面或修改Jsp页面,都是由ViewAction推出,那么到底是推出新增Jsp页面还是修改Jsp页面呢?

关键是ViewAction的输入参数Action的值,根据Action的值来判断是新增还是修改。如果设置Action值如为空或为create值(如http://xxx/viewAction.do?action=create),则直接输出新增性质的JSP页面;而Action值如为edit(如http://xxx/viewAction.do?action=edit),则是要求输出进行编辑页面,根据ID查询数据库获得存在的数据,然后输出编辑修改性质的JSP页面。

当然,在ViewAction中还有一些具体参数的检查,如果是编辑,则关键字ID不能为空,因为后台要根据此ID为主键,查询数据库其相应的记录,如果数据库未查询到该主键的记录,则需要显示无此记录等信息。

2)创建有关该数据的JSP页面,既用于新增页面,也用于修改页面。将该Jsp页面作为ViewAction的输出页面。该Jsp页面结构如下:

<html:form action=”/XXXSaveAction.do”>

   <html:hidden property="action" />

<!—action的值是调用viewAction.do?action参数的值  -->

   …….

</html:form>

以上是ViewAction  -à  表单Jsp 页面流程

 

 

3 创建SaveAction

当用户填写完该Jsp页面中的表单数据将提交给一个新Action子类实现:专门用于接受表单数据并保存持久化它们。SaveAction用来接受提交表单的数据,不同于ViewAction专门用于输出Jsp表单页面,该Action专门用于接受Jsp表单页面提交的数据。

SaveAction中主要是调用业务层Service实现数据持久化操作,调用Service之前,需要将表单的数据ActionForm转为一个对象(DTO对象),然后作为方法参数传送给Service具体方法, Service处理完成后,返回结果对象,SaveAction还需要检查Service是否操作成功等。

一个数据的增删改查流程有上面总结的流程组成:

1. ViewAction  -à  表单Jsp 页面 

2. 用户填写表单页面提交 -à SaveAction  --à 结果Jsp页面

一个数据新增删除修改流程需要创建两个Action,一个Jsp页面;当然Struts 1.2中已经通过DispatchAction解决了需要创建两个Action问题,但是还是需要Action编码,而Jdon框架提供了Struts缺省Action实现(也就是ViewAction SaveAction 代码实现),就不需要另外Action编码,只要直接进行strus-config.xml配置即可。

ModelModelForm

Model是域模型,是采取领域模型分析法从系统需求分析中获得的,反映了应用系统的本质,Model是一个简单的POJO,属于数据类型的POJO,区别于POJO服务。从传统意义上理解,Model设计相当数据表设计,在传统的过程化编程中,一个数据库信息系统设计之前我们总是从数据表设计开始,而现在我们提倡是从域模型提炼开始。

Jdon框架对模型对象建立有两个要求,继承com.jdon.controller.model.Model,每个Model有一个主键。

每个Model需要一个主键

每个Model必须有一个主键,就如同每个数据表设计都有主键一样,Model的主键是和其对应的数据表主键在类型上是一致的。例如一个ModelAccount如下:

 

public class Account extends Model {

 

  private String username;

  private String password;

  private String email;

  private String firstName;

  private String lastName;

  ……

 

}

其持久化数据表account的表结构

 

CREATE TABLE ACCOUNT (

  username varchar(80) NOT NULL default '',

  email varchar(80) NOT NULL default '',

  ……

  PRIMARY KEY  (username)

}

account表的主键是username数据类型是varchar通过JDBC对应可知其对应的Java类型是String那么Account模型的主键也必须是String当然主键名称可以不一样Accountusernameaccount的表的主键可以是userId。下面以MySQL为例,说明JDBC缺省在对象和数据表之间的类型对应:

 

Model主键类型

数据表主键类型

String

varcharchar

Integer

int

Long

BIGINT

 

主键类型(包括Model和数据表)一般推荐统一使用String Integer/intLong/long。缺省建议使用StringOracle数据库处理字符串中空格有些麻烦,需要特别注意)。下载案例包中SimpleJdonFrameworkTest主键是使用String类型,而simpleMessage案例包主键类型是Long类型。

注意:模型类中一定要提供主键的settergetter方法,Jdon框架内部是根据主键的getter方法返回类型察知主键的类型。

如果你的数据表没有主键怎么办?那么强制给它一个主键,这可以由一个专门序列器产生。

在使用本框架前提下,数据表字段不同类型选择所带来的性能等差异非常小,几乎忽略不计。相关数据类型讨论可见:http://www.jdon.com/jive/article.jsp?forum=91&thread=23875

 

ModelModelIF

Jdon框架提供的com.jdon.controller.model.Model是一个抽象类,这需要你的Domain Model继承这个抽象类,由于Java的单继承特点,有可能使你的Domain Model类再不能继承其他抽象类,为此,在Jdon框架1.4版本以后,提供了com.jdon.controller.model.ModelIF接口,5.6版本以后,提供了@Model元注解。

本说明书中谈到Model对象,也同时意指ModelIF,也全部可以使用@Model来替代。

Model配置

Modeljdonframework.xml中配置,如下:

<model key="username" class ="com.jdon.framework.samples.jpetstore.domain.Account">

     …..

</model>

key的值就是指定Model的主键,这里是usernameclassAccount的类,这样就定义了一个Model

Model配置除定义Model自身属性以外,还需要定义了子属性:actionFormhandler,这两个定义在下面章节描述。

 

ModelFormModel的映射

Jdon框架通过映射设计,保证表现层、Jdon框架、服务层和持久层之间能够解耦独立,相互可插拔、脱离或组合。

ModelForm相当于StrutsActionForm,属于界面对象,使用Jdon框架一个要求是:ModelForm的内容(字段/方法)需要大于等于Model的内容,这样,才能将表现层的数据传送到Model业务层处理,ModelForm内容可以多于Model内容,这些多余内容可能只与界面有关,不涉及业务逻辑。

ModelModelForm映射是通过相同字段拷贝实现的,也就是这两个类之间有相同的字段属性,那么字段属性的值可以在他们之间拷贝转移。例如Model Account的代码:

public class Account extends Model {//也可以用@Model替代,无需继承Model

 

  private String username;

  private String password;

  private String email;

  private String firstName;

  private String lastName;

  ……

 

}

那么其ModelForm的类代码如下

public class AccountForm extends ModelForm {

 

  private String username;

  private String password;

  private String email;

  private String firstName;

  private String lastName;

  ……

 

}

这样两者代码保证它们之间的映射,这是使用Jdon框架的规则之一。

Jdon框架是使用org.apache.commons.beanutils.PropertyUtils.copyProperties方法在这两个类的对象之间进行字段拷贝的,因此两个类对应字段类型必须一致。

ModelForm配置

ModelForm就是ActionForm,因此只需要在struts-config.xml<form-beans>中定义ActionForm就可以,如下:

<struts-config>

  <form-beans>

    <form-bean name="accountFrom"

type="com.jdon.framework.samples.jpetstore.presentation.form.AccountForm"/>

  </form-beans>

</struts-config>

注意ModelForm的名字是accountFrom,因为ModelModelForm是映射对应关系,我们需要告诉Jdon框架这种对应关系。

那么,拓展前面的Model配置,在jdonframework.xml配置如下:

<model key="username" class ="com.jdon.framework.samples.jpetstore.domain.Account">

      <actionForm name="accountForm"/>

      ……

</model>

jdonframework.xml中的配置是让ModelModelForm有唯一的对应关系。

jdonframework.xml中配置的ActionForm名称必须是全局唯一的,也就是说,如果有两个ActionForm名称一样的配置,就是它们的Model class值不一样,Jdon框架也视为是一样的。

CRUD基本流程

这里首先介绍一下Jdon框架在上面思路上延伸抽象设计的思路:

一个数据的增删改查流程有上面总结的流程组成:

1.ViewAction  -à  表单Jsp 页面

2.用户提交已经填入数据的Jsp页面 -à SaveAction  --à 结果Jsp页面

如下面流程图:通过浏览器网址(或Html链接)调用我们推出用于新增或编辑的页面,确定保存输入的数据后,激活ModelSaveActionModelSaveAction通过jdonframework.xml的配置,调用相应的Service类,这个Service类是你自己编制的,根据原始网址或Html链接调用的参数action不一样,调用不同的Service接口中相应的方法。

上图中,在ModelSaveAction步骤有一个ModelHandler,它其实是真正和Service接口打交道的类,必要时,你可以自己继承实现自己的ModelHandler子类,它与ModelSaveAction的关系如下:

 

ModelViewActionModelSaveAction中有关具体每个数据有不同的操作,都委托给ModelHandler实现,用户使用框架时,只要继承实现ModelHandler,填补异性方法就可以。缺省情况下,上图中的jdonframework.xml配置实际就是ModelHandlel实现,所以一般不必自己再编制ModelHandler实现了,但是特殊情况下可能还是需要的,提供缺省和特殊两种方便。

下面我们针对上面流程图对每一个环节详细描述如下:

CRUD页面显示

.ViewAction  -à  表单Jsp 页面的流程图如下:

 

 

调用ModelViewAction

Jdon框架中的com.jdon.strutsutil.ModelViewAction主要是实现在推出让用户新增数据或编辑数据的Jsp页面的之前的准备工作。它主要是做Jsp页面推出前的准备工作,然后推出不同的Jsp页面。

ModelViewAction根据调用参数action的值,判断用户发出的是新增或编辑命令。

Action有两个规定值create edit,也就是说,只有三种URL调用形式:

新增URL http://xxxx/XXXModelViewAction.do?action=create

新增URL http://xxxx/XXXModelViewAction.do

编辑URL http://xxxx/XXXModelViewAction.do?action=edit

如果用户在浏览器发出这三种URL调用,将激活ModelViewActionModelViewAction将根据参数action的值分别推出用于创建性或编辑性的页面。下面分别详细说明这两种流程:

1.在创建性Jsp页面推出之前,要做一些准备工作,由于Jsp页面中表单数据(<form></form>之间数据)是由ModelFormActionForm)概括的,所以,创建性Jsp页面推出前的准备工作主要是ModelForm的初始化创建,ModelFormStrutsActionForm继承者,ModelForm是抽象页面表单数据,它是Model在表现层的映射影子。

ModelForm的初始化创建很重要,它决定了推出给用户创建性页面的内容,需要程序员介入度很高, Jdon框架提供有几种初始化ModelForm方法:

首先检查ModelHanlderinitForm方法,检查用户有无自己实现初始化ModelForm实现;如果无,则用Struts的自动创建ActionFormModelForm),这时的ModelForm实例是空对象,这时还有另外一种方式提供用户初始化ModelForm实例的方法:调用ModelhandlerinitModel方法,将该方法返回的Model对象拷贝到ModelForm中,这符合ModelModelForm相互映射的关系。下面章节将描述ModelHandlerinitForminitModel方法有何区别。

2.推出编辑性页面之前的准备工作主要是:根据主键查询从服务层获得一个已经存在的数据模型,简单地说:根据主键从数据库查询已经存在的一个记录,这样,推出的Jsp页面中包括数据的表单,用户可修改编辑。

这个工作涉及两部分:ModelForm创建;然后从服务层获得一个有数据的Model,将Model数据拷贝到ModelForm实例中。

注意:从Jdon Framework 1.4以后,参数名不一定是action,可以是method,也就是可以如下调用:

新增URL http://xxxx/XXXModelViewAction.do?method=create

新增URL http://xxxx/XXXModelViewAction.do

编辑URL http://xxxx/XXXModelViewAction.do?method=edit

这里的actionmethod参数值将传送到ModelViewAction推出的Jsp页面中。

 

Struts配置和Jsp

根据不同域模型,我们有相应的不同的配置,例如:有一个ModelA,那么为了推出A新增或修改或删除的页面,我们需要在struts-config.xml配置如下:

    <action name="aActionForm" path="/aAction"  type="com.jdon.strutsutil.ModelViewAction">

      <forward name="create" path="/a.jsp" />

      <forward name="edit" path="/a.jsp" />

    </action>

这是一个标准的Action配置,AActionFormaActionForm,新增修改删除AJsp页面为a.jsp,如上面配置,这样,如果用户从浏览器网址如下调用:

http://xxxxx/aAction.do?action=create

这样调用将推出a.jsp用于新增;

http://xxxxx/aAction.do?action=edit

这样调用将推出a.jsp用于修改删除。

再举例,如果ModelBBActionFormbActionFormJsp页面为b.jsp,那么Struts-config.xml只要如下配置:

    <action name="bActionForm" path="/bAction" type="com.jdon.strutsutil.ModelViewAction">

      <forward name="create" path="/b.jsp" />

      <forward name="edit" path="/b.jsp" />

    </action>

如果Modelc,那么struts-config.xml配置如下:

    <action name="cActionForm" path="/cAction" type="com.jdon.strutsutil.ModelViewAction">

      <forward name="create" path="/c.jsp" />

      <forward name="edit" path="/c.jsp" />

    </action>

总结上面配置,Model名称不同,只要更换一下有关Model名称即可,其余基本不变,配置具备模板化编程的特点,拷贝粘贴然后修改,修改且有规律。

通过以上配置,我们确定了a.jspb.jspModelViewAction推出的页面,我们再看看a.jspb.jsp的代码:

 

<html:form action="/userSaveAction.do" method="POST" onsubmit="return checkPost();">

<html:hidden property="action"/> <!--  这是必须的  -->

 

UserId<html:text property="userId"/> <!--  主键一般是必须的  -->

<br>

Name<html:text property="name"/>

<br>

<html:submit property="submit" value="Submit"/>

</html:form>

这个是一个CRUD页面,如果是修改,那么其中userIdName将有值。注意两点:

必须有action隐含字段,当然参数名称也可能是method,取决于你如何调用ModelViewAction

必须有主键隐含字段,上例主键userId是可编辑的,一般主键是专门序列号产生器产生的,不能修改,但是必须包含在表单中。

纯粹显示使用 ModelDispAction

前面是ModelViewAction显示数据页面是为了编辑或删除,如果你纯粹是为了显示,并无需考虑后续操作结果,那么可以在配置Struts-config.xml中使用ModelDispAction,其他Jsp页面和ModelViewAction处理尾一样的。

例如:你有一个如下类,你希望显示这个类调用的结果UserPaper

public UserPaper getPtime(EventModel em){

       UserPaper userPaper=(UserPaper)em.getModel();

       try{

       logger.debug("first User:"+userPaper);

             logger.debug("userDao:"+userpaperDao.getPtime());

        logger.debug("userDao.getPtime:"+(userpaperDao.getPtime()).getPtime());

       userPaper=userpaperDao.getPtime();

       logger.debug("object Userpaper value:"+userPaper.getPtime());     

 

       }catch(Exception ex){

              logger.error(ex);

              em.setErrors(Constants.MESSAGE_SAVE_ERROR);

       }

       System.out.println("last user Paper:"+userPaper.getPtime());

       return userPaper;

    }

 

使用服务命令调用模式是无法返回值的,所以这个getPtime不能用这个方式,因为返回值你要显示出来,显示就要有Jsp页面。几个步骤:

1.public UserPaper getPtime(EventModel em) 抽象为接口 getPtimeIF 并实现这个接口。

2.UserPaper做一个ModelForm UserPaperForm

3.struts-config.xml中配置:

<action name="userPaperForm" path="/你的路径"

   type="com.jdon.strutsutil.ModelDispAction" scope="request" validate="false">

                 <forward name="success" path="/你要显示UserPaper值的JSP页面" />                 

</action>

4.其他步骤参考上面ModelViewAction章节。

接受CRUD处理

当用户在浏览器中新增或修改页面填入数据后,将按“确定submit”按钮提交后台进行保存或删除等处理,从CRUD基本流程章节的流程图可以看到看到:这主要是由ModelSaveActionModelSaveAction主要委托ModelHandler,而ModelHandler则是用户自己定义的Jdonframework.xml MVC配置实现的:

流程图如下:

 

ModelSaveAction原理

ModelSaveAction是前面的SaveAction实现,它是真正的核心Action实现,专门将用户新增或修改后的数据提交到服务层,实现持久化如保存到数据库中。

ModelSaveAction主要是委托ModelHandler实现数据提交和服务激活,这个过程比较单一有规律,用户可介入程度低,因此可以使用配置来代替代码实现。

我们看看ModelSaveAction是如何将Jsp页面提交的数据传递给后台,ModelSaveActionModelFormActionForm)中获得用户提交的数据,将其拷贝到相应的Model实例,然后将Model对象打包到EventModel对象中,将EventModel作为方法参数,调用ModelHandlerserviceAction方法,ModelHandlerserviceAction则是调用服务层相应Service的对应的create/update/delete方法,所以ModelSaveAction主要工作委托给ModelHandler实现,我们将在下面研究重要的ModelHandler类。

 

 

Struts配置和Jsp

ModeSaveActionModelViewAction类似,根据Model不同,在struts-config.xml中配置如下:

    <action name="aActionForm" path="/aSaveAction" type="com.jdon.strutsutil.ModelSaveAction">

      <forward name="success" path="/aResult.jsp" />

      <forward name="failure" path="/aResult.jsp" />

    </action>

如果ModelB,那么struts-config.xml的配置是:

    <action name="bActionForm" path="/bSaveAction" type="com.jdon.strutsutil.ModelSaveAction">

      <forward name="success" path="/bResult.jsp" />

      <forward name="failure" path="/bResult.jsp" />

    </action>

关于ModeSaveAction使用是在Jsp页面中赋值给action

<html:form action=”/aSaveAction.do”>

   <html:hidden property=”action” />

   <html:text ….

   ….

</html:form>

Jsp页面是新增或修改性质的页面,例如可以是前面介绍的ModelViewAction推出的a.jspb.jsp

至此,ModelViewAction -à jsp àModelSaveAction实现了首尾衔接,实现了一个CRUD操作流程。

通过配置使用Jdon框架中的ModelViewActionModelSaveAction,程序员避免了象开发普通Struts应用系统那样建立至少一个Action子类。

在这个流程中,需要和服务层服务交互,这需要由程序员根据具体程序定制,下面介绍程序员可介入定制的重要类ModelHandler

CRUD流程配置jdonframework.xml

现在我们进入CRUD流程图的下面一个环节:表现层和业务层接口部分了。

前面我们说过,ModelViewActionModelSaveAction其实是委托ModelHandlerService接口打交道的,程序员需要定制实现的部分由继承ModelHandler实现。但是缺省情况下,可以通过jdonframework.xml配置实现。

配置实例说明

假设,我们已经编制了一个Service接口如下:

public interface AccountService {    

  Account getUser(EventModel em); //查询获得存在的Model

  void createUser(String username); //新增方法

  void updateUser(EventModel em); //修改方法

  void deleteUser(EventModel em); //删除方法

}

其中getUser方法是由调用ModelViewAction调用的,其余都是由ModelSaveAction调用的,为了让Service的具体方法告诉Jdon框架,我们可以通过jdonframework.xml配置告诉它:

<handler>

<service ref="accountService">

             <getMethod name="getUser"/>  //getUser对应AccountService接口的getUser方法

             <createMethod name="createUser"/>//createUser对应AccountService接口createUser方法

             <updateMethod name="updateUser"/>// updateUser对应AccountService接口updateUser方法

             <deleteMethod name="deleteUser"/>// deleteUser对应AccountService接口deleteUser方法

</service>

</handler>

我们通过上述配置,建立了表现层ModelViewAction/ModelSaveAction和业务层Service之间的调用关系。当然这段配置应该放置在jdonframework.xmlModel配置中,以前面Model章节的配置为例子:

 

<model key="username" class ="com.jdon.framework.samples.jpetstore.domain.Account">

      <actionForm name="accountForm"/>

        <handler>

      ……//上述配置插入在这里

</handler>

</model>

这样,你自己编写的Service接口嵌入Jdon框架的CRUD流程,非常简单。

你可能还有一个疑问,Strutsstruts-config.xml中的配置的ModelVewActionModelSaveAction是怎么定位自己的那个Service,例如struts-config.xml如下配置:

<action path="/serviceAction" type="com.jdon.strutsutil.ModelViewAction"

    name=" accountForm"  scope="request"  validate="false">

<forward name="xxxxx" path="/result.jsp"/>

</action>

然后,你定义了自己的Service,如上面jdonframework.xml中的accountService,那么ModelViewAction是如何找到accountService,而不是其他定义的accountService2service呢?

这是通过上面jdonframework.xml中的<actionForm name="accountForm"/>的这个FormBean名称,accountForm这个FormBean在是在struts-config.xml中定义的,它是struts-config.xmljdonframework.xml两个配置之间的桥梁。

一个FormBean对应struts-config.xml中的一个action配置;同时对应jdonframework.xml中的一个model CRUD流程配置,这里的桥梁FormBean是指FormBean名称,而不是FormBean类,同一个FormBean可以配置多个FormBean名称,这是在struts-config.xmlFormBeans中配置的。

 

配置语法

对比前面的CRUD基本流程 jdonframework.xml中的对应CRUD配置语法解释如下:

<handler>

<service ref="accountService">

<initMethod name="XXXX"/> //推出新增页面前对Model的初始化方法,CRUD可选参数

             <getMethod name="XXXX"/>   //推出编辑页面前对存在的Model进行查询

             <createMethod name=" XXXX "/>//新增页面提交后保存新增数据

             <updateMethod name=" XXXX "/>// 编辑页面提交后保存编辑数据

             <deleteMethod name=" XXXX "/>// 删除数据

</service>

</handler>

jdonframework.xml中这段配置其实对应CRUD流程两个部分:initMethod getMethod 用于CRUD页面显示;而createMethod/ updateMethod/ deleteMethod用于接受CRUD处理。说白了,initMethod/ getMethod是在ModelViewAction中被调用;而createMethod/ updateMethod/ deleteMethod则是在ModelSaveAction中被调用。

initMethod是实现CRUD非必须参数配置外,其他都是实现CRUD必须参数。

通过jdonframework.xmlCRUD这段配置,实际是将你的业务层Service接口相应的CRUD方法告诉Jdon框架,这样应用系统运行时,客户端通过调用URL参数,如http://localhost/xx.do?action=edit就可以直接调用你的业务层Service相应的CRUD方法,省却了表现层琐碎的编码,不只是对CRUD这几个方法,通过服务调用命令模式还可以对你自己定义的任何调用直接调用相应的业务层Service方法。见后面章节介绍。

CRUD流程MVC配置在架构中位置:

 

 

 

服务Service编码约束

现在使用Jdon框架CRUD功能只剩下最后一步了,前面都是讲述如何配置,本节是涉及编码代码方面了。前面说过:

通过jdonframework.xmlCRUD配置,实际是将你的业务层Service接口相应的CRUD方法告诉Jdon框架,你的Service方法名告诉Jdon框架了,那么Jdon框架怎么知道你的方法参数呢?这两者Jdon框架都知晓了才能在运行时自动调用你指定的Service方法啊!

服务Service编码约束有两条:

方法参数类型约束

第一条:jdonframework.xmlCRUD配置中涉及的所有方法除getMethod以外方法参数类型都必须是EventModel

也就是说,在initMethod/createMethod/ updateMethod/ deleteMethod中配置的Service方法参数都必须是EventModel类型。

<handler>

<service ref="accountService">

<!-- 对应的方法必须为  initXxx(EventModel em)  -->

<initMethod name="initXxx"/>  

<!--对应的方法必须为 getXxx(String yyyy)  -->

             <getMethod name=" getXxx "/>           

<!--对应的方法必须为 void createXxx(EventModel em) -->

<createMethod name=" createXxx "/>

<!--对应的方法必须为 void updateXxx(EventModel em) -->

             <updateMethod name=" updateXxx "/>

<!--对应的方法必须为 void deleteXxx (EventModel em) -->

             <deleteMethod name=" deleteXxx "/>

</service>

</handler>

对应的service接口代码如下,注意方法参数类型:

public interface AccountService {    

     Account initXxx(EventModel em);  //被配置成<initMethod name=" getXxx "/>

  Account getXxx(String username);  //被配置成<getMethod name=" getXxx "/>

  void createXxx (EventModel em);   //被配置成<createMethod name=" createXxx "/>

  void updateXxx (EventModel em);   //被配置成<createMethod name=" updateXxx "/>

  void deleteXxx (EventModel em);   //被配置成<createMethod name=" deleteXxx "/>

}

上个章节的配置实例说明正好也说明了这个限制。

关于方法返回参数方面,Jdon框架是这样假设的:

对于initMethod/ getMethod方法,返回参数是当前Model;如:

Account getXxx(EventModel em);

getXxx返回的参数Account为当前Model,方法参数是EventModelEventModel实际是Model的再次封装,这在下节会专门讲述。

对于createMethod/ updateMethod/ deleteMethod新增删除修改方法,方法返回类型为void,如果方法运行时有出错信息需要返回表现层,通过将错误放入EventModelsetErrors方法中返回表现层,具体见出错信息处理章节。

getMethod方法参数约束

注:JdonFramework 6.0以上版本已经没有这个限制约束了。

getMethod方法参数不是EventModel,必须是String类型;方法参数值变量名没有限制。具体规定是:

String类型 + Model主键的值

如前面案例getUser方法在在AccountService中是这样规定编写的:

Account getUser(String username);

如果你的Model主键类型不是使用String,比如是Long/longInteger/int,这里方法参数也必须是String,然后自己的Service实现子类中,进行类型转换,当然如果你的Model主键都统一成String则最好了,推荐!见:

http://www.jdon.com/jive/article.jsp?forum=91&thread=23875

注意,以上两条限制是对你的业务层Service代码的限制。也就是说,为了使用Jdon框架的CRUD功能,最后一步必须按照本限制编制你的Service代码。当然,这个限制只是对方法参数有限制,其他都是自由的,如果你觉得这点也不方便,可以阅读下章节:深入定制,则就完全没有限制了。

 

EventModel

com.jdon.controller.events. EventModel是用于表现层向业务层传递参数,相当于DTOData Transfer Object J2EE模式),EventModel中装载的是Model实例,一般Model实例是来自于ModelForm的拷贝,而ModelForm则来自客户端提交的表单参数,如下图:

上图展示了客户端浏览器提交的表单数据被服务器端接受后,使用Jdon框架处理的流程,最终被打包成EventModel作为Service方法参数传入业务层,那么我们实现业务层Service代码时,就需要从EventModel中将Model再取出来,如:

public class AccountServiceImp implements AcountService{

     ……

public void createUser(EventModel em) {

   //取出Model,而且确证是Account这个Model

   Account  account  = (Account) em.getModel();

   ……

}

……

}

为什么上面代码取出Model后确证这个Model类型就是Account,这和jdonframework.xml CRUD配置有关,

<model key="username" class ="com.jdon.framework.samples.jpetstore.domain.Account">

      <actionForm name="accountForm"/>

       <handler>

<service ref="accountService">

           <getMethod name="XXXX"/>

         <!-- 对应上面代码AccountServiceImpcreateUser方法  -->

                   <createMethod name=" createUser "/>

                      <updateMethod name=" XXXX "/>

                      <deleteMethod name=" XXXX "/>

</service>

</handler>

</model>

上面是jdonframework.xml CRUD完整标准配置,其中黑体部分com.jdon.framework.samples.jpetstore.domain.Account已经表明当前Model的类型,在这个Model配置中,有<createMethod name=" createUser "/>一行,这样我们在AccountServiceImp代码中的createUser方法中取出的Model可以肯定是Account了。

如果createMethod/ updateMethod/ deleteMethod方法运行时有出错信息需要返回表现层,通过将错误放入EventModelsetErrors方法中返回表现层,具体见出错信息处理章节。

需要注意的是:一定要搞清楚EventModel的流程来源,上图演示了EventModel的来源和流程,在jdonframework.xml中除了getMethod方法不使用EventModel作为方法参数外,其余都是使用EventModel作为方法参数:

<!-- 对应的方法必须为  initXxx(EventModel em)  -->

<initMethod name="initXxx"/>  

<!--对应的方法必须为 getXxx(String yyyy)  -->

             <getMethod name=" getXxx "/>           

<!--对应的方法必须为 void createXxx(EventModel em) -->

<createMethod name=" createXxx "/>

<!--对应的方法必须为 void updateXxx(EventModel em) -->

             <updateMethod name=" updateXxx "/>

<!--对应的方法必须为 void deleteXxx (EventModel em) -->

             <deleteMethod name=" deleteXxx "/>

我们已经知道:initMethod是用于初始化创建页面;getMethod是用于初始化编辑页面;这两个方法是ModelViewAction调用的;createMethod/ updateMethod/ deleteMethod则是作为接受提交页面保存处理,是ModelSaveAction调用的。

initMethod方法中的EventModel中的Model是从表现层ActionFormModelForm)来的,而ActionForm则是从客户端调用URL来的,因为页面初始化工作在initMethod业务层方法中实现,所以这对客户端调用URL有一些要求,具体可见”JiveJdon3.0源码中的发贴功能实现,它的URL是这样形式:

/message/messageAction.shtml? forum.forumId=xxxx

注意,是forum.forumId 而不是通常的直接以forumId=xxxx为参数形式,这样,forumId参数值就被messageAction对应的ActionFormMessagForm中的ForumforumId接受,并且复制到ForumMessage这个Model,最后在业务层initMethod中使用下列语句取出这个forumId参数值:

Long forumId = forumMessage.getForum().getForumId();

  另外还有一个小的Struts技术使用细节这时MessagForm这个ActionForm需要对其中Forum这个字段对象进行初始化可在MessagForm构造方法中写入

  public MessageForm(){

        forum = new Forum(); // for parameter forum.forumId=xxx

  }

 

深入定制

至前面章节,Jdon框架的CRUD架构基本完成,可以在一般应用中使用,如果你特殊应用场景,或者上述配置可能无法满足你的应用要求,可以阅读本章节进一步深入定制,同时也会对jdonframework.xmlCRUD配置运行原理有深入了解。

简单一般以灵活牺牲为代价,如果你觉得约束太大,那么你可以自己编码实现ModelHandler,ModelHandler中自己通过代码调用Service接口,完全回到你的自由编码阶段,下面针对配置和编码两种实现比较一下,这是以完成Jdon框架中要求的相同功能为前提的。

编辑之前的数据查询

Jdon框架的CRUD流程中,需要推出一个用于编辑的页面,既然是编辑,和新增页面不同的就是,编辑页面中各个字段已经有以前的数据,这样,你可在原来数据上修改。那么在编辑页面推出之前,Jdon框架需要调用Service接口的 查询方法,如上面Account getUser(String username);方法。

Jdon框架是通过ModelHandlerfindModelByKey方法实现的,两种实现方法:

1. 代码实现,继承ModelHandler实现,如果你的Model查询不是从数据库获得,如是从HttpSession获得的,那么你要继承实现自己的方法findModelByKey,如下代码:

public Model findModelByKey(String keyValue,

HttpServletRequest request)  throws  Exception{

        AccountService accountService =

(AccountService) WebAppUtil.getService("accountService", request);

        return accountService.getUser(key);

 }

代码实现后,记住告诉Jdon框架你的ModelHandler子类,配置jdonframework.xml

<model ……j>

      <handler class="你的ModelHandler子类" />

</model>

一般情况下,是通过服务层查询数据库获得的,那么涉及到服务层交互,可以采取配置实现。

有人可能奇怪:为什么上面ModelHandlerfindModelByKey方法参数keyValue一定是String型的,难道我的Model主键类型一定是String吗?

不是,这里keyValue是主键数值,不是主键本身,主键数值一般是通过调用ModelViewActionURL参数传入的,如下:

/xxxViewAction.do?action=edit&key=keyValue

这样,keyValue一定是字符串型的,如果你的Service的查询方法主键参数不是String,你可以将String型号转为你的主键参数。

 

2. 配置实现,在jdonframework.xml配置如下:

<model ….>

      <handler>

        <service ref="accountService">

            <getMethod name="getUser" />                       

          …..

        </service>

      </handler>

</model>

那么,要求你的服务层服务有相应的方法如下:

public class AccountServiceImp implements AccountService{

    private ProductManager productManager;   

    public AccountServiceImp(ProductManager productManager){

        this.productManager = productManager;

    }

 

    public Account getUser(String username) {

           try{

            account = accountDao.getAccount(username);

            account.setCategories(productManager.getCategoryList());

            //other business logic

        }catch(DaoException daoe){

            Debug.logError(" Dao error : " + daoe, module);

        }

        return account;

    }

    ….

}

 

数据提交保存

用户在Jsp页面填写或修改数据后,将实现保存提交,或实现删除提交,这时Jsp页面的action是提交给ModelSaveAction的,ModelSaveAction委托ModelHandlerserviceAction实现结果保存或删除。serviceAction有两种实现:

1.代码实现,这部分工作主要是实现传递工作,代码比较有规律,一般使用配置实现,如果你需要在提交之前实现一些其他特殊实现,那么使用代码实现如下:

public void serviceAction(EventModel em, HttpServletRequest request) throws

      java.lang.Exception {

    try { //Httpsession获得User,保存到News中,这是特殊实现

      User user = (User) ContainerUtil.getUserModelAfterLogin(request);

      if (user != null) {

        News news = (News) em.getModel();

        news.setUser(user);

      }

  

   NewsManagerLocal newsManager = (NewsManagerLocal) WebAppUtil.

          getEJBService("newsManager",  request);

 

      switch (em.getActionType()) {

        case Event.CREATE:

          newsManager.createNews(em); //递交service创建方法

          break;

        case Event.EDIT:

          newsManager.updateNews(em);//递交service修改方法

          break;

        case Event.DELETE:

          newsManager.deleteNews(em);//递交service删除方法

          break;

      }

    }

    catch (Exception ex) {

      throw new Exception(" serviceAction Error:" + ex);

    }

  }

还需要配置jdonframework.xml你的ModelHandler代码实现:

<model ……j>

      <handler class="news.web.NewsHandler" />

</model>

2.配置实现,如果在递交服务处理之前没有特殊工作实现,可使用配置,如下:

<model ….>

      <handler>

        <service ref="accountService">

                <createMethod name=" createNews " />

             <updateMethod name=" updateNews " />

                <deleteMethod name=" deleteNews " />                      

          …..

        </service>

      </handler>

</model>

那么,要求你的ServiceinsertAccount updateAccountdeleteAccount方法,而且这三个方法参数必须是EventModel类型,当然,方法名是可以根据你的service具体方法不同,在配置中更改,例如你的Serviceinsert方法,那么配置就是:

<createMethod name="insert" />

方法名可以变化,任意取,但是方法参数必须是EventModel类型的。如:

public interface AccountService {   

    Account initAccount(EventModel em);  //初始化

  Account getAccount(String username); //查询获得存在的Model

  void insertAccount(EventModel em); //新增方法

  void updateAccount(EventModel em); //修改方法

}

当然,如果你使用代码实现,就没有这些对Service类的编程规定了。

页面初始化

有些应用系统可能要求:推出新增编辑的页面必须初始化,实现装入一些其他数据,例如:我们新增人员名单的页面,可能有一个部门下拉菜单的选择,而这些部门数据是我们事先已经输入进入数据库的,这时在新增人员页面时,要装载进来。

推出新增性和编辑性页面的初始化工作是不一样的。我们知道,Jsp页面的初始化其实是ActionFormModelForm)的初始化,我们分别从新增和编辑页面的初始化两条线路说明。

如果你需要给ModelForm中属性字段简单初始化一些常量,那么很显然这些在ModelForm的构造方法中实现,如:

public class AccountForm extends ModelForm{

  private List languages;

public AccountForm() {

    languages new ArrayList();

    languages.add("english");

    languages .add("japanese");

}

}

上面代码需要给languages加入一些初始值,这样,推出页面时,用户可以进行多项选择(使用htmlselect/options multibox等实现)。

如果初始化这些属性值需要从数据库中获得,那么就需要和服务层实现交互了,那么就要考虑继承实现ModelHandler了。

ModelForm创建可由通过服务层后台交互实现,这种交互实现也有两种方式:

1. 通过代码实现:ModelHandler提供的initForm方法, initForm程序员只要继承ModelHandler,在initForm方法中调用服务层服务,获得初始化值,再赋值入ModelForm,如下代码:

public class NewsHandler  extends ModelHandler {

 

  public ModelForm initForm(HttpServletRequest request) throws Exception {

    Debug.logVerbose("enter iniForm .", module);

    NewsActionForm nf = new NewsActionForm(); //创建ModelForm

    NewsManagerLocal newsManager = (NewsManagerLocal)

WebAppUtil.getEJBService("newsManager",  request);

    PageIterator pi = newsManager.getNewsTypePage(0, 50);

    Collection newsTypes = new ArrayList();

    while (pi.hasNext()) {

      String id = (String) pi.next();

      NewsType newsType = newsManager.getNewsType(id);

      newsTypes.add(newsType);

    }

    nf.setNewsTypes(newsTypes); //将新闻类型列表赋值到新闻这个ModelForm

    return nf;

  }

  ……

}

还需要配置一下,告诉Jdon框架你的ModelHandler实现:

<model ……>

      <handler class="news.web.NewsHandler" />

</model>

以上是代码实现直接初始化ModelForm,还有一种初始化ModelForm,思路是:初始化Model,然后将Model值拷贝到Model中,这是通过ModelHandlerinitModel实现的。不过initModelinitForm只能选择其一实现,initModel还可以通过配置实现:

 

2. 通过配置实现,配置主要是通过initModel方法实现,在jdonframework.xml中配置如下:

<model ….>

      <handler>

        <service ref="accountService">

          <initMethod   name="initAccount" />                  

          …..

        </service>

      </handler>

</model>

上面配置initMethod方法是访问accountServiceinitAccount方法,也就是说,ModelForm的初始化推给Model初始化,而Model初始化则由accountServiceinitAccount实现,程序员必须实现的accountServiceinitAccount方法。如:

public class AccountServiceImp implements AccountService{

    private ProductManager productManager;   

    public AccountServiceImp(ProductManager productManager){

        this.productManager = productManager;

    }

 

public Account initAccount(EventModel em){

        Account account = new Account();

        account.setCategories(productManager.getCategoryList());

        return account;

    }

    ….

这两种页面初始化选用依据,是根据用来初始化的数据来自何处?是来自后台或服务层,那么选择第二个方案;如果来自request相关的例如HttpSession,则选用第一个方案。

上述代码和配置比较中,案例情况不一样,实际上,如果你将代码实现中initForm方法中代码移植到Service接口的initAccount方法中实现,也就可以采取配置实现了。

 

ModelHandler原理

ModelHandler几个重要方法是总结了表现层和服务层三种交互操作,如果你的应用不属于这三种交互操作,那么可能就无法使用Jdon框架的CRUD功能,直接使用Struts实现。

ModelHandler有几个重要方法是 initForm方法、initModel方法、findModelByKey方法和serviceAction方法。前面两个方法是和页面初始化有关,页面初始化有可能和服务层交互访问;findModelByKey是根据主键查询数据库存在的记录,这是和编辑页面相关,编辑之前需要先查询,这个方法也需要和服务层交互访问;serviceAction则是将用户对数据的新增修改删除决定通知服务层进一步处理,这是和服务层主要交互访问。

上面章节中,jdonframework.xml配置:

<handler>

<service ref="accountService">

             <getMethod name="getUser"/>  //getUser对应AccountService接口的getUser方法

             <createMethod name="createUser"/>//createUser对应AccountService接口createUser方法

             <updateMethod name="updateUser"/>// updateUser对应AccountService接口updateUser方法

             <deleteMethod name="deleteUser"/>// deleteUser对应AccountService接口deleteUser方法

</service>

</handler>

Jdon框架中是自动调用com.jdon.model.handler.XmlModelhandler作为代码实现,也就是说,XmlModelhandler根据上面配置自动生成前面的代码,属于一种简单的代码自动生成。

注意:有时我们可能需要代码配置混合实现,ModelHandler几个方法中只代码实现一个方法,其他都可以配置实现,这也是可以的,只是这时你的代码不是继承ModelHandler,而是XmlModelHandler了,同时配置要写明你的子类实现:

<model ….>

      <handler class=”你的XmlModelHandler子类”>

        <service ref="accountService">

                <createMethod name="insertAccount" />

                <updateMethod name="updateAccount" />

                <deleteMethod name="deleteAccount" />                    

          …..

        </service>

      </handler>

</model>

更多配置技巧

CRUD功能实现标准配置

参考前面的ModelViewActionModelSaveAction配置章节,一个数据Model的标准CRUD功能实现无需编写任何Action,只要有ActionForm(还是Model的影子),通过struts-config.xml配置就可以实现:

1.推出创建性或编辑性页面:

    <action name="aActionForm" path="/aAction" type="com.jdon.strutsutil.ModelViewAction">

      <forward name="create" path="/a.jsp" />

      <forward name="edit" path="/a.jsp" />

    </action>

约束:

1type必须是com.jdon.strutsutil.ModelViewAction

2forwardname值必须是createedit

2.接受数据提交数据并递交Service服务层处理。

<action name="aActionForm" path="/aSaveAction" type="com.jdon.strutsutil.ModelSaveAction">

      <forward name="success" path="/aResult.jsp" />

      <forward name="failure" path="/aResult.jsp" />

    </action>

约束:

type必须是com.jdon.strutsutil.ModelSaveAction

forwardname值必须是successfailure

CRUD功能实现分离配置

CRUD可以通过两行action配置ModelViewAction ModelSaveAction合并完成,也可以将新增和修改分开配置,例如用户注册功能,用户注册(用户创建)和用户资料修改属于不同的需要分离开的两个过程,用户注册功能是针对新用户,或者说是非注册用户;而用户资料修改是针对注册用户,两者服务对象角色是不一样的,因此,不能象其他Model那样将CRUD合并在一起。

1. 推出创建性页面的配置:

    <action name="accountForm" path="/shop/newAccountForm"

type="com.jdon.strutsutil.ModelViewAction" scope="session">

      <forward name="create" path="/account/NewAccountForm.jsp" />

    </action>

    forwardedit值为空,没有这一行。

2. 接受创建数据并并递交Service服务层处理

    <action name="accountForm" path="/shop/newAccount"

type="com.jdon.strutsutil.ModelSaveAction" scope="session"

       validate="true" input="/account/NewAccountForm.jsp">

      <forward name="success" path="/shop/index.shtml" />

      <forward name="failure" path="/shop/newAccountForm.shtml" />

    </action>

 3. 推出编辑性页面的配置          

    <action name="accountForm" path="/shop/editAccountForm"

type="com.jdon.strutsutil.ModelViewAction" scope="session">

      <forward name="edit" path="/account/EditAccountForm.jsp" />

    </action>

   forwardcreate值为空,没有这一行,不过调用/shop/editAccountForm.shtml形式还是必须要有参数的:/shop/editAccountForm.shtml?action=edit&username=XXX

  4. 接受修改后的数据并并递交Service服务层处理

    与第2条类似,不同的是pathjsp因为编辑页面不一样而不同:

    <action name="accountForm" path="/shop/editAccount"

type="com.jdon.strutsutil.ModelSaveAction" scope="session"

validate="true" input="/account/EditAccountForm.jsp">

      <forward name="failure" path="/shop/editAccountForm.shtml" />

      <forward name="success" path="/shop/index.shtml" />

    </action>

单纯输出Jsp页面

如果有时只是为了输出一个Jsp页面而做一个Action比较麻烦,Jdon框架实现了一个缺省的转发Action,可以简单重用在很多这样场合。只要在struts-config.xml配置如下:

 

<action path="/XXX" type="com.jdon.strutsutil.ForwardAction"

name="XXX" scope="request"validate="false">

<forward name="forward" path="/xxx.jsp"/>

</action>

上述配置有三点要求如下:

type必须是com.jdon.strutsutil.ForwardAction

forwardname值是forward

当然,你也可以自己编制这样缺省简单action

服务命令调用模式

多层结构的好处是带来了良好的可维护性,可拓展性,但是也带来开发的复杂性,有时我们为实现一个小功能,需要做如下步骤:

创建Domain Model

创建界面模型,如strutsActionForm

创建StrutsAction,作为界面控制器;

配置界面流程struts-config.xml

创建Domain ModelService接口

创建Domain Model的持久化DAO

对于复杂功能,上述过程是必须的,这样可以达到很好的分层解耦作用。但是,有时一个小功能,或者是Domain Model的特殊功能,只是在Service增加了一个小方法,是否还要整个流程都走一遍呢?

使用Jdon框架就不必了,Jdon框架提供基于URL调用参数的服务命令调用模式,就象当初我们在C/S结构中,一个菜单按钮功能直接激活其功能方法一样。

Jdon框架提供的服务命令调用模式有几种实现方式,可根据功能复杂程度灵活选择,并且提供最简单直接的缺省实现。

 

我们先看看Jdon框架提供的服务命令调用模式是什么样?如下图:

上图调用url形式可以有两种:  /aaa.do?action=xxxxx/aaa.do?method=xxxxx 使用actionmethod都可以,推荐使用method

 

当浏览器客户端调用某个StrutsAction,如serviceAction.do时,跟随的参数actionmethod值如果为xxxxx,那么通过Jdon框架,就可以直接调用对应Service接口的方法名为xxxxx的方法,如果xxxxxabcde;那么就调用serviceabcde方法。

 

这种直接将客户端命令和后台业务层服务直接对应的关系可以大大简化我们很多小功能开发,从而达到类似Ruby on Rails那样的快速开发。

下面我们提供三种不同的实现方式,根据功能复杂程度进行选用,注本章节功能在Jdon框架1.4版本后可使用:

最简单实现

最简单实现是指命令没有任何输入参数,也就是无需ModelModelForm的命令直接调用,调用形式如下:

http://localhost:8080/myAction.do?service=xxx&method=xxx

这个调用方式适合无返回值的命令调用。

其中,service值就是jdonframework.xml中定义的pojoService名称,如:

<pojoService name="testService" class="com.jdon.framework.test.service.TestServicePOJOImp"/>

那么调用参数中service值就是testService

Mehtod名称为testService代表的那个接口的方法,如下:

public interface TestService{

   …… 

   void xxxxx();//注意方法是无返回值 无输入参数 这是两个约束条件

   ……

}    

上述TestService接口方法为xxxxx调用参数中的mehtod值就是xxxxx

调用URL中的myAction.do是在struts-config.xml中配置的,如下:

<action path="/myAction" type="com.jdon.strutsutil.ServiceMethodSimpleAction"

     scope="request"  validate="false">

     <forward name="success" path="/result.jsp"/>

</action>

其中com.jdon.strutsutil.ServiceMethodSimpleActionJdonFramework 1.5版本以后提供的一个缺省Struts Action实现,注意,Action配置中需要配置一个名称为successforward页面, 表示成功后跳转页面,也可不配置。

最后,通过浏览器网址URL直接调用TestService接口的xxxxx方法为:

http://localhost:8080/myAction.do?service=testService&method=xxxxx

 

是不是非常简单?您无需书写任何表现层的代码如JspServletAction,就可以通过浏览器客户端直接调用你的业务层服务方法,这个简单方式经常使用在系统调试维护或一些简单的功能实现上。

以上实现的缺点就是:你无法向TestService的方法xxxxx输送方法参数,因此你的业务层服务方法必须是没有方法参数的,如果必须输送方法参数,如何实现?

有两个解决方案:

1.               不写Action代码,将这些参数组合成一个Model和相应ModelForm,如果参数简单,就可以组合到一个现成的Model中。无论多复杂情况,大多数情况可以采取这种方式。

2.               自己写代码Action,就无需组合Model了。

下面先讨论自己写代码Action方式,再下一章再讨论不写Action代码的方式:

Action代码定制实现

你自己必须实现一个StrutsDispatchAction实现,DispatchAction的特殊之处是可以根据method方法参数直接进入其方法的。struts-config.xml配置如下:

<action path="/aaa" type="你的DispatchAction子类"

      scope="request"  validate="false" parameter=”method”>

     <forward name="xxxxx" path="/xxx.jsp"/>

</action>

注意这里配置多了一个parameter=”method”,那么你的浏览器客户端参数调用必须符合Struts 1.2中这个新功能规定:

/aaa.do?method=xxxxx

其中xxxxx对应上面配置parameter的值,这样,struts就直接调用你的你的DispatchAction子类的xxxxx方法,如下:

public class MyAction extends DispatchAction{

  ……

//按照strutsdispathcAction规则,方法名必须为xxxxx

public ActionForward xxxxx (ActionMapping mapping, ActionForm form,

        HttpServletRequest request, HttpServletResponse response) throws Exception  {

     

   

     //手工代码调用Service接口

    

TestService testService = (TestService) WebAppUtil.getService(" testService ", request);

testService. xxxxx(aa, bb)

  }

  ……

}

 

MyAction中使用代码访问Service接口,调用WebAppUtilcallService方法即可,可返回任何对象,callService调用参数说明如下:

服务名称:在jdonframework.xmlservices定义的名称;如testService,在jdonframework.xml中是这样定义的:

<pojoService name="testService" class="com.jdon.framework.test.service.TestServicePOJOImp"/>

 

方法名称:需要调用服务的方法名称,也就是我们的调用参数xxxxx

方法参数:是Object[]数组形式,将要传给testServicexxxxx参数值,如有两组参数aabb,则将aabb参数打包入数组Object[]

按照这个调用要求,你的代码testService如下:

public interface TestService{

   …… 

   void xxxxx(AType aa, BType bb);//这是有方法参数的 注意方法是无返回值的

   ……

}    

 

 

以上方法适合有一些个别方法参数时,自己必须做一个Action,将这些URL调用参数转换为TestService的方法参数。

这种方式好处,就是无需建立Model/ModelForm,但是需要写Action代码,有时还要自己控制缓存的清除,缓存清除需要在Action代码中如下调用:

modelManager.removeCache(keyValue); //keyValueModel主键

具体可参考ModelSaveActionmakeModel方法。

还有一种更简单,无需写Action的方式,只要将上述参数aabb组合成一个Model就可以了,请看下一章:

不写Action代码方式

这是推荐大家使用的方式,无论多复杂的情况,都可以通过搞清头绪,根据参数建立好Model,或者把参数组合到一个Model中,然后建立相应ModelForm就可以了。这样就无需写Action调用:

http://localhost:8080/aaa.do?service=testService&method=xxxxx

 

上面一行意思是:调用testService的方法xxxxx。当然,没有返回值。就是一个简单的没有返回值的命令。

下面讨论已经建立了专门的ModelModelForm后,服务命令调用如何简单实现?

调用URL将变成:

http://localhost:8080/aaa.do?method=xxxxx

      注意,其中没有指定testService了,因为我们已经配置,详细步骤如下:

假设你的Model已经建立完成,相应的ModelFormaForm

 

第一步:配置struts-config.xml如下:

<action path="/aaa" type="com.jdon.strutsutil.ServiceMethodAction"

     name="aForm" scope="request"  validate="false">

     <forward name="xxxxx" path="/xxx.jsp"/>

</action>

其中aForm是一个ModelForm的名称,必须在struts-config.xml中配置。

这个配置主要使用Jdon框架的一个现成Action子类:com.jdon.strutsutil.ServiceMethodAction,配置其中forward配置中(<forward name="xxxxx" path="/xxx.jsp"/>)name值必须是参数method的值xxxxx,这样,调用Service处理后,可返回对应的Jsp页面。

第二步:指定Model和服务的调用关系,配置Jdon框架的配置文件jdonframework.xml,告诉系统,你所要调用的Service接口是哪个类。如下:

<model key="primary key of your model主键" class ="你的model">

    <actionForm name="aForm"/>

     <handler>

       <service ref="testService" /> <!--  指定对应的接口Service名称  -->

    </handler>

</model>

<pojoService name="testService" class="com.jdon.framework.test.service.TestServicePOJOImp"/>

第三步,最后就是你自己要编码实现Service接口和子类,接口中必须有和调用参数值xxxxx相同的方法名,方法参数必须是com.jdon.controller.events.EventModel(使用下节代码定制无此限制);在EventModel中可封装你的传输参数Model等,方法返回类型可以返回值或者为空,返回值应该是Model类型, 返回值Model中的值将被复制到对应的ModelForm,也就是aForm,这里假设缺省是空void,如下:

public interface TestService{

   …… 

   Model  xxxxx(EventModel em);//注意,方法参数必须统一为是EventModel 可有返回值

   ……

}    

全部完成,这样你就可以使用下面URL直接调用,不必写Action代码:

http://localhost:8080/aaa.do?method=xxxxx

上面这一行中没有指定testService,是因为我们已经在jdonframework.xml中定义了model配置。

虽然xxxxx(EventModel em)方法没有返回值,但是你可以在这个方法实现中,对EventModel.setErrors()设置错误,以告诉界面命令是否成功执行。

无论多复杂的情况,都可以通过搞清头绪,根据参数建立好Model,或者把参数组合到一个Model中,然后建立相应ModelForm就可以了。也就实现开始的图示:

 

 

使用服务命令调用模式,可以节省StrutsAction代码书写,少写Action代码的最大好处就是:可以避免误将业务逻辑写到Action中,造成真正业务层空壳,因此推荐尽量使用服务命令调用模式。

下面举一个实际应用例子来说明如何启发服务命令调用模式使用,例如:用户注册功能中有一个忘记密码功能,用户通过Jsp提交给一个Action,由这个Action转发给某个Service,由于忘记密码功能不属于专门的CRUD功能,而且个单个功能,如果我们为这些单个功能都写一个ActionDispatchAction,增加系统代码的复杂性,更容易导致不懂MVC模式的人或不小心将业务写入这些Action,造成代码复杂难于维护。

其实,JdonFrameworkCRUD流程简化就是依据本章基本原理实现新增 删除修改查询等服务命令的简单直接调用的。

最普遍的用法

我们已经看到,在CRUD流程中,最后需要保存新增或修改结果时,提交给ModelSaveAction的参数action/mehtod值非常重要, action参数来决定整个流程是新增保存还是修改保存或者是删除,action/mehtod的参数值是固定的,有三个createeditdelete,这是Jdon框架固定的,必须是这三个字符中一个。那么,参数除了这三个以外,是否可以是其他值呢?或者我们是否一定取名action/method值为create editdelete这样的字符呢?

有时,我们向后台提交保存结果可能不是新增、编辑或删除这么简单,可能是两者性质的删除,在我们的Service中有两个delete方法,那么如何使得前台提交到这两个delete方法呢?一个使用Jdon框架的CRUD方式,另外一个或更多的方式如何?

很简单,直接使用服务命令调用模式即可,例如向ModelSaveAction提交时,action值为deleteAll;那么只要你的Service接口中deleteAll方法即可;

从这里看出,提交是Actionmethod的值也不一定是框架规定的create editdelete这些字符,可以直接是你service的任何方法,包括CRUD方法。

步骤如下: Struts-config.xml 正常配置ModelSaveAction

 

<action path="/aaa" type="com.jdon.strutsutil. ModelSaveAction"

     name="aForm" scope="request"  validate="false">

     <forward name="sucess" path="/xxx.jsp"/>

</action>

jdonframework.xml,告诉系统,你所要调用的Service接口是哪个类。如下:

<model key="primary key of your model主键" class ="你的model">

    <actionForm name="aForm"/>

     <handler>

       <service ref="testService" /> <!--  指定对应的接口Service名称  -->

    </handler>

</model>

<pojoService name="testService" class="com.jdon.framework.test.service.TestServicePOJOImp"/>

TestService接口代码

public interface TestService{

   …… 

   void xxxxx(EventModel em);//注意,方法参数必须统一为是EventModel

   ……

}    

那么我们就可以进行/aaa.do?method= xxxxx 的直接调用,也无需指定service名称了,提交的内容是在aForm和其对应的"你的model"中。

xxxxx可以是createModel方法 updateModel deleteModel这些方法。

当前方式是Jdon框架使用最简单直接通用的方式。

上述展示了CRUDCUD直接调用方式,关于Read查询,则配置struts-config.xmlModelSaveActionModelSaveAction即可。

 

出错信息处理

在服务层一旦实现数据库操作出错,Jdon框架通过EventModelsetErrors方法向表现层传递出错信息,因此,只要在你的服务层Service实现方法的出错代码调用该方法即可,如:

public void insertOrder(EventModel em) {

        Order order = (Order)em.getModel();

        try{

            orderDao.insertOrder(order);

        }catch(Exception daoe){

             Debug.logError(" Dao error : " + daoe, module);

            em.setErrors("db.error");

        }

    }

当存储orderDao调用出错,将db.error保存到EventModel中,而db.errorstrutsApplication.properties中定义的,,需要在你的应用系统中定义,Jdon框架应用到的信息如下:

id.required = you must input id

id.notfound = sorry, not found

db.error=sorry, database operation failure!

system.error=sorry, the operation failure cause of the system errors.

maxLengthExceeded=The maximum upload length has been exceeded by the client.

notImage=this is not Image.

主要有id.required id.notfound db.error等几个,需要在J2EE应用系统中定义。

案例:UsertTest建模

本文是建立模型UserTestCRUD和批量查询功能,本文实现功能其实和上篇是一样的,操作过程也一样,只是描述方式不一样,上篇是一种概要大体步骤描述,是依据CRUD和批量查询两个功能实现展开的,而本文是依据开发中业务层/表现层/持久层为主题展开的。本部分代码见SimpleJdonFrameworkTest源码包。

 

开始开发程序, 建立Model实现UserTest

建立Model对应的ModelFormUserActionForm是继承ModelForm,而ModelForm实际也是继承StrutsActionForm

UserTest中方法和字段拷贝到UserActionForm,原则上这两者内容是一致的,JdonFramework将自动在这两个对象之间拷贝相同方法的值,但是它们服务的对象不一样,UserTest是为了中间层或后台业务层服务的;而UserActionForm是为了界面服务的,两者分离是为了使得我们的业务层和界面分离,这样,当有新的可替代Struts界面框架出现,我们可以不修改业务层代码下更换界面框架。

业务层开发

Jdon框架支持Web应用和EJB应用,下面两种方案中任意选一种,也就是说,是采取POJO服务实现或EJB服务实现取决你的系统架构,如果你不想使用EJB,那么就采取POJO服务,相关EJB服务实现及其他就都不要看了。

POJO服务实现

POJO服务只要编写一个TestService接口和一个实现TestServicePOJOImp即可,在实现子类中调用持久层jdbcDao.

EJB服务实现

这里使用EJB实现业务层功能,建立一个无态Session Beans

TestEJB,主要负责UserTest的数据库增删改查等功能,这部分开发不在JdonFramework规定之类,而要遵循EJB相关规定。这部分开发细节可参见有关JBuilder开发EJB教程。

建立一个实体Bean名为User:首先需要将JBuilder配置成能够直接访问数据库,我们使用MySQL数据库,配置后,重新启动JBuilder

配置JBoss中的数据源,数据源JNDI名称是DefaultDS。在代码中调用JBossJNDIJBoss有特定规定,应该是java:/DefaultDS

创建CMP时有两种方式:一种直接创建新的CMP,当该J2EE部署时,将自动创建CMP对应的数据表,或者先创建数据表,由数据表导入CMP。本演示采取后者方式。

完成Session Bean TestEJB的新增、修改和删除方法,下面完成查询和批量查询方法,查询方法建议使用DAO+JDBC实现。

持久层:增删改查CRUD实现

持久层的增删改查功能可使用Jdon框架的Jdbc模板实现:

新增操作

   public void insert(UserTest userTest) throws Exception{

       String sql = "INSERT INTO testuser (userId , name) "+

       "VALUES (?, ?)";

       List queryParams = new ArrayList();

       queryParams.add(userTest.getUserId());

       queryParams.add(userTest.getName());

       jdbcTemp.operate(queryParams,sql);

       clearCacheOfItem();

   }

  修改操作:

   public void update(UserTest userTest) throws Exception{

       String sql = "update testuser set name=? where userId=?";

       List queryParams = new ArrayList();

       queryParams.add(userTest.getName());

       queryParams.add(userTest.getUserId());

       jdbcTemp.operate(queryParams,sql);

       clearCacheOfItem();

   }

  删除操作:

   public void delete(UserTest userTest) throws Exception{

       String sql = "delete from testuser where userId=?";

       List queryParams = new ArrayList();

       queryParams.add(userTest.getUserId());

       jdbcTemp.operate(queryParams,sql);

       clearCacheOfItem();

   }

 查询操作:

   public UserTest getUser(String Id) {

        String GET_FIELD = "select  * from testuser where userId = ?";

        List queryParams = new ArrayList();

        queryParams.add(Id);

 

        UserTest ret = null;

 

        try {

            List list = pageIteratorSolverOfUser.queryMultiObject(queryParams,

                    GET_FIELD);

            Iterator iter = list.iterator();

            if (iter.hasNext()) {

                Map map = (Map) iter.next();

                ret = new UserTest();

                ret.setName((String) map.get("name"));

                ret.setUserId((String) map.get("userId"));

            }

        } catch (Exception se) {

        }

        return ret;

    }

增删改查CRUD配置

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

<!DOCTYPE app PUBLIC "-//JDON//DTD Framework 2005 1.0 //EN"    "http://www.jdon.com/jdonframework.dtd">

<app>

  <models>

    <model key="userId" class ="com.jdon.framework.test.model.UserTest">

      <actionForm name="userActionForm"/>

      <handler>

        <service ref="testService">

          <getMethod name="getUser" />

          <createMethod name="createUser" />

          <updateMethod name="updateUser" />

          <deleteMethod name="deleteUser" />

        </service>

      </handler>

    </model>

  </models>

  ….. <!--  配置  -->

</app>

注意Handler配置有两种缺省是上述配置如果情景复杂需要代码完成则编写一个Hamdler类继承ModelHandler实现即可然后在上述handler段配置如下

<handler class=”xxxx.yourHandler” />

POJO服务配置实现

<services>

  <pojoService name="testService" class="com.jdon.framework.test.service.TestServicePOJOImp"/>

   <pojoService class="com.jdon.framework.test.dao.JdbcDAO" name="jdbcDAO">

               <constructor value="java:/DefaultDS"/>

    </pojoService> 

 </services>

上面定义了一个POJO服务:com.jdon.framework.test.service.TestServicePOJOImp,因为TestServicePOJOImp中使用了JdbcDAO,这里配置了com.jdon.framework.test.dao.JdbcDAO实现,如果你使用其他持久层技术,可以在此更换另外一个JdbcDao

这里的jdbcDAO需要数据库连接,数据库连接参数不是在应用程序中配置,而是使用J2EE服务器已经配置成功的JNDI名称,Tomcat中配置JNDI数据库连接池可见:http://www.7880.com/Info/Article-37f05fa0.htmlJBossJND配置见本站相关文章。

 

特别注意:

java:/DefaultDSJBoss的数据库连接池JNDI写法,如果你使用其他服务器,参考他们的JNDI要求写法。

例如 JBossmysql-ds.xml中配置一个数据库的JNDITestDS

那么在J2EE系统中(上面JdbcDAO配置中),就应该前面加一个java:/ 就成了java:/TestDS

如果Tomcatserver.xml配置数据库的JNDITestDS

那么在J2EE(上面JdbcDAO配置中),使用该JNDI的名称应为: java:comp/env/TestDS

两者写法不一样,不同的服务器写法都有规定。也可以在web.xml配置JNDI引用。

 

如果服务配置中存在ejbService,如下:

    <ejbService name="testService2" >

      <jndi name="TestEJB" />

      <ejbLocalObject class="com.jdon.framework.test.ejb.TestEJBLocal"/>

      <interface class="com.jdon.framework.test.service.TestService" />

    </ejbService>     

那么可以在ejbServicepojoService之间更换,将pojoServiceejbServicename值对换一下即可, <ejbService name="testService2" >更换为<ejbService name="testService" >,将<pojoService name="testService"改名为 <pojoService name="testService2" 这样,Model部分配置的服务指向就从调用POJO服务更换到调用EJB服务了。

 

界面流程配置

CRUD功能无需编制代码,代码功能已经在上节的jdonframework.xml中配置完成,这里只要配置struts的页面流程struts-config.xml配置和编写Jsp两步即可。

(1) 按照前面章节规则二:配置ModelViewAction介绍的。实现新增或编辑视图输出的Action:

    <action name="userActionForm" path="/userAction" type="com.jdon.strutsutil.ModelViewAction">

      <forward name="create" path="/user.jsp" />

      <forward name="edit" path="/user.jsp" />

</action>

上述配置中,需要修改的跟Model名称相关的配置。

2)创建user.jsp页面。

user.jsp中要增加一行关键语句:

<html:hidden property="action"/> 用来标识新增 修改等动作。

3 配置userSaveAction实现数据真正操作。

    <action name="userActionForm" path="/userSaveAction"

type="com.jdon.strutsutil.ModelSaveAction">

      <forward name="success" path="/result.jsp" />

      <forward name="failure" path="/result.jsp" />

    </action>

4 创建数据操作结果页面result.jsp 上述配置修改不大,需要修改的跟Model名称相关的配置。


批量分页查询快速开发

   Jdon框架的批量分页查询功能不但适合开发大批量集合数据显示,也适合一对多数据显示,例如一个对象中包含子对象集合,这个对象可以称为父对象,那么如果需要显示这个父对象和其子对象集合,使用本章节介绍的批量查询也可快速实现。

原理

所谓批量分页查询是指对大量数据进行分页查询,实现如下图显示效果:

批量查询由于是频繁操作,对系统性能设计要求很高,而且批量查询几乎存在每个数据库信息系统,因此,这个功能具有可重用性,Jdon框架根据Jive等传统成熟系统批量查询设计原理,总结多个应用系统,在不破坏多层情况下,抽象出批量查询子框架。

批量查询设计两个宗旨:

尽量减少数据库访问,减少数据库负载和I/O性能损耗。

由于J2EE是一个多层结构,尽量减少在多个层次之间传送的数据量。减少传送损耗。

因此,批量查询设计将涉及到两个层面:表现层和持久层,Jdon框架提供了持久层下JDBC查询操作模板(JdbcDao),这个JdbcDao可以被服务层不同服务类型(POJO ServiceSession Bean)调用。

根据批量查询设计宗旨,有下面实现要点:

使用缓存减少数据库访问

持久层不能将满足查询条件的每页所有完整数据记录传送到表现层,试想一下,如果一条数据记录有1000个字节,那么一页显示20条,那么就有2万个字节传送,目前很多批量查询设计都是这样做,存在浪费内存和性能消耗大的隐患。

缓存越靠近用户界面端,性能越好,因此,持久层如果只传送满足查询条件的数据记录主键(一个字段)集合,那么表现层可以优先从缓存中根据主键获得该数据完整数据,查询越频繁使用,缓存击中率越高,各方面消耗就越小。

根据以上设计原则,Jdon框架的批量查询设计方案如下:

对于持久层:

·               获取满足查询条件的所有数据表记录总个数。

·               获取当前页面所有数据记录主键集合(ID集合)。

·               将上述两种数据打包成一个对象(PageIterator)传送到前台

对于表现层:

·               获得后台满足查询条件的PageIterator对象。

·               遍历PageIterator对象中的主键集合,根据主键再查询后台获取完整数据对象(Model),先查询缓冲,如果没有,再访问数据库。

·               将获取的完整数据对象(Model)封装成一个集合,打包成ModelListForm对象。

·               Jsp页面再展开ModelListForm对象中Model数据集合,逐条显示出来。

批量查询的主要示意图:

图中解释:后台将满足查询条件的数据记录的主键集合(ID集合)包装成PageIterator对象,然后由服务层返回到表现层(通过ModelListAction调用服务EJBServicePOJO ServicegetPageIterator方法),表现层的ModelListAction再遍历这个ID集合,根据每个ID查询获得完整Model数据,再打包到ModelListForm对象中。

更详细的实现方案可参考《Java实用系统开发指南》一书。

批量查询框架基于一个前提是:如果你的Model没有主键怎么办?那么使用强制给它一个Object ID,这可以由一个专门序列器产生。这与前面CRUD实现的前提是一致的。

实现约束

为利用Jdon框架提供的批量查询高性能功能,基本有以下步骤:

第一步, 建立Model,建立对于列表中每行视为一个Model,如下图:

上图中每一行实际是一个Model对象,多行显示,实际就是多个Model对象显示,表结构“主题名”、“回复” “作者” “时间”则是Model对象的字段。

Model的建立和上章节CRUDModel规则是一样的,每个Model有一个主键,而且Model主键类型必须和数据表的主键数据类型是对应的,详细可参考上章节CRUD的“ModelModelForm”中“每个Model需要一个主键”。

第二步,在表现层,继承com.jdon.strutsutil.ModelListAction实现子类,然后将该子类配置进入strutsstruts-config.xml,供客户端浏览器调用。细节见本章节后面说明。

第三步,在持久层,创建一个PageIterator对象,然后,将这个PageIterator对象传送到表现层供ModelListAction的子类getPageIterator方法调用。

 

重要对象PageIterator

从前面批量查询原理中看出,PageIterator实际是一个在多层之间穿梭的数据载体,前后台关系只是靠PageIterator来实现消息交互。

因此,无论后台持久层使用什么技术(JDBC、实体BeanHibernateiBatis),只要能提供一个查询结果对象PageIterator给前台就可以。

考虑到持久层技术发展极快,Jdon框架并没有在持久层的缺省实现,但提供了JDBC实现的帮助API(见包:com.jdon.model.query),具体可参考Jdon框架源码包Samples目录下JdonNewsnews.ejb.dao. JdbcDao。这个JdbcDao虽然为EJB Session Bean调用,也可以为普通的POJO服务调用,JdbcDao开发也有模板化,一般是一个Model完成三个方法,具体可见“开发一个简单Jdon应用系统”章节的“批量查询持久层实现”。

Jdon-Jpetstore中,批量查询持久层实现是使用iBatis,然后在服务层将iBatisPaginatedList类转换成Jdon框架的PageIterator即可。

PageIterator定义

PageIterator是在持久层(Dao类)或业务层创建的、为显示每个页面数据服务的临时对象,只要我们在持久层向表现层传递一个PageIterator,前台表现层只要使用特定Jsp标签,也就能够出现分页标识。

其实,PageIterator就是java.util.ListIterator一个子类实现,比ListIterator多了结束点和遍历个数两个定义。

使用PageIterator后,不但前台表现层代码基本无需编写,最重要的是,在后台持久层将对批量查询数据进行缓存优化,极大提高性能,这将在下节PageIteratorSolver讲述。

从持久层产生的PageIterator主要包含符合查询条件的数据集合(是数据记录的主键集合,而非全部数据记录,可以更快地从数据库获取);还有显示时每页开始的第一个数据记录的指针,这个指针是数据集合的指针;当然最后还有显示时每页结束的最后一个数据记录的指针。

注意:也就是说:PageIterator包含的数据集合个数总是大于等于当前页需要显示的数据个数,当前页面显示的数据个数是根据客户端url调用决定的,例如/userListAction.do?count=20,那么表示每页显示的数据个数是20。当然,也并不是说,这个个数完全按客户端查询条件决定的,如果恶意查询,count数值很大,则会对系统存在伤害。这里有一个最大值,缺省是200,如果你需要改变,在PageIteratorSolver中进行修改即可。

下面介绍PageIterator缺省构造方法,第一个构造器是Block构造器,是缺省的构造器,如果你使用PageIteratorSolver来获得PageIterator,框架内部就是使用这种构造器来自动构造PageIterator

第一种构造器(Block PageIterator):

 

public PageIterator(int allCount, Object[] keys, int startIndex, int endIndex, int count)

 

缺省构造方法有5个参数,分别详细描述如下:

int allCount:符合查询条件的所有数据记录(Model)总数。

Object[] keys:这是一个Block集合,所谓Block就是一整块记录集合,缺省Block长度是200个,也就是说,这长200的一个Block中一定包含符合查询条件的当前页面数据记录(Model)的ID集合,只要在具体显示时根据startIndex等值从中遍历就可以。这也决定了该PageIteratorBlock PageIterator性质。

int startIndex:当前页面在keys中的开始点。

int endIndex: 当前页面在keys中的结束点。

int actualCount: 当前页面实际可显示个数,当查询条件count小于每页显示最大值200时,actualCount其实就是count,否则,就是200

第二中构造器(Page PageIterator):

  public PageIterator(int allCount, Object[] keys, int startIndex) {

      this.allCount = allCount;

      this.keys = keys;

      this.endIndex =  keys.length;

  }

该构造器两个参数意义如下:

int allCount:符合查询条件的所有数据记录(Model)总数。

Object[] keys:符合查询条件的当前页面数据记录(Model)的ID集合,这个ID集合长度一定和当前页面需要显示长度是等长,不能多也不能少,这点和上面第一个缺省构造器区别。

这种构造器称为Page PageIterator,是因为keys的意义和前面Block PageIterator不一样,keys参数就是符合查询条件当前页面主键ID集合,有多少符合查询条件,这个keys中就放置多少,没有多余的,不是一个长200Block集合。

第二个构造器有两个为兼容老版本的衍生构造器,多了两个参数int startIndex boolean hasNext,这两个参在1.4以后版本已经丢弃不用了。

如果你使用HibernateIbatis或自己实现数据库查询,然后使用得到的ID集合手工构造一个PageIterator,可以使用这个构造器。如JpestoreOrderServiceImp中:

 

public PageIterator getOrdersByUsername(String username, int start, int count) {

          PageIterator pageIterator = null;

             try {

              //查询数据库从start开始共count个记录的主键集合

                 List list = orderDao.getOrderIDsByUsername(username, start, count);

                 int allCount = orderDao.getOrderIDsByUsernameCount(username);

                 int currentCount = start + list.size();

              //下面是使用第二个构造器(老版本)自己构造一个PageIterator

                 pageIterator = new PageIterator(allCount, list.toArray(), start,

                         (currentCount < allCount)?true:false);

 

        } catch (Exception daoe) {

            Debug.logError(" Dao error : " + daoe, module);

        }

      

      return pageIterator;

    }

 

 

自己构造PageIterator

如果持久层不使用Jdon框架,或者通过别的数据来源产生一个数据集合,如果希望在页面能够使用Jdon框架的自动分页查询和性能优化,那就在业务层Service中自己构造一个PageIterator,如下代码:

         List resultSortedIDs = xxxDao.getHotThreadKeys(qc);//从数据源获得一个ID集合

if (resultSortedIDs.size() > 0){

                                   List pageIds = new ArrayList(resultSortedIDs.size());

                                   for (int i = start; i < start + count; i++) {

                                            if (i < resultSortedIDs.size()){

                                                     pageIds.add(resultSortedIDs.get(i));       

                                            }else

                                                     break;

                                   }                        

          //返回自己构造的PageIterator

                          return new PageIterator(resultSortedIDs.size(), pageIds.toArray());

 

         }

 

遍历PageIterator

有时我们会根据后台持久层的PageIterator进行查询符合一定条件的数据,或者要将所有数据记录都获得,也就是前台页面不需要分页,但是后台持久层还使用Jdon框架的PageIterator查询,这时,我们可以如下实现:

int  start = 0;

int  count = 30;//每页30个元素

          boolean found = false;

 PageIterator pi = forumMessageQueryService.getMessages(threadId, start, count);

 int allCount = pi.getAllCount();

 while((start < allCount) && (!found)){//遍历所有的元素 直至满足寻找条件

       while(pi.hasNext()){

                       Long messageIdT = (Long)pi.next();

                       if (messageIdT.longValue() == messageIdL.longValue()){

                                    found = true;                                      

                                    break;

                           }

      }

     if (found) break;

     //继续遍历

     start = start + count ;

                  logger.debug("start = " + start + " count = " + count);

     //获得下一页

                  pi = forumMessageQueryService.getMessages(threadId, start, count);                                                     

         }

             

构造Model类型PageIterator

前面介绍的是构造PageIterator时,装载的Model主键集合,Jdon框架的ModelListAction会遍历PageIterator的主键集合,根据主键再查询获得整个Model,如果有时不能这样分两次前后查询,例如后台是Lucene这样搜索持久层,每次一次性获得一个Model完整集合,如果先获得Model主键集合,再逐个根据主键获得整个Model,效率很差。

在这种情况下,我们可以直接将Model集合构造成PageIterator,如下:

new PageIterator(resultSortedIDs.size(), modelCollection.toArray());

注意,需要调用PageIterator.setElementsIsKeyfalse,这样Jdon框架的ModelListAction会识别PageIterator中装载的是完整Model

此功能适合Jdon框架5.0版本。

 

持久层PageIteratorSolver

PageIterator可以由开发者自己根据前台用户界面需求自己创建,当然Jdon框架也提供一般情况下的PageIterator的自动创建,开发者只需从PageIteratorSolver对象直接获取即可,那么PageIteratorSolver如何使用?

Jdon框架提供了PageIterator创建的持久层创建APIcom.jdon.model.query.PageIteratorSolver,这是一个基于SQLJDBC实现,当然你完全可以其他持久层技术实现创建PageIterator

使用Jdon框架的PageIteratorSolver提供JDBC模板操作,在查询功能上几乎无需写JDBC语句。使用PageIteratorSolver可以书写很少代码就实现一种Model的批量分页查询功能,PageIteratorSolver可以被Session Bean调用,或者作为普通POJO被调用,因此, PageIteratorSolver使用有两种情况:

配置在Jdon框架容器中使用,如果你不使用EJB

单独直接使用。

这两种使用方式和PageIteratorSolver提供两种构造器有关:

public PageIteratorSolver(DataSource dataSource, CacheManager cacheManager)

 

public PageIteratorSolver(DataSource dataSource)

第一种构造器需要指定CacheManagerCacheManager已经在Jdon框架启动时作为基础组件激活,你只要创建DataSource即可,而DataSource是和容器的数据源JNDI名称相关,这里给出一种方式以供参考:

第一步:创建一个JdbcTempSource作为DataSource的包装器,JdbcTempSource构造参数如下:

  public JdbcTempSource(String jndiname) {

        try {

            ServiceLocator sl = new ServiceLocator();

            dataSource = (DataSource) sl.getDataSource(jndiname);

            jdbcTemp = new JdbcTemp(dataSource);

        } catch (ServiceLocatorException slx) {

            logger.error(slx);

        }

    }

jdonframework.xml中配置如下:

<pojoService name="jdbcTempSource"

                      class="com.jdon.jivejdon.dao.sql.JdbcTempSource">

             <constructor value=" java:/NewsDS "/>

 </pojoService>

其中java:/NewsDSJboss的数据源在server/default/deploy/mysql-ds.xml中配置的JNDI名称。如果你使用Tomcatweblogic其他,根据它们数据源JNDI说明在这里填写。

第二步,创建你自己的DAO类,构造参数如下:

 

public MessageDaoSql(JdbcTempSource jdbcTempSource, CacheManager cacheManagerl) {

     this.pageIteratorSolver =

 new PageIteratorSolver(jdbcTempSource.getDataSource(),CacheManager cacheManager);

 }

假设MessageDaoSql是你自己的Dao类,那么我们在jdonframework.xml配置如下:

 

<pojoService name="messageDao"

                      class="com.jdon.jivejdon.dao.sql. MessageDaoSql "/>

 

因为CacheManager事先已经在Jdon框架的container.xml中配置过了。

通过以上配置,由于Jdon框架的autowiring功能,为自动为你的Dao对象创建匹配其他的类,这样在你的Dao类中可以正常使用PageIteratorSolver

下面谈谈第二中构造器的使用,直接new就可以创建,适合在EJB这样Jdon框架体外调用,如下:

      ServiceLocator sl = new ServiceLocator(); //Jdon框架中的ServiceLocator

      DataSource  dataSource = (DataSource) sl.getDataSource("java:/NewsDS");

      PageIteratorSolver  pageIteratorSolverOfType = new PageIteratorSolver(dataSource);

因为这种方式下,PageIteratorSolver自己创建一个新缓存器,而不是象上面那样可以使用Jdon框架的统一缓存系统,这带来的问题是:你要自己hold住你的PageIteratorSolver对象,也就是将PageIteratorSolver对象引用保存在第三方处,以保证每次客户端请求时,都能找到上次操作的PageIteratorSolver对象,千万不能每次请求访问时,创建一个新PageIteratorSolver对象,那要造成内存泄漏。

建议,你可以为每个Model建立一个对应的PageIteratorSolver对象,这样,该Model更新时,只要刷新这个Model相关的缓存,具体可见下面缓存清除方法。

是不是觉得自己管理缓存非常麻烦?所以还是推荐你使用第一种PageIteratorSolver的构造器构造PageIteratorSolver对象。

有了PageIteratorSolver对象,我们就可以从PageIteratorSolver对象中获取我们最重要的分页对象PageIterator了。

获得PageIterator

首先,根据批量查询原理,首先要查询符合查询条件的记录总数和当前页面的所有ModelID集合,这是通过PageIterator的创建实现。

PageIteratorSolver 中重要方法getPageIterator是为了创建一个PageIterator对象,需要的输入参数相当比较复杂,这里详细说明一下:

getPageIterator方法如下:

public PageIterator getPageIterator(String sqlqueryAllCount,

            String sqlquery, String queryParam, int start, int count) throws Exception

注意:PageIteratorSolvergetDatasgetPageIterator实则一样,getPageIterator名称或参数都显得易懂正规一些。但是更要注意的是:getDatasgetPageIterator方法参数顺序是不一样的,特别是第一个参数,虽然都是String,但是意义不一样,使用时切勿混淆。

public PageIterator getDatas(String queryParam, String sqlqueryAllCount,

                               String sqlquery, int start,

                               int count) throws Exception

首先从String sqlqueryAllCount参数解释:

sqlqueryAllCount参数其实是一个sql语句,该sql语句用来实现查询满足条件的所有记录的总数。例如查询某个表myTablesql语句如下:

String   sqlqueryAllCount   =  "select count(1) from myTable ";

当然这是一个标准sql语句,后面可写入where等查询条件

sqlquery参数则是关键的查询语句,主要实现批量查询中查询符合条件的数据记录(Model)的主键ID集合。例如表myTable的主键是id,则

String sqlquery =  “select  id  from T_ myTable where categoryId = ?”

 

重要规定:

getPageIterator方法两个重要参数就是上面的sqlqueryAllCountsqlquery两条SQL语句,批量查询的事先对这两条SQL语句返回值有规定:sqlqueryAllCount中返回的必须是满足查询条件的总数(count(1))sqlquery必须是满足查询条件的所有主键集合(id)

 

queryParam参数则是和sqlquery参数相关,如果sqlquery语句中有查询条件“?”,如上句categoryId = ??的值就是queryParam参数,这样sqlqueryqueryParam联合起来形成一个查询语句。如果没有查询条件,那么queryParam就赋值为null""

注意,如果查询条件有多个“?”,那么就需要将这些queryParam打包成一个Collection,然后调用方法:

public PageIterator getPageIterator(String sqlqueryAllCount,

          String sqlquery, Collection queryParams, int start, int count) throws Exception

这个方法与前面的getDatas区别就是方法参数由String queryParam变成集合 queryParamsqueryParams是多个查询条件“?”的集合,注意,查询条件目前Jdon框架支持常用的几种查询参数类型:String Integer Float Long,例如,如果你的查询条件如下:

String sqlquery = select  id  from T_ myTable where categoryId = ? and name = ?

这里有两个查询参数,那么将这两个查询参数打包到数组中即可,注意顺序和问号的顺序是一样,如果问号对应的变量categoryIdValuenameValue,则如下

Collection queryParams = new ArrayList();

queryParams.add(categoryIdValue);

queryParams.add(nameValue);

 

参数startcount则是有表现层传入的,start表示当前页面是从所有符合查询条件记录中第几个开始的,而count则是从start开始查询多少条记录个数,也就是一页显示的记录数目。

获取数据块

PageIteratorSolver内部是如何创建PageIterator呢?实际是根据数据块创建PageIterator的,PageIteratorSolvergetPageIterator方法代码如下:

  public PageIterator getPageIterator(String sqlqueryAllCount,

String sqlquery, Collection queryParams,

int startIndex, int count) {

//every query max length must be less than blockLength

       if ((count > this.blockLength) || (count <= 0)) {

            count = this.blockLength;

        }

        Block currentBlock = getBlock(sqlquery, queryParams, startIndex, count);//关键

        startIndex = currentBlock.getStart();

        int endIndex = startIndex + currentBlock.getCount();

        Object[] keys = currentBlock.getList().toArray();

        int allCount = getDatasAllCount(queryParams, sqlqueryAllCount);       

        if (endIndex < startIndex){

            return new PageIterator();

        }else{          

            return new PageIterator(allCount, keys, startIndex, endIndex, count);

        }

    }

其中getBlock方法是关键,那么数据块是怎样的概念呢?

1.4版本以前批量查询时总是根据查询条件指定的范围从数据库获得特定范围,但是不同查询条件可能获得不同的数据结果范围,而且两者结果集合中可能会重复,这样,缓存的利用效率就不是很高。

1.4版本以后更改数据库查询方式,不是根据查询条件指定范围查询,而是以固定范围查询,如每次读取都是读取200个数据,然后在内存中通过逻辑检查,客户端所要查询的指定范围是否在这200数据块中。

数据块都将被缓存起来,如果每页显示20个,这样这一个包含200个数据的数据块至少适合10页查询,进一步降低翻页对数据库的操作次数。

有关数据块的两个方法可供外界调用,以便在复杂查询中,更大程度利用缓存中的数据块:

public Block getBlock(String sqlquery, Collection queryParams, int startIndex, int count)

 

public Block locate(String sqlquery, Collection queryParams, Object locateId)

前者getBlock是根据查询条件获得一个Block对象,然后由程序员自己加工再自行创建PageIterator,提供了更大的灵活性。

后者locate是在符合查询条件的所有Block中寻找包含主键locateId值的那个Block,需要注意的是,在调用该locateId方法之前,你必须首先根据locateId直接查询一下数据库或缓存,确定存在locateId这个值,否则locate翻遍数据库或缓存都没有发现,浪费性能。

这两个方法都包含激活缓存的操作,也就是说,第一次调用locate可能需要真正操作数据库,但是第二次则会从缓存中获得,而且通过locate的操作,缓存可能有益于getBlockgetPageIterator方法,反过来也然。

有关数据块设计部分细节可见:http://www.jdon.com/artichect/oo_math.htm

 

批量查询缓存清除

由于PageIteratorSolver内置了缓存,缓存两种情况:

符合查询条件的所有记录总数,这个总数第一次使用后将被缓存,不必每次都执行select count的数据操作,这是很耗费数据性能的。

当前页面中所有ModelID集合,在没有新的Model被新增或删除情况下,这个ID集合几乎是不变的,因此其结果在第一次被使用后将被缓存。但是,某个Model发生新增或删除情况下,我们要主要清除这些缓存,否则新增或删除的Model将不会出现在Model列表中,PageIteratorSolver缓存设计前提是Model的新增和删除不会很频繁,至少没有查询频繁。

PageIteratorSolverclearCache()提供这两种缓存的主动清除,是符合查询条件的主键数据块集合。注意,清除的不是具体一个Model,这是另外需要清除方式,见Model缓存使用

需要在Model的新增方法或删除方法中加入调用PageIteratorSolverclearCache方法的代码。因为Model的新增或删除方法是由程序员自己实现,如使用CMPHibernate等实现,也需要加入clearCache方法。

我们建议PageIteratorSolver创建依据每个Model类型有一个对应的PageIteratorSolver对象,这样,这个Model类型变动只会清除它的有关缓存;如果所有Model类型共用一个PageIteratorSolver对象,一旦一个Model对象变动将引起所有缓存清除,降低缓存效益。

其他形式PageIterator创建

上面是使用Jdon框架的JDBC模板实现PageIterator创建,你有可能使用其他技术实现持久层,这里提供两种PageIterator创建的参考代码:

例如,productDao是封装了别的持久层技术(iBatisHibernate)。要求两步就可以完成PageIterator创建:

第一步. productDao返回符合条件的当前页面ID集合:

  List list = productDao.getProductIDsListByCategory(categoryId, start, count);

第二步. productDao返回符合条件的总数:

  int allCount = productDao.getProductIDsListByCategoryCount(categoryId);

创建PageIterator代码如下:

 int currentCount = start + list.size();//计算到当前页面已经显示记录总数

 PageIterator pageIterator = new PageIterator(allCount, list.toArray(), start,

                   (currentCount < allCount)?true:false);

 

以上代码是在服务层实现,一旦服务层能够返回一个PageIterator实例,就可以按照下面表现层实现完成批量分页查询功能。

另外一个PageIterator创建实现,假设所有数据ID或数据模型包装在一个List中,从List中创建PageIterator的代码如下:

        //计算未显示记录个数

        int offset = itemList.size() - start;

        int pageCount = (count < offset)?count:offset;

        //生成当前页面的List

        List pageList = new ArrayList(pageCount);

        //start开始遍历,遍历次数是一页显示个数

        //将当前页面要显示的数据取出来放在pageList

        for(int i=start; i< pageCount + start;i++){

            pageList.add(itemList.get(i));

        }

        int allCount = itemList.size();

        int currentCount = start + pageCount;

        PageIterator pageIterator = new PageIterator(allCount, pageList.toArray(), start,

                (currentCount < allCount)?true:false);

使用JdbcTemp实现单个查询

前面已经获得PageIterator对象,Jdon框架会在表现层ModelListAction中遍历这个PageIterator对象中ID集合,然后根据ID先从缓存中获取完整Model数据,如果没有则从数据库获得,那么我们在持久层还需要提供单个Model查询的实现。

Jdon框架已经内置查询模板JdbcTemp,无需使用者自己实现JDBC语句操作,直接使用模板即可,注意:如果数量量很大近百万,推荐使用专门的持久层框架,比如iBatisHibernate等。

JdonNewsModel NewsType查询一个实例为例子:

首先确定sql语句写法,如下:

String GET_TYPE = "select  * from T_NEWS_TYPE where typeid = ?";

其中typeid参数值由外界传入,因此写方法如下:

  public NewsType getNewsType(String Id) throws Exception {

    String GET_TYPE = "select  * from T_NEWS_TYPE where typeid = ?";

    List queryParams = new ArrayList();

    queryParams.add(Id); //IdGET_TYPE sql语句中的?的值

   

    …..

}

上述代码准备了JDBC模板查询两个参数:GET_TYPEqueryParams

调用PageIteratorSolver中的两个Model查询方法,其实是调用JdbcTemp.java

  //适合查询返回结果是单个字段,如:

  // select name from user where id=?

  public Object querySingleObject(Collection queryParams, String sqlquery)  throws Exception {

return jdbcTemp.querySingleObject(queryParams, sqlquery);

}

 

  //适合查询返回结果是多个字段,而且有多项记录

  //例如:select id, name, password from user where id = ?

  public List queryMultiObject(Collection queryParams, String sqlquery)  throws Exception {

return jdbcTemp.queryMultiObject(queryParams, sqlquery);

}

这两个方法适合不同的查询语句,当你的查询sql语句返回不只一条数据记录,而且每条数据记录不只是一个字段,而是多个字段,使用queryMultiObjectqueryMultiObject经常使用。下面是获得一个Model查询的完整写法:

  public NewsType getNewsType(String Id) throws Exception {

    String GET_TYPE = "select  * from T_NEWS_TYPE where typeid = ?";

    List queryParams = new ArrayList();

    queryParams.add(Id);

    

    NewsType ret = null;

    try {

      List list = pageIteratorSolverOfType.queryMultiObject(queryParams, GET_TYPE);

      Iterator iter = list.iterator();

      if (iter.hasNext()) {//遍历查询结果,根据你的判断,决定这里使用ifwhile

        Map map = (Map)iter.next(); //List中每个对象是一个Map

        ret = new NewsType();

        ret.setTypeName((String)map.get("typename")); //根据字段名称从Map中获取其相应值

        ret.setTypeId(Id);

      }

    }

    catch (Exception se) {

      throw new Exception("SQLException: " + se.getMessage());

    }

    return ret;

  }

JdbcTemp还提供了更多数据库操作的简化模板,具体可参考:JdbcTemp

表现层ModelListForm

前面章节主要谈论了批量查询的持久层实现,Jdon框架的批量查询主要在表现层封装了主要设计原理,让我们看看表现层Struts的实现步骤:

 

这里以查询商品类别(Category)下所有商品(Product)列表为需求,演示批量的查询的开发步骤。

使用Jdon框架实现批量查询时,无需使用代码创建ModelFormActionForm),Jdon框架提供一个批量查询的缺省实现:com.jdon.strutsutil.ModelListForm

只要在struts-config.xml中配置这个ActionForm就可以:

 <form-bean name="productListForm" type="com.jdon.strutsutil.ModelListForm"/>

习惯地,我们将这个ActionForm命名为:

model名称+ListForm

例如,查询商品列表是显示一个商品Prodcut数据,那么这个ActionForm的名称就是productListForm

也就是说,我们只要在struts-config.xml配置ModelListForm就可以,然后在Jsp页面中使用logic:iterator获取就可以,见Jsp MultiPages标签

 

ModelListForm是一个普通的JavaBeans,主要属性如下:

public class ModelListForm extends ActionForm{

 

  private int allCount = 0;  //符合查询条件的所有记录总数

  private int start = 0;   //当前页面的开始

  private int count = 20;  //当前页面的可显示的记录数

  private boolean hasNextPage = false;  //是否有下一页

 

  /**

   * Model

   */

  private Model oneModel = null;

 

  /**

   * 批量显示的Model集合

   * 这是本类主要的属性

   */

  private Collection list = new ArrayList();

 

  ……

}

 

抽象类ModelListAction

com.jdon.strutsutil.ModelListAction.ModelListAction是一个struts的标准Action,它主要实现如下功能:

从服务层获得符合查询条件PageIterator,这是需要通过getPageIterator方法实现;

展开遍历PageIterator,根据ID,首先从缓存中查询是否有其Model(完整数据记录),如无,则调用服务层从持久层数据库获得,这是需要通过findModelByKey方法实现。

将获得的Model集合封装在ModelListFormlist字段中。

所以,ModelListAction实际是将ID集合换算成Model集合,然后交给ModelListFormJsp页面的显示只和ModelListForm有关。

public abstract class ModelListAction extends Action {

private ModelManager modelManager;

 

//struts action的缺省execute方法

  public ActionForward execute(ActionMapping actionMapping,

                               ActionForm actionForm,

                               HttpServletRequest request,

                               HttpServletResponse response) throws Exception {

    ……

 

    //从服务层获得符合查询条件的PageIterator

    PageIterator pageIterator = getPageIterator(request, start, count);

    if (pageIterator == null) {

      throw new Exception(

          "getPageIterator's result is null, check your ModelListAction subclass");

    }

    //获得ModelListForm实例

    ModelListForm listForm = getModelListForm(actionMapping, actionForm,

                                              request, pageIterator);

    //赋值页面起始数等值到ModelListForm

    listForm.setStart(start);

    listForm.setCount(count);

    listForm.setAllCount(pageIterator.getAllCount());

    listForm.setHasNextPage(pageIterator.isNextPageAvailable());

 

    //根据pageIterator获得Model集合

    Collection c = getModelList(request, pageIterator);

    Debug.logVerbose(" listForm 's property: getList size is " + c.size(), module);

    //Model集合赋值到ModelListFormlist字段

    listForm.setList(c);

    //设置其他ModelModelListForm,以便jsp页面能显示其他Model

    listForm.setOneModel(setupOneModel(request));

    //程序员自己定义的优化ModelListForm其他动作,供实现者自己优化。

  customizeListForm(actionMapping, actionForm, request, listForm);

 

    ……

}

ModelListAction是一个抽象类,它必须有一个实现子类需要有两个方法必须实现:

获得服务层的Pageiterator,也就是getPageIterator方法

根据keyID获得单个Model的方法,findModelByKey方法。

 

其他可选实现的方法有:

是否激活缓存方法protected boolean isEnableCache()

有时,批量查询实现可能不需要缓存,获得每个Model都一定要执行findModelByKey方法。通过覆盖isEnableCache方法实现。

 

批量查询的Jsp页面,可能不只是需要显示某一个Model的很多实例列表;还可能需要其他类型Model实例显示,可以覆盖方法:

protected Model setupOneModel(HttpServletRequest request)

自己再优化加工ModelListForm方法,覆盖方法:

public void customizeListForm(ActionMapping actionMapping,

                                    ActionForm actionForm,

                               HttpServletRequest request,

                              ModelListForm modelListForm ) throws Exception

通过以上方法覆盖实现,基本使用ModelListAction可以实现批量查询的各种复杂功能实现,如果你觉得还不行,那么干脆自己模仿ModelListAction自己实现一个标准的strutsAction子类,只要ActionForm还是ModelListForm,页面显示还是可以使用Jdon框架的页面标签库。

使用ModelListAction可以实现master details,使用setupOneModelcustomizeListForm实现MasterModelListForm则装载的多条显示的details

 

com.jdon.strutsutil.ModelListAction.ModelListAction有两个方法需要实现,共分下面三个步骤:

getPageIterator方法

public PageIterator getPageIterator(HttpServletRequest request, int start, int count)

这是从服务层获得满足查询条件的当前页面所有ModelID(主键)集合,当前页面是通过start count两个变量说明,前者表示从第几个开始;后者表示开始后需要显示多少个Model

以查询商品为例子,ProductListAction继承ModelListAction,详细方法内容如下:

public class ProductListAction extends ModelListAction {

    public PageIterator getPageIterator(HttpServletRequest request, int start,

            int count) {

        //获得一个服务

        ProductManager productManager = (ProductManager) WebAppUtil.getService(

                "productManager", request);

        //从查询参数中获得categoryId

        String categoryId = request.getParameter("categoryId");

        //调用服务的方法

        return productManager.getProductListByCategory(categoryId);

    }

 

    public Model findModelByKey(HttpServletRequest request, Object key) {

    ……

}

所以,关键在于服务层的服务需要有个返回参数类型是方法,这是对服务层服务方法的约束要求,这个服务方法如何实现在后面章节介绍。

findModelByKey方法

这个方法主要也是从服务层获得一个Model实例,ProductListAction的该方法实现如下:

    public Model findModelByKey(HttpServletRequest request, Object key) {

        //获得一个服务

        ProductManager productManager = (ProductManager) WebAppUtil.getService(

                "productManager", request);

        return productManager. getProduct ((String)key);

    }

非常需要注意的是:需要搞清楚这里获得Model实例是哪个?

是商品类别Category 还是商品Product

这主要取决于你的批量查询显示的是什么,如果希望显示的是商品Product列表,当前这个案例是需要查询某个商品类别Category下的所有商品Product列表,那么这里的Model类型就是Product

搞清楚Model类型是Product后,那么我们可能对findModelByKey方法参数Object类型的key有些疑问,其实这个key就是com.jdon.controller.model. PageIterator中重要属性keys(满足查询条件的ID集合)中的一个元素,这里根据key查询获得一个完整Model,所以我们知道,PageIterator中封装的是Model主键,也是数据表的主键,以后需要根据这些ID能够获得唯一一个Model

以上述案例为例:

productManager. getProduct ((String)key);

这是将key类型Object强制转换为String类型,因为PageIteratorkeysID集合)都是String类型。所以, key的类型其实是由你后台创建PageIterator时决定的。

其他深入定制方法

以上面例子为例,商品批量查询除了显示多个商品Product信息外,还需要显示商品目录Category,也就是说在一个页面中,不但显示多个商品,也要显示商品目录的名称,比如商品目录名是“电器”,其下具体商品列表很多。

在一个页面中装入一个以上Model有两种方法:

1. 配置另外一个需要装入ModelActionForm,例如Category对应的ActionFormCategoryForm,只要在struts-config.xml中配置CategoryFormscopesession,同时注意CategoryForm在操作流程中要预先赋值。

2. 由于批量查询的ActionFormModelListForm,我们可以向这个ModelListForm加入一个Model,实现ModelListActioncustomizeListForm方法, 在这个方法中,实现将Category加入的语法::

public void customizeListForm(ActionMapping actionMapping,

                                    ActionForm actionForm,

                               HttpServletRequest request,

                              ModelListForm modelListForm ) throws Exception{

        ModelListForm listForm = (ModelListForm) actionForm;

        ProductManager productManager = (ProductManager) WebAppUtil.getService(

                "productManager", request);

        String categoryId = request.getParameter("categoryId");

        Category category = productManager.getCategory(categoryId);

        listForm.setOneModel(category);

}

如果加入的布置一个Model,那么可以使用DynamicModel将那些Model装载进来,然后在Jsp页面再一个个取出来。

struts-config.xml配置

批量查询与jdonframework.xml配置无关,也就是说jdonframework.xml中没有与批量查询实现相关的配置,简化了工作,这点与CRUD实现相反,CRUD更多的是jdonframework.xml配置(当然都少不了struts-config.xml配置),CRUD缺省情况下除了ModelModelForm以外可以没有表现层编码,而批量查询主要是表现层编码,由于查询条件多种多样,只有靠更多编码才能实现查询的灵活性。

上例子中,商品查询的ModelListForm配置如同一般ActionForm一样配置:

  <form-beans>      

    <form-bean name="productListForm" type="com.jdon.strutsutil.ModelListForm"/>

    ……

   </form-beans>

ProductListAction配置如同一般Action一样:

<action-mappings>

        <action path="/shop/viewCategory"

      type="com.jdon.framework.samples.jpetstore.presentation.action.ProductListAction"

      name="productListForm" scope="request"

      validate="false" >

      <forward name="success" path="/catalog/Category.jsp"/>

    </action>

    ……

</action-mappings>

剩余最后工作就是Jsp标签使用,通过使用strutslogic:iterator将前面配置的productListForm中的Model集合遍历出来。遍历标签语法如下:

<logic:iterate indexId="i"   id="user" name="listForm" property="list" >

      <bean:write name="user" property="userId" />

      <bean:write name="user" property="name" />

</logic:iterate>

上述语法将逐个输出每行记录,如果配合Htmltable语法,输出效果如下:

 

Jsp MultiPages标签

批量查询输出还有一个重要功能:页面显示,如上图的“前页”和“后页”显示,这是使用Jdon框架的标签库实现的。

只要在Jsp页面中你希望显示“前页”和“后页”部位贴入下面代码即可:

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

 

…..

<MultiPages:pager actionFormName="listForm" page="/userListAction.do">

<MultiPages:prev>Prev</MultiPages:prev>

<MultiPages:index />

<MultiPages:next>Next</MultiPages:next>

</MultiPages:pager>

这是一个MultiPages标签,注意标签使用方法前提:

1. Jsp页面头部需要声明该标签:

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

2. web.xml中有声明解释该标签库文件所在位置:

<taglib>

    <taglib-uri>/WEB-INF/MultiPages.tld</taglib-uri>

    <taglib-location>/WEB-INF/MultiPages.tld</taglib-location>

  </taglib>

这表示Jsp中的uri定义/WEB-INF/MultiPages.tld实际是执行本地文件/WEB-INF/ MultiPages.tld,那么在你的Web项目的WEB-INF下必须要有MultiPages.tld文件,可以从Jdon框架源码包目录src\META-INF\tlds下将MultiPages.tld拷贝过去即可,有些开发工具如JBuilder在配置了框架功能后,会自动在建立Web项目时拷贝这些标签库的。

下面说明一下批量查询的MultiPages标签使用方法:

<MultiPages:pager actionFormName="listForm" page="/userListAction.do">

其中actionFormName是你在struts-config.xml中配置的ModelListForm名称,也就是这段配置中的name值:

<form-bean name="listForm" type="com.jdon.strutsutil.ModelListForm"/>

MultiPages中的page是指当前页面是通过哪个action调用产生的,这样,在显示多页其它页面也可以调用这个action,这里是/userListAction.do,很显然,这个/userListAction.do是你的struts-config.xml中的action配置中的path值,如下:

<action name="listForm" path="/userListAction" type="com.jdon.framework.test.web.UserListAction"

scope="request">

      <forward name="success" path="/userList.jsp" />

 </action>

上述配置中的typecom.jdon.framework.test.web.UserListAction是你的ModelListAction实现子类。如果你的查询是条件的,也就是说/userListAction.do后面有查询参数,那么你也需要写在MultiPages后面,如:

<MultiPages:pager actionFormName="listForm" page="/userListAction.do"

paramId="catId" paramName="catId">

MultiPages的语法类似strutshtml:page标签,如果你觉得功能不够丰富,可以自己继承MultiPages.tld中定义的类,将自己子类写入MultiPages.tld配置即可。

参考一例:http://www.jdon.com/jive/thread.jsp?forum=61&thread=24112

MultiPages标签的其它配置很简单,无需变化,如下:

<MultiPages:prev>Prev</MultiPages:prev>

<MultiPages:index />

<MultiPages:next>Next</MultiPages:next>

MultiPages:prev是显示“前页”的标签,Prev是显示“Prev”,你可以使用图标或汉字来替代Prev

<MultiPages:index/>是自动显示 1 2 3 …多个数字。目前只提供数字显示。有一个参数displayCount,表示如果有很多页,显示前面或后面多少个页:

<MultiPages:index displayCount="8" />

表示在当前页面前面或后面如果超过8页,显示8页,例如当前页是第14页,如下:

[Prev ] 1 ... 6 7 8 9 10 11 12 13 14

 

MultiPages:nextMultiPages:prev类似,显示“后页”字样。

MultiPages:prev MultiPages:next还有另外一种写法,如下:

 

<MultiPages:prev name="[Prev ]" />

 

使用了name属性,这样个语法的好处是:当前页面如果没有超过一页,将没有”Prev””Next”字样出现,只有超过一个页面时,才会有相应的字样出现,显示智能化。

如果你觉得MultiPages这些功能还不能满足你的需要,你可以定制自己的标签库,只要继承com.jdon.strutsutil.taglib包下的各个标签库,然后修改自己项目的WEB-INF目录下MultiPages.tldtld文件,将自己编写的标签类替代原来com.jdon.strutsutil.taglib包下的标签类。

 

 


 

案例:表现层实现

CRUD功能只需配置和Jsp实现就可以,而批量查询表现层需要进简单编码,然后再需要配置和Jsp完成。还有一个区别是:前者无需编码,替代的是需要配置jdonframework.xml;而后者因为编码实现;无需配置jdonframework.xml了。

编码:UserListAction

UserListAction继承ModelListAction实现,按照前面批量查询章节,这里完成两个方法:getPageIteratorfindModelByKey

getPageIterator方法如下:

  public PageIterator getPageIterator(HttpServletRequest request, int start,

                                      int count) {

    PageIterator pageIterator = null;

    try {

        TestService testService = (TestService) WebAppUtil.getService("testService",  request);

      pageIterator = testService.getAllUsers(start, count);

    } catch (Exception ex) {

        logger.error(ex);

    }

    return pageIterator;

  }

 findModelByKey方法:

  public Model findModelByKey(HttpServletRequest request, Object key) {

    Model model = null;

    try {

        TestService testService = (TestService) WebAppUtil.getService("testService",  request);

      model = testService.getUser( (String) key);

    } catch (Exception ex) {

        logger.error(ex);

    }

    return model;

 

  }

代码中:

  TestService testService = (TestService) WebAppUtil.getService("testService",   request);

其中testServicejdonframework.xml配置中<pojoService name=" testService "> 中名称。

Struts-config.xml配置

1)配置一个批量查询的ActionForm 首先在struts-config.xml创建一个用于批量分页查询的ActionForm,固定推荐名称为listForm

    <form-bean name="listForm" type="com.jdon.strutsutil.ModelListForm" />   

2)配置UserListAction,按照普通Struts action配置

    <action name="listForm" path="/userListAction" type="jdonframeworktest.web.UserListAction" >

      <forward name="success" path="/userList.jsp" />

    </action>

4)编写userList.jsp,用于分页显示查询结果。

 userList.jsp中使用到分页的标签库:

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

 

<MultiPages:pager actionFormName=" Struts-config.xmlformBeans名称:listForm" page="Struts-config.xml中当前的action path值:/userListAction.do">

<MultiPages:prev>前页</MultiPages:prev>

<MultiPages:index />

<MultiPages:next>后页</MultiPages:next>

</MultiPages:pager>

我们将userList.jsp作为本演示首页,将新增 修改和删除等操作的执行集中在这个页面实现,使用JavaScript来实现。

持久层批量查询实现

这里是为了实现批量查询,如果不需要批量查询,可跳过此步:

持久层需要向前台提供数据库等持久层数据的查询和获得,一般可通过Jdon框架的简单JDBC模板实现,我们创建一个JdbcDao类。

PageIterator对象创建

首先为每个Model引入分页对象;

//通过JNDI获得Datasource

dataSource = (DataSource) sl.getDataSource(JNDINames.DATASOURCE);

//框架使用 为每个Model建立一个分页对象。

pageIteratorSolverOfUser = new PageIteratorSolver(dataSource);

三个方法实现

JdbcDao有三个缺省方法必须才能完成从数据库中获得所要求的数据,这三个方法分别是:

User getUsers(String Id);    //查询获得单个Model实例

PageIterator getUsers(int start, int count); //查询某页面的所有ModelID集合

public void clearCacheOfUser() ;  //清除某个Model的缓存。

上述三个方法中,只有一个方法需要编写代码,其它可委托JdonFramework提供的工具API方法完成。

第一个方法完成: 编写获得单个Usertest的数据库查询方法:从现成的示例拷贝如下:

    public UserTest getUser(String Id) {

        String GET_FIELD = "select  * from testuser where userId = ?";

        List queryParams = new ArrayList();

        queryParams.add(Id);

        UserTest ret = null;

        try {

            List list = pageIteratorSolverOfUser.queryMultiObject(queryParams,

                    GET_FIELD);

            Iterator iter = list.iterator();

            if (iter.hasNext()) {

                Map map = (Map) iter.next();

                ret = new UserTest();

                ret.setName((String) map.get("name"));

                ret.setUserId((String) map.get("userId"));

            }

        } catch (Exception se) {

        }

        return ret;

    }

 

第二个方法实现:编写分页查询SQL实现方法,直接使用框架内部的方法如下:

        String GET_ALL_ITEMS_ALLCOUNT = "select count(1) from testuser ";

 

        String GET_ALL_ITEMS = "select userId  from testuser ";

 

        return pageIteratorSolverOfUser.getDatas("", GET_ALL_ITEMS_ALLCOUNT,

                GET_ALL_ITEMS, start, count);

 注意getDatas的方法参数使用,参考批量查询章节

第三个方法实现.:编写缓存清除方法:该方法是在该Model被新增或删除等情况下调用。  这样,当前台页面新增新的数据后,批量查询能够立即显示新的数据,否则将是缓存中老的数据集合,无法立即更新。

public void clearCacheOfUser() {

    pageIteratorSolverOfUser.clearCache();

  }

 

JdbcDAO模板化编程到此结束。
下一步,使用EJBSession Bean包装这个JdbcDao,如果服务层不是使用EJB实现,直接通过一般的POJO服务包装JdbcDao

基本全部完成。

 

Step By Step:Jdon应用系统案例开发

这是一个从无到有的开发过程,如果你还追求更高的快速性,那么就使用框架应用源码包中的SimpleJdonFrameworkTest项目作为模板,将该整个项目拷贝到另外一个目录,将该目录下的UserTest字符串更换为你的应用模型名称,再修改相应字段即可(现在一些基于脚本语言的快速框架可以自动帮你完成这些)。

案例场景:每个系统都是从域建模入手设计,通过建模将业务需求转化为软件域范围的模型,本文以围绕一个模型实现该模型的基本功能:增删改查(CRUD)和批量分页查询,通过Jdon框架的迅速简化高质量的开发,建立一个复杂系统的基础部分,使得开发者将真正精力集中在每个项目系统的特殊业务处理。

源码见Jdon框架源码包中的simpleMessage项目或SimpleJdonFrameworkTest项目,以下该项目所需要的全部类和配置展示图:5个类、一个接口和两个配置文件

 

案例需求:简单的留言簿,实现留言Message模型的新增、修改、删除和批量查询。

增删改查(CRUD)和批量分页查询是每个系统的基本功能,下面分这两部分描述。

 

CRUD开发步骤

说明:每个应用系统中存在大量重复的CRUD开发流程,通过本框架可快速完成这些基本基础工作量,将精力集中在特殊功能设计上。

CRUD快速开发主要简化了表现层的流程,将其固化,或者是模板化,以配置替代代码编制,灵活而快速。每个Model一套固化CRUD流程。

开发步骤分两个小部分:代码编写和配置。

 

代码:三步代码编写

代码只需要三步:

第一步:域建模:建立sample.model.Message,如下:

 

public class Message extends Model {

  private String messageId;

  private String name;

 

  public String getName() {    return name;  }

  public void setName(String name) {    this.name = name;  }

}

 

注意点:

模型类Message必须继承框架的com.jdon.controller.model.Model,或者实现com.jdon.controller.model.ModelIF接口,5.6版本以后可以使用@Model注解。

该模型类必须有一个能够标识其对象唯一性的主键,如messageId,这个主键相当于数据表的主键。

 

第二步:建立Model组件服务:首先建立模型Message的服务接口sample.service. MessageService

public interface MessageService {

    public void createMessage(EventModel em);

    public void updateMessage(EventModel em);

    public void deleteMessage(EventModel em);

    public Message getMessage(String messageId);

}

至于MessageService的具体实现子类可以在现在或者以后建立,可见源码包中的sample.service.MessageServiceImp.

第三步:建立Model的表现层边界模型:sample.web.MessageForm,必须继承框架的ModelForm,如下:

public class MessageForm extends ModelForm {

 

    private String messageId;

    private String name;

 

public String getName() {    return name;  }

  public void setName(String name) {    this.name = name;  }

}

   

表现层MessageForm内容基本上是从业务层模型Message类中拷贝过来的,主要是为了保持MessageFormMessage的字段一致,我们就可以通过框架内MessageFormMessage的相同字段的复制进行数据传送,将业务层的Message数据传送到表现层MessageForm;或将界面表现层MessageForm传送到Message中。

一个模型Message有关CRUD实现的代码工作到此结束,如果有其他模型,完全按照上述三个步骤再做一次,是不是不太费脑筋?有点模板化开发味道?下面谈谈CRUD实现第二组成部分:配置。

配置分两个配置文件,这两个配置文件分别是:

将前面三步编写的类建立关系:jdonframework.xml

配置界面流程:struts-config.xml

 

配置之一:Jdon框架配置文件

首先我们将前面三步编写的三个类:模型Message、服务MessageService和界面模型MessageForm建立起联系,也就是告诉Jdon框架这三者是解决一个模型增删改查CRUD功能实现的。

由于这个配置文件是告诉Jdon框架的,因此,我们取名为jdonframework.xml,当然你也可以取其他名称,无论取什么名称,都要告诉Jdon框架,在struts-config.xml中配置

  <plug-in className="com.jdon.strutsutil.InitPlugIn">

    <set-property property="modelmapping-config"  value="jdonframework.xml" />

  </plug-in>

 

jdonframework.xml配置内容如下:

<models>

   <!-- 配置模型的类是Message,其主键是messageId -->

   <model key="messageId" class ="sample.model.Message">

      <!-- 下行是配置界面模型MessageForm -->

      <actionForm name="messageForm"/>

      <handler>

    <!-- 以下配置MessageService -->

        <service ref="messageService">

          <getMethod name="getMessage" />

          <createMethod name="createMessage" />

          <updateMethod name="updateMessage" />

          <deleteMethod name="deleteMessage" />

        </service>

      </handler>

    </model>

</models>

<services>

     <!-- 以下配置MessageService -->

    <pojoService name="messageService" class="sample.service.MessageServiceImp"/>

</services>

以上配置是配置模型Message、模型服务MessageService和界面模型MessageForm三者关系的,下面详细说明三个部分的配置:

一、模型Message的配置:

这是通过第一行中的class值来指定当前Modelsample.model.Message

<model key="messageId" class ="sample.model.Message">

其中,Message模型的主键是messageId,这个messageId必须是Message类的一个字段;同时是用来唯一标识唯一的Message模型对象,也就是Object ID,或者可以认为是模型Message对应的数据表message的主键。

二、界面模型MessageForm配置:

<actionForm name="messageForm"/>

可能你已经注意到:这里并没有写界面模型完整类:sample.web.MessageForm,而好像是MessageForm类的名称messageForm

那么配置中messageForm名称是从哪里来的呢?是struts-config.xmlActionForm定义名称,如下:

<struts-config>

  <form-beans>

    <form-bean name="messageForm" type="sample.web.MessageForm" />

     …… 

  </form-beans>

 …..

</struts-config>

可见我们的界面模型完整类sample.web.MessageForm是在struts-config.xmlform-beans中配置,并且命名为messageForm,而这个messageForm就是jdonframework.xml中的messageForm

三、模型服务MessageService配置:

jdonframework.xml中首先申明MessageService完整实现是类sample.service.MessageServiceImp,并且取名为messageService

<pojoService name="messageService" class="sample.service.MessageServiceImp"/>

这样,我们就可以详细将我们自己编写的messageServiceCRUD方法名告诉Jdon框架了:

      <handler>

    <!-- 以下配置MessageService -->

        <service ref="messageService">

          <getMethod name="getMessage" />

          <createMethod name="createMessage" />

          <updateMethod name="updateMessage" />

          <deleteMethod name="deleteMessage" />

        </service>

      </handler>

黑体字部分正是messageService所指的sample.service.MessageServiceImp所继承的接口sample.service. MessageService四个方法,可见前面代码步骤第二步。

配置之二:界面流程配置

界面流程主要是配置CRUD界面流程,Jdon框架CRUD流程主要分两个部分:第一是推出供用户新增修改删除的页面;第二是接受用户提交新增修改过的数据,以便递交到业务层保存。

这部分配置主要是配置struts-config.xml

第一、配置推出CRUD页面流程:

<action name="messageForm" path="/messageAction" type="com.jdon.strutsutil.ModelViewAction"

                  scope="request" validate="false">

      <forward name="create" path="/message.jsp" />

      <forward name="edit" path="/message.jsp" />

    </action>

其中com.jdon.strutsutil.ModelViewActionJdon框架类。只要客户端浏览器调用http://localhost:8080/messageAction.do,通过上述配置将激活forwardname=”create”流程,就能得到一个空白表单的页面message.jsp;如果客户端浏览器调用http://localhost:8080/messageAction.do?action=edit&messageId=18,通过上述配置将激活forward name=”edit”流程,得到一个填满数据的表单页面,供用户修改。

第二、配置:接受用户提交新增修改过的数据,以便递交到业务层保存:

<action name="messageForm" path="/messageSaveAction" type="com.jdon.strutsutil.ModelSaveAction"

                  scope="request" validate="true" input="/message.jsp">

      <forward name="success" path="/result.jsp" />

      <forward name="failure" path="/result.jsp" />

</action>

其实在上一步的message.jsp中已经使用到这一步的配置,在message.jsp的表单action值就是本步配置的path值:/messageSaveAction.do

<html:form action="/messageSaveAction.do" method="POST" >

<html:hidden property="action"/>

MessageId<html:text property="messageId"/>

<br>Name<html:text property="name"/>

<br><html:submit property="submit" value="Submit"/>

</html:form>

在上面message.jsp中一定要有<html:hidden property="action"/>一行。

 

至此,模型MessageCRUD功能开发完毕。

 

批量查询实现

代码:三步代码编写

第一步、表现层编写一个查询Action,继承Jdon框架的com.jdon.strutsutil.ModelListAction,该类名称为sample.web. MessageListAction,完成getPageIteratorfindModelByKey两个方法。

其中getPageIterator方法内容是业务层MessageService的调用:

MessageService messageService= (MessageService) WebAppUtil.getService("messageService",request);

return messageService.getAllMessages(start, count);

所以MessageService接口中必须有getAllMessages这个方法,主要功能是返回PageIterator对象

findModelByKey方法内容也是业务层MessageService的调用:

MessageService messageService= (MessageService) WebAppUtil.getService("messageService", request);

return messageService.getMessage((String)key);

MessageService接口中必须有getMessage方法。

第二步、业务层实现MessageService接口方法getAllMessages内容,一般是直接调用持久层MessageDao方法。

第三步、持久层实现返回PageIterator对象:

public PageIterator getMessages(int start, int count) throws Exception {

        String GET_ALL_ITEMS_ALLCOUNT = "select count(1) from testmessage ";

        String GET_ALL_ITEMS = "select messageId  from testmessage ";

        return pageIteratorSolverOfMessage. getPageIterator (GET_ALL_ITEMS_ALLCOUNT,

                GET_ALL_ITEMS, "",start, count);

    }

如果有参数,可以如下查询:

public PageIterator getMessages(Long categoryId, int start, int count) {

 String GET_ALL_ITEMS_ALLCOUNT = "select count(1) from message where categoryId = ? ";

  String GET_ALL_ITEMS = "select messageId  from message where categoryId = ? ";

  Collection params = new ArrayList(1);

  params.add(categoryId);//参数放在Collection

  return pageIteratorSolver.getPageIterator(GET_ALL_ITEMS_ALLCOUNT, GET_ALL_ITEMS, params, start, count);

}

 

 

配置之一:Jdon框架配置文件

本步骤主要是需要告诉jdonframework.xml我们的MessageService实现子类是什么,以及调用的MessageDao等组件,jdonframework.xml如下:

<services>

  <pojoService name="messageService" class="sample.service.MessageServiceImp"/>

<component name="messageDAO" class="sample.dao.MessageDAO"/>    

           <component name="constants" class="sample.Constants">

                    <constructor value="java:/TestDS"/>

    </component> 

</services>

因为MessageServiceImp类中调用了MessageDAOMessageDAO中又涉及JNDI名称,所以它们之间依赖关系靠Jdon框架的IOC容器实现。MessageServiceImp必须有构造器如下:

public class MessageServiceImp implements MessageService{

private MessageDAO messageDAO;

public MessageServiceImp(MessageDAO messageDAO){

this.messageDAO = messageDAO;

}

}

 

配置之二:界面流程配置

这一步主要是struts-config.xml配置,和通常strutsActionFormAction配置类似:

<form-beans>

  ……

    <form-bean name="listForm" type="com.jdon.strutsutil.ModelListForm" />

</form-beans>

其中com.jdon.strutsutil.ModelListForm是框架批量查询特别使用的类。

<action name="listForm" path="/messageListAction"

type="sample.web.MessageListAction" scope="request">

      <forward name="success" path="/messageList.jsp" />

    </action>

其中sample.web.MessageListAction是我们前面代码编写部分编写的代码。这样,客户端浏览器通过http://localhost:8080/ messageListAction.do就可以实现所有Message批量分页查询显示。

注意,messageList.jsp中编码和通常StrutsJsp编码是一样的,需要使用logic:iteratorActionFormlistFormlist字段中获取单个的Message对象,然后显示这些单个Message对象,,如下:

<logic:iterate indexId="i" id="message" name="listForm" property="list" >

<bean:write name="message" property="name" />

.........

</logic:iterate

messageList.jsp中加入下面标签库可以自动显示多页,缺省一个页面显示30个条目。

<MultiPages:pager actionFormName="listForm" page="/messageListAction.do">

 

<MultiPages:prev name="[Prev ]" />

<MultiPages:index displayCount="1" />

<MultiPages:next name="[Next ]" />

</MultiPages:pager>

 

 

模型Message的批量查询功能已经全部完成。

 

 

 


复杂案例:JPetstore

IBatis.comiBATIS-Jpetstore为例,我们使用Jdon框架对其重构成为Jdon-JPetstore,本章开发环境是Eclipse(本章案也适用其他开发工具),部署运行环境是JBoss

Eclipse是一个非常不错的开源开发工具,使用Eclipse开发和使用JBuilder将有完全不同的开发方式。我们使用Eclipse基于Jdon框架开发一个完全Web应用,或者可以说,开发一个轻量(lightweight)的J2EE应用系统。

通过这个轻量系统开发,说明Jdon框架对完全POJO架构的支持,因为EJB分布式集群计算能力,随着访问量提升,可能需要引入EJB架构,这时只要使用EJB session Bean包装POJO服务则可以无缝升级到EJB。使用Jdon框架可实现方便简单地架构升迁。

Eclipse安装简要说明

1.下载Eclipse:http://www.eclipse.org 的下载点中选择tds ISP 比较快。

2.安装免费插件:

编辑Jsp需要lomboz http://www.objectlearn.com/projects/download.jsp

注意对应的Eclipse版本。

编辑XML,使用Xmlbuddy: http://xmlbuddy.com/

基本上这两个插件就够了。

 

如果希望开发 Hibernate,插件:

http://www.binamics.com/hibernatesync

 

代码折叠

http://www.coffee-bytes.com/eclipse/update-site/site.xml

 

3. 关键学习ant的编写build.xml,在build.xml将你的jsp javaclass打包成warjarear就可以。都可以使用antjar打包,build.xml只要参考一个模板就可以:SimpleJdonFrameworkTest.rar 有一个现成的,可拷贝到其它项目后修改后就可用.

然后在这个模板上修改,参考 ant的命令参考:

http://ant.apache.org/manual/tasksoverview.html

网上有中文版的ant参考,在google搜索就能找到。

关键是学习antbuild.xml编辑,SimpleJdonFrameworkTest.rar 有一个现成的,可拷贝到其它项目后修改后就可用.

ant编译替代Eclipse的缺省编译:选择项目属性-->Builders ---> new --> Ant Builder --->选择本项目的build.xml workspace 选择本项目

eclipse开发就这些,非常简单,不象Jbuilder那样智能化,导致项目目录很大。eclipse只负责源码开发,其它都由ant负责

架构设计要点

Jdon-JPetstore除了保留iBATIS-JPetstore 4.0.5的域模型、持久层ibatis实现以及Jsp页面外,其余部分因为使用了Jdon框架而和其有所不同。

保留域模型和Jsp页面主要是在不更改系统需求的前提下,重构其架构实现为Jdon框架,通过对比其原来的实现或SpringJPetstore实现,可以发现Jdon框架的使用特点。

在原来jpetstore iBatis包会延伸到表现层,例如它的分页查询PaginatedListiBatis只是持久层框架,它的作用范围应该只限定在持久层,这是它的专业范围,如果超过范围,显得 ….。所以,在Jdon-Jpetstore中将iBatis封装在持久层(砍掉PaginatedList这只太长的手),Jdon框架是一种中间层框架,联系前后台的工作应该由Jdon这样的中间层框架完成。

iBatis 4.0.5版本中它使用了一个利用方法映射Reflection的小框架这样将原来需要在Action实现方法整入了ActionForm中实现ActionForm变成了一个复杂的对象页面表单抽象以及与后台Service交互ActionForm中调用后台服务是通过单态模式实现这是在一般J2EE开发中忌讳的一点关于单态模式的讨论可见http://www.jdon.com/jive/article.jsp?forum=91&thread=17578

Jdon框架使用了微容器替代单态,消除了Jpetstore的单态隐患,而且也简化了ActionForm和服务层的交互动作(通过配置实现)。

用户注册登陆模块实现

用户域建模(Model

首先,我们需要从域建模开始,建立正确的领域模型,以用户账号为例,根据业务需求我们确立用户账号的域模型Account,该模型需要继承Jdon框架中的com.jdon.controller.model.Model

 

public class Account extends Model {

 

  private String username;

  private String password;

  private String email;

  private String firstName;

  private String lastName;

  ……

 

}

username是主键。

域模型建立好之后就可以花开两朵各表一支表现层和持久层可以同时开发先谈谈持久层关于用户模型的CRUD功能实现。

持久层Account CRUD实现

主要是用户的新增和修改主要用于注册新用户和用户资料修改。

public interface AccountDao {

  Account getAccount(String username);  //获得一个Account

  void insertAccount(Account account);  //新增

  void updateAccount(Account account); //修改

}

持久层可以使用多种技术实现,例如Jdon框架的JdbcTemp代码实现比较方便,如果你的sql语句可能经常改动,使用iBatissql语句XML定义有一定好处,本例程使用Jpetstore原来的持久层实现iBatis。见源码包中的Account.xml

 

表现层Account表单创建(ModelForm

这是在Domain Model建立后最重要的一步,是前台表现层Struts开发的起步,表单创建有以下注意点:

表单类必须继承com.jdon.model.ModelForm

表单类基本是Domain Model的影子,每一个Model对应一个ModelForm实例,所谓对应:就是字段名称一致。ModelForm实例是由Model实例复制获得的。

 

public class AccountForm extends ModelForm {

 

  private String username;

  private String password;

  private String email;

  private String firstName;

  private String lastName;

  ……

 

}

当然AccountForm可能有一些与显示有关的字段例如注册时有英文和中文选择以及类别的选择那么增加两个字段在AccountForm

  private List languages;

  private List categories;

这两个字段需要初始化值的,因为在AccountForm对应的Jsp的页面中要显示出来,这样用户才可能进行选择。选择后的值将放置在专门的字段中。

有两种方式初始化这两个字段:

1. AccountForm构造方法中初始化,前提是:这些初始化值是常量,如:

public AccountForm() {

    languages new ArrayList();

    languages.add("english");

    languages .add("japanese");

}

2.如果初始化值是必须从数据库中获取,那么采取前面章节介绍的使用ModelHandler来实现,这部分又涉及配置和代码实现,缺省时我们考虑通过jdonframework.xml配置实现。

 

Account CRUDstruts-config.xml的配置

第一步配置ActionForm

上节编写了ModelForm代码,ModelForm也就是strutsActionForm,在struts-config.xml中配置ActionForm如下:

 <form-bean name="accountFrom" type="com.jdon.framework.samples.jpetstore.presentation.form.AccountForm"/>

第二步配置Action

这需要根据你的CRUD功能实现需求配置,例如本例中用户注册和用户修改分开,这样,配置两套ModelViewActionModelSaveAction,具体配置见源码包中的struts-config-security.xml,这里将struts-config.xml根据模块划分成相应的模块配置,实现多模块开发,本模块是用户注册登陆系统,因此取名struts-config-security.xml

Account CRUDJdonframework.xml配置

    <model key="username" class ="com.jdon.framework.samples.jpetstore.domain.Account">

      <actionForm name="accountForm"/>

      <handler>

        <service ref="accountService">

          <initMethod   name="initAccount" />                  

          <getMethod    name="getAccount" />

          <createMethod name="insertAccount" />

          <updateMethod name="updateAccount" />

          <deleteMethod name="deleteAccount" />

        </service>

      </handler>

    </model> 

.其中有一个initMethod主要用于AccuntForm对象的初始化。其他都是增删改查的常规实现。

Account CRUD Jsp页面实现

在编辑页面EditAccountForm.jsp中加入:

<html:hidden name="accountFrom" property="action" value="create" />

在新增页面NewAccountForm.jsp加入:

<html:hidden name="accountFrom" property="action" value="edit" />

所有的字段都是直接来自accountFrom

 

整理模块配置

商品模块功能完成,struts提供了多模块开发,因此我们可以将这一模块单独保存在一个配置中:/WEB-INF/struts-config-security.xml,这样以后扩展修改起来方便。

 

商品查询模块实现

iBATIS-JPetstore中没有单独的CategoryForm,而是将三个ModelCategoryProduct、,Item合并在一个CatalogBean中,这样做的缺点是拓展性不强,将来这三个Model也许需要单独的ActionForm

由于我们使用Jdon框架的CRUD功能配置实现,因此,不怕细分这三个Model带来代码复杂和琐碎。

由于原来的Jpetstore“偷懒”,没有实现Category Product等的CRUD功能,只实现它们的查询功能,因此,我们使用Jdon框架的批量查询来实现查询。

持久层 Product批量查询实现

商品查询主要有两种批量查询,根据其类别IDCategoryId查询所有该商品目录下所有的商品;根据关键字搜索符合条件的所有商品,下面以前一个功能为例子:

iBatis-jpetstore使用PaginatedList作为分页的主要对象,该对象需要保存到HttpSession中,然后使用PaginatedListNextPage等直接遍历,这种方法只适合在小数据量合适,J2EE编程中不推荐向HttpSession放入大量数据,不利于cluster

根据Jdon批量查询的持久层要求,批量查询需要两种SQL语句实现:符合条件的ID集合和符合条件的总数:以及单个Model查询。

  //获得ID集合

 List getProductIDsListByCategory(String categoryId, int pagessize);

  //获得总数

  int getProductIDsListByCategoryCount(String categoryId);

  //单个Model查询

  Product getProduct(String productId) ;

这里我们需要更改一下iBatis原来的Product.xml配置,原来,它设计返回的是符合条件的所有Product集合,而我们要求是Product ID集合。

修改Product.xml如下:

<resultMap id="productIDsResult" class="java.lang.String">

    <result property="value" column="PRODUCTID"/>

</resultMap>          

            

<select id="getProductListByCategory" resultMap="productIDsResult" parameterClass="string">

    select PRODUCTID from PRODUCT where CATEGORY = #value#

</select>

    

<select id="getProductListByCategoryCount"  resultClass="java.lang.Integer" parameterClass="string">

    select count(1) as value from PRODUCT where CATEGORY = #value#

</select>

    ProductDaoIBatis DAO实现,读取Product.xml中配置:

    public List  getProductIDsListByCategory(String categoryId, int start, int pagessize) {

        return sqlMapDaoTemplate.queryForList(

                "getProductListByCategory", categoryId, start, pagessize);

    }

 

    public int getProductIDsListByCategoryCount(String categoryId){

        Integer countI = (Integer)sqlMapDaoTemplate.queryForObject(

                "getProductListByCategoryCount", categoryId);

        return countI.intValue();

    }  

这样,结合配置的iBatis DAOJdon框架批量查询,在ProductManagerImp中创建PageIterator,当然这部分代码也可以在ProductDao实现,创建PageIterator代码如下:

           public PageIterator getProductIDsListByCategory(String categoryId, int start, int count)

  {

      PageIterator pageIterator = null;

       try {

           List list = productDao.getProductIDsListByCategory(categoryId, start, count);

           int allCount = productDao.getProductIDsListByCategoryCount(categoryId);

           int currentCount = start + list.size();

           pageIterator = new PageIterator(allCount, list.toArray(), start,

                   (currentCount < allCount)?true:false);

 

         } catch (DaoException daoe) {

             Debug.logError(" Dao error : " + daoe, module);

         }

       

       return pageIterator;

表现层Product批量查询实现

    根据批量查询的编程步骤,在表现层主要是实现ModelListAction继承、配置和Jsp编写,下面分步说:

    第一步,创建一个ModelListAction子类ProductListAction,实现两个方法:getPageIteratorfindModelByKeygetPageIterator方法如下:

      public PageIterator getPageIterator(HttpServletRequest request, int start,

            int count) {

        ProductManager productManager = (ProductManager) WebAppUtil.getService(

                "productManager", request);

        String categoryId = request.getParameter("categoryId");

        return productManager.getProductIDsListByCategory(categoryId, start, count);

                

    }

    findModelByKey方法如下:

      public Model findModelByKey(HttpServletRequest request, Object key) {

        ProductManager productManager = (ProductManager) WebAppUtil.getService(

                "productManager", request);

        return productManager.getProduct((String)key);

    }

    由于我们实现的是查询一个商品目录下所有商品功能,因此,需要显示商品目录名称,而前面操作的都是Product模型,所以在显示页面也要加入商品目录Category模型,我们使用ModelListActioncustomizeListForm方法:

    public void customizeListForm(ActionMapping actionMapping,

            ActionForm actionForm, HttpServletRequest request,

            ModelListForm modelListForm) throws Exception {

        ModelListForm listForm = (ModelListForm) actionForm;

        ProductManager productManager = (ProductManager) WebAppUtil.getService(

                "productManager", request);

        String categoryId = request.getParameter("categoryId");

        Category category = productManager.getCategory(categoryId);

        listForm.setOneModel(category);

    }

    第二步,配置struts-config.xml,配置ActionFormAction

    <form-bean name="productListForm" type="com.jdon.strutsutil.ModelListForm"/>

    action配置如下:

    <action path="/shop/viewCategory"

type="com.jdon.framework.samples.jpetstore.presentation.action.ProductListAction"

      name="productListForm" scope="request"

      validate="false" >

      <forward name="success" path="/catalog/Category.jsp"/>

    </action>

  第三步,编写Category.jsp

  productListForm中取出我们要显示两个模型,一个是oneModel中的Category;另外一个是Product Model集合listJsp语法如下:

<bean:define id="category" name="productListForm" property="oneModel" />

<bean:define id="productList" name="productListForm" property="list" />

我们可以显示商品目录名称如下:

<h2><bean:write name="category" property="name" /></h2>

这样我们就可以遍历productList中的Product如下:

<logic:iterate id="product" name="productList" >

  <tr bgcolor="#FFFF88">

  <td><b><html:link paramId="productId" paramName="product" paramProperty="productId" page="/shop/viewProduct.shtml"><font color="BLACK"><bean:write name="product" property="productId" /></font></html:link></b></td>

  <td><bean:write name="product" property="name" /></td>

  </tr>

</logic:iterate>

加上分页标签库如下:

  <MultiPages:pager actionFormName="productListForm " page="/shop/viewCategory.do"

               paramId="categoryId" paramName="category" paramProperty="categoryId">

<MultiPages:prev><img src="../images/button_prev.gif" border="0"></MultiPages:prev>

<MultiPages:index />

<MultiPages:next><img src="../images/button_next.gif" border="0"></MultiPages:next>

</MultiPages:pager>

至此,一个商品目录下的所有商品批量查询功能完成,由于是基于框架的模板化编程,直接上线运行成功率高。

商品搜索批量查询:

参考上面步骤,商品搜索也可以顺利实现,从后台到前台按照批量查询这条线索分别涉及的类有:

持久层实现:ProductDao中的三个方法:

List searchProductIDsList(String keywords, int start, int pagessize); //ID集合

 

int searchProductIDsListCount(String keywords); //总数

 

Product getProduct(String productId) ; //单个Model

表现层:建立ProductSearchAction类,配置struts-config.xml如下:

    <action path="/shop/searchProducts"

type="com.jdon.framework.samples.jpetstore.presentation.action.ProductSearchAction"

      name="productListForm" scope="request"

      validate="false">

      <forward name="success" path="/catalog/SearchProducts.jsp"/>

    </action>

与前面使用的都是同一个ActionFormproductListForm

编写SearchProducts .jsp,与Category.jsp类似,相同的是ActionForm;不同的是action

商品条目Item批量查询

条目Item批量实现与Product批量查询类似:

持久层:ItemDao提供三个方法:

List getItemIDsListByProduct(String productId, int start, int pagessize);//ID集合

 

  int getItemIDsListByProductCount(String productId);//总数

 

  Item getItem(String itemId); //单个Model

表现层:创建一个ItemListAction继承ModelListAction:完成getPageIteratorfindModelByKey,如下:

public class ItemListAction extends ModelListAction {

 

    public PageIterator getPageIterator(HttpServletRequest request, int start,

            int count) {

        ProductManager productManager = (ProductManager) WebAppUtil.getService(

                "productManager", request);

        String productId = request.getParameter("productId");

        return productManager.getItemIDsListByProduct(productId, start, count);

    }

 

    public Model findModelByKey(HttpServletRequest request, Object key) {

        ProductManager productManager = (ProductManager) WebAppUtil.getService(

                "productManager", request);

        return productManager.getItem((String)key);

    }

 

    public void customizeListForm……….

}

与前面的ProductListAction相比,非常类似,不同的是Model名称不一样,一个是Product一个是Item;

struts-config.xml配置如下:

    <form-bean name="itemListForm" type="com.jdon.strutsutil.ModelListForm"/>

 

    <action path="/shop/viewProduct"

type="com.jdon.framework.samples.jpetstore.presentation.action.ItemListAction"

      name="itemtListForm" scope="request"

      validate="false">

      <forward name="success" path="/catalog/Product.jsp"/>

    </action>

比较前面product的配置,非常类似,其实itemListFormproductListForm是同一个ModelListForm类型,可以合并起来统一命名为listForm,节省ActionForm的配置。

Product.jsp页面与前面的Category.jsp SearchProdcuts.jsp类似。

<bean:define id="product" name="itemListForm" property="oneModel" />

<bean:define id="itemList" name="itemListForm" property="list" />

分页显示:

  <MultiPages:pager actionFormName="itemListForm" page="/shop/viewProduct.do"

              paramId="productId" paramName="product" paramProperty="productId">

    …..

商品条目Item单条查询

单个显示属于CRUD中的一个查询功能,我们需要建立Model对应的ModelForm,将Item的字段拷贝到ItemForm中。配置这个ActionForm如下:

    <form-bean name="itemForm"

type="com.jdon.framework.samples.jpetstore.presentation.form.ItemForm"/>

第二步:因为这个功能属于CRUD一种,无需编程,但是需要配置jdonframework.xml

   <model key="itemId" class ="com.jdon.framework.samples.jpetstore.domain.Item">

      <actionForm name="itemForm"/>

      <handler>

        <service ref="productManager">

          <getMethod name="getItem" />

        </service>

      </handler>

    </model>

配置中只要一个方法getMethod就可以,因为只用到CRUD中的读取方式。

第三步:配置struts-config.xml如下:

    <action  path="/shop/viewItem"  type="com.jdon.strutsutil.ModelDispAction"

             name="itemForm" scope="request"

             validate="false">

      <forward name="success" path="/catalog/Item.jsp" />

      <forward name="failure" path="/catalog/Product.jsp" />

    </action>

第四步编辑Item.jsp,现在开始发现一个问题,Item.jsp中不只是显示Item信息,还有Product信息,而前面我们定义的是Item信息,如果使得Item.jsp显示Product信息呢,这就从设计起源Domain Model上考虑,在ItemModel中有Product引用:

  private Product product;

  public Product getProduct() { return product; }

  public void setProduct(Product product) { this.product = product; }

ItemProduct的多对一关系其实应该在域建模开始就考虑到了。

那么,我们只要在持久层查询Item时,能够将其中的Product字段查询就可以。在持久层的iBatisProduct.xml实现有下列SQL语句:

  <select id="getItem" resultMap="resultWithQuantity" parameterClass="string">

    select

      I.ITEMID, LISTPRICE, UNITCOST, SUPPLIER, I.PRODUCTID, NAME,

      DESCN, CATEGORY, STATUS, ATTR1, ATTR2, ATTR3, ATTR4, ATTR5, QTY

    from ITEM I, INVENTORY V, PRODUCT P where P.PRODUCTID = I.PRODUCTID and I.ITEMID = V.ITEMID and I.ITEMID = #value#

  </select>

这段语法实际在查询Item时,已经将Product查询出来,这样Item Model中已经有Product数据,因为ActionFormModel映射,因此,前台Jsp也可以显示Product数据。

Item.jsp中,进行下面定义:

<bean:define id="product" name="itemForm " property="product" />

<bean:define id="item" name="itemForm " />

itemFormproduct属性定义为product即可;这样不必大幅度修改原来的Item.jsp了。

整理模块配置

商品模块功能完成,struts提供了多模块开发,因此我们可以将这一模块单独保存在一个配置中:/WEB-INF/struts-config-catalog.xml,这样以后扩展修改起来方便。

 

购物车模块实现

购物车属于一种有状态数据,也就是说,购物车的scope生命周期是用户,除非这个用户离开,否则购物车一直在内存中存在。

有态POJO服务

现在有两种解决方案:

第一,将购物车状态作为数据类,保存到ActionForm中,设置scopesession,这种形式下,对购物车的数据操作如加入条目等实现不很方便,iBatis-jpetstore 4.0.5就采取这个方案,在数据类Cart中存在大量数据操作方法,那么Cart这个类到底属于数据类Model?还是属于处理服务类呢?

在我们J2EE编程中,通常使用两种类来实现功能,一种是数据类,也就是我们设计的Model;一种是服务类,如POJO服务或EJB服务,服务属于一种处理器,处理过程。使用这两种分类比较方便我们来解析业务需求,EJB中实体BeanSession Bean也是属于这两种类型。

iBatis-jpetstore 4.0.5则是将服务和数据类混合在一个类中,这也属于一种设计,但是我们认为它破坏了解决问题的规律性,而且造成数据和操作行为耦合性很强,在设计模式中我们还使用桥模式来分离抽象和行为,因此这种做法可以说是反模式的。那么我们采取数据类和服务分离的方式方案来试试看:

第二.购物车功能主要是对购物车这个ModelCRUD,与通常的CRUD区别是,数据是保存到HttpSession,而不是持久化到数据库中,是数据状态保存不同而已。所以如果我们实现一个CartService,它提供addupdatedelete等方法,只不过操作对象不是数据库,而是其属性为购物车Cart,然后将该CarService实例保存到HttpSession,实现每个用户一个CartService实例,这个我们成为有状态的POJO服务。

这种处理方式类似EJB架构处理,如果我们业务服务层使用EJB,那么使用有态会话Bean实现这个功能。

现在问题是,Jdon框架目前好像没有提供有状态POJO服务实例的获得,那么我们自己在WebAppUtil.getService获得实例后,保存到HttpSession中,下次再到HttpSession中获得,这种有状态处理需要表现层更多代码,这就不能使用Jdon框架的CRUD配置实现了,需要我们代码实现ModelHandler子类。

考虑到可能在其他应用系统还有这种需求,那么能不能将有状态的POJO服务提炼到Jdon框架中呢?关键使用什么方式加入框架,因为这是设计目标服务实例的获得,框架主要流程代码又不能修改,怎么办?

Jdon框架的AOP功能在这里显示了强大灵活性,我们可以将有状态的POJO服务实例获得作为一个拦截器,拦截在原来POJO服务实例获得之前。在Jdon框架设计中,目标服务实例的获得一般只有一次。

创建有状态POJO服务拦截器com.jdon.aop.interceptor. StatefulInterceptor,再创建一个空接口:com.jdon.controller.service.StatefulPOJOService,需要实现有状态实例的POJO类只要继承这个接口就可以。

配置aspect.xml,加入这个拦截器:

  <interceptor name="statefulInterceptor" class="com.jdon.aop.interceptor.StatefulInterceptor"

pointcut="pojoServices" />    

这里需要注意的是:你不能让一个POJO服务类同时继承Poolable,然后又继承Stateful,因为这是两种不同的类型,前者适合无状态POJO;后者适合CartService这样有状态处理;这种选择和EJB的有态/无态选择是一样的。

ModelService设计

购物车模块主要围绕域模型Cart展开,需要首先明确Cart是一个什么样的业务模型,购物车页面是类似商品条目批量查询页面,不过购物车中显示的不但是商品条目,还有数量,那么我们专门创建一个Model来指代它,取名为CartItemCartItemItem父集,多了一个数量。

这样购物车页面就是CartItem的批量查询页面,然后还有CartItemCRUD操作,所以购物车功能主要是CartItemCRUD和批量查询功能。

iBatis 4.0.5原来设计了专门Cart Model,其实这个Cart主要是一个功能类,因为它的数据项只有一个MapList,这根本不能代表业务需求中的一个模型。虽然iBatis 4..0.5也可以自圆其说实现了购物车功能,但是这种实现是随心所欲,无规律性可遵循,因而以后维护起来也是困难,维护人员理解困难,修改起来也没有章程可循,甚至乱改一气。

CartItem可以使用iBatis原来的CartItem,这样也可保持Cart.jsp页面修改量降低。删除原来的Cart这个Model,建立对应的CartService,实现原来的Cart一些功能。

public interface CartService {

  CartItem getCartItem(String itemId);

  void addCartItem(EventModel em);

  void updateCartItem(EventModel em);

  void deleteCartItem(EventModel em);

  PageIterator getCartItems();

}

CartServiceImpCartService子类,它是一个有状态POJO服务,代码简要如下:

public class CartServiceImp implements CartService, Stateful{

    private ProductManager productManager;

    //将原来iBatis Cart类中两个属性移植到CartServiceImp

    private final Map itemMap = Collections.synchronizedMap(new HashMap());

    private  List itemList = new ArrayList();

 

    public CartServiceImp(ProductManager productManager) {

        super();

        this.productManager = productManager;

    }

    ……

}

itemMap是装载CartItem的一个Map,是类属性,由于CartServiceImp是有状态的,每个用户一个实例,那么也就是每个用户有自己的itemMap列表,也就是购物车。

CartServiceImp中的 getCartItemIDs是查询购物车当前页面的购物条目,属于批量分页查询实现,这里有一个需要考量的地方,是getCartItems方法还是getCartItemIDs方法?也就是返回CartIem的实例集合还是CartItemItemId集合?按照前面标准的Jdon框架批量分页查询实现,应该返回CartItemItemId集合,然后由Jdon框架的ModelListAction根据ItemId首先从缓存中获得CartItem实例,但是本例CartItem本身不是持久化在数据库,而也是内存HttpSession中,所以ModelListAction这种流程似乎没有必要。

如果将来业务需求变化,购物车状态不是保存在内存而是数据库,这样,用户下次登陆时,可以知道他上次购物车里的商品条目,那么采取Jdon框架标准查询方案还是有一定扩展性的。

这里,我们就事论事,采取返回CartIem的实例集合,展示一下如何灵活应用Jdon框架的批量查询功能。下面是CartServiceImp getCartItems方法详细代码:

 public PageIterator getCartItems(int start, int count) {

        int offset = itemList.size() - start; //获得未显示的总个数

        int pageCount = (count < offset)?count:offset;

        List pageList = new ArrayList(pageCount); //当前页面记录集合

        for(int i=start; i< pageCount + start;i++){

            pageList.add(itemList.get(i));

        }

        int allCount = itemList.size();

        int currentCount = start + pageCount;

        return new PageIterator(allCount, pageList.toArray(new CartItem[0]), start,

                (currentCount < allCount)?true:false);

}

getCartItems方法是从购物车所有条目itemList中查询获得当前页面的条目,并创建一个PageIterator

注意,现在这个PageIteratorkeys属性中装载的不是数据ID集合,而是完整的CartItem集合,因为上面代码中pageList中对象是从itemList中获得,而itemList中装载的都是CartItem

表现层购物车显示功能

由于PageIterator中封装的是完整Model集合,而不是ID集合,所以现在表现层有两种方案,继承框架的ModelListAction;或重新自己实现一个Action,替代ModelListAction

这里使用继承框架的ModelListAction方案,巧妙地实现我们的目的,省却编码:

public class CartListAction extends ModelListAction {

 

    public PageIterator getPageIterator(HttpServletRequest request, int arg1, int arg2) {

        CartService cartService = (CartService)WebAppUtil.getService("cartService", request);

        return cartService.getCartItems();

    }

 

    public Model findModelByKey(HttpServletRequest arg0, Object key) {

        return (Model)key; //因为key不是主键,而是完整的Model,直接返回

    }

   

    protected boolean isEnableCache(){

        return false;  //无需缓存,CartItem本身实际是在内存中。

    }

 

}

配置struts-config.xml

<form-beans>

   <form-bean name="listForm" type="com.jdon.strutsutil.ModelListForm"/>

</form-beans>        

 <action-mappings>

    <action path="/shop/viewCart"

type="com.jdon.framework.samples.jpetstore.presentation.action.CartListAction"

      name="listForm" scope="request"

      validate="false">

      <forward name="success" path="/cart/Cart.jsp"/>

    </action>

    ……

  </action-mappings>

上面是购物车显示实现,只要调用/shop/viewCart.shtml就可以显示购物车了。

Cart.jsp页面插入下面标签:

<logic:iterate id="cartItem" name="listForm" property="list">

    ….

</logic:iterate>

分页显示标签如下:

<MultiPages:pager actionFormName="listForm" page="/shop/viewCart.shtml">

<MultiPages:prev name="<font color=green><B>&lt;&lt; Prev</B></font>"/>

<MultiPages:index />

<MultiPages:next name="<font color=green><B>Next &gt;&gt;</B></font>"/>

</MultiPages:pager>

 

 

购物车新增删除条目功能

前面完成了购物车显示功能,下面是设计购物车的新增和删除、修改功能。

参考Jdon框架的CRUD功能实现,ModelCartItem,配置jdonframework.xml使其完成新增删除功能:

             <model key="workingItemId"

                      class="com.jdon.framework.samples.jpetstore.domain.CartItem">

                      <actionForm name="cartItemForm"/>

                      <handler>

                               <service ref="cartService">

                                        <createMethod name="addCartItem"/>

                                        <deleteMethod name="deleteCartItem"/>

                               </service>

                      </handler>

             </model>

在这个配置中,只有新增和删除方法,修改方法没有,因为购物车修改主要是其中商品条目的数量修改,它不是逐条修改,而是一次性批量修改,这里的ModelCartItem,这是购物车里的一个条目,因此如果这里写修改,也只是CartItem一个条目的修改,不符合我们要求。下面专门章节实现这个修改。

表现层主要是配置,没有代码,代码都依靠cartService中的addCartItemdeleteCartItem实现:例如:

    public void addCartItem(EventModel em) {

        CartItem cartItem = (CartItem) em.getModel();

        String workingItemId = cartItem.getWorkingItemId();

        ……

    }

注意addCartItem中从EventModel实例中获取的ModelCartItem,这与我们在jdonframework.xml中上述定义的Model类型是统一的。

Struts-config.xml中定义是CRUD的标准定义,注意,这里只有ModelSaveAction无需ModelViewAction,因为将商品条目加入或删除购物车这个功能没有专门的显示页面:

    <action path="/shop/addItemToCart" type="com.jdon.strutsutil.ModelSaveAction"

      name="cartItemForm" scope="request"

      validate="false">

      <forward name="success" path="/cart/Cart.jsp"/>

    </action>

 

    <action path="/shop/removeItemFromCart" type="com.jdon.strutsutil.ModelSaveAction"

      name="cartItemForm" scope="request"

      validate="false">

      <forward name="success" path="/cart/Cart.jsp"/>

    </action>

注意,调用删除功能时,需要附加action参数:

/shop/removeItemFromCart.shtml?action=delete

/shop/addItemToCart.shtml是新增属性,缺省后面无需跟参数。

购物车条目批量修改功能

上面基本完成了购物车主要功能;购物车功能一个复杂性在于其显示功能和修改功能合并在一起,修改功能是指修改购物车里所有商品条目的数量。

既然有修改功能,而且这个修改功能比较特殊,我们需要设计一个独立的ActionForm,用来实现商品条目数量的批量修改。

首先设计一个ActionFormModelForm),该ModelForm主要用来实现购物车条目数量的更改,取名为CartItemsForm,其内容如下:

public class CartItemsForm extends ModelForm {

    private String[] itemId;

    private int[]quantity;

    private BigDecimal totalCost;

    …..

}

itemIdquantity设计成数组这样Jsp页面可以一次性提交多个itemIdquantity数值。

现在CartItemsForm已经包含前台jsp输入的数据,我们还是将其传递递交到服务层实现处理。因此建立一个Model,内容与CartItemsForm类似,这里的Model名为CartItems,实际是一个传输对象。

public class CartItems extends Model{

    private String[] itemId;

    private int[] quantity;

    private BigDecimal totalCost;

    ……

}

表现层在jdonframework.xml定义配置就无需编码配置如下

             <model key=" "

                      class="com.jdon.framework.samples.jpetstore.domain.CartItems">

                      <actionForm name="cartItemsForm"/>

                      <handler>

                               <service ref="cartService">

                                        <updateMethod name="updateCartItems"/>

                               </service>

                      </handler>

             </model>

上面配置中,ModelCartItemsActionFormcartItemsForm,这两个是专门为批量修改设立的。只有一个方法updateMethod。因为在这个更新功能中,没有根据主键从数据库查询Model的功能,因此,这里modelkey可以为空值。

服务层CartServiceImpupdateCartItems方法实现购物车条目数量更新:

    public void updateCartItems(EventModel em) {

        CartItems cartItems = (CartItems) em.getModel();

        try {

            String[] itemIds = cartItems.getItemId();

            int[] qtys = cartItems.getQuantity();

            int length = itemIds.length;

            for (int i = 0; i < length; i++) {

                updateCartItem(itemIds[i], qtys[i]);//逐条更新购物车中的数量

            }

        } catch (Exception ex) {

            logger.error(ex);

        }

    }

 

注意updateCartItems中从EventModel取出的是CartItems和前面addCartItem方法中取出的是CartItem Model类型不一样这是因为这里我们在jdonframework.xml中定义与updateCartItems相对应的ModelCartItems.

最后一步工作是Cat.jsp中加入CartItemsForm,能够在购物车显示页面有一个表单提交,客户按提交按钮,能够立即实现当前页面购物车数量的批量修改。Cat.jsp加入如下代码:

<html:form action="/shop/updateCartQuantities.shtml" method="post" >

<html:hidden property="action" value="edit" />

……

<input type="hidden" name="itemId" value="<bean:write name="cartItem" property="workingItemId"/>">

  <input type="text" size="3" name="quantity" value="<bean:write name="cartItem"

property="quantity"/>" />

…….

注意,一定要有action赋值edit这一行,这样提交给updateCartQuantities.shtml实际是ModelSaveAction时,框架才知道操作性质。

购物车总价显示功能

最后,还有一个功能需要完成,在购物车显示时,需要显示当前购物车的总价格,注意不是显示当前页面的总价格,所以无法在Cart.jsp直接实现,必须遍历购物车里所有CartItem计算总数。

该功能是购物车显示时一起实现,购物车显示是通过CartListAction实现的,这个CartListAction实际是生成一个ModelListForm,如果ModelListForm能够增加一个getTotalPrice方法就可以,因此有两种实现方式:继承ModelListForm加入自己的getTotalPrice方法;第二种无需再实现自己的ModelListFormModelListForm可以携带一个Model,通过setOneModel即可,这个方法是在ModelListAction的子类CartListAction可以override覆盖实现的,在CartListAction加入下列方法:

  protected Model setOneModel(HttpServletRequest request){

        CartService cartService = (CartService)WebAppUtil.getService("cartService", request);

        CartItems cartItems = new CartItems();

        cartItems.setTotalCost(cartService.getSubTotal());       

        return cartItems;

    }

我们使用空的CartItems作为携带价格总数的Model,然后在Cart.jsp中再取出来显示:

<bean:define id="cartItems " name="listForm" property="oneModel" />

<b>Sub Total: <bean:write name="cartItems" property="subTotal" format="$#,##0.00" />

将当前页面listForm中属性oneModel定义为cartItems,它实际是我们定义的CartItems

下一行取出总价即可。

用户喜欢商品列表功能

在显示购物车时,需要一起显示该用户喜欢的商品列表,很显然这是一个批量分页查询实现,但是它有些特殊,它首先显示的第一页不是由URL调用的,而是嵌入在购物车显示中,那么只能在购物车显示页面的ModellistForm中做文章。

在上节中,在CartListActionsetOneModel方法中,使用CartItems作为价格总数的载体,现在恐怕我们也要将之作为本功能实现载体。

还有一种实现载体,就是其他scopesessionActionFormAccountForm很适合做这样的载体,而且和本功能意义非常吻合,所以在AccountForm/Account中增加一个myList字段,在myList字段中,放置的是该用户喜欢的商品Product集合,注意不必放置Product的主键集合,因为我们只要显示用户喜欢商品的第一页,这一页是嵌入购物车显示页面中,所以第一页显示的个数是由程序员可事先在程序中定义。

这样在Account获得时,一起将myList集合值获得。

订单模块实现

我们还是从域模型开始,Order是订单模块的核心实体,其内容可以确定如下:

public class Order extends Model {

 

  /* Private Fields */

 

  private int orderId;

  private String username;

  private Date orderDate;

  private String shipAddress1;

  private String shipAddress2;

  …..

}

第二步建立与Model对应的ModelForm我们可以称之为边界模型代码从Order拷贝过来即可。当然OrderForm还有一些特殊的字段以及初始化:

public class OrderForm extends ModelForm

private boolean shippingAddressRequired;

    private boolean confirmed;

    static {

        List cardList = new ArrayList();

        cardList.add("Visa");

        cardList.add("MasterCard");

        cardList.add("American Express");

        CARD_TYPE_LIST = Collections.unmodifiableList(cardList);

      }   

   

    public OrderForm(){

        this.shippingAddressRequired = false;

        this.confirmed = false;       

    }

   …..

}

第三步,建立Order Model的业务服务接口,如下:

public interface OrderService {

  void insertOrder(Order order);

  Order getOrder(int orderId);

  List getOrdersByUsername(String username);

}

第四步,实现OrderServicePOJO子类:OrderServiceImp

第五步,表现层实现,本步骤可和第四步同时进行。

OrderService中有订单的插入创建功能,我们使用Jdon框架的CRUDcreate配置实现,配置struts-config.xmljdonframework.xml

      <form-bean name="orderForm"

type="com.jdon.framework.samples.jpetstore.presentation.form.OrderForm"/>

 

     <model key="orderId"

                      class="com.jdon.framework.samples.jpetstore.domain.Order">

                      <actionForm name="orderForm"/>

                      <handler>

                               <service ref="orderService">

                                        <createMethod name="insertOrder"/>

                               </service>

                      </handler>

     </model>

第六步:根据逐个实现界面功能,订单的第一个功能创建一个新的订单,在新订单页面NewOrderForm.jsp推出之前,这个页面的ActionForm已经被初始化,是根据购物车等Cart其他Model数据初始化合成的。

新订单页面初始化

根据Jdon框架中CRUD功能实现,初始化一个ActionForm有两种方法:一继承ModelHandler实现initForm方法;第二通过jdonframework.xmlinitMethod方法配置。

这两个方案选择依据是根据用来初始化的数据来源什么地方。

订单表单初始化实际是来自购物车信息或用户账号信息,这两个都事先保存在HttpSession中,购物车信息是通过有态CartService实现的,所以这些数据来源是和request相关,那么我们选择第一个方案。

继承ModelHandler之前,我们可以考虑首先继承ModelHandler的子类XmlModelHandler,只要继承initForm一个方法即可,这样节省其他方法编写实现。

public class OrderHandler extends XmlModelHandler {

   

    public ModelForm initForm(HttpServletRequest request) throws

    Exception{

        HttpSession session = request.getSession();

        AccountForm accountForm = (AccountForm) session.getAttribute("accountForm");

        OrderForm orderForm = createOrderForm(accountForm);

        CartService cartService = (CartService)WebAppUtil.getService("cartService", request);

       

        orderForm.setTotalPrice(cartService.getSubTotal());

 

        //below can read from the user's creditCard service;

        orderForm.setCreditCard("999 9999 9999 9999");

        orderForm.setExpiryDate("12/03");

        orderForm.setCardType("Visa");

        orderForm.setCourier("UPS");

        orderForm.setLocale("CA");

        orderForm.setStatus("P");

 

        Iterator i = cartService.getAllCartItems().iterator();

        while (i.hasNext()) {

          CartItem cartItem = (CartItem) i.next();

          orderForm.addLineItem(cartItem);

        }

        return orderForm;        

    }

   

    private OrderForm createOrderForm(AccountForm account){

      ……

    }

}

ModelHandlerinitForm继承后,因为这使用了JdonCRUD功能实现,这里我们只使用到CRUD中的创建功能,因此,findModelBykey方法就无需实现,或者可以在jdonframework.xml中配置该方法实现。

考虑到在initForm执行后,需要推出一个NewOrderForm.jsp页面,这是一个新增性质的页面。所以在struts-config.xml

              <action path="/shop/newOrderForm" type="com.jdon.strutsutil.ModelViewAction"

      name="orderForm" scope="request"     validate="false">

             <forward name="create" path="/order/NewOrderForm.jsp"/>

    </action>  

订单确认流程

新的订单页面推出后,用户需要经过两个流程才能确认保存,这两个流程是填写送货地址以及再次完整确认。这两个流程实现的动作非常简单,就是将OrderForm中的shippingAddressRequired字段和confirm字段赋值,相当于简单的开关,这是一个很简单的动作,可以有两种方案:直接在jsp表单中将这两个值赋值;直接使用strutsAction实现。后者需要编码,而且不是非有这个必要,只有第一个方案行不通时才被迫实现。

第一步:填写送货地址

使用Jdon框架的推出纯Jsp功能的Action配置struts-config.xml

    <action path="/shop/shippingForm" type="com.jdon.strutsutil.ForwardAction"

      name="orderForm" scope="session"      validate="false">

      <forward name="forward" path="/order/ShippingForm.jsp"/>          

    </action>

这是实现送货地址页面的填写,使用的还是OrderForm。更改前面流程NewOrderForm.jsp中的表单提交action值为本action path: shippingForm.shtml

<html:form action="/shop/shippingForm.shtml" styleId="orderForm" method="post" >

  ……

</html:form>

ShippingForm.jsp中增加将shippingAddressRequired赋值的字段:

<html:hidden name="orderForm"  property="shippingAddressRequired" value="false"/>

第二步:确认订单

类似上述步骤,配置struts-config.xml

   <action path="/shop/confirmOrderForm" type="com.jdon.strutsutil. ForwardAction"

      name="orderForm" scope="session"  validate="false">

      <forward name="forward" path="/order/ConfirmOrder.jsp"/>         

   </action>    

将上一步ShippingForm.jsp的表单action改为本actionpath: confirmOrderForm.shtml

<html:form action="/shop/confirmOrderForm.shtml" styleId="orderBean" method="post" >

修改ConfirmOrder.jsp中提交的表单为最后一步,保存订单newOrder.shtml

<html:link page="/shop/newOrder.shtml?confirmed=true"><img border="0" src="../images/button_continue.gif" /></html:link>

第三步:下面是创建数据保存功能实现:

    <action path="/shop/newOrder" type="com.jdon.strutsutil.ModelSaveAction"

      name="orderForm" scope="session"

      validate="true" input="/order/NewOrderForm.jsp">

      <forward name="success" path="/order/ViewOrder.jsp"/>

    </action>

ModelSaveAction是委托ModelHandler实现的,这里有两种方式:配置方式:在jdonframework.xml中配置了方法插入;第二种是实现代码,这里原本可以使用配置方式实现,但是因为在功能上有要求:在订单保存后,需要清除购物车数据,因此只能使用代码实现方式,在ModelHandler中实现子类方法serviceAction

public void serviceAction(EventModel em, HttpServletRequest request) throws java.lang.Exception {

   try {

      CartService cartService = (CartService) WebAppUtil.getService("cartService", request);

cartService.clear(); //清楚购物车数据

 

      OrderService orderService = (OrderService) WebAppUtil.getEJBService("orderService", request);

      switch (em.getActionType()) {

            case Event.CREATE:

                Order order = (Order) em.getModel();

                orderService.insertOrder(order);

                cartService.clear();

                break;

        case Event.EDIT:

                break;

        case Event.DELETE:

                break;

       }

   } catch (Exception ex) {

            throw new Exception(" serviceAction Error:" + ex);

   }

}

用户订单列表

用户查询自己的订单列表功能很明显可以使用Jdon框架的批量查询事先。

struts-config.xml中配置ModelListForm如下:

      <form-bean name="listForm" type="com.jdon.strutsutil.ModelListForm"/>

建立继承ModelListAction子类OrderListAction

public class OrderListAction extends ModelListAction {

 

    public PageIterator getPageIterator(HttpServletRequest request, int start, int count) {

        OrderService orderService = (OrderService) WebAppUtil.getService("orderService", request);

        HttpSession session = request.getSession();

        AccountForm accountForm = (AccountForm) session.getAttribute("accountForm");

        if (accountForm == null) return new PageIterator();

        return orderService.getOrdersByUsername(accountForm.getUsername(), start, count);

    }

 

    public Model findModelByKey(HttpServletRequest request, Object key) {

        OrderService orderService = (OrderService) WebAppUtil.getService("orderService", request);

        return orderService.getOrder((Integer)key);

    }

 

}

修改OrderService, 将获得Order集合方法改为:

public class OrderService{

 

PageIterator getOrdersByUsername(String username, int start, int count)

….

}

根据Jdon批量查询要求,使用iBatis实现返回ID集合以及符合条件的总数。

最后编写ListOrders.jsp,两个语法:logic:iterator MultiPages

配置jdon框架启动

目前我们有四个struts-config.xml,前面每个模块一个配置:

/WEB-INF/struts-config.xml 主配置

/WEB-INF/struts-config-catalog.xml  商品相关配置

/WEB-INF/struts-config-security.xml 用户相关配置

/WEB-INF/struts-config-cart.xml 购物车相关配置

/WEB-INF/struts-config-order.xml 订单相关配置

本项目只有一个jdonframework.xml,当然我们也可以创建多个jdonframework.xml,然后在其struts-config.xml中配置。

  <plug-in className="com.jdon.strutsutil.InitPlugIn">

    <set-property property="modelmapping-config"  value="jdonframework_iBATIS.xml" />

  </plug-in>

修改iBatisDAO配置

iBatis 4.0.5中原来的配置过于扩张(从持久层扩张到业务层),AccountDao每个实例获得都需要通过daoManager.getDao这样形式,而使用Jdon框架后,AccountDaoDAO实例获得无需特别语句,我们只要在AccountService直接引用AccountDao接口,至于AccountDao的具体实例,通过Ioc注射进入即可。

因此,在jdonframework.xml中有如下配置:

     <pojoService name="accountDao"

         class="com.jdon.framework.samples.jpetstore.persistence.dao.sqlmapdao.AccountSqlMapDao"/>

     <pojoService name="accountService"

                 class="com.jdon.framework.samples.jpetstore.service.bo.AccountServiceImp"/>

     <pojoService name="productManager"

                 class="com.jdon.framework.samples.jpetstore.service.bo.ProductManagerImp"/>

AccountServiceImp代码如下:

public class AccountServiceImp implements AccountService, Poolable {

    private AccountDao accountDao;

    private ProductManager productManager;

   

    public AccountServiceImp(AccountDao accountDao,

                             ProductManager productManager){

        this.accountDao = accountDao;

        this.productManager = productManager;

    }

 

AccountServiceImp需要两个构造方法实例,这两个中有一个是AccountDao

按照iBatis原来的AccountDao子类AccountSqlMapDao有一个构造方法参数是DaoManager,但是我们无法生成自己的DaoManager实例,因为DaoManager是由dao.xml配置文件读取后生成的,这是一个动态实例;那只有更改AccountSqlMapDao构造方法了。

根源在于BaseSqlMapDao类,BaseSqlMapDao是一个类似JDBC模板类,每个Dao都继承它,现在我们修改BaseSqlMapDao如下:

public class BaseSqlMapDao extends DaoTemplate implements SqlMapExecutor{

    …..

}

BaseSqlMapDaoXML配置和JDBC模板的结合体,在这个类中,这两者搭配在一起,在其中实现SqlMapExecutor各个子方法。

我们再创建一个DaoManagerFactory,专门根据配置文件创建DaoManager实例:

主要方法如下:

Reader reader = Resources.getResourceAsReader(daoResource);

daoManager = DaoManagerBuilder.buildDaoManager(reader);

其中daoResourcedao.xml配置文件,这个配置是在jdonframework.xml中配置:

<pojoService name="daoManagerFactory"

                 class="com.jdon.framework.samples.jpetstore.persistence.dao.DaoManagerFactory">

                      <constructor 

value="com/jdon/framework/samples/jpetstore/persistence/dao/sqlmapdao/sql/dao.xml"/>

     </pojoService>

这样,我们可以通过改变jdonframework.xml配置改变dao.xml配置。

Dao.xml配置如下:

<daoConfig>

  <context>

    <transactionManager type="SQLMAP">

      <property name="SqlMapConfigResource"       

value="com/jdon/framework/samples/jpetstore/persistence/dao/sqlmapdao/sql/sql-map-config.xml"/>

    </transactionManager>

 

    <dao interface="com.ibatis.sqlmap.client.SqlMapExecutor"  

implementation="com.jdon.framework.samples.jpetstore.persistence.dao.sqlmapdao.BaseSqlMapDao"/>

 

  </context>

</daoConfig>

dao.xml中,我们只配置一个JDBC模板,而不是将所有的如AccountDao配置其中,因为我们需要iBatis只是它的JDBC模板,实现持久层方便的持久化,仅此而已!

DaoManagerFactory为我们生产了DaoManager实例,那么如何赋值到BaseSqlMapDao中,我们设计一个创建BaseSqlMapDao工厂如下:

 

public class SqlMapDaoTemplateFactory {

 

    private DaoManagerFactory daoManagerFactory;

 

    public SqlMapDaoTemplateFactory(DaoManagerFactory daoManagerFactory) {

        this.daoManagerFactory = daoManagerFactory;

    }

   

    public SqlMapExecutor getSqlMapDaoTemp(){

        DaoManager daoManager = daoManagerFactory.getDaomanager();

        return (SqlMapExecutor)daoManager.getDao(SqlMapExecutor.class);

    }

 

}

通过getSqlMapDaoTemp方法,由DaoManager.getDao方法获得BaseSqlMapDao实例,BaseSqlMapDao的接口是SqlMapExecutor,这样我们通过DaoManager获得一个JDBC模板SqlMapExecutor的实例。

这样,在AccountDao各个子类AccountSqlMapDao中,我们只要通过SqlMapDaoTemplateFactory获得SqlMapExecutor实例,委托SqlMapExecutor实现JDBC操作,如下:

public class AccountSqlMapDao implements AccountDao {

  //iBatis数据库操作模板

  private SqlMapExecutor sqlMapDaoTemplate;

    //构造方法

  public AccountSqlMapDao(SqlMapDaoTemplateFactory sqlMapDaoTemplateFactory) {

      sqlMapDaoTemplate = sqlMapDaoTemplateFactory.getSqlMapDaoTemp();

  }

 //查询数据库

  public Account getAccount(String username) throws SQLException{

    return (Account)sqlMapDaoTemplate.queryForObject("getAccountByUsername", username);

  }

 

 

部署调试

当在JBossTomcat控制台 或者日志文件中出现下面字样标识Jdon框架安装启动成功:

<========  Jdon Framework started successfully! =========>

Jdon框架启动成功后,以后出现的错误基本是粗心大意的问题,仔细分析会很快找到原因,相反,如果编程时仔细慢一点,则后面错误出现概率很小。

使用Jdon框架开发Jpetstore 一次性调试通过率高,一般问题都是存在数据库访问是否正常,一旦正常,主要页面就出来了,其中常见问题是jsp页面和ActionForm的字段不对应,如jsp页面显示如下错误:

No getter method available for property creditCardTypes for bean under name orderForm

表示在OrderForm中没有字段creditCardTypes,或者有此字段,但是大小写错误等粗心问题。

如果jsp页面或后台log记录显示:

System error! please call system Admin.java.lang.Exception

一般这是由于前面出错导致,根据记录向前搜索,搜索到第一个出错记录:

2005-07-07 11:55:16,671 [http-8080-Processor25] DEBUG com.jdon.container.pico.PicoContainerWrapper - getComponentClass: name=orderService

2005-07-07 11:55:16,671 [http-8080-Processor25] ERROR com.jdon.aop.reflection.MethodConstructor -  no this method name:insertOrder

第一个出错是在MethodConstructor报错,没有insertOrder方法,根据上面一行是orderService,那么检查orderService代码看看有无insertOrder

public interface OrderService {

  void insertOrder(Order order);

  Order getOrder(int orderId);

  List getOrdersByUsername(String username);

}

OrderService接口中是有insertOrder方法,那么为什么报错没有呢?仔细检查一下,是不是insertOrder的方法参数有问题,哦, 因为orderService的调用是通过jdonframework.xml下面配置进行的:

     <model key="orderId"

                      class="com.jdon.framework.samples.jpetstore.domain.Order">

                      <actionForm name="orderForm"/>

                      <handler>

                               <service ref="orderService">

                                        <createMethod name="insertOrder"/>

                               </service>

                      </handler>

             </model>

而根据Jdon框架要求,使用配置实现ModelHandler,则要求OrderServiceinsertOrder方法参数必须是EventModel,更改OrderServiceinsertOrder方法如下:

public interface OrderService {

          void insertOrder(EventModel em);

}

同时,修改OrderService的子类代码OrderServiceImp

  public void insertOrder(EventModel em) {

        Order order = (Order)em.getModel();

        try{

            orderDao.insertOrder(order);

        }catch(Exception daoe){

            Debug.logError(" Dao error : " + daoe, module);

            em.setErrors("db.error");

        }

    }

 

注意em.setErrors方法,该方法可向struts页面显示出错信息,db.error是在本项目的application.properties中配置的。

关于本次页面出错问题,还有更深缘由,因为我们在jdonframework.xml中中定义了createMethod,而根据前面已经有ModelHandler子类代码OrderHandler实现,所以,这里不用配置实现,jdonframework.xml的配置应该如下:

             <model key="orderId"

                      class="com.jdon.framework.samples.jpetstore.domain.Order">

                      <actionForm name="orderForm"/>

                      <handler class="com.jdon.framework.samples.jpetstore.presentation.action.OrderHandler"/>

             </model>

直接定义了hanlderclassOrderHandler,在OrderHandler中的serviceAction我们使用代码调用了OrderServiceinsertOrder方法,如果使用这样代码调用,无需要求OrderServiceinsertOrder的参数是EventModel了。

 

 

总结

Jpetstore整个开发大部分基于Jdon框架开发,特别是表现层,很少直接接触使用struts原来功能,Jdon框架的表现层架构基本让程序员远离了struts的烦琐开发过程,又保证了strutsMVC实现。

Jpetstore中只有SignonAction这个类是直接继承strutsDispatchAction,这个功能如果使用基于J2EE容器的安全认证实现(见JdonNews),那么Jpetstore全部没有用到strutsAction,无需编写Action代码;ActionForm又都是Model的拷贝,ActionActionFormstruts编码的两个主要部分,这两个部分被Jdon框架节省后,整个J2EEWeb层开发方便快速,而且容易得多。

这说明Jdon框架确实是一款快速开发J2EE工具,而且是非常轻量的。

纵观Jpetstore系统,主要有三个层的配置文件组成,持久层由iBatisProduct.xml等配置文件组成;服务层由jdon框架的jdonframework.xml组成;表现层由strutsstruts-config.xmljdonframework.xml组成;剩余代码基本是Model之类实现。

 

 


 
Jdon
框架核心设计架构

架构设计

由于整个框架中中组件基于Ioc实现,组件类之间基本达到完全解耦。

从上图中可以看出,任何JavaBeans组件只要在XML配置文件(container.xml aspect.xmljdonframework.xml)中配置,由容器装载机制注册到Ioc 容器中,这些组件的客户端,也就是访问者,只要和微容器(或者组件的接口)打交道,就可以实现组件访问。

因此,本框架中每个功能块都可从框架中分解出来,单独使用它们。用户自己的任意功能块也可以加入框架中,Jdon框架成为一种完全开放、伸缩自如的基础平台。

运行原理图:

当客户端请求产生时,首先将访问目标服务参数包装在userTargetMetaDef对象中,该对象访问位于HttpSession中的动态代理实例,被动态代理将目标服务肢解成Method和方法参数Args,然后被拦截器拦截,最后达到目标服务实例,当然,有的拦截器可能就此完全拦截(堵住去路,如PoolInterceptor),目标服务实例是事先被注册到容器中的,在被访问时,缺省情况是每次访问产生一个新的目标服务实例。

Jdon容器如图是被保存在Servlet容器的ServletContext中的。

 

包结构

Jdon框架有下列包名组成:

AOP  : 表示AOP相关功能的类,其与bussinessproxy包关系最紧密,两者是系统的核心包。

Bussinessproxy:与动态代理、目标服务相关的类。

Container 实现将组件装载入容器,这是与Ioc 微容器相关的类,缺省使用了PicoContainer,但是可以替换的。包括容器的组件注册、配置读取、组件访问等功能。以上三个包可从Jdon框架中独立出来。

Controller:  这是一些基础功能类、以及和应用相关类的功能包,该包是Container的客户端。

Model:这是和数据模型增删改查(CRUD)。批量查询、缓存优化等相关功能的类。

Security:这是和用户注册登陆相关功能类,

Servicelocator:这是和EJB 运行定位相关的类

Strutsutil:与Struts相关类,前面Model包中各种功能必须通过具体表现层技术实现。

Util:一些工具类。

container

 Container包主要是负责容器管理方面的功能,其他包中的组件都被写入配置文件container.xmlaspect.xmljdonframework.xml中,而container包主要负责与这些配置文件打交道,从配置文件中获得其他包的组件,向容器中注册,并启动容器。

主要一个接口是ContainerWrapper    ContainerWrapper有两个主要方法:向容器注册组件;从容器查询获得组件。

ContainerWrapper接口的缺省实现是PicoContainerWrapperVisitorContainerWrapper两个子类,如下图:

PicoContainerWrapper使用了著名的PicoContainer实现(http://www.picocontainer.org),ContainerWrapper接口主要从其抽象出来。

访问者模式

VisitorContainerWraper是观察者模式的实现,传统观察者模式中,对于Visitor角色一般有下面多个访问不同被访问者的方法:

visitAcomponent();

visitBcomponent();

…..

由于AcomponentBcomponent这些类已经注册到容器,因此,通过容器可以直接直接实现不同组件的访问,只需通过下面一个方法实现:

public ComponentVisitor getComponentVisitor(){

        return new ComponentOriginalVisitor(this);       

}

而访问者Visitor则可以节省为单一方法即可:

visit(XXX xxx);

使用访问者模式的原因:主要为了实现缓存,提高一些组件运行性能,如果一些组件每次访问时,都需要new,例如Proxy.newInstance如果频繁反复运行,将是十分耗费性能的,因此,使用缓存尽量避免每次创建将提高系统的运行性能。

Visitor有两个子类实现:ComponentOriginalVisitorHttpSessionProxyVisitor,这里又使用了装饰者模式,Decoratee ComponentOriginalVisitor;而DecoratorHttpSessionProxyVisitorHttpSessionProxyVisitorHttpSessionBindingListener,也就是说,我们使用了HttpSession作为缓存机制,HttpSession的特点是以用户为Key的缓存,符合♂的缓存机制,当然,我们以后也可以使用更好的缓存机制替换HttpSession,替换了HttpSession根本不必涉及到其他类的更好,因为这里使用模式实现了彻底的解耦。

访问者模式另外一个重要角色:Visitable,它则是那些运行结果需要缓存的组件必须继承的,注意,这里有一个重要点:不是那些组件本省生成需要缓存,而是它的运行结果需要缓存的。继承Visitable这些组件事先必须注册在容器中。

目前Visitable有两个子类:

  * @see com.jdon.bussinessproxy.dyncproxy.ProxyInstanceFactoryVisitable

  * @see com.jdon.bussinessproxy.target.TargetServiceFactoryVisitable

前者主要是动态代理的创建,因为Proxy.newInstance频繁执行比较耗费性能,第一次创建后,将动态代理实例保存在httpSession中,当然每个Service对应一个动态代理实例。

TargetServiceFactoryVisitable主要是为了缓存那些目标服务的实例,目前这个功能没有激活,特殊情况下才推荐激活。

 

容器的启动

容器启动主要是将配置文件中注册的组件注册到容器中,并启动容器,这涉及到container包下ConfigBuilder等几个子包。

Config包主要负责从container.xmlaspect.xml读取组件;

Builder包主要负责向ContainerWrapper注册这些组件,注册过程是首先从基础组件(container.xml)开始,然后是拦截器组件(aspect.xml),最后是用户的服务组件(jdonframework.xml)

容器的生命周期

容器启动后,容器本身实例是放置在Web容器的ServletContext中。

容器启动并不是在应用系统部署到Web容器时就立即启动,而是该应用系统被第一次访问时触发启动,这个行为是由ContainerSetupScriptstartup触发的,而startup方法则是由ServletContainerFinderfindContainer触发。

当应用系统从Web容器中销毁或停止,Jdon框架容器也就此销毁,最好将你的组件中一些对象引用释放,只要继承Startable,实现stop方法即可。

容器的使用

客户端访问组件必需通过容器进行,这个过程分两步:

Web容器中获得框架容器ContainerWrapper实例。

ContainerWrapper容器中查询获得组件实例,有两者实例方式:单例和多例。

关于具体使用方式可见前面章节“如何获得POJO实例”等。

这个容器使用过程是通过finder包下面的类完成,主要是ServletContainerFinder类,ComponentKeys类保存中一些container.xml中配置的组件名称,这些组件名称可能需要在框架程序中使用到,这就需要引起注意,这个类中涉及的组件名称不要在container.xml中随意改动(当然这个类在以后重整中争取去除)。

com.jdon.controller.WebAppUtil是专门用于客户端对微容器的访问,这个客户端目前是Http客户端,主要方法:

public static Object getService(String name, HttpServletRequest request)

这是最常用的从容器中获得服务实例的方法。需要HttpServletRequest作为客户端,Jdon框架将来会提供作为Application客户端调用的专门方法。

public static Object getComponentInstance(String name, HttpServletRequest request)

这是获得组件实例的方法。

public static String getContainerKey()

通过获得容器保存在ServletContext中的Key

 

从容器内部访问容器

上节“容器的使用”是指从容器外部(客户端)如何访问和使用容器,如果当前客户端是在容器内部,例如需要在一个Service服务类中访问容器的缓存机制等,该如何访问?

使用com.jdon.container.finder. ContainerCallback,同时,该服务POJO类以ContainerCallback作为构造参数,当该POJO服务类注册到容器中时,容器的Ioc特性将会找到事先以及注册的ContainerCallback类。

通过ContainerCallback获得ContainerWrapper容器实例,然后通过ContainerWrapper下面两个方法:

public Object lookup(String name);

从容器中获得container.xml中注册的组件实例,这种方法每次调用获得的是同一个实例,相当于单例方式获得。

public Object getComponentNewInstance(String name);

每次从容器中获得container.xml中注册的组件的一个新实例,这种方法每次调用获得的是新的实例,该方法不适合一些单例资源的获得。

例如如果向在POJOService中访问容器的缓存机制,因为缓存整个容器只能有一处,因此必须以lookup方式获得,获得ModelManager的实例如下:

ModelManager modelManager = containerWrapper. lookup (“modelManager”);

其中“modelManager”字符串名称是从Jdon框架的jdonFramework.jar包中META-INFcontainer.xml中查询获知的。

 

AOP

Jdon AOP的设计目前功能比较简单,不包括标准AOP中的MixinIntroduction等功能;AOP包下有几个子包:Interceptorjoinpointreflection,分别涉及拦截器、;拦截器切点和反射机制等功能。

bussinessproxy

Jdon框架从架构角度来说,它是一种业务代理,前台表现层通过业务代理层访问业务服务层,使用AOP实现业务代理是一种新的趋势,因此本包功能是和AOP包功能交互融合的。

bussinessproxy包中的config子包是获取jdonframework.xml中的两种服务EJBPOJO配置,然后生成meta子包中TargetMetaDef定义。

target子包封装了关于这两种服务由元定义实现对象创建的工厂功能,服务对象创建透过访问模式使用了HttpSession作为EJB实例缓存,这个功能主要是为了EJB中有态会话Bean实现,这样,客户端通过getService获得一个有态会话Bean时,无需自行考虑保存这个Bean引用到HttpSession中这一使用细节了。无态会话bean从实战角度发现也可以缓存,提高了性能。

Jdon框架既然是一种业务代理,那么应该服务于各种前台表现层,Jdon框架业务代理还适合客户端为Java客户端情况下的业务服务访问。这部分功能主要是com.jdon.bussinessproxy.remote子包实现的。使用方式参考:http://www.jdon.com/product/ejbinvoker.htm

 

TargetMetaDef

TargetMetaDef是目标服务的元定义,TargetMetaDef主要实现有两种:EJBTargetMetaDefPOJOTargetMetaDef,分别是EJB服务和POJO服务实现。

所谓目标服务元定义也就是程序员在jdonframework.xml中定义的services

Jdon框架提供目标服务两种形式:目标服务实例创建和目标服务本身。

ServiceFactory

目标服务创建工厂ServiceFactory可以根据目标服务元定义创建一个目标服务实例,这也是WebAppUtul.getService获得目标服务的原理实现,

com.jdon.controller.service.DefaultServiceFactoryServiceFactory缺省实现,在DefaultServiceFactory中,主要是通过动态代理获得一个服务实例,使用了DefaultServiceFactory来实现动态代理对象的缓存。不必每次使用Proxy.newinstance第一次执行后,将其结果保存在HttpSession中。

Service

目标服务Service则是通过方法Relection运行目标服务,只要告知Service目标服务的类、类的方法、方法参数类型和方法参数值这些定义,除了方法参数值,其余都是字符串定义,这是Java的另外一种调用方式。

security

Security包主要应用于JdonSD(一个组件库)产品的基于容器的安全权限认证。

 

strutsutil

本包封装了Jdon框架基于Struts的表现层实现,Jdon框架的CRUD功能和批量分页查询以及树形显示功能都是在该包中实现,还有上传图片等功能(待测试)。

 

 

 

controller

controller包是Jdon框架的整体结构包,主要面向框架使用客户端,其中WebAppUtil是经常被调用的类。

cache子包是框架的缓存机制,这个缓存机制主要面向数据类设计,例如Model类,功能类是通过container实现管理,功能类使用访问者模式实现结合HttpSession实现有状态。

Jdon框架缺省缓存是使用了开源OfBiz的一个缓存,如果你希望使用更好的缓存产品。可以通过继承CacheLRUCache,然后在container.xml中替代如下配置即可:

   <component name="cache" class="com.jdon.controller.cache.LRUCache" >

      <constructor value="cache.xml"/>

   </component>

cache子包提供的缓存不但为Model服务,还为批量查询的PageIterator创建服务,可参见PageIteratorSolver类;另外也为上传图片缓存服务,以后可拓展为更多数据性缓存服务。

Config子包主要是为读取pojoServiceejbService配置实现XML读取功能。

Events子包包含的是装载数据Model载体在各层之间穿梭,类似信使,通常表现层会将Model封装到EventModel中,传给服务层的服务,服务处理完毕,如果出错将出错信息封装在EventModel对象中,这样表现层可从EventModel中查知后台处理是否出错,并返回出错信息。

EventModelsetErrors方法是用于服务层封装处理出错信息的方法,可直接将”id.notfound”这样约定出错信息赋值进去,前台如struts可根据id.notfoundApplication.properties中查找本地化信息提示。

Model子包是封装了框架重要的数据类ModelModel既是域模型中的Model,也是一种DTO,它是在各层之间传送数据的载体,因此Model可以允许嵌套ModelDynamicModel也许更加适合临时组装一个传送对象。

Model是框架的CRUD功能重要角色;PageIterator是框架批量查询的重要角色。

Pool子包封装了Apache Pool功能,主要用于Pool拦截器,如果你的POJO服务需要池化提升性能,只要让你的POJO服务类继承该包下的Poolable或加入@Poolable元注解Annotation即可。

Service子包封装框架两大有关服务的重要功能:创建一个服务ServiceFactory.create;或直接运行一个服务Service.execute

 

model

model包主要是围绕ModelCRUD功能实现展开,这部分是中间层,尽量做到和表现层strutsutil包实现解耦,这样如果有新的表现层如JSF可直接和本包发生作用,实现JSF下的CRUD功能。

ModelManager是面向表现层操作Model的主要客户端类,ModelFormModelHandler都是可能需要客户端继承实现的重要类。

Config子包主要是从jdonfamework.xml中获得models相关配置,将这些加载到ModelMapping对象中。

Handler子包是延续config子包,model配置中Modelhandler部分配置被加载到HandlerMetaDef对象中,然后预先实现class.forName运行,HandlerObjectFactory是创建ModelHandler实例工厂,一次性生产20个,如果不够用,还可以再生产20个,因为ModelHandlerstrutsaction直接调用,而action是多线程,所以使用这种池形式可提高性能,这里使用了自制的池功能,原理简单,池性能好,简单性能就好是Jdon框架设计一个原则。

Query子包主要是为批量查询服务,这些类在持久层被调用。

 

servicelocatorUtil

这两个包都是工具,大部分类从其它开源借鉴过来,正是因为这些经过实践证明运行良好的基础组件,才使得Jdon框架的整体基础牢固,感谢他们。

站在巨人的基础上发展是Jdon框架设计的另外一个原则。

更详细的描述将在专门Jdon框架设计文档中描述。

 

 


Jdon框架高级使用

从前面章节已经清楚:Jdon框架快速开发主要体现在CRUD和批量查询这两个基本功能上,大量数据库系统基本都由这两个基本功能组成。

Jdon框架的这两个功能横跨J2EE多个层次,主要简化工作体现在表现层,这一层主要是简化了Struts一些烦琐的配置和代码过程。

在这一章,主要介绍在深入使用Jdon框架中碰到的一些问题和解决方案,从而达到更加灵活地使用Jdon框架。

C/S架构下胖客户端远程调用

Jdon框架不但是一个B/S架构框架,还支持C/S架构,客户端可以是Java类型的任何客户端,如SwingAppletSWT等。

 

使用Jdon框架,可以在无需改变业务服务层代码的情况下,将这些服务Service提供给远程Java客户端调用,这是一种基于Htpp协议的RPC,它区别于Web服务的最大不同是:Jdon框架将客户端请求打包对象传送;而不是转换成XML;省却了两端转换解析步骤,提高了性能。

有两种使用方式,一种见下面,还有一种更方便方式,使用Hessian,可见Jdon框架5.8版本增加功能

 

架构原理图如下:

客户端配置

客户端调用远程服务器的服务核心代码很简单,如下:

    MySessionLocal mySessionLocal =  (MySessionLocal)serviceFactory.getService(

                        FrameworkServices.MySessionEJB);           

    mySessionLocal.insert();

客户端调用服务就如同与服务在同一个JVM中一样,但是因为客户端在远程,所以需要进行对远程服务器的一些配置,比如规定远程服务器的IP地址和Servlet Prox名称。

具体客户端代码可见框架案例Samples中的remoteClient代码。

服务器端配置

首先激活框架系统的远程访问,在web.xml加入:

<servlet>

    <servlet-name>EJBInvokerServlet</servlet-name>

    <display-name>接受远程客户端访问</display-name>

    <servlet-class>com.jdon.bussinessproxy.remote.http.InvokerServlet</servlet-class>

    <init-param>

         <param-name>configList</param-name>

         <param-value>jdonframework.xml</param-value>

     </init-param>

</servlet>

<servlet-mapping>

    <servlet-name>EJBInvokerServlet</servlet-name>

    <url-pattern>/auth/EJBInvokerServlet</url-pattern>

</servlet-mapping>

然后激活容器基于HTTP的基本验证,配置web.xml

<login-config>

    <auth-method>BASIC</auth-method>

    <realm-name>SecurityRealm</realm-name>

</login-config>

web.xml中定义访问权限如下:

<security-constraint>

    <display-name>User protected</display-name>

    <web-resource-collection>

      <web-resource-name>Collection1</web-resource-name>

      <url-pattern>/auth/*</url-pattern>

    </web-resource-collection>

    <auth-constraint>

      <role-name>User</role-name>

    </auth-constraint>

    <user-data-constraint>

      <transport-guarantee>NONE</transport-guarantee>

    </user-data-constraint>

</security-constraint>

这样,访问/auth /EJBInvokerServlet将得到授权保护。下面在web.xml中分别配置该系统的角色和JNDI配置。

  <security-role>

    <role-name>Admin</role-name>

  </security-role>

  <security-role>

    <role-name>User</role-name>

  </security-role>

  <ejb-local-ref>

    <ejb-ref-name>ejb/EJBContollerLocal</ejb-ref-name>

    <ejb-ref-type>Session</ejb-ref-type>

    <local-home>com.jdon.security.auth.ejb.SecurityFacadeLocalHome</local-home>

    <local>com.jdon.security.auth.ejb.SecurityFacadeLocal</local>

    <ejb-link>SecurityFacade</ejb-link>

  </ejb-local-ref>

jboss-web.xml配置中使用容器安全机制:

<jboss-web>

    <security-domain>java:/jaas/SecurityRealm</security-domain>

</jboss-web>

然后在JBosslogin-config.xml中配置SecurityRealm

 

 

 

Struts+Jdon表现层组件编程

通常,一个Jsp页面可能显示很多各种内容,涉及到肯定不只一个数据库,也就是多个组件的结合,但是Struts配置文件中一般又是一个Action一个ActionForm一个Jsp,他们之间的关系是1:1:1

如何在这样一个体系下实现灵活的一个页面多个组件编程模式呢?

首先,要区分两种情况:一个Action一个ActionForm,一个ActionForm中装载多个组件;一个Action一个ActionForm一个组件;

一个ActionForm多个组件

第一种情况:如果同一个Action就可以对付这些组件,那么在这种情况下又有两个办法:

1.将这多个组件装入一个ActionForm中,如使用MapForm等机制;

一般多个组件在同一个Jsp页面显示情况比较经常发生在批量查询中,对于批量查询,因为Jdon框架规定必须有一个ModelListForm,在ModelListForm中最多只能装载一个Model,如果你有多个组件,也就是多个Model,如何装入ModelListForm呢?

可以使用Jdon框架的com.jdon.controller.model.DynamicModel装载多个Model,其实DynamicModel中有一个Map,然后将DynamicModel装入ModelListFormoneModel中。

Struts对于Map可以直接访问,以DynamicModel为例子:

Action中代码:

    DynamicModel dynamicModel = new DynamicModel();

    Test test = new Test();

    test.setName("ooooooooooooooooooooo");

    //装入Test组件,可以装入更多其他组件。”test”Test实例的key

    dynamicModel.put("test", test);

    modelListForm.setOneModel(dynamicModel);

Jsp中显示代码:

<bean:define id="dynamicModel" name=" modelListForm " property="oneModel"/>

<bean:define id="test" name="dynamicModel" property="value(test)"/>

<bean:write name="test" property="name"/>

其中value(test)是这样解释:valuedynamicModel方法getValue方法的简写,test是之间保存Test实例的key值,然后从test对象中取出其属性name的值。

 

2.手工将多个组件装入request/sessionscope中,然后根据其名称在jsp中获得。这种方式比较传统和原始,类似Jsp编程方式,学习难度小,方便。

一个ActionForm一个组件

前面情况,多个组件装载在一个ActionForm中,隐含的意思实际是:所有功能都可以在一个Action中实现,在这个Action中,我们将多个组件装入当前对应的ActionForm,但是,如果这些组件必须有预先由不同的Action来处理,每个组件必须经过Action -->ActionForm流程,那么在这种情况下也有两种办法:

1. 使用Tiles, 不同流程输出到同一个页面的不同区域。是一种并行处理方式。

2. 对多个流程首尾相连,第一Action forward结果是第二个Action,最后输出一个Jsp,在这个jsp中就可以使用前面多个流程的多个ActionForm了,这属于串行方式。

例如,当前页面是使用Jdon框架的批量查询实现,Jdon框架的批量查询一定是一个ModelListAction对应一个ModelListForm,这是一个比较固定的流程了。如果在当前这个批量查询页面中还要显示其他Model的批量查询,也就是两个或多个Model的批量查询在同一个Jsp页面中显示,在这种情况有上述两个方案选择。

第一个方案前提是你必须使用Struts+Tiles架构,使用Tiles可以将页面划分成任意块,这样多个批量查询在页面任何位置以任何方式组合显示,完全灵活,缺点是多加入Tiles概念。

在一般小型应用中,我们只需采取第二方案,将多个批量查询的Actionstruts-config.xml配置进行首尾连接既可,这种方式类似是按照Jsp页面先上后下的顺序串联的。

例如:threadPrevNexListFormmessageListForm是两个批量查询的ModelListForm,那么struts-config.xml配置如下:

    <action path="/message/messageList"

type="com.jdon.jivejdon.presentation.action.ThreadPrevNexListAction"

      name="threadPrevNexListForm" scope="request"

      validate="false">

      <forward name="success" path="/message/messageListBody.shtml"/>

    </action>

 

      <action path="/message/messageListBody"

type="com.jdon.jivejdon.presentation.action.MessageListAction"

      name="messageListForm" scope="request"

      validate="false">

      <forward name="success" path="/message/messageList.jsp"/>

    </action>

这样,当url调用/message/messageList.shtml时,最后导出/message/messageList.jsp这个页面,在messageList.jsp中可以访问threadPrevNexListFormmessageListForm两个ActionForm了。

上述组件构造方法可应用在复杂的Master-Details编程中,将Master看成一个对象ModelDetails是一系列子Model的集合。

 

内嵌对象(Embedded Object)缓存设计

请看下面这段代码:

 

public class Category extends Model{

String Id;

Product product;   //内嵌包含了一个Product对象

}

Category这个Model内嵌了Product这个Model,属于一种关联关系。

目前Jdon框架提供的缺省缓存是扁平式,不是嵌入式或树形的(当然也可以使用JbossCache等树形缓存替代),因此,一个ModelB对象(如Product)被嵌入到另外一个ModelA对象(如Category)中,那么这个ModelB对象随着ModelA对象被Jdon框架一起缓存。

假设实现ModelA已经在缓存中,如果客户端从缓存直接获取ModelB,缓存由于不知道ModelB被缓存到ModelA中(EJB3实体Bean中是通过Annotation标注字段),那么缓存可能告知客户端没有ModelB缓存。那么有可能客户端会再向缓存中加一个新的ModelB,这样同一个ID可能有两份ModelB,当客户端直接调用的ModelB进行其中字段更新,那么包含在ModelA中的ModelB可能未被更新(因为ModelA没有字段更新)。

有两种解决方案:

第一.最直接方式,通过手工清除ModelA缓存方式来更新,或者耐心等待ModelA缓存自动更新。手工清除缓存见下章。

注意:下面这种做法将也会导致不一致现象发生:

DAO层读取数据库。生成ModelA时,直接读取数据库将ModelB充填。

 

第二.在进行ModelAModelB的相关操作服务设计时,就要注意保证这两种情况下ModelB指向的都是同一个。如下图:

为达到这个目的,只要在Service层和Dao层之间加一个缓存Decorator,服务层向Dao层调用的任何Model对象都首先经过缓存检查,缓存中保存的ModelA中的ModelB是一个只有ModelB主键的空对象,在服务层getModelA方法中,再对ModelAModelB进行充实,填满,根据ModelA中的ModelB的主键,首先再到缓存查询,如果有,则将缓存中ModelB充填到ModelAModelB中,这样上图目的就可以实现了。

相关实现可参考JiveJdon 3.0的代码,Forum/ForumMessage都属于这种情况。

 

 

 

Model缓存使用

Jdon框架通过两种方式使用Model缓存:

CRUD框架内部使用,如果你使用Jdon框架提供的CRUD功能,那么其已经内置Model缓存,而且会即时清除缓存。

通过CacheInterceptor缓存拦截器,如果你不使用Jdon框架的CRUD功能,缓存拦截器功能将激活,在向Service获取Model之前,首先查询当前缓存器中是否存在该Model,如果有从缓存中获取。当你的Model中数值更改后,必须注意自己需要手工清除该Model缓存,清除方法如下介绍。

 

Jdon框架除了提供单个Model缓存外,还在持久层Dao层提供了查询条件的缓存,例如如果你是根据某个字段按照如何排列等条件进行查询,这个查询条件将被缓存,这样,下次如果有相同查询条件,该查询条件将被提出,与其相关的满足查询条件一些结果(如符合条件总数等)也将被从缓存中提出,节省翻阅数据库的性能开销。

手工访问缓存

在一般情况下,前台表现层通过getService方法访问服务层一个服务,然后通过该服务Service获得一个Model,这种情况Jdon框架的缓存拦截器将自动首先从缓存读取。

但是,有时我们在服务层编码时,需要获得一个Model,在这种情况下,Jdon框架的缓存拦截器就不起作用,这时可能我们需要手工访问缓存。

因为所有服务类POJO都属于Jdon框架的容器内部组件,这实际是在容器内访问容器组件的问题。

使用com.jdon.container.finder. ContainerCallback,同时,该服务POJO类以ContainerCallback作为构造参数,当该POJO服务类注册到容器中时,容器的Ioc特性将会找到事先以及注册的ContainerCallback类。

通过ContainerCallback获得ContainerWrapper容器实例,然后通过ContainerWrapper下面方法:

public Object lookup(String name);

从容器中获得container.xml中注册的组件实例,这种方法每次调用获得的是同一个实例,相当于单例方式获得。

ModelManager modelManager =

(ModelManager)containerWrapper. lookup (“modelManager”);

其中“modelManager”字符串名称是从Jdon框架的jdonFramework.jar包中META-INFcontainer.xml中查询获知的。

获得ModelManager后,我们基本可以访问Model有关的功能安排,如ModelManagergetCache方法。

下节的手工清除缓存中,我们是通过WebAppUtil.getComponentInstance获得ModelManager实例,这是一种从容器外获得容器组件的方式,本节介绍从容器内获得容器组件的方式,这两种方式可根据我们实际需要灵活使用,关键是弄清除你需要在哪里触发组件调用?

 

手工清除缓存

注意:手工清除缓存不是必要的,因为缓存中对象是有存在周期的,这在Cache.xml中设置的,过一段时间缓存将自动清除那些超过配置时间不用的对象,这样你修改的数据将被从数据库重新加载。如果你等不及这些内在变化,可以手工处理:

有两种情况需要手工清除缓存,首先,在持久层的Dao类中,总是需要手工清除查询条件的缓存(注意不是Model缓存,是查询条件的缓存),只要在相应的增删改方法中调用PageIteratorSolverclearCache方法既可。

如果你不实行这种缓存清除,那么你更改一个Model数据或新增一个新的Model数据,你在批量查询时,将看不到任何变化:Model数据没有被修改;新的Model没有出现在查询页面中。

其次,单个Model缓存在不使用Jdon框架的CRUD功能下也必须手工清除,如果你使用1.2.3以后版本,可以调用com.jdon.strutsutil.util. ModelUtil类的clearModelCache方法,该方法一般是Action中调用后台增删改服务之前被激活调用:

注意,手工清除Model缓存代码关键是:

modelManager.removeCache(keyValue);

keyValueModel的主键值,例如User的主键userId值是”2356”,那么keyValue就是”2356”。简化代码如下:

//获得ModelManager实例

ModelManager modelManager = (ModelManager)
WebAppUtil.getComponentInstance(ComponentKeys.MODEL_MANAGER, request);

modelManager.removeCache(keyValue);

 

上面代码是在容器外访问获得ModelManager,使用上节容器内访问组件方式也可以获得ModelManager;前者适合在表现层使用;后者适合在服务层使用。

最后,介绍一下清除全部缓存的方式,调用ModelManagerclearCache方法(Jdon框架1.3以上版本),这样实际上将整个Jdon框架缓存全部清零。

前面两种清除缓存方式前提是首先获得ModelManager,特别是服务层需要清除缓存时,需要以容器内访问组件方式获得ModelManager,这只适合POJOService构成的服务层,如果我们使用EJBSession Bean作为服务层实现,这时当前版本的Jdon框架容器不会在EJB容器中加载,因此,在Session Bean中无法访问到容器,无法获得ModelManager了,在这种情况下,可以通过设置ModelsetModified属性为True,表示该Model已经修改更新,这样当表现层获取该Model时,Jdon框架缓存拦截器拦截时,发现该Model已经被修改,也就不会从缓存中获取。

Model还有另外一个方法setCacheble,当设置为false时,该Model将不会被保存到缓存中。如果你不希望某个Model被框架自动存入缓存,那么使用此功能,setCacheblesetModified区别是,前者一旦设置为真,相当于缓存失效,以后再也不能用缓存;而后者则是当前Model表示被修改过,这样当有任何再次(限一次)试图从缓存中读取这个Model时,都被会阻挡,从而可直接从数据库获得,然后再保存到缓存中,这样缓存中的Model数据就是新鲜的了。

明白Jdon框架setModified这个神奇作用,当你设置一个ModelsetModified为真,那么你再读取缓存时,Jdon框架内部将忽略你这个读取,返回一个null;这样你就根据返回是否为空,再从数据库直接获得,获得后,别忘记再保存到缓存中,已覆盖前次修改的旧数据,保证以后每次从缓存中读取的都是新鲜数据。

=总结如下:手工缓存清除共有两种方式:

直接操作缓存,从缓存中清除;

如果无法操作到缓存体系,那么设置ModelsetModified

失效Model缓存

如果你的系统已经对Model进行了缓存,那么可以失效JFModel缓存:

winrarJdonFramework.jarMETA-INFaspect.xml解压出来,然后去除配置中cacheInterceptor,再将aspect.xml拖入winrarJdonFramework.jarMETA-INF下覆盖原来的。

<interceptor  name="cacheInterceptor"

class="com.jdon.aop.interceptor.CacheInterceptor" pointcut="services" />

 

代码指定失效缓存:

model. isCacheable isCacheable决定model是否保存到缓存。 缺省是true, 如果设置为false,该Model将不会保存到缓存中,但是如果之前已经在缓存中,设置为false后,不会自动立即从缓存中消除。

Model isModified isModified决定model是否从缓存中读取。缺省是false,如果设置为true,该Model将不再从缓存中获得,但该Model有可能还存在缓存中,直到下次新的同样Model覆盖它。

这两种方法可以跳过缓存,但是不能清除缓存中原来的Model,必须手工清除,或等该Model自动失效。

配置encache作为缓存

JdonFramework5.1以后,在该项目目录下有一个componenets/encache项目,缺省Jdon框架的缓存是使用一个简单的com.jdon.util.UtilCache,这种缓存是可以更换的,如果想更换为encache,步骤简单,如下:

1. 更改JdonFramewor.jar包中META-INFcontainer.xml(方法可通过winrar打开JdonFramewor.jar,将container.xml解压更改后,再拖放回去覆盖原来的):

 

  <!-- 将原来缺省这行注释掉

comment/delete this line in jdonframework.jar /META-INF/container.xml 

   <component name="cache" class="com.jdon.util.LRUCache" >

      <constructor value="cache.xml"/>

   </component>

    -->

   

    <!—加入下面行

 active EnCache see prodject : components/encache: add these lines  in jdonframework.jar /META-INF/container.xml -->   

   <component name="cache" class="com.jdon.components.encache.EncacheProvider" />

  

   <component name="ehcacheConf" class="com.jdon.components.encache.EhcacheConf" >

      <constructor value="ehcache.xml"/>

      <constructor value="sampleCache1"/>

   </component>

  

2. 将项目目录componenets/encache/dist下的jar包文件jdon-encahe.jar ehcache-1.2.4.jar commons-logging.jarJdonFramework.jar放在一起。

3. 重新启动JBossTomcat

 

常用API说明

Jdon框架提供了丰富的案例源码,一般简单情况下可以通过参考案例源码的用法明白一些用法,但是在深入使用过程中,需要熟悉一些API的用法,上节缓存的使用实际已经涉及了Jdon框架的API使用。

JdbcTemp API

com.jdon.model.query.JdbcTemp提供了一个简单的持久层解决方案,通过JdbcTemp(JDBC模板)可以完成增///查等SQL语句操作。

一般我们会选择Hibernate/Ibatis等作为持久层架构,但是这些框架需要另外学习和复杂的配置,对非常轻量的简单应用,直接使用JdbcTemp可节省大量时间,随着项目复杂和成熟,还是可以使用这些专门框架来替代JdbTemp的。

JdbcTemp主要帮助开发者省却复杂的数据库JDBC操作语句(如打开/关闭数据库连接等),只要告诉它你要操作的SQL语句和参数数值,通过如下一句:

jdbcTemp. operate (queryParams, sql);

其中queryParams参数是一个List集合类型,里面装载的是参数集合,是有一定顺序的;sql是一个字符串类型的SQL语句,如:

       String sql = "INSERT INTO testuser (userId , name)  VALUES (?, ?)";

       List queryParams = new ArrayList();

       queryParams.add(userTest.getUserId());

       queryParams.add(userTest.getName());

       jdbcTemp.operate(queryParams,sql);

queryParams中装载的是sql语句中两个问号参数,顺序也是和问号的顺序对应的,第一个问号是对应testuseruserId字段;第二个问号对应testusername字段。

数据库的INSERT UPDATE DELETE操作都是可以通过operate方法实现。

目前JdbcTempoperate方法只支持有限几种类型:String Integer Float Long Double Bye Short,也就是说,当你需要更新数据库的参数类型符合这几种,可以使用operate方法,简化JDBC语句编写。

但是,如果这几种类型不符合你的参数类型,只能直接使用JDBC或使用专门的持久层框架等,Jdon框架提供这个简单operate方法是为了简化一般系统的开发工作。

 

JdbcTemp也提供多种查询操作语句:queryMultiObjectquerySingleObject方法,例如:

jdbcTemp. queryMultiObject (queryParams, sql);

jdbcTemp. querySingleObject (queryParams, sql);

querySingleObject是查询返回一个对象,适合如下SQL语句:

select name from testuser where userId = ?

select count(1) from testuser where userId = ?

返回的是数据表一个字段,querySingleObject则返回的相应类型,如字符串等,整数型则是整数型对象,如int则返回的是IntegerbigInt则返回的是Long

queryMultiObject是最常用的查询方法,与querySingleObject返回单个字段相反,它返回的是多个字段,当然也可能是多行多个字段。类如SQL语句:

select userId, name from testuser where userId = ?

这个返回的两个字段,而且结果一般是一行,就是一条记录,但是下面SQL语句:

select userId, name from testuser where name = ?

可能返回多行符合查询条件的记录,每行记录包含两个字段。

这两种形式都是可以采取queryMultiObject调用,queryMultiObject返回的是一个List结果,在这个List结果中按照查询顺序装载着查询结果,这个List中每个元素是一个Map对象,Mapkey是数据表字段名称,Mapvalue是数据表字段名对应的查询出来的值。

因此,从queryMultiObject结果中取值的写法如下:

List list = pageIteratorSolverOfUser.queryMultiObject(queryParams, GET_FIELD);

Iterator iter = list.iterator();

if (iter.hasNext()) { //如果有多行记录,这里是while

       Map map = (Map) iter.next();

       ret = new UserTest();

       ret.setName((String) map.get("name"));

       ret.setUserId((String) map.get("userId"));

}

List结果中装载内容结构如下:

JdbcTemp使用前提

    只要将DataSource传给JdbcTemp即可,DataSource可以来自容器的JNDI;也可以来自自己编码的连接(自己编码的连接代码需要向外界提供获得DataSource方法);也可来自Hibernate等持久层框架,在这些框架配置中配置数据库连接,然后通过这些框架的API获得DataSource赋值给JdbcTemp即可。

    目前JdbcTempoperate方法只支持有限几种类型:String Integer Float Long Double Bye Short,也就是说,当你需要更新数据库的参数类型符合这几种,可以使用operate方法,简化JDBC语句编写。

这样做的理由基于“将所有数据表字段尽可能都设计成字符串类型”:http://www.jdon.com/jive/thread.jsp?forum=91&thread=23875

 

  

DaoCRUDTemplate API

本功能只有Jdon框架5.1以后才有。使用本功能可以无需你写CRUD的持久层代码了,大大减轻开发工作量。

Struts+Jdon +Hibernate3.2的案例samples_hibernate.zip中,无论任何对象的持久化,都可以使用如下一个JDBC DAO接口实现:

public void insert(Object o) throws Exception;

 

public void update(Object o) throws Exception;

 

public void delete(Object o) throws Exception;

 

public Object loadModelById(Class classz, Serializable Id) throws Exception;

        

这些CRUD方法已经在Jdon框架的com.jdon.persistence. DaoCRUDTemplate模板中已经实现,你只需直接调用就可以了。如你自己的JdbcDaoImp可以如下实现:

 

public class JdbcDaoImp  extends DaoCRUDTemplate implements JdbcDao{

    ….

}

当然,如果要使用DaoCRUDTemplate,还需要激活CloseSessionInView,在web.xml配置CloseSessionInViewFilter,如下:

<filter>

        <filter-name>hibernateFilter</filter-name>

        <filter-class>com.jdon.persistence.hibernate.CloseSessionInViewFilter</filter-class>

 </filter>

 <filter-mapping>

        <filter-name>hibernateFilter</filter-name>

        <url-pattern>/*</url-pattern>

 </filter-mapping>

 

这样可以实现一个请求结束后再进行Session关闭,这样,Hibernate3.2缺省的懒加载功能就可以正常使用。更多可见:5.1新功能章节

 

 

 

多态、有态和单态

目前在Jdon框架1.3以上版本中,提供三种接口让你的应用服务Service类继承:

com.jdon.controller.pool.Poolable   //对于主要很大的类推荐使用,使用对象池提高你的服务实例运行性能

com.jdon.controller.service.Stateful  //有状态,当你需要处理有状态数据使用,如购物车

com.jdon.container.visitor.data.SessionContextAcceptable //需要获得容器的登陆信息,见下节。

上述三种接口选择根据具体业务需求决,SessionContextAcceptable可以和前面两种混合使用,Poolable   Stateful  不能并列使用,只能二选其一。

5.6版本以后,分别提供以上三种Annotation元注解,这样不用你的代码实现这些接口,从而减少所谓侵略性。

假设你的一个应用服务类的接口名为MyServiceIF,类实现为MyServiceImp,那么当客户端进行如下服务调用:

MyServiceIF ms = (MyServiceIF) WebAppUtil.getService("myService ", request);

这时,ms这个实例产生和MyServiceImp所继承的上述三种接口有关系,如果MyServiceImp实现了Poolable,那么ms就是MyServiceImp多态实例池中的任何一个;如果MyServiceImp实现了Stateful接口,那么,每个客户端每次获得的ms都是同一个MyServiceImp实例;如果MyServiceImp不继承任何接口,那么每次调用上述服务获得的ms实例都是一个新的实例,相当于new一个实例。

如果希望每次获得ms实例是单态实例,也就是是一个实例,直接通过上述WebAppUtil.getService语句是不行的,但是有一个变通办法,将MyServiceImp嵌入在其他服务类中,例如嵌入一个叫OurService类中,例如:

public class OurService implements OurServiceIF {

   

    private MyServiceIF myservice;

   

    public OurService(MyServiceIF myservice) {

        this.myservice = myservice;

    }

   

    public MyServiceIF getMyservice() {

        return myservice;

    }

}

这样,首先获得OurService实例:

OurServiceIF os = (OurServiceIF) WebAppUtil.getService("ourService ", request);

MyServiceIF ms = os. getMyservice();

这样,每次获得ms就是同一个实例,或者称为单态,原因是:MyServiceImp是通过Ioc容器注射进入OurService中的,而Ioc容器每次注射的都是单态实例。

 

          

 

 

用户注册登陆

用户注册登陆系统是每个系统必须模块,主要包括两个功能块:用户资料注册和修改部分;用户登陆和访问权限ACL部分。

前一个部分主要是用户资料的增删改查CRUD,可以使用Jdon框架轻易完成;如JPestore中的SignAction类。

第二个部分有两种实现方式:基于容器和手工定制(Container-based vs. custom security),这两种实现方式都可由用户自己选择,这里主要说明一下,如何将用户登陆后的信息如用户ID共享给业务模型,例如:论坛发言者登陆后,发表一个帖子,这个帖子应该有一个用户ID,如何将发言者的用户ID赋值给帖子里?依据不同的实现方式有不同的捷径:

手工定制就是开发者自己编制代码完成,为需要保护的资源设立filter,或者在每个Jsp页面include一个安全权限检查Jsp模块,在这个方式下,用户登陆信息一般是保存在HttpSession中,那么在表现层继承ModelHandler实现一个子类,在这个Handler中能够访问HttpSession,并取出事先保存在HttpSession中的用户信息如ID

当然,基于容器的实现方式也可以采取上述办法,例如JdonNewsNewsHandler类中的serviceAction方法代码,如下:

User user = (User) ContainerUtil.getUserModelAfterLogin(request);

if (user != null) {

  News news = (News) em.getModel();

  news.setUser(user);

}

else

  Debug.logVerbose(" not found user in any scope ", module);

这样,将用户登陆信息user直接赋值到news实例中。

这种方式是通用方式,缺点是需要专门实现一个ModelHandler类,需要编写代码,还有一个不需要编写代码的方式:

获得基于容器登陆用户Principle

下面介绍在服务层能够直接获得基于容器的登陆用户UserPrinciple名称的方式,这种方式只适合POJO Service架构,而且必须是基于容器实现方式,相关基础知识点见:

http://www.jdon.com/idea/jaas/06001.htm

使用本方式目的在于简化表现层代码,在Jdon框架中,表现层基本都是通过jdonframework.xml配置实现,一般情况不必编码,如果不使用本方式,那么就必须自己实现一个ModelHandler子类,现在则不必,可以在业务服务层服务Service对象中直接获得request.getPrinciple()方法结果。

直接简单的使用方式:

1.Service对象继承com.jdon.container.visitor.data.SessionContextAcceptable,该接口目前有两个方法:

  void setSessionContext(SessionContext sessionContext);

  SessionContext getSessionContext ();

这样这个Service类必须完成接口上述两个方法,实际是Jdon容器在运行时,通过com.jdon.aop.interceptor. SessionContextInterceptor这个拦截器在运行时注射到被调用的这个Service对象中。

目前com.jdon.container.visitor.data.SessionContext中已经包含String类型的principleName,这个值来源于request.getUserPrincipal()获得的PrincipalName值,如下:

Principal principal = request.getUserPrincipal();

String principleName= principal.getName();

上述这段代码是在Jdon框架中执行, 这样通过这种方式,在服务层服务对象中我们可以容易获得保存在SessionContext中用户登陆信息Principle Name,使用这个方式虽然方便,但是必须有下列注意点:

1. 继承com.jdon.container.visitor.data.SessionContextAcceptable的服务层服务Service对象必须被客户端直接调用,调用方式是代码或配置方式都可以,客户端代码方式如下:

FrameworkTestServiceIF frameworkTestService = (FrameworkTestServiceIF) WebAppUtil.getService(

            "frameworkTestService",  request);

frameworkTestService.getName()

或在jdonframework.xml中配置model部分指向frameworkTestService调用也可以,这是一种配置方式调用。

只有这个服务Service对象直接面对客户端调用,拦截器的注射功能才能将SessionData注射进入它的“体内”。

2.访问服务层服务Service对象的客户端必须在容器安全体系保护下,一般这样客户端都是JspServlet/Action 都会有Url,例如http://localhost:8080/MyWeb/Admin/XXXX.do,在web.xml以及配置/Admin/*都是必须被特地角色才能访问。

因为只有这样,Web容器的Request对象中getUserPrinciple才会有值,这是J2EE规范规定的。载有角色值的Request访问/Admin/XXXX.doXXXXX.doStruts的一个Action,再通过Action访问特定的服务Service对象。

在上述两种情况同时满足情况下,服务层服务Service对象中sessionContext中的principleName才会有该用户的登陆信息,注意这个登陆信息令牌可以是用户ID或用户名,取决于你容器配置如JBosslogin-config.xml,只有密码验证通过过Web容器才会有值。

一旦你在Service中获得principleName,你就可以再通过查询数据库获得该用户的完整资料,从而将用户信息共享给所有业务服务层。

具体代码见Jivejdon 3.0com.jdon.jivejdon.service.imp. ForumMessageServiceImp

 

SessionContext用处

上述容器登陆信息Principle Name通过保存在SessionContext中使得服务层服务对象能够获得用户的登陆信息。

SessionContext是保存在Web容器的HttpSession中一个载体,当在服务层服务对象中需要缓存一些数据,这些数据的ScopeSession性质,这样可以通过SessionContext实现数据共享。Jdon框架还提供Stateful提供另外一种与Session有关状态载体选择。

如果希望缓存或共享的数据的scopeRequest,可以使用JDK 新语言特性ThreadLocal,服务层的服务对象就能够和Jdon容器以外的Request请求相关发生联系,例如我们可以做一个ServletFilter,将某些数据保存在ThreadLocal中,然后在服务层服务对象中从ThreadLocal中获取,这属于一般应用技巧,这里不详细描述,只是做一个应用提示。

如果缓存或共享的数据scopeApplication,就将其注册到Jdon容器中,因为Jdon容器的scopeApplication的,这样整个服务层也可以访问。

目前Jdon框架通过SessionContextSetup将一些用户相关信息保存到SessionContext,当应用系统通过Jdon框架调用任何服务时,将激活SessionContextSetupSessionContextSetup的缺省实现是com.jdon.security.web. HttpRequestUserSetup

目前在HttpRequestUserSetup中,实现两个功能:将用户通过容器登陆的Principleremote Address地址保存在SessionContext,这样,在业务层中的所有服务将可以通过SessionContext获得这两个数值。

HttpRequestUserSetup是通过在container.xml中配置激活的:

   <component name="sessionContextSetup" class="com.jdon.security.web.HttpRequestUserSetup" />

如果你还有更多数值需要供业务层服务使用,可以提到这行配置,见组件替换

例如:缺省HttpRequestUserSetup中是从request中获得用户登录的Principle,如果你想自己实现用户登录,使用ServletFilter来实现登录验证,验证通过后,将验证后的用户信息保存在RequestThreadLocal中,这样,你就需要实现自己的SessionContextSetup,用来从RequestThreadLocal中获取用户信息给Jdon框架。

 

 

cookie自动登录

本节是针对基于Web容器登录体系,login.jsp登录页面如下:

通常基于容器的登录,在login.jsp中提交的actionj_security_check,为了激活“自动登录”功能,我们需要使用自己的servlet,然后再转发到j_security_check,如下:

<form method="POST" action="<%=request.getContextPath()%>/login"

….

</form>

这里/login是一个Servlet,在web.xml中配制如下:

<servlet>

    <servlet-name>login</servlet-name>

    <servlet-class>com.jdon.security.web.LoginServlet</servlet-class>

    <init-param>

      <param-name>login</param-name>

      <param-value>/account/login.jsp</param-value>

    </init-param>  

     <init-param>

      <param-name>logout</param-name>

      <param-value>/account/logout.jsp</param-value>

    </init-param>

     <load-on-startup>2</load-on-startup>

  </servlet>

 

   <servlet-mapping>

    <servlet-name>login</servlet-name>

    <url-pattern>/login</url-pattern>

  </servlet-mapping> 

com.jdon.security.web.LoginServletJdonFramework中的一个类,主要功能:

如果是从login.jsp提交,LoginServle判断自动登录是否选中,如果是,将用户和密码加密后保存在客户端cookie中,然后再转发到j_security_check

如果是直接/login?logout调用,表示退出,我们应用的退出按钮连接需要指向这个连接,退出主要是session失效和cookie失效。

如果是直接无参数/login调用,首先判断当前cookie是否保存用户和密码,如果是,使用这对用户密码发往j_security_check自动登录;如果不是,推出login.jsp页面。

LoginServlet需要配置两个参数:loginlogout表示你的应用的login.jsp位置和logout,jsp位置,LoginServlet将根据条件推出这些页面。

以上是配置之一,还有下面的web.xml配置。

web.xmlform-login-page不要直接将登录页面/account/login.jsp写入这样自动登录功能就不能激活或者说通过前面步骤我们配置激活cookiecookie中保存了我们的用户和密码,但是具体使用时还必须到cookie中查询,因此需要配置form-login-page/login,也就是LoginServlet

<login-config>

    <auth-method>FORM</auth-method>

    <form-login-config>

      <form-login-page>/login</form-login-page>

      <form-error-page>/account/login_error.jsp </form-error-page>

    </form-login-config>

</login-config>

 

继承模型最佳实践

Jdon框架除了可以实现根据Evans DDD的关联模型以外,也可以方便实现继承关系的模型,下面是来自JiveJdon的继承模型最佳实践,具体案例见JiveJdonShortMessage代码。

消息模型是一个父子继承关系,这样父子继承关系的模型如何使用Jdon框架来简单明了的实现,也就是不破坏领域层的继承关系,将这种继承关系延伸到非对象化的表现层和持久层,下面根据消息模型的实践总结为最佳实践如下图:

 

 

组合模型最佳实践

 

关注订阅模型是JiveJdon的一个功能,该模型的特点是Subscription根模型中有一个接口关联,而接口有不同实现,将来可能要扩展,但是在具体运行中,该接口肯定是其具体实现的一个子类,如何协调表现层模型来实现这一点,不破坏组合模型的特点和优点,JiveJdon的关注Subscription模型提供一条最佳实践方式。具体见其代码。

struts-config.xml的配置一个ActionForm:

<form-bean name="subscriptionForm"

  type="com.jdon.jivejdon.presentation.form.SubscriptionForm" />

 

    model.xml模型的CRUD配置中,也配置ActionFormModel一对一.

      <model key="subscriptionId"

           class="com.jdon.jivejdon.model.subscription.Subscription">

           <actionForm name="subscriptionForm"/>

           <handler>

              <service ref="subscriptionService">

                  <getMethod    name="getSubscription"/>

                  <createMethod name="createSubscription"/>

                  <deleteMethod name="deleteSubscription"/>

              </service>

           </handler>

     </model>     

 

微妙之处在com.jdon.jivejdon.presentation.form.SubscriptionForm代码中.:存在subscribeType这个参数, 而模型Subscription中没有subscribeType这个参数,只有数据库中有subscribeType这个字段,转换关系:

表现层: subscribeType  <---- > 模型中 Subscribe类型 <---- >数据表subscribeType 

通过这种转换,可保证模型层使用OO面向对象方式设计,将数据完美表现为对象.

SubscriptionFormsubscribeType  Subscribe转换主要两个方法如下:

/**

     * transfer Form to Model join fields into Subscribed;

     * Form拷贝到Model,将自动执行FormgetSubscribe,将结果通过

调用ModelsetSubscribe方法,赋值到Model.

     * @return

     */

 

    public Subscribed getSubscribe() {

       return SubscribedFactory.create(subscribeType, subscribeId);

    }

 

    /**

     * transfer Model to Form separate Subscribed into fields;

     * 模型拷贝到Form,将自动执行模型的getSubscribe方法,将其结果通过Form setSubscribe方法赋值其中.

     * @return

     */

 

    public void setSubscribe(Subscribed subscribe) {

       this.subscribeType =

SubscribedFactory.getSubscribeType(subscribe);

       this.subscriptionId = subscribe.getSubscribeId();

    }

 

 

 

Domain Event模式

Evans DDD实现过程中,经常会碰到实体和服务Service以及Repository交互过程,这个交互过程的实现是一个难点,也是容易造成失血贫血模型的主要途径。

Domain Event提出解决方案,Jdon框架提供的异步观察者模式Domain Event实现提供更优雅的解决方案。

详细文章见http://www.jdon.com/jivejdon/thread/37289

使用6.2版本的领域消息可以彻底实现领域模型和技术架构的耦合,领域模型任何事件只要通过消息就可以和技术架构比如持久化 领域服务进行交互,而且是异步的,没有任何事务单一线程。

 

 

版本新增或改变功能

1.X版本

1.4版本

 

增加ModelIF接口其意义等同于以前Model是继承Model类或实现ModelIF接口可由框架应用者自行决定。

服务调用命令模式,见这里

批量分页查询,提供新的批量读取数据库算法,增加批量查询时缓存的重用效率。

 

1.5版本

 

Fixed一些1.4版本的BUG

update 02:解决主键类型是非String时,可能带来的问题,在1.4及其以前版本,如果主键类型是非StringService接口中查询模型方法如getMessage(String messsageId)方法,该方法参数messageId必须为String,这会带来初学者一些岐义,现在1.5以后这个方法参数messageId主键类型可以和模型主键类型统一了,如模型主键类型是Long方法,那么Service接口可以为getMessage(Long messsageId)

JDK5.0下编译通过,将JF应用案例全部在JDK5.0下调试通过。

暂时没有在1.5版本中加入JDK5.0特殊语法,换言之,1.5版本还可以在JDK1.4平台编译通过。

1.5版本推荐使用平台: JDK5.0 + JBoss 3.2.8

5.x版本

5.0版本

 

升级到JavaEE5.0平台,本框架只能在JDK5.0以上平台编译和运行,引入了5.0高质量的并行并发概念,提高Jdon框架在高访问下的线程安全性和并发性。

增加对EJB3服务的支持,如果你的普通JavaBean架构需要升级到EJB2/EJB3,无需更改客户端或表现层代码,直接修改JdonFramework配置即可。

5.1版本

 

 

JdonFramework 5.1主要变化是增加了Hibernate3支持,并且将原来一个jdonFramework.jar包分为三个包:jdonFramework.jar jdon-struts1x.jarjdon-hibernst3x.jar
   
如果使用struts+jdon+Hibernate架构,这三个包需要完全使用。如果只使用Jdon框架作为业务层框架,只需要jdonFramework.jar ,可以使用Jdon框架的IOC管理功能,不过Jdon框架提供的CRUD流程简化将无法实现。
    JdonFramework 5.1
重点是增加Hibernate3整合,特别是Hibernate3的懒加载支持,每个使用Jdon+Hibernate项目都需要在web.xml配置CloseSessionInViewFilter,如下:

<filter>

        <filter-name>hibernateFilter</filter-name>

        <filter-class>com.jdon.persistence.hibernate.CloseSessionInViewFilter</filter-class>

 </filter>

 <filter-mapping>

        <filter-name>hibernateFilter</filter-name>

        <url-pattern>/*</url-pattern>

 </filter-mapping>

 

这样可以实现一个请求结束后再进行Session关闭,这样,Hibernate缺省的Lazy=true功能就可以正常使用。
    
懒加载功能可以大幅度提高Hibernate关联性能(前提是基于DDD分析设计),是Hibernate必须使用的功能,但是单纯使用Hibernate却无法激活懒加载,致使很多人关闭懒加载lazy="false"
     Spring
也提供了HibernateOpenSessionInView功能,但是它是将HibernateSession再表现层打开和关闭,而JdonFramework 5.1只是检查是否打开的Session,如果有则关闭,这样缩短Session无谓开启时间,降低出错率,同时简化事务(Spring+Hibernate架构Spring事务缺省是read-only只读,只有配置显式create方法事务为非read-only后,才能使用Hibernate保存创建新资料,非常不方便);

     Struts+Jdon+Hibernate(
简称SJH)Struts+Spring+Hibernate(简称SSH)最大的特点是:
1.SJH
Hibernate无侵略性,Hibernate全局配置还是在自己的hibernate.cfg.xml中配置,而不似SSH需要在Spring中配置。这样从设计上减少彼此依赖性,实现真正分层松耦合。jdon-hibernate3x.jar可以单独使用。
2.SJH
以更少的代码快速实现增删改查和批量分页查询。
3.Jdon
框架内置缓存+Hibernate二级缓存+懒加载最大化提高缓存使用效率,性能优异,有效实现数据库零访问和必要访问。

     JdonFramework 5.1
推出同时,也推出struts+jdon+Hibernate3的整合应用案例
http://www.jdon.com/jdonframework/download/samples_hibernate.zip
   
该案例领域模型图:


5.5版本

将框架与HttpRequestHttpSession分离,框架不一定需要用在WEB中,也可应用在Application,直接运行源码包中的runTest.bat测试。

 

Jdon框架可以只作为一个IOC/DI框架,使用在JavaEE 或普通JavaJ2ME/JME中,拓展用途。

5.5版本经过严格的并发性能测试,解决了以往框架中可能存在的内存泄漏漏洞,使用ThreadLocal以及java.concurrent Callable FutureJDK5.0以上新的并行功能,增强了并行计算能力。

5.5版本10分钟内发出近万次并发请求,没有任何报错和问题,测试客户端见JiveJdon3.5源码包中jmeterTest.jmx,可以使用Jmeter打开,服务器端运行JprofilerJBoss服务器,注意:进行并发测试时,需要将concurrent_myaspectconcurrent_web.xml提到原来的myaspect.xmlweb.xml,失效JiveJdon的防SPAM功能。

5.5版本的源码经过并发性能重构,增强稳健型和快速性,是目前Jdon框架中最稳定的版本。

限制sessionContext中最多只能放置10个对象,超过就自动清楚,这样,不要将重要状态放入sessionContext中,也不要放置超过10个对象,sessionContext最大个数10设置是在container.xml中。

使用ehcache作为Jdon框架缺省的缓存,以增强兼容性和伸缩性,分布式Cache Terracotta http://www.terracotta.org/ 可以将encache自动进行分布到多台服务器,这样,Jdon框架也能支持分布式缓存和计算(无需借助EJB http://www.jdon.com/article/34888.html)

Jdon框架部署在集群环境下,由于集群环境,HttpSession将被在多台服务器间同步,而Jdon框架利用HttpSession进行动态代理优化缓存,这时可能会引起NotSerializableException问题,采取办法可以失效优化缓存。

确认用winrar打开JdonFramework.jar,将META-INFcontainer.xml拖到某个文件夹下。将其中下面这一行中true改为false,保存后再将container.xml拖回winrar中即可。

<component name="httpSessionVisitorFactorySetup"

class="com.jdon.container.visitor.http.HttpSessionVisitorFactorySetup">

<constructor value="40" />

<!-- it is count of the cached instances that saved in a sessionContext instance, -->

<constructor value="true" /> <!-- disable all session cached except SessionConext -->

</component>

正常单机情况下,也可以将这一设置改为false,可以节省内存,经过测试,稳定运行后性能差异不大。

5.6版本

5.6版本主要是增加Annotation元注释来替代原来一些接口,减少框架的侵略性。

Jdon框架默认提供了三种拦截器,当然你可以自己增加定制,这三种拦截器分别是对象池 Session周期和有态,以前为让你的service实现对象池等这三个功能,需要特别实现三个接口Poolable SessionContextAcceptable Stateful,而现在有一个可替代方法,就是使用元注解Annotation,例如:

@Poolable

public class ForumMessageShell implements ForumMessageService

表示ForumMessageShell将以对象池形式存在,这样对于一个主要代码很多的类,可以起到提高性能,防止资源无限消耗。对象池优点见: http://www.jdon.com/jivejdon/thread/35191.html,注意不要将小类做成Pool,因为对象池本省也损耗一定性能。

另外一个元注释是Model,原来所有Domain Model需要实现接口ModelIF或继承Model类,现在可以使用@Model来替代了,如:

@Model

public class Account{}

 

目前Jdon框架是AnnotationXML配置组合使用,Jdon框架并没有使用Annotation全面替代XML配置文件,因为我们对于元注解Annotation的观点是:适当但是不可乱用,是一把双刃剑。当所有组件都可以自由拆开IOC/DI,运行时也可自由指定顺序AOP,那么我们就能够更快地跟随变化,甚至无需重新编译整个项目,只要带一个组件到现场,XML配置改写一下,就能上线运行,注意,我这里提到了XML配置,如果你完全使用Annotation就达不到这个目的。

http://www.jdon.com/jivejdon/forum/messageList.shtml?thread=35373&message=23119938#23119938

 

5.8版本

增加基于Http的远程remote远程调用,可以方便开发出基于RIA富客户端的多层架构C/S系统。见案例remote_javafx

案例remote_javafx是使用RIA技术JavaFX + Hessian + Jdon框架的一个Demo案例,开发简要步骤:

web.xml配置Hessian Servlet Proxy如下:

<servlet>

<servlet-name>Hessian2Jdon</servlet-name>

<servlet-class>com.jdon.bussinessproxy.remote.HessianToJdonServlet

</servlet-class>

</servlet>

<servlet-mapping>

<servlet-name>Hessian2Jdon</servlet-name>

<url-pattern>/remote/*</url-pattern>

</servlet-mapping>

 

这样,当远程客户端调用http://localhost:8080//remote/helloService,那么这个Servlet 将在jdonframeowrk.xml查找服务名称为helloService的服务:

<app>

   <services>

     <pojoService name="helloService" class="sample.HelloServiceImpl"/>

    </services>

</app>

远程客户端代码如下:

HessianProxyFactory factory = new HessianProxyFactory();

HelloService _service = (HelloService)
factory.create(HelloService.class, _url);

_service.hello(s);

客户端调用HelloServicehello方法时,将激活HelloServiceImpl.hello方法。Demo在线演示网址:http://www.jdon.com:8080/jdonremote/

6.x版本

6.0版本

 

(一)增加Annotation注射功能, @Service@Component替代了原来XML配置:

<pojoService name="给自己类取的名称" class="完整类的名称"/>

或者在具体类代码上加入:@Service("给自己类取的名称")

 

<component name="给自己类取的名称" class="完整类的名称"/>

或者在具体类代码上加入@Component("给自己类取的名称")

 

ServiceComponent都表示组件构建概念,可以是一个类,也可以是一个类为主的多个类,如果这个组件供客户端调用,那么就称为服务。两者在Jdon框架中没有区别,只是使用时称呼不同而已。

@Component(“给自己类取的名称”)也可以简化为@Component,这样,Component的名称就是该类的完整类名称(getClass,getName())

@Service则不可以这样,因为是供Jdon框架容器以外的外部客户端调用的,一定要取一个名称。

因为需要被客户端调用,那么就要指定服务的名称,所以,@Service@Component的区别就是,@Service必须规定一个名称@Service("给自己类取的名称");而@Component则没有name属性,缺省是类名称getClass().getName()名称,如果客户端有时需要临时直接调用@Component标注的组件,也可以按类名称调用。

开发Jdon框架变得异常简单,只需要两步:

第一步:将存在依赖关联关系的两个类用@Service@Component标注:

@Poolable

@Service("helloService")

public class HelloServiceImpl implements HelloService{

      UserRepository userRepository;

      public HelloServiceImpl(UserRepository userRepository){

            this.userRepository = userRepository;

      }

..

}

@Component

public class UserRepositoryInMEM implements UserRepository {

 

 

客户端调用代码:

HelloService helloService = (HelloService) WebAppUtil.getService("helloService", req);

String result = helloService.hello(myname);

 

    无需jdonframework.xml配置文件和相关XML配置。全部演示代码见JdonFramework源码包目录下examples/testWeb

 

(二).增加@Singleton Annotation,缺省客户端调用getService时,每次请求获得一个新的Service实例,也可以使用@Poolable对象池来对代码比较大的Service类进行性能提高,现在又提供了另外一种方法,使用单例@Singleton

单例和对象池区别主要是:对象池可以进行实例最多个数控制,这样,能够保护服务器不会被尖峰冲击造成资源耗尽。

Service服务代码比较简单小的情况下,Service实例三个创建方式:单例、对象池以及每次请求生成一个新实例,在性能上几乎没有太大差别,已经使用框架源码包中examples目录下的testWeb经过测试了。

 

 

(三).没有将有关Action的配置转为Annotation,原来的配置如下继续保留:

<model key="username"

                 class="com.jdon.jivejdon.model.Account">

                 <actionForm name="accountForm"/>

                 <handler>

                        <service ref="accountService">

                               <initMethod   name="initAccount"/>

                               <getMethod    name="getAccountByName"/>

                               <createMethod name="createAccount"/>

                               <updateMethod name="updateAccount"/>

                               <deleteMethod name="deleteAccount"/>

                        </service>

                 </handler>

</model>

因为这个配置是有关MVC表现层,而表现层不是Jdon框架的核心,所以,Jdon框架核心是业务层,对Domain ModelService服务组件进行管理,所以,不能在业务层耦合具体表现层。上述配置可以认为是表现层和业务层的桥连接配置。

 

6.1版本

增加异步事件处理功能,可以在当前处理过程同时,抛出另外一个过程处理其他事务,不影响当前主要处理过程,例如,发送邮件是一个很费时的过程,如果一个业务过程需要发送Email,那么将发送Email这件事情通过异步,放到另外一个线程中处理,从而能实现处理效率性能的提供。

6.1版本根据Jdk 6.0的并发Conncurrent模型实现了异步事件处理功能com.jdon.async.EventProcessor

该类有两个构造参数,第一个是你分配几个线程同时来处理的异步事件,一般是设置一个,Queue队列性质,一对一,如果你希望多个线程并发同时处理,发挥并发性能,那么可以选择更大数字,不过要注意,你的业务是否能够被多个线程同时执行,比如Email发送如果多个线程发送,那么就多发几次,一次就够了,因此设置为1.

第二个参数,是允许同时几个线程在处理事务,因为你是将一个个事件或消息逐个放到EventProcessor队列中,如果每个事件或消息处理起来很费时,在之前事件消息还没处理完,你是否同意系统再启动其他线程处理后面的事件消息,这个参数需要根据你的硬件系统处理能力而定,如果处理能力不高,那么设置小一些。

6.1版本还基于EventProcessor提供了观察者模式,用来实现对模型中数据变动的监测。

通常模型中数据变动,我们是通过直接关联或依赖关系,来通知另外一个模型同时变动,如下:

void changeValue(OtherModel om){

    this.value = “newValue”;

    om.setValue(“newValue too”);//通过这个方法通知另外一个模型修改

}

这种方式适合与核心模型关系紧密的模型使用,比如同属于一个聚合模型边界内的模型对象们,但是现实中,还有一些关系并不如聚合关联那么紧密,是和核心模型有关联,但是是一种非常松散的关联,如何实现他们之间变动事件的传递?

使用观察者模式,传统的JDK观察者 API实现是同步的,如下:

void changeValue(OtherModel om){

    this.value = “newValue”;

    setChanged(); //设置变化点

    notifyObservers;//通过这个方法通知观察者事件

    doothers(); //必须等上面观察者实现了其响应行为才执行本方法。

}

而使用com.jdon.async.observer.ObservableAdaptercom.jdon.async.observer. TaskObserver则可以实现异步的观察者,这样,上面方法doothers就不必等待观察者Observer做完它的事情才能继续,两者同时运行。

异步观察者模式具体使用见JiveJdon3.7版本中的Subscription关注订阅模型,当主题有新内容时,通知关注者,我们在ForuThreadaddNewMessage方法中加入观察激活点,当有人在这个主题下发新回帖时,激活这个观察点,从而异步激活订阅通知处理功能,由它来检查哪些人关注订阅了这个主题,然后给他们逐个发送消息或邮件,这个过程就不会影响发新回帖处理过程,保证发新回帖处理过程快速完成。

异步观察者模式步骤总结:

1. 继承TaskObserver,实现其action方法,这是激活后所要实现的方法。

2. 将观察者TasjObserver加入ObservableAdapter

3. 将设置观察点,在被观察或监听的类中,调用ObservableAdapter,在具体激活方法中调用ObservableAdapternotifyObservers方法。

上面三个部分是松耦合,可以分别在代码三个地方执行,有时也可以合并一起:

private void sendComposer(EmailTask emailTask) {

     //创建一个观察者 继承继承TaskObserver

       EmailTaskListerner emailTaskListerner = new      
                        EmailTaskListerner(emailTask);

         //创建一个ObservableAdapter

       ObservableAdapter subscriptionObservable = new
                        ObservableAdapter(
ep);

         //将观察者TasjObserver加入ObservableAdapter

        subscriptionObservable.addObserver(emailTaskListerner);

         //激活出发

       subscriptionObservable.notifyObservers(null);

}

      关于领域模型设计和相关监视事件,见有关Qi4j讨论,Jdon框架和Qi4j一样已经考虑到此类实践问题:http://www.jdon.com/jivejdon/thread/37186

 

 

6.2版本AOP

6.2版本架构改变比较大,最大变化是增强了AOP功能,具体如下:

1. 使用CGLIB替代JDK的动态代理,性能有所提高:http://www.jdon.com/jivejdon/thread/37330

 

3.               增强了AOP功能,提供基于Annotation的方法拦截功能,但是AOP

Spring/Aspect的有些区别,主要是在被拦截类进行定义,如下:

 

@Introduce("c")

public class A implements AInterface {

 

       @Before("testOne")

       public Object myMethod(@Input() Object inVal, @Returning() Object returnVal) {

              System.out.println("this is A.myMethod is active!!!! ");

              return inVal + "myMethod" + returnVal;

       }

 

       @After("testWo")

       public Object myMethod2(Object inVal) {

              System.out.println("this is A.myMethod2 is active!!!! ");

              return "myMethod2" + inVal;

       }

 

}

通过元注解@Introduce引入拦截器cc是一个组件的名称,用Component(“c”)定义的类,@Before("testOne")表示c这个类的testOne方法将在当前方法myMehtod之前执行,@After则表示是以后执行。

使用这种引入式定义的拦截器c类不需要特别其他配置,只是一个普通组件而已,如下:

@Component("c")

public class C {

 

       public Object testOne(Object inVal) {

              System.out.println("this is C.testOne " + inVal);

              return " interceptor" + inVal;

       }

 

       public Object testWo(Object inVal) {

              System.out.println("this is C.testWo ");

              return inVal + " interceptor";

       }

 

}

  6.2版本还提供了一种Around方式的拦截,就是在包围原始方法前后都执行的方法,引入的类方式和BeforeAfter类似:

@Introduce("aroundAdvice")

public class D implements DInterface {

 

       @Around

       public Object myMethod3(Object inVal) {

              System.out.println("this is D.myMethod3 is active!!!! ");

              return "myMethod3" + inVal;

       }

}

但是拦截器aroundAdvice写法就不是普通类的写法,需要实现接口org.aopalliance.intercept.MethodInterceptor,完成其方法,同时必须有类Annotation@Intercepto,整个内容如下:

@Interceptor("aroundAdvice")

public class AroundAdvice implements MethodInterceptor {

      

       public Object invoke(MethodInvocation invocation) throws Throwable {

              System.out.print("\n\n this is AroundAdvice  before \n\n");

 

              if (isAdviceAround(invocation.getThis().getClass()))

                     System.out.print("\n\n this is AroundAdvice  myMethod3 \n\n");

 

              Object o = invocation.proceed(); //这句关键表示执行原始方法

              System.out.print("\n\n this is AroundAdvice after \n\n");

              return o;

       }

}

 

以上6.2版本新增的Before After Around拦截方式的编写规则,使用Java代码替代复杂的ponitcut规则表达式。简单易用。

另外还有一种和Spring类似,在拦截器类中配置被拦截的点Pointcut的方式,使用这个方式,就无需象上面在被拦截的地方进行注解,解放了被拦截类,而上面这种写法是解放了拦截类,灵活应用。

@Interceptor(name = "myInterceptor", pointcut = "a,c")

public class MyInterceptor implements MethodInterceptor {

 

       public Object invoke(MethodInvocation methodInvocation) throws java.lang.Throwable {

              System.out.print("\n\n this is MyInterceptor  before \n\n");

              if (methodInvocation.getMethod().getName().equals("myMethod")) {

                     System.out.print("\n\n this is MyInterceptor for a.myMethod  \n\n");

              }

              Object o = methodInvocation.proceed();

              System.out.print("\n\n this is MyInterceptor after \n\n");

              return o;

       }

 

}

拦截器必须实现MehtodInvoker接口,而且必须标注@Interceptor(name = "myInterceptor", pointcut = "a,c") 其中名称name是拦截器自己的名称,而pointcut是被拦截类的@Component(“a”)@Component(“c”)

如果拦截所有对象,那么就直接@Interceptor(); 没有任何参数,不过这个拦截所有对象的意思不是在所有对象只要被调用就激活,而是客户端通过WebApp进行对Jdon框架通过Annotation标注的这些Component@Service时才会激活。

更多见AOP章节

 

6.2版本领域消息

见前面领域模型章节

 

 

 

   

 

 

 

 

 

技术支持

无论Jdon框架本身还是使用Jdon框架开发的任何系统出现问题,都可以在论坛

http://www.jdon.com/jivejdon/forum.jsp?forum=61

发生错误后,请按下面步骤贴出错误:

1. 需要打开Jboss/server/default/log/server.log

2. 键入搜索" ERROR ",注意ERROR两边各有一个空格,找到第一个错误,那是错误起源,贴到论坛中。

Weblogic + Oracle环境下使用注意点

注意两点:

1. Oracle字符串类型很特殊,它和Java JDBC字符串类型有些特别,Oracle字符串如果是10位,而Java传给它是两个字母ab,你必须将剩余8个字母用空格填满,否则,出现“查询页面不显示记录,但是可以向数据库里插入记录”现象。

建议oracle数据库主键使用Number,模型Model主键使用Long即可。

2. log4j需要正常在Weblogic中显示,配置不是非常容易,按本说明书相应章节耐心配置。

总之,试验一个Jdon框架技术应该分开几个环节来分别测试:

1. 使用Jdon框架推荐的环境测试。

2. 使用自己特殊的数据库测试。

3. 使用自己特殊的服务器测试。

从这几个环境中,可以分别了解差异产生的原因。

开发环境配置

为方便初学者快速入手使用Jdon框架开发J2EE/Java EE系统,现将开发环境配置和软件下载陈述如下:

下载Java运行环境 也就是JDK1.4JDK5.0

下载Eclipse IDE开发软件,因为Eclipse插件非常多,安装后不一定能用,可在本站VIP下载区下载完整立即可用的Eclipse压缩包

下载运行环境,使用Tomcat/JBoss/Weblogic等等都可以,本站提供JBoss + MySQL现成压缩包。注意,需要将最新版本的Jdon框架拷贝进去,方法见前面安装章节

以上开发环境安装完成后,你可打开Jdon框架的案例项目进行研究学习,祝你成功。

 

配置启动Jdon框架

1)配置jdonframework.xmlstruts-config.xml中。

  <plug-in className="com.jdon.strutsutil.InitPlugIn">

    <set-property property="modelmapping-config"  value="jdonframework.xml完整路径" />

  </plug-in>

  2)将jdonframework.xml打包到你的Web项目。

否则,后台控制台会出现:

[InitPlugIn]  looking up a config: jdonframework.xml

[InitPlugIn]  cannot locate the config:: jdonframework.xml

打包部署

有两种方式打包成部署文件.war.ear

第一:如果是Eclipse工具,使用Ant进行打包,在本系统源码中已经编制好build.xml文件,更改其中J2EE服务器为的路径后,即可直接运行Ant打包。

第二:通过JBuilder直接打包,点本项目的Web项目,选择make即可,将自动出现xxx.war部署包(如果你是Tomcat就不会出现war文件,只能安装Tomcat方式配置)。

注意,将Web项目中去除任何包,也就是说WEB-INF/lib下没有任何包,StrutsJdonFramework包都放置在JBossserver/default/lib下。

如果出现与tld相关的错误, 如:

File "/WEB-INF/MultiPages.tld" not found

解决:在web.xml中应该有下面两行tld配置:

  <taglib>

    <taglib-uri>/WEB-INF/MultiPages.tld</taglib-uri>

    <taglib-location>/WEB-INF/MultiPages.tld</taglib-location>

  </taglib>

  <taglib>

    <taglib-uri>/WEB-INF/TreeView.tld</taglib-uri>

    <taglib-location>/WEB-INF/TreeView.tld</taglib-location>

  </taglib>

 并且确保/WEB-INF/MultiPages.tld/WEB-INF/TreeView.tld下文件存在,这两个MultiPages.tldTreeView.tlJdonFramework.jar中的META-INF/tlds中,可手工拷贝过去。

 

使用JBuilder2005直接打包时,缺省是不会将所有的编译好class打包进去的,同时也不会将jdonframework.xml等资源文件打包进去,我们需要经过特别步骤处理:

选择项目的Web模块如testweb,右键点选属性,选择其中的Content,点选include all classes and resources,在Content子节点中,选择Dependencies,将所有包都exclude all,因为这些包我们事先已经放置在JBossserver/default/lib下,不需要在本Web模块中携带。

确定后,重新编译一次项目,检查Web模块如testweb下的testweb.war文件,展开后,检查是否有jdonframework.xml,如果没有,接着如下步骤:

再次选择项目的Web模块如testweb,右键点选属性,选择Content,这次选择include speified filtered file and resource, 然后点按Add Filters,输入**/*.*,表示全部资源,确定。

再到Add Filters下面选择Add File,将jdonframework.xml等资源文件加入。

再次编译整个项目,检查war文件,应该所有class和资源文件都包括其中了。

 

 更多详细错误,打开JBoss日志文件:server/default/log/server.log,找到server.log中第一个ERROR信息,分析原因,或者贴到Jdon框架开发论坛中。

运行案例

打开浏览器,键入http://localhost:8080/testWeb/index.jsp

     我们将index.jsp导向真正执行/userListAction.do

网上实时演示网址: http://www.jdon.com:8080/testWeb/

当在JBossTomcat控制台 或者日志文件中出现下面字样标识Jdon框架安装启动成功:

<========  Jdon Framework started successfully! =========>

性能压力测试

用户自己对Jdon框架应用案例的测试心得:

http://www.jdon.com/jivejdon/thread/32894.html

 

 

附:JBuilder环境配置

如果你使用JBuilder开发工具,按下列步骤:

JdonFramework导入项目库,或者成为整个配置库。如图:

同时,配置Framework配置,如下图:

该配置是为了激活项目Web开发支持JdonFramework的标签库taglib,使用JdonFramework开发应用系统时,自动支持多页批量查询和树形结构显示,这两种功能需要在Jsp页面导入特定的标签库,该设置就是为实现此功能。

注意:由于JBuilder的部分BUG,最好将JdonFramework.jarMEAT-INF/tlds目录下的MultiPages.tld等文件解压出,形成单独文件,在这里导入这些单独文件。

配置WEB模块时,需选中Web框架,如下图:



如果你使用的是Eclipse,在将JdomFramework.jar作为一般的JAR导入项目。

另外,如果你的JBuilder中看不到源码包下面的jdonframework.xml文件,例如本项目的配置文件在com.jdon.framework.test包下面,但是你展开后,可能只发现application.properties等资源文件,但是没有发现jdonframework.xml,解决方式:

项目属性中,Build节点的子节点Resource,在下拉菜单中选择XML,然后点选右上部的copy即可(原来为do not copy)。

 

 

企业培训内容

我们提供Jdon框架上门培训和咨询,通过4天培训让企业用户完全掌握使用Jdon框架开发J2EE信息系统,总报价:14400元 由Jdon框架设计者彭晨阳亲赴贵公司培训,时间长短可调节。

1 6课时)

目标:掌握Jdon框架设计原理和开发特点

培训内容

J2EE多层架构系统简介

J2EE表现层、组件层Model和持久层特点介绍

传统J2EE开发优缺点比较

Jdon框架的总体特点介绍

Struts技术特点介绍

Hibernate技术特点介绍

Jdon设计原理简介

2 6课时)

目标:通过简单开发演示让学员了解Jdon框架整体开发步骤

培训内容

现场命题一个简单业务需求

域建模分析

权限登录机制设计

业务层功能特点分类和设计

模型的增删改查功能实现

批量分页查询功能实现

调试运行

3 6课时)

目标:Jdon框架开发特点和步骤详解

培训内容

开发步骤总体介绍

域建模过程和分析特点以及与数据库建模区别

Ioc/Aop微容器原理

Jdon框架组件构件配置重点和原理

模型Model编码特点

CRUD实现原理

CRUD复杂程度不同的开发场景设计

Jdon框架批量查询特点

数据批量查询最优设计和缓存设计

4天(6课时)

目标:通过复杂案例讲解让学员进一步掌握使用JdonFramework

培训大纲

基于JdonFramework快速开发J2EE系统的注意点。

JiveJdon3.0或网上商店为例,讲解基于JdonFramework设计开发一个复杂的J2EE系统的全过程:包括架构设计、直至最终实现。让学员能够完全理解复杂系统源码,并能重用这些设计思想和方法。

针对具体项目咨询,学员结合这几天所学,并结合项目特点,向老师征求一些实际应用方面的意见,根据以往类似项目的经验,提出一些具有建设性的意见。

 

全文完