统一对象消息编程(11)—web框架

    我们的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、效率

  一个框架在遵守逻辑性的前提下要有效率。框架单例模式效率极大提高。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值