Head First JSP---随笔六

无脚本的JSP

Web页面设计人员没必要懂Java,所以我们使用EL表达式。(这一章中所说到的性质,是指对象中的字段


使用表达式语言(EL)和标准动作构建JSP页面

7.1 使用EL中的顶级变量编写一个代码片段。包括以下隐式变量:pageScope,requestScope,sessionScope和applicationScope;param和paramValues;header和headerValues;cookies;以及initParam。
7.2 使用以下EL操作符编写一个代码片段:性质访问操作符(.)和集合访问操作符([])。
7.3 使用以下EL操作符编写一个代码片段:算术操作符、关系操作符和逻辑操作符。
7.4 对于EL函数:使用EL函数编写一个代码片段;明确或创建用于声明EL函数的TLD文件结构;明确或创建一个代码示例来定义EL函数。
8.1 给定一个设计目标,使用以下标准动作创建一个代码片段:jsp:useBean(有以下属性:id、scope、type和class),jsp:getProperty和jsp:setProperty(包括所有属性组合)。
8.2 给定一个设计目标,使用以下标准动作创建一个代码片段:jsp:include,jsp:forward和jsp:param。
6.7 给定一个特定的设计目标,要求将一个JSP片段包含在另一个页面中,编写JSP代码使用最合适的包含机制(include指令或< jsp:include >标准动作)


使用与bean相关的标准动作

我们先假设有一个名为person的属性,它的值是一个Person类,这个类在foo包下并且有一个属性是name和一个方法是getName()。这个person对象在request作用域中,则如下。

使用脚本时:

<html><body>
<%  foo.Persion p = (foo.Person) request.getAttribute("person");%>
Person is: <%= p.getName()%>
</body></html>

使用标准动作

<html><body>
<jsp:useBean id="person" class="foo.Person" scope="request"/>
Person is: <jsp:getProperty name="person" property="name">
</body></html>

上面两段代码是等价的,不过我们说过不准使用脚本代码


分析jsp:useBean和jsp:getProperty

先看jsp:userBean,id是声明bean对象的标识符,class是声明对象的类型,scope说明对象的作用域(从request中拿出一个属性名为”person”的对象,这对象的类型是foo.Person类型的)。

在看jsp:getProperty,name表示具体的bean对象(与useBean中的id相匹配),property是只Person中的属性(从useBean中拿出一个id名为”person”的对象,获取这个对象的”name”属性)。


jsp:useBean还能创建一个bean

_jspService()


jsp:useBean可以有体

正如看到的,useBean可以有体。当然下面还有一个jsp:setProperty,它可以出现在体外面,功能与getProperty相反。setProperty是设置属性。

<jsp:useBean id="person" calss="foo.Person" scope="page">
    <jsp:setProperty name="person" property="name" value="Fred">
</jsp:useBean>

放在体内的代码,转换成servlet将会在这个对象为null的时候为这个对象new完之后再执行相应的操作。如果这个对象不为null的话就不会执行体内的操作

jsp:useBean有体时生成的serlvet:
jsp
可以看出最后一行写的好复杂,但它的作用和person.setName("Fred")等价。


多态的bean引用

我们先看看我们原来jsp:useBean生成的servlet代码:
原来的
继承的UML图:
继承UML
如果我们使用跟原来一样的代码(很明显,抽象类不能实例化):
原来的代码
我们增加一个type属性(可以是一个class类型、抽象类或者一个接口):
这里写图片描述

这里指出:如果只有一个type属性,没有class属性的话,结果也会报错!
警告
这里指出scope属性的默认值是”page”


直接从请求到JSP,没有经过servlet

假设我们的表单是:
表单

想要获得请求的参数就需要如下:
图片


param属性

要获取请求的参数时,如果要像上面那样做,明显是不好的。

利用param属性,可以把bean的性质值设置为一个请求参数的值。只需指定请求参数!如下:
这里写图片描述


更好的方法

更好的方法
这样就方便了许多!!


还有更好的···

更好的
真的特别酷!一句话搞定所有事情。


自动转换基本类型的性质

如果是纯数字,那么String会自动转换成int。
自动转换


如果属性不是String或基本类型

例如以下这种场景:
这里写图片描述
我需要打印出Person的Dog的name,又该如何呢?

这时候我们就需要表达式语言(EL)${person.dog.name}

它等价于<%= (foo.Person) request.getAttribute("person").getDog().getName()%>


JSP表达式语言(EL)剖析

这里写图片描述
如图中所说明的,除了pageContext比较特殊以外,其他都是映射


使用点号.和括号[]

我们可以用${person.name}访问一个bean和map。同样的用${person["name"]}也可以访问一个bean和map。

那如果person是一个数组或者是一个List呢?又该如何?

可以使用括号。如下:
这里写图片描述

这里指出:数组和List中的String索引会被强制转换为int(如musicList[“1”]等价于musicList[1])。

这里还指出:如果[ ]中不是String直接量,那么就会进行计算(例如:musicList[0]=1,那么musicList[musicList[0]] = musicList[1])。


EL隐式对象

这里写图片描述
我们再次注意到最后一个pageContext对象不是一个map。那它拿来做什么呢?(这很重要!)


EL中的请求参数

想要从请求中获取对应的参数(param),如下:
这里写图片描述
看上去不错,我们在试试获取”host”首部(header):
这里写图片描述
我们再来获取一个HTTP请求方法:
这里写图片描述
好像不太行,获取不到request的方法


requestScope不是请求对象

隐式的requestScope只是请求作用域属性的一个Map,而不是request对象本身!

想要得到HTTP方法,就需要先得到一个request对象。所以在于我们如何获取这个request对象呢?

通过pageContext来获得其他的一切。例如:${pageContext.request.method}这样就可以通过request对象获取到HTTP方法。

需要注意的:
这里写图片描述


作用域隐式对象能救你

会出现这样一种情况:request.setAttribute("foo.person",p);。我们如何在JSP中获取这个键所对应的值呢?

显然,用${foo.person.name}是不行的,所以我们的隐式作用域对象派上用场了!可以使用:${requestScope["foo.person"].name},完美“救场”!


得到Cookie和初始化参数

我们除了cookie和初始化参数的相应隐式对象外,其他隐式对象我们都已经了解了。接下来我们说明cookie和初始化参数的隐式对象。

打印“userName” cookie的值
脚本来完成:

<%
    Cookie[] cookies = request.getCookies();

    for(int i=0;i<cookies.length;i++){
        if((cookies[i].getName().equals("userName"))){
            out.println(cookies[i].getValue());
        }
    }
%>

用EL:${cookie.userName.value}就可以了。

打印一个上下文初始化参数的值
在DD中配置:

<context-param>
    <param-name>mainEmail</param-name>
    <param-value>[email protected]</param-value>
</context-param>

然后再用脚本:<%= application.getInitParameter("mainEmail")%>就可以了。

EL:${initParam.mainEmail}就可以了。

该注意的地方:
这里写图片描述


EL函数

如果我们需要在JSP中用EL表达式去调用一个方法来返回一个值,那又该如何呢?

这里指出:EL函数可以有参数,与普通方法一样,只是定义TLD时参数列表是(包名.类型)。例如:int function(java.util.Map)

需要做4步事情:
这里写图片描述
一幅图解释工作过程:
这里写图片描述
部署环境:
这里写图片描述
需要注意:(函数名不一定要和EL中的函数名一致,EL的函数名必须与function标签下的name标签的内容处于一致)
这里写图片描述


另一些EL操作符

另一些操作符


EL能妥善处理null值

假设没有一个名为“foo”的属性,但确实有一个名为“bar”的属性,而且这个“bar”没有名为“foo”的性质或键

如下(注意,foo是null,bar是一个对象,bar[foo]不是bar[“foo”],这里指出:如果是bar[“foo”]则将报错!):
这里写图片描述


JSP表达式语言(EL)复习

  1. EL表达式总是用大括号括起,而且前面有一个美元符($)前缀(例如:${expression})。
  2. 表达式中第一个命名变量要么是一个隐式对象,要么是某个作用域(页面、请求、会话、应用作用域)中的一个属性
  3. 点号操作符允许你使用一个Map键或一个bean(点号操作符的第一个字符后面可以有数字,但是不能有其他符号)。
  4. 点号右边只能放合法的Java标识符(例如:${foo.1}是不允许的)。
  5. [ ]操作符比点号功能更强大,因为利用[ ]可以访问数组和List,可以把包含命名变量的表达式放在括号里(例如:${musicList["name"]}),而且可以作任意层次的嵌套(例如:${musicList[musicList["name"]]})。
  6. 例如,如果musicList是一个ArrayList,可以是${musicList[0]}${musicList["0"]}来访问列表中的第一个值。EL并不关心列表索引加不加引号。
  7. 如果括号里的内容没有用引号引起来,容器就会进行计算。如果确实放在引号里,而不是一个数组或List的索引,容器就会把它看做是性质或键的直接量名(也就是bean或者Map)。
  8. 除了一个EL隐式对象(PageContext)外,其他隐式对象都是Map。从这些Map隐式对象可以得到任意4个作用域的属性(requestScope,responseScope,sessionScope,applicationScope)、请求参数值(param)、首部值(header)、cookie值和上下文初始化参数(initParam)。非映射的隐式对象pageContext,它是PageContext对象的一个引用。
  9. 不要把隐式EL作用域对象(属性的Map)与属性所绑定的对象混合一谈(例如:requestScope和request对象不是同一个东西,这种作用域对象有4个)。
  10. EL函数允许你调用一个普通Java类中的公共静态方法(函数名不一定与具体的方法名匹配,这个根据TLD文件来做)。
  11. 使用TLD(标记库描述文件)将函数名映射到一个具体静态方法。
  12. 要在JSP中使用函数,必须使用taglib指令声明一个命名空间(例如:<%@ taglib prefix="mime" uri="/WEB-INF/foo.tld"%>)。
  13. EL表达式调用函数的方式:${前缀:方法()}(例如:${mime:function()})。

可重用的模板部件

如果我有234个JSP文件并且它们都有同一个导航栏,如果我需要改变导航栏的代码,那么234个JSP都要改。这简直是噩梦

JSP中有一个对应的处理机制,这就是包含(include)

形式如下:

<html><body>
<!-- 在这里插入页眉文件 -->
Welcome to our size...
blah blah blah more stuff here...
<!-- 在这里插入页脚文件 -->
</html></body>

include指令

include指令告诉容器:复制所包含文件中所有内容,在把它粘贴到这个文件中,而且就放在这里…
这里写图片描述


jsp:include标准动作

这里写图片描述


上述两者的内部原理并不相同

<jsp:include page="Header.jsp" />标准动作和<%@ include file="Header.jsp" %>指令的内部原理并不同。
这里写图片描述


include指令在转换(.jsp变成.java)时发生jsp:include标准动作在运行时发生

很容易就能明白,指令是将其变为out.write()输出,而标准动作调用方法

这里指出:指令的效率会比标准动作的高


对应第一个请求的include指令

执行过程如下:
这里写图片描述


对应第一个请求的jsp:include标准动作

执行过程如下:
这里写图片描述


两个注意的地方

属性名不一样:
这里写图片描述
位置是否敏感(include指令是位置敏感的):
这里写图片描述

这里还说明了一个问题:不要把开始和结束HTML和body标记放在可重用部件中!设计和编写布局模板时,要假设它们会包含在其他页面中


使用jsp:param定制包含的内容

如果我们希望页眉上有一个与上下文相关的子标题,它要依相应的页面而定,该怎么做呢?

两种方法:

  1. 笨方法:把子标题的信息放在主页面上,作为页眉之后的第一个内容,也就是紧挨着放在页面中包含页眉的include的后面。
  2. 更聪明的办法:把子标题信息作为新的请求参数传递给所包含的页面!

第二种办法如下所示:
这里写图片描述
我们注意到jsp:include标准动作的可以增加(或替换)请求参数,供被包含的片段使用。


jsp:forward标准动作

有这样一个问题,如果我的客户访问我的页面,但是还没有登录,我想让他转跳到登录页面,该怎么做呢?

虽然这不是MVC中视图的任务(很明显应该给控制器处理),但我们还是提供了这么一种操作,就是<jsp:forward>


有条件的转发

代码如下:
这里写图片描述
结果如下:
这里写图片描述

这里指出:利用<jsp:forward>,缓冲区会在转发之前清空

看下面这个有趣的问题:
这里写图片描述
是的,毫无疑问的你收货了一个异常


初识JSTL标记

由于我们不能在JSP页面写脚本,那么如果我们需要对页面测试又该怎么办呢?

JSTL标记解决这个问题!如下:
这里写图片描述
我们看到,有一个taglib指令,并且它的prefix的属性值是”c”。我们先放着!


Bean相关标准动作复习(要点)

  1. <jsp:useBean>标准动作会定义一个变量,它可能是一个现有bean属性的引用,如果还不存在这样一个bean,则会创建一个新的bean,这个变量就是新bean的引用。
  2. <jsp:useBean>必须有一个“id”属性,这个属性声明了JSP中引用bean时所用的变量名。
  3. 如果<jsp:useBean>中没有“scope”属性,作用域默认为页面(page)作用域。
  4. “class”属性是可选的,它声明了创建一个新bean时使用的类类型。这个类型必须是公共的、非抽象的,而且有一个公共的无参数构造函数。
  5. 如果在<jsp:useBean>中放了一个“type”属性,bean必须能强制转换为这种类型。
  6. 如果有一个“type”属性,但是没有“class”属性,bean必须已经存在,因为你没有指定为这个新bean实例化哪个类类型。
  7. <jsp:useBean>标记可以有一个体,体中的内容会有条件的运行,只有当创建一个新的bean作为<jsp:useBean>的结果时,才会运行体中的内容(这说明,指定或默认作用域中不存在有该“id”的bean)。
  8. <jsp:useBean>体的主要作用是使用<jsp:setProperty>设置新bean的性质。
  9. <jsp:userProperty>必须有一个name属(它要与<jsp:useBean>的“id”匹配),还有一个“property”属性必须有一个具体的性质名,或者是通配符“*”。
  10. 如果没有包含“value”属性,只有当一个请求参数的名字与性质名匹配时,容器才会设置性质值。如果“property”属性使用通配符(*),容器会为所有与某个请求参数名匹配的性质设置值。(其他性质不受影响)
  11. 如果请求参数名与性质名不同,但是你想把性质的值设置为请求参数值,可以在<jsp:setProperty>标记中使用“param”属性。
  12. <jsp:setProperty>动作使用自省将“性质”匹配到一个JavaBean设置方法。如果性质是“*”,JSP将迭代处理所有请求参数来设置JavaBean性质。
  13. 性质值可以是String或基本类型,<jsp:setProperty>标准动作会自动转换。

包含复习(要点)

  1. 通过使用两种包含机制之一(include指令或<jsp:include>标准动作),可以利用可重用的组件建立页面。
  2. include指令在转换时完成包含,只发生一次。所以如果包含的内容已经部署后不太可能改变,使用include指令就很合适。
  3. include指令实际上只是复制被包含文件中的所有内容,把它粘贴到有include指令的页面中。容器把所有被包含文件的内容合并起来,只编译一个文件来生成servlet。在运行时,有include指令的页面将成为一个“大”页面,就像是你自己把所有源代码键入一个文件中一样。
  4. <jsp:include>标准动作在运行时把包含页面的响应包含到原页面中。所以如果包含的内容在部署之后可能更新,include标准动作就很实用,此时不适用include指令。
  5. 这两种机制都能包含静态html页面,也可以包含动态元素(例如,有EL表达式的JSP代码)。
  6. include指令是唯一一个对位置敏感的指令,所包含的内容会插入到页面中include指令所在的位置。
  7. include指令和include标准动作的属性命名不一致,指令使用“file”属性,而标准动作使用“page”属性。
  8. 在你的可重用组件中,一定要去掉开始和结束标记(html和body)。否则,生成的输出会有嵌套的开始和结束标记,对于这种嵌套的开始和结束标记,并非所有浏览器都能处理。设计和构造可重用部件时,要知道它们会包含/插入到别的页面中。
  9. 可以在<jsp:include>的体中使用<jsp:param>标准动作设置(或替换)请求参数,用来定制所包含的文件。
  10. 尽管在这一章没有介绍,但是要知道,<jsp:param>也可以用在<jsp:forward>标记的体中。
  11. <jsp:param>只能放在<jsp:include><jsp:forward>标准动作中。
  12. 如果<jsp:param>中使用的参数名已经有一个值(作为请求参数),新值会覆盖原来的值。否则,就会向请求增加一个新的请求参数。
  13. 对被包含资源有一些限制:它不能改变响应状态码或设置首部。
  14. <jsp:forward>标准动作可以把请求转发到同一个Web应用中的另一个资源(就像使用RequestDispatcher一样)。
  15. 发送转发时,会首先清空响应缓冲区!请求转发到目标资源会先清空输出。所以转发前挟制响应的所有内容都会清掉。
  16. 如果在转发之前先提交响应(例如,通过调用out.flush()),会把刷新输出的内容发送给客户,但是仅此而已。不会发送转发,原页面的余下部分不会得到处理。

本章完

猜你喜欢

转载自blog.csdn.net/qq_37340753/article/details/81388411