jsp:getProperty…Name was not previously introduced as per JSP.5.3的解决方法



jsp:getProperty…Name was not previously introduced as per
JSP.5.3的解决方法。我昨天看jsp的javabean部分,当按照书中所讲在a.jsp中使用
<jsp:useBean id=”test” scope=”reqst”
class=”test.TestBean”></jsp:useBean>来设置javabean的scope为
reqst,session或application,并设置了javabean test属性的值后,当forward
或incl? b.jsp的时候,在b.jsp网页中不再设置<jsp:useBean id=”test” scope=”reqst”
class=”test.TestBean”></jsp:useBean>而直接使用<jsp:getProperty
property=”id” name=”test”/>正常情况下是应该得到javabean
test的id属性的值的。但是我的tomcat服务器却给我报了如下的错误:
org.apache.jasper.JasperException:
jsp:getProperty for bean with name ‘test’. Name was not previously introd? as
per
JSP.5.3
org.apache.jasper.compiler.Generator$GenerateVisitor.visit(Generator.java:1086)
org.apache.jasper.compiler.Node$GetProperty.accept(Node.java:1124)
org.apache.jasper.compiler.Node$Nodes.visit(Node.java:2361)
org.apache.jasper.compiler.Node$Visitor.visitBody(Node.java:2411)
org.apache.jasper.compiler.Node$Visitor.visit(Node.java:2417)
org.apache.jasper.compiler.Node$Root.accept(Node.java:495)
org.apache.jasper.compiler.Node$Nodes.visit(Node.java:2361)
org.apache.jasper.compiler.Generator.generate(Generator.java:3416)
org.apache.jasper.compiler.Compiler.generateJava(Compiler.java:231)
org.apache.jasper.compiler.Compiler.compile(Compiler.java:347)
org.apache.jasper.compiler.Compiler.compile(Compiler.java:327)
org.apache.jasper.compiler.Compiler.compile(Compiler.java:314)
org.apache.jasper.JspCompilationContext.compile(JspCompilationContext.java:589)
org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:317)
org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:313)
org.apache.jasper.servlet.JspServlet.service(JspServlet.java:260)
javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
开始我以为是我的程序的错误,但我后来拿看的书的配套光盘中带的tomcat5.016服务器来试,就没有这个问题,能够正常的显示出javabean的
属性。我的tomcat的版本是官网现在最新的6.026。后来在网上查了一下,发现这个问题原来是tomcat的一个bug。具体可参见apache官
网的bug记录:isss.apache.org/bugzilla/show_bug.cgi

这个虽然是tomcat自己的bug,但自己也能解决这个bug(其实这个严格来说不是bug,相反像我上面所写的那样的对javabean的用法才是不
规范的,是对最新jsp规范的违背,这个我后面会说),因为tomcat应该是开源的吧(我不怎么熟对这些东西,不清楚它算不算,反正从官网上可以搞到原
代码)。这个看上面的错误记录就能知道出问题的程序在
org.apache.jasper.compiler.Generator$GenerateVisitor.visit(Generator.java:1086),
如果不太会看这个错误信息的话,比如我这种菜鸟,还有一种办法,就是在tomcat的源代码目录上使用windowns的搜索功能,搜哪个文件里含有 “Name was
not previously introd? as per
JSP.5.3”这样的文字,一样可以搜到Generator.java文件。这个文件在apache-tomcat-6.0.26-src\java
\org\apache\jasper\compiler目录中。
找到它的1086行,这行代码所处的方法如下:
p lic void
visit(Node.GetProperty n) throws JasperException {
String name =
n.getTextAttribute(“name”);
String property =
n.getTextAttribute(“property”);

n.setBeginJavaLine(out.getJavaLine());

if
(beanInfo.checkVariable(name)) {
// Bean is defined using useBean, introspect
at compile time
Class bean = beanInfo.getBeanType(name);
String beanName =
JspUtil.getCanonicalName(bean);
java.lang.reflect.Method meth =
JspRuntimeLibrary
.getReadMethod(bean, property);
String methodName =
meth.getName();
out
.printil(“out.write(org.apache.jasper.runtime.JspRuntimeLibrary.toString(”
+
“(((”
+ beanName
+ “)_jspx_page_context.findAttribute(”
+ “\”"
+
name + “\”)).” + methodName + “())));”);
} else if
(varInfoNames.contains(name)) {
// The object is a custom action with an
associated
// VariableInfo entry for this name.
// Get the class name and
then introspect at
runtime.
out
.printil(“out.write(org.apache.jasper.runtime.JspRuntimeLibrary.toString”
+
“(org.apache.jasper.runtime.JspRuntimeLibrary.handleGetProperty”
+
“(_jspx_page_context.findAttribute(\”"
+ name
+ “\”), \”"
+
property
+ “\”)));”);
} else {
StringBuilder msg =
new
StringBuilder(“jsp:getProperty for bean with name
‘”);
msg.append(name);
msg.append(
“‘. Name was not previously introd?
as per JSP.5.3″);

throw new
JasperException(msg.toString());
}

n.setEndJavaLine(out.getJavaLine());
}
看程序就能知道,就是因为在beanInfo,varInfoNames两个集合中找不到javabean的名字test,所以才抛错。原因是这个,但怎
么改呢?我对tomcat并不了解,先试着跟了跟代码,了解了一下,其实分析了一下这些代码的来龙去脉对解决这个问题并没有太多帮助,但可以多少了解点
tomcat的一些执行机制。这两个集合都是出自apache-tomcat-6.0.26-src\java\org\apache\jasper
\compiler\PageInfo.java文件中的PageInfo类,并且是每次在jsp转换成java源文件的时候,由apache-
tomcat-6.0.26-src\java\org\apache\jasper\compiler\Compiler.java文件中的
Compiler类调用的generateJava()方法来new一个PageInfo并在其构造函数参数中new一个
BeanRepository(beanInfo的类),varInfoNames则是在PageInfo的构造函数中new出来的,代码如下:
protected
String[] generateJava() throws Exception {

String[] smapStr =
null;

long t1, t2, t3, t4;

t1 = t2 = t3 = t4 = 0;

if
(log.isDebugEnabled()) {
t1 = System.currentTimeMillis();
}

//
Setup page info area
pageInfo = new PageInfo(new
BeanRepository(ctxt.getClassLoader(),
errDispatcher),
ctxt.getJspFile());
…….

PageInfo(BeanRepository beanRepository,
String jspFile) {

this.jspFile = jspFile;
this.beanRepository =
beanRepository;
this.varInfoNames = new
HashSet<String>();


而且我用windows搜索搜了一下给beanRepository赋值的函数addBean,并没有在源代码中找到可以添加javabean的代
码,varInfoNames也是一样。所以从程序上来说当forward或incl?一个新的jsp网页的时候,这两个集合中肯定不会含有前一个网
页设置的javabean的名字了,所以注定会抛错。
不过好在tomcat的archive做的好,我从上面又下到了tomcat5.016的源代码,看了下这个没问题的
Generator.java(jakarta-tomcat-5.0.16-src\jakarta-tomcat-jasper\jasper2
\src\share\org\apache\jasper\compiler目录中),它的代码如下:
p lic void
visit(Node.GetProperty n) throws JasperException {
String name =
n.getTextAttribute(“name”);
String property =
n.getTextAttribute(“property”);

n.setBeginJavaLine(out.getJavaLine());

if
(beanInfo.checkVariable(name)) {
// Bean is defined using useBean, introspect
at compile time
Class bean = beanInfo.getBeanType(name);
String beanName =
JspUtil.getCanonicalName(bean);
java.lang.reflect.Method meth
=
JspRuntimeLibrary.getReadMethod(bean, property);
String methodName =
meth.getName();
out.printil(
“out.write(org.apache.jasper.runtime.JspRuntimeLibrary.toString(”
+
“(((”
+ beanName
+ “)pageContext.findAttribute(”
+ “\”"
+ name
+
“\”)).”
+ methodName
+ “())));”);
} else {
// The object could be a
custom action with an associated
// VariableInfo entry for this name.
//
Get the class name and then introspect at
runtime.
out.printil(
“out.write(org.apache.jasper.runtime.JspRuntimeLibrary.toString”
+
“(org.apache.jasper.runtime.JspRuntimeLibrary.handleGetProperty”
+
“(pageContext.findAttribute(\”"
+ name
+ “\”), \”"
+ property
+
“\”)));”);
}

n.setEndJavaLine(out.getJavaLine());
}
一对比就发现了,这里根本没有什么varInfoNames,就是如果beanInfo中没有这个javabean,就会输出如下的java代码:
out.write(org.apache.jasper.runtime.JspRuntimeLibrary.toString(org.apache.jasper.runtime.JspRuntimeLibrary.handleGetProperty(pageContext.findAttribute(“test”),
“id”)));
而在tomcat
6.026中,就因为这里抛错而不能生成java代码。所以我就按tomcat5.016的样子进行了修改(实际上在5.016的版本中,根本没有
varInfoNames这个东西的存在),去掉了对varInfoNames的判断和最后抛错的else部分,变成跟5.016相同的样子。然后按照
tomcat源代码文件夹中的BUILDING.txt文件中的指示,在源文件目录中使用ant进行编译。要编译tomcat源文件首先得下载安装
ant(去apache官网下来解压就行,为了使用文件可以设置一个path)。
先执行ant
download,它会下载一些编译需要的东西到C:\usr\share\java目录,然后再执行ant命令,就编译ok了,然后去C:
\tomcat\output\build\lib目录中,把新编译成的jasper.jar文件替换到tomcat安装目录中的lib中,至此,再执行
开头所说的那种情况的程序,就能正确显示,没有问题了。

那这样修改会不会对tomcat有什么影响,我觉得应该不会。其实这个bug在上面给的apache的bug网页上已经有人给出了修改方法,不过跟我的略
有不同,可以去svn.apache.org/viewvc这 个网址来看Konstantin
Kolinko是怎么改的这个文件。他是在systemprops.xml文件中添加了一个
org.apache.jasper.compiler.Generator.STRICT_GET_PROPERTY布尔类型的配置,然后在
Generator.java中进行了这样的修改:
添加了取得那个属性的方法:
/* System property that controls if
the requirement to have the object
82         * used in jsp:getProperty
action to be previously “introd?”
83         * to the JSP processor (see
JSP.5.3) is enforced.
84         */
85        private static final
boolean STRICT_GET_PROPERTY = Boolean.valOf(
86
System.getProperty(
87
“org.apache.jasper.compiler.Generator.STRICT_GET_PROPERTY”,
88
“tr”)).booleanVal();

然后抛错的部分的修改是:
if
(beanInfo.checkVariable(name)) {
1058                    // Bean is defined
using useBean, introspect at compile time
1059
Class<?> bean = beanInfo.getBeanType(name);
1060
String beanName = JspUtil.getCanonicalName(bean);
1061
java.lang.reflect.Method meth = JspRuntimeLibrary
1062
.getReadMethod(bean, property);
1063
String methodName = meth.getName();
1064
out.printil(“out.write(org.apache.jasper.runtime.JspRuntimeLibrary.toString(”
1065
+ “(((”
1066                            +
beanName
1067                            +
“)_jspx_page_context.findAttribute(”
1068                            +
“\”"
1069                            + name + “\”)).” + methodName +
“())));”);
1070                } else if (!STRICT_GET_PROPERTY ||
varInfoNames.contains(name)) {
1071                    // The object is a
custom action with an associated
1072                    // VariableInfo
entry for this name.
1073                    // Get the class name and then
introspect at runtime.
1074
out.printil(“out.write(org.apache.jasper.runtime.JspRuntimeLibrary.toString”
1075
+
“(org.apache.jasper.runtime.JspRuntimeLibrary.handleGetProperty”
1076
+ “(_jspx_page_context.findAttribute(\”"
1077
+ name
1078                            + “\”),
\”"
1079                            + property
1080
+ “\”)));”);
1081                } else {
1082
StringBuilder msg =
1083                        new
StringBuilder(“jsp:getProperty for bean with name ‘”);
1084
msg.append(name);
1085
msg.append(
1086                            “‘. Name was not previously
introd? as per JSP.5.3″);
1087
1088
throw new JasperException(msg.toString());
1089
}

在那个xml文件中添加的节点为:在<section name=”Jasper”>节点下添加:
<property
name=”org.apache.jasper.compiler. Generator.STRICT_GET_PROPERTY”>
95
<p>If <code>tr</code>, the requirement to have the
object referenced in
96          <code>jsp:getProperty</code>
action to be previously “introd?”
97          to the JSP processor, as
specified in the chapter JSP.5.3 of JSP 2.0 and
98          later
specifications, is enforced. If not specified, the specification
99
compliant default of <code>tr</code> will be
used.</p>
100
</property>

其实看了这个人的修改所做的注释,也就应该能明白为什么tomcat在6.x(当然可能还包括之前的5.x的版本,tomcat准确从什么时候改的我不清
楚)的版本中要对程序做这样的修改来抛错了。因为它是为了符合最新的jsp2.0的规范,也就是上面注释中说的chapter JSP.5.3 of JSP
2.0,为此我还专门下了一个jsp2.0 specification的pdf,果然在5.3节说的就是这个问题,原文如下:
The val of the
name attribute in jsp:setProperty and jsp:getProperty will
refer to an object
that is obtained from the pageContext object through its
findAttribute
method.
The object named by the name must have been
“introd?” to the JSP
processor using either the jsp:useBean action or a
custom action with an
associated VariableInfo entry for this name. If the
object was not introd? in this
manner, the container implementation is
recommended (but not required) to raise
a translation error, since the page
implementation is in violation of the
specification.
Note – A conseqnce
of the previous paragraph is that objects that are stored
in, say, the
session by a front component are not automatically visible to
jsp:set-
Property and jsp:getProperty actions in that page unless a
jsp:useBean action, or
some other action, makes them
visible.

所以我前面说,以前那种对javabean的用法其实是对最新jsp规范的违背,所以tomcat抛错是正确的,因为违背了jsp规范嘛,严格来说这根本
就不是一个bug。正确的用法其实应该是在b.jsp网页中使用比如<jsp:useBean id=”test” scope=”reqst”
class=”test.TestBean”></jsp:useBean>申明了javabean之后,再使用<
jsp:getProperty property=”id” name=”test”/>来取值,我也做过这样的试验,这样做在tomcat
6.026上面是能正确取到test的id属性的。

只是它这个为了严格顺应最新规范的改动对过去的用法就不兼容了,比如过去的某些jsp程序就运行不了了,抛错了,所以就成了bug。

在那个bug记录里最后也说了,从6.027版本以后将包含对这个问题的patch,所以让我们期待tomcat
6.027吧。

唉,想想自己真是点背,本来只是想看看jsp,学习一下嘛,谁知道就为这么点个屁事功能,居然会碰上tomcat的这个问题,结果是费了半天的劲,跟
tomcat又干了一仗,虽然也并不是没有收获,但这个本来就应该是开发tomcat的人的问题,也应该由他们来负责解决,他们当初就不该把程序做死了,
应该做个配置可以让tomcat对旧程序兼容。