代码,是每个程序员的财富。积累代码,就是积累财富;编写高效代码,就是创造财富。
最近在对”财富“进行整理的时候,找到了我10多年前自己写的框架包,无比怀念,处理成了maven工程,生成了javadoc,git到了开源中国的Gitee上,进行一下开源,大概讲解一下,地址:
故事开始大概在2008年左右,我毕业工作2年的时候,那时候开发还是用struts1,现在流行的spring还没那么火,在实际工作中基本没遇到和看到,页面还是jsp的,JDK还是1.6的,还在使用mybatis的前身ibatis。
在空闲的时候,自己开始思考mvc的机制,struts是怎么跑起来的,感觉不是很难,自己就写了一个简易的mvc,然后逐步丰富功能,增加了事务处理,异常处理,统一所有数据,上送数据统一校验等等。
后来写的功能流程多了,就渐渐有了一些模块化开发的萌芽和想法,要是通过配置xml,不写具体代码,就能完成一个业务流程,应该也能实现,于是,加上了基于配置化的业务流转功能,理想状态下,准备好基础组件,配置好数据xml,业务流转xml,就可以不写一行代码(服务端)的跑起来。
同时增加了一些常用工具类,开发更方便。
现在看来,就好似原始版本的springmvc,据说当时springmvc之所以被开发,就是作者觉得mvc没那么难,我当时也是这个想法,呵呵。
但在当时,能思考这些问题,对于一个刚毕业没多久,完全没接触软件设计思想的菜鸟来说,非常难能可贵,也为我日后在开发这条路上越走越远打下了很好的基础。
下边详细介绍一下这个框架:
目录
LCMVC框架介绍
- 框架特点:
- 体积小,功能全。
- 实现mvc基本功能与xml流程翻转控制,整合所有数据项。
- 包含常用工具方法,包装hibernate各项方法。
- 核心:
框架核心内容是实现了所有数据项的整合与逻辑流程的xml翻转控制,管理更加方便。
- 数据整合:
接口Context是数据整合的核心接口,ContextImp实现了该接口,该类将数据统计进行管理,使用嵌套的ConcurrentHashMap来存储数据,这是一个线程安全的HashMap。
所谓嵌套存储是指在get与put时,使用“XXXXX.XXXX.XXXX”来存储数据,如:
context.put(“aaaa.bbb.ccc.ddd”,”aaaa”);
context.get(“aaaa.bbb.ccc.ddd”);
这时,程序会建立key为aaaa的ConcurrentHashMap,value为一个ConcurrentHashMap,这个ConcurrentHashMap的key为bbbb,value为ConcurrentHashMap,以此类推,支持无线扩展,这样就将数据统一存储,而作为HashMap,get方法取时是非常快的。
代码将application,session,request数据都进行了统一的封装,其本质则是将数据封装后继续存储在application,session,request中,使用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为该数据项类型,类型包括:string,int,double,三种。Value为默认值,即当null时,类型不合法时,超长或超短时使用的数值。Length为范围,当类型为string时,这里标示string的length长度,当为int或double时,这里标示数值长度。
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为唯一id,name用于说明,在日志中打印,dataId是这个流程指定的数据字典,表明该流程只接受这个数据字典定义的数据项。
Action标签里的id为这个flow里唯一,流程开始默认用StartAction,implclass为该action的执行类,lable为说明,打印日志显示。
每个action下包含一个或多个transition标签,该标签指定此action返回信息后,流转到下一个action的id。Dest为下一个action 的id,condition为本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,分别是StartAction,NoSubmitTimeStartAction,EndAction。
其中StartAction中包含了防止重复提交内容,初步防止用户在0.5秒内重复提交。
NoSubmitTimeStartAction则无此设置,是为页面跳转准备的。
EndAction则是标准结束流程。
- 工程参数初始化
项目启动时,可指定properties文件,来加载系统参数:
ReadConfig.config(context, "app");
App为文件名。参数加载后,可用getConfig方法来进行调用。
- 数据库封装与事物处理
框架包对hibernate进行了初步封装,分为两个基类Dao,分别是BaseDao与BaseTransDao。
其中,BaseDao是独立事物,而BaseTransDao则是统一事务,即一个完整流程中采用一个事务,如发现错误则全部进行回滚,在流程中这两个基类可混用,不产生影响。
如在流程中,需要部分提交,可从context中取得事物,进行提交,如:
LCFlow.commitTx(context);
PageInfo类则是对翻页的数据项进行了封装,使用时参考样例。
- 日志打印
框架对日志进行了简单封装,增加了唯一线程号,使用Log4jUtil.log来进行打印,同时,也有Log4jUtil.logNoId,这标示没有唯一线程号的日志。
使用log打印时,当日志级别为error或FATAL时,输出会包括当前session数据,request数据和自身数据。
- 其他封装
框架对hibernate,进行了封装,可直接将hql或sql传入进行查询。
详见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");
%>
来源:oschina
链接:https://my.oschina.net/lichongcoco/blog/3216342