与 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。