一 引言
1.1 c/s架构与b/s架构
c/s架构与b/s架构是软件发展过程当中的两种软件架构方式
c/s架构:(client/server 客户端/服务器)
特点:必须在客户端安装特定软件
优点:图形显示效果比较好(比如3d游戏)
缺点:服务器的软件与功能进行升级,客户端也必须升级,不利于维护
常见的c/s程序:qq,微信,游戏等
b/s架构:(browse/server 浏览器/服务器)
特点:无需安装客户端,浏览器可以直接访问
优点:涉及到功能的升级,只需要升级服务器即可
缺点:图形显示效果不如c/s架构
需要通过HTTP协议访问
二.实现servlet的四种方法
2.1 创建一个对象继承httpservlet类的service方法
2.2 创建一个httpservlet类的父类genericservlet抽象类,以此继承service方法
2.3 创建一个servlet接口来调用service方法
2.4 直接利用doget与dopost方法来接收数据,而使service方法处于缺省状态
三.Servlet的生命周期
Servlet没有main方法,无法独立运行,他的运行完全由Servlet引擎来控制和调度,所谓生命周期就是servlet容器何时创建servlet实例,何时调用其方法进行请求的处理,何时并销毁其实例的整个过程。
1.实例和初始化时期2
当请求到达容器时,容器首先查找该servlet方法是否存在,如果不存在,则会创建实例进行初始化。
2.就绪/调用/服务阶段
有请求到达容器时,容器调用servlet对象的service()方法,处理请求方法在整个生命周期中可以被多次调用,Httpservlet的service()方法,会依据请求方式来调用doGet()方法doPost()方法,但是,这两个do方法默认情况下,会抛出异常,需要子类去override.
3.销毁时机
当容器关闭时(应用程序停止时),会将容器中的Servlet实例进行销毁
上述的生命周期可以通过Servlet中的生命周期方法来观察,在 Servlet中有三个生命周期方法,不用用户手动调用,而是在特定的时机时由容器自动调用,观察这三个生命周期方法即可观察到Servlet的生命周期
三个servlet生命周期方法
1.init方法,在Servlet实例创建之后执行(证明该Servlet有实例创建了)
public void init(Servletconfig config) throws ServletException{
System.out.println("实例创建了...");
}
2.service方法,每次有请求到达某个Servlet方法时执行,用来处理请求(证明该Servlet进行服务了)
protected void service(HttpServletRequest req,HttpServletResponse resp)
throws ServletExecption,IOExecption{
System.out.println("服务器调用了...");
}
3.destory方法,Servlet实施销毁时执行(证明该Servlet的实例被销毁了)
public void destory(){
System.out.println("实例销毁了...");
}
Servlet的生命周期,简单描述分为四步,servlet类加载->实例化->服务->销毁
1.Web Client向Servlet容器(Tomcat)发出Http请求
2.Servlet容器接收Web Client的请求
3.Servlet容器创建一个HttpServletRequest对象,将Web Client请求的信息封装到这个对象中
4.Servlet容器创建一个HttpServletResponse对象
5.Servlet容器调用HttpServlet对象的service方法,把Request与Response作为参数,传给HttpServlet
6.HttpServlet调用HttpServletRequest对象的有关方法,获取Http请求
7.HttpServlet调用HttpServletResponse对象的有关方法,生成响应数据
8.Servlet容器把HttpServlet的响应结果传给Web Client
四.HttpServletRequest对象
HttpServletRequest对象:主要作用是用来接收客户端发送的请求信息,例如:请求的参数,发送的头消息等都属于客户端发送的消息,service()方法中形参接收的是HttpServiceReques接口的实例化对象,表示该对象主要应用在HTTP协议上,该对象是由Tomcat封装好传递过来的。
HttpServletRequest是ServletRequest的子接口,ServletRequest只有一个子接口,就是HttpServletRequest,既然只有一个子接口那么为什么不合并在一起呢?
从长远来讲,现在主要使用的是HTTP协议,但是以后可能会出现更多的协议,如果以后想要支持这种新的协议,只需要继承ServletRequest接口就行了。
在HttpServletRequest接口中,定义的方法有很多,但是都是围绕客户端参数的。但是怎么拿到对象呢?不需要,直接在Servlet方法中由容器传入进来,而我们需要做的就是取出对象中的数据,进行分析处理。
4.1 接收方法
方法实现:
package com.xxx.servlet;
import sun.plugin2.message.GetAuthenticationMessage;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/s02")
public class servlet01 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
/*常用方法*/
//获取请求时的完整路径(从http开始到?前面结束)
String url = req.getRequestURL() + "";
System.out.println("获取请求的完整路径"+url);
//获取请求时的部分路径(从项目的站点名开始到?结束)
String uri = req.getRequestURI();
System.out.println("获取请求的部分路径"+uri);
// 获取请求时的参数字符串(从?开始到最后的字符串)
String queryString = req.getQueryString();
System.out.println("获取请求的参数字符串"+queryString);
// 获取请求方式(GET,POST)
String method = req.getMethod();
System.out.println("获取请求方式"+method);
// 获取协议版本(HTTP/1.1)
String prototol = req.getProtocol();
System.out.println("获取当前的协议版本"+prototol);
// 获取项目的站点名(项目对外访问路径)
String webapp = req.getContextPath();//上下文路径
System.out.println("获取项目的站点名"+webapp);
}
}
获取请求参数的两种方法:一种是获取请求参数,一种是获取对应参数的所有值,遍历进行输出
/*获取请求的参数*/
//获取指定名称的参数值(返回字符串)
String uname = req.getParameter("uname");
String upwd = req.getParameter("upwd");
System.out.println("uname:"+uname+".upwd"+upwd);
//获取指定名称参数的所有值,返回一个字符串数组 (用于复选框传值)
String[] hobbys = req.getParameterValues("hobby");
//判断数组是否为空
if(hobbys !=null && hobbys.length > 0){
for (String hobby : hobbys){
System.out.println("爱好:" + hobby);
}
}
}
}
最后输出想要得到的对应参数
4.2 请求乱码问题
由于现在的request属于接收客户端的参数,所以必然有其默认的语言编码,主要是由于在解析过程中默认使用的编码方式为ISO-8859-1(此编码不支持中文)所以解析时一定会出现乱码,要想解决这种乱码问题,需要设置request中的编码方式,告诉服务器以何种方式进行解析数据,或者在接收到乱码数据之后,再通过相应的编码格式还原。
4.3 请求转发
请求转发是一种服务器行为,当客户端请求到达后,服务器进行转发,此时会将请求对象进行保存,地址栏中的URL地址不会改变,得到响应后,服务器端将再次响应发送给客户端,从始至终都只有一个请求发出,实现方式如下,达到多个资源协同响应的效果。
request.getRequestDispatcher(url).forward(request,request);
通俗易懂来说就是请求转发是可以做跳转的,跳转之后还可以将原来的对象赋给你
查看代码可知图片中写了两个请求跳转的命令,一个跳转到servlet04,一个跳转到login.jsp,在浏览器中访问servlet03文件,浏览器会首先访问servlet03文件,之后运行到请求转发命令时,再次跳转页面到servlet04或login.jsp页面,**但是浏览器显示的url并没有改变,并且后台会将servlet03的参数同样传给跳转之后的页面,**显示效果如下
1.跳转到servlet04界面
访问的url为servlet03
但是后台返回了03与04页面的响应,所以存在了请求转发
2.跳转到login.jsp界面
servlet03响应结果
url显示是servlet03文件路径但是页面跳转到login.jsp
所以即实现了请求跳转
4.4 request 域对象
通过该对象可以在一个请求中传递数据,作用范围;在一次请求中有效,即服务器跳转有效。
即在Java中我们不能传输对象及数据,但是利用域对象可以实现参数,对象及其数据的传输,但是只在请求转发中实现。
在servlet05中,定义对象及其数据
访问servlet05显示Servlet05
表示访问成功:
之后创建一个servlet06实现servlet05跳转及其对象带入:
最后实现输出.
注:当访问一串文件使用请求转发时,例如访问servlet04,05,06,07,08只要一直使用请求转发功能,里面所有的数据都会共享,即作用域,但是一旦直接访问路径,则作用域就会失效。
五.HttpServletResponse对象
Web服务器收到客户端的http请求时,会针对每一次请求,分别创建一个用于代表请求的resquest对象和代表响应的response对象。
resquest和response对象分别代表请求和响应,获取客户端数据,通过resquest对象,向客户端输出数据,需要通过response对象。
HttpServletResponse的主要功能用于服务器对于客户端的请求进行响应,将web服务器处理后的结果返回给客户端,service()方法中形参接收的是HttpServletResponse接口的实例化对象,这个对象中封禁了向客户端发送数据,发送响应头,发送响应状态码的方法。
5.1 响应数据
接收到客户端请求后,可以通过HttpServletResponse对象直接进行响应,响应时需要获取输出流。
有两种形式:
getWrite()获取字符流(只能响应字符)
getOutStream()获取字节流(能响应一切数据)
注意:两者不能同时使用
注:两种输出流方法同时使用,会优先由前一个方法调用reponse对象,因为前面的方法已经调用了response对象,所以会报错,后面的方法无调用对象。
5.2响应乱码问题
在响应中,如果我们响应的内容中含有中文,则有可能出现乱码,这时因为服务器响应的数据也会经过网络传输,服务器端有一种编码方式,但是客户端也存在一种编码方式,当两端使用的编码方式不同时则会出现乱码。
getWrite()的字符乱码
对于getWrite()获取到的字符流,响应中文必定出乱码,由于服务器端在进行编码是默认会使用ISO-8859-1格式的编码,该编码格式不支持中文。
要解决该种乱码问题只能在服务器端告知服务器使用一种能够支持中文的编码格式,比如我们常用的"UTF-8"。
response.setCharacterEncoding("UTF-8")
此时还只完成了一半的工作,要保证数据正确显示,还需要指定客户端的解码方式。
response.setHeader("content-type" , "test/html ; charset = UTF-8");
两端指定编码后,乱码就解决了,一句话:保证发送端和接收端的编码一致
实验:
一.字符流响应数据:
显示乱码
二.字节流响应数据
显示乱码
解决方法:
1.设置服务端编码格式:
再次查看界面显示
显示Servlet02显示界面与03一致但还是乱码
原因:由于客户端与服务端解码方式不一致会导致,乱码,所以只设置了服务端但是没有设置客户端编码格式,所以依然会导致乱码。
再次设施客户端编码格式:
首先在客户端将我们输入的字体转换为html类型,使客户端可以查看,之后再该变其编码格式,使客户端与服务端一致,再次运行显示成功。
同时设置客户端与服务端的编码:
getOutStream()字节乱码
对于getOutStream()方式获取到的字节流,响应中文时,由于本身就是传输的字节,所以此时可能出现乱码,也可能正确显示。当服务器端给的字节恰好和客户端使用的编码方式一致时则文本正确显示,否则出现乱码。无论如何我们都应该准确掌握服务器和客户端使用的是哪种编码格式,以确保数据正确显示。
制定客户端和服务端使用的编码方式一致。
解决方法同字符流乱码解决方式一致
代码中同时设置服务端与客户端编码格式
成功显示文本
总结
响应乱码只在显示中文的时候会存在,为了避免这种情况,我们同时在写代码的时候就设置客户端与服务端的编码格式。
5.3 重定向
重定向是一种由服务端引导,客户端的行为,客户端发送一个请求,被服务端接收处理之后,服务端会进行响应,在响应的同时,服务端会给客户端一个新的地址(下次请求的地址 response.sendRedirect(url);)当客户端接收到响应后,会立刻马上,自动根据服务器给的新地址发送第二个请求,服务器接收到请求并作出响应,重定向完成。
从描述中可以看出重定向当中有两个请求存在,并且属于客户端行为。
//重定向跳转到index.jsp
response.sendRedirect("index.jsp")
实现重定向
通过观察浏览器我们发现第一次请求获取的响应码为302,并且含有一个location头信息,并且地址栏最终看到的地址是和第一次不同的,地址栏已经发生了变化。
如图所示,首先访问Servlet04之后服务端会对客户端发送一个重定向的url,客户端收到之后会立刻再次向服务端发送请求,之后定向到Servlet05,而此时就存在两个请求,上图所示,即302重定向到了05
重定向时传参
如果给04传一个参数,查看05是否能够收到参数
首先在04,05分别创建一个对象接收参数:
之后向04传递一个参数,查看重定向之后05是否也会接收到这个参数
如图显示,05并没有接收到这个参数的数据,但是如果我们单独给05传参时就可以接收到参数的数据,这是因为我们实现了两次请求,创建了两个resquest对象,所以是无法实现请求转发的,而跳转过后的页面也就无法收到前一个请求的数据了。
5.4请求转发与重定向的区别
两者都可以进行跳转,根据需求选取即可。
重定向与请求转发的区别
1.请求转发的地址栏不会改变,重定向会发生改变
2.请求转发只有一次请求,但是重定向有两次请求
3.请求转发时request对象可以共享,重定向时request对象不共享
4.请求转发是服务器行为,重定向是客户端行为
5.请求转发的地址只能是当前站点下(当前项目)的资源,重定向的地址可以是任何地址
六.Cookie对象
Cookie是浏览器提供的一种技术,通过服务器的程序能将一些只须保存在客户端,或者在客户端进行处理的数据,放在本地的计算机上,不需要网络连接,因而提高网页处理的效率,并且能够减少服务器的负载,但是由于Cookie是服务器端保存在客户端的信息,所以其安全性也是很差的。例如常见的记住密码就可以通过Cookie来实现。
有一个专门操作Cookie的类javax.servlet.http.Cookie,随着服务器的响应发送给客户端,保存在浏览器,当下次访问时再将Cookie带回服务器。
Cookie格式:键值对用"="链接,多个键值对利用":"隔开
6.1Cookie对象的创建和发送
通过new Cookie("key","value");来创建一个Cookie对象,要想Cookie随响应发送到客户端。需要先添加到response对象中,response.addCookie(cookie);此时该cookie对象随着响应发送到了客户端上,在浏览器上也可以看见
//创建Cookie对象
Cookie cookie = new Cookie("uname","zhangsan")
//发送Cookie对象
response.addCookie(cookie);
F12查看
Cookie对象的发送:
f12进行查看
6.2 Cookie的获取
在服务器端只提供了一个getCookie()的方法用来获取客户端回传的所有Cookie组成的一个数组,如果需要获取单个cookie则需要通过遍历,getName()获取Cookie的名称,getValue()获取Cookie的值。
通俗易懂就是,首先查看cookie文件是否为空,不为空之后创建一个数组来遍历cookie的name与value。的到cookie文件内容。
首先查看到之前的cookie还在,说明cookie是浏览器存放的一个文件,不会随着浏览器哦或者网页的关闭或重启而改变或消失,是本地的文件,其次查看到这次访问的cookie数据
服务端也通过遍历查看到cookie文件的内容。
即获取cookie文件内容。
6.3 设置Cookie到期时间
除了Cookie的名称和内容外,我们还需要关心一个信息,到期时间,到期时间用来指定该cookie何时失效,默认当前浏览器关闭即失效,我们也可以手动设定cookie到期时间(通过到期时间计算),通过setMaxAge(int time)方法设定cookie最大有效时间,以秒为单位。
到期时间的取值
负整数
若为负数,表示不存储该cookie.
cookie的maxAge属性的默认值为-1,表示只在浏览器内存中存活,一 旦关闭浏览器窗口,那么cookie就会消失。
正整数
若大于0的整数,表示存储的秒数
表示cookie对象可存活指定的秒数,当生命大于0时,浏览器会把Cookie保存到硬盘上,就算关闭浏览器,就算重启客户端电脑,cookie也会存活响应的时间。
零
若为零,则删除此cookie
cookie生命等于0是一个特殊的值,它表示cookie被作废!也就是说,如果原来浏览器已经保存了这个Cookie,那么可以通过Cookie的setMaxAge(0)来删除这个Cookie。无论是在浏览器内存中,还是在客户端硬盘上都会删除这个Cookie.
注:到期时间是指当前时间加有效时间,当到期时间为-1时,即关闭时,到期时间为当前时间-1,所以当到期时间为-1时,浏览器关闭时就会删除此cookie。
设置cookie到期时间:
显示如下:zahngsan与lisi显示成功,但是wangwu已经被删除,关闭浏览器之后显示张三也被删除
6.4 Cookie注意点
1.Cookie保存在当前浏览器中
在一般的站点中常常有记住用户名这样的操作,该操作只是将信息保存在本机上,换电脑之后这些信息就无效了,而且cookie还不能跨浏览器。
2.Cookie存中文问题
Cookie中不能中文,如果有中文则通过URLEncoder.encode()来进行编码,获取时则通过URLDecoder.decoder()来进行编码。
不能存放中文
浏览器显示报错
**解决方法**:使用uncoder,encoder加解密
上面进行加密,后面进行解密
将中文加密之后存入浏览器,使其可以存储,之后返回服务器时解密得到cookie解密之后的中文。
访问之后得到加密后的cookie
再次访问,查看服务器端
得到中文的cookie
3.同名Cookie问题
如果服务器端发送重复的Cookie那么会覆盖掉原来的Cookie。
之后访问域名发现,value已被覆盖成zhangsan
4.浏览器存放Cookie的数量
不同的浏览器对Cookie也是有限定的,Cookie的存储是有上限的,Cookie是存储在客户端(浏览器)的,而且一般是由服务器端创建和设定的,后期结合Session来实现会话跟踪。
6.5 Cookie的路径
Cookie的setPath设置Cookie的路径,这个路径直接决定服务器的请求是否会从浏览器中加载某些coookie。
实现
当访问cook05时只能得到cookie1和2,因为1的cookie任意文件访问都可以得到,而2是因为只要是ser04项目中的文件都可以访问到,而4指定了sc04/cook02目录,所以只有访问此目录时才能得到cookie,而只有访问sc03项目时可以得到3
七.HttpSession对象(会话对象)
HttpSession对象是javax.servlet.http.HttpSession的实例,该接口不像HttpServletRequest或HttpServletResponse还存在一个父接口,该接口只是一个纯粹的接口。这因为session本身就属于HTTP协议的范畴。
对于服务而言,每一个连接到它的客户端都是一个session,servlet容器使用此接口创建HTTP客户端的HTTP服务器之间的会话,会话将保留指定的时间段,跨多个连接或者来自用户的页面请求,一个会话通常对应于一个用户,该用户可能多次访问一个站点。可以通过此接口查看和操作有关某个会话的信息,比如会话标识符,创建时间和最后一次访问时间,在整个session中,最重要的就是属性的操作。
session无论客户端还是服务器端都可以感知到,若重新打开一个新的浏览器,则无法取得之前设置的session,因为每一个session只保存在当前的浏览器中,并在响应的页面取得。
Session的作用就是为了标识一次会话,或者说确认一个用户;并且在一次会话(一个用户的多次请求)期间共享数据。我们可以通过request.getSession()的方法,来获取当前会话的session对象。
7.1 Session常用方法
如图session对象的常用方法:
常用方法
* 获取session的会话标识符 getId()
* 获取session的创建时间 getCreationTime()
* 获取最后一次的访问时间 getLastAccessedTime()
* 判断是否是新的session对象 isNew()
运行时服务器端显示:
第一个箭头指向第一次访问,从上向下分别为
id
创建时间(时间戳)
最后一次访问时间(由于此时第一次访问,所以创建时间与最后一次访问时间一致)
是否为新创建的session(第二次访问时显示false即不是新创建的session,此会话在你关闭浏览器之前都一直存在)
说明:session在你访问此界面时创建,即会话开始,在你关闭浏览器之前session会一直存在,会话也一样,会话中有很多请求,但是请求只能存在一次,但是其中会话会一直存在,类似于,你与老师谈话,会话是你与老师谈话这一段时间,但是请求只是老师与你之间的一个问题。
7.2 标识符 JSESSIONID
Session既然是为了标识一次会话,那么此次会话就应该有一个唯一的标识,这个标志就是sessionid。
每当一次请求到达服务器,如果开启了会话(访问了session),服务器第一步会查看是否从客户端回传一个名为JSESSIONID的cookie,如果没有则认为这是一次新的会话,会创建一个新的session对象,并用唯一的sessionid为此次会话做一个标识,如果有JESSIONID这个cookie回传,服务器则会根据JESSIONID这个值去查看是否含有id为JESSION值的session对象,如果没有则认为是一个新的会话,返回该session对象,数据达到共享。
这里提到一个叫做JESSIONID的cookie,这是一个比较特使的cookie,当用户请求到达服务器时,如果访问了sesion,则服务器会创建一个名为JESSIONID,值为获取到的session(无论时获取到的还是新创建的)sessionid的cookie对象,并添加到response对象中,响应给客户端,有效时间为关闭浏览器。
所以Session的底层依赖Cookie来实现。
JSESSIONID的cookie
7.3 Session域对象
Session用来表示一次会话中,在一次会话中数据是可以共享的,这时session作为域对象存在,可以通过setAttribute(name,value);方法向域对象中添加数据,通过getAttribute(name)从域对象中获取数据,通过removeAttribute(name)从域对象中移除数据。
如图所示,session域对象常见用法
数据存储在session域对象中,当session对象不存在了,或者时两个不同的session对象时,数据也就不能共享了,这就不得不谈到session的生命周期了。
而session域对象与request域对象的不同之处在于:
session域对象存在于整个会话存在期间,但是request域对象只存在于一次请求之间
所以对于重定向与请求转发,session都会存在,但是request只在请求转发中存在,在重定向中因为客户端进行了两次访问,所以不会存在第二次中。
如图上方为请求转发,此时客户端访问jsp界面,即uname,upwd与name都会显示
但是如果此时将请求类型转为重定向,此时name对象就无法显示,因为nae对象为request域对象。
此时request对象由于只存在于一次请求中所以无法取到它的值。
5.4 Session域对象的销毁
1.默认到期时间
当客户端第一次请求servlet并且操作session时,session对象生成,Tomcat中session默认的存活时间为30min,即你不操作界面的时间,一旦有操作,session会重新计时。
那么session的默认时间可以修改吗?答案是肯定的
可以在 Tomcat中的conf目录下的web.xml文件中进行修改
例如:当你上个厕所回来网站依旧连接session,但是当你睡了个午觉回来,网站显示需要重新登录,即代表此时session失效了,需要重新登录。
2..自己设定到期时间
当然除了以上的修改方式外,我们也可以在程序中自己设定session的生命周期,通过session.setMaxinactiveInterval(int)来设定session的最大不活动时间,单位为秒
当然我们也可以通过getMaxinactiveIntervcal()的方法来查看当前的session对象的最大不活动时间。
3.立刻失效
即每次查看JSESSIUONID对象时显示的数值都不一样
4.关闭浏览器
从前面的JESSIONID可以知道,session底层依赖于cookie实现,并且该cookie的有效时间为关闭浏览器,从而session在浏览器关闭时相当于失效了(因为没有相应的JSESSION再与之对应)
5.关闭服务器失效
当关闭服务器时,session销毁。
Session失效则意味着此次会话结束,数据共享结束。