【基础系列二十二】--Servlet
Servlet
Servlet定义
- SUN公司制定
- 用于扩展web服务器功能的组件。
1. 扩展web功能—实现web动态资源的处理
2. 组件–符合规范的,完成部分功能的程序模块。需放到容器里才能运行。
3. 容器–为组件提供运行环境,封装了与浏览器交互功能的程序。例如TomCat容器。
怎样开发一个Servlet
- 编写一个类
- 实现Servlet接口或者继承HttpServlet/GenericServlet
Servlet体系结构
从上图可以看出 Servlet 规范就是基于这几个类运转的,与 Servlet 主动关联的是三个类,分别是 ServletConfig、ServletRequest 和 ServletResponse。这三个类都是通过容器传递给 Servlet 的,其中 ServletConfig 是在 Servlet 初始化时就传给 Servlet 了,而后两个是在请求达到时调用 Servlet 时传递过来的。
Servlet继承结构
Servlet分析
Servlet接口
Servlet接口:该接口定义了5个方法--三个和声明周期相关
1. init(ServletConfig config),初始化 servlet 对象,完成一些初始化工作。它是由 servlet 容器控制的,该方法只能被调用一次
2. service(),接受客户端请求对象,执行业务操作,利用响应对象响应客户端请求。
3. destroy(),当容器监测到一个servlet从服务中被移除时,容器调用该方法,释放资源,该方法只能被调用一次。
4. getServletConfig(),ServletConfig 是容器向 servlet 传递参数的载体。
5. getServletInfo(),获取 servlet 相关信息。
GenericServlet抽象类
在该类中定义了一个本地变量config,用来保存容器传过来的ServletConfig对象。另外该类并没有重写service()方法
该对象保存了Servlet应用的相关信息。
1)private transient ServletConfig config;
2)public String getInitParameter(String name):返回指定名称的初始化参数值;
3)public Enumeration getInitParameterNames():返回一个包含所有初始化参数名的 Enumeration 对象;
4)public String getServletName():返回在 DD 文件中<servlet-name>元素指定的 Servlet 名称;
5)public ServletContext getServletContext():返回该 Servlet 所在的上下文对象;
6) public abstract void service(ServletRequest req, ServletResponse res)throws ServletException, IOException;
7)public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
另外:为什么该类中要定义两个初始化参数?
1. 这是一个抽象类。对外可以重写该类的init()方法。
2. 当我们重写init(ServletConfig config)方法时就会破坏该类的init(。。)方法。是的config一直为null。因此建议我们重写该类的初始化方法时,应该重写init()方法。
Servlet上下文
延伸:ServletContext上下文?
1. 每个Web项目都有唯一一个---上下文实例。
2. 在web项目启动时候创建,在web项目销毁时销毁。
怎样得到Servlet应用的ServletContext的上下文对象呢?
查看源码:
public ServletConfig getServletConfig() {
return config;
}
public ServletContext getServletContext() {
return getServletConfig().getServletContext();
}
因此有两种方式:
直接获取:getServletContext();
间接获取:getServletConfig().getServletContext();
上下文实例有什么作用?
1. 检索 Servlet 容器的信息
1. public String getServletContext():返回 ServletContext 对应的 web 应用程序名称 <display-name>元素定义的名称;
2. 使用 ServletContext 对象存储数据
1. (1)、public void serAttribute(String name,Object object):将给定名称的属性值对象绑定到上下文对象上;
2. (2)、public Object getAttribute(String name):返回绑定到上下文对象的给定名称的属性值;
3. (3)、public Enumeration getAttributeNames():返回绑定到上下文对象上的所有属性名的 Enumeration 对象;
4. (4)、public void removeAttribute(String name):删除绑定到上下文对象指定名称的属性
3. 获得应用程序的初始化参数
(1)、public String getInitParameter(String name):返回指定参数名的字符串参数值,没有则返回 null;
(2)、public Enumeration getInitParameterNames():返回一个包含多有初始化参数名的 Enumeration 对象;
4. 登陆日志:使用 log()方法可以将指定的消息写到服务器的日志文件中
通过源码: 该类实现ServletConfig的相关的方法底层也是获取了上下文对象的相关方法来实现的。
Servlet中的共享变量
Servlet中共享变量的作用域?
1. ServletRequest 共享的对象仅在请求的生存周期中可以被访问;
2. HttpSession 共享的对象仅在会话的生存周期中可以被访问;
3. ServletContext 共享的对象在整个 Web 应用程序启动的生存周期中可以被访问;
HttpServlet抽象类
代码分析
public abstract class HttpServlet extends GenericServlet;
该抽象类中定义的7种Http请求方法名
private static final String METHOD_DELETE = "DELETE";
private static final String METHOD_HEAD = "HEAD";
private static final String METHOD_GET = "GET";
private static final String METHOD_OPTIONS = "OPTIONS";
private static final String METHOD_POST = "POST";
private static final String METHOD_PUT = "PUT";
private static final String METHOD_TRACE = "TRACE";
为什么定义?
主要为了判断浏览器的请求方式,针对不同的请求方式调用不同的方法来处理。
其中我们常用的两种请求方式:GET和POST请求。
1. 两者的区别?
1. GET:
1. GET会吧请求参数直接显示在URL之后。
1. 只能传递少量数据,大约2kb。
2. 数据不安全。
2. POST:
1. 数据相对安全。因为它不会直接将请求参数显示在URL地址栏。
2. 可以传递大量的数据。
3. GET请求和POST请求都不能对数据进行加密。
该抽象类实现了service方法查看源码发
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException {
HttpServletRequest request;
HttpServletResponse response;
try {
request = (HttpServletRequest) req;
response = (HttpServletResponse) res;
} catch (ClassCastException e) {
throw new ServletException("non-HTTP request or response");
}
service(request, response);
}
将ServletRequest和ServletResponse---转化为HttpServletRequest和HttpServletResponse对象。该两个对象封装了浏览器的请求信息和服务器的响应信息。
=======================================================================
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String method = req.getMethod();
if (method.equals(METHOD_GET)) {
long lastModified = getLastModified(req);
if (lastModified == -1)
doGet(req, resp);
} else {
long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
if (ifModifiedSince < (lastModified / 1000 * 1000)) {
maybeSetLastModified(resp, lastModified);
doGet(req, resp);
} else {
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
}
} else if (method.equals(METHOD_HEAD)) {
long lastModified = getLastModified(req);
maybeSetLastModified(resp, lastModified);
doHead(req, resp);
} else if (method.equals(METHOD_POST)) {
doPost(req, resp);
} else if (method.equals(METHOD_PUT)) {
doPut(req, resp);
} else if (method.equals(METHOD_DELETE)) {
doDelete(req, resp);
} else if (method.equals(METHOD_OPTIONS)) {
doOptions(req,resp);
} else if (method.equals(METHOD_TRACE)) {
doTrace(req,resp);
} else {
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[1];
errArgs[0] = method;
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
}
}
创建一个Servlet应用
1. 创建一个类实现Servlet接口,或者继承HttpServlet/GenericServlet类。
2. 创建一下目录结构
1. 应用名(随意起)
1. WEB-INF--必须
1. classes--类文件
2. lib--jar包
3. web.xml--必须
4. 其他
2. 其他
3. 编译
4. 打包
5. 部署
6. 运行
Servlet 调用过程
调用过程时序图
Servlet生命周期
实例化
a.什么是实例化?
容器调用Servlet的构造器创建对应的对象。
b.什么时候实例化?
b1.默认情况下,容器收到请求之后,才会创建其实例。
b2.容器只会创建一个实例。(单例)
b3.容器启动之后,立即创建其实例(需要额外配置--在Web.xml中配置<load-on-startup>1<load-on-startup>)
初始化
a.什么是初始化?
实例化之后,容器调用该实例的init方法。
(该方法只会调用一次)。
b.GenericServlet已经提供了init方法的实现。
会将容器传递过来的ServletConfig对象保存下来,
并且提供了一个getServletConfig方法。
c.初始化参数
step1.配置初始化参数(web.xml)
d.可以override GenericServlet提供的init方法来实现
自己的初始化处理逻辑。
注:建议override init(),而不是init(ServletConfig config)。
在web.xml中配置<init-param></init-param>初始化参数,我们就可以利用ServletConfig提供的getInitParameter方法获取。
调用(就绪)
a.什么是就绪?
容器收到请求之后,会调用Servlet实例的service方法来处理请求。
b.HttpServlet已经实现了service方法。
依据请求类型来调用对应的doXXX方法。
比如,get请求会调用doGet方法,post请求会调用
doPost方法。
c.可以override HttpServlet的service方法或者override
HttpServlet的doGet,doPost方法来写处理逻辑。
销毁
a.什么是销毁?
容器在删除Servlet实例之前,会调用该实例的destroy方法。
(destroy方法只会执行一次)。
b.可以override GenericServlet提供的destroy方法来实现自已的
销毁处理逻辑。
Servlet 乱码问题
(1)为什么会有乱码?
表单提交时,浏览器会对表单中的中文参数值进行编码
(注:会使用表单所在的页面打开时使用的字符集来编码),
服务器端默认会使用iso-8859-1来解码,所以会产生乱码。
(2)如何解决?
1)post请求
request.setCharacterEncoding(String charset);
注:
a.要加到所有的getParameter方法的前面。
b.只针对post请求有效。
2)get请求
GET提交的乱码问题可以通过手动编解码来解决!!
//>>username为乱码, 通过乱码反向编码得回二进制数组
byte[] bytes = username.getBytes("iso8859-1");
//>>通过二进制数组查询正确的码表, 得出正确的数据
username = new String(bytes, "utf-8");
服务端响应--中文乱码问题
response.setContentType("text/html;charset=utf-8");
Servlet请求路径问题
容器怎样处理请求路径问题
比如,在浏览器地址栏输入
http://ip:port/servlet-day04/abc.html
step1.容器默认认为访问的是一个servlet,会从web.xml
中去查找有没有匹配的servlet。
匹配规则:
(1)精确匹配 <url-pattern>/abc.html</url-pattern>
(2)通配符匹配 (使用*匹配0个或者多个字符,比如)
<url-pattern>/*</url-pattern>
<url-pattern>/abc/*</url-pattern>
(3)后缀匹配 (以*.开头,后接任意的字符,比如)
<url-pattern>*.do</url-pattern>
匹配所有以.do结尾的请求。
step2.如果没有匹配的servlet,则查找对应的文件。
Servlet虚拟路径的配置
在web.xml中的servlet对外访问的虚拟路径的配置, 可以直接写一个路径, 或者通过 * 号匹配符写一个路径.
方式一:直接写一个路径: /servlet/SecondServlet
方式二:通过*号匹配符写一个路径:
(1)以 / 开头, 以 /* 结尾, 如: /servlet/* /a/* /*
(2)以 *.后缀 的形式, 如: *.html *.servlet *.do *.action
使用*号匹配符写路径, 路径的配置变得更加灵活, 但是也可能会造成, 一个url会被多个servlet Mapping所匹配
Url:http://localhost/day09/servlet/SecondServlet.do
Servlet1:Test1: /servlet/*
Servlet2:Test2: *.do
匹配规则:
*.后缀的优先级永远最低!!
哪一个更接近哪一个起作用!!
Servlet线程安全问题
a.容器默认情况下,对于某个Servlet,只会创建一个实例。
b.容器收到一个请求,就会启动一个线程来处理请求,这样,就有可能
有多个线程同时调用同一个Servlet实例,就有可能产生线程安全问题
(比如,多个线程同时去修改某个属性)。
如何解决?
a.使用synchronized对有线程安全问题的代码加锁。
注:会影响一些性能。
b.尽量避免写有线程安全的代码,比如,不要修改属性。
Servlet 域对象
Request
代表浏览器请求对象,封装浏览器请求信息
Request生命周期
一次请求开始时创建request对象, 一次请求结束时销毁request对象
Request作用范围
整个请求链
Request主要作用
1. 获取客户端相关信息
getMethod -- 得到客户机请求方式
getRequestURL方法 -- 返回客户端发出请求完整URL
如: http://localhost/day09/servlet/SecondServlet
getRequestURI方法 -- 返回请求行中的资源名部分
如: /day09/servlet/SecondServlet
getContextPath -- 获得当前web应用虚拟目录名称
/day10
2. 获取请求头信息
3. 获取请求参数
getParameter(String name) --- String 通过name获得值
getParameterValues(String name) --- String[ ] 通过name获得多值 checkbox
getParameterMap() --- Map<String,String[ ]> key :name value: 多值
getParameterNames() --- Enumeration<String> 获得所有name
4. 请求转发
请求转发的特点:
一次请求对应一次响应
地址栏地址不会发生变化
请求转发只能在同一个WEB应用内部资源之间进行跳转! 不能是不同的WEB应用或者不同的主机!
request.getRequestDispatcher("/servlet/RequestDemo4").forward(request, response);
注意:在web阶段写路径时,除了请求转发和请求包含
在写路径是不用包含web应用的虚拟路径,其他地方都需要加上web应用的虚拟路径。
完整路径:http://localhost/day09/servlet/RequestDemo4
5. 为域对象来使用
域对象,如果一个对象具有一个可以被看见的范围, 利用该对象上的map可以在整个范围内实现资源的共享!
域对象提供的方法(可以操作map中的数据):
setAttribute(String name, Object value); 用来存储一个对象,也可以称之为存储一个域属性
getAttribute(String name); 用来获取request中的数据
removeAttribute(String name); 用来移除request中的域属性
getAttributeNames(); 获取所有域属性的名称
6. 请求包含
如果浏览器访问Servlet A, 但是A不能独立的处理这次请求, 需要另外一个Servlet B帮忙, 于是在A中可以将B包含进来, 包含的代码如下:
request.getRequestDispatcher(“B的路径”).include(request, response);
将B包含进来后, 将会由A和B共同来处理这次请求, 处理的结果也会合并在一起, 一起打给浏览器!
Request开发细节:
在转发之前, 如果response缓冲区被写入了数据但是还没有打给浏览器, 在转发时response缓冲区(数据)将会被清空!
在转发之前, 如果response缓冲区被写入了数据并且已经打给了浏览器, 转发将会失败!!因为已经响应过了,一次请求对应一次响应。
在同一个Servlet中转发不能进行多次!!(A既转发B, 又转发给C)但是可以进行多重转发(比如A转发给B, B再转发给C)
Response
代表服务端的响应对象
Response生命周期
一次请求开始时创建Response对象, 一次请求结束时销毁Response对象
Response作用范围
整个请求链响应链
Response主要作用
1. 向客户端发送数据
相关方法 设置状态码的方法
void setStatus(int sc)
void setStatus(int sc, String sm)
设置响应头的方法
void setHeader(String name, String value)
void setDateHeader(String name, long date)
void setIntHeader(String name, int value)
void addHeader(String name, String value)
void addDateHeader(String name, long date)
void addIntHeader(String name, int value)
注:set为设置,当本来存在这个头时是修改,add只是添加。
设置响应实体内容的方法
ServletOutputStream getOutputStream()
PrintWriter getWriter()
2. 实现重定向
利用302状态码加上location响应头实现请求重定向。
手动实现:
response.setStatus(302);
response.setHeader(“location”, “/day10/index.jsp”);
调用方法:
response.sendRedirect(“/day10/index.jsp”);
注意:
重定向的特点:
两次请求, 两次响应
地址栏地址会发生变化
既可以实现在同一个WEB应用内部资源之间进行跳转, 也可以在不同的WEB应用或者是不同的服务器资源之间进行跳转
由于是两次请求,两次响应,无法通过request对象共享数据
3.定时刷新
与重定向类似,不同之处就是可以指定几秒之后跳转。可以通过refresh头实现在多少秒之后跳转指定的资源.
代码: response.setHeader(“refresh”, “3;url=/day10/index.jsp”);
定时刷新的特点:
两次请求, 两次响应
地址栏地址会发生变化
既可以实现在同一个WEB应用内部资源之间进行跳转, 也可以在不同的WEB应用或者是不同的服务器资源之间进行跳转
4. 控制浏览器的缓存行为
setDateHeader("Expires", -1);
setHeader("Cache-control", "no-cache");
setHeader("Pragma", "no-cache");
控制浏览器缓存:
setDateHeader("Expires", System.currentTimeMillis()+1000*60*60*24);
或
setHeader("Cache-control", "max-age=60"); //优先级更高
Response开发细节
getOutputStream()和getWriter() 这两个方法是互斥的, 在一次请求当中调用了其中的一个, 就不能再调用另一个!!!
在调用完getOutputStream()或getWriter()方法之后, 不需要手动去关闭流, 服务器会自动帮我们去关闭!!!
这两个方法获取到的流并不是指向客户端的流, 而是指向response缓冲区的流, 通过流将数据写入response缓冲区, service方法执行结束, 请求回到服务器, 由服务器将数据组织成响应消息打给浏览器!!
下一篇:【基础系列二十三】–JSP