1.Servlet交互和作用域对象
在JavaWeb基础(六)中我分享了三个主要内容Servet3.0注解的使用
、Cookie技术实现请求数据共享
、Session技术实现请求数据共享
.在这篇博客中,我主要分享以下两方面的内容。
Servlet交互
作用域对象
1.1 Servlet交互
在J2EE规范中, Servlet的交互方式主要有三种
。Servlet之所以需要交互是因为我们不可能将所有业务逻辑
都揉和在一个Servlet中.所以一个Sevlet一般对应一个功能。多个Servlet之间就涉及到如何进行数据的传递
和跳转
, 这我们又称为Servlet的交互。简而言之,Servlet的交互
讲的是Servlet之间的数据传递
和跳转
。
1.1.1 Servlet交互方式
Servlet交互方式,主要分为以下两种, 请求转发
, URL重定向
,请求包含
请求转发
- 获取分发器,并调用forward开始转发.
req.getRequestDispatcher(path).forward(req, resp);
FirstServlet
@WebServlet("/request_forward/FirstServlet")
public class FirstServlet extends HttpServlet{
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
PrintWriter pw = resp.getWriter();
System.out.println("FirstServlet before");
pw.println("FirstServlet before");
req.getRequestDispatcher("/request_forward/SecondServlet").forward(req, resp);
System.out.println("FirstServlet Request =" + req);
System.out.println("FirstServlet after");
pw.println("FirstServlet after");
}
}
SecondServlet
@WebServlet("/request_forward/SecondServlet")
public class SecondServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
PrintWriter pw = resp.getWriter();
System.out.println("SecondServlet before");
pw.println("SecondServlet before");
System.out.println("SecondServlet Request =" + req);
System.out.println("SecondServlet after");
pw.println("SecondServlet after");
}
}
浏览器输出
控制台输出
特点研究
我们根据浏览器的输出
和控制器台输出
,结合代码来研究请求转发的特点
.
我们从浏览器地址栏
、请求转发整个过程Servlet的执行先后顺序
、请求报文变化
、参数是否可以传递
、请求转发能够访问的资源
来研究。
-
1.浏览器地址栏
如上图, 浏览器地址栏没有发生变化.我们可以得出结论请求转发不会改变浏览器地址栏
. -
2.请求转发整个过程Servlet的执行先后顺序
如上图控制台的输出,FirstServlet before
-SecondServlet before
-SecondServlet after
-FirstServlet after
.我们可以发现最后响应给浏览器并不是直接通过SecondServlet而是先回到了转发位置,然后继续执行FirstSevrlet后的代码。但是决定最后内容的是SecondServlet。我们可以得出以下结论一.决定最终返回数据的是SecondServlet, 但是并不是直接从SecondServlet响应给浏览器,而是还需要通过FirstServlet
.二.前后的请求对象不是同一个
3.请求报文的变化
观察如上图, 在输入http://127.0.0.1:8080/request_forward/FirstServlet后请求报文只有一次,并没有发生变化。说明请求转发的请求只有一次
.
4.参数是否可以传递
更改FirstServlet的代码,请求转发前给path增加name参数,并且存放一个age数据到reqeust域中.
req.setAttribute("age", 13);
req.getRequestDispatcher("/request_forward/SecondServlet?name=will").forward(req, resp);
SecondServlet最后先新增如下代码
pw.println("name = "+req.getAttribute("name")+",age = "+req.getAttribute("age"));
浏览器输出
结论:通过请求转发上填写参数无法共享,但是request对象的数据可以共享
5.请求转发能够访问的资源
尝试通过请求转发访问百度
req.getRequestDispatcher("www.baidu.com").forward(req, resp);
浏览器输出
可以得出以下结论:请求转发无法跨域访问
尝试通过请求转发访问WEB-INF目录下的资源
req.getRequestDispatcher("/baidu.html").forward(req, resp);
浏览器输出
可以得出以下结论:请求转发可以访问WEB-INF下的资源文件
总结
-
1.请求转发不会改变浏览器地址栏
(由第一点得出) -
2.请求转发内容由第二个Servlet决定,但是最后必须经过第一个Servlet
(由第二点得出) -
3.请求转发的请求次数只有一次
(由第三点得出) -
4.请求转发只能共享request中的数据.请求转发只能填写资源路径,填写参数无效.
(由第四点得出) -
5.请求转发只能不能跨域访问
(由第五点得出) -
6.请求转发能够访问WEB-INF下的资源
(有第五点得出)
URL重定向
URL重定向主要通过response对象的sendRedirect方法
实现.其会响应浏览器当前请求, 并让浏览器再发一次请求到新的资源路径。所以称之为重定向.
FirstServlet
@WebServlet("/url_redirect/FirstServlet")
public class FirstServlet extends HttpServlet{
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
PrintWriter pw = resp.getWriter();
System.out.println("FirstServlet before");
pw.println("FirstServlet before");
req.setAttribute("age", 13);
// req.getRequestDispatcher("/baidu.html").forward(req, resp);
resp.sendRedirect("/request_forward/SecondServlet");
System.out.println("FirstServlet Request =" + req);
System.out.println("FirstServlet after");
pw.println("FirstServlet after");
}
}
SecondServlet
@WebServlet("/url_redirect/SecondServlet")
public class SecondServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
PrintWriter pw = resp.getWriter();
System.out.println("SecondServlet before");
pw.println("SecondServlet before");
System.out.println("SecondServlet Request =" + req);
System.out.println("SecondServlet after");
System.out.println("name = "+req.getAttribute("name")+",age = "+req.getAttribute("age"));
pw.println("name = "+req.getAttribute("name")+",age = "+req.getAttribute("age"));
pw.println("SecondServlet after");
}
}
浏览器输出
控制台输出
特点研究
浏览器地址栏
如上图浏览器输出的地址栏发生了变化.可以得出结论:URL重定向会改变浏览器地址栏为请求地址
。请求转发整个过程Servlet的执行先后顺序
SecondServlet before
-Second after
.所以URL重定向后响应给浏览器的内容由SecondServlet决定
, 并且是直接由SecondServlet响应给浏览器,因为这是一次新的请求。请求报文变化
如上图,请求报文的请求行地址就发生了改变, 说明这是第二次请求.
所以URL重定向的请求是两次
-
参数是否可以传递
见上面的浏览器输出图,URL重定向参数不可传递
-
请求转发能够访问的资源
跨域访问
resp.sendRedirect("https://www.baidu.com");
浏览器输出.没法录制动图,自己尝试下
结论:URL重定向可以跨域访问
访问WEB-INF
resp.sendRedirect("/WEB-INF/views/login.html");
结论:URL重定向不能访问WEB-INF目录下的资源
总结
-
1.URL重定向会改变浏览器地址栏
(由第一点得出) -
2.URL重定向内容由第二个Servlet决定, 并且由第二个Servlet直接响应给浏览器
(由第二点得出) -
3.URL重定向的请求次数有二次
(由第三点得出) -
4.URL重定向不能共享request中的数据.
(由第四点得出) -
5.URL重定向能跨域访问
(由第五点得出) -
6.URL重定向不能够访问WEB-INF下的资源
(有第五点得出)
可见URL重定向和请求转发的6个特点都是相反的.
请求包含
请求包含主要用在JSP中, 用在Servlet中没有多大意义。因为我们的每个Servlet一般有不同职责, 并不能够包含再一起。而JSP页面可以进行复用,这时候就可以用包含.
语法:request.getRequestDispatcher(path).include(req, resp);
如何选择请求转发和URL重定向
请求转发和URL重定向都是用于Web组件(Servlet和JSP
)的跳转。
- 如果需要在跳转后
共享数据
, 或者访问WEB-INF
下的资源,那么必须用请求转发 - 如果需要在跳转后
不需要共享数据
,或者需要跨域访问资源
,那么必须用URL重定向。 - 其他情况任选
1.2 作用域对象
Servlet三大作用域对象和其操作
Servlet的三大作用域对象其中两个, 我们已经在之前讲过, 分别是request
、session
.之所以说他们是作用域是因为保存在他们中的数据是有作用域范围, request是在同一个请求对象间
, session是在一次会话间
, 又因为它们都是Servlet下的规范,所以称为Servlet作用于对象。说了那么多, 最后一个Servlet作用域对象是ServletContext对象.
ServletContext
ServletContext是一个接口
, 表示Web应用
对象。在JSP中将其称为application
.
- 获取ServletContext
获取Servlet可以通过request
,也可以通过sessioin
、还可以通过在Servlet子类中调用父类的getServletContext()方法
.如下图所示
- ServletContext常用API
- getRealPath(path)
获取资源的绝对路径
控制台输出String path = getServletContext().getRealPath("/login.html");
- getContextPath()
获取当前响应的上下文路径
.即
中path的值.
控制台输出 因为当前Context的path我配置成空字符串,所以输出空
- getRealPath(path)
代码演示Servlet三大作用域
@WebServlet("/scope/servlet")
public class ScopeServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
final String NUM_IN_REQUEST = "NUM_IN_REQUEST";
final String NUM_IN_SESSION = "NUM_IN_SESSION";
final String NUM_IN_APPLICATION = "NUM_IN_APPLICATION";
Integer numInRequest = (Integer) req.getAttribute(NUM_IN_REQUEST);
if (null == numInRequest) {
req.setAttribute(NUM_IN_REQUEST, 1);
}else {
req.setAttribute(NUM_IN_REQUEST, numInRequest+1);
}
HttpSession session = req.getSession();
Integer numInSession = (Integer) session.getAttribute(NUM_IN_SESSION);
if (null == numInSession) {
session.setAttribute(NUM_IN_SESSION, 1);
}else {
session.setAttribute(NUM_IN_SESSION, numInSession+1);
}
ServletContext app = req.getServletContext();
Integer numInApplication = (Integer) app.getAttribute(NUM_IN_APPLICATION);
if (null == numInApplication) {
app.setAttribute(NUM_IN_APPLICATION, 1);
}else {
app.setAttribute(NUM_IN_APPLICATION, numInApplication+1);
}
PrintWriter pw=resp.getWriter();
pw.println("request num = " + req.getAttribute(NUM_IN_REQUEST));
pw.println("session num = " + session.getAttribute(NUM_IN_SESSION));
pw.println("application num = " + app.getAttribute(NUM_IN_APPLICATION));
}
}
第一次访问
第二次刷新
打开另外一个窗口,地址相同
Servlet三大作用域总结
作用域对象 | 作用域类型 | 作用域范围 | 表示 |
---|---|---|---|
request | HttpServletRequest | 当前请求对象 | 每次请求都是一个请求对象 |
session | HttpSession | 一次会话的多个请求之间 | 每次会话都是表示一个session对象 |
application | ServletContext | 作用于整个Web应用, 可以实现多次会话之间的数据共享 | 表示一个web应用 |
1.3 JSP
JSP又称为动态网页, 其专门为页面渲染而生。就要聊聊最初的Servlet
最初的Servlet
-
最早Servlet
诞生的时候就是为了完成动态网页的开发, 是需要负责页面的渲染
, 也就是使用pw =response对象.getWriter()
, 然后使用PrintWriter一行行输出。 - 问题:HTML代码与业务代码耦合。
后来发现这样做不合理, 因为Servlet里要负责的事太多, 既要负责业务逻辑编写, 又要负责输出HTML页面代码, 导致了业务逻辑代码和HTML代码耦合在一起, 难以维护。
责任分离
出于以上的问题, 就将Servlet里负责HTML页面代码的责任进行了分离。让Servlet专注于业务逻辑。JSP技术就顺理成章的诞生了, JSP技术就是专门为了编写HTML代码。
JSP基本语法
- JSP语法的标志
<%符号 %>
.其中符号
用@
、=
、!
、--
代替, 分别有不同的含义.和@
结合表示指令, 和=
结合表示输出, 和!
结合表示定义类成员, 和--
结合表示注释(--
需要成对) - JSP本质上是一种Servlet,所以其也拥有
成员
,拥有service方法
.以下四种语法分别代表四种含义。
注释。
语法:<%-- 注释内容 --%>
输出。用字符输出流输出
语法: <%= "sweetcs" %>
等价于 out.write("sweetcs")
脚本代码段。(定义service方法中的代码)
语法:
<%
代码1;
代码2;
%>
定义类成员。
语法: <%! private int age; %>
代码演示
<%@page import="java.util.ArrayList"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
Insert title here
<%-- 这是一行注释 --%>
<%! private int age = 13;
public ArrayList names = new ArrayList();
%>
<%= "Hello JSP By SweetCS" %>
<%
names.add("AAA");
names.add("BBB");
names.add("CCC");
%>
<%= names %>
JSP原理剖析
看完上述语法和代码你也许还不是懂。没事, 要理解这是怎么回事, 我们得结合底层来理解。
- 定位对应的Servlet
打开项目部署的路径。找到统一级目录下的work
目录.以我mac下的为例, 是在tomcat安装目录work/Catalina/localhost/_/org/apache/jsp/views/jsp
下. - 观察其目录结构
观察其目录结构,我们可以发现我们hello.jsp
是先被翻译
成hello_jsp.java
, 再被编译
成hello_jsp.class
- 查看源码阅读下面的源代码,我们可以发现很多有用的信息。
-
<%! %>
和<% %>
翻译后的位置。<%! %>
和<% %>
定义的代码都被原样的翻译到了这个java文件中。只是<%! %>
对应的是类成员定义,<% %>
中的代码是被搬到_jspService方法之中
。 - JSP本质是Sevlet.该
hello_jsp
继承自HttpJspBase
.而HttpJspBase
又继承自HttpServlet
, 所以我们说hello_jsp是一种servlet, 即其对应的hello.jsp
本质上是一种servlet - JSP的生命周期方法。该Servlet也有
_jspInit
、_jspDestroy
、_jspService
.分别对应Servlet的init方法
、destory方法
、service方法
-
/*
* Generated by the Jasper component of Apache Tomcat
* Version: Apache Tomcat/7.0.90
* Generated at: 2018-08-20 17:14:11 UTC
* Note: The last modified time of this file was set to
* the last modified time of the source file after
* generation to assist with modification tracking.
*/
package org.apache.jsp.views.jsp;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
import java.util.ArrayList;
public final class hello_jsp extends org.apache.jasper.runtime.HttpJspBase
implements org.apache.jasper.runtime.JspSourceDependent {
private int age = 13;
public ArrayList names = new ArrayList();
private static final javax.servlet.jsp.JspFactory _jspxFactory =
javax.servlet.jsp.JspFactory.getDefaultFactory();
private static java.util.Map _jspx_dependants;
private volatile javax.el.ExpressionFactory _el_expressionfactory;
private volatile org.apache.tomcat.InstanceManager _jsp_instancemanager;
public java.util.Map getDependants() {
return _jspx_dependants;
}
public javax.el.ExpressionFactory _jsp_getExpressionFactory() {
if (_el_expressionfactory == null) {
synchronized (this) {
if (_el_expressionfactory == null) {
_el_expressionfactory = _jspxFactory.getJspApplicationContext(getServletConfig().getServletContext()).getExpressionFactory();
}
}
}
return _el_expressionfactory;
}
public org.apache.tomcat.InstanceManager _jsp_getInstanceManager() {
if (_jsp_instancemanager == null) {
synchronized (this) {
if (_jsp_instancemanager == null) {
_jsp_instancemanager = org.apache.jasper.runtime.InstanceManagerFactory.getInstanceManager(getServletConfig());
}
}
}
return _jsp_instancemanager;
}
public void _jspInit() {
}
public void _jspDestroy() {
}
public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response)
throws java.io.IOException, javax.servlet.ServletException {
final javax.servlet.jsp.PageContext pageContext;
javax.servlet.http.HttpSession session = null;
final javax.servlet.ServletContext application;
final javax.servlet.ServletConfig config;
javax.servlet.jsp.JspWriter out = null;
final java.lang.Object page = this;
javax.servlet.jsp.JspWriter _jspx_out = null;
javax.servlet.jsp.PageContext _jspx_page_context = null;
try {
response.setContentType("text/html; charset=UTF-8");
pageContext = _jspxFactory.getPageContext(this, request, response,
null, true, 8192, true);
_jspx_page_context = pageContext;
application = pageContext.getServletContext();
config = pageContext.getServletConfig();
session = pageContext.getSession();
out = pageContext.getOut();
_jspx_out = out;
out.write("\n");
out.write("\n");
out.write("\n");
out.write("\n");
out.write("\n");
out.write("\n");
out.write("Insert title here \n");
out.write("\n");
out.write("\n");
out.write("\t");
out.write('\n');
out.write(' ');
out.write('\n');
out.write(' ');
out.print( "Hello JSP By SweetCS" );
out.write('\n');
out.write(' ');
names.add("AAA");
names.add("BBB");
names.add("CCC");
out.write('\n');
out.write(' ');
out.print( names );
out.write("\n");
out.write("\n");
out.write("");
} catch (java.lang.Throwable t) {
if (!(t instanceof javax.servlet.jsp.SkipPageException)){
out = _jspx_out;
if (out != null && out.getBufferSize() != 0)
try {
if (response.isCommitted()) {
out.flush();
} else {
out.clearBuffer();
}
} catch (java.io.IOException e) {}
if (_jspx_page_context != null) _jspx_page_context.handlePageException(t);
else throw new ServletException(t);
}
} finally {
_jspxFactory.releasePageContext(_jspx_page_context);
}
}
}
JSP三大指令
JSP指令很多,但是实际开发能用到只有少数.主要分为以下三大类指令.JSP中的指令都具备以下两个特点。
- 不会向客户端产生输出。
- 在JSP整个文件范围内有效。
page指令
page指令。用于表示JSP页面的相关配置信息。
比如响应编码的设置, 导入哪些包
.
常用属性
-
languae:
表示JSP中编写的脚本语言类型。 -
contentType:
表示输出的MIME类型和编码。 -
import:
表示要导入的包
<%@ page
language="java"
contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"
import="java.util.ArrayList"
%>
include指令
include指令
主要用来做静态包含
。后面再结合动态包含补充介绍。
taglib指令
taglib指令
后面分享jstl在细讲.其主要为了用来引入标签库
JSP的9大内置对象
JSP中内置了9个对象, 这9个对象是可以直接访问,而无序创建的。因为JSP本质是Servlet, 既然其本质是Servlet, 为了便于访问Servlet常用的对象, JSP中就有了9大对应的对象,如下表。
内置对象 | 内置对象对应的类型 | 描述 | 对应代码 |
---|---|---|---|
request | HttpServletRequest | 请求对象 | |
response | HttpServletResponse | 响应对象 | |
pageContext | PageContext | 当前JSP作用域对象 | pageContext = _jspxFactory.getPageContext(this, request, response,null, true, 8192, true); |
session | HttpSession | 会话对象, sessioin ="true" | session = pageContext.getSession(); |
exception | Throwable | 异常类型, isErrorPag="true" | |
application | ServletContext | 当前web应用对象 | pageContext.getServletContext(); |
config | ServletCofig | 当前web.xml配置对象 | config = pageContext.getServletConfig(); |
out | JspWriter | 字符输出流对象 | out = pageContext.getOut(); |
page | Object | 当前Servlet对象 | final java.lang.Object page = this; |
JSP的四大作用域
Servlet有三大作用域(request
、session
、application
), 又因为JSP本质也是Servlet所以.JSP也有Servlet中的三大作用域, 并且JSP还多了一个作用域就是pageContext
, 表示当前JSP作用域.