为什么不要在JSP中编写Java语言, 一切都是为了解耦. JSP技术中提供了EL表达式语言以及可以引入标签库来让JSP页面变得更加明晰, 也不会和Java代码捆绑在一起.

不过相比于使用Java代码, EL表达式和标签库有很多细节需要了解. 总体而言, 还是比直接编写Java代码要好太多了.

为何要使用EL和标签的一大原因是方便取出数据, 从前边我们知道, 其实页面的关键因素在于通过模型提供的数据, 页面大部分时候只是一个框架, 需要数据去填充. 而且对于Java来说, 属性更多的时候不是一个字符串, 而是一个数据对象, 需要获取然后拆解后展示.

如果可以将取数据的Java脚本通过EL和标签来简化, 就可以方便快捷的编写代码了.

EL表达式其实也是一种标签, 还可以开发自定义JSP标签来使用, 这一部分等到后边专门学习. JSP的动作也是一大要素, 要知道常用的几个

最后一点要注意, 就是EL表达式究竟是什么, 其实EL表达式是一个对象. 将 ${var} 看成是一个对象,而不是一个字符串, 这样对理解标签和JSP标准动作就很有帮助. 在页面中EL表达式的位置显示的结果, 是编译的时候将这个对象的toString()打印到响应中的结果, 在JSP内部, 则是将其当成一个对象来处理的.

  1. 在使用EL表达式之前…
  2. EL表达式
  3. EL隐式对象
  4. EL操作符
  5. JSP动作元素

在使用EL表达式之前…

在使用El表达式之前, 有人就想到了可以将数据对象先分离成一个Bean, 在JSP中所有需要用到的地方, 用动作标签来获取, 这样免得编写大量重复的代码.

在之前我们的代码中, 给request对象放入了一个键名为penguin的Penguin对象, 如果按照原始的方法取出其名称, 应该编写如下的代码:

<% Penguin penguin = (Penguin) request.getAttribute("penguin"); %>
<%= penguin.getName() >

使用EL表达式语言的前提就是, 我们传入的对象是一个JavaBean, 必须带有一个空参构造器, 还带有getter和setter方法的标准Bean, 获取的属性一定是getXxx方法, Xxx一定是内部同名属性的首字母大写. 博主在这里就是吃了亏, 忘记设置无参构造器, 结果在JSP动作那里一直报错. 还是要按照标准来创建Bean才行.

好在JavaBean的数据对象已经是大家日常开发的规范了. 现在可以将request.getAtttribute()抽出来, 用一个名称来代表数据对象:

<jsp:useBean id="penguin" class="com.example.domain.Penguin" scope="request"/>

上边这句的意思就是在request中按照id的值获取对象, 这个对象的类型是Penguin, 这个标签就代表了request.getAtttribute("penguin")这行代码.

这个叫做JSP动作元素.

EL表达式的通用写法是 ${var} , 这个表达式的结果就是字符串写在当前位置.

对于上边的 <%= penguin.getName() >, 可以不需要之前获取对象, 直接写成 ${penguin.name}

现在回头看看, 就知道为什么要在使用EL表达式之前, 要让数据对象符合JavaBean的标准, 而且域都返回字符串. ${penguin.name}就是从域里以penguin键名查找数据对象, 然后对其调用getName()方法, 然后把结果替换掉当前的表达式.

EL表达式

EL表达式中的最左边一项, 是一个属性名或者是一个EL的隐式对象. 这个属性名, 可以是四个作用域里存在的任何一个. 而EL隐式对象和JSP的隐式对象是不同的, 仅仅只有PageContext是指向同一个引用.

之后如果有点操作符, 则左边的必须是一个映射性质或者一个JavaBean, 可以通过点右边的名称, 从其中拿出对应的值, 或者getXxx方法获取值.

操作符右侧的名称必须符合java的变量规范.

在获取到最右边的符号指示的对象后, EL表达式就将这个对象的.toString()的结果显示在当前的地方.

看到这里其实就可以明白了, EL表达式是可以自动拆解Map对象和JavaBean的, 而且可以嵌套拆解到底.

除了点操作符, 还可以使用中括号操作符, 比如上边的代码可以改成: ${penguin["name"]}., 这个时候左边的变量除了映射和JavaBean之外, 可以支持Map, List 或者数组, 对于带有索引的List和数组, 可以不用字符串, 直接使用数字索引.

这样就极大的扩展了EL表达式能够支持的集合, 我们很多时候也确实是传递一个集合对象给视图, 而不简简单单是单个对象.

注意, ${penguin["name"]}.中现在是一个字符串常量, 刚才说了还可以是数字常量, 如果传一个变量名称也是可以的, 这个变量名称也会自动像EL一样到域中去查询然后取出值. 嵌套使用也是可以的.

EL隐式对象

EL隐式对象都是映射, 也就是可以通过名称从其中取出内容. 其实除了PageContext之外, 其他的隐式对象都是Map类型.

  1. pageScope, 四大作用域之一
  2. requestScope, 四大作用域之一
  3. sessionScope, 四大作用域之一
  4. applicationScope, 四大作用域之一
  5. param, 请求参数
  6. paramValues, 请求参数, 与上一个不同的是这个对应一个参数有多个值, 实际类型是Map<String, String[]>.
  7. header, 头部信息
  8. headerValues, 头部信息, 也是对应一个头部多个值
  9. cookie, cookie的键和cookie对象组成的Map
  10. initParam, 注意, 是ServletContext的初始化参数, 也就是应用的初始化参数, 不是当前servlet的.

有了隐式就能获取更多的数据了, 上边的${penguin.name}可以写成${requestScope.penguin.name}, 相比之前的写法, 这个是明确指定了requestScope中来查找, 之前的写法是按顺序依次查找.

隐式对象的还一大好处就是, 没有必要一定要控制器给三个作用域上附加属性才能传递给JSP了, 从前边可以知道, 表单提交的内容和URL参数通过param都可以访问到, 所以有了EL表达式之后, 可以直接将JSP作为处理数据的对象, 而不仅仅是视图层了.

回头再想想那些Servlet对象的很多getXxx方法, 此时就都可以派上用场了, 对于简单的程序, 可以直接使用JSP来处理, 未必要通过控制器了.

EL操作符

既然是一个表达式, 除了对单个变量显示之外, EL还能执行一些简单的运算. EL支持的操作符如下:

  1. + – * / div % mod, 都是数学运算符, 如果/0会得到INFINITY
  2. && and || or ! not, 都是逻辑运算符, 可以用符号也可以用单词
  3. == eq != ne < lt > gt <= le >= ge, 关系操作符, 可以使用符合也可以使用单词

既然操作符有一些是单词, 那么你肯定会想到这些也是保留字, 并不能随便使用. 确实是的, 除此之外, 还有true false null instanceof empty 等操作符.

其中empty可以用来查看一个结果是否为空, 比如 ${empty requestScope.peng}, 但其实没有peng属性, 这个表达式就会显示成true.

JSP动作

JSP的标准动作就是之前说的:

<jsp:useBean id="penguin" class="com.example.domain.Penguin" scope="request"/>

这一类以<jsp:开头的标签都叫做JSP的动作, 是由JSP规范确定的. 其本质就是一种特殊的带有属性的XML标签, 其背后是一段Java代码, 通过传递的属性进行操作.

也可以将其理解为JSP的内置标签, 和 <% 系列标签都属于JSP自带的. 从一个原始JSP文件的角度来看, 其中支持EL表达式, <%@指令, <%scriplet, <%=表达式, JSP动作元素五大不属于HTML的要素.

JSP的动作标签因为有了JSTL库之后, 就不大被使用了, 这里了解一下部分JSP的动作标签.

  1. <jsp:useBean <jsp:getPropertry, 第一个用于创建一个Bean或者获取已经存在的数据对象, 用法为:
        <jsp:useBean id="penguin" class="com.example.domain.Penguin" scope="request"/>
    

    这其中的id相当于变量名称同时也是键名, 类别指定了这个变量的类型, scope则表示四大作用域之一, 上边这句话的意思相当于从request作用域获取penguin键对应的对象, 如果没有, 就新建一个
    这要求penguin必须是一个Bean, 即有无参构造器和getter setter方法. 在新建或者获取之后, 就可以来读取其中的内容:

        <jsp:getProperty name="penguin" property="name"/>
    

    这个动作元素中的name属性一定要是通过jspBean定义的或者已经存在的变量名称, property表示取出的属性名, 实际上相当于调用getName()方法.
    <jsp:useBean除了自闭合之外, 还可以有主体, 其中包含<jsp:setProperty的话, <jsp:setProperty只会在新建Bean的时候才设置属性, 如果是已经存在的Bean, 就不会设置属性.

  2. <jsp:setProperty, 这个用来设置对应的属性, 需要这个Bean有此属性和对应的setter方法才可以设置, 用法如下:
        <jsp:setProperty name="penguin" property="name" value="gugugug"/>
    

    其中的name必须与之前的id或者变量名匹配, property是你想改变的域名, value是值, 直接输入的话就是按字符串解析. 注意值也可以是一个对象, 只要和对象的类型匹配即可, Penguin类中的name是一个字符串, 因此可以给其一个任意字符串的结果, 所以可以用一个EL表达式来传给value属性, 例如:

        <c:set target="${penguin}" property="name" value="${pageContext.request.method}"/>
    

    除此之外, <jsp:setProperty还有一个param属性, 用来将页面接收到的参数的同名属性设置到对应的Bean上.这个下边详细学习.

  3. 多态的<jsp:useBean, 需要添加一个type属性, 指向接口类或者超类, class必须指向能够实际实例化的类, 而且这个类必须有一个无参构造器. 如果仅仅只有type而没有class, 则当前pageContext必须能够查找到一个和id属性相同的变量, 其类型和type属性相同, 否则便会报错.
  4. <jsp:useBean的scope属性默认是pageContext作用域, pageContext其实相当于this. 如果需要其他作用域, 可以显式指定, 就像刚才显式指定为request作用域一样.

setProperty的param属性

在之前的EL表达式中, 因为有了隐式对象, 可以直接获取到请求参数. setProperty的param属性更加牛逼, 可以通过对应关系直接就给Bean设置上.

我们创建一个带有字符串类型的 userName 域的User类, 让其成为一个标准的Bean, 代码就不写了, 然后让一个表单直接发送POST请求到JSP来, 其中包含id和userName属性.

如果想要直接获取到POST过来的User对象, 可以采取如下写法:

<jsp:useBean id="user" class="com.example.domain.User">

    <jsp:setProperty name="user" property="userName" param="userName"/>

</jsp:useBean>

只要表单POST进来有一个userName属性, 就会被设置到新建的user变量名称对应的Bean上, 如果已经存在user对象, 就不会设置了.

更牛逼的是, 如果POST进来的表单的属性和user的属性名称一致(就像这个例子), 不写param参数也可以, 但这个仅限于一个属性, 上边的例子写成下边这样也可以工作:

<jsp:useBean id="user" class="com.example.domain.User">

    <jsp:setProperty name="user" property="userName"/>

</jsp:useBean>

如果POST进来的属性完全一致, 是指的POST进来的表单的所有属性都能够对应到Bean的属性上, 那么不仅不需要param, 连property的名称都可以只使用一个通配符*即可, 我们给User类再添加一个password字段和一个int类型的userId字段, 然后用表单POST进来, 修改JSP文件为如下:

<jsp:useBean id="user" class="com.example.domain.User">

    <jsp:setProperty name="user" property="*"/>

</jsp:useBean>

<p>Post进来的用户是:<jsp:getProperty name="user" property="userName"/></p>
<p>Post进来的密码是:<jsp:getProperty name="user" property="password"/></p>
<p>Post进来的ID是:<jsp:getProperty name="user" property="userId"/></p>

*的含义是自动匹配, 即使Person类的字段比POST进来的多也没有关系, 只要有对应关系都可以自动装配上去. 只要getProperty不要去获取没有的属性就可以了.

如果测试上边代码, 会发现POST进来的字符串类型的userId也会正常显示, 这说明后台发生了类型转换. 实际上容器在这里可以自动转换String和基本类型, 扩展类型就不能直接这么操作了.

EL表达式就是因为JSP标准动作元素无法方便的展开对象才出现的.

之前学到的 <jsp:include也是标准动作之一, 关于动作可以了解这么多. 因为JSP的标准动作现在使用的不多, 都是使用自定义的JSP动作, 也就是JSTL标签库了.