我们的web框架是在tomcat环境上开发的,也是基于前面所述的消息对象编程框架或模板。整个应用模式与前面介绍的普通环境下的一样,所以理解了前面的案例,那么也很容易理解这个了。cn.tianlong.java包下的servletutils 为web框架,servletdemo为测试代码。配置在WEB-INF/conf 下。
框架流程:
下面我们看过滤器的配置和代码。
首先在tamcat的web站点配置文件web.xml文件中配置框架过滤器:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<filter>
<filter-name>tlfilter</filter-name>
<filter-class>cn.tianlong.java.servletutils.TLFilterWithSingleFactory</filter-class>
<init-param>
<param-name>configPath</param-name>
<param-value>conf</param-value>
</init-param>
<init-param>
<param-name>configFile</param-name>
<param-value>moduleFactory_config.xml</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>tlfilter</filter-name>
<url-pattern>/tlobject/*</url-pattern>
</filter-mapping>
</web-app>
根据文件配置,对于web站点下/tlobject/* 所有访问通过TLFilterWithSingleFactory过滤处理。过滤框架目录/tlobject 可根据自己情况定义。参数configPath定义配置文件目录(相对于WEB_INF下),默认模块配置都在该目录下。看过滤器代码:
public class TLFilterWithSingleFactory implements Filter{
protected String configFile;
protected String configDir;
protected Map<String,HttpServletRequest> requestMap =new ConcurrentHashMap<>();
protected Map<String,HttpServletResponse>responseMap =new ConcurrentHashMap<>();
protected Map<String,HashMap<String ,Object>>sessionDatas =new ConcurrentHashMap<>();
protected TLMsg startAppMsg = new TLMsg().setAction("start");
protected ServletContext context;
protected TLObjectFactory moduleFactory;
public void init(FilterConfig config) throws ServletException {
Filter.super.init(config);
context = config.getServletContext();
String initConfigPath=config.getInitParameter("configPath");
if(initConfigPath==null || initConfigPath.isEmpty())
initConfigPath="conf";
configDir = context.getRealPath("/WEB-INF/"+initConfigPath);
String initConfigFile=config.getInitParameter("configFile");
if(initConfigFile==null || initConfigFile.isEmpty())
initConfigFile="moduleFactory_config.xml";
configFile = configDir + File.separator + initConfigFile;
TLObjectFactory.setConfigDir(configDir,configFile);
moduleFactory = new TLObjectFactory("moduleFactory");
registInfactory(moduleFactory, "servletContext", context);
registInfactory(moduleFactory, "servletRequest", requestMap);
registInfactory(moduleFactory, "servletResponse", responseMap);
registInfactory(moduleFactory, "sessionDatas", sessionDatas);
moduleFactory.boot();
}
public void destroy() {
moduleFactory.destroyModule();
}
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException
{
long startTime = System.currentTimeMillis();
String threadName=Thread.currentThread().getName();
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
HashMap<String ,Object> datas =new HashMap<>();
requestMap.put(threadName, request);
responseMap.put(threadName, response);
sessionDatas.put(threadName,datas);
moduleFactory.putMsg("appCenter", startAppMsg);
requestMap.remove(threadName);
responseMap.remove(threadName);
sessionDatas.remove(threadName);
Long nowTime = System.currentTimeMillis();
Long runtime = nowTime - startTime;
TLLog.setLog(TLFilterWithSingleFactory.class, "运行时间:" + runtime, LogLevel.INFO);
}
protected void registInfactory(TLObjectFactory modulefactory, String name, Object object)
{
TLMsg registInFactoryMsg = new TLMsg().setAction("registInFactory")
.setParam(FACTORY_MODULENAME, name)
.setParam(INSTANCE, object);
modulefactory.putMsg(modulefactory, registInFactoryMsg);
}
}
在过滤器的初始化函数init中,首先设置文件目录、配置文件等项,然后实例化一个模块工厂。过滤器定义了三个共享数据requestMap,responseMap,sessionDatas,这三个数据用于保存线程数据,从而实现一个工厂单例来服务所有线程的目的。通过registInfactory方法将共享数据注册的到工厂里,这样线程中任何模块都可以访问共享数据。
在过滤器运行函数doFilter中,首先将线程单独的数据request、reponse保存到共享数据里。然后模块工厂启动appCenter,从而启动框架运行:
putMsg(appCenter, startAppMsg);
appCenter对应的是TLWAPPCenter,我们看它的启动消息行为:
private TLMsg dispatchUrl(Object fromWho, TLMsg msg) {
putMsg("urlMap",createMsg().setAction("doWithUrl").setParam("url",msg.getParam("url")));
return msg ;
}
protected void start(Object fromWho, TLMsg msg) {
HttpServletRequest request =getRequest();
String aurl= request.getRequestURI();
if (aurl == null || aurl.isEmpty())
return ;
ServletContext context= getContext();
String prefixUrl=context.getContextPath()+params.get("tlobjectPath");
String url=aurl.substring(prefixUrl.length());
putMsg(this,createMsg().setAction("dispatchUrl").setParam("url",url));
}
appCenter在start方法中解析出url,然后通过dispatchUrl方法启动urlMap模块,其指令消息"doWithUrl" ,urlMap模块对url进行解析,完成url与消息msg的转换,因为我们的模块都是对消息的处理。urlMap的配置文件,也就是描述url对应消息msg的文件:
<?xml version="1.0" encoding="UTF-8" ?>
<moduleConfig>
<params>
<verison value="1"/>
</params>
<beforeMsgTable>
<action value="toServlet" >
<msg action="auToLogin" destination="webUser" useInputMsg="false" usePreReturnMsg ="false"/>
<msg action="authInUrlMap" destination="webauth" useInputMsg="false" usePreReturnMsg ="false"/>
<msg action="getCache" destination="servletCache" useInputMsg="false" usePreReturnMsg ="false" ></msg>
</action>
</beforeMsgTable>
<!-- 目录相对于filter配置目录下 -->
<!-- 目录相对于filter配置目录下
对于每个映射,可以更改user,client类型。
clientUser : user类型
clientType : client接口类型
clientVars : 客户端输入的变量列表,自动获取后传给后端模块
varType : 客户端变量类型 目前仅为 json
-->
<url-mapping>
<url value="/*" >
<!-- 对于目录设置*,该目录下只能为同一个servmodule处理 。
map方式: 该目录后下url=“方法” ,如/home 对应 index 方法。没有map的方法无法访问-->
<msg destination="homepage" default="index"
bd="baidu" home="index" input="input" content="content"
getmsg="getmsg" ve="velocity" http="http" getuser="getuser"
getParam="getWebParam" />
</url>
<url value="/baidu" >
<msg action="baidu" destination="homepage" />
</url>
<url value="/service" >
<msg action="service" destination="msgMap" />
</url>
<url value="/index" >
<msg action="index" destination="homepage" />
</url>
<url value="/test/*" >
<msg destination="servletTest1" getuser="user" index="index" />
</url>
<url value="/db/*" >
<msg destination="servletDbTest" find="find" findall="findall"
query="query" clientUser="tokenUser" />
</url>
<url value="/db/dbmodle" >
<msg action="dbmodle" destination="servletDbTest" clientVars="name" />
</url>
<url value="/login" >
<msg action="regist" destination="loginModule" />
</url>
</url-mapping>
</moduleConfig>
配置逻辑比较清晰。url为/baidu 转到homepage模块的baidu行为。对于"/db/*" ,db目录下所有的url都转给servletDbTest,其中/db/find 对应消息行为find, /db/dbmodle 对应消息行为为dbmodle 。对于根目录"/*"下的访问,都转给appCenter,其中bd也转向baidu。这样通过/baidu、/bd访问的是一个内容。/home也转向index行为,这样/home、 /index 都访问index行为。从这里看出我们可以灵活配置url对应的消息模块及处理方法。
clientVars定义了用户输入变量,如果设置了,如“var1;var2” 参数,则自动取出用户变量var的值传递给处理模块。处理模块处理模块之间通过msg.getparam("var1)活得变量值。
clientUser、ClientType 分辨定义了该url对应的user类型 和client类型。不同的类型可以不同的处理。如果client为json数据,则输入为json格式。
看到这里,有同学发现没有,我们模块有名字,通过名字直接访问模块,模块的方法也有名字,消息msg的action其实就是模块方法的名字。用名字来调用对象或方法非常方便。。
TLWUrlMap完成url到msg的转换后将消息发送到对应的模块,由模块进行处理。例如下面是一个模块的处理方法:
private void input(Object fromWho, TLMsg msg) {
String username = getUserData("username");
String [] inputname ={"username","passwd"};
TLMsg input=getUserData(inputname);
String username1= (String) input.getParam("username");
if(username1==null)
username1="";
String passwd = (String) input.getParam("passwd");
if(passwd==null)
passwd="";
outData odata = creatOutDataMsg();
odata.addData("用户输入:");
odata.addData("username",username);
odata.addData("inputUsername",username1);
odata.addData("inputPasswd",passwd);
putOutData(odata);
}
根据上面的url转msg配置,url为/input 对应上面这个行为。对于input?username=tianlong&&passwd=12356。可以单独取出某个输入变量,如:
String username = getUserData("username")
也可以用数组的方式统一取出输入变量,转换到一个msg中的参数
String [] inputname ={"username","passwd"};
TLMsg input=getUserData(inputname);
输出时,先创建输出变量:
outData odata = creatOutDataMsg();
然后对输出变量赋值,最后输出变量
putOutData(odata);
输出接口为velocity模块,会根据velocity模块的配置文件查找html模板 ,velocity模块配置文件:
<templates>
<dataid name="appCenter.velocity" template="appCenter.velocity.vm" />
<dataid name="servletTest1.index" template="servletTest1.index.vm" />
<dataid name="servletTest1.user" template="servletTest1.user.vm" />
<dataid name="servletDbTest.db.user" template="servletDbTest.db.user.vm" />
</templates>
当我们创造一个输出变量时,默认有个变量id,在配置中,这个变量id对应相应的velocity模板。如果更换html模板,则更改配置即可。没有对应模板的输出数据则直接输出。上面那个/input输出数据没有对应的模板,因此直接输出,我们看结果:
在input行为中,我们没有用到jsp或servlet技术,就是获得、处理、输出,所有这一切都是在框架中实现。现在我们看看web的日志:
从日志中可以清晰的看到框架流程。首先取出工厂,启动一个appCenter,启动urlMap并解析url,最后输出。我们发现上面许多解析配置,说明这模块刚创建。因为初始化工厂时,仅仅工厂实例化,其他模块没有实例化。第一次运行实例化后,则模块保存在工厂里,下次运行无需再创建,这和servlet是一样的。对于保存在工厂里的模块,要注意实例变量,因为模块为公共的。如果需要模块每次实例化,那么在配置文件中设置非单例模式即可,见前面的配置文件设置说明。上面看到启动了abMsgtable、mslog,这是appcenter初始化信息执行的,可以取消,目前为实验。下面我们看下第二次访问情况;
这时我们发现没有解析配置的日志提示了,说明模块实例都已经初始化完备了,每次访问直接调用了,运行时间由开始的14ms 减小到1ms。当然不是所有的模块都实例化,只有该url用到的模块实例化。
通过上面大概介绍,看到关于这个web框架的设计体现了以下的原则:
1、灵活性
模块可随意配置,例如介绍配置文件中的输入、输出接口模块。如果输出模板不用velocity模板采用其他模板,则根据接口规则设计其他模板模块即可,不影响其他模块。
2、层次透明性
一个层次做的工作不应该涉及其他层次。相对于应用模块,servlet属于底层,应用模块获得url参数,不该操作servlet层。应用模块有自己的getindata和putoutdata。相对于应用模块,输出模块也是底层,也不应该直接操作输出模块。输出模块自行分析应用模块来的数据而采取如何输出,这在velocity配置文件中得到体现,对于应用模块的输出只负责定义了数据id,输出模块怎么输出这个数据是输出的事情。我看有些框架直接把html模板名定义到应用模块里,这就是混淆了层次。
3、效率
一个框架在遵守逻辑性的前提下要有效率。框架单例模式效率极大提高。