【问题标题】:Tomcat JSP/JSTL without HTTP没有 HTTP 的 Tomcat JSP/JSTL
【发布时间】:2012-01-21 15:46:03
【问题描述】:

我有一个在 Tomcat 7 下运行的非常标准的 Web 应用程序。

我现在要做的是利用 JSP/JSTL 作为模板语言,独立于 Tomcat 的 HTTP/Web 服务方面来生成可以通过电子邮件发送并转换为 PDF 的 HTML。

之前有没有其他人尝试过这样做并且可以帮助我一些指示?

提前致谢。

【问题讨论】:

标签: java jsp tomcat jstl


【解决方案1】:

与 Stephen C 所说的相反,是的,JSP 是 Servlet 等等。(而且 Velocity 相当好用且易于使用)

但是,什么是 Servlet?

这是一个界面。一个主要方法的接口:

service(ServletRequest req, ServletResponse res)

找到 JSP 类,将其转换为 Servlet,创建 ServletRequest 和 ServletResponse 的实现,然后...

String jspClassName = findJspClassForJSP("your.jsp");
Class jspClass = Class.forName(jspClassName);
Servlet jspServlet = (Servlet)jspClass.newInstance();
MyServletRequest req = new MyServletRequest();
MyServletResponse resp = new MyServletResponse();
jspServlet.init();
jspServlet.service(req, resp);
jspServlet.destroy();
String results = reps.getContent();

这行得通吗?好吧,经过一些工作,它会的。显然,您需要实现 ServletRequest/Response 的最小外观以及您的 JSP 将需要什么。但是,您可能只需要属性和流。如果你让你的响应返回一个 StringWriter,你就成功了。

下一部分是从 JSP 创建 servlet。 Jasper 编译器很方便地为您做这件事——游戏正在调用它。我从来没有直接做过,但它显然可以做到,因为 servlet 容器以及 JSPC 脚本/bat 文件、ant 任务以及大多数 Servlet 容器都使用 Jasper。所以,这是可以做到的。一旦您知道如何调用它,您就会知道最终生成的 JSP 类名。 (参见示例的第一行。)

我做过这个吗?不,但我敢打赌,你会在不到一天的时间内知道这是否可行。我敢打赌,尤其是如果您没有遇到任何类加载器恶作剧的话。如果您让用户更改并重新生成 JSP(因此 MyEmail.jsp 被编译到 MyEmail.class、MyEmail_2.class 等),您可能会遇到问题。但是,如果您自己调用 Jasper,您可能会对此有更多的控制权。 另一个困难的部分是确定 JSP 的类名。大多数容器都遵循这里的基本模式,因此如果您在 WAR 生成的代码中四处寻找,您可能会找到它。

保持 JSP 相当简单(并且电子邮件模板不应该因为嵌入式 Java 或任何进行随机调用的东西而变得超级复杂),而且它更有可能工作。

您的解决方案可能无法从 Tomcat 开箱即用,但您可能不会在意。与我交谈过的人使用 JSP 作为模板,只需打开一个到他们自己的服务器的套接字并发出请求。他们也没有走这么远。

但从表面上看,节省一些古怪的类加载器黑洞地狱,我敢打赌你可以让这个工作很快。尽可能少地实现请求和响应,在 JSP 和 JSTL 调用你没有计划的东西时对抗一些 NPE,正如圣诞老人所说,

砍掉,砍掉,砍掉一切!

附录:

所以,对于所有的反对者......

public void runJsp() {
    JspC jspc = new JspC();
    jspc.setUriroot("/tmp/app");
    jspc.setOutputDir("/tmp/dest");
    jspc.setJspFiles("newjsp.jsp");
    jspc.setCompile(true);
    try {
        jspc.execute();
        Class cls = Class.forName("org.apache.jsp.newjsp_jsp");
        Servlet s = (Servlet) cls.newInstance();
        MyRequest req = new MyRequest();
        MyResponse resp = new MyResponse();

        s.init(getServletConfig());
        s.service(req, resp);
        s.destroy();
        System.out.println(resp.getSw().toString());
    } catch (JasperException ex) {
        throw new RuntimeException(ex);
    } catch (ClassNotFoundException ex) {
        throw new RuntimeException(ex);
    } catch (InstantiationException ex) {
        throw new RuntimeException(ex);
    } catch (IllegalAccessException ex) {
        throw new RuntimeException(ex);
    } catch (ServletException ex) {
        throw new RuntimeException(ex);
    } catch (IOException ex) {
        throw new RuntimeException(ex);
    }
}

令人惊奇的源代码和调试器中的 1/2 小时将为您做些什么。

我在 /tmp/app/newjsp.jsp 中创建了一个简单的 JSP。

jspc.setUriroot 告诉编译器你的“网络应用”的基础在哪里。 jspc.setOutputDir 告诉 jspc 将生成的 Java 和 Class 文件放在哪里。 jspc.setJspFiles 根据 URI Root 告诉 jspc 要编译哪些文件。 jspc.setCompile 告诉它实际编译代码。最后,jspc.execute() 做事。

默认情况下,Jasper 使用包 org.apache.jsp,并根据 JSP 文件名创建一个新类。对于我的简单实验,我只是将“/tmp/dest”放在 Glassfish 容器的类路径中,以便容器找到生成的类。

我加载类,并获取一个实例。

最后,我创建了 MyRequest、MyRequest 以及最终的 MySession。我的 IDE 方便地为各个接口创建了存根。在这种情况下,我实现了:MyRequest.getSession()、MyResponse.setContentType()、MyResponse.setBufferSize() 和 MyResponse.getWriter()。

public PrintWriter getWriter() throws IOException {
    if (sw == null) {
        sw = new StringWriter();
        pw = new PrintWriter(sw);
    }
    return pw;
}

显然 sw 和 pw 是 MyResponse 的实例变量。

MyRequest 返回了 MySession 的一个实例。我对 MySession 的实现——什么都没有。但是运行时需要一个 Session,它只是不为我非常简单的 JSP 单独使用它,而且我没有动力从 Servlet 中填充它。

我在 Glassfish v2.1 上对此进行了测试。我只是将 appserv_rt.jar(来自 glassfish/lib)添加到我的构建类路径(因此它可以找到 JspC jar),但我没有将它捆绑在 WAR 中(因为它已经在容器中)。

而且,shazam,它奏效了。在“现实生活”中,假设想要利用 JSP 的进程实际上来自 Web 请求,我将简单地创建一个 HttpServletResponseWrapper 并覆盖前面的三个方法,其余的可能只是工作。如果图片中根本没有 Web 请求,那么您需要创建自己的 Session 实现(其实没什么大不了的,它只是一张地图)。

我还会使用私有 URLClassLoader 来加载虚假的 JSP 类。如果我知道我永远不会重新加载 JSP,那么只需将目标设为我的 WEB-INF/classes 目录并为其提供自己的包并让系统加载它们。

但是,是的,它奏效了。没什么大不了。这只是java。

【讨论】:

  • 我目前正在处理类似的事情。这个话题似乎有点老了,但你还记得 getServletConfig() 方法包含什么吗?提前谢谢你
  • @hammerfest 毫无疑问,我在另一个 Servlet 中运行了它,所以它只依赖于容器给我的东西。如果您查看 ServletConfig,那么其中最重要的部分就是 ServletContext。如果您自己编写,您需要考虑您的需求,即在这里充实哪些方法。奇怪的是你不会关心任何 Servlet 特定的东西,但是 contextPath,也许是 init 参数和属性。其中一些可能很有趣。老实说,我会在任何地方删除一个返回 null 的内容,看看是否有任何问题。我怀疑任何事情都会发生,这取决于 JSP 页面。
  • 谢谢,确实这似乎是一个可行的解决方案。我设置了一个自己的 ServletConfig/ServletContext 实现存根(只有 initParameters 和属性是相关的)并最终让 servlet 成功使用它。感谢您回来,非常感谢
【解决方案2】:

这真的没有意义。 JSP 是一种很好的语法,可以生成 Java EE servlet 类。实际上,JSP 的“servlet”/“http”特性通过 API 以及 JSP 和 JSTL 的语义模型完全交织在一起。

如果您想独立于 Web 请求生成 HTML,您最好使用不同的模板技术;例如速度或 FreeMarker。如果您希望 HTML 也作为 Web 响应交付,请安排您的 servlet 调用模板引擎以生成响应。 (如果您使用的是 Spring,那么现有的基础架构可以做到这一点。其他框架可能有类似的支持,但如果没有,自己实现一些胶水代码应该不难。)

【讨论】:

    猜你喜欢
    • 2018-12-27
    • 1970-01-01
    • 1970-01-01
    • 2015-09-30
    • 2015-03-17
    • 2012-10-06
    • 1970-01-01
    • 2012-09-23
    • 1970-01-01
    相关资源
    最近更新 更多