Servlet 与 JSTL/EL
Servlet 与 JSTL/EL
前面介绍的JSP基础语法(在页面中嵌入Java小脚本和表达式),虽然可以基本实现动态网站的开发,但却使得界面层代码的维护变得复杂,HTML代码和Java代码混编在页面中,页面美工和Java程序员也难以实现分工。
下面介绍的Servlet和JSTL/EL可以实现界面层Java代码和HTML代码的完美分离,让我们的Web应用程序更容易维护。
1 Servlet
前面我们讨论过一个JSP页面的执行过程如下图所示。JSP页面虽然包含了Java脚本,但毕竟还不是一个Java类,要把一个JSP页面作为一个Java程序被执行并最终输出HTML结果,服务器还须要把JSP页面转换成一个实际的Java类。Servlet就是JSP页面最终被解释成的Java类,换句话,JSP页面实质上就是一个Servlet类。
1.1 Servlet生命周期
Servlet是JSP规范中的一个非常重要接口,实现了该接口的类,就可以部署在服务器(Tomcat)中,用来处理用户发送过来的请求。
Serlvet接口的主要成员如下所示:
方法 描述
void init(ServletConfig config) 初始化一个Servlet
void service() 用于处理(响应)一个请求
void destory() 用于销毁一个Servlet
当客户发送一个请求到服务器时,服务器会根据请求的URL挑选一个合适的Servlet类去处理该请求;如果选中的Servlet类还没被实例化,则实例化出一个Servlet对象,依次执行该对象的init()方法和service()方法,service()方法会根据请求内容生成HTML响应,并由服务器发回给客户;如果选中的Servlet类已经被实例化,则越过实例化和init()阶段,直接调用该Servlet对象的service()方法去生成HTML响应;Servlet对象的destory()方法会在Web应用程序停止(或服务器停止)时被调用,用于销毁Serlvet对象。每个Servlet类在服务器中都是单例的,也就说通常URL相同的所有请求会由同一个Servlet对象处理。
每个Servlet会经历实例化(new,仅在第一次请求时)、初始化(init,仅在第一次请求时)、服务(service,在每次请求时)和销毁(destory,仅在应用停止时)四个阶段,这个过程常常被称为“Servlet生命周期”。
1.2 HttpServlet
Serlvet接口实在过于底层,直接实现它是难以实现HTTP请求处理的,实际编程中,我们往往采用继承HttpServlet类的方式去编写HTTP请求处理。HttpServlet类同样派生自Servlet接口,但其内部已经实现了大量的Servlet API基础构件,通过使用这些基础构件,我们就可以轻松完成请求处理并返回HTML响应。
在HttpServlet中,原来Servlet接口中用于处理请求的service方法已经被解构成doGet、doPost等若干个子方法,每个方法处理一种方式的HTTP的请求(如HTTP的get请求和post请求);而且在doXxx方法中,我们可以通过参数(HttpServletRequest和HttpSerlvetResponse)获取Servlet API,这样我们只需要把编程的关注点放在业务上就可以了。
使用HttpServlet来处理请求,须要两个步骤:其一,创建一个HttpServlet类编写doGet或doPost处理代码;其二,在web.xml中配置该HttpServlet类,让它处理某种URL的请求。
下面的示例使用一个Servlet来输出HTML页面。
public class MyFirstServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<HTML>");
out.println(" <HEAD><TITLE>A Servlet</TITLE></HEAD>");
out.println(" <BODY>");
out.print(" This is ");
out.print(this.getClass());
out.println(", using the GET method");
out.println(" </BODY>");
out.println("</HTML>");
out.flush();
out.close();
}
}
要使得客户端可以请求该Servlet,须要在web.xml对其进行配置,如下所示:
<servlet>
<servlet-name>MyFirstServlet</servlet-name>
<servlet-class>com.demo.action.MyFirstServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>MyFirstServlet</servlet-name>
<url-pattern>/servlet/MyFirstServlet</url-pattern>
</servlet-mapping>
当用户请求网站的“/servlet/MyFirstServlet”URL时,服务器就会使用“com.demo.action.MyFirstServlet”类来对其响应。
1.3 Servlet的实际应用
Servlet和JSP同样可以处理用户的请求,但它们所侧重的地方不一样,JSP是一个页面文件,更适合用于组织HTML的输出;而Servlet则是一个Java类,更适合用来执行业务(调用业务层代码,准备数据)处理和控制页面的转向。
回顾之前的一个示例,我们在完成JSP登录验证并控制页面转向时使用过一个loginController.jsp页面,用于调用业务层实现用户验证并根据验证结果实现跳转。该页面只是一个逻辑页面,不存在HTML代码,如果使用JSP来实现,会有种奇怪的感觉。
<%@ page language="java" pageEncoding="UTF-8"%>
<%
request.setCharacterEncoding("UTF-8"); //解决Post参数中文乱码问题
String username = request.getParameter("username");
String password = request.getParameter("password");
if("sam".equals(username) && "123".equals(password)){
response.sendRedirect("login_success.jsp");
}else{
request.setAttribute("error", "用户名或密码有误。");
request.getRequestDispatcher("login.jsp").forward(request, response);
}
%>
这种专门用来中转的JSP页面就很适合用Servlet来实现。
其实,在每个JSP页面中,与显示无关(非HTML输出控制)的Java脚本代码,都应该编写在Serlvet中。
2 JSTL和EL
通过Servlet技术,我们已经可以把JSP中与显示无关的Java逻辑代码分离到Servlet了,但是与显示相关的一些Java脚本(如for循环的HTML输出控制、在页面中注入的Java表达式)还无法替代。为此SUN公司推出了JSTL和EL,用来实现页面的输出控制和表达式的注入。
JSTL叫做JSP标准标签库,提供了大量的标签来控制JSP页面的输出,可以替代页面中用于控制输出的Java脚本(<% ...; %>);而EL是一种表达式语言,用于实现动态的Java表达式求解,可以替代页面中的表达式(<%=… %>)。
2.1 EL的使用
EL的实质是一个字符串,其语法非常简单,基本语法如下所示:
${ 范围.对象.属性 } 或 ${ 范围.对象[属性] }
EL可以替代Java表达式,获取几乎所有页面上下文范围中的数据,控制EL表达式的取值范围的隐式对象如下所示:
范围对象 类型 说明
pageContext javax.servlet.ServletContext 表示此JSP的PageContext
pageScope java.util.Map 取Page范围的属性名所对应的值
requestScope java.util.Map 取Request范围的属性名所对应的值
sessionScope java.util.Map 取Session范围的属性名称所对应的值
applicationScope java.util.Map 取Application范围的属性名所对应的值
param java.util.Map 同request.getParameter()获取请求参数
paramValues java.util.Map 同request.getParameterValues()
header java.util.Map 同request.getHeader()
headerValues java.util.Map 同request.getHeaders()
cookie java.util.Map 同request.getCookies()
initParam java.util.Map 同ServletContext.getInitParameter()
下面页面示例代码通过EL获取名为username的cookie中的值:
<h1>欢迎光临,${cookie.username.value}。</h1>
如果不使用EL表达式,获取同样的cookie值须要繁复的Java脚本代码:
<%
String username = null;
Cookie[] cookies = request.getCookies();
if(cookies != null){
for(Cookie c : cookies){
if(c.getName().equals("username")){
username = c.getValue();
}
}
}
%>
<% if (username!=null) { %>
<h1>欢迎光临,<%=username%>。</h1>
<% } %>
使用EL访问数据对比使用Java表达式有两个很大的优势:一是无需担心对象为空值时抛出NullPointerException,EL遇到对象为空时会忽略该表达式不作任何显示;二是无需类型转换,EL表达式中的变量都是弱类型的。
另外,EL表达式中的一些范围对象是可以省略的。下面示例通过EL获取Session中保存的user对象中的username属性值 :(注Session范围是可省略范围对象声明的)
<h1>欢迎光临,${sessionScope.use.userrname }。</h1>
如果使用Java脚本和表达式,则须要做繁复的类型转换和非空判断:
<%
if(session.getAttribute("user") != null) {
User user = (User)session.getAttribute("user");
%>
<h1>欢迎光临,<%=user.getUsername()%>。</h1>
<% } %>
EL表达式除了可以获取数据之外还可以实现一些运算,完成一个复杂的表达式的求解。
下面示例在EL中使用三元运算符“?:”输出登录用户的用户名,若用户没有登录(session的user为空)则显示“您尚未登录”。
<h1>欢迎光临,${ user!=null? user.username : "您尚未登录" }。</h1>
2.2 JSTL的使用
JSTL(JSP Standard Tag Library,JSP标准标签库)是SUN公司为JSP技术推出的一套完善标签库,用于控制JSP页面中的HTML输出。
JSTL分为core(核心)、fmt(格式)、fn(函数)、sql、xml五套自标签。
使用JSTL,要在页面开始的地方用<%@ taglib uri="标签库uri" prefix="前缀简称" %>声明所引用的标签库。
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
下面将为大家讲解常用的几个JSTL标签。
2.2.1 core标签
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
(1)<c:out>
<c:out>
标签用于在JSP中显示数据,它有如下属性:
属 性 描 述 是否必须 缺省值
value 输出的信息,可以是EL表达式或常量 是 无
default value为空时显示信息 否 无
escapeXml 为true则避开特殊的xml字符集 否 true
下面示例使用<c:out>
和EL输出用户名,当EL索取的值为空时显示“游客”:
您的用户名是<c:out value="${user.username}" default="游客" />
(2)<c:set>
<c:set>
标签用于在JSP中设置保存数据,它有如下属性:
属 性 描 述 是否必须 缺省值
value 要保存的信息,可以是EL表达式或常量 否
target 要修改属性的变量名,一般为javabean对象 否 无
property 需要修改的javabean属性 否 无
var 需要保存信息的变量 否 无
scope 保存信息的变量的范围 否 page
注意:如果指定了target属性, 那么property属性也必须指定。
下面示例将EL获取的值设置到Session范围中保存,session的键为test2:
<c:set value="${test.testinfo}" var="test2" scope="session" />
(3)<c:if>…</c:if>
<c:if>标签用户JSP页面中的分支流程控制,有如下属性:
属 性 描 述 是否必须 缺省值
test 需要评价的条件,相当于if (...){}语句中的条件 是 无
var 要求保存条件结果的变量名 否 无
scope 保存条件结果的变量范围 否 page
(4)<c:forEach>…</c:forEach>
<c:forEach>
标签用于循环遍历集合元素,它有以下属性:
属 性 描 述 是否必须 缺省值
items 进行循环的项目 否 无
begin 开始条件 否 0
end 结束条件 否 最后一项
step 步长 否 1
var 代表当前项目的变量名 否 无
varStatus 显示循环状态(含有属性index) 否 无
值得一提的是,其中varStatus包括以下属性:
特性 描述
current 当前这次迭代的(集合中的)项
index 当前这次迭代从 0 开始的迭代索引
count 当前这次迭代从 1 开始的迭代计数
first 用来表明当前这轮迭代是否为第一次迭代的标志
last 用来表明当前这轮迭代是否为最后一次迭代的标志
begin begin 属性值
end end 属性值
step step 属性值
下面示例通过<c:forEach>
和<c:if>
标签,实现电影信息的显示,并且实现表格斑马条纹的样式效果。
<table>
<tr><th>分类编号</th><th>分类名称</th></tr>
<c:forEach var="c" items="${categories}" varStatus="vs">
<c:if test="${vs.index%2==0}">
<tr style="background-color:yellow;"><td>${c.id}</td><td>${c.name}</td>
</c:if>
<c:if test="${vs.index%2!=0}">
<tr><td>${c.id}</td><td>${c.name}</td>
</c:if>
</c:forEach>
</table>
2.2.2 fmt标签
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
日期格式化标签:<fmt:formatDate>
属性名 | 说明 | EL | 类型 | 必须 | 默认值 |
---|---|---|---|---|---|
value | 将要格式化的日期对象。 | 是 | Java.util.Date | 是 | 无 |
type | 显示的部分(日期 date、时间 time或者两者 both)。 | 是 | String | 否 | date |
partten | 格式化的样式。 | 是 | String | 否 | 无 |