模块化架构之tomcat的jsp加载处理

最近一直在思考模块化项目的架构问题,也学过一点点OSGI的东西,感觉有点高大上,自身目前还用不上,或者用的不够好。就想着能不能实现一个简单一点的模块化方式,于是便有了前一篇的阅读spring源码,解决了实体实例化上的模块化问题,下面我们来看看资源模块化的问题,主要是jsp。我们都知道,对于服务器(tomcat)来说,要求jsp都要在项目目录下,那如果我们要把jsp封装到相对应的模块jar包中呢,tomcat还能加载吗?

一开始,笔者只是简单的做了读取操作,对于jsp的请求都拦截了读取内容返回前台,结果就出现了很喜感的一幕,jsp根本就不能好好执行,这时笔者才想起来,jsp本质上是一个变种servlet,而不是一个单纯的html页面。于是我们又开始了阅读源码的过程。

通过阅读我们发现,tomcat处理jsp的关键是其默认配置了一个JSPServlet类,用于处理所有的jsp文件。通过分析Service方法和serviceJspFile方法我们发现了tomcat对于jsp的处理基本过程是:

首先,通过uri获取要处理的jsp路径。

其次,尝试从JspRuntimeContext缓存容器中获取JspWrapper如果没有,就开启线程同步去构建一个,构建时会通过context.getResource来判断是否有jsp文件(也就是该方法限定了jsp必须在项目目录下,也就是配置的docBase目录下)。

第三,如果有,就创建JSPWrapper并以uri为key加入缓存中。

第四,调用JspWrapper.service方法来完成jsp响应(该方法完成了jsp的编译存储和服务处理。每次都会判断是不是开发模式和最后一次修改时间,如果有更新就重新编译)

在阅读中我们发现,对于jsp资源获取的关键类实际上只有3个一个是JSPServlet,一个是JspWrapper一个是JspCompilationContext.这三个类。

JSPServlet说了,是用于处理*.jsp的请求,负责获取jsp的路径,并查找相关的JSPWrapper来响应。

JSPWrapper,包装了jsp文件,完成了jsp的编译,和响应

JspCompilationContext,Jsp的编辑容器,完成了jsp的资源查找和编辑器创建等操作,在jsp第一次加载的时候完成了大量的初始化工作。


下面我们就改造这三个类来实现我们自己的想法,这里我继承这三个类分别作了ModuleJspServlet,ModuleJSPWrapper和ModuleJSPCompilationContext类,来完成相关的扩展功能。

ModuleJspServlet中我们简单修改了,判断方法,也就是不在简单的使用ServletContext.getResource来判断是否存在jsp,而是通过JspCompilationContext来判断。


 private void serviceJspFile(HttpServletRequest request,
                                HttpServletResponse response, String jspUri,
                                Throwable exception, boolean precompile)
        throws ServletException, IOException {

        JspServletWrapper wrapper = rctxt.getWrapper(jspUri);
       <span style="color:#ff0000;"> ModuleJspCompilationContext jc = new ModuleJspCompilationContext(jspUri, false, options, context, null, rctxt);</span>
        if (wrapper == null) {
            synchronized(this) {
                wrapper = rctxt.getWrapper(jspUri);
                if (wrapper == null) {
                    // Check if the requested JSP page exists, to avoid
                    // creating unnecessary directories and files.
                    if (<span style="color:#ff0000;">null == jc.getResource(jspUri)</span>) {
                        handleMissingResource(request, response, jspUri);
                        return;
                    }
                    boolean isErrorPage = exception != null;
                    wrapper = new ModuleJspServletWrapper(config, options, jspUri,
                                                    isErrorPage, rctxt);
                    rctxt.addWrapper(jspUri,wrapper);
                }
            }
        }

        try {
            wrapper.service(request, response, precompile);
        } catch (FileNotFoundException fnfe) {
            handleMissingResource(request, response, jspUri);
        }

    }
红色部分使我们的修改。这个类的修改比较简单。

对于ModuleJSPWrapper我们也只是修改了其构造函数:

public ModuleJspServletWrapper(ServletConfig config, Options options,
			String jspUri, boolean isErrorPage, JspRuntimeContext rctxt)
			throws JasperException {
		super(config, options, jspUri, isErrorPage, rctxt);
		this.isTagFile = false;
		this.config = config;
		this.options = options;
		<span style="color:#ff0000;">if(jspUri.startsWith("jar:file:")) {
			jarPath = jspUri.substring(0,jspUri.indexOf("!/") + 2);
			jspUri = jspUri.substring(jspUri.indexOf("!/") + 1);
		}else if(jspUri.startsWith("file:")) {//文件系统开始,以此开头处理的是开发模式下
			jarPath = jspUri.substring(0,jspUri.indexOf("/") + 1);
			jspUri = jspUri.substring(jspUri.indexOf("/"));
		}</span>
		this.jspUri = jspUri;
		ctxt = new ModuleJspCompilationContext(jspUri, isErrorPage, options,
				config.getServletContext(), this, rctxt);
	}
这里增加了对于jar包文件和工作空间的处理,后者是针对开发的便利性提出的开发模式来的,这样在完成开发前都可以不用打包。

主要的修改在ModulJspCompilationContext中,获取资源的方法我们做了下简单修改。

 public URL getResource(String res) throws MalformedURLException {
        URL result = null;
        if (res.startsWith("/META-INF/")) {
            // This is a tag file packaged in a jar that is being compiled
            URL jarUrl = tagFileJarUrls.get(res);
            if (jarUrl == null) {
                jarUrl = tagFileJarUrl;
            }
            if (jarUrl != null) {
                result = new URL(jarUrl.toExternalForm() + res.substring(1));
            }
        } else if (res.startsWith("jar:file:") || res.startsWith("file:")) {
                // This is a tag file packaged in a jar that is being checked
                // for a dependency
                result = new URL(res);

       <span style="color:#ff0000;"> } else if (this.jarPath != null){
        	result = new URL(jarPath + res);</span>
        }else {
            result = context.getResource(canonicalURI(res));
        }
        return result;
    }


我们针对自己的路径做了下处理。这样就完成了jsp的跨项目加载和模块化加载。


总结:这里通过跟踪分析,知识修改了jsp的加载方式,不在只是从项目目录下加载。其他未做修改。

在测试时发现,我们单纯的返回资源不设置请求响应码,容易造成浏览器莫名其妙的缓存问题。这点算是额外收获了。





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值