FreeOZ论坛

标题: JavaBean在JSP中的应用 —— 小key出品 [打印本页]

作者: key    时间: 12-7-2009 16:11
标题: JavaBean在JSP中的应用 —— 小key出品
JavaBean是什么?一个practical的定义是:有getter/setter的POJO。
本文的目的是讨论在JSP中,如何使用JavaBean。
作者: key    时间: 12-7-2009 16:19
标题: 为什么要用JavaBean?
JSP是一种相对自由的页面技术,由于可以在页面上采用包括指令/directive,
声明/declaration,scriptlets等技术手段,在没有明确的设计convention约束下,
程序员可以写出各种各样稀奇古怪的JSP代码来。

我个人认为,JavaBean技术引入JSP中,主要是为了页面代码的规范化。
这和后来引入JSP的EL不同。EL的目的是为了页面表现与逻辑分离。而JavaBean
则没有这样的作用。

从这个意义上来说,JavaBean是一个相对落后的技术。面对强大的MVC架构,
JavaBean同学基本上可以回家看孩子去了。

不过,作为JSP技术中重要的一项,还是有必要了解一下这位同学的。
作者: key    时间: 12-7-2009 16:26
标题: 小豆的实质
JavaBean其实质是一些对象。在JSP网页上使用JavaBean,其主要的作用是
用来封装数据、共享以及参与一些逻辑操作。正是因为JavaBean可以方便地参与
了逻辑处理,所以,在各路豪杰大呼网页代码逻辑分离的今天,JavaBean技术
显得有点过时的原因。

说到底,JavaBean就是一些对象,即一些数据的封装。象其他数据一样,
JavaBean可以:
1. 在四种scope下共享
2. 利用JavaBean的快速构建机制,处理FORM数据
3. 利用JavaBean的序列化能力,实现一些简单的持久化操作
作者: key    时间: 13-7-2009 01:23
标题: 1. 数据共享
一般地,我们要实现数据共享,最简单的做法是通过
在不同的共享对象中采用setAttribute()/getAttribute()实现。
这是一个代码过程,相对自由。

而JavaBean技术则规范化了这个共享方法:通过采用
<jsp:useBean>
前指定对应的scope,从而实现在不同的范围内共享同一个JavaBean对象。

<jsp:useBean>
这是用来声明一个JavaBean的action标签。通过这个标签,我们可以声明
一个用于本页面的、共享于某个指定范围的JavaBean。

那这个JavaBean从哪里来呢?这会涉及下面几种可能的“来源”
1. 创建
2. 共享
3. 序列化

1. 创建
每个JavaBean的类必须提供一个public的无参数的构造方法。

写一个非常简单的jsp:
  1. <jsp:useBean id="helloBean" class="com.hello.HelloBean" />
复制代码
转译后的结果是:
  1. 53       com.hello.HelloBean helloBean = null;
  2. 54       synchronized (_jspx_page_context) {
  3. 55         helloBean = (com.hello.HelloBean) _jspx_page_context.getAttribute("helloBean", PageContext.PAGE_SCOPE);
  4. 56         if (helloBean == null){
  5. 57           helloBean = new com.hello.HelloBean();
  6. 58           _jspx_page_context.setAttribute("helloBean", helloBean, PageContext.PAGE_SCOPE);
  7. 59         }
  8. 60       }
  9. 61       out.write('\n');
复制代码
不难看出,在发现helloBean为null的时候,程序采用new操作直接生成一个新的helloBean对象。

2. 共享
从前面转译出来的代码就可以看到数据共享的特性了。

在前面的例子里,我采用了缺省的scope,所以转译器转译成PAGE_SCOPE。我还可以选择其他的scope,比如:
<jsp:useBean id="helloBean" class="com.hello.HelloBean" scope="request" />
转换出来的代码是:
  1. 53       com.hello.HelloBean helloBean = null;
  2. 54       synchronized (request) {
  3. 55         helloBean = (com.hello.HelloBean) _jspx_page_context.getAttribute("helloBean", PageContext.REQUEST_SCOPE);
  4. 56         if (helloBean == null){
  5. 57           helloBean = new com.hello.HelloBean();
  6. 58           _jspx_page_context.setAttribute("helloBean", helloBean, PageContext.REQUEST_SCOPE);
  7. 59         }
  8. 60       }
  9. 61       out.write('\n');
复制代码
如果scope是session
<jsp:useBean id="helloBean" class="com.hello.HelloBean" scope="session" />
转译出来的代码是:
  1. 53       com.hello.HelloBean helloBean = null;
  2. 54       synchronized (session) {
  3. 55         helloBean = (com.hello.HelloBean) _jspx_page_context.getAttribute("helloBean", PageContext.SESSION_SCOPE);
  4. 56         if (helloBean == null){
  5. 57           helloBean = new com.hello.HelloBean();
  6. 58           _jspx_page_context.setAttribute("helloBean", helloBean, PageContext.SESSION_SCOPE);
  7. 59         }
  8. 60       }
  9. 61       out.write('\n');
复制代码
如果scope是application
<jsp:useBean id="helloBean" class="com.hello.HelloBean" scope="application" />
转译出来的代码是:
  1. 53       com.hello.HelloBean helloBean = null;
  2. 54       synchronized (application) {
  3. 55         helloBean = (com.hello.HelloBean) _jspx_page_context.getAttribute("helloBean", PageContext.APPLICATION_SCOPE);
  4. 56         if (helloBean == null){
  5. 57           helloBean = new com.hello.HelloBean();
  6. 58           _jspx_page_context.setAttribute("helloBean", helloBean, PageContext.APPLICATION_SCOPE);
  7. 59         }
  8. 60       }
  9. 61       out.write('\n');
复制代码
3. 序列化
老实说,JavaBean的序列化在JSP应用中到底有多大的作用,我真的不知道。不过,作为一种简单的持久化手段,
还是挺有点意思的。

太晚了,明天再吹
作者: key    时间: 13-7-2009 10:11
标题: 2. 再谈数据共享
在楼上,我谈到采用class属性可以让JSP用一种保守的手段来建立数据共享:
  1. 如果在某个指定范围内已经存在这个id指定的数据,则通过getAttribute()来获得
  2. 如果不存在,则创建。
但这种保守的手段带来了一些问题:如果有些数据我们并不希望在当前JSP页面创建,
本页面只做consumer的角色,那又该怎么办呢?

另一个useBean的属性可以提供这种需求的一个解决方案:
缺省范围
<jsp:useBean id="helloBean" type="HelloBean" />
转译出来的结果:
  1. 53       com.hello.HelloBean helloBean = null;
  2. 54       synchronized (_jspx_page_context) {
  3. 55         helloBean = (com.hello.HelloBean) _jspx_page_context.getAttribute("helloBean", PageContext.PAGE_SCOPE);
  4. 56         if (helloBean == null){
  5. 57           throw new java.lang.InstantiationException("bean helloBean not found within scope");
  6. 58         }
  7. 59       }
  8. 60       out.write('\n');
复制代码
可以看出,如果采用type属性,转译出来的代码里不会再尝试创建新的对象;一旦
找不到指定的对象,马上会抛出InstantiationException。这有时也不是我们想要的

对于其他范围的转译结果,也就只改变line-55就可以了。

type属性的重要特点是让我们建立一个基于类层次的对象。虽然这个事class属性也基本可以办到,但因为class属性会导致类的创建,
在语义上不合。所以应该避免采用class属性来建立这种类层次对象访问。

有时我们希望这样的逻辑:
  1. 对象以某个父类的形式出现
  2. 当这个对象不存在时,则建个某个子类的对象
可以通过综合使用class和type属性来达到效果。
  1.   1 <jsp:useBean id="helloBean"
  2.   2     type="com.hello.HelloBean"
  3.   3     class="com.hello.HelloDerivedBean"
  4.   4     scope="request"/>
复制代码
上面的jsp代码,转译出来的结果是:
  1. 53       com.hello.HelloBean helloBean = null;
  2. 54       synchronized (request) {
  3. 55         helloBean = (com.hello.HelloBean) _jspx_page_context.getAttribute("helloBean", PageContext.REQUEST_SCOPE);
  4. 56         if (helloBean == null){
  5. 57           helloBean = new com.hello.HelloDerivedBean();
  6. 58           _jspx_page_context.setAttribute("helloBean", helloBean, PageContext.REQUEST_SCOPE);
  7. 59         }
  8. 60       }
  9. 61       out.write('\n');
复制代码

作者: key    时间: 13-7-2009 10:42
标题: 3. 序列化
Java的序列化是指使用java.io.ObjectInputStream.writeObject(),java.io.ObjectOutputStream.readObject()从
硬盘或某个指定的地方读入或写出指定的对象,实现对象的持久化。被操作的对象的类必须实现了序列化接口。

在JSP中,对象的序列化采用更高级的封装:java.bean.Beans.instantiate()。这个高级工具实质上也是调用
ObjectInputStream/ObjectOutputStream,但同时利用了ClassLoader,对于定制ClassLoader的JSP container
来说是很有作用的。同时,这个接口还hardcode了.ser作为持久化后的文件后缀,以及采用 . 连接的方式
建立一个类似类路径的持久化文件体系。

要实现JSP中JavaBean的持久化,我们需要用到type和beanName。其中beanName是持久化对象的路径的
点表示法。而type则是这个对象的类或父类。

为什么不用class呢?我们知道class属性在语义上有精确指定对象的创建类的意思,在语义上不是太合适。

下面是一个例子:
  1.   1 <jsp:useBean id="helloBean"
  2.   2     type="com.hello.HelloBean"
  3.   3     beanName="com.myser.helloBean"
  4.   4     scope="request"/>
复制代码
转译出来的代码是
  1. 53       com.hello.HelloBean helloBean = null;
  2. 54       synchronized (request) {
  3. 55         helloBean = (com.hello.HelloBean) _jspx_page_context.getAttribute("helloBean", PageContext.REQUEST_SCOPE);
  4. 56         if (helloBean == null){
  5. 57           try {
  6. 58             helloBean = (com.hello.HelloBean) java.beans.Beans.instantiate(this.getClass().getClassLoader(), "com.my    ser.helloBean");
  7. 59           } catch (ClassNotFoundException exc) {
  8. 60             throw new InstantiationException(exc.getMessage());
  9. 61           } catch (Exception exc) {
  10. 62             throw new ServletException("Cannot create bean of class " + "com.myser.helloBean", exc);
  11. 63           }
  12. 64           _jspx_page_context.setAttribute("helloBean", helloBean, PageContext.REQUEST_SCOPE);
  13. 65         }
  14. 66       }
  15. 67       out.write('\n');
复制代码

作者: key    时间: 13-7-2009 11:33
标题: 4. Request-time Attribute Value
<jsp:useBean>的几个属性中,有的能用Request-time Attribute Value,有的则不能。
其实从转译出来的代码就可以猜到谁能谁不能了。

beanName
beanName是一个作为java.beans.Beans.inistantiate()的字符串输入参数,显然,可以采用
Request-time Attribute的形式:
  1.   1 <jsp:useBean id="helloBean"
  2.   2     type="com.hello.HelloBean"
  3.   3     beanName="${param.hello}"
  4.   4     />
复制代码
转译出来的代码是:
  1. 53       com.hello.HelloBean helloBean = null;
  2. 54       synchronized (_jspx_page_context) {
  3. 55         helloBean = (com.hello.HelloBean) _jspx_page_context.getAttribute("helloBean", PageContext.PAGE_SCOPE);
  4. 56         if (helloBean == null){
  5. 57           try {
  6. 58             helloBean = (com.hello.HelloBean) java.beans.Beans.instantiate(this.getClass().getClassLoader(), (java.lang.String) org.apache.jasper.runtime.PageContextImpl.proprietaryEvaluate("${param.hello}", java.lang.String.class,
  7.     (PageContext)_jspx_page_context, null, false));
  8. 59           } catch (ClassNotFoundException exc) {
  9. 60             throw new InstantiationException(exc.getMessage());
  10. 61           } catch (Exception exc) {
  11. 62             throw new ServletException("Cannot create bean of class " + (java.lang.String) org.apache.jasper.runtime.PageContextImpl.proprietaryEvaluate("${param.hello}", java.lang.String.class, (PageContext)_jspx_page_context, null
  12.     , false), exc);
  13. 63           }
  14. 64           _jspx_page_context.setAttribute("helloBean", helloBean, PageContext.PAGE_SCOPE);
  15. 65         }
  16. 66       }
  17. 67       out.write('\n');
复制代码
但如果我使用下面的写法:
  1.   1 <jsp:useBean id="helloBean"
  2.   2     type="${param.type}"
  3.   3     beanName="${param.hello}"
  4.   4     />
复制代码
编译是不通过的。
作者: key    时间: 13-7-2009 12:14
标题: 5. <jsp:setProperty>
<jsp:setProperty>是用来设置bean的属性,设置的方法有两种:
1. 直接赋值
2. 以request中的参数进行赋值。

这样就对应了两个setProperty属性:
1. value
2. param

1. value
value很容易理解,可以直接提供一个字面值/常量,也可以用Request-time Attribute的形式进行赋值。
  1. <jsp:setProperty name="helloBean" property="vx" value="1" />
复制代码
这句JSP语句会转译成:
  1. 68       org.apache.jasper.runtime.JspRuntimeLibrary.introspecthelper(_jspx_page_context.findAttribute("helloBean"), "v ", "1", null, null, false);
复制代码
这个转译代码要做的是下面几个步骤:
1. 使用pageContext的findAttribute方法,找到指定id的bean
2. 利用JspRuntimeLibrary.introspecthelper()进行赋值

对于第一点,显然会引来两个推论:
1. 如果我在当前网页没在指定<jsp:useBean>,照样可以用<jsp:setProperty>进行处理
2. 如果一个不小心,我们通过各种方式,比如scriptlet,设置了某个级别的scope内的一个attribute,
如果我们尝试去用<jsp:setProperty>来设置一个更高级别scope的一个JavaBean,将被低级别的
JavaBean所屏蔽。原因是findAttribute()会从page开始查找,先找低级别的scope,再找高级别的scope。

当然,如果你还要考虑性能之类,想到的问题可能会更多。

这是Tomcat 6.0的实现,我不知道其他版本或JSP container的转译结果。

下面转译结果会让你多少有点吃惊:
  1. <%
  2.     String value="1";
  3.     String hello="hello";
  4.     String prop = "prop";
  5. %>
  6. <jsp:setProperty name="<%=hello%>" property="<%=prop%>" value="<%=value%>" />
复制代码
转译出来的结果是:
  1. 54     String value="1";
  2. 55     String hello="hello";
  3. 56     String prop = "prop";
  4. 57
  5. 58       out.write('\n');
  6. 59       org.apache.jasper.runtime.JspRuntimeLibrary.handleSetProperty(_jspx_page_context.findAttribute("<%=hello%>"),
  7.     "<%=prop%>",
  8. 60 value);
复制代码
显然,除了value能顺利转译外,其他两个属性都没有转择,而是采用字面值。
我估计原因是JSP Specificiation中指明name和property两个值是不能用Request-time Attribute来设定的。

另外,如果试用EL的${name}方式来设置value也是不成功的。
      org.apache.jasper.runtime.JspRuntimeLibrary.handleSetPropertyExpression(_jspx_page_context.findAttribute("hello"),
"prop", "${value}", _jspx_page_context, null);

情况真的是这样吗?
我们来看一下JspRuntimeLibrary.handleSetPropertyExpression()的实现:

被注释掉的一段实现
  1. 617     // handles <jsp:setProperty> with EL expression for 'value' attribute
  2. 618 /** Use proprietaryEvaluate
  3. 619     public static void handleSetPropertyExpression(Object bean,
  4. 620         String prop, String expression, PageContext pageContext,
  5. 621         VariableResolver variableResolver, FunctionMapper functionMapper )
  6. 622     throws JasperException
  7. 623     {
  8. 624     try {
  9. 625             Method method = getWriteMethod(bean.getClass(), prop);
  10. 626         method.invoke(bean, new Object[] {
  11. 627         pageContext.getExpressionEvaluator().evaluate(
  12. 628             expression,
  13. 629             method.getParameterTypes()[0],
  14. 630                     variableResolver,
  15. 631                     functionMapper,
  16. 632                     null )
  17. 633         });
  18. 634     } catch (Exception ex) {
  19. 635         throw new JasperException(ex);
  20. 636     }
  21. 637     }
  22. 638 **/
复制代码
当前使用的实现版本:
  1. 639     public static void handleSetPropertyExpression(Object bean,
  2. 640         String prop, String expression, PageContext pageContext,
  3. 641     ProtectedFunctionMapper functionMapper )
  4. 642         throws JasperException
  5. 643     {
  6. 644         try {
  7. 645             Method method = getWriteMethod(bean.getClass(), prop);
  8. 646             method.invoke(bean, new Object[] {
  9. 647                 PageContextImpl.proprietaryEvaluate(
  10. 648                     expression,
  11. 649                     method.getParameterTypes()[0],
  12. 650             pageContext,
  13. 651                     functionMapper,
  14. 652                     false )
  15. 653             });
  16. 654         } catch (Exception ex) {
  17. 655             throw new JasperException(ex);
  18. 656         }
  19. 657     }
复制代码
其实line-647 ~ 651就是做EL翻译的。但prop则没有做任何转换。
从这个地方可以看出一个bug
如果你使用的值本身就是一个字面值,比如${value},那就很可能中招了。
这个问题主要是因为Tomcat的开发者没有在转译器阶段来实现EL转换,
而是把这个转换延迟到运行时,这是很不智的做法,估计下一个版本就会
改进吧。
作者: shenlh    时间: 26-8-2009 21:25
标题: 回复
今天还没有来得及拜读全文,但个人认为Javabean应该是其他java技术,特别是框架技术使用反射机制的基础。
欢迎指正。




欢迎光临 FreeOZ论坛 (https://www.freeoz.org/ibbs/) Powered by Discuz! X3.2