liwanxing

前言

ActQryPreForMobile

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,指定了可以接受的请求参数及其校验方式,指定了通道及其视图。

 

<wiz_code_mirror>
 
 
 
 
 
 
 
  <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

 

<wiz_code_mirror>
 
 
 
 
 
 
 
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

 

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

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

处理multipart请求

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

 

<wiz_code_mirror>
 
 
 
 
 
 
 
MultipartFile mfile = (MultipartFile) context.getData("ContractPhoto");
 
 

国际化

Local解析

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

 

<wiz_code_mirror>
 
 
 
 
 
 
 
org.springframework.web.servlet.i18n.SessionLocaleResolver
 
 

消息

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

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

异常处理

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

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

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

 

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

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

 

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

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

 

<wiz_code_mirror>
 
 
 
 
 
 
 
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块。

 

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

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

 

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

视图

视图解析器

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

 

<wiz_code_mirror>
 
 
 
 
 
 
 
<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得到视图名来解析视图,并完成渲染:

 

<wiz_code_mirror>
 
 
 
 
 
 
 
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的结合体,既需要查找是否能够渲染视图,又需要执行渲染逻辑。

 

<wiz_code_mirror>
 
 
 
 
 
 
 
<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直接采用开源方案。

前言

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,指定了可以接受的请求参数及其校验方式,指定了通道及其视图。

  1. <transactionid="QueryList"template="queryTemplate"><!--id值即为xxx-->
  2. <actions><!--动作-->
  3. <refname="action">QueryListAction</ref>
  4. </actions>
  5. <fields><!--字段及其校验方式-->
  6. <fieldname="Seq"></field>
  7. </fields>
  8. <channels><!--通道-->
  9. <channeltype="http">
  10. <paramname="success">area/QueryList</param>
  11. </channel>
  12. <channeltype="WX">
  13. <paramname="success">json,</param>
  14. </channel>
  15. <channeltype="PMBS">
  16. <paramname="success">json,</param>
  17. </channel>
  18. </channels>
  19. </transaction>

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

multipart支持

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

Multipart解析器

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

  1. if(multipartResolver !=null&& multipartResolver.isMultipart(request)){
  2. if(request instanceofMultipartHttpServletRequest)
  3. //请求已被解析为Multipart
  4. else
  5. try{
  6. processedRequest = multipartResolver.resolveMultipart(request);
  7. }
  8. ...
  9. }

Multipart配置信息

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

  1. <multipartResolver>
  2. <paramname="defaultEncoding">GBK</param>
  3. <paramname="uploadTempDir">${uploadTempDir}</param>
  4. <paramname="maxUploadSize">${maxUploadSize}</param>
  5. </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视图

  1. if(request.getHeader("Accept").contains("application/json")){
  2. model =newHashMap();
  3. resolverRejectMessages(messageSource, request, locale, ex, context, model);
  4. request.setAttribute("_viewReferer", defaultJsonErrorView);
  5. return model;
  6. }

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

  1. if(backToInputForValidationError &&(ex instanceofValidationMessage)){
  2. //...
  3. }

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

  1. if(viewName ==null){
  2. if(context !=null){
  3. User user = context.getUser();
  4. if(user ==null|| user.isLogout())
  5. request.setAttribute("_viewReferer", defaultPublicErrorView);
  6. else
  7. request.setAttribute("_viewReferer", defaultErrorView);
  8. }else{
  9. request.setAttribute("_viewReferer", defaultErrorView);
  10. }
  11. }else{
  12. request.setAttribute("_viewReferer", viewName);
  13. }

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

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

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

  1. thrownewValidationRuntimeException(CHECKMSG.EXECUTE_SHELL_FAILED);//文件传输失败

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

  1. publicclass CHECKMSG {
  2. publicstaticfinalString VALIDAATION_CHECK_TRANSTIME="Validaation.check.TransTime";//不在当日交易时间内
  3. ...
  4. }

视图

视图解析器

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

  1. <beanid="mainViewResolver"class="com.csii.pe.channel.http.servlet.HashMapViewResolver">
  2. <mapname="mapping">
  3. <beanname="servlet"class="com.csii.pe.channel.http.servlet.UrlView">
  4. <refname="dynamicWebModuleRegistry">WebModuleRegistry</ref>
  5. <paramname="cacheSeconds">0</param>
  6. <paramname="prefix"></param>
  7. <paramname="suffix">.do</param>
  8. <paramname="localeMode">0</param>
  9. <paramname="clientType">false</param>
  10. </bean>
  11. </map>
  12. </bean>

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

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

视图

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

  1. <beanname="default"class="com.csii.pe.channel.http.servlet.UrlView">
  2. <paramname="usingDeviceClass">false</param>
  3. <refname="dynamicWebModuleRegistry">WebModuleRegistry</ref>
  4. <paramname="cacheSeconds">0</param>
  5. <paramname="prefix">/WEB-INF/</param>
  6. <paramname="suffix">.jsp</param>
  7. <paramname="forceJavaScriptDisabled">true</param>
  8. </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直接采用开源方案。

相关文章: