1、实现演变过程
消息对象框架启动顺序是首先启动模块工厂,然后工厂启动模块运行。在最开始用于web框架时,由于缺乏经验,我设计由每个线程单独启动一个工厂,然后工厂启动各个模块,线程结束时,工厂及所有模块销毁。这样实现功能是没有问题的,但是效率很低,因为每次一个客户端的request都要引起一个线程从工厂建立、模块初始化的过程。为了避免每次工厂和模块的初始化,借鉴数据库的连接池,我又采取了工厂池的方案,当系统启动时初始化一定数量的工厂,像传统的servlet一样,模块实例化后一直在内存中,这样避免了每次线程的工厂、模块的初始化。通过工厂池的运用,运行效率极大提高。但这又有个问题一直隐含着,就是每个线程都有一套框架,从工厂到各个模块,同样一个模块在所有线程中都有一个。虽然单线程的运行效率高,线程数据隔离,但是整体来说占用内存的,如果N个线程,就N个框架,整体内存是一个框架的N倍。这个问题我一直是我个心病、bug,也一直没有好的解决方案,可以用一个map保存所有的线程数据,但是如何区分呢,一直没想出来。有一天我百度时无意看到一句话---springmvc是单例模式,之前我对其他框架不了解,包括springmvc。这句话引起我很大的震动——springmvc是单例模式,它怎么实现的的? 通过百度了解它用了ThreadLoacal这东西,大概了解下其原理,真是给我当头一棒,我怎么忘记了线程ID,可以做它mapkey来区分线程数据啊。(当时一个感触,很简单的一个问题自己竟想了好久,看来一个人做事情真有思维局限的,如果有另一个人提醒下就很快解决了)
这就是现在消息对象web框架的单例原理,建立一个map为所有线程公用,用线程id来区分数据,自己只能取自己线程的数据。下面看实现过程。
2、实现
许多多线程实现是用了ThreadLoacal来实现数据共享,但在我这里没有用。因为消息对象框架一个特点是所有的模块都由工厂创建,并且创建后工厂把自己放入所有模块里,工厂成为其所创建模块的一个属性,工厂本身就是个共享数据了。借助这个特点,工厂的数据就自然可以被其他模块或线程使用了。现在看代码:
这是Tomcat过滤器,用于将所有访问转到框架,过滤器负责启动工厂,框架开始运行。
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);
}
}
过滤器定义了三个对象全局变量 requesMap、responseMap、sessionDatas,分别用于每个请求的request、response及可以在线程中共享的数据。
工厂实例化后,通过 registInfactory(moduleFactory, "servletRequest", requestMap) 方法,将建立的全局变量保存到工厂里面,这样其他模块就可以访问这些数据了。
有客户端访问时,过滤器开始工作, 运行doFilter方法,在其方法内,对于每个线程的数据,保存到共享数据里。
requestMap.put(threadName, request);
responseMap.put(threadName, response);
这样线程的其他模块就可以通过工厂获得自己线程的数据。在基本模块TLWServModule里:
例如获得requet:
protected HttpServletRequest getRequest(){
Map<String,HttpServletRequest> requestMap = (Map<String, HttpServletRequest>) getModuleInFactory("servletRequest");
String threadName=Thread.currentThread().getName();
return requestMap.get(threadName);
}
代码非常简单,首先从工厂中获得共享数据requesMap,然后根据自己的线程名称获取自己的数据。由于只是取自己的数据,因此不涉及线程数据竞争问题。
如果像把数据保存起来给其他模块用,那么可以存入共享数据中:
protected void setSessionData(String varname ,Object value)
{
Map<String,HashMap<String ,Object>> sessionDatas = (Map<String, HashMap<String, Object>>) getModule("sessionDatas");
String threadName=Thread.currentThread().getName();
HashMap<String ,Object> datas =sessionDatas.get(threadName);
datas.put(varname,value);
}
其他模块可以在任意地方取自己线程的数据:
protected Object getSessionData(String varname)
{
Map<String,HashMap<String ,Object>> sessionDatas = (Map<String, HashMap<String, Object>>) getModule("sessionDatas");
String threadName=Thread.currentThread().getName();
HashMap<String ,Object> datas =sessionDatas.get(threadName);
return datas.get(varname);
}
框架的单例有效节省了内存,提高了效率。