开源:实现MVC与流程控制的框架包LCMVC

点点圈 提交于 2020-04-06 08:51:08

    代码,是每个程序员的财富。积累代码,就是积累财富;编写高效代码,就是创造财富。

    最近在对”财富“进行整理的时候,找到了我10多年前自己写的框架包,无比怀念,处理成了maven工程,生成了javadoc,git到了开源中国的Gitee上,进行一下开源,大概讲解一下,地址:

https://gitee.com/lichongcoco/LCMVC

     故事开始大概在2008年左右,我毕业工作2年的时候,那时候开发还是用struts1,现在流行的spring还没那么火,在实际工作中基本没遇到和看到,页面还是jsp的,JDK还是1.6的,还在使用mybatis的前身ibatis。

    在空闲的时候,自己开始思考mvc的机制,struts是怎么跑起来的,感觉不是很难,自己就写了一个简易的mvc,然后逐步丰富功能,增加了事务处理,异常处理,统一所有数据,上送数据统一校验等等

    后来写的功能流程多了,就渐渐有了一些模块化开发的萌芽和想法,要是通过配置xml,不写具体代码,就能完成一个业务流程,应该也能实现,于是,加上了基于配置化的业务流转功能,理想状态下,准备好基础组件,配置好数据xml,业务流转xml,就可以不写一行代码(服务端)的跑起来

    同时增加了一些常用工具类,开发更方便。

    现在看来,就好似原始版本的springmvc,据说当时springmvc之所以被开发,就是作者觉得mvc没那么难,我当时也是这个想法,呵呵。

    但在当时,能思考这些问题,对于一个刚毕业没多久,完全没接触软件设计思想的菜鸟来说,非常难能可贵,也为我日后在开发这条路上越走越远打下了很好的基础。

    下边详细介绍一下这个框架:

目录

LCMVC框架介绍

开发指南

LCMVC框架介绍

  • 框架特点:
  1. 体积小,功能全。
  2. 实现mvc基本功能与xml流程翻转控制,整合所有数据项。
  3. 包含常用工具方法,包装hibernate各项方法。
  • 核心:

        框架核心内容是实现了所有数据项的整合与逻辑流程的xml翻转控制,管理更加方便。

  • 数据整合:

        接口Context是数据整合的核心接口,ContextImp实现了该接口,该类将数据统计进行管理,使用嵌套的ConcurrentHashMap来存储数据,这是一个线程安全的HashMap。

        所谓嵌套存储是指在getput时,使用“XXXXX.XXXX.XXXX”来存储数据,如:

context.put(“aaaa.bbb.ccc.ddd”,”aaaa”);

context.get(“aaaa.bbb.ccc.ddd”);

        这时,程序会建立keyaaaaConcurrentHashMapvalue为一个ConcurrentHashMap,这个ConcurrentHashMapkeybbbbvalueConcurrentHashMap,以此类推,支持无线扩展,这样就将数据统一存储,而作为HashMapget方法取时是非常快的。

        代码将applicationsessionrequest数据都进行了统一的封装,其本质则是将数据封装后继续存储在applicationsessionrequest中,使用context进行存储,这样所有数据都进行了统一的管理。

        具体接口说明如下:

//核心get方法,用法key="xxxxxx.xxxxx.xxxxx"
public V get(Object key);

//核心put方法,用法key="xxxxxx.xxxxx.xxxxx",当第一个XXXXXX为“session”时,则自动将数据存储到session中,当第一为“application”时,则数据存储在application中,第一个为“request”时,则存储在request中,如没有,则存储在自身。
public V put(Object key, Object value);
	
//取得原始session对象
public HttpSession getOriginalHttpSession();
	
//对session数据进行了封装,其本质为get("session." + key)
public V getSession(Object key);
	
//对session数据进行了封装,其本质为put("session." + key, value);
public V putSession(Object key, Object value);
	
//对application数据进行了封装,其本质为get("application." + key);
public V getApplication(Object key);
	
//取得系统启动时从XXXX.properties文件中取得的参数,其本质为get("application.config." + key);
public V getConfig(Object key);
	
//取得页面上送数据,其本质为从request中取得数据,然后按照数据字典新型和取值范围进行验证,并过滤有害字符串,get("request.submit." + key);
public V getSubmit(Object key);
	
//流程内部使用,当有request时,则存储在request中,没有就存储在自身,其本质为:put("request.flow." + key, value);或put(key, value);
public V putFlow(Object key, Object value);
	
//流程内部使用,当有request时,则从request中取值,没有就从自身取值,其本质为:get("request.flow." + key);或get(key);
public V getFlow(Object key);
	
//一次性打印所有context管理的request数据内容。
public void toStringRequest();//默认日志级别info
	
//一次性打印所有context管理的自身数据内容。
public void toStringSelf();//默认日志级别info

//一次性打印所有context管理的application数据内容。
public void toStringApplication();//默认日志级别info

//一次性打印所有context管理的session数据内容。
public void toStringSession();//默认日志级别info

//将context管理的所有request,session,application,自身等数据进行打印。
public String toString();//默认日志级别info
	
//指定日志级别
public String toStringRequest(int logType);

//指定日志级别
public String toStringSelf(int logType);

//指定日志级别
public String toStringApplication(int logType);

//指定日志级别
public String toStringSession(int logType);

//指定日志级别
public String toString(int logType);

    使用时,需要在工程启动时进行application初始化:

contextImp.init(getServletContext());
  • 数据字典:

         数据字典是指用户提交数据项的定义文件,该文件定义了上送合法的数据项与该数据项类型,大小范围,默认值等信息。

         使用数据字典可将一些验证方法与安全控制方法放到底层实现,从getSubmit方法去除后的数据都是非null的安全数据,因为系统会将null或有害字符串进行过滤。

        在系统启动项配置context后,系统从数据字典dataDict.xml加载数据,代码会将加载数据字典内容到context中。

        使用FlowReadXml.formatDataDictXml();来加载数据字典,该方法可指定文件名来进行多文件的加载,如FlowReadXml.formatDataDictXml(ddd.xml);如不指定文件名,则加载默认文件dataDict.xml

        数据字典格式如下:

<request_submit id="year">

       <data name="year" type="int" value="0" length="-1~9999" />

</request_submit>

        其中request_submit标签的id不可重复,data标签的name为上送数据项名称,type为该数据项类型,类型包括:stringintdouble,三种。Value为默认值,即当null时,类型不合法时,超长或超短时使用的数值。Length为范围,当类型为string时,这里标示stringlength长度,当为intdouble时,这里标示数值长度。

         request_submit标签可包含其他标签,使用方法:

<include id="username" />

         该方法可省去重复定义的麻烦,但只能包含一层,如include id里还有include,则不包含。

  • 翻转控制流程:

         框架包的另一个核心就是流程的翻转控制。

         系统在启动时,加载流程配置xml文件:

FlowReadXml.formatFlowXml("flow_noSession.xml");

FlowReadXml.formatFlowXml("flow_session.xml");

FlowReadXml.formatFlowXml();

        该方法可指定文件名进行多文件加载,也可以不指定文件名,加载默认文件flow.xml。

        文件中指定流程的流转信息:

<flow id="index" name="公共流程" dataId="index">

    <action id="StartAction" implClass="LC.atcion.LCAction.startAction"   label="开始">

           <transition dest="indexOne0" condition="0"/>

    </action>

    <action id="indexOne0" implClass="com.action.indexOne" label="步骤一">

           <transition dest="indexThree0" condition="1"/>

           <transition dest="indexTwo0" condition="0"/>

    </action>

    <action id="indexTwo0" implClass="com.action.indexTwo" label="步骤二">

           <transition dest="indexThree0" condition="0"/>

    </action>

    <action id="indexThree0" implClass="com.action.indexThree" label="步骤三">

           <transition dest="endAction0" condition="0"/>

    </action>

    <action id="endAction0" implClass="LC.atcion.LCAction.endAction" label="成功结束" jsp="index.jsp" />

</flow>

         Flow标签的id为唯一idname用于说明,在日志中打印,dataId是这个流程指定的数据字典,表明该流程只接受这个数据字典定义的数据项。

         Action标签里的id为这个flow里唯一,流程开始默认用StartActionimplclass为该action的执行类,lable为说明,打印日志显示。

         每个action下包含一个或多个transition标签,该标签指定此action返回信息后,流转到下一个actionidDest为下一个action idcondition为本action的返回值。

         Flow以流转到LC.atcion.LCAction.endAction为结束标志。此时,有jsp设定,标示跳转到什么页面。

         Flow标签内也可以使用include标签,用于引入其他流程:

    <flow id="update_one_productInfo" name="更新productInfo" dataId="productInfo">

       <include id="update_one_message" />

    </flow>
  • 默认mvc

         框架有默认的mvc,工程在启动时在web.xml中配置即可

 <servlet>

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

    <servlet-class>LC.action.Servlet.DefaultServlet</servlet-class>

    <load-on-startup>0</load-on-startup>

    <init-param>

    <param-name>errorPage</param-name>

    <param-value>system_error_500.html</param-value>

    </init-param>

  </servlet>

  <servlet-mapping>

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

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

  </servlet-mapping>

         mvc设置了流程的加载与执行,返回控制等,程序员可直接使用,然后页面通过制定流程即可实现mvc功能。

         mvc中需设置errorPage参数,该参数设定了流程错误时跳转的错误页面。

指定流程有两种方法:

         一:使用flowId传递,如:

xxxx.do?flowId=index&year=<%=year %>&month=<%=month %>

         二:直接使用流程id作为.do 如:

index.do? year=<%=year %>&month=<%=month %>

        

        如同时使用两种方法,则优先使用flowId传值。

         flowId不用在数据字典中指定。

指定流程结束后跳转页面也有两种方法:

         一:直接在连接中写, 如:

xxxx.do?flowId=noFlow&jsp=user_inout_day_main&year="+year+"&month="+month+"&day="+day

        此时不用写后缀.jsp,系统会自动添加,这种方式不提倡使用。

        其中的noFlow也是一个flow,但没有逻辑,开始即结束,可用户登录后的页面跳转安全检查,此时jsp可不写后缀。

         二:在flow中配置,见“翻转控制流程”配置。

         在框架包中,初始化编码了三个action,分别是StartActionNoSubmitTimeStartActionEndAction

         其中StartAction中包含了防止重复提交内容,初步防止用户在0.5秒内重复提交。

         NoSubmitTimeStartAction则无此设置,是为页面跳转准备的。

         EndAction则是标准结束流程。

  • 工程参数初始化

        项目启动时,可指定properties文件,来加载系统参数:

ReadConfig.config(context, "app");

        App为文件名。参数加载后,可用getConfig方法来进行调用。

  • 数据库封装与事物处理

        框架包对hibernate进行了初步封装,分为两个基类Dao,分别是BaseDaoBaseTransDao

        其中,BaseDao是独立事物,而BaseTransDao则是统一事务,即一个完整流程中采用一个事务,如发现错误则全部进行回滚,在流程中这两个基类可混用,不产生影响。

        如在流程中,需要部分提交,可从context中取得事物,进行提交,如:

LCFlow.commitTx(context);

        PageInfo类则是对翻页的数据项进行了封装,使用时参考样例。

  • 日志打印

        框架对日志进行了简单封装,增加了唯一线程号,使用Log4jUtil.log来进行打印,同时,也有Log4jUtil.logNoId,这标示没有唯一线程号的日志。

        使用log打印时,当日志级别为errorFATAL时,输出会包括当前session数据,request数据和自身数据。

  • 其他封装

        框架对hibernate,进行了封装,可直接将hqlsql传入进行查询。

        详见API文档。

 


开发指南

  • 环境

        Jdk1.6

        Maven项目,具体包引入见工程pom.xml

  • 配置web.xml
  <filter>

       <filter-name>encoding</filter-name>

       <filter-class>LC.util.String.CharsetFilter</filter-class>

    </filter>

  <filter-mapping>

     <filter-name>encoding</filter-name>

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

  </filter-mapping>

用框架包内的编码转换器来统一字符编码。

 <servlet>

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

    <servlet-class>LC.action.Servlet.DefaultServlet</servlet-class>

    <load-on-startup>0</load-on-startup>

    <init-param>

        <param-name>errorPage</param-name>

        <param-value>system_error_500.html</param-value>

    </init-param>

  </servlet>

 <servlet-mapping>

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

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

  </servlet-mapping>

引入默认mvc管理,并指定系统级错误页面。

  <servlet>

      <servlet-name>initServlet</servlet-name>

      <servlet-class>In.Out.base.InitServlet</servlet-class>

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

  </servlet>

指定启动时初始化类

 

  • 初始化类
public class InitServlet extends HttpServlet{

    private static final long serialVersionUID = -5652071148837106939L;

    @SuppressWarnings("rawtypes")

    public void init()  throws ServletException {

       //初始化application

       ContextImp.init(getServletContext());

       //初始化公共数据

       try {

           //读取数据字典

           FlowReadXml.formatDataDictXml();

           Log4jUtil.logNoId(this.getClass(),Log4jUtil.INFO,"初始化数据字典成功");

           //读取flow

           FlowReadXml.formatFlowXml("flow_noSession.xml");

           FlowReadXml.formatFlowXml("flow_session.xml");

           Log4jUtil.logNoId(this.getClass(),Log4jUtil.INFO,"初始化公共流程成功");

           //初始化日志线程号

           ContextImp context = new ContextImp();

           int logId = 0;

           context.put("application.count.thread", logId);

           //读取初始化配置文件

           ReadConfig.config(context, "app");

           Log4jUtil.logNoId(this.getClass(),Log4jUtil.INFO,"初始化系统参数成功");

       } catch (Exception e) {

           Log4jUtil.logNoId(this.getClass(),Log4jUtil.ERROR,"初始化公共参数错误");

           e.printStackTrace();

       }

      

       //定时任务开始每天1点开始

       Calendar calendar = Calendar.getInstance();

       calendar.set(Calendar.HOUR_OF_DAY, 1);

       calendar.set(Calendar.MINUTE, 0);

       calendar.set(Calendar.SECOND, 0);

       new Timer().schedule(new InOutTimer(), calendar.getTime() , 24 * 60 * 60 * 1000);

    }
  • 定义数据字典
<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE context SYSTEM "dataDict.dtd">

<context>

    <request_submit id="selectOne">

       <include id="flowType" />

       <data name="id" type="int" value="0" length="-100~9999999" />

    </request_submit>

    <request_submit id="flowType">

       <data name="selectType" type="String" value="product" length="0~20" />

    </request_submit>

    <request_submit id="message">

       <include id="selectOne" />

       <include id="flowType" />

       <data name="content" type="String" value="" length="0~2147483647" />

       <data name="description" type="String" value="" length="0~400" />

       <data name="fileName" type="String" value="" length="0~50" />

       <data name="key" type="String" value="" length="0~400" />

       <data name="title" type="String" value="" length="0~100" />

    </request_submit>

    <request_submit id="product">

       <include id="selectOne" />

       <include id="message" />

       <include id="flowType" />

       <data name="typeClass" type="String" value="" length="0~5" />

       <data name="typeCompany" type="String" value="" length="0~5" />

       <data name="proIndex" type="String" value="" length="0~50" />

    </request_submit>

    <request_submit id="parameter">

       <include id="selectOne" />

       <include id="flowType" />

       <data name="about" type="String" value="" length="0~2147483647" />

       <data name="name" type="String" value="" length="0~100" />

       <data name="type" type="String" value="" length="0~100" />

       <data name="value" type="String" value="" length="0~100" />

    </request_submit>

    <request_submit id="productInfo">

       <include id="selectOne" />

       <include id="flowType" />

       <data name="index" type="int" value="0" length="-100~9999999" />

       <data name="pioId" type="int" value="0" length="-100~9999999" />

       <data name="type" type="String" value="" length="0~50" />

       <data name="content" type="String" value="" length="0~2147483647" />

    </request_submit>

    <request_submit id="indexProduct">

       <include id="selectOne" />

       <include id="flowType" />

       <data name="type" type="String" value="" length="0~50" />

       <data name="proIndex" type="String" value="" length="0~50" />

    </request_submit>

</context>

 

  • 定义流程
<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE context SYSTEM "flow.dtd">

<context>

    <flow id="login" name="用户登录" dataId="login">

       <action id="StartAction" implClass="LC.action.LCAction.StartAction"   label="开始">

           <transition dest="LoginAction0" condition="0"/>

       </action>

       <action id="LoginAction0" implClass="In.Out.action.LoginAction" label="登录验证">

           <transition dest="IndexAction0" condition="0"/>

           <transition dest="endAction1" condition="1"/>

       </action>

       <action id="IndexAction0" implClass="In.Out.action.IndexAction" label="首页准备">

           <transition dest="endAction0" condition="0"/>

       </action>

       <action id="endAction0" implClass="LC.action.LCAction.EndAction" label="成功结束" jsp="user_inout_month.jsp" />

       <action id="endAction1" implClass="LC.action.LCAction.EndAction" label="失败结束" jsp="login.jsp" />

    </flow>

   

    <flow id="reg" name="用户注册" dataId="reg">

       <action id="StartAction" implClass="LC.action.LCAction.StartAction"   label="开始">

           <transition dest="RegAction0" condition="0"/>

       </action>

       <action id="RegAction0" implClass="In.Out.action.RegAction" label="用户注册">

           <transition dest="endAction0" condition="0"/>

       </action>

       <action id="endAction0" implClass="LC.action.LCAction.EndAction" label="成功结束" jsp="reg.jsp" />

    </flow>

   

    <flow id="getPass" name="找回密码" dataId="getPass">

       <action id="StartAction" implClass="LC.action.LCAction.StartAction"   label="开始">

           <transition dest="GetpassAction0" condition="0"/>

       </action>

       <action id="GetpassAction0" implClass="In.Out.action.GetpassAction" label="找回密码">

           <transition dest="endAction0" condition="0"/>

           <transition dest="endAction1" condition="1"/>

           <transition dest="endAction2" condition="2"/>

       </action>

       <action id="endAction0" implClass="LC.action.LCAction.EndAction" label="失败结束" jsp="getPassword1.jsp" />

       <action id="endAction1" implClass="LC.action.LCAction.EndAction" label="成功结束" jsp="getPassword2.jsp" />

       <action id="endAction2" implClass="LC.action.LCAction.EndAction" label="成功结束" jsp="getPassword3.jsp" />

    </flow>

   

    <flow id="noFlowNosession" name="空流程" dataId="noFlow">

       <action id="StartAction" implClass="LC.action.LCAction.StartAction"   label="开始">

           <transition dest="endAction0" condition="0"/>

       </action>

       <action id="endAction0" implClass="LC.action.LCAction.EndAction" label="成功结束" />

    </flow>

</context>

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

<!DOCTYPE context SYSTEM "flow.dtd">

<context>

    <flow id="index" name="进入首页" dataId="index">

       <action id="StartAction" implClass="In.Out.action.AuthorityStartAction"   label="开始(安全验证)">

           <transition dest="IndexAction0" condition="0"/>

           <transition dest="endAction1" condition="1"/>

       </action>

       <action id="IndexAction0" implClass="In.Out.action.IndexAction" label="首页准备">

           <transition dest="endAction0" condition="0"/>

       </action>

       <action id="endAction0" implClass="LC.action.LCAction.EndAction" label="成功结束" jsp="user_inout_month.jsp" />

       <action id="endAction1" implClass="LC.action.LCAction.EndAction" label="失败结束" jsp="login.jsp" />

    </flow>

</context>

此时重写了StartAction里的expand方法,用于安全验证

/**

 * 重写开始类,用户权限验证

 * @author lichong

 *

 */

public class AuthorityNoSubmitTimeStartAction extends NoSubmitTimeStartAction {

   

    @SuppressWarnings("rawtypes")

    public void expand(Context context)throws LCException{

       //登录验证

       try{

           InOutUser InOutUser = (InOutUser)context.getSession("user");

           if (InOutUser.getId()==null) {

              context.putFlow("error", "您还没有登陆!");

              throw new LCException("您还没有登陆!");

           }

       } catch (Exception e) {

           context.putFlow("error", "您还没有登陆!");

           throw new LCException("您还没有登陆!");

       }

    }

}

  • 开发action
/**

 * 注册模块

 * @author lichong

 *

 */

public class RegAction implements Action{


    @SuppressWarnings("rawtypes")

    public String excent(Context context) throws LCException{


       UserDao UserDao = new UserDao();

       QuartzDao QuartzDao = new QuartzDao();


       String username = (String)context.getSubmit("username");

       String password = (String)context.getSubmit("password");

       String about = (String)context.getSubmit("about");

       String inOutClass = (String)context.getSubmit("inOutClass");

       String name = (String)context.getSubmit("name");

       String inOutType = (String)context.getSubmit("inOutType");

      
       context.putFlow("username", username);

       UserDao.selectOnebyUsername(context);

       InOutUser InOutUserT = (InOutUser)context.getFlow("InOutUser.selectOne");

       if(InOutUserT.getId()!=null || username(username)){

           context.putFlow("error", "您输入的用户名不合法或已被占用,请重新输入");

           return "0";

       }

       if(name.equals("") || about.equals("")){

           context.putFlow("error", "请输入密码找回问题及答案");

           return "0";

       }

       if(!context.getSession("image.2.verifyCode").equals(inOutClass)){

           context.putFlow("error", "验证码输入错误,请重新输入");

           return "0";

       }
      

       String Md5Password = new MD5().getMD5ofStr(password);

       InOutUser InOutUser = new InOutUser();

      

       InOutUser.setAllLogin(Long.valueOf("0"));

       InOutUser.setPassword(Md5Password);

       InOutUser.setUseranswer(about);

       InOutUser.setUserask(name);

       InOutUser.setUsername(username);

       InOutUser.setUsertype("user");

       InOutUser.setSex(inOutType);

       InOutUser.setUserMonth(0);

       InOutUser.setSftv(((String)context.getConfig("LunarCalendar")).replace(",", "\n"));

       InOutUser.setLftv(((String)context.getConfig("LunarCalendar2")).replace(",", "\n"));

       context.putFlow("InOutUser.selectOne", InOutUser);

       UserDao.insertOne(context);


       InOutQuartz InOutQuartz = new InOutQuartz();

       InOutQuartz.setBegindate(new Date());

       InOutQuartz.setType("tongji");

       InOutQuartz.setAbout(InOutUser.getId().toString());

       InOutQuartz.setTypetype("userReg");

       context.putFlow("InOutQuartz.selectOne", InOutQuartz);

       QuartzDao.insertOne(context);


       context.putFlow("error", "注册成功!请登陆");

       return "0";

    }

 

  • 页面编码
<%@ page language="java" pageEncoding="UTF-8"%>

<%@ page import="LC.action.Code.ContextImp" %>

<%@ page import="In.Out.bean.UserDateBean"%>

<%@ page import="java.util.List"%>

<%

ContextImp context = (ContextImp)request.getAttribute("context");

List<UserDateBean> sFlv = (List<UserDateBean>)context.getFlow("sFlv");

List<UserDateBean> lFlv = (List<UserDateBean>)context.getFlow("lFlv");

String error = (String)context.getFlow("error");

%>

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!