JSP&Servlet之间的交互&EL&JSTL
为何学习JSP呢?
在之前我们学习的Servlet。我们要想输出内容到浏览器。我们是通过在Servlet内部编写html代码,通过输出流进行输出到浏览器的。 这种写法是行得通的,但是写起来很麻烦。这也说明Servlet本身是不擅长做页面输出工作的。
谁擅长输出界面呢?就是我们接下来要学习的JSP技术。
JSP 是什么?
JSP(全称Java Server Pages)是由 Sun Microsystems 公司倡导和许多公司参与共同创建的一种使软件开发者可以响应客户端请求,而动态生成 HTML、XML 或其他格式文档的Web网页的技术标准。
也就是说,JSP动态生成HTML,然后响应给浏览器显示。 这是JSP擅长的事情。
我们可以使用Servlet和JSP结合使用,完成数据的展示。发挥各自的擅长的部分。这样也体现了java中的责任分离思想。
编写第一个JSP页面
1.创建一个JSP页面 如:
<%@page import="java.util.Date"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
北京时间:<%= new Date().toLocaleString() %>
</body>
</html>
2.设置JSP的编码 如:
3.通过浏览器访问 如:
JSP 的实现原理
我们通过浏览器访问完以后,打开Tomcat的目录找到work文件夹,会发现里面产生两个文件。 一个是java源文件,一个是相应的字节码文件。然后我们打开java源文件,惊奇的发现,原来是把我们写在JSP文件中的内容,包装成了html,然后通过JspWriter对象输出的。如:
那么整个请求流程是怎样的呢?它是怎么知道我浏览器中访问的是jsp,并且产生一个java文件和一个编译后的字节码文件的。
这里我们应该从Tomcat服务器下手。因为在通过浏览器访问的时候,肯定是被拦截,并进行处理了。因此查看web.xml,发现在里面配置了一个JSPServlet,当我们访问jsp的时候,被拦截转交给JSPServlet。它就是把JSP页面翻译成了Servlet.
执行流程图如下:
JSP 的语法 (了解)
JSP的基本语法:
1、JSP的注释:
作用:注释Java脚本代码
语法:<%--这是JSP注释--%>
2、JSP中的Java脚本片段:(实际开发中,应做到JSP中不能出现一行Java脚本片段)
作用:书写Java代码逻辑
语法: <% 语句1; 语句2; %>
原理:其中的语句会原封不动的被服务器翻译到对应的Servlet的_jspService方法中。
3、JSP的Java脚本表达式:
作用:输出数据到页面上
语法:<%=表达式%>(实际上就是调用输出流打印到页面上) out.print(表达式);
4、JSP的声明:
作用:定义类的成员
语法:<%! Java代码 %>
原理:其中的语句会原封不动的被服务器翻译到对应Servlet类中,作为成员变量和方法.
这个JSP的语法我们只作为了解即可。不用重点掌握,因为后面要学习EL和JSTL最终会取代掉JSP的语法。
我们这里通过一个图来演示一下JSP的语法:
JSP 的指令
什么是JSP指令?
当我们创建一个jsp页面时,在页面第一行有一段代码如下:
<%@ page language="java" pageEncoding="utf-8"%>
这个就是指令,用于设定JSP网页的整体配置信息
每一个JSP都得有page指令,一般放在JSP的第一行.
●特点:
1:并不向客户端产生任何输出;
2:指令在JSP整个文件范围内有效;
3:为翻译阶段提供了全局信息;
●语法:
<%@ 指令名称 属性="属性值" ... %>
例如:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
●JSP的三大指令:
1):page:指定页面的一些全局信息,如导入类,指定编码等
2):include:表示包含某个页面(静态包含)
3):taglib:用来导入标签库(后面学习)
●page指令: 属性和值有很多,不用去记,使用默认的即可
例如:
<%@page language="java" import="java.util.*" contentType="text/html; charset=utf-8" pageEncoding="UTF-8"%>
属性:
language :当前JSP所使用的语言.只能是java.
contentType :当前JSP的MIME类型.
pageEncoding :当前JSP保存到硬盘的编码及输出的字符编码.和上面的保持一致
import :导入Java中的包(import属性可以出现多次的) <%@page import="java.util.Date"%>
errorPage :设置JSP页面中出现错误信息该如何处理的(错误页要设置isErrorPage为true)
extends :设置JSP被翻译成Servlet后继承的类:默认值:org.apache.jasper.runtime.HttpJspBase.
autoFlush :设置自动刷出缓存。默认true
buffer :JSP的缓冲区的大小。默认8kb
session :默认值是true.设置JSP的页面中是否可以直接使用session对象.
isELIgnored :设置JSP是否忽略EL表达式.默认值false 不忽略.可以在JSP中写EL表达式.
isErrorPage :设置JSP页面中出现错误信息应该如何处理的.
●include指令:在当前JSP页面引入其他页面
例如:
<%@ include file="logo.jsp" %>
Body
<%@ include file="footer.jsp" %>
●taglib指令:用于导入标签库.
例如:
<%@taglib prefix="" uri="" %>
uri:表示标签库的URI地址; prefix:表示标签库的命名前缀(起个简单名字)
●扩展:JSP标签
<jsp:forward />
转发:<jsp:forward page="demo.jsp"></jsp:forward>
<jsp:include />
动态包含:<jsp:include page="logo.jsp"></jsp:include>
静态包含(<%@ include file="footer.jsp" %>)和动态包含(<jsp:include page="logo.jsp"/>)的区别?
* 静态包含相当于代码的copy,最终被翻译成一个Servlet解释执行的.动态包含,包含其他页面的运行的结果.最终翻译成多个Servlet解释执行的.
Servlet和JSP之间的交互
Servlet 和 JSP之间为何要交互呢?
前面我们在讲为何学习JSP的时候说过,为了体现出责任分离思想,只做自己擅长的事情。比如:
Servlet擅长:
1.接收请求参数
2.调用业务方法。
3.控制页面跳转
JSP擅长:
显示页面的内容
问题:
我们要完成一个商品列表展示的功能。先定义一个ProductServlet接收浏览器发送来的请求。在Servlet中调用业务逻辑把商品数据从数据库中查询出来,需要把数据显示给用户,如何显示?
我们知道Servlet并不擅长做页面输出,所以需要定义一个JSP页面用来做数据的显示工作。这时ProductServlet和list.jsp 之间就需要交互。
定义一个DeleteServlet来处理删除数据功能。那么删除之后呢?
需要重新获取数据,然后通过JSP显示出来,这些代码我们已经在ProductServlet中做过了,不需要在DeleteServlet中,再重复做一遍,那么直接跳转到ProductServlet即可。这时ProductServlet和DeleteServlet需要交互。
如:
它们之间的交互需要解决两个问题。
第一个问题Servlet如何跳转到JSP页面。(跳转问题)
第二个问题JSP页面上的数据从何而来,Servlet共享数据给页面,如何共享数据。(数据共享问题)
接下来我们解决上面的两个问题。
交互解决的第一个问题:跳转问题
跳转问题有三种解决方案:
- 1 ) : 请求转发(forward)
- 2 ) : URL重定向 (redirect)
- 3 ) : 请求包含 (include)
请求转发(forward)
什么是请求转发?
浏览器向服务器请求Servlet1,Servlet1接收请求并处理,但并没有处理完毕,需要Servlet2接着处理,那么Servlet1就需要把请求转发给Servlet2进行处理,那这个过程就叫作请求转发(forward)
我们应该如何下手查找API文档呢?
理解着概念,我们应该从请求的对象(HttpServletReqeust)入手,我们寻找的方法应该带一个参数,参数应该是我们要转发到哪里?参数是一个”路径“,通过查找,我们找到一符合要求的方法。getRequestDispatcher(String path)方法
再看API中对该方法的介绍:
Returns a RequestDispatcher object that acts as a wrapper for the resource located at the given path. A RequestDispatcher object can be used to forward a request to the resource or to include the resource in a response. The resource can be dynamic or static.
返回RequestDispatcher对象,该对象充当位于给定路径的资源的包装器。RequestDispatcher对象可用于将请求转发给资源或在响应中包含资源。资源可以是动态的,也可以是静态的。
通过request对象调用 getRequestDispatcher方法可以获取一个请求分发器对象(RequestDispatcher),参数为资源路径。
在开发中,资源路径一般是使用的绝对路径。以“/"开头的路径
文档中关于绝对路径也做了解释:
If the path begins with a "/" it is interpreted as relative to the current context root. This method returns null if the servlet container cannot return a RequestDispatcher.
如果路径以“/”开头,则将其解释为相对于当前上下文根。如果servlet容器无法返回RequestDispatcher,则此方法返回null。
这个方法只是获取一个请求转发器,并不能完成我们的请求转发工作。还要借助于另外一个方法。打开RequestDispatcher 相关的API
里面有个forward(ServletRequest request,ServletResponse response)方法.看方法名称应该符合我们的要求。
看API对该方法的介绍:
Forwards a request from a servlet to another resource (servlet, JSP file, or HTML file) on the server. This method allows one servlet to do preliminary processing of a request and another resource to generate the response.
将请求从servlet转发到服务器上的另一个资源(servlet、JSP文件或HTML文件)。此方法允许一个servlet对请求进行初步处理,并允许另一个资源生成响应。
使用请求转发完成一个案例
需求: 完成两个Servlet的跳转,观察:url(地址栏中请求路径的变化),数据的传送
需求分析:定义一个AServlet,BServlet。在AServlet中通过resp对象输出数据到浏览器,在BServlet中通过resp对象输出数据到浏览器。AServlet通过请求转发跳转到BServlet.
代码如下:
AServlet:
@WebServlet("/forward/a")
public class AServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 设置请求编码和响应编码
req.setCharacterEncoding("utf-8");
resp.setContentType("text/html;charset=utf-8");
System.out.println("===========a===before================");
// 接收浏览器传过来的参数
String name = req.getParameter("name");
System.out.println(name);
// 请求转发到BServlet
req.getRequestDispatcher("/forward/b").forward(req, resp);
System.out.println("===========a===after================");
// 响应数据到浏览器
resp.getWriter().println("我是AServlet");
}
}
BServlet:
@WebServlet("/forward/b")
public class BServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 设置请求编码
req.setCharacterEncoding("utf-8");
// 设置响应编码
resp.setContentType("text/html;charset=utf-8");
System.out.println("===========b===before================");
// 接收浏览器传过来的参数
String name = req.getParameter("name");
System.out.println(name);
System.out.println("===========b===after================");
// 响应数据到浏览器
resp.getWriter().println("我是BServlet");
}
}
执行结果:
我们通过一张图来分析一下它的执行流程:
通过运行结果我们得知以下四个结论:
结论:1:浏览器地址栏不发生改变,依然是/forward/a,不是目标地址(/forward/b).
结论:2:请求转发只发送一个请求.
结论:3:共享同一个请求中的数据.
结论:4:最终响应给浏览器的由BServlet来决定.
使用请求转发只能在服务器内部跳转,不能跳转到站外资源,比如我们做一个一个商城项目,支付是对接的银行,这种情况我们下单支付时是不能使用请求转发的。
举个例子:
比如定义一个Servlet,在Servlet中使用请求转发跳转到百度。
代码如下:
@WebServlet("/forward/c")
public class CServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("===========c===before================");
// 请求转发到baidu.com
req.getRequestDispatcher("http://baidu.com").forward(req, resp);
System.out.println("===========c===after================");
// 响应数据到浏览器
resp.getWriter().println("我是CServlet");
}
}
会报404的错误,因为请求转发传入的路径(path)是相对于项目的上下文的。
运行结果:
因此我们得出第五条结论:
结论:5:请求转发不能跨域访问,只能跳转到当前应用中的资源.(http://localhost:8080/...协议、ip、端口三者中有一个不同就表示跨域)
我们说在web项目中定义在WEB-INF文件夹下面的资源是不能通过浏览器直接访问的。那么我们该如何访问WEB-INF文件夹下面的资源呢? 答案是请求转发。
比如:我们在WEB-INF下面定义一个jsp页面。通过请求转发访问该页面。
代码如下:
@WebServlet("/forward/d")
public class DServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("===========d===before================");
// 请求转发
req.getRequestDispatcher("/WEB-INF/index.jsp")
.forward(req, resp);
System.out.println("===========D===after================");
}
}
运行结果:
通过运行结果发现,我们可以得出第六条结论:
结论: 6:请求转发可以访问WEB-INF目录中的资源.(浏览器不可以直接访问WEB-INF文件夹中的文件,但可以通过Servlet间接访问)
重定向(redirect)
什么是重定向?
浏览器向服务器请求Servlet1,但是Servlet1接收请求之后并没有处理,而是直接响应给浏览器,让浏览器去请求Servlet2,那这个过程就叫作请求重定向(redirect)
我们应该如何下手查找API文档呢?
根据概念,请求Servlet1,Servlet1响应给浏览器一个Servlet2的路径,让浏览器去请求这个路径。所以我们应该去查看响应对象的API。
我们发现在提供这么方法中,包含redirect这个单词的只有一个方法--
sendRedirect(String location)。看上去应该是我们要找的方法。打开方法的介绍。文档中是这样介绍该方法的。
Sends a temporary redirect response to the client using the specified redirect location URL.
使用指定的重定向位置URL向客户端发送临时重定向响应
使用重定向完成一个案例
需求: 完成两个Servlet的跳转,观察:url(地址栏中请求路径的变化),数据的传送
需求分析:定义一个AServlet,BServlet。在AServlet中通过resp对象输出数据到浏览器,在BServlet中通过resp对象输出数据到浏览器。AServlet通过请求转发跳转到BServlet.
代码如下:
@WebServlet("/redirect/a")
public class AServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 设置请求编码和响应编码
req.setCharacterEncoding("utf-8");
resp.setContentType("text/html;charset=utf-8");
System.out.println("===========a===before================");
// 接收浏览器传过来的参数
String name = req.getParameter("name");
System.out.println(name);
// 重定向到BServlet
resp.sendRedirect("/redirect/b");
System.out.println("===========a===after================");
// 响应数据到浏览器
resp.getWriter().println("我是AServlet");
}
}
@WebServlet("/redirect/b")
public class BServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 设置请求编码
req.setCharacterEncoding("utf-8");
// 设置响应编码
resp.setContentType("text/html;charset=utf-8");
System.out.println("===========b===before================");
// 接收浏览器传过来的参数
String name = req.getParameter("name");
System.out.println(name);
System.out.println("===========b===after================");
// 响应数据到浏览器
resp.getWriter().println("我是BServlet");
}
}
执行流程图:
通过流程图分析我们得出四个结论:
结论:1:浏览器地址栏发生改变,从/redirect/a?name=redirect,变成目标地址(/redirect/b).
结论:2:URL重定向发了两次请求.(把目标地址拷贝到浏览器地址栏,敲回车).
结论:3:因为URL重定向是两次独立的请求,所以不共享请求中的数据.
结论:4:最终响应给浏览器的由BServlet来决定
重定向可以跨域访问。比如从Servlet重定向到百度。
@WebServlet("/redirect/c")
public class CServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("===========c===before================");
// 重定向到baidu.com
resp.sendRedirect("http://baidu.com");
System.out.println("===========c===after================");
// 响应数据到浏览器
resp.getWriter().println("我是CServlet");
}
}
通过实验,我们应该得出第五条结论:
结论5::URL重定向能跨域访问,可以访问其他应用中的资源.
重定向不能访问WEB-INF文件夹下面的资源。比如 我么定义一个DServlet,通过重定向访问WEB-INF下面的资源。
代码如下:
@WebServlet("/redirect/d")
public class DServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("===========d===before================");
// 使用重定向访问WEB-INF下面的资源 (这是错误的)
resp.sendRedirect("/WEB-INF/index.jsp");
System.out.println("===========D===after================");
}
}
代码的执行流程图:
通过实验,我们应该得出第六条结论:
结论:6:URL重定向不能访问WEB-INF目录中的资源.
注意:在代码中,当重定向结束后,既不能再次重定向也不能请求转发。
在文档中也有所介绍:
If the response has already been committed, this method throws an IllegalStateException. After using this method, the response should be considered to be committed and should not be written to.
如果响应已经提交,该方法将抛出IllegalStateException。使用此方法后,应认为响应已提交,不应将其写入。
如果重复做了会报500的错误。如图:
提交两次重定向:
重定向结束,然后又使用请求转发:
请求包含(include)(了解,不讲)
和请求转发类似,如:
二者的共同特点:
*这两种都是一个请求跨多个Servlet(可以共享request的域属性)
*多个Servlet在一个请求中共享一个Request对象,比如说就是:在AServlet.setAttribuet()方法执行之后,可以在BServlet.getAttribute();
二者的区别:
*请求转发可以这样理解:如果ASerlet转发到BServlet 中,那么这个请求就交给bServlet来处理,ASevlet就不要插手了。
*请求包含,例如:AServlet包含BServlet,那么这两个Servlet共同处理请求
二者的联系:
请求转发和请求包含都需要一个相同的对象,RequestDispatcher
请求转发和URL重定向的选择?
1:若需要共享请求中的数据,只能使用请求转发.
2:若需要访问WEB-INF中的资源,只能使用请求转发.
3:若需要跨域访问,只能使用URL重定向.
4:请求转发可能造成表单的重复提交问题.
5:其他时候,任选.
交互解决的第二个问题:数据共享问题
数据共享: 当Servlet对请求处理完成以后,需要将一些数据传递给JSP页面或者另一个Servlet继续处理,这个操作被称为数据共享。
数据共享的实现需要借助于作用域对象。在Servlet中有三个作用域对象。如:
Servlet中的三大作用域:
请求作用域:request
表示一次请求的范围,请求结束后,request作用域的生命周期就结束
如果在Web组件之间需要共享同一个请求中的数据,只能使用请求转发.
类型:HttpServletRequest
会话作用域:session
会话:就是浏览器和服务器的交互过程
一次回话:从浏览器打开访问服务器开始,到浏览器关闭结束,这期间可以完成多次请求和多次响应
如果需要在一次会话中的多个请求之间需要共享数据,只能使用session.
类型:HttpSession,
对象的创建: request对象.getSession();
应用作用域:application
在服务器启动的时候,会为每个项目创建一个application对象,在关闭服务器的时候销毁,这期间可以有多次会话的操作
在一个应用中有且只有一个application对象.作用于整个Web应用,可以实现多次会话之间的数据共享.
类型:ServletContext
对象的创建: 在Servlet中直接调用getServletContext()
最后我们再通过一张图来对比三个域对象的区别:
上图中,注意图形叠加在一起,不是包含关系。是用来说明它们的存活周期不同。比如:
request的存活周期最短,只在一次请求中有效。请求结束,request对象销毁。
session的存活周期为一次回话。从浏览器打开到关闭,当浏览器关闭session对象销毁。
application的存活周期最长。从服务器启动到关闭,当服务器关闭的时候application对象销毁。
三个对象是独立的,互不干扰。
或许我们会想,定义一个application不就可以了吗?为何设计三个作用域对象。这个时候,我们应该想想一下当初学习java,java为何设计八种数据类型。其中的道理是一样的。避免资源的浪费。
换一种思考,三个作用域对象,其实可称为数据的载体,数据的容器。
说道数据容器,无非就是操作数据,对其进行增删改查操作。
我们通过查看API,三个域对象都有对应Setter和Getter方法。所以也不难找出增删改查方法。如:
1.添加共享数据
void setAttribute(String name, Object value)
2.删除共享数据
void removeAttribute(String name)
3.修改共享数据
重新设置一个同名的共享数据
void setAttribute(String name, Object value)
4.获取共享数据
Object getAttribute(String name)
代码操作事例:
AServlet:
@WebServlet("/scope/a")
public class AServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// request
req.setAttribute("request_scope", "request");
// session
req.setAttribute("session_scope", "session");
// application
req.setAttribute("application_scope", "application");
// 跳转到BServlet
req.getRequestDispatcher("/scope/b").forward(req, resp);
}
}
BServlet:
@WebServlet("/scope/b")
public class BServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// request
String requestValue = (String) req.getAttribute("request_scope");
System.out.println("requestValue...." + requestValue);
// session
String sessionValue = (String) req.getSession().getAttribute("session_scope");
System.out.println("sessionValue...." + sessionValue);
// application
String applicationValue = (String) getServletContext().getAttribute("application_scope");
System.out.println("applicationValue...." + applicationValue);
}
}
注意:
数据放在哪个作用域就必须从哪个作用域去取
遵循设计原则,能用小的作用域对象,就不要使用大的
ServletContext接口
表示Web应用对象,从Tomcat启动到关闭之间,每个应用中有且只有一个ServletContext对象,而且是在Tomcat启动的时候就创建的,所以在JSP中把该对象称之为:application
如何获取ServletContext对象
- 1 . 方式1:在Servlet类中: super.getServletContext();
- 2 .方式2:通过request对象来获取:request.getServletContext(); 该方法是从Tomcat7开始存在的.(推荐使用)
- 3 . 方式3:通过session对象来获取:request.getSession().getServletContext();
注意:无论是哪一种方式,获取的都是同一个ServletContext对象.
常用方法
1):String getContextPath():获取当前应用的上下文路径.
2):String getRealPath(String path):获取某个资源的决定路径.
3):全局初始化参数:
先看传统的Servlet的初始化参数:
因为配置在当前ServletContextDemo中,所以只能被ServletContextDemo使用,其他Servlet无法使用.
而在开发中,多个Web组件(Servlet/Filter)可以拥有共同的初始化参数,此时只能配置N次,不利于维护.
解决方案:使用全局的初始化参数,Web组件都可以共用,配置问web.xml文件中.
String getInitParameter(String name):获取指定名称的全局初始化参数.
Enumeration<String> getInitParameterNames():获取所有全局的初始化参数的名字.
★注意:
ServletConfig接口中的获取初始化参数的方法,只能获取当前Servlet自身的初始化参数
ServletContext接口中的获取初始化参数的方法,只能获取全局的初始化参数
JSP 内置对象
在Servlet中有三个域对象 ,其作用是用来共享数据。在JSP页面也存在域对象,其目的是一样的。在JSP页面中有九大内置对象,其中包括四个域对象。
什么是JSP内置对象?
在JSP中预先定义好的,可以直接拿来使用的对象.不需要我们创建。
那么对象在何时创建的呢?
我通过查看jsp被翻译成Servlet的代码发现。在翻译完成,对象创建完成。如:
我们通过一个图来整理一下这些对象。如:
在九个内置对象中,包含了四个域对象,这四个域对象需要我们记一下。其中三个域对象和Servlet中的域对象是一样的。其用法也一样。相同的域对象我们通过代码演示一下,不再细讲。如:
<!-- 往域对象中存入值 -->
<%
pageContext.setAttribute("msg","pageContext");
request.setAttribute("msg", "request");
session.setAttribute("msg", "session");
application.setAttribute("msg", "application");
%>
<!-- 从域对象中获取值 -->
pageContext: <%= pageContext.getAttribute("msg") %> <br/>
request: <%= request.getAttribute("msg") %> <br/>
session: <%= session.getAttribute("msg") %> <br/>
application: <%= application.getAttribute("msg") %> <br/>
运行结果:
我们只说没有讲过的PageContext对象。pageContext对象的作用域,只在当前JSP页面有效。在四个域对象中存活周期最短的。它除了和其他域对象相同的用法之外,还有其他特殊的吗?
特殊之一 pageContext 它可以操作其他三个域对象
意思是可以往任意一个域对象中存入数据。拿往Session中存入数据来举个例子。如:
比如:通过pageContext 往session中存入值。 通过session获取<br/>
<%
pageContext.setAttribute("session","pageContext_session",pageContext.SESSION_SCOPE);
%>
<br/>
<%= session.getAttribute("session") %>
<br/>
特殊之二 pageContext 它提供一个强大的方法findAttribute(String name)
在文档中是这样介绍的:
Searches for the named attribute in page, request, session (if valid), and application scope(s) in order and returns the value associated or null.
按顺序搜索页面、请求、会话(如果有效)和应用程序范围中的命名属性,并返回相关的值或null。
通过看文档分析:该方法是从pageContext/request/session/application四大作用域中按顺序去查询共享数据,如果有立刻返回,反之返回null.
通过上面我们学习的JSP中提供的四大作用对象,通过在JSP页面上写Java代码,操作起来非常的麻烦。
比如,如果从域对象中获取数据,如果获取失败,返回的是null。如:
这样往往是不合理的。在JSP页面上获取数据失败,页面上显示一个null,给用户看到,是不友好的。所以更为妥善的处理方法是显示空字符串。
我们可以使用三目运算符来处理一下:
虽然目的是达到了,但是实现的代码太过于麻烦。 我们可以通过下面EL表达式来简化这个操作。如:
${request}
EL 表达式
什么是EL表达式
百度百科中的解释:
EL(Expression Language) 是为了使JSP写起来更加简单。表达式语言的灵感来自于 ECMAScript 和 XPath 表达式语言,它提供了在 JSP 中简化表达式的方法,让Jsp的代码更加简化。
为何需要学习EL表达式?
其目的已经很明显,从作用域中获取指定属性名的共享数据. 替代掉通过使用JSP语法的方式获取数据。
EL的语法
${表达式}
在EL中访问JavaBean属性方式:
方式1:使用.来访问.
方式2:使用[]来访问.
${ 对象.属性名} ----->对象.getXxxx,注意属性必须提供getter方法.
若操作的是Map: ${ 对象.key}
比如:${u.userName} 等价于 ${u["userName"]}
通过一个事例来看一下EL表达式的用法。
User:
@Getter
@ToString
public class User {
private Long id = 1L;
private String name = "我是谁";
private String[] hobby = { "java", "Android" };
private List list = Arrays.asList("a", "b", "c");
private HashMap map = new HashMap() {
{
this.put("key", "value");
this.put("cn.wolfcode", "叩丁狼");
}
};
}
//把数据存入request域对象中
<%
request.setAttribute("user", new User());
%>
//通过EL表达式的方式操作域对象
获取整个对象的信息: ${user } <br/>
获取User对象的id: ${user.id }<br/>
获取User对象的name: ${user.name }<br/>
获取User对象的第一个hobby: ${user.hobby[0] }<br/>
获取User对象的list的第一个值: ${user.list[0] }<br/>
获取User对象的map的第一个key的value: ${user.map.key }<br/>
获取User对象的map的第二个key的value: ${user.map["cn.wolfcode"] }<br/>
运行结果:
注意:El表达式获取数据其本质是相当于调用getter方法。所以JavaBean必须提供相应的getter方法。
EL其他细节
1.EL的内置对象
如果我们需要从指定的作用域中获取共享数据,应该如何实现?
此时需要指定作用域(pageScope|requestScope|sessionScope|applicationScope)
如果需要从请求作用域中获取msg的共享数据:${requestScope.msg}
2.从Tomcat7开始,EL中不仅支持属性访问,还支持调用方法.
EL中的内置对象: pageContext
JSP 页的上下文。它可以用于访问 JSP 隐式对象,如请求、响应、会话、输出、servletContext 等。例如,${pageContext.response} 为页面的响应对象赋值。
获取项目的上下文路径:${pageContext.request.contextPath}
项目的上下文路径:${pageContext.getRequest().getContextPath()}
3.EL表达式的算术运算
在EL中可以完成基本的算术运算
${1+1} ${age+10}
empty关键字的使用
${empty name}----->判断name属性是否为空(如果是字符串,判断其是否为null并且是否为空字符串)
如果empty后面是判断集合的话,如果集合为null或者集合的元素个数为0,都表示是空
三元运算符:${布尔表达式?值1:值2}
JSTL 标签库
标签就是很简单而且可重用的代码结构。
Java标签库:
1):自定义标签库.
2):标准标签库(JSTL).(SUN预先提供好的,我们使用的.)java standard taglib language
为什么要使用JSTL?
之前使用EL表达式我们已经解决了从作用域中获取共享数据的问题,但是,如果我们需要在JSP中对数据做循环遍历或者逻辑判断的话,仅仅使用EL表达式是完成不了的,可以使用JSTL标签库来解决。所以我们需要学习JSTL。
EL + JSTL 就可以完全取代JSP相关的语法。
什么是JSTL?
JSTL(JavaServer Pages Standard Tag Library,JSP标准标签库)是一个不断完善的开放源代码的JSP标签库,是由apache的jakarta小组来维护的。JSTL只能运行在支持JSP1.2和Servlet2.3规范的容器上,如tomcat 4.x。在JSP 2.0中也是作为标准支持的。
JSTL的分类
如图:
JSTL的用法
我们要使用第三方的工具。
第一步:要导入相关的jar包。这个jar在apache-tomcat-8.0.53/webapps/examples/WEB-INF/lib里面。
taglibs-standard-impl-1.2.5.jar
taglibs-standard-spec-1.2.5.jar
第二步:在需要使用的地方。先通过taglib指令引入,然后再使用。如:
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
标签库中我们是靠taglib指令来引入不同的标签库,如何区分标签库的呢?是通过URI。我们通过图来看一下不同标签库引用时对应的uri的内容。如:
JSTL 的常用标签
逻辑判断标签:
单条件的判断(if)
- 1 . 根据表达式的结果来判断是否实现标签体中的数据。 如:
<%
request.setAttribute("age", 17);
%>
<c:if test="${age>=18 }">
可以观看。
</c:if>
- 2 . 获取到表达式的结果,将其共享到作用域中. 如:
<!-- 使用result 变量接收前面EL表达式的结果(true/false),
将其存储到作用域 中,默认是存储在pageContext中。
可以使用scope属性来修改存储的作用域
(page,request,session,application)-->
比如:把结果存入session. <br/>
<c:if test="${age>=18 }" var="result" scope="session"/> <br/>
从session中取出:<br/>
${sessionScope.result } <br/>
多条件的判断(choose when otherwise)
<c:choose>
<c:when test="${age<18 }">
未满18周岁,进入入内.
</c:when>
<c:when test="${age>=18 && age<=80 }">
可以体验
</c:when>
<c:otherwise>
不招待
</c:otherwise>
</c:choose>
循环遍历标签
循环遍历标签(c:forEach),相当于Java中的foreach
- 循环遍历一个数组或者集合
<%
List strList = Arrays.asList("a","b","c","d");
request.setAttribute("list", strList);
%>
<br/>
<!-- items:指定遍历的数组或者集合
var : 使用指定的变量名来接收遍历出来的元素。
该变量的数据共享到pageContext作用域中
varStatus: 对应的一个对象,该对象存在两个属性(index/count)
index: 当前元素在集合中的索引,从0开始
count:当前遍历出来的元素是第几个元素,从1开始 -->
<c:forEach items="${list }" var="item" varStatus="vs">
${vs.count } ---> ${pageScope.item }<br/>
</c:forEach>
- 2 . 在页面上循环输出一串数字:1,2,3,4,5,6...
<!-- begin: 开始位置
end: 结束位置
step: 步长
var: 接收的变量 -->
<c:forEach begin="1" end="10" step="2" var="num">
${pageScope.num } <br/>
</c:forEach>
日期的国际化标签:
需要引入响应的标签库。 如:
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
再使用响应的标签:
<%
request.setAttribute("date", new Date());
%>
<fmt:formatDate value="${date }" pattern="yyyy-MM-dd"/>