Java Web Reinforcement 06 EL表达式

Java Web Reinforcement 06 EL表达式

为什么不要在JSP中编写Java语言, 一切都是为了解耦. JSP技术中提供了EL表达式语言以及可以引入标签库来让JSP页面变得更加明晰, 也不会和Java代码捆绑在一起. 不过相比于使用Java代码, EL表达式和标签库有很多细节需要了解. 总体而言, 还是比直接编写Java代码要好太多了. 为何

为什么不要在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标签库了.
LICENSED UNDER CC BY-NC-SA 4.0
Comment