JavaEE Servlet知识点总结
1.1 Servlet介绍
问题:
服务器在接收到浏览器的请求后,会自动调用对应的逻辑代码进行请求处理。但是逻辑代码是由
程序员编写并放到服务器中,那么服务器怎么知道该怎么调用并选择调用哪个类和哪个方法来进
行请求处理呢?
解决:
程序员在编写代码的时候如果能够按照服务器能够识别的规则进行编写,浏览器按照指定的规则
发送请求,那么服务器就可以调用并执行相应的逻辑代码进行请求处理了。
实现:
使用Servlet技术
1.1.1 Servlet的概念
狭义的Servlet是指Java语言实现的一个接口,广义的Servlet是指任何实现了Servlet这个接口的类,
一般情况下,人们将Servlet理解为后者。Servlet运行于支持Java的应用程序中。从原理上讲,
Servlet可以响应任何类型的请求,但绝大多数情况下Servlet只用来扩展基于HTTP协议的WEB服务
器。也可以说Servlet是服务器后台处理程序的入口。
1.1.2 Servlet的特点
- 运行在支持Java的应用服务器上
- Servlet的实现遵循了服务器能够识别的规则,也就是服务器会自动的根据请求调用对应的
Servlet进行请求处理。 - 简单方便,可移植性强。
1.1.3 Servlet的使用步骤
- 1.创建普通的Java类并继承HttpServlet
- 2.重写service()方法
- 3.在service()方法中编写业务逻辑代码
- 4.在webRoot下的WEB-INF文件夹下的web.xml文件中配置Servlet
示例:
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 MyServlet extends HttpServlet{
//重写service方法
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("这是我的第一个Servlet程序");
resp.getWriter().write("this is my first servlet");
}
}
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">
<!-- 配置servlet -->
<servlet>
<servlet-name>my</servlet-name>
<servlet-class>org.sun.servlet.MyServlet</servlet-class><!-- Servlet的全限定路径(包名+类名) -->
</servlet>
<servlet-mapping>
<servlet-name>my</servlet-name>
<!-- 配置servlet的访问别名方式一:精确配置 -->
<url-pattern>/s</url-pattern>
<!-- 配置servlet的访问别名方式二:模糊配置
<url-pattern>*.后缀名</url-pattern>
所有以指定后缀名结尾的请求都会调用该Servlet
主要用于模块化开发的划分等-->
<!-- 配置servlet的访问别名方式三:拦截所有请求
<url-pattern>*</url-pattern>
-->
<!-- 配置servlet的访问别名方式四:
会拦截处理所有servlet的别名以one开头的路径的请求
<url-pattern>/one/*</url-pattern>
-->
</servlet-mapping>
</web-app>
一个Servlet可以配置多个url-pattern,但是一个url-pattern仅能对应一个Servlet。
服务器会在启动阶段加载项目内的web.XML文件至内存中。
如果一个url-pattern对应多个Servlet会在服务器启动阶段报错!
1.2 服务器中Servlet的创建是单例的
服务器在接收到浏览器的请求后,会开辟一个线程来处理此次请求,在线程中调用对应的Servlet进行处理。
服务器调用Servlet处理请求,但是一个Servlet服务器只会创建一个实例化对象,该对象是线程共享的。
当有多个请求来调用同一Servlet时,仅仅是通过一个Servlet对象调用service()方法,但每个线程给service
方法传递的参数不同,也就是说每个请求的线程操作的是不同的数据。这样避免了重复创建大量对象导致的
服务器性能的降低。
1.3 Servlet在服务器中的生命周期
Servlet的生命周期从服务器开启实例化Servlet对象开始,直到服务器关闭,销毁Servlet对象。
通常情况下,Servlet对象会在服务器开启后第一个请求调用该Servlet对象时被实例化,而后调用该Servlet
的所有请求不再实例化新的对象。
如果需要在服务器开启的时候就实例化所有Servlet对象,而不是等请求调用时再去创建,需要在web.XML
配置的每个Servlet标签中添加如下标签:
<load-on-startup>1</load-on-startup>
其中的数字代表Servlet对象的加载顺序。
这种情况下,Servlet的生命周期也就变为从服务器开启到服务器关闭。
完整的代码结构如下:
<?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">
<!-- 配置servlet -->
<servlet>
<servlet-name>my</servlet-name>
<servlet-class>org.sun.servlet.MyServlet01</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>my</servlet-name>
<url-pattern>/s1</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>my2</servlet-name>
<servlet-class>org.sun.servlet.MyServlet02</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>my2</servlet-name>
<url-pattern>/s2</url-pattern>
</servlet-mapping>
</web-app>
1.3.1 Sevlet中的init()方法
该方法中的代码会在服务器实例化Servlet对象时被调用,仅被执行一次。
1.3.2 Servlet中的destroy()方法
该方法中的代码会在服务器关闭并销毁Servlet时被调用。
1.4 doGet() doPost()和service()方法的区别
doGet(HttpServletRequest req,HttpServletResponse resp)
特点:
- 处理get方式的请求。
doPost(HttpServletRequest req,HttpServletResponse resp)
特点:
- 处理post方式的请求
service(HttpServletRequest req,HttpServletResponse resp)
特点:
-
无论是什么类型的请求方式,服务器都会优先执行service方法
-
这是由Servlet的底层源码决定的,当服务器接收一个请求时,首先会在Servlet中寻找service方法,
如果Servlet中没有定义service方法,就会执行父类HttpServlet中的service方法,父类中的service
方法会先判断该请求的方式,如果子类中重写了与该请求方式相对应的方法,则执行子类中的方法
否则执行父类中与该请求方式相对应的方法,父类中所定义的与请求方式相对应的方法实际只是返
回了错误信息,因此浏览器会报错。
注意:
如果Servlet中没有声明service方法,会根据请求的方式调用对应的方法进行请求处理
如果在Servlet中没有与其对应的请求处理方法,则会报405错误。
1.5 Servlet常见错误总结
1.5.1 404错误-资源未找到
原因一:在请求地址中servlet的别名书写错误。
原因二:虚拟项目名称拼写错误。
1.5.2 500错误-内部服务器错误
错误一:java.lang.ClassNotFoundException
解决方法:在web.XML中校验servlet类的全限定路径是否拼写错误
错误二:service方法体代码执行错误
解决办法:根据控制台错误提示对service方法中的代码进行更改
1.5.3 405错误-请求方式不支持
原因:请求方式和servlet中的方法不匹配造成的
解决:尽量使用service方法进行请求处理,并且不要在service方法中调用父类的service。
1.6 HttpServletRequest
request对象:用于存储请求数据
服务器在接收到请求后,会给此次请求创建一个request对象,该对象中封存了此次请求相关的数据
每次请求都会创建新的request对象存储请求数据
特点:
-
request对象由服务器创建,并将此对象作为实参传递给service方法
-
一次请求结束之后,request对象随即被销毁
请求数据包括:请求行、请求头、请求实体。
获取请求行中的数据信息:
获取请求方式:
String method=req.getMethod();
获取i请求URL:
StringBuffer requestURL=req.getRequestURL();
获取请求URI:
String requestURI=req.getRequestURI();
获取get请求中URL中的用户数据(?后的所有字符):
String queryString=req.getQueryString();
获取协议:
String schema=req.getSchema();
获取请求头中的数据信息:
获取请求头中键名的枚举:
Enumeration headerNames=req.getHeaderNames();
获取键名的枚举后可以通过遍历的方式遍历所有请求头的键值对。
也可以通过下列语句获取单独的键的信息,例如
获取浏览器UserAgent:
String userAgent=req.getHeader("User-Agent");
获取请求实体数据(用户数据):
根据键名获取数据:
String value=req.getParameter(String name);
键名就是前端页面中的表单标签的name属性的值或者前端页面其他方式提交数据的键的名字。
如果请求中没有对应的请求数据则返回null,在代码的其他部分易导致NullPointerException
获取同名不同值的实体数据(如多选框checkbox中的数据):
String[] favs=req.getParameterValues(String name);
这里对favs进行遍历使用时,最好做一下判断其是否为空。
获取请求实体中键名的枚举:
Enumeration names=req.getParameterNames();
//遍历枚举
while(name.hasMoreElements()){
//获取键名
String name=(String) names.nextElement();
//获取值
String value=req.getParameter(name);
}
注意这里在进行遍历时可能遇到枚举中键名所对应的键为多选框等同名多值的情况
这时候在获取该键名的值的时候要使用getParameterValues(String key)方法。
获取请求相关的网络信息:
获取客户端IP信息:
String remoteAddr=req.getRemoteAddr();
获取客户端的端口号(浏览器):
int remotePort=req.getRemotePort();
获取服务器主机IP:
String localAddr=req.getLocalAddr();
获取服务器的端口号:
String localPort=req.getLocalPort();
1.7 HttpServletResponse
1.7.1 设置响应行
响应行包括:协议、状态码、状态消息
自定义响应异常:可以自主的响应状态给浏览器
resp.sendError(int status);
1.7.2 设置响应头
添加响应头信息:
resp.addHeader(String key,String value);
//如果已存在同名键,addHeader仍会新增一个同名键
设置响应头信息:
resp.setHeader(String key,String value);
//如果键不存在,setHeader会创建该键
//如果键存在,新的键值会覆盖旧键值
1.7.3 设置响应实体
resp.getWriter().write(String s);
注意:
实体内容可以分开多次进行响应。
一旦使用resp对象作出了请求响应,则意味着此次请求处理完毕。
服务器在响应后会将此次请求相关的req对象和resp对象销毁。
1.8 乱码问题
请求乱码问题:服务器获取的请求数据乱码
-
post请求方式乱码解决:
-
在获取请求数据之前设置请求编码格式:
req.setCharacterEncoding("utf-8");
-
-
get请求方式乱码解决:
-
方式一:获取请求数据时重新为数据编码
String originUname=req.getParameter("uname"); String uname=new String(uname.getBytes("iso-8859-1"),"utf-8");
该方式对每个数据都要进行重新编码,效率较低。
-
方式二:
req.setCharacterEncoding("utf-8");
在tomcat的server.XML文件中的Connector标签中增加属性:
useBodyEncodingForURI=‘true’;
-
响应乱码问题:浏览器显示的响应数据乱码
在响应处理结果之前设置响应编码格式:
resp.setContentType("text/html;charset=utf-8");
//下面这种写法也是可以的
resp.setHeader("content-type","text/html;charset=utf-8");
为规范service方法的编写流程,通常按照如下步骤:
- 设置请求编码格式
- 设置响应编码格式
- 获取请求信息
- 处理请求信息
- 响应处理结果
1.9 请求转发
问题:
一次请求的处理需要多个Servlet的联动操作,第一个Servlet需要用到其他Servlet已经声明的
逻辑处理代码,这个时候需要怎么办呢?
解决:
使用请求转发
解释:
所谓的请求转发就是在一个Servlet中调用其他的Servlet,第一个Servlet执行请求转发时,会将
request对象和response对象作为数据载体传递给其他Servlet,最终浏览器接收到的响应并非来
自初始的Servlet而是来自于其他Servlet。
使用:
req.getRequestDispatcher("url-pattern").forword(req,requst);//url-pattern为servlet别名不包含斜杠“/“
特点:
- 降低Servlet之间的代码冗余
- 一次请求转发内的Servlet共享此次请求的request和response对象
- 浏览器地址栏信息不改变,只有一次请求。
request的作用域:
一次请求转发内的Servlet,它可以用于数据流转的载体
缺点:
由于请求转发的地址栏信息不改变,只要刷新浏览器,就会再发送一个请求给服务器,造成请求再次被转发,
使数据被重复提交。这会导致某些业务需求下的问题,比如银行转账操作。下一小节中的重定向技术将会解决
该问题。
1.10 重定向技术
解释:重定向技术是指当一个请求调用第一个Servlet时,该Servlet将其重新定向,并发起一次新的请求到其他Servlet。
作用:保护第一次的请求,避免由于用户的刷新动作频繁的触发第一次请求的Servlet中service方法的重复执行。
特点:
- 两次请求,地址栏信息改变。
使用:
resp.sendRedirect("url-pattern");//url-pattern为servlet别名不包含斜杠“/”
重定向技术很好的保护了数据,但是传递数据又成为了新的问题。而session和cookie解决了这个问题。
1.11 Cookie技术
问题:
重定向技术很好的解决了请求转发导致的数据重复提交的问题,但是重新定向使用了两次请求,而首次请求结束之后,
request对象和response对象随即被销毁,如果第二次请求需要使用首次请求中的数据需要怎么办?
思考:
为使用首次请求中的数据,如果能在首次请求中的request对象和response对象被销毁前,将服务器在下次或其他请求
中需要使用的数据保存到浏览器客户端本地,然后浏览器在第二次或其他请求中附带上本地所保存的数据,传递数据的
问题不久解决了吗?那么这种功能正是Cookie技术所提供的。
解决:
使用Cookie技术
特点:
- 浏览器端的数据存储技术
- 服务器端以响应的方式通知浏览器哪些数据需要被存储
- 不适合大量数据的存储
- 解决了不同请求之间数据共享的问题
使用:
如何将数据存储到Cookie?
//创建Cookie对象
Cookie cookie = new Cookie(String key,String value);
//设置cookie的有效期,参数的单位为秒
cookie.setMaxAge(int seconds);
//设置cookie的有效URI,即"虚拟项目名/url-pattern"
c.setPath(String uri);
//通知浏览器将Cookie存储到本地
resp.addCookie(Cookie cookie);
默认情况下,如果不为Cookie指定有效期,那么Cookie是保存在浏览器的内存之中,其有效期会随浏览器进程的关闭而
到期。如果为Cookie指定了有效期,那么在有效期内,Cookie会存储在浏览器所在客户端的硬盘中。
如果不为Cookie指定有效URI,任何的请求都会附带该Cookie,可能造成无用数据的频繁传输,降低网络效率。
如何从Cookie中获取数据?
//声明一个空变量
String key =null;
//从请求中获取所有Cookie
Cookie[] cookies = req.getCookies();
//使用for-each循环在Cookie中寻找指定的键值
for(Cookie cookie:cookies){
if(cookie.getName().equals(“key”)){
key=cookie.getValue();
}
}
1.12 Session技术
什么是Session?
Session是一种服务器端的状态管理技术,Session是基于Cookie技术之上的。当浏览器访问服务器时,
服务器会为当前用户创建一个Session对象,该对象拥有一个唯一识别编码SessionID,默认情况下,服
务器会通知浏览器将该唯一识别码以Cookie的形式保存到浏览器内存中。在当前会话中,浏览器再次
访问服务器时,会将SeesionID发送给服务器,服务器据此便可寻找到之前创建的Session对象。
什么是状态管理?
所谓的状态管理就是将客户端与服务器之间多次交互当作一个整体来看,并且将多次交互所涉及的数据
(状态)保存下来。
什么是会话?
当用户打开浏览器,访问多个WEB资源,然后关闭浏览器的过程,称之为一个会话。通常一个会话是针对
一个浏览器的进程来说的。比较老旧的浏览器,每打开一个浏览器窗口(注意不是标签或选项卡)都会创建
一个新的不同的进程,由于SessionID是保存在浏览器的内存中的,不同的进程有不同的内存,所以这时会
创建一个新的Session。而目前大多数较新的浏览器多个窗口共享同一个进程、一块内存,因此仅会创建一
个Session对象。
使用:
以下为Servlet-s01中的service()方法代码:
//设置请求编码格式
req.setCharacterEncoding("utf-8");
//设置响应编码格式
resp.setContentType("text/html;charset=utf-8");
//获取请求信息
String b = req.getParameter("b");
//创建Session对象
HttpSession session =req.getSession();
//向Session中添加数据,键类型为String,值类型为Object
session.setAttribute("b", b);
//响应处理结果,重定向
resp.sendRedirect("s02");
以下为Servlet-s02中的service()方法代码:
//设置请求编码格式
req.setCharacterEncoding("utf-8");
//设置响应编码格式
resp.setContentType("text/html;charset=utf-8");
//获取请求信息
//获得Session对象,此处语句与创建Session对象相同
//如果重新定向过来的请求没有SessionID,则会新建Session对象。
//如果重定向过来的请求中包含SessionID,则会获取此ID的Session对象(假如该对象未过期)
//如果session对象到期销毁了,即使有SessionID也会重新创建一个Session对象。
HttpSession session=req.getSession();
//获取Session中的数据,注意getAttribute方法返回的类型是Object
String b=(String)session.getAttribute("b");
//可以使用下列代码删除Session中的属性,如果无此属性则什么也不做(直接return)
//session.removeAttibute("keyName");
//处理请求信息
System.out.println(b);
//响应处理结果
resp.getWriter().write("拿到了s01中的数据b:"+b);
设置全局Session对象的有效期:
在tomcat安装目录下的conf文件夹中,找到web.xml文件中的session-config标签中的子标签
session-timeout的值,默认为30,单位为分钟。
单独的为某个Session对象设置有效期:
session.setInactiveInterval(int seconds);//单位是秒
强制销毁Session:
session.invalidate();
Session的特点:
- Session解决了同一个用户不同请求的数据共享问题。
- Session的作用域:浏览器不关闭、Sesion不失效情况下或一次会话中,同一用户的任意请求获取的都是同一个Session对象。
1.13 ServletContext对象
问题:
Request解决了一次请求内的数据共享问题,Session解决了用户不同请求的数据共享问题,那么不同的用户的数据共享该怎么办呢?
解决:
使用ServletContext对象
作用:
解决了不同用户的数据共享问题
原理:
ServletContext对象有服务器进行创建,一个项目中只拥有一个ServletContext对象。不管在项目的任意位置进行获取得到的都是同
一个对象,因此不同的用户所发起的请求获取到的都是同一个ServletContext对象,也就是说该对象被所有用户共享。
特点:
- 由服务器创建
- 用户共享:一个项目仅有一个该对象
- 生命周期:服务器启动到服务器关闭
- 作用域:当前项目内
使用:
//获取ServletContext对象,以下三种方式获取的都是同一对象
//方式一:
ServletContext servletContext = this.getServletContext();
//方式二;
ServletContext servletContext = this.getServletConfig().getServletContext();
//方式三:
ServletContext servletContext = req.getSession().getServletContext();
//向ServletContext对象中添加/修改数据
servletContext.setAttribute(String key,Object obj);
//获取ServletContext对象中的数据
Object value=servletContext.getAttribute(String key);
//获取ServletContext对象中的所有键的枚举
Enumeration attributeNames = servletContext.getAttributeNames();
//移除ServletContext对象中的数据
servletContext.removeAttribute(String key);
通过ServletContext对象还能获取web.xml中的全局配置属性
这种全局配置属性通常在web.xml中这样声明:
<context-param>
<param-name>paraName1</param-name>
<param-value>paraValue1</param-value>
</context-param>
<context-param>
<param-name>paraName2</param-name>
<param-value>paraValue2</param-value>
</context-param>
使用ServletContext对象获取web.xml全局配置属性:
String paraName = servletContext.getInitParameter(String paraName);
//获取web.xml全局配置的所有键的枚举
Enumeration initParameterNames = servletContext.getInitParameterNames();
使用这种全部配置的一个显而易见的好处就是可以将项目中的部分动作和源文件进行解耦,只需更改xml中的配置
文件就可以达到改变代码执行的效果。
使用ServletContext对象获取WebRoot下资源的流对象:
//path为相对路径,是相对Webapps来说的
//例如 D:/Program Files/apache-tomcat-9.0.22/webapps/project01/IMAGE/logo.png
//只需写project01/IMAGE/logo.png
InputStream inputStream= servletContext.getResourceAsStream(String path);
使用ServletContext对象获取WebRoot下资源的绝对路径:
//relativePath为相对webapps的路径,如 project01/IMAGE/logo.png
//则获取到的绝对路径为 D:/Program Files/apache-tomcat-9.0.22/webapps/project01/IMAGE/logo.png
Sting path = servletContext.getRealPath(String relativePath);
1.14 ServletConfig对象
问题:
使用ServletContext对象可以获取web.xml中的全局配置文件,在web.xml中每个Servlet也可以进行单独的配置,
那么该怎么获取配置信息呢?
解决:
使用ServletConfig对象
作用:ServletConfig对象是Servlet的专属配置对象,每个Servlet都单独拥有一个ServletConfig对象,用来获取
web.xml中的配置信息。
配置当前Servlet的属性:
<servlet>
<servlet-name>Servlet01</servlet-name>
<servlet-class>com.bjsxt.servlet.Servlet01</servlet-class>
<init-param>
<param-name>paraName1</param-name>
<param-value>paraValue1</param-value>
</init-param>
<init-param>
<param-name>paraName2</param-name>
<param-value>paraValue2</param-value>
</init-param>
</servlet>
获取ServletConfig对象:
ServletConfig servletConfig = this.getServletConfig();
获取web.xml中当前Servlet属性配置的键名:
String key = servletConfig.getInitParameter(String key);
获取web.xml中当前Servlet属性配置键名的枚举:
Enumeration initParameterNames = servletConfig.getInitParameterNames();