UML软件工程组织

 

 

基于J2EE轻量级容器构件框架

2008-05-04 作者:luoqx 出处:blogjava
 

一、总体结构图

二、总体介绍

利用此框架开发主要将代码分为三层,即控制层(Command),业务逻辑层(Service)和数据访问层(DAO),command代码只关注于页面跳转逻辑关系,收集请求数据,转发请求及最后跳转到指定的显示页面(JSP);Service主要关注于页面逻辑,客户的需求转化为实际的业务逻辑就由Service层来实现,其中权限事务日志等不是纯业务的逻辑交由AOP的专门程序处理,使开发人员关注点聚焦而不会被繁琐的其它事情所影响效率;DAO层主要完成业务逻辑中需要与数据库间交互的部分代码,主要是跟sql语句,存储过程等相应的数据库(存储)打交道的部分代码的封装,把这一层抽离出来可以实现更好的移植性(如果转换数据库,只要修改DAO这一层就可以了)。调用关系图如下:

执行过程如上图,首先请求发送给主控制器,主控制器委托辅助类(Request Helper)根据分析url然后在配置文件中找到实际的command并执行,command获取请求的参数并委托Services层的各相应Manager类来处理相应的业务逻辑,其中如果有数据库操作的话则委托DAO来完成,将结果返回到command,command将结果(Data Bean)重新放入请求中并根据配置信息转发到显示(view)部分即JSP页面,最终JSP页面负责将取来的数据结合页面信息生成html代码返回到客户端浏览器上,实现了一个完整的交易过程。

三、控制器部分

controller包提供了三个主要功能:

如下图:

1、 mvc的主控制servlet

Main Controller类提供了总体控制作用,所有的请求都先映射到此类上,然后在统一分发到各个部分控制类。由于servlet是支持多线程的,所以性能是可以保证的。

MainController类继承于HttpServlet类,主要方法有:

1) init()方法,初始化方法,在此方法里面主要初始化一些公共的资源、服务,过滤器和一些系统基础配置参数等。初始化服务过程委托services包中的Service Manager类来完成,初始化各种filter由filter包下的Filter Manager来完成。

2) doPost()和doGet()方法,接收各种请求并交给processRequest()方法统一处理,统一了post, get两种请求方式。

通过在web.xml中注册和映射,使得各种符合要求的请求都直接转发到此控制类上,配置如下:

<servlet>

<servlet-name>mainCommand</servlet-name>

<servlet-class>

com.daosheng.blackfin.controller.MainController

</servlet-class>

<init-param>

<param-name>sysconfig</param-name>

<param-value>/WEB-INF/blackfin-config.xml</param-value>

</init-param>

<load-on-startup>1</load-on-startup>

</servlet>

<servlet-mapping>

<servlet-name>mainCommand</servlet-name>

<url-pattern>/command/*</url-pattern>

</servlet-mapping>

<servlet-mapping>

<servlet-name>mainCommand</servlet-name>

<url-pattern>/main/*</url-pattern>

</servlet-mapping>

<servlet-mapping>

<servlet-name>mainCommand</servlet-name>

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

</servlet-mapping>

这样碰到请求url以.cmd结尾的或者以/command/或/main/开头的都映射到此类上来做统一处理。实现了统一控制功能。

2、filter的接口和管理类

filter包主要包含filter接口和filter的管理类。FilterManager类负责初始化各注册的filter(开发人员可以自己开发并注册在注册文件中),系统控制类接收到各种请求先按需要交给过滤类(filter)来进行过滤,然后在由各自业务处理类完成业务处理。

统一过滤类可以用来完成一些需要每次请求都需要判断的逻辑,比如说登入校验(判断用户是否登入过,如果没有直接跳转到登入页面)还有一个比较关键的过滤就是CodeFilter类。一般的http请求分为两种类型,一种是普通类型,一种是为multipart 类型(form的enctype="multipart/form-data",可以上传文件)两种类型导致在后面服务器端接收form中的参数使用的方法各不相同,multipart类型的用getParameter()方法是没法取出提交上来的参数的,而是要得到一个Binary数组自己进行分拆才能得到。导致程序取参数的不一致性,可移植性减弱。所以使用CodeFilter对每个request进行过滤,将两种情况上传的各种参数都重新setAttribute到request中,这样就能够使后面的command类只使用getAttribute()方法就能取到实际的参数而不需要管是哪种方式提交的了,并且CodeFilter中还可以把一些字符过滤掉,比如说单引号等。

3、请求辅助类

请求辅助类(RequestHelper)主要有两个作用,一是将request,response和配置参数封装起来然后作为command的参数,使每个command都能够获取到前台请求的各种信息,第二个作用是查找command的各种配置参数信息。

四、异常处理

Java提供了两类主要的异常:runtime exception和checked exception。所有的checked exception是从java.lang.Exception类衍生出来的,而runtime exception则是从Java.lang.RuntimeException或java.lang.Error类衍生出来的。

从逻辑的角度来说,checked exceptions和runtime exception是有不同的使用目的的。checked exception用来指示一种调用方能够直接处理的异常情况。而runtime exception则用来指示一种调用方本身无法处理或恢复的程序错误。

框架定义了一个异常基类ApplicationException是属于checked exceptions类型主要有以下几个功能:

1、异常自底向上抛出统一处理,减少处理代码:

传统(过程式)方式编程,采用返回参数告诉调用的函数被调用的函数出现错误,所以在调用函数里面要加入很多的if从句来判断各个被调用函数是否正确返回值,代码充斥了大量的大括号。采用异常机制,发生异常的位置包装成自定义的应用异常然后向上抛出,上面的各层均不用处理直接抛出到统一异常处理的MainController里面即可,由统一的类来处理

2、异常统一编号,用户看到统一的处理信息:

每个异常都可以确定编号,并将编号与提示信息维护在数据库中,页面只显示统一维护的错误信息,这样就不会让错误信息编程开发人员随手写的不规范而且难以理解的信息,造成用户感觉系统难以使用。

3、异常信息记录到错误日志中方便追踪异常位置:

统一处理程序抓取异常,并将异常信息记录日志。调试阶段还可以在控制台信息中打出异常堆,方便开发人员快速定位异常发生位置。

4、根据获取异常AOP声明式事务控制进行回滚操作:

整个开发使用声明式的事务,何时回滚就取决与应用异常的抛出。

配置如下:

<property name="transactionAttributes">

<props>

<prop key="insert*">

PROPAGATION_REQUIRED,-ApplicationException

</prop>

</props>

</property>

应用异常基类如下图:

其它异常都继承于基类ApplicationException,主要包含错误编号属性(errorNumber)、异常信息(errorMessage)、异常跟踪(stackTrace)。以便实现上述功能。

五、事务的AOP处理

权限、事务、日志处理如果混杂在业务代码之中会使整个代码变得非常复杂而且可读性很差,难以修改重构。

面向对象的分析和设计引入了继承、抽象和多态等概念,由此为我们提供了降低软件复杂性的工具。但是,开发人员在软件设计过程中仍然经常会面对无法用面向对象软件开发技术轻易解决的问题。这些问题之一就是如何处理应用程序中的横切关注点(Cross-cutting concerns)。

1、横切关注点

关注点就是设计人员感兴趣的某一概念或区域。例如,在一个订货系统中,核心关注点可能是订单处理和生产,而系统关注点可能是事务处理和安全管理。

横切关注点是影响多个类或模块的关注点,即未能很好地局部化和模块化的关注点。

横切关注点的表现有:

·代码纠结—当一个模块或代码段同时管理多个关注点时发生这种情况。

·代码分散—当一个关注点分布在许多模块中并且未能很好地局部化和模块化时发生这种情况。

这些现象会从几个方面影响软件;例如,它们会导致软件难以维护和重用,并且难以编写和理解。

2、关注点的隔离

面向方面编程试图通过引入“关注点的隔离”这一概念来解决这些问题。采用这一概念,可以以一种模块化而且适当局部化的方式实现关注点。AOP解决这个问题的办法是在设计空间中增加额外一维,并且引入了一些构造,这些构造使我们能够定义横切关注点,将它们转移进新的维,并且以模块化方式将它们打 包。

声明式的AOP事务配置如下:

<bean id="nodeTarget"

class="com.daosheng.service.impl.NodeManagerImpl" singleton="true"

lazy-init="default" autowire="default" dependency-check="default">

<property name="dao">

<ref local="nodeDAO" />

</property>

</bean>

<bean id="nodeManager"

class="com.daosheng.cms.right.RightTransactionProxyFactoryBean"

singleton="true" lazy-init="default" autowire="default"

dependency-check="default">

<property name="transactionManager">

<ref local="transactionManager" />

</property>

<property name="target">

<ref local="nodeTarget" />

</property>

<property name="transactionAttributes">

<props>

<prop key="insert*">

PROPAGATION_REQUIRED,-ApplicationException

</prop>

<prop key="update*">

PROPAGATION_REQUIRED,-ApplicationException

</prop>

</props>

</property>

</bean>

其实在注册nodeManager时注册的实际类不是自己开发的NodeManagerImpl类而是注册的RightTransactionProxyFactoryBean代理工厂类。在其它类里调用的nodeManager方法实际上是先调用的此代理工厂类的加入横切点代码(事务处理代码)然后在调用的NodeManagerImpl的相应方法,这样就实现了NodeManagerImpl类里不用操心事务问题,而实际的事务由加入在切点的Advice来完成。其在真正方法执行前启动事务,执行后提交事务(commit),如果出现异常则回滚(rollback)。

代理工厂类是采用jdk4以上自带的动态代理机制实现的。

框架的AOP中提供了4种处理切入类型:around,before,after,introduction.顾名思义, 

1) around是针对具体的某个切入点的方法(比如,现在有个OrderBook方法,around的切入类型是就这个方法的内部调用,是通过java的元 数据,在运行时通过Method.invoke来调用,具有返回值,当发生意外的时候会终止.记住的一点是,返回值.);

2)before是在方法调用前调用(在OrderBook方法前调用,但是没有返回值,同时在通常意外情况下,会继续运行下一步方法.记住的一点是没有返回值);  

3)after和before刚好相反,没有什么特别的地方.

4)introduction是一个更加特殊的,但功能更加强大的切入类型.比如(你现在有Book对象,Computer对象,还有几十个这种业务对象,现在你希望在每个这样的对象中都加入一个记录最后修改的时间.但是你又不希望对每个类都进行修改,因为太麻烦了,同时更重要的一点,破坏了对象的完整性,说不定你以后又不需要这个时间数据了呢.框架AOP为了专门实现这种思想提供了一个切入处理,那就是introduction.introduction可以为动态加入某些方法,这样可以在运行时,强制转换这些对象,进行插入时间数据的动作,更深的内幕就是C++虚函数中的vtable思想).

下面时序图为事务在整个各层代码协作过程中启动和提交的过程。每个Manager或者DAO实体可能是开发的实际类也可以是代理类。

六、数据bean(DataBean)

Data Bean 在本框架中有Value Object、View Object、BO、Java Bean、POJO等多种身份和用途。DataBean是个普通的JavaBean,作为POJO通过O/R Mapping来将数据固化到关系数据库或其它存储介质中;作为Value Bean可以将数据在各层之间互相传输;作为View Object帮助JSP页面将数据传输并显示出来;作为BO与service层的Manager们分别具有客观实物类的属性部分和行为部分,这样分离可以使服务层更好的分离出来并提供基于接口定义的服务,并更好的使Manager及相关类形成独立的组件(类似Session Bean,符合fa?ade模式)。

DataBean贯穿于各个层,所以当整个项目组分配任务是以层次来划分而不是以模块划分工作的时候,DataBean就成了各开发人员定义任务,理解任务,相互沟通的关键部分,因此DataBean必须能够快速完成并更新。因此本框架采用工具自动批量生成的办法生成DataBean,为了保证其准确性,项目组内严令禁止手工写DataBean。DataBean来自于数据库模型直接生成的XML Schema,并通过Castor生成DataBean。有专门维护数据库模型的人员统一生成,即便在整个项目过程中由于客户的原因需求不断变更导致数据库设计不断变更,DataBean也能正确的快速的生成出来。

DataBean还有三个个关键的方法就是marshal()、unmarshal()和validate()。Validate()方法提供了对数据进行校验的功能,这样不仅提供了在客户端用javascript进行校验的功能,还能在服务器进行数据的校验。Marshal和unmarshal两个方法分别可以把DataBean序列化和反序列化为xml文件,实现了和xml文件的快速转换功能。生成的xml可以单独存储(除了数据库存储的另一中固化方案),也可以作为客户端浏览器xmlhttp传输的数据格式,还可以作为提供的web service的传递参数(SOA)。

七、系统初始化服务和过滤器配置

服务初始化过程

系统启动时初始化两部分,一是系统服务,二是系统过滤器,框架定义了系统服务和过滤器的接口允许后期根据实际业务实现新的服务和过滤器并配置部署到系统中。

初始化服务的配置

配置信息如下:

<system-config name="services">

<item name="condition"

value="com.daosheng.service.impl.ConditionManagerImpl" />

</system-config>

只配置了一个服务:决策树服务。

初始化过滤器的配置

配置信息如下:

<system-config name="filters">

<item name="codefilter"

value="com.daosheng.blackfin.controller.filter.CodeFilter" />

<item name="loginfilter" value="com.cqc.filter.LoginFilter" />

<item name="logfilter" value="com.cqc.filter.LogFilter" />

</system-config>

配置xml里面描述了三个过滤器分别是请求代码过滤器、登入过滤器、日志过滤器,在系统初始化这些Filter,每次执行请求时都经过这些过滤器的过滤。

 

组织简介 | 联系我们 |   Copyright 2002 ®  UML软件工程组织 京ICP备10020922号

京公海网安备110108001071号