Servlet3.0下组件化开发——静态文件的刷新

来源:互联网 发布:手机数据分析公交 编辑:程序博客网 时间:2024/06/03 12:11

接上一篇博客Servlet3.0之组件化开发,在我们选择将静态文件打包到JAR中后,就会面临这样一个问题:Tomcat会在启动时将JAR中的静态资源载入内存,之后的读取直接进行内存操作。所以作出的修改必须重启才能看到效果;这样无疑是非常影响开发效率的。

1. 引言

在上一篇博客中我们就如何从JAR中获取静态资源这一操作进行了讨论,而对于客户端的请求Tomcat是如何进行处理的呢?

这里我们借用下上一篇博客里的参考链接里的例子,作者给出的示例项目里是让Spring来负责静态资源请求的。通过跟踪一次完整的资源请求,我们可以发现对于静态资源,Spring最终是包装为ServletContextResource实例;而ServletContextResource类中对getInputStream方法的实现就是直接调度给ServletContext接口的getResourceAsStream方法;也就是将读取静态资源的逻辑最终还是由容器Tomcat来完成的

2. Tomcat如何读取静态资源

Tomcat是以ApplicationContextFacade实例向外暴露ServletContext接口实现的;而ApplicationContextFacadeApplicationContext的门面模式,所有的操作最终都是调度给了ApplicationContext。所以最终
ServletContext接口的getResourceAsStream方法实现还得到ApplicationContext类中查找。

2.1 ApplicationContext对getResourceAsStream方法的实现

@Overridepublic InputStream getResourceAsStream(String path) {    if (path == null)        return (null);    if (!path.startsWith("/") && GET_RESOURCE_REQUIRE_SLASH)        return null;    String normalizedPath = RequestUtil.normalize(path);    if (normalizedPath == null)        return (null);    // 这里的context正是容器StandardContext;     // 而返回的resources类型为ProxyDirContext;     // 这也和Mybatis中的MixedSqlNode类类似,内部定义的SqlNode实现类最终都是以MixedSqlNode形式向外暴露,这样就统一了门面。    DirContext resources = context.getResources();    if (resources != null) {        try {            // 所以这里就是我们所要找的核心了,ProxyDirContext类的lookup方法的实现            Object resource = resources.lookup(normalizedPath);            if (resource instanceof Resource)                return (((Resource) resource).streamContent());        } catch (NamingException e) {            // Ignore        } catch (Exception e) {            // Unexpected            log(sm.getString("applicationContext.lookup.error", path,                    getContextPath()), e);        }    }    return (null);}

2.2 ProxyDirContext类中对lookup方法的实现

@Overridepublic Object lookup(Name name)    throws NamingException {    // 先从缓存里面找, 注意这里的cacheLookup方法,可以通过配置<context>中的cachingAllowed="false"来保证返回null    CacheEntry entry = cacheLookup(name.toString());    if (entry != null) {        if (!entry.exists) {            throw NOT_FOUND_EXCEPTION;        }        if (entry.resource != null) {            // Check content caching.            return entry.resource;        } else {            return entry.context;        }    }    // 因为ProxyDirContext本身只是个代理,真正的实现还是交给底层的被代理对象dirContext    Object object = dirContext.lookup(parseName(name));    if (object instanceof InputStream)        return new Resource((InputStream) object);    else        return object;}

2.3 BaseDirContext类实现的lookup方法

// lookup方法的实现是在BaseDirContext类中完成的;// 而BaseDirContext抽象类是FileDirContext,VirtualDirContext, WARDirContext的基类; 子类并没有对这个方法进行覆盖//  这是tomncat7.0.50里的逻辑!!!!    @Override    public final Object lookup(String name) throws NamingException {        // First check for aliases        if (!aliases.isEmpty()) {            AliasResult result = findAlias(name);            if (result.dirContext != null) {                return result.dirContext.lookup(result.aliasName);            }        }        // Next do a standard lookup        // 先来一次标准的查询; 这是一个抽象方法, 交由子类去实现各自的逻辑 ;        Object obj = doLookup(name);        if (obj != null)            return obj;        // Check the alternate locations        for (DirContext altDirContext : altDirContexts) {            try {                // 这里就是我们本次所要关注的重点了;也就是说tomcat每次读取资源时候,都是以这样的规则尝试从altDirContexts字段所代表的资源容器中读取到匹配的资源。                obj = altDirContext.lookup("/META-INF/resources" + name);                if (obj != null)                    return obj;            } catch ( NamingException ex) {                // ignore            }        }        // Really not found        throw new NameNotFoundException(                sm.getString("resources.notFound", name));    }

2.4 altDirContexts字段是如何被赋值的

// BaseDirContext类对addResourcesJar方法的实现,正是对altDirContexts字段进行了填充// 而这个方法只有一个被调用的位置,就是StandardContext类的addResourceJarUrl方法, 这样整个逻辑就通了// 读取到的资源被存放到了BaseDirContext实例的altDirContexts字段中,// 而上面的BaseDirContext实例则是存在与StandardContext实例中/** * Add a resources JAR. The contents of /META-INF/resources/ will be used if * a requested resource can not be found in the main context. */public void addResourcesJar(URL url) {    try {        JarURLConnection conn = (JarURLConnection) url.openConnection();        JarFile jarFile = conn.getJarFile();           ZipEntry entry = jarFile.getEntry("/");        WARDirContext warDirContext = new WARDirContext(jarFile,                new WARDirContext.Entry("/", entry));        warDirContext.loadEntries();        altDirContexts.add(warDirContext);    } catch (IOException ioe) {        log.warn(sm.getString("resources.addResourcesJarFail", url), ioe);    }}

3. 思路

自此,结合上一篇文章,JAR中静态资源的存和取我们应该有了基本印象;我们也大概能想到一个解决所提出的问题的方法了
1. 启动时获取到tomcat容器中代表本项目的那个StandardContext;
2. 同时监控我们自己的jar,如果它们发生了改变,就重新读取新的静态资源,并用它们刷新Context中代表资源的那个字段值。

4. 实现

因为涉及到公司资源,这里我只给思路;详细实现代码就不贴了。

public class StaticResourceRefresher extends ContextConfig implements        ContainerServlet, Servlet, Runnable {    // 略}

5. 不完美

  1. StaticResourceRefresher 目前是整个WEB-INF/lib目录刷新一次;后期可以优化为只刷新单个jar, 甚至只刷新jar中的那个被修改的文件。
  2. tomcat不同版本中的 ContextConfig类中的方法的签名是不一样的;例如 tomcat 7.0.5和tomcat 7.0.50就存在这样的差异。

6. 参考

这次是真没有。

原创粉丝点击