有一种简单的方法可以调整它以生成所需的任何输出,以将JSP转换为所需的任何形式,包括页面的对象模型:
- 定义一个Node.Visitor子类来处理JSP的节点(标签等)
- 编写一个简单的Compiler子类,重写其generateJava()来调用访问者
- 继承编译器执行器JspC的子类,重写其方法getCompilerClassName()以返回您的编译器的类
让我们看一下代码。
实作
1.自定义访问者
编译器将调用Visitor来处理已解析的JSP的树对象模型。 此实现仅打印有关页面中有趣的节点子集的信息,以使其嵌套清晰。
package org.apache.jasper.compiler;
import java.util.LinkedList;
import org.apache.jasper.JasperException;
import org.apache.jasper.compiler.Node.CustomTag;
import org.apache.jasper.compiler.Node.ELExpression;
import org.apache.jasper.compiler.Node.IncludeDirective;
import org.apache.jasper.compiler.Node.Visitor;
import org.xml.sax.Attributes;
public class JsfElCheckingVisitor extends Visitor {
private String indent = "";
@Override
public void visit(ELExpression n) throws JasperException {
logEntry("ELExpression", n, "EL: " + n.getEL());
super.visit(n);
}
@Override
public void visit(IncludeDirective n) throws JasperException {
logEntry("IncludeDirective", n, toString(n.getAttributes()));
super.visit(n);
}
@Override
public void visit(CustomTag n) throws JasperException {
logEntry("CustomTag", n, "Class: " + n.getTagHandlerClass().getName() + ", attrs: "
+ toString(n.getAttributes()));
doVisit(n);
indent += " ";
visitBody(n);
indent = indent.substring(0, indent.length() - 1);
}
private String toString(Attributes attributes) {
if (attributes == null || attributes.getLength() == 0) return "";
LinkedList<String> details = new LinkedList<String>();
for (int i = 0; i < attributes.getLength(); i++) {
details.add(attributes.getQName(i) + "=" + attributes.getValue(i));
}
return details.toString();
}
private void logEntry(String what, Node n, String details) {
System.out.println(indent + n.getQName() + " at line:"
+ n.getStart().getLineNumber() + ": " + details);
}
}
笔记:
- 访客必须位于org.apache.jasper.compiler包中,因为基本类org.apache.jasper.compiler.Node是包私有的
- visitBody方法触发对嵌套节点的处理
- 还有更多我可以覆盖的方法(和通行方法doVisit),但是我只选择了对我来说有趣的那些方法
- 节点的属性为... sax类型。 Attributes ,它包含属性名称和值作为字符串
- attribute.getType(i)通常是CDATA
- Node结构包含有关父节点,标签名称,标签处理程序类,源文件的相应行以及源文件的名称的信息以及其他有用信息
- CustomTag可能是最有趣的节点类型,例如,所有JSF标签都属于这种类型
输出示例(对于JSF页面)
jsp:directive.include at line:5: [file=includes/stdjsp.jsp]
jsp:directive.include at line:6: [file=includes/ssoinclude.jsp]
f:verbatim at line:14: Class: com.sun.faces.taglib.jsf_core.VerbatimTag, attrs:
htm:div at line:62: Class: com.exadel.htmLib.tags.DivTag, attrs: [style=width:100%;]
h:form at line:64: Class: com.sun.faces.taglib.html_basic.FormTag, attrs: [id=inputForm]
htm:table at line:66: Class: com.exadel.htmLib.tags.TableTag, attrs: [cellpadding=0, width=100%, border=0, styleClass=clear box_main]
htm:tr at line:71: Class: com.exadel.htmLib.tags.TrTag, attrs:
htm:td at line:72: Class: com.exadel.htmLib.tags.TdTag, attrs:
f:subview at line:73: Class: com.sun.faces.taglib.jsf_core.SubviewTag, attrs: [id=cars]
jsp:directive.include at line:74: [file=/includes/cars.jsp]
h:panelGroup at line:8: Class: com.sun.faces.taglib.html_basic.PanelGroupTag, attrs: [rendered=#{bookingHandler.flowersAvailable}]
...
htm:tr at line:87: Class: com.exadel.htmLib.tags.TrTag, attrs: [style=height:5px]
htm:td at line:87: Class: com.exadel.htmLib.tags.TdTag, attrs:
(我不打印“关闭标签”,因为很明显,当出现另一个具有相同或较小缩进的节点或输出结束时,标签结束。)
2.编译器子类
重要的部分是generateJava,我刚刚复制了它,从中删除了一些代码,并添加了对Visitor的调用。 因此,实际上下面清单中的3行是新的(6,56,70)
public class OnlyReadingJspPseudoCompiler extends Compiler {
/** We're never compiling .java to .class. */
@Override protected void generateClass(String[] smap) throws FileNotFoundException,
JasperException, Exception {
return;
}
/** Copied from {@link Compiler#generateJava()} and adjusted */
@Override protected String[] generateJava() throws Exception {
// Setup page info area
pageInfo = new PageInfo(new BeanRepository(ctxt.getClassLoader(),
errDispatcher), ctxt.getJspFile());
// JH: Skipped processing of jsp-property-group in web.xml for the current page
if (ctxt.isTagFile()) {
try {
double libraryVersion = Double.parseDouble(ctxt.getTagInfo()
.getTagLibrary().getRequiredVersion());
if (libraryVersion < 2.0) {
pageInfo.setIsELIgnored("true", null, errDispatcher, true);
}
if (libraryVersion < 2.1) {
pageInfo.setDeferredSyntaxAllowedAsLiteral("true", null,
errDispatcher, true);
}
} catch (NumberFormatException ex) {
errDispatcher.jspError(ex);
}
}
ctxt.checkOutputDir();
try {
// Parse the file
ParserController parserCtl = new ParserController(ctxt, this);
// Pass 1 - the directives
Node.Nodes directives =
parserCtl.parseDirectives(ctxt.getJspFile());
Validator.validateDirectives(this, directives);
// Pass 2 - the whole translation unit
pageNodes = parserCtl.parse(ctxt.getJspFile());
// Validate and process attributes - don't re-validate the
// directives we validated in pass 1
/**
* JH: The code above has been copied from Compiler#generateJava() with some
* omissions and with using our own Visitor.
* The code that used to follow was just deleted.
* Note: The JSP's name is in ctxt.getJspFile()
*/
pageNodes.visit(new JsfElCheckingVisitor());
} finally {}
return null;
}
/**
* The parent's implementation, in our case, checks whether the target file
* exists and returns true if it doesn't. However it is expensive so
* we skip it by returning true directly.
* @see org.apache.jasper.JspCompilationContext#getServletJavaFileName()
*/
@Override public boolean isOutDated(boolean checkClass) {
return true;
}
}
笔记:
- 我从生成Java中删除了许多对我来说不重要的代码; 对于与我预期不同的分析类型,某些代码可能会有用,因此请查看原始的Compiler类并自己决定。
- 我不太在乎JSP EL,因此可以优化编译器,使其只需要执行一次即可。
3.编译器执行器
直接使用编译器很困难,因为它取决于许多复杂的设置和对象。 因此,最简单的方法是重用Ant任务JspC,这还有查找要处理的JSP的额外好处。 如前所述,关键是重写getCompilerClassName以返回编译器的类(第8行)
import org.apache.jasper.JspC;
/** Extends JspC to use the compiler of our choice; Jasper version 6.0.29. */
public class JspCParsingToNodesOnly extends JspC {
/** Overriden to return the class of ours (default = null => JdtCompiler) */
@Override public String getCompilerClassName() {
return OnlyReadingJspPseudoCompiler.class.getName();
}
public static void main(String[] args) {
JspCParsingToNodesOnly jspc = new JspCParsingToNodesOnly();
jspc.setUriroot("web"); // where to search for JSPs
//jspc.setVerbose(1); // 0 = false, 1 = true
jspc.setJspFiles("helloJSFpage.jsp"); // leave unset to process all; comma-separated
try {
jspc.execute();
} catch (JasperException e) {
throw new RuntimeException(e);
}
}
}
笔记:
- JspC通常会在指定的Uriroot下找到所有文件,但是您可以通过将其逗号分隔的名称传递给setJspFiles来告诉它忽略所有选定的文件。
编译依赖
以你的常春藤形式:
<dependency name="jasper" org="org.apache.tomcat" rev="6.0.29">
<dependency name="jasper-jdt" org="org.apache.tomcat" rev="6.0.29">
<dependency name="ant" org="org.apache.ant" rev="1.8.2">
执照
这里的所有代码都直接来自Jasper,因此属于同一许可证,即Apache许可证,版本2.0 。
结论
Jasper并非真正为扩展和模块化而设计,因为关键的Node类是包私有的,并且其API非常复杂,以致仅重用其中的一部分非常困难。 幸运的是,通过提供一些“伪”对象,Ant任务JspC使它可以在servlet容器之外使用,并且有一种方法可以通过很少的工作来调整它以满足我们的需求,尽管要弄清楚它并不是那么容易。 我不得不应用一些肮脏的技巧,即使用包私有类中的内容,并覆盖一个不打算被覆盖的方法( generateJava ),但是它可以工作并提供非常有价值的输出,这使得您可以做任何想做的事情用一个JSP来做。
翻译自: https://www.javacodegeeks.com/2011/06/hacking-jasper-to-get-object-model-of.html