PE框架源码分析(2): mvc

前言

PE拥有自己的web框架,下面具体分析PE-MVC。

基本流程

请求从JSP/Servlet容器出发到前端业务逻辑处理的流程如下: 
MainServlet–>MainController–>CoreController–>Chain–>Template–>Action

MainServlet

MainServlet是通过MainServletRegistry类来完成注册的,MainServletRegistry主要做了以下两个工作:

  1. 注册MainServlet,映射到“/”,这样它会作为默认的servlet。
  2. 注册静态资源映射。

MainServlet组合了以下对象:

  • multipartResolver multipart解析器
  • localeResolver locale解析器
  • viewResolver 视图解析器
  • mainController 主控制器

MainServlet的主要工作是:

  • 将请求委托给mainController处理
  • 解析视图,由视图对象完成视图的渲染和返回。

MainController

MainController做的是HTTP Adapter工作,将HTTP请求转换为与请求来源无关的context,其组合了以下对象:

  • idResolver transactionId解析器
  • contextResolver 上下文解析器
  • exceptionHandler 异常处理
  • coreController 逻辑处理,责任链入口

MainController的主要工作是:

  • 将HTTP请求解析为Context
  • 委托coreController完成逻辑处理
  • 返回model数据
  • 若发生异常委托exceptionHandler处理

从Servlet容器到这里就基本是PE的MVC部分。与SpringMVC类比,MainServlet可以看做DispatchServlet,MainController可以看作映射到/*.do的Controller。

功能详解

Controller

PE-MVC中的“Controller”(指MVC中的Controller)并没有SpringMVC中那样的灵活,PE-MVC中所有请求都是以xxx.do的末尾路径,而xxx表示一个transaction(交易),交易的最终执行逻辑单元即为action(动作),通常一个交易映射一个动作。因此可以将action类比SpringMVC中的Controller。

一个常见的交易配置信息如下,PE-MVC通过transaction指定了请求路径中的xxx,指定了action,指定了可以接受的请求参数及其校验方式,指定了通道及其视图。

<transaction id="QueryList" template="queryTemplate"><!--id值即为xxx-->
<actions><!--动作-->
<ref name="action">QueryListAction</ref>
</actions>
<fields><!--字段及其校验方式-->
<field name="Seq"></field>
</fields>
<channels><!--通道-->
<channel type="http">
<param name="success">area/QueryList</param>
</channel>
<channel type="WX">
<param name="success">json,</param>
</channel>
<channel type="PMBS">
<param name="success">json,</param>
</channel>
</channels>
</transaction>

可以看到,这是一种格式很固定的交易流风格,后面将对transaction进行分析。

multipart支持

multipart形式的数据,指的就是用户的上传行为。

Multipart解析器

MainServlet将解析multipart请求数据的任务委托给Spring的MultipartResolver接口的实现,将请求转换为MultipartHttpServletRequest

if(multipartResolver != null && multipartResolver.isMultipart(request)){
if(request instanceof MultipartHttpServletRequest)
//请求已被解析为Multipart
else
try{
processedRequest = multipartResolver.resolveMultipart(request);
}
...
}

Multipart配置信息

PE框架中使用的Servlet版本很古老,自然是不支持Servlet3.0对multipart的支持。 
由于采取自定义DTD,单纯从配置信息无法看到其实现类,实际上其采用的是CommonsMultipartResolver

<multipartResolver>
<param name="defaultEncoding">GBK</param>
<param name="uploadTempDir">${uploadTempDir}</param>
<param name="maxUploadSize">${maxUploadSize}</param>
</multipartResolver>

通过自定义dtd,对框架的重要bean采用自定义标签而非通用的Spring标签。略去实现类等关键信息,只留下支离破碎的没有文档解释的配置信息,这是一种很糟糕的封装方式。

处理multipart请求

在业务逻辑代码中,只需要使用multipart的抽象即可,很简单:

  1. MultipartFile mfile = (MultipartFile) context.getData("ContractPhoto");

国际化

Local解析

在MainServlet中,通过名为_locale的请求参数提取local信息,然后获取Locale对象,并将其存储于session中。 
其解析器为:

  1. org.springframework.web.servlet.i18n.SessionLocaleResolver

消息

PE采用的是Spring的MessageSource接口。信息主要分为以下几个大类,一般配合自定义JSP标签使用:

  • checkmsg 用于自定义错误信息
  • consmsg 用于前端字段自定义显示,如下拉列表选项
  • dictionary 字典,用于校验错误的信息展示

异常处理

在web开发中,如何将请求处理过程中抛出的异常加以区分,并合理的返回给客户端是很重要的。这有着两方面的作用:

  • 提升用户友好度,合理的错误展示让用户能清晰理解
  • 系统安全,防止内部错误泄露

在PE框架的ExceptionHandler类中,异常的处理方式为: 
1.请求返回类型为json,解析异常消息,返回json视图

if(request.getHeader("Accept").contains("application/json")){
model = new HashMap();
resolverRejectMessages(messageSource, request, locale, ex, context, model);
request.setAttribute("_viewReferer", defaultJsonErrorView);
return model;
}

2.异常为验证错误,解析异常信息,返回提交视图

if(backToInputForValidationError && (ex instanceof ValidationMessage)){
//...
}

3.解析异常信息,存在提交页则返回提交页,否则返回公共错误视图

if(viewName == null){
if(context != null){
User user = context.getUser();
if(user == null || user.isLogout())
request.setAttribute("_viewReferer", defaultPublicErrorView);
else
request.setAttribute("_viewReferer", defaultErrorView);
} else{
request.setAttribute("_viewReferer", defaultErrorView);
}
} else{
request.setAttribute("_viewReferer", viewName);
}

无论怎么处理,都需要对异常内容进行解析,通过message的映射,实现国际化以及良好的展示。这里有两个点:

  • messageKey的提取,通过特定接口MessageablegetMessageKey()或Exception的getMessage()来获取
  • placeholder的填充

在业务逻辑代码中,如果验证性错误(前端提交数据有误)或需要向前端反馈的错误,可以使用ValidationRuntimeException来承载可解析异常,而且因为是RuntimeException,也不需要一层层的try-catch块。

throw new ValidationRuntimeException(CHECKMSG.EXECUTE_SHELL_FAILED);//文件传输失败

java代码中的messageKey可以通过一个工具类CHECKMSG来维护。

public class CHECKMSG {
public static final String VALIDAATION_CHECK_TRANSTIME="Validaation.check.TransTime"; //不在当日交易时间内
...
}

视图

视图解析器

PE中的ViewResolver类比SpringMVC中的ViewResolvers,本质是一个map:

<bean id="mainViewResolver" class="com.csii.pe.channel.http.servlet.HashMapViewResolver">
<map name="mapping">
<bean name="servlet" class="com.csii.pe.channel.http.servlet.UrlView">
<ref name="dynamicWebModuleRegistry">WebModuleRegistry</ref>
<param name="cacheSeconds">0</param>
<param name="prefix"></param>
<param name="suffix">.do</param>
<param name="localeMode">0</param>
<param name="clientType">false</param>
</bean>
</map>
</bean>

MainServlet中根据model属性_viewReferer得到视图名来解析视图,并完成渲染:

void render(Object model, HttpServletRequest request, HttpServletResponse response, Locale locale){
String viewName = (String)request.getAttribute("_viewReferer");
String splittedViewName[] = resolverViewResolverName(viewName);
CsiiView view = viewResolver.resolveView(splittedViewName[0]);
if(view != null)
view.render(splittedViewName[1], model, locale, request, response);
}

视图

PE中的View类比SpringMVC中的ViewResolver与View的结合体,既需要查找是否能够渲染视图,又需要执行渲染逻辑。

<bean name="default" class="com.csii.pe.channel.http.servlet.UrlView">
<param name="usingDeviceClass">false</param>
<ref name="dynamicWebModuleRegistry">WebModuleRegistry</ref>
<param name="cacheSeconds">0</param>
<param name="prefix">/WEB-INF/</param>
<param name="suffix">.jsp</param>
<param name="forceJavaScriptDisabled">true</param>
</bean>

针对JSP、下载、重定向、json、文件(pdf、xls)等等都提供了视图,但由于糅合了查找和渲染的功能,因此总体逻辑较乱。

JSP标签

PE的主要采用JSP视图,因此提供了许多自定义标签,由于文档不全,曾经我花了一段时间去分析标签的功能及实现,后来发现根本没有意义。JSP提供的标签技术简单来说三类:老的旧接口、简化的新接口、标签文件,PE采用的是简化的新接口。 
总的来说,JSP+标签的模板技术易用性相比Velocity,Thymeleaf等现代模板技术来说,还有很大不足。

REST支持与内容协商

PE-MVC对REST几乎没有任何支持,除了可以简单的生成JSON格式视图外。PE-MVC将JSON视图与请求渠道(手机银行,微信,PMBS)进行粗糙的绑定,通过名为_ChannleId的请求属性来确定请求所属来源。

在PE-MVC中所有请求都是以交易的方式存在,通常无论是browser还是phone的请求都路由到同一个交易action中,导致JSON视图与html视图共享的相同的model数据,只是表现形式不同。换种话说,就是PE-MVC对json视图的支持是简单的把html视图的model进行json序列化而已,是很粗糙的方式。从最佳实践来讲,提供独立的REST api很有必要,因为它们需要的数据不同。

总结

PE-MVC是为PE框架后续流程服务的,是browser,mobile client,wx gateway的访问入口。由于历史久远,同时没有本质的更新迭代,因此相比文档完整、功能完全、注解驱动的SpringMVC,SpringBoot等开源框架有着很大的不足。比如视图仅支持jsp,json,vx而不支持许多开源模板如FreeMarker、Velocity等,异常处理不支持HTTP状态码映射,不支持Restful,不支持路径参数…代码质量也一般,抽象和可扩展性不足,组件有的采用配置而有的采用硬编码,封装层次过深… 
对于新手而言,我不建议在pe-mvc框架上花太多时间。而建议深入学习SpringMVC,学习WEB MVC中的核心思想,这是PE-MVC提供不了的。 
PE-MVC是10多年前为银行量身打造的业务框架,我不认为这样一个业务型框架能像SpringMVC一样通用,我曾经在其上开发P2P,也听闻前同事在其上开发商城,我认为都是不合适的:功能不全、效率低下。非网银产品,我建议丢弃PE-MVC直接采用开源方案。

猜你喜欢

转载自www.cnblogs.com/liwanxing/p/9389512.html
今日推荐