这个是参照B站上的一个学习视频做的笔记,但是那个视频里面老师讲的比较碎,我学的不连续,怕忘了,就记好笔记,方便随时查阅。

视频链接:我在B站看的学习视频

目录

WEB启动时创建Servlet

建立Dynamic Web Project

建立Servlet的java代码

建立请求Servlet的映射关系 

测试

启动WEB时自动启动

ServletConfig

用法

ServletContext 

域属性

 getContextPath()

getRealPath() 

欢迎页面的制作 

urlPattern的设置模式

精确路径模式

通配符路径模式

全路径模式

后辍名模式

注意事项 

匹配原则

路径优先后辍匹配原则

精确路径优先匹配原则

最长路径优先匹配原则

Servlet核心

GenericServlet

方法一:方法空实现

方法二:实现 ServletConfig接口 

方法三:无参 init()

官方的GenericServlet

HttpServlet

获取请求的提交方式

Servlet规范中的HttpServlet

快速定义Servlet

HttpServletRequest

请求参数

域属性 

服务端相关信息​​​​​​​

中文乱码问题

原因 

解决方法

HttpServletResponse

向客户端发送数据 

响应乱码的解决方案

请求转发与重定向 

定义

重定向数据传递

重定向数据传输中文乱码解决

重定向到另一个应用里面

请求转发与重定向的选择

RequestDispatcher

forward()与 include()

小细节:提前关闭输出流

访问路径问题

访问路径的组成

绝对路径 

相对路径

以斜杠开头的相对路径

以路径开头的相对路径

前台路径问题举例

后台路径问题举例

以路径名称开头的相对路径

线程安全问题

概念了解

浏览器下查看Cookie

服务端生成Cookie

服务端获取并解析Cookie 

Cookie的禁用

Session

基本用法

Session的工作原理 

写入Session列表

 服务器生成并发送Cookie

客户端接收和发送Cookie 

从Session列表中查找 

Session失效

Cookie禁用后使用Session对会话的追踪

手工重写URL 

重定向重写URL

非重定向重写URL

 总结


注意哈,前面很大的篇幅是对各个细节和原理的解说,如果要自己写代码方便,我这文章里面关于快速开发的小节是:快速定义Servlet、请求转发与重定向的选择。

WEB启动时创建Servlet

建立Dynamic Web Project

首先不管其他什么,必须建立一个完整的框架。那么步骤如下:

先按ctrl+n来新建,然后在框内输入dy就自动出现了我们要选择的目标了

 

然后按next,输入该项目的名字,模板的版本可以自选,我学的那个视频用的是2.5版本,我也是用的2.5版本。之后直接点finish。

 完成之后的左侧的展开效果如下:

建立Servlet的java代码

看到上图的那个src/main/java了吗?选中它,然后ctrl+n,在框内输入cla即可出现class类型了,选中它,然后next

下一步给这个class命名,选择Package,还有加上接口类型(Servlet) 

选中第一个,然后点击ok,最后点击finish 

完成之后看左边和生成的class代码,是这样的:

我画红线的地方就是当时我写的包的名字; 

 自己可以在那个java代码里面写一个无参构造器SomeServlet(),见下图:

 为了方便理解,给那三个方法加上输出,可以清晰看出这个方法的调用

建立请求Servlet的映射关系 

需要在配置文件里面进行配置!

刚双击打开可能会出问题,把那对<display-name></display-name>标签给删了就行其余多余的也暂时用不上,删成下图即可。

这种映射关系的建立称为“注册” 

 输入上面代码的时候,你可以看到我的第八行报错了,报错的原因是:

这个的解决办法很简单:把这个的java改成大写的JAVA即可(看我下图的第四行)

 下面的name是随便起名的,但是那个class就必须是你写的那个java文件的全名就如同我上图的样子

name的内容要和上面的一致,至于/some是什么意思,就是url的提交内容为some,,那么servlet就算是找到了,响应!

测试

实际上映射关系做好之后,就可以访问了。我们做一个演示:

先右击总项目,然后按上图的来。 

我是已经配置好这个服务器,所以就直接选择。(实际上,配置好这个Tomcat服务器是基础,我不多废话,可以自己搜,有很多的博客解释的) 

刚开始会找不到页面,但是没关系,记得之前的/some吗?只要在上图页面的url的后面输入some即可转出正确的页面 

上图因为我实际上之前并没有写过对应的欢迎页面,所以虽然运行不异常,但是确实是空白页面。这个时候你去看你的控制台的内容,因为之前你做了输出的要求,所以,控制台会有输出的,部分人会有中文乱码(比如说我),就是因为没有做好最前面的设置,问题不大。 

服务器既然可以开启,那么也可以销毁的,注意下图的操作:

就关掉了!黑字部分有中文乱码,请不要在意,我当时没有去改。 

启动WEB时自动启动

前面的几个步骤是必要的,说明了我们的Servlet是可以运行的,那么接下来我们就需要做自动启动的配置了。

还是打开web.xml文件,在原来的<servlet>标签内添加如下的内容:

英文很容易理解吧,就是开启的时候加载。然后标签内填写整型数字(必须大于等于0,负数不报错,但是不执行该项),数字表示优先级(不同于前端CSS文件里面z-index的数值越高表示优先级越高,这里越低越高)。这样的原因是一个web里面可能会有多个Servlet,虽然我们现在只做了一个。 

因为文章不是一天写完的,存完草稿之后我又去调整了中文乱码问题,上图就是优化后的。也可以看到上图就是在启动WEB的时候自动的效果。 

值得注意的一点:<url-pattern>标签里面的内容应该是唯一的,尤其是同一个web下的不同的Servlet。

另外提一嘴getServletInfo()方法,其实就是关于Servlet的一些基础信息的返回

ServletConfig

所谓的ServletConfig就是抽象化的web.xml里面的<servlet>标签及里面的内容!

这里的arg0形参是不规范的,这是因为我没有下载Tomcat的源码,所以不规范。你可以自己去下载,然后连接即可。我后来是下载并连接了,具体很简单:比如你在SomeServlet的java文件开头是有extends Servlet,鼠标放在Servlet上,按下ctrl再同时按下鼠标左键,进入页面后有一个显眼的attrach样式的按钮,点击,然后选择下面的(非默认的那个)寻找本地源码,就是点Browse按钮,在你本地下载的源码的压缩包的位置连接上(不需要解压)就行了。 

下一步另外需要将getServletConfig()的返回值修改,不再是null,而是config;

为了验证内容,我们在初始化的时候顺便打印一下内容,看一下效果。 

用法

这里提一个小细节:你可以直接写右边的部分,数据类型什么的,先不写,然后用Alt+shift+L键自动补齐

上面是获取内容应该有的代码,下面是运行效果(运行只需要刷新网页即可): 

其实ServletConfig还有其他的方法可以用,那么让我们了解一下该如何去运用:

比如说初始化参数: 

首先是在web.xml里面进行配置:

既然我们要使用初始化参数的方法,那么必须要有初始化参数存在吧,不然怎么用呢? 

上图的代码就是用来遍历初始化参数的,这个有Java基础的会比较容易看懂。下面看一下运行效果: 

ServletContext 

一个应用(APP)就只有一个ServletContext!(注意APP的概念,初学web结构的同学可能对此不理解,就是指)

和上面的ServletConfig十分类似,这里也是需要初始化的,我们看一下效果:

这个是写在web.xml里面的,8~11行作为一个单位,这样可以写很多个,都是初始化参数。 

域属性

这个是全局性的变量,所有的Servlet都可以用的,为了验证这一点,我们新建一个OtherServlet.java,并进行配置。

 

上面这个值得注意的是OtherServlet的一些初始化设置也是要做的,详细参考SomeServlet,我是直接复制了内容,然后改了service的内容。 

先看some的页面,只有这样才会设置域属性:

控制台运行正常,接下来,先清空控制台,再运行other,然后清楚地看运行效果: 

除此以外,还可以对域属性进行改和删!(我直接用视频原图了,那个里面的moblie就是我的phone) 

 getContextPath()

其实就是获取这个应用的名称(比如我这个应用是My_Try):

getRealPath() 

往往我们现在的框架下的文件夹是不够的,我们会依据自己需求建立像images文件夹(ctrl+n后输入fol回车就行)这样的内容:

欢迎页面的制作 

之前打开的时候,总是404报错页面看的难受,这里我们做一个欢迎页面:

首先是点击src,然后ctrl+n,新建html页面,起名叫index就行

然后是对index.html页面的制作,这个就看个人了。 

这个欢迎页面的实现是依据web.xml文件里面配置实现的: 

<welcome-file>标签可以有多个,但是服务器会从所有序列的从上到下依次查找,找到了就不再往下找了。 

urlPattern的设置模式

精确路径模式

除了完全一致,一个Servlet的配置可以有很多种,就像上图。(也就是说你在栏里面输入some或test/other或/xxx/ooo/third这些都是指向同一个Servlet) 

通配符路径模式

该模式中的路径由两部分组成:精确路径部分与通配符部分。请求路径中只有携带了<url-pattern/>值中指定的精确路径部分才可被当前 Servlet 处理。

星号就是表示随便什么都可以,但是它前面的一定要一丝不苟 

全路径模式

提交的所有请求全部可被当前的 Servlet 处理(这句话的意思也是在说该方式会拦截所有请求)。其值可以指定为 /* ,也可指定为 / 。

使用 /* ,表示当前的 Servlet 会拦截用户对于静态资源( .css 、 .js 、 .html 、 .jpg 、 .png….. )
与动态资源( .jsp )的请求。即用户不会直接获取到这些资源文件,而是将请求交给当前 Servlet 来处理了
使用 / ,表示当前的 Servlet 会拦截用户对于静态资源( .css 、 .js 、 .html 、 .jpg 、 .png….. ),
但对于动态资源的请求,是不进行拦截的。即用户请求的静态资源文件是不能直接获取到的
综上所述,对于 Servlet 的 <url-pattern/> 的设置,我们一般是不会将其指定为 /* 或 / 的。一旦有一个 Servlet 的 <url-pattern/> 被设置为了 /* 或 / ,则整个应用的静态资源将可能无法正常显示!

后辍名模式

请求路径最后的资源名称必须携带 <url-pattern/> 中指定的后辍名,其请求才可被当前Servlet 处理(可以理解为拦截特定后缀的请求)

注意事项 

为 <url-pattern/> 设置值时,带斜杠的通配符路径模式与后辍名模式不能同时使用,例如,不能使用/*.do , /xxx/*.do 等形式。否则,服务器将无法正常启动。

匹配原则

当一个请求发送到服务器的时候,服务器首先会解析请求路径。将请求的 URL 去掉“HOST + ContextPath ”,即去掉“服务器路径 + 当前应用上下文的路径”,然后将 URL的剩余部分作为要与<url-pattern/> 进行匹配的 URI 。
例如,用户提交的请求为http://localhost:8080/My_Try/xxx/ooo ,则去掉“HOST+ContextPat h ”后剩余部分为/xxx/ooo.服务器会将这个/xxx/ooo与 web.xml 中所有的 <url-pattern/> 进行匹配。
这个映射匹配过程是有顺序的,而且当有一个 servlet 匹配成功以后,就不会再去与其它 Servlet 进行匹配了。具体的匹配原则为:

路径优先后辍匹配原则

例如 SomeServlet 的 <url-pattern/> 为 *.do , OtherServlet 的 <url-pattern/>为 /xxx/* 。
若用户提交的请求 URL 为 http://localhost:8080/My_Try/xxx/abc.do ,此时服务器发现SomeServlet 的 *.do 与 OtherServlet 的 /xxx/* 都可以与用户提交请求的/xxx/abc.do 相匹配。那么服务器会按照“路径优先后辍匹配”的原则选择 OtherServlet

精确路径优先匹配原则

例如 SomeServlet 的 <url-pattern/> 为 /some , OtherServlet 的 <url-pattern/>为 /* 。若用户提交的请求 URL 为http://localhost:8080/My_Try/some,此时服务器发现SomeServlet 的/some 与 OtherServlet 的/*都可以与用户提交请求的/some 相匹配。那么服务器会按照“精确路径优先匹配”的原则选择 SomeServlet。

最长路径优先匹配原则

如 SomeServlet 的 <url-pattern/> 为 /some/* , OtherServlet 的<url-pattern/>为 /some/other/* 。若用户提交的请求 URL 为http://localhost:8080/oa/some/other,此时服务器发现 SomeServlet 的 /some/*与OtherServlet 的 /some/other/* 都可以与用户提交请求的 /some/other 相匹配。
那么服务器会按照“最长路径优先匹配”的原则选择 OtherServlet 。

Servlet核心

在通过实现 Servlet 接口来定义 Servlet 时存在一个很不方便的问题:
有太多不需要的方法必须要实现。通常我们只关心 service() 方法,在 service() 方法中完成业务逻辑,但由于Servlet 接口中还存在另外四个方法,所以也要必须实现。

GenericServlet

方法一:方法空实现

 由于 Servlet 中通常只使用 service()方法,其它四个方法基本不用,但也需要实现,于是我们就想,能否定义一个通用的抽象类,让其实现 Service 接口,并以简单方式对 service()

以外的其它方法进行实现,即要么是空方法体,要么返回 null ,而将 service() 方法声明为抽
象方法。
这样,以后再定义 Servlet 时就只需要继承自这个通用的抽象类即可,无需再实现了 Service 接口了!

 先按照之前的方法建立一个普通的Servlet接口的java程序,名字叫做GenericServlet,但是内容要改成上面这样!

我们新建一个普通的java程序,然后来继承我们定义的GenericServlet即可

成功的样式是上面这样的。为了测试,那么我们先在web.xml里面注册: 

 

为了看到效果,在InheritServlet.java里面做这样: 

  

 看一下运行效果:

方法二:实现 ServletConfig接口 

上面的 GenericServlet 定义存在一个问题:
将来子类实现 GenericServlet 的 service() 方法 后,无法获取到ServletConfig 对象。当然,也就无法获取到 Servlet 的初始化参数,无法获取到 ServletContext 对象,及上下文参数了。所以,需要修改这个类的定义。

直接在GenericServlet里面重新定义修改ServletConfig就可以了。(如果有报错,记得看看开头的地方包都导全了吗?)

package com.try_my;

import java.io.IOException;
import java.util.Enumeration;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public abstract class GenericServlet implements Servlet,ServletConfig {

	private ServletConfig config;
	
	@Override
	public void destroy() {}

	@Override
	public ServletConfig getServletConfig() {
		return config;
	}

	@Override
	public String getServletInfo() {
		return null;
	}

	@Override
	public void init(ServletConfig arg0) throws ServletException {}

	@Override
	public abstract void service(ServletRequest arg0, ServletResponse arg1)
			throws ServletException, IOException;
	
	public String getInitParameter(String name) {
		return config.getInitParameter(name);
	}
	
	public Enumeration<String> getInitParameterNames() {
		return config.getInitParameterNames();
	}
	
	public ServletContext getServletContext() {
		return config.getServletContext();
	}
	
	public String getServletName() {
		return config.getServletName();
	}
}

接下来我们进行测试,首先看一下测试的代码:

package com.try_my;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class InheritServlet extends GenericServlet {

	@Override
	public void service(ServletRequest arg0, ServletResponse arg1) throws ServletException, IOException {
		System.out.println("InheritServlet成功实现");
		String phone = this.getInitParameter("phone");
		System.out.println("phone = " + phone);
		
		String servletName = this.getServletName();
		System.out.println("servletName = " + servletName);
	}
}

另外需要在GenericServlet里面初始化,否则会报错:

  

看一下运行效果: 

 

方法三:无参 init()

若一个类例如 SomeServlet ,声明继承自 GenericServlet 类,要求在 SomeServlet 类进行
初始化时做一些工作,这个需求怎么实现呢?
一种途径是,让 SomeServlet 重写 GenericServlet 的 init(ServletConfig) 方法,但这个重写 的 init(ServletConfig) 方法必须要调用父类的 init(ServletConfig) 方法,即在第一句必须写上
super.init(config) ;否则将无法获取到 ServletConfig 对象。
若 ServletConfig 对象未获取,直接导致无法获取 ServletContext 、 Servlet 初始化参数。当然,程序在运行时就会直接抛出空指针异常。但这句对父类 init(ServletConfig) 方法的调用语句,极易忘写。

看一下效果: 

虽然可以在GenericServlet.java里面直接写这个特殊的代码(像这个打印的例子),但是一旦在那个里面写,所有继承的子Servlet都会有打印的代码,这个是不合适的。 

另一种途径是比较好的解决方式。在 GenericServlet 中以无参的形式重载 init(ServletConfig)方法,而 init(ServletConfig) 方法调用无参 init() 方法。让子类重写这个无参的 init() 方法。而这也是一种设计模式,“模板方法”设计模式。
在GenericServlet中的代码
在InheritServlet中的代码

 上图是视频里面的截图,和我的区别就是我是不规范的arg0他是config,注意看一下就行。 

官方的GenericServlet

在 Servlet 规范中已经为程序员定义好了 javax.servlet.GenericServlet 抽象类。这个抽象类比我们自己定义的 GenericServlet 功能更强大。
也就是说,我们以后再定义 Servlet 类,只需要直接继承自 GenericServlet 抽象类即可。

可以看一下官方的文件长什么样子: 

HttpServlet

获取请求的提交方式

一般,用户提交的东西,我们都是通过表单接收进来的,下面我们就以改造index.html为例子给展示一下:

很显然,我们LoginServlet还没有做,我们做一下(记得在web.xml里面注册) 

点击finish之后就是上图的样子,为了方便测试,我改成下面这样: 

下面还是web.xml注册: 

看一下运行效果: 

网页正常打开,再点击一下登录,看一下控制台会有什么变化: 

千万注意:form表单里面的action的内容一定是和web.xml里面设置的<url-pattern>是一致的,否则将会404错误! 

可以思考一个问题,如果不提交表单直接用/LoginServlet不就会直接可以访问后台了嘛,这样安全性很低,所以需要考虑判别提交方式是否为POST。

那么,如何获取用户请求的提交方式呢?肯定要从请求对象 ServletRequest 中获取。但查看 ServletRequest 的 API 文档,发现其中并没有可以获取到请求提交方式的方法。
不过,查看其子接口 HttpServletRequest ,发现其中的 getMethod() 方法可以获取用户请求提交方式。所以,我们就需要将 ServletRequest 对象强转为 HttpServletRequest。

我直接用了视频里面的截图,因为老师写的很好,就当回顾Java知识了。

下面看一下运行效果(我也在我电脑上做了): 

上面是提交表单的,下面的是直接输入url内容的。可以看出,提交的方式是不一样的。 

Servlet规范中的HttpServlet

为了方便,可以直接定义一个HttpServlet,省去再去继承GenericServle的步骤。不过自定义写起来麻烦,本来规范里面就有我们直接继承就可以了。

我这里重新写一个Login_Servlet的内容来测试一下:

 

值得注意的是:HttpServletRequest和HttpServletResponse不要写错了,这个是我之前一直没看出来,一直出错的地方。

快速定义Servlet

上面的参数是需要根据你的需要来设置的:Java package后面就是填写包名;Class name就是填写你要给这个java程序起什么名字,我之前都是写SomeServlet这样的名字;Superclass一般直接默认就可以了。因为前面的学习也很清楚了,HttpServlet可以自己继承GenericServlet,所以HttpServlet可以有很大的应用范围。 

最后点Finish就完成了。 

HttpServletRequest

请求参数

HttpServletRequest 对于请求中所携带的参数是以 Map 的形式接收的,并且该 Map  的 key为 String , value 为 String 数组。注意,是 String 数组!
为什么是 String 数组而不是 String 呢?因为 Http 请求协议允许一个请求参数具有多个值的情况出现。例如下面表单中的复选框 hobby 的值就可能是多个。

首先说明一下获取参数的一些方法:

· Map<String,String[]> getParameterMap()
获取包含所有请求参数及值的 Map 对象。需要注意,该 Map 的 value 为 String[],即一个参数所对应的值为一人数组。说明一个参数可以对应多个值。
· Enumeration<String> getParameterNames ()
获取请求参数 Map 的所有 key ,即获取所有请求参数名 name 。
· String[] getParameterValues(String name)
根据指定的请求参数名称,获取其对应的所有值。这个方法一般用于获取复选框数据。
· String getParameter(String name)
根据指定的请求参数名称,获取其对应的值。若该参数名称对应的是多个值,则该方法获取到的是第一个值。这个方法是最常用的方法。

下面是一个代码举例:

首先是建立必须的html页面,这样才能有提交的页面(我是直接改造了index页面):

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h1>Hello,World!</h1>
	<form action="Login_Servlet" method="POST">
		用户名:<input type="text" name="username"/><br>
		年龄:<input type="text" name="age"/><br>
		爱好:
			<input type="checkbox" name="hobby" value="reading"/>阅读
			<input type="checkbox" name="hobby" value="running"/>跑步
			<input type="checkbox" name="hobby" value="gaming"/>游戏<br>
		<input type="submit" value="登录">
	</form>
</body>
</html>

然后是定义Servlet,我是直接改造之前现成的Login_Servlet的: 

package com.try_my;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class Login_Servlet extends HttpServlet {

	public void doGet(HttpServletRequest request,HttpServletResponse response) 
			throws ServletException, IOException{
		doPost(request,response);
//		System.out.println("非法用户");
	}
	
	protected void doPost(HttpServletRequest request,HttpServletResponse response) 
			throws ServletException, IOException{
//		System.out.println("下一步进入用户阶段");
		String username = request.getParameter("username");
		String age = request.getParameter("age");
		String[] hobby = request.getParameterValues("hobby");
		
		System.out.println("username = " + username);
		System.out.println("age = " + age);
		for (String h : hobby) {
			System.out.println("hobby = " + h);
		}
	}
}

下面来验证一下效果:

打算输入这样的东西,我们看一下控制台的反应: 

 效果是很好的(注意最后一个因为是数组形式,所以要遍历的)

注意!需要掌握以下三点:

域属性 

在 Request 中也存在域属性空间,用于存放有名称的数据。该数据只在当前 Request 请求中可以进行访问。

常见的方法:

· void setAttribute(String name, Object object)
在 Request 域属性空间中放入数据。其生命周期与 Request 的生命周期相同
· Object getAttribute(String name)
从 Request 的域属性空间中获取指定名称的数据
· void removeAttribute(String name)
从 Request 的域属性空间中删除指定名称的数据。
由于这里我们举例子需要用到另外一个方法,所以将这个方法也进行介绍。
· RequestDispatcher getRequestDispatcher (String path)
该方法用于创建请求转发器,而该请求转发器有一个方法 forward() ,用于完成将请求对
象转发给下一个资源。 forward() 方法的原型如下:
void forward(HttpServletRequest request, HttpServletResponse response);
下面还是举个例子来方便了解这个域属性:
首先index.html是保持之前的样式不变,然后还是继续在Login_Servlet.java的页面的基础上改进:
package com.try_my;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class Login_Servlet extends HttpServlet {

	public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doPost(request, response);
//		System.out.println("非法用户");
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
//		System.out.println("下一步进入用户阶段");
		String username = request.getParameter("username");
		String ageStr = request.getParameter("age");
		Integer age = Integer.valueOf(ageStr);
		String[] hobby = request.getParameterValues("hobby");

		System.out.println("username = " + username);
		System.out.println("age = " + age);
		for (String h : hobby) {
			System.out.println("hobby = " + h);
		}

		request.setAttribute("username", username);
		request.setAttribute("age", age);
		request.getRequestDispatcher("Other").forward(request, response);
	}
}

我又新建了一个Other_Servlet.java文件(是为了不破坏我之前写的OtherServlet):

package com.try_my;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class Other_Servlet extends HttpServlet {
	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		doPost(request, response);
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		String username = (String) request.getAttribute("username");
		Integer age = (Integer) request.getAttribute("age");

		System.out.println("username is " + username);
		System.out.println("age is " + age);

//		request.getRequestDispatcher("/welcome.html").forward(request, response);
	}

}

另外记得检查web.xml里面的配置:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
		 xmlns="http://java.sun.com/xml/ns/javaee" 
		 xsi:schemaLocation="http://JAVA.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" 
		 id="WebApp_ID" version="2.5">
  
  <!-- 定义初始化参数 -->
  <context-param>
  	<param-name>cy4</param-name>
  	<param-value>出生</param-value>
  </context-param>
  
<servlet>
  	<servlet-name>SomeServlet</servlet-name>
  	<servlet-class>com.try_my.SomeServlet</servlet-class>
  	<load-on-startup>1</load-on-startup>
  	<init-param>
  		<param-name>cy4</param-name>
  		<param-value>出生</param-value>
  	</init-param>
  	<init-param>
  		<param-name>荣拳皇</param-name>
  		<param-value>gn_show_show_way</param-value>
  	</init-param>
</servlet>
<servlet-mapping>
  	<servlet-name>SomeServlet</servlet-name>
  	<url-pattern>/some</url-pattern>
  </servlet-mapping>
  
<servlet>
  	<servlet-name>OtherServlet</servlet-name>
  	<servlet-class>com.try_my.OtherServlet</servlet-class>
  	<load-on-startup>2</load-on-startup>
  	<init-param>
  		<param-name>cy4</param-name>
  		<param-value>gn_show_show_way</param-value>
  	</init-param>
  	<init-param>
  		<param-name>荣拳皇</param-name>
  		<param-value>出生</param-value>
  	</init-param>
</servlet>
<servlet-mapping>
  	<servlet-name>OtherServlet</servlet-name>
  	<url-pattern>/other</url-pattern>
</servlet-mapping>

<servlet>
  	<servlet-name>InheritServlet</servlet-name>
  	<servlet-class>com.try_my.InheritServlet</servlet-class>
  	<init-param>
  		<param-name>phone</param-name>
  		<param-value>123456</param-value>
  	</init-param>
</servlet>
<servlet-mapping>
  	<servlet-name>InheritServlet</servlet-name>
  	<url-pattern>/Inherit</url-pattern>
</servlet-mapping>

<servlet>
  	<servlet-name>LoginServlet</servlet-name>
  	<servlet-class>com.try_my.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
  	<servlet-name>LoginServlet</servlet-name>
  	<url-pattern>/LoginServlet</url-pattern>
</servlet-mapping>

<servlet>
  	<servlet-name>Login_Servlet</servlet-name>
  	<servlet-class>com.try_my.Login_Servlet</servlet-class>
</servlet>
<servlet-mapping>
  	<servlet-name>Login_Servlet</servlet-name>
  	<url-pattern>/Login_Servlet</url-pattern>
</servlet-mapping>
 
 <servlet>
  	<servlet-name>Other_Servlet</servlet-name>
  	<servlet-class>com.try_my.Other_Servlet</servlet-class>
</servlet>
<servlet-mapping>
  	<servlet-name>Other_Servlet</servlet-name>
  	<url-pattern>/Other</url-pattern>
</servlet-mapping>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
  </welcome-file-list>
</web-app>

看一下运行效果:

服务端相关信息

相关的方法:
· StringBuffer getRequestURL():
获取请求的 URL ,即从请求协议开始的绝对路径。其方法返回值一般与 toString() 方法连用,转为 String 类型。例如, http://localhost:8080/httpServlet/some ,就是一个完整的 URL 。
· String getRequestURI():
获取请求的 URI ,即从项目名称开始的请求路径。例如上面的 URL 中的 URI 部分 是/httpServlet/some 。
· String getContextPath():
获取当前应用在 Web 容器中的名称,就是根路径,比如我的那个"My_Try"
· String getServletPath() 与 String getPathInfo():
获取请求路径中与 web.xml 中注册 Servlet 时的 <url-pattern/> 相匹配的路径信息。其中 serveltPath 指的是与 <url-pattern/> 精确部分相匹配的路径,而 pathInfo 则是与非精确部分相匹配的路径(一般配置文件里面都是写*表示非精确部分)。
例如,<url-pattern/> 的值为 /xxx/ooo/* ,用户提交的请求为 http://localhost:8080/httpServlet/ xxx/ooo/abc/def ,则从 request 中获取到的 ServletPath值为/xxx/ooo ,而 PathInfo 则为 /abc/def 。
不过,若用户提交的请求为后辍方式,例如 http://localhost:8080/httpServlet/aaa/bbb/ccc. do ,则 ServletPath 的值为 /aaa/bbb/ccc.do , 而PathInfo 的值为 null 。
· String getMethod():
获取请求方式,是 GET 请求,还是 POST 请求。
· String getRemoteAddr():
获取客户端浏览器的 IP ,对于服务端来说,客户端就是远程端,而自己则是本地端

中文乱码问题

Http 协议中规定,数据的传输采用字节编码方式,即无论浏览器提交的数据所包含的中文是什么字符编码格式,一旦由浏览器经过 Http 协议传输,则这些数据均将以字节的形式上传给服务器。因为HTTP协议的底层使用的是TCP传输协议。TCP(Transmission Control

Protocol) ,传输控制协议,是一种面向连接的、可靠的、基于字节流的、端对端的通信协议。在请求中,这些字节均以% 开头,并以十六进制形式出现。如 %5A%3D 等。 
举个例子,我前一个小节的用户提交页面,之前都是没有用中文提交的,现在看一下用中文会怎么样:

可以看出,张三变成了乱码,这就是我们需要解决的一个问题。 

原因 

在解决问题之前,我们最好了解为什么会出现这样的问题:
当用户通过浏览器提交一个包含 UTF-8 编码格式的两个字的中文请求时,浏览器会将这两个中文字符变为六个字节(一般一个 UTF-8 汉字占用三个字节),即形成六个类似 %8E 的字节表示形式,并将这六个字节上传至 Tomcat 服务器。
Tomcat 服务器接收到这六个字节后,并不知道它们原始采用的是什么字符编码。而Tomcat默认的编码格式为 ISO-8859-1 。所以会将这六个字节按照 ISO-8859-1 的格式进行编码,编码后在控制台显示,所以在控制台会显示乱码。

解决方法

针对 POST 提交方式:
在接收请求参数之前先通过 request 的 setCharacterEncoding() 方法,指定请求体的字符编码格式。这样的话,在接收到请求中所携带的参数后,就可按照指定的字符编码进行编码。
注意, request 的 setCharacterEncoding() 方法只能解决 POST 提交方式中的乱码问题,对
于 GET 提交方式的不起作用。因为该方法设置的是请求体中的字符编码, GET 提交中的参数不出现在请求体中,而出现在请求行。
package com.try_my;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class Login_Servlet extends HttpServlet {

	public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		request.setCharacterEncoding("UTF-8");
		doPost(request, response);
//		System.out.println("非法用户");
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
//		System.out.println("下一步进入用户阶段");
		request.setCharacterEncoding("UTF-8");
		String username = request.getParameter("username");
		String ageStr = request.getParameter("age");
		Integer age = Integer.valueOf(ageStr);
		String[] hobby = request.getParameterValues("hobby");

		System.out.println("username = " + username);
		System.out.println("age = " + age);
		for (String h : hobby) {
			System.out.println("hobby = " + h);
		}

		request.setAttribute("username", username);
		request.setAttribute("age", age);
		request.getRequestDispatcher("Other").forward(request, response);
	}
}

针对 GET 提交方式:
可以通过修改 Tomcat 默认字符编码的方式来解决 GET 提交方式中携带中文的乱码问题。在Tomcat 安装目录的 conf/server.xml 中,找到端口号为 8080 的 <Connector/> 标签,在其中
添加 URIEncoding=UTF-8 的设置,即可将  Tomcat 默认字符编码修改为 UTF-8 。
重启 Tomcat 服务器后, GET 提交方式的中文乱码问题解决。
(一般来说,Tomcat是官网下载的话,这个9版本的已经解决该问题了,不放心的可以去检查一下自己的相关文件)

万能解决方案:

 该方式无需设置 Tomcat 中的 server.xml 中的 Tomcat 默认字符编码,无需设置 request

的请求体的字符编码。
从数据在请求中的存放形式,到数据被 Tomcat 中的 Servlet 接收到后的存放形式,均是由单个字节的形式存在,而在众多字符编码格式中,ISO8859-1 为单字节编码,所以,首先以 ISO8859-1 的形式先对单字节的数据进行编码,并将编码后的数据存放在字节数组中。然
后,再将字节数组中的数据,按照指定的 UTF-8 格式进行解码,即变为了需要的 UTF-8 字符编码的数据,解决了中文乱码问题。
·编码,即重新编排,即打散,将字符串打散后按照指定编码进行重新编排。这里的编码使用的是 String 的 getBytes() 方法,完成的工作是:按照当前字符编码将数据打散。
·解码,即解释执行,即组装,对打散的字符按照指定编码组装后进行解释执行。这里的解码使用的是 String 的带参构造器,完成的工作是:按照原有字符编码将数据组装。

当然了,这个是有弊端的,比如如果有很多的请求对象,你就得去写很多的上面15-19行的那三个核心代码,很麻烦。(除非是你工作的公司用的是低版本的Tomcat,否则get方法不用烦心,只需要记住post方法改一改就行) 

HttpServletResponse

向客户端发送数据 

其实有一个很正常的现象就是如果用户提交完内容之后页面会进行跳转或者页面返回一些东西,最基础的就是在页面上输出文本:
package com.try_my;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class Login_Servlet extends HttpServlet {

	public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		request.setCharacterEncoding("UTF-8");
		doPost(request, response);
//		System.out.println("非法用户");
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
//		System.out.println("下一步进入用户阶段");
		request.setCharacterEncoding("UTF-8");
		String username = request.getParameter("username");
		String ageStr = request.getParameter("age");
		Integer age = Integer.valueOf(ageStr);
		String[] hobby = request.getParameterValues("hobby");

		System.out.println("username = " + username);
		System.out.println("age = " + age);
		for (String h : hobby) {
			System.out.println("hobby = " + h);
		}
		PrintWriter out = response.getWriter();
		out.println("username is " + username);
		out.print("age is " + age);

//		request.setAttribute("username", username);
//		request.setAttribute("age", age);
//		request.getRequestDispatcher("Other").forward(request, response);
	}
}

总结:不需要管关不关的问题,下面是解释:

 对于输出流对象,一般是需要关闭的,但对于 PrintWriter 流对象,其是否需要关闭,要取决于业务的需求。若直接是要将流中数据输出到客户端浏览器的,则可以将 PrintWriter进行关闭。若项目中使用到了过滤器 Filter(后面学习),且 Filter 对响应对象还进行了其它操作,或在标准输出流向客户端发送数据之前,其它 Servlet 还向流中写入数据,那么PrintWriter 对象就不能由程序员手工关闭了。无论是否显式地关闭 PrintWriter 对象,在本次响应结束时,Web 容器会自动将没有关闭的 PrintWriter 对象进行自动关闭。

响应乱码的解决方案

若在 PrintWriter 流中写入中文字符,那么,在客户端浏览器中将显示乱码。例如,本例中的 username 在表单中填入汉字,则将显示乱码.
响应时会产生乱码,是因为 HTTP 协议中规定,默认响应体的字符编码为ISO-8859-1。故,若要解决乱码问题,就需要修改响应体的默认编码。一般情况下,有两种方式可以修改:
·HttpServletResponse 的 setContentType(“text/html;charset=utf-8”) 方法,用于设置响应内容的 MIME 类型,其中可以指定 MIME 的字符编码。而 MIME 的字符编码,即响应体的字符编码

·HttpServletResponse 的 setCharacterEncoding(“utf-8”)方法,用于修改 MIME 的字符编码, 即修改响应体的字符编码。

但!使用的前提是,之前必须要通过使用方法setContentType()方法设置响应内容的 MIME 类型。否则setCharacterEncoding()方法不起作用。

(所以我个人认为第二个方法有点画蛇添足,第一个方法直接设定成UTF-8就好了)

特别需要注意的一点!这些设置,必须在 PrintWriter 对象产生之前先设置,否则不起作用。

请求转发与重定向 

定义

请求转发:

资源 1 在服务器内部,直接跳转到的资源 2 ,所以请求转发也称为 服务器内跳转 。整个过程浏览器只发出一次请求,服务器只发出一次响应。所以,无论是资源1还是资源 2 ,整个过程中,只存在一次请求,即用户所提交的请求。所以,无论是资源 1 还是资源 2 ,它们均可从这个请求中获取到用户提交请求时所携带的相关数据。
重定向:
资源 1 需要访问资源 2 ,但并未在服务器内直接访问,而是由服务器自动向浏览器发送一个响应,浏览器再自动提交一个新的请求,这个请求就是对资源 2 的请求。对于资源 2 的访问,是先跳出了服务器,跳转到了客户端浏览器,再跳回到了服务器。所以重定向又称为服务器外跳转。
整个过程中,浏览器共提交了两次请求,服务器共发送了两次响应。只不过,第一次响应与第二次请求,对于用户来说是透明的,是感知不到的。用户认为,自己只提交了一次请求,且只收到了一次响应。
这样的话,就出现了一个问题:资源 2 中是无法获取到用户提交请求中的数据的。它只能获取到第二次请求中所携带的数据。

  

请求转发举例(看一下意思就行,不需要怎么去敲代码验证了):

  

这里的OtherServlet就相当于资源2了,看一下下面的效果:

很显然,因为是一个请求,所以之前设定的那些域属性是可以继承的。这就验证了是一个请求的说法。 

重定向举例(只需要改掉之前使用的方法就行):

 

很显然,因为不是一个请求,所以会有空指针异常!不信咱们可以看下面的验证(注意OtherServlet的代码改动): 

重定向数据传递

只需要改下面的一行代码就可以进行:

response.sendRedirect("Other?username=" + username + "&age=" + age);

主要的要素就是"?"和"&"两个部件,用来传递数据的,但是一个缺陷是,这个会在url的栏目里面显示。

重定向数据传输中文乱码解决

首先看一下出问题的代码(英文、数字可以传输,即上一小节的代码):

package com.try_my;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class Login_Servlet extends HttpServlet {

	public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		request.setCharacterEncoding("UTF-8");
		doPost(request, response);
//		System.out.println("非法用户");
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
//		System.out.println("下一步进入用户阶段");
		request.setCharacterEncoding("UTF-8");
		String username = request.getParameter("username");
		String ageStr = request.getParameter("age");
		Integer age = Integer.valueOf(ageStr);
		String[] hobby = request.getParameterValues("hobby");

		System.out.println("username = " + username);
		System.out.println("age = " + age);
		for (String h : hobby) {
			System.out.println("hobby = " + h);
		}
		PrintWriter out = response.getWriter();
		out.println("username is " + username);
		out.print("age is " + age);

//		request.setAttribute("username", username);
//		request.setAttribute("age", age);
//		request.getRequestDispatcher("Other").forward(request, response);
		response.sendRedirect("Other?username=" + username + "&age=" + age);
	}
}
package com.try_my;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class Other_Servlet extends HttpServlet {
	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		doPost(request, response);
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		String username = (String) request.getAttribute("username");
		Integer age = (Integer) request.getAttribute("age");

		System.out.println("username is " + username);
		System.out.println("age is " + age);

		String user_name = request.getParameter("username");
		String aGe = request.getParameter("age");

		System.out.println("username ==== " + user_name);
		System.out.println("age ==== " + aGe);
//		request.getRequestDispatcher("/welcome.html").forward(request, response);
	}

}

 其实很容易判断:问题是出现在了传输里面的!

package com.try_my;

import java.io.IOException;
import java.io.PrintWriter;
import java.net.URLEncoder;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class Login_Servlet extends HttpServlet {

	public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		request.setCharacterEncoding("UTF-8");
		doPost(request, response);
//		System.out.println("非法用户");
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
//		System.out.println("下一步进入用户阶段");
		request.setCharacterEncoding("UTF-8");
		String username = request.getParameter("username");
		String ageStr = request.getParameter("age");
		Integer age = Integer.valueOf(ageStr);
		String[] hobby = request.getParameterValues("hobby");

		System.out.println("username = " + username);
		System.out.println("age = " + age);
		for (String h : hobby) {
			System.out.println("hobby = " + h);
		}
		PrintWriter out = response.getWriter();
		// 只要下面的请求转发和重定向不去实现,在本页面内的都可以打印,否则就有其他的考虑了
        // 这个考虑会在下面的小节解释的
		out.println("username is " + username);
		out.print("age is " + age);

//		request.setAttribute("username", username);
//		request.setAttribute("age", age);
//		请求转发:
//		request.getRequestDispatcher("Other").forward(request, response);
		username = URLEncoder.encode(username, "UTF-8");// 编码:打散
		response.sendRedirect("Other?username=" + username + "&age=" + age);// 重定向
	}
}

 注意看倒数第四行,有一个打散的过程,那么显然,之后肯定是要重组的了:

package com.try_my;

import java.io.IOException;
import java.net.URLDecoder;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class Other_Servlet extends HttpServlet {
	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		doPost(request, response);
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
//		String username = (String) request.getAttribute("username");
//		Integer age = (Integer) request.getAttribute("age");
//
//		System.out.println("username is " + username);
//		System.out.println("age is " + age);

		String user_name = request.getParameter("username");
		user_name = URLDecoder.decode(user_name, "UTF-8");// 解码:组装
//		user_name = new String(user_name.getBytes("ISO8859-1"), "UTF-8");
		// 老师解释因为是网页传来的数据,所以还是需要再来一次上面的代码,但是我测试的结果是不需要上面的代码
		// 大概是因为老师测试用的是Tomcat7吧
		String aGe = request.getParameter("age");

		System.out.println("username ==== " + user_name);
		System.out.println("age ==== " + aGe);
//		request.getRequestDispatcher("/welcome.html").forward(request, response);
	}

}

重定向到另一个应用里面

为此,我不得不新建立一个应用(我个人是非常爱惜自己电脑的内存的[笑],主要是穷,买不起好装备)。

首先是对My_Try里面的Login_Servlet进行改进: 

package com.try_my;

import java.io.IOException;
import java.io.PrintWriter;
import java.net.URLEncoder;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class Login_Servlet extends HttpServlet {

	public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		request.setCharacterEncoding("UTF-8");
		doPost(request, response);
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		request.setCharacterEncoding("UTF-8");
		String username = request.getParameter("username");
		String ageStr = request.getParameter("age");
		Integer age = Integer.valueOf(ageStr);
		String[] hobby = request.getParameterValues("hobby");

		System.out.println("username = " + username);
		System.out.println("age = " + age);
		for (String h : hobby) {
			System.out.println("hobby = " + h);
		}
		

//		request.setAttribute("username", username);
//		request.setAttribute("age", age);
//		请求转发:
//		request.getRequestDispatcher("Other").forward(request, response);
		username = URLEncoder.encode(username, "UTF-8");// 编码:打散
		response.sendRedirect("/My_Try_Copy/other?username=" + username + "&age=" + age);// 重定向
	}
}

然后是新建的My_Try_Copy的里面的OtherServlet代码的编写:

package com.My_try;

import java.io.IOException;
import java.io.PrintWriter;
import java.net.URLDecoder;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class OtherServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		doPost(request, response);
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		request.setCharacterEncoding("UTF-8");
		response.setContentType("text/html;charset=UTF-8");
		System.out.println("成功执行了My_Try_Copy的OtherServlet");
		PrintWriter out = response.getWriter();
		out.print("这是My_Try_Copy的OtherServlet");
		String username = request.getParameter("username");
		username = URLDecoder.decode(username, "UTF-8");// 解码:组装
		String age = request.getParameter("age");
		System.out.println("username == " + username);
		System.out.println("age == " + age);
		out.print("username == " + username);
		out.print("age == " + age);
	}
}

另外注意新的web.xml的配置。

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
		xmlns="http://java.sun.com/xml/ns/javaee" 
		xsi:schemaLocation="http://JAVA.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" 
		id="WebApp_ID" version="2.5">
  <display-name>My_Try_Copy</display-name>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.jsp</welcome-file>
    <welcome-file>default.htm</welcome-file>
  </welcome-file-list>
  <servlet>
    <description></description>
    <display-name>OtherServlet</display-name>
    <servlet-name>OtherServlet</servlet-name>
    <servlet-class>com.My_try.OtherServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>OtherServlet</servlet-name>
    <url-pattern>/other</url-pattern>
  </servlet-mapping>
</web-app>

看一下运行效果:

很显然,实现了应用间的跳转。 

请求转发与重定向的选择

请求转发的特点:
A 、浏览器只发出一次请求,收到一次响应
B 、请求所转发到的资源中可以直接获取到请求中所携带的数据
C 、浏览器地址栏显示的为用户所提交的请求路径
D 、只能跳转到当前应用的资源中
重定向的特点:
A 、浏览器发出两次请求,接收到两次响应
B 、重定向到的资源不能直接获取到用户提交请求中所携带的数据
C 、浏览器地址栏显示的为重定向的请求路径,而非用户提交请求的路径。也正因如此,重定向的一个很重要作用是: 防止表单重复提交(防止被恶意攻击服务器资源)
D 、重定向不仅可以跳转到当前应用的其它资源,也可以跳转到到其它应用中资源

具体的选择:

(1)若需要跳转到其它应用,则使用重定向。
(2)若是处理表单数据的 Servlet 要跳转到其它 Servlet ,则需要选择重定向。为了防止表单
重复提交。
(3)若对某一请求进行处理的 Servlet 的执行需要消耗大量的服务器资源( CPU 、内存),此时这个 Servlet 执行完毕后,也需要重定向。
(4)其它情况,一般使用请求转发。

RequestDispatcher

RequestDispatcher 是 javax.servlet 包下的一个接口,通过 HttpServletRequest 可以获取到该 接口对象,该对象就是用于完成请求转发功能的。

forward()与 include()

forward() 与 include() 均可完成请求的转发 。即可以将请求中所携带的参数由当前 Servlet 传递给下一下资源,如另一个 Servlet 。也就是说,这两个方法对于请求来说是相同的,都是请求转发。但它们的不同之处是响应,是 标准响应输出流的开启时间不同 。
void forward(ServletRequest request, ServletResponse response)
使用该方法,则当前的 Servlet 中只能进行业务处理,而不能向浏览器发送要显示的数据。因为请求还未完成,需要继续向前(forward ),当请求完成后,服务器才会开启标准响应输出流,向输出流中写入数据。
该方法的响应对象,使用的是第二个资源的响应对象。即第二个资源向浏览器回送
的响应数据。
不过,需要注意的是,在后面的测试代码的 SomeServlet 与 OtherServlet 中均添加对于 Response 对象的输出语句,会发现这两个 Servlet 中所使用的 Response 对象为同一个 ResponseFacade 对象。那为什么在 SomeServlet 中向 out 中 print() 数据后,并不会显示到客户端浏览器?原因就是 Response 对象在 SomeServlet 中创建了,但标准输出流并未开启。输出流的开启是在 OtherServlet 中进行的。
void include(ServletRequest request, ServletResponse response)
使用该方法,在将请求向后转发时,服务器会将标准响应输出流开启。当前 Servlet可以向输出流中写入数据,并且服务器还会将要转向的资源的输出流中数据合并到当前的输出流中。
该方法的响应对象,使用的是第一个资源的响应对象,即当前 Servlet 向浏览器回送的响应数据。
需要注意的是,在后面的测试代码的 SomeServlet 与 OtherServlet 中均添加对于Response 对象的输出语句,会发现 SomeServlet 输出的是 ResponseFacade 对象,而OtherServlet 输出的则是 ApplicationHttpResponse 对象。 ApplicationHttpResponse 是HttpServletResponse 接口的实现类 ResponseFacade 的 装 饰 者 类 , 其增强了ResponseFacade 类的功能。
ApplicationHttpResponse 底层完成的一个工作是,将当前的 OtherServlet 中的输出流中的数据合并到了 SomeServlet 的标准输出流中。

测试:

首先测试使用forward()方法,然后再测试include()方法,请主要关注response的不同结果!

package com.try_my;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class Login_Servlet extends HttpServlet {

	public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		request.setCharacterEncoding("UTF-8");
		doPost(request, response);
//		System.out.println("非法用户");
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
//		System.out.println("下一步进入用户阶段");
		request.setCharacterEncoding("UTF-8");
		String username = request.getParameter("username");
		String ageStr = request.getParameter("age");
		Integer age = Integer.valueOf(ageStr);
		String[] hobby = request.getParameterValues("hobby");

		System.out.println("username = " + username);
		System.out.println("age = " + age);
		System.out.println("request = " + request);
		System.out.println("response = " + response);
		for (String h : hobby) {
			System.out.println("hobby = " + h);
		}
		PrintWriter out = response.getWriter();
		out.println("username is " + username);
		out.print("age is " + age);

//		request.setAttribute("username", username);
//		request.setAttribute("age", age);
//		请求转发:
		request.getRequestDispatcher("Other").forward(request, response);
//		username = URLEncoder.encode(username, "UTF-8");// 编码:打散
//		response.sendRedirect("/My_Try_Copy/other?username=" + username + "&age=" + age);// 重定向

	}
}
package com.try_my;

import java.io.IOException;
import java.net.URLDecoder;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class Other_Servlet extends HttpServlet {
	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		doPost(request, response);
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
//		String username = (String) request.getAttribute("username");
//		Integer age = (Integer) request.getAttribute("age");
//
//		System.out.println("username is " + username);
//		System.out.println("age is " + age);

		String user_name = request.getParameter("username");
		user_name = URLDecoder.decode(user_name, "UTF-8");// 解码:组装
//		user_name = new String(user_name.getBytes("ISO8859-1"), "UTF-8");
		// 老师解释因为是网页传来的数据,所以还是需要再来一次上面的代码,但是我测试的结果是不需要上面的代码
		// 大概是因为老师测试用的是Tomcat7吧
		String aGe = request.getParameter("age");

		System.out.println("username ==== " + user_name);
		System.out.println("age ==== " + aGe);
//		request.getRequestDispatcher("/welcome.html").forward(request, response);
		System.out.println("request = " + request);
		System.out.println("response = " + response);
	}

}

我这个代码里面有些和测试无关的也没有注释掉是有原因的,其中一个就是为了隔开两次的输出来方便区别。

很清楚的是,这里的response是没有发生改变的!下面我们看一下换成include的代码: 

package com.try_my;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class Login_Servlet extends HttpServlet {

	public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		request.setCharacterEncoding("UTF-8");
		doPost(request, response);
//		System.out.println("非法用户");
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
//		System.out.println("下一步进入用户阶段");
		request.setCharacterEncoding("UTF-8");
		String username = request.getParameter("username");
		String ageStr = request.getParameter("age");
		Integer age = Integer.valueOf(ageStr);
		String[] hobby = request.getParameterValues("hobby");

		System.out.println("username = " + username);
		System.out.println("age = " + age);
		System.out.println("request = " + request);
		System.out.println("response = " + response);
		for (String h : hobby) {
			System.out.println("hobby = " + h);
		}
		response.setContentType("text/html;charset=UTF-8");
		PrintWriter out = response.getWriter();
		out.println("username is " + username);
		out.print("age is " + age);

//		request.setAttribute("username", username);
//		request.setAttribute("age", age);
//		请求转发:
		request.getRequestDispatcher("Other").include(request, response);
//		username = URLEncoder.encode(username, "UTF-8");// 编码:打散
//		response.sendRedirect("/My_Try_Copy/other?username=" + username + "&age=" + age);// 重定向

	}
}
package com.try_my;

import java.io.IOException;
import java.net.URLDecoder;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class Other_Servlet extends HttpServlet {
	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		doPost(request, response);
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
//		String username = (String) request.getAttribute("username");
//		Integer age = (Integer) request.getAttribute("age");
//
//		System.out.println("username is " + username);
//		System.out.println("age is " + age);

		String user_name = request.getParameter("username");
		user_name = URLDecoder.decode(user_name, "UTF-8");// 解码:组装
//		user_name = new String(user_name.getBytes("ISO8859-1"), "UTF-8");
		// 老师解释因为是网页传来的数据,所以还是需要再来一次上面的代码,但是我测试的结果是不需要上面的代码
		// 大概是因为老师测试用的是Tomcat7吧
		String aGe = request.getParameter("age");

		System.out.println("username ==== " + user_name);
		System.out.println("age ==== " + aGe);
		System.out.println("request = " + request);
		System.out.println("response = " + response);
	}

}

很明显的就是response在include的情况下给增强了!除此以外,还有一个有意思的情况,就是无论用forward还是用include,直接加载在最初的请求页面,比如此例的Login_Servlet,但是是否会输出则有区别。看下面的截图: 

forward()方法的结果

include()方法的结果

 

下面的两张图也是有助于理解的: 

注意哈,老师的这个图里面的SomeServlet就相当于我的例子里面的Login_Servlet而Other_Servlet就相当于OtherServlet。 

小细节:提前关闭输出流

简而言之,在前面的例子中,若在 Other_Servlet 中将输出流进行了关闭(如out.close()),则 SomeServlet 中 include() 方法后的输出数据将无法再写入到输出流中。也就是说,将无法显示到页面。

访问路径问题

我觉得关于路径是一个程序员的常识性问题,什么绝对路径、相对路径的,应该耳熟能详的。这里为了使文章内容更水字数全面,稍微说一下。

访问路径的组成

URL , 统一资源定位符 ,用于定位资源的一种方式。通常的 URL资源访问路径由两部分构成 : 资源路径 与 资源名称 。资源名称指的是要访问资源的直接名称,如 show.html ,或与要访问资源存在映射关系的间接名称,如 show.do 。而资源路径,则是通过该路径则可以定位到指定的资源,即资源路径是指在 URL 资源访问路径中除了资源名称以外的其它部分。
根据以上规则, URI , 统一资源标识符 ,也可以分为资源路径与资源名称两部分。

说个规则:

一般情况下,在 URL 或 URI 中,最后一个斜杠后的部分为资源名称,而其它部分则为资源路径。
如下图:

绝对路径 

Web 应用的绝对路径,是指带访问协议的路径,即 URL 。
如下面例的路径就是一个带有 http 访问协议的绝对路径:
http://127.0.0.1:8080/primary/index.jsp

相对路径

相对路径,是指仅根据访问路径无法准确定位资源的路径。 相对路径必须要结合其参照 路径才可组成可以准确定位资源的绝对路径。
绝对路径 = 参照路径 + 相对路径
如,你要告诉对方你现在的位置,你说:我在人民公园的正门门口,但是不说你在哪个城市,谁知道你在哪里呢?不过实际情况往往是对方不会搞错,因为这里存在一个默认的参照路径:与听者在同一个城市。所以 听者会将你所说的相对地址,自动转换为一个绝对地址。

在 Web 应用中,浏览器或服务器会自动为不同的相对路径添加不同的参照路径,将相对路径转换为绝对路径,以定位这个资源。

相对路径的写法有两种:一种是以斜杠开头的相对路径,一种是以路径名称开头的相对路径。 根据相对路径是否以斜杠开头,且路径出现的文件的不同,其默认的参照路径是不 同的。

以斜杠开头的相对路径

据路径所在文件所处位置的不同,分为两种:前台路径与后台路径。

前台路径:

由浏览器解析执行的代码中所包含的路径,
如, html 、 css 、 js中的路径,及 jsp 文件中静态部分的路径( HTML 标签中的路径)。
【顺便提前预告一下,所谓的jsp文件就是html+java组成的文件】
再具体的例子就是:
html 及 jsp 文件中的<img src=””/> 、 <a href=””></a> 、 <form action=””></form>等标签中的路径;css 文件中的 background:img(“”)等属性中的路径 ;js 文件中的 window.location.href=”” 等属性中的路径。
前台路径的参照路径是 Web 服务器的根路径 ,即 http://127.0.0.1:8080

后台路径:

由服务器解析执行的代码及文件中所包含的路径。
如, java 代码中的路径、jsp 文件动态部分( java 代码块)中的路径、 xml 等配置文件中路径(配置文件是要被 java 代码解析后加载到内存的,其中的路径会出现在 Java 代码中)等。
后台路径的参照路径是 Web 应用的根路径 。如 http://127.0.0.1:8080/My_Try

注意!

当代码中使用 response 的 sendRedirect() 方法进行重定向时,若其参照路径是以斜杠开头,则其参照路径不是web 应用的根路径,而是 web服务器的根路径。
如,执行 response.sendRedirect(“/show.jsp”);
将会报 404 错误。因为其参照路径是Tomcat的根,而非当前项目的根。所以若要使用重定向,就需要在路径上添加上项目名称:
response.sendRedirect(request.getContextPath + “/show.jsp”)
因为 sendRedirect() 方法可以重定向到其它应用,若不指定要跳转的应用,其将无法确定跳转方向。

以路径开头的相对路径

无论是出现在前台页面,还是出现在后台 Java 代码或配置文件中, 其参照路径都是当前访问路径的资源路径 。即使是 response 的 sendRedirect() 方法的参数路径,若不以斜杠开头,其也属于“以路径名称开头的相对路径”类,参照路径为当前访问路径的资源路径。(就是同级里面寻找的意思)

前台路径问题举例

这个看老师的视频里面的例子吧,我截图如下(自己也试了,但是老师那么多的解释性文字没写):

第一个是超链接的例子: 

结构

index页面代码

对应的浏览器的页面

 很显然,第一张图片是没有展示出来的,只有替代性文字。

第二个是前台表单提交的例子:

错误示范

正确示范

后台路径问题举例

第一个例子就是典型的配置文件的相关路径:

其实最为常见的就是我们最开始接触的web.xml文件,里面有一个叫做<url-pattern>的标签,里面的内容是如/some等以斜杠开头的内容。这个是有原因的,因为前面的参照路径是应用的根路径,所以加上这个就是完整且正确的绝对路径。

第二个例子是java代码里面的相关路径:

这个其实前面也是有代码提及的,比如请求转发是在java里面实现的,那么起作用的代码语句:

request.getRequestDispatcher("/Other").forward(request, response);

就是后台的路径,由服务器做的解析。

第三个例子则是一个特例的分析:

老师在视频里面也说明了一个事实,重定向不只有response.sendRedirect这种方法,只是目前没有教,也只有这种方法是特例,其他都遵守规则。 

以路径名称开头的相对路径

首先了解一下前台页面的例子:

这里不截图了,只听描述就可以听懂的:无论是之前那些例子里面的表单的action还是图片的链接,不再需要再前面加上应用名使其成为web应用的根目录,而是把前面的/给删了就可以了。因为会自动使用当前的资源路径(其实就可以理解为web的根目录了,因为我们的images文件夹或者Servlet都是放在同级目录下的)

好吧,怕说的不清楚还是截个图吧:

再了解一下后端的例子:

实际上,如果你的request.getRequestDispatcher("/Other").forward(request, response);这句代码去掉斜杠,也可以进行跳转,但是不建议去掉,这是因为不带斜杠是用当前的资源路径,一般都是可以说就是web应用的根路径,但是也可能出现跳转的过程发生变化,所以用斜杠来锁死为web的根路径是最好的。

线程安全问题

总结起来两个条件:

· 存在多线程并发访问
·  存在可修改的共享数据
就是说一个程序会被多个浏览器端同时访问,然后一些程序里面的共享的内容会被修改,然后就造成了彼此混乱的情况。

概念了解

生活中上网最为常见的一种情况是:

当在登录某网页 时选上如“十天内免登录”,之后再登录就不需要再输入登录信息了,就自动登陆好了!
再登录时的确是不需要输入用户名与密码了,那么是网站不需要对用户进行验证了?当然不是。那么,网站用于用户验证的用户名与密码是从哪里获取到的?

实际上吧,细心的同学也会发现一个情况:换台机器就不再是前面所述情况了,换台电脑打开网站,仍然需要输入用户名与密码。那就说明一个问题:那些信息是保存在客户端电脑里的,并没有保存在服务器中!

将原来的电脑重启后,再次访问这些网站,网站仍然不需要输入用户名与密码。那就说明一个问题:那些信息是保存在客户端电脑的硬盘中的,而不是内存中。(这是一个很基础的概念,不懂的同学可以看看我关于计算机组成原理的CSDN笔记)

下面是一个定义,有兴趣的可以看一下,论实际的使用,上面的那些解释就够了:

Cookie 是 1993 年由网景公司( Netscape )前雇员发明的一种进行网络会话状态跟踪的技术。
会话是由一组请求与响应组成,是围绕着一件相关事情所进行的请求与响应。所以这些请求与响应之间一定是需要有数据传递的,即是需要进行会话状态跟踪的。然而 HTTP 协议是一种无状态协议,在不同的请求间是无法进行数据传递的。此时就需要一种可以进行请求间数据传递的会话跟踪技术,而 Cookie 就是一种这样的技术。
Cookie 是由服务器生成,保存在客户端的一种信息载体。这个载体中存放着用户访问该站点的会话状态信息。只要 Cookie 没有被清空,或  Cookie 没有失效,那么,保存在其中的会话状态就有效。
用户在提交第一次请求后,由服务器生成 Cookie ,并将其封装到响应头中,以响应的形式发送给客户端。客户端接收到这个响应后,将 Cookie 保存到客户端。当客户端再次发送同请求后,在请求中会携带保存在客户端的 Cookie 数据,发送到服务端,由服务器对会话进行跟踪。
Cookie 技术并不是 JavaWeb 开发专属技术,而是属于 Web 开发的技术,是所有 Web 开发语言均支持的技术。
Cookie 是由若干键值对构成,这里的键一般称为 name ,值称为 value 。 Cookie 中的键值对均为字符串。

浏览器下查看Cookie

不同的浏览器查看方式不一样,老师视频里面是用的火狐浏览器,我没有下载过这个浏览器,所以不做截图演示,这个不同浏览器的查看是很基础的操作,可以到网上去搜教程,我这里不废话了。

服务端生成Cookie

package com.try_my;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class CookieServlet extends HttpServlet {
	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		// 创建了两个Cookie
		Cookie cookie_school = new Cookie("School", "CPU");
		Cookie cookie_student = new Cookie("Student", "F");

		// 设置Cookie绑定的路径,这里指定的路径要求必须加上项目名称
		// 访问该路径是确保浏览器可以能够提交Cookie的,老师的演示视频里面就也表明了这个是不会改变页面的路径的
		cookie_school.setPath(request.getContextPath() + "/a/bb/ccc");
		cookie_student.setPath(request.getContextPath() + "/ccc/bb/a");

		// 设置Cookie可以在硬盘里面留存的时间,最基础的时间单位为秒
		cookie_school.setMaxAge(60 * 60);// 有效期为1个小时
		cookie_student.setMaxAge(60 * 60 * 24 * 10);// 有效期为10天

		// 向响应中添加Cookie
		response.addCookie(cookie_school);
		response.addCookie(cookie_student);
	}

}

我们先看一下运行的效果:

因为我没有在这个里面设置任何提示内容什么的,所以自然是空白页面,不过很明显的是,没有404等错误,说明运行正常。我使用的是Microsoft Edge浏览器,所以看一下这个里面关于Cookie的信息: 

完全符合我们代码的内容! 

另外,我们再看下面这个图片,是对于setPath()的理解:

服务端获取并解析Cookie 

举个例子:

首先改造Other_Servlet,然后重新打开浏览器(记得之前重启服务器),这是为了清除在内存里面的Cookie内容。

package com.try_my;

import java.io.IOException;
import java.net.URLDecoder;

import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class Other_Servlet extends HttpServlet {
	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		doPost(request, response);
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
//		String username = (String) request.getAttribute("username");
//		Integer age = (Integer) request.getAttribute("age");
//
//		System.out.println("username is " + username);
//		System.out.println("age is " + age);

		String user_name = request.getParameter("username");
		user_name = URLDecoder.decode(user_name, "UTF-8");// 解码:组装
//		user_name = new String(user_name.getBytes("ISO8859-1"), "UTF-8");
		// 老师解释因为是网页传来的数据,所以还是需要再来一次上面的代码,但是我测试的结果是不需要上面的代码
		// 大概是因为老师测试用的是Tomcat7吧
		String aGe = request.getParameter("age");

		System.out.println("username ==== " + user_name);
		System.out.println("age ==== " + aGe);
		System.out.println("request = " + request);
		System.out.println("response = " + response);

		Cookie[] cookies = request.getCookies();
		for (Cookie cookie : cookies) {
			System.out.println(cookie.getName() + " =is= " + cookie.getValue());
		}
	}

}

然后打开写Cookie的那路径下的页面(我的是http://localhost:8080/My_Try/CookieServlet ,另外就是原来的CookieServlet记得把setPath的内容给注释掉,否则会报500错误,这是因为Other_Servlet的资源路径和我之前测试里面那个随便乱写的是不一样的),这样做的原因是为了能够创建Cookie。

随后,我会打开http://localhost:8080/My_Try,因为我上面的代码是写在Other_Servlet里面的,这个的进入因为之前的几个测试,必须经过最开始的表单的POST才可以进入Other_Servlet(主要是我不愿意删掉最开始做测试的那些代码的,留着就当隔断了)

 当然了,既然可以对每一个Cookie内容进行访问了,那么就可以解析并操作了。具体怎么操作,那还是要看各位的实际需求的。

Cookie的禁用

网上有Cookie可以被黑客攻击电脑后丢失用户名与密码的说法,这个可能吗?确实可能!但是,我相信来看这篇文章的也不是什么大人物,黑客不会闲着去攻击一个随机的普通人的。并且很多网站是如果你禁掉了Cookie的话,那么就无法使用它的服务的!至于为什么,很简单:你是安全了,做网站的程序员要吃苦了呗,谁愿意干这个事呢。

我事不写怎么禁Cookie了,自己摸索一下就会了,实在不行就网上找一下帖子或者博客什么的来自己学一下,很容易的。

Session

基本用法

Session ,即会话,是 Web 开发中的一种会话状态跟踪技术。当然,前面所讲述的 Cookie
也是一种会话跟踪技术。不同的是 Cookie 是将会话状态保存在了客户端,而 Session 则是将会话状态保存在了服务器端。
注:当用户打开浏览器,从发出第一次请求开始,一直到最终关闭浏览器,就表示一次会话的完成。

老师举的例子是由三部分代码组成(登录的index.html页面、设置session的SomeServlet页面、接收并验证session相关内容的OtherServlet),我在接下来会展示截图:

Session的工作原理 

在服务器中系统会为每个会话维护一个 Session 。不同的会话,对应不同的 Session!

写入Session列表

服务器对当前应用中的 Session 是以 Map 的形式进行管理的,这个 Map 称为 Session 列表。该 Map 的 key 为一个 32 位长度的随机串,这个随机串称为 JSessionID , value 则为 Session对象的引用。
当用户第一次提交请求时,服务端 Servlet 中执行到 request.getSession() 方法后,会自动生成一个 Map.Entry 对象, key 为一个根据某种算法新生成的 JSessionID , value 则为新创建的HttpSession 对象。

 服务器生成并发送Cookie

在将 Session 信息写入 Session 列表后,系统还会自动将“ JSESSIONID ”作为 name ,这个32 位长度的随机串作为 value ,以 Cookie 的形式存放到响应报头中,并随着响应,将该Cookie发送到客户端。

客户端接收和发送Cookie 

客户端接收到这个 Cookie 后会将其存放到浏览器的缓存中。即,只要客户端浏览器不关闭,浏览器缓存中的Cookie 就不会消失。
当用户提交第二次请求时,会将缓存中的这个 Cookie,伴随着请求的头部信息,一块发送到服务端。

从Session列表中查找 

服务端从请求中读取到客户端发送来的 Cookie ,并根据 Cookie 的 JSSESSIONID 的值,从
Map 中查找相应 key 所对应的 value ,即 Session 对象。然后,对该 Session 对象的域属性进行读写操作。

Session失效

Web 开发中引入的 Session 超时的概念, Session 的失效就是指 Session 的超时。若某个
Session 在指定的时间范围内一直未被访问,那么 Session 将超时,即将失效。
在 web.xml 中可以通过 <session-config/> 标签设置 Session 的超时时间,单位为分钟。默
认 Session 的超时时间为 30 分钟。需要再次强调的是,这个时间并 不是 从 Session 被创建开始计时的生命周期时长,而 是 从最后一次被访问开始计时,在指定的时长内一直未被访问的 时长。

若未到超时时限,也可通过代码提前使 Session 失效。 HttpSession 中的方法 Invalide() ,使得 Session 失效。

 public void invalidate()

举例:

直接在项目 httpSession 中进行修改,定义并注册 ThirdSession。

而对于项目的测试访问,可以遵循如下步骤:
先访问 SomeServlet ,将域属性数据放入 Session 。
再访问 OtherServlet ,将域属性数据读取出来。证明 Session 中是有数据的。
然后访问 ThirdServlet ,使 Session 失效。
最后再访问 OtherServlet ,证明无法再从 Session 中读取数据。

其实所谓的失效吧,按照官方文档的说明就是解除所有与之相关的内容绑定。这倒不是说Session已经删掉了,只是它什么都没有了。

Cookie禁用后使用Session对会话的追踪

从前面 Session 的工作原理可知,服务器只所以可以针对不同的会话找到不同的 Session,是因为 Cookie 完成了会话的跟踪。但是,若客户端浏览器将 Cookie 禁用,那么服务器还怎样保证同一会话使用的是同一个 Session 呢?
若客户端浏览器禁用了 Cookie ,会发现向服务器所提交的每一次请求,服务器在给出的响应中都会包含名称为 JSESSIONID 的 Cookie ,只不过这个 Cookie 的值每一次都不同。也就是说,只要客户端浏览器所提交的请求中没有包含JSESSIONID ,服务器就会认为这是一次新
的会话的开始,就会为其生成一个 Map.Entry 对象, key 为新的 32 位长度的随机串, value
为新创建的 Session 会话引用。这样的话,也就无法实现会话跟踪了。
如果做测试,查看控制台的输出,会发现每次使用的 Session 都是不同的 Session!

手工重写URL 

在浏览器的 Cookie 被禁用的情况下,若要实现会话跟踪,需要使用 URL 重写机制。所
谓 URL 重写机制是指,将 JSessionID 添加到请求的最后。
这里使用手工重写 URL 的方式来实现。将 JSessionID 添加到地址栏的请求最后。 key 为全小写的 jsessionid ,而 value 则为服务端响应头部发送来的 JSESSIONID 。不过,需要的是原请求与 jsessionid 间使用分号分隔,而非问号。
上面的程序在运行时,从响应头部将 JSessionID 复制,然后按照如下形式添加到请求中:
http://127.0.0.1:8080/httpSession-closeCookie/test/some;jsessionid=42F374 ..... 16D6D查看控制台,发现使用的 Session 对象是同一个 Session 。
运行结论:
从前面的运行情况可知,客户端浏览器的关闭,并不会使得与之绑定的 Session 对象失效。对一次请求的响应结束后,客户端浏览器与服务器间的连接就关闭了。所以浏览器的关闭,对于服务器来说,是不知情的。学习到这里,我们再来看什么是一次“会话”。从表面上看,一次会话就是指从浏览器打开并发送第一次对该服务器的请求开始,到浏览器关闭,这就是一次会话。但从本质上说,一次会话是指,从 Session 对象的创建开始,一直到最终的 Session 失效,这才是一次会话。即浏览器关闭后,对系统来说会话并未结束。只不过对于用户来说,好像会话结束了。(所以这里就反映了一个问题:假如你在什么地方上网,之后关闭网页了,但是有人偷偷记住了你的jsessionid,那么它就可以登回你关闭的页面,然后做操作,对你的利益造成损害。所以有些安全性高的网站,比如网上银行设置成关闭后就Session失效)

重定向重写URL

HttpServletResponse 具有一个方法 encodeRedirectURL() ,可以完成对重定向 URL 的重写, 即在重定向的路径后会自动添加 jsessionid。

下面是测试代码(是有很多注释的代码,你测试的时候可以删掉,我是在一个页面的基础上改的,舍不得删) 

package com.try_my;

import java.io.IOException;
import java.io.PrintWriter;
import java.net.URLEncoder;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

public class Login_Servlet extends HttpServlet {

	public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		request.setCharacterEncoding("UTF-8");
		doPost(request, response);
//		System.out.println("非法用户");
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
//		System.out.println("下一步进入用户阶段");
		request.setCharacterEncoding("UTF-8");
		String username = request.getParameter("username");
		String ageStr = request.getParameter("age");
		Integer age = Integer.valueOf(ageStr);
		String[] hobby = request.getParameterValues("hobby");

		System.out.println("username = " + username);
		System.out.println("age = " + age);
		System.out.println("request = " + request);
		System.out.println("response = " + response);
		for (String h : hobby) {
			System.out.println("hobby = " + h);
		}
		response.setContentType("text/html;charset=UTF-8");
		PrintWriter out = response.getWriter();
		// 只要下面的请求转发和重定向不去实现,在本页面内的都可以打印,否则就转到其他页面的内容了
		out.println("username is " + username);
		out.print("age is " + age);

//		request.setAttribute("username", username);
//		request.setAttribute("age", age);
//		请求转发:
//		request.getRequestDispatcher("/Other").forward(request, response);
		username = URLEncoder.encode(username, "UTF-8");// 编码:打散
//		response.sendRedirect("/My_Try_Copy/other?username=" + username + "&age=" + age);// 重定向

		HttpSession session = request.getSession();
		session.setAttribute("can can", "need");
		String uri = request.getContextPath() + "/Other";
		uri = response.encodeRedirectURL(uri);
		response.sendRedirect(uri);

	}
}
package com.try_my;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

public class Other_Servlet extends HttpServlet {
	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		doPost(request, response);
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
//		String username = (String) request.getAttribute("username");
//		Integer age = (Integer) request.getAttribute("age");
//
//		System.out.println("username is " + username);
//		System.out.println("age is " + age);

//		String user_name = request.getParameter("username");
//		user_name = URLDecoder.decode(user_name, "UTF-8");// 解码:组装
//		user_name = new String(user_name.getBytes("ISO8859-1"), "UTF-8");
		// 老师解释因为是网页传来的数据,所以还是需要再来一次上面的代码,但是我测试的结果是不需要上面的代码
		// 大概是因为老师测试用的是Tomcat7吧
//		String aGe = request.getParameter("age");

//		System.out.println("username ==== " + user_name);
//		System.out.println("age ==== " + aGe);
		System.out.println("request = " + request);
		System.out.println("response = " + response);

		Cookie[] cookies = request.getCookies();
		for (Cookie cookie : cookies) {
			System.out.println(cookie.getName() + " =is= " + cookie.getValue());
		}

		HttpSession session = request.getSession(false);
		if (session != null) {
			String str = (String) session.getAttribute("can can");
			System.out.println("读取到的session内容是:" + str);
		}
	}

}

下面看一下运行结果:

 可以看到:jsessionid已经自动加上了,这样的方法无论用户是否禁掉Cookie都可以跟踪会话,不过安全性非常低,因为jsessionid都已经展示出来了,这也是为什么很多网站不允许你禁掉Cookie了。

上图是我没有禁掉Cookie的情况下的控制台的内容,里面有很多值得我们思考的内容:

·jsessionid的内容和url栏里面的内容是一样的!说明是同一个会话!

·而且能看到,还有一个Cookie内容在,但是我中途没有去连接CookieServlet所在的链接页面,那么为什么那个读Cookie的代码会有执行?还记得我们之前测试了将Cookie存入硬盘的最大时间,school=CPU的那个时间是设置为60*60也就是1个小时,现在已经过期了(这是我过了2天后才写到这个位置的测试),但是那个student=F我设置的时间是60*60*24*10,也就是说过期时间是10天,现在没过期,所以是还存在的!

·session的内容是need,完全符合我们的设置,所以传递没有问题!

非重定向重写URL

只要不是重定向的,都可以用:

HttpServletResponse 的 方法 encodeURL() ,可以完成对类似对超链接这样的非重定向页面跳转的 URL 的重写,即在其路径后会自动添加 jsessionid 。
下面是老师视频里面的截图,非常简洁易懂:

 总结

基本上Servlet的部分就结束了,那么相信对这部分内容也有一定的了解了。其实还有更多的内容值得大家探索,毕竟这个老师的内容里面实际案例是比较少的,真正的项目要自己实践才会有经验积累。学海无涯,努力吧!

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐