小编典典

不使用HTTP的Tomcat JSP / JSTL

jsp

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

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

之前有没有其他人尝试过这样做并且可以帮助我提供一些建议?

提前致谢。


阅读 268

收藏
2020-06-08

共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所需的外观。但是,可能您可能只需要属性和流。如果您使Response返回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告诉编译器“
Web应用程序”的基础位置。jspc.setOutputDir告诉jspc将生成的Java和Class文件放在何处。jspc.setJspFiles根据URI根告诉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。

2020-06-08