【问题标题】:JSP programmatically renderJSP 以编程方式呈现
【发布时间】:2010-12-15 17:03:23
【问题描述】:

我需要以编程方式呈现 JSP 页面。据我了解,JSP 应该有一些编译器。问题是我可以在没有 JspServlet 和其他人的情况下直接使用这个编译器吗?我只需要文档说明如何使用 JSP 编译器(例如 Jasper)。

我认为,一些额外的信息可以澄清情况。我不能使用标准的 JspServlet。我想在编译前以某种方式更改源 JSP(准确地说是把两个 JSP 合并在一起),所以我需要一种直接使用 JSP 编译器从 InputStream(或 Reader)编译 JSP 结果的方法。

两个 JSP 的合并是布局要求。你可以问:“但是为什么这家伙不使用 SiteMesh 或类似的东西呢?”。 JSP 页面之一不是静态的。它由用户提供并存储在数据库中。我们清理和验证这个 JSP 布局(用户只能使用标签的子集,而且它们都不是标准的,而是专门为它们创建的),缓存它们等等。但是现在我们需要一种方法来使用这些 JSP 页面(存储在内存中)作为用户请求的所有 JSP 页面的布局。

【问题讨论】:

  • 我遇到了类似的问题,我发现“最简单”的解决方案是将整个项目切换到 Velocity(或其他模板引擎),这样的任务是微不足道的。看起来你在开发中已经太远了,无法做出这样的改变,但只是说......

标签: jsp servlets rendering


【解决方案1】:

如果服务器的部署文件夹是可写的,并且服务器被配置为热发布部署文件夹中的任何更改(默认情况下是 Tomcat),那么您可以让 servlet 在此处写入 JSP 文件并将请求转发到某个主 JSP 文件.

想象一下,你想动态地创建一个 main.jsp 包含以下内容:

<jsp:include page="${page1}" />
<jsp:include page="${page2}" />

如果${page1} 解析为page1.jsp${page2} 解析为page2.jsp,那么这里是SSCCE

package com.stackoverflow.q1719254;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/test")
public class TestServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException
    {
        File root = new File(getServletContext().getRealPath("/"));
        
        String main = "<jsp:include page=\"${page1}\" /><jsp:include page=\"${page2}\" />";
        write(main, new File(root, "main.jsp"));
        
        String page1 = "<p>We are in ${data1}";
        write(page1, new File(root, "page1.jsp"));
        request.setAttribute("page1", "page1.jsp");
        request.setAttribute("data1", "first jsp");
        
        String page2 = "<p>We are in ${data2}";
        write(page2, new File(root, "page2.jsp"));
        request.setAttribute("page2", "page2.jsp");
        request.setAttribute("data2", "second jsp");

        request.getRequestDispatcher("main.jsp").forward(request, response);
    }
    
    private static void write(String content, File file) throws IOException {
        try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), "UTF-8"))) {
            writer.write(content);
        }
    }
    
}

在 http://localhost:8080/playground/test(或您使用的任何主机/上下文名称)执行它,您会看到

We are in first jsp
We are in second jsp

为了提高效率,我会缓存所有资源并使用File#exists() 检查特定页面是否已保存在磁盘上。

【讨论】:

  • 好的,让我们深入了解需求。合并两个 JSP 是布局要求。你可以问:“但是为什么这家伙不使用 SiteMesh 或类似的东西呢?”。 JSP 页面之一不是静态的。它由用户提供并存储在数据库中。我们清理和验证这个 JSP 布局(用户只能使用标签的子集,它们都不是标准的,而是专门为它们创建的),缓存它们等等。但是现在我们需要一种方法来使用这些 JSP 页面(顺便说一下,它们存储在内存中)作为用户请求的所有 JSP 页面的布局。
  • 好的,它会工作的。但我希望我的 JSP 看起来更干净一些。像 ... 和 page 标签的子标签描述页面。页面标签本身会在编译时替换为布局。这在某种程度上可能吗?
  • 只需在 JSP 内容的顶部包含标记库。只需按通常的方式编写 JSP 代码。您需要做的就是将其存储在 webapp 的 webcontent 中。请参阅我上次添加的 SSCCE。
  • 如果在应用服务器启动后修改了 page1.jsp 或 page2.jsp,示例代码会起作用吗?在大多数生产环境中,应用服务器不会重新编译这些文件。
  • 这需要一种不同的方法。给它另一个文件名等等。然而,我认为这不适用于他的具体情况。
【解决方案2】:

我不完全确定这是否是您要查找的内容,但 DWR framework 包含一个名为 WebContext.forwardToString 的方法,该方法将当前请求和虚假响应对象转发到 URL,然后读取缓冲区的内容进入记忆。以下是代码示例:

StringWriter sout = new StringWriter();
StringBuffer buffer = sout.getBuffer();

HttpServletResponse realResponse = getHttpServletResponse();
HttpServletResponse fakeResponse = new SwallowingHttpServletResponse(realResponse, sout, realResponse.getCharacterEncoding());

HttpServletRequest realRequest = getHttpServletRequest();
realRequest.setAttribute(WebContext.ATTRIBUTE_DWR, Boolean.TRUE);

getServletContext().getRequestDispatcher(url).forward(realRequest, fakeResponse);

return buffer.toString();

您可以使用它来获取 jsp rednering 的结果并将它们存储在内存中。您可以从上面的链接下载源代码,看看 SwallowingHttpServletResponse 是如何工作的。

【讨论】:

    【解决方案3】:

    如果 JSP 已经被应用服务器预编译,那么您可以查找生成的 .class 文件。在 Tomcat 中,它应该位于 $CONTEXT_ROOT/org/apache/jsp/ 目录下。你也许可以以某种方式运行这个类并生成你的输出。

    编辑:错过了关于修改 JSP 源的编辑。

    看看 org.apache.jasper.compiler.AntCompiler(包含在 Tomcat 的 jasper.jar 中)。有一个名为 generateClass 的受保护方法,您可能可以覆盖和弄乱它:)

    【讨论】:

      【解决方案4】:

      您必须这样做的原因是什么? 如果您需要合并 2 个 jsp 文件来处理,可能使用 include 或者您需要其他想法?您能提供样品说明您的要求吗?

      【讨论】:

        【解决方案5】:

        JSTL 只是在 JSP 文件中使用的标签库。所以在这种情况下真的没关系。

        由于 JSP 编译器将 JSP 文件转换为 Java Servlet,我怀疑您是否可以直接运行它(编译器实际上并不运行任何东西!)或渲染一个 JSP 文件。

        实际上很难从你的问题中理解你真正在寻找什么。

        编辑:我会推荐 jsp:include 来完成这项工作

        【讨论】:

        • 是的,在这种情况下提及 JSTL 很不方便,同意。没错,JSP 页面在编译后变成了 servlet。我需要的是一种自己将 JSP 页面编译为 servlet 的方法。我可以更深入地解释我的任务。我需要以某种方式(从源代码)合并两个 JSP 文件,然后编译结果。
        • 将其中一个 JSP 文件包含在另一个文件中还不够吗?请参阅 jsp:include 和
        • 不幸的是,没有。 JSP 合并的规则很重要。当然,这本身就是问题,但是...
        【解决方案6】:

        也许你可以使用Tomcat's JspC ant task

        【讨论】:

        • 不完全是。我需要自己在服务器端编译 JSP。所以蚂蚁任务不是我要找的。但我会看看这个任务的源代码。也许会有所帮助。谢谢。
        猜你喜欢
        • 1970-01-01
        • 2021-09-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-02-02
        • 2020-12-19
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多