Servlet深入
体系
![](https://img-blog.csdnimg.cn/af14ea4c1fb846139feb6ffc35d30e9a.png)
请求流程
![](https://img-blog.csdnimg.cn/75f7a90e800546feb48a9e9b80f33616.png)
生命周期
init: 初始化Servlet对象 注意: 一个Servlet类自始至终只会创建一个对象
当没有配置loadOnStartup=1时, 第一次访问该Servlet时会创建Servlet的对象, 后面访问该Servlet时将不会创建该Servlet对象, 而是直接执行service方法, 对其进行服务
如果配置了loadOnStartup=1时, 当服务器启动时就会创建该Servlet对象, 以后不管第几次访问该Servlet都不会再创建该Servlet对象
配置loadOnStartup
方式1: web.xml配置
<servlet>
<servlet-name>TestServlet</servlet-name>
<servlet-class>com.demo.TestServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>TestServlet</servlet-name>
<url-pattern>/test</url-pattern>
</servlet-mapping>
方式2: 注解配置
@WebServlet(name = "TestServlet",value = "/test",loadOnStartup = 1)
service: 进行服务,在进行服务时会先判断其请求方式,然后再调用其对应的方法
destroy: 销毁,当服务器关闭时,Serlvet类的实例会被销毁
线程安全问题
问题原因
因为Servlet实例是单例模式,当多个客户端并发访问同一个Servlet类时,Tomcat会创建多个线程,多个线程会使用同一个Servlet实例,会导致线程安全问题
解决方案
方案1: 实现SingleThreadModel接口
让我们的Servlet类实现SingleThreadModel接口,每个线程都会创建servlet实例,避免了多线程使用同一个Servlet实例的情况,但是使用这种方式会导致对客户端的请求响应效率变低,增加了服务器因频繁创建和销毁Servlet实例的开销,因此此种方式不建议使用,已经过时。
方案2: 使用锁
示例:
package com.demo;
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("/us")
public class UserServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
synchronized (this){
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
}
}
建议: 在Servlet实例中尽量不使用成员变量(属性)
Servlet核心类
请求
类名: HttpServletRequest
作用: 客户端向服务器发送的请求信息都会被封装到request对象
方法:
String value=request.getParameter(key);
request.setCharacterEncoding("utf-8");
value=new String(value.getBytes("ISO8859-1"),"UTF-8");
String method = request.getMethod();
String url = request.getRequestURL().toString();
String protocol = request.getProtocol();
Enumeration<String> en = request.getHeaderNames();
while(en.hasMoreElements()){
String key = en.nextElement();
String value = request.getHeader(key);
System.out.println(key+":"+value);
}
ServletInputStream inputStream = request.getInputStream()
响应
类名: HttpServletResponse
作用: 用于响应客户端请求(给客户端发数据)
方法:
response.setStatus(200);
response.setContentType("text/html;charset=UTF-8");
response.setContentLength(1024);
response.setHeader("Content-Type","text/html");
response.setCharacterEncoding("utf-8");
ServletOutputStream outputStream = response.getOutputStream();
PrintWriter out = response.getWriter();
转发与重定向
当客户端请求到了某个Servlet类之后,Servlet类进行处理,但是并不使用这个Servlet来响应客户端,而是要使用另一个Servlet来响应。
转发
流程
![](https://img-blog.csdnimg.cn/7fafdb5c5b20408aaf06f72f5b39c874.png)
特点
转发是在服务器端,两个Servlet之间的请求行为
浏览器只对服务器进行了一次请求
浏览器的地址不会改变,浏览器请求ServletA,ServletA转到ServletB由ServletB响应浏览器,浏览器显示的是ServletA的访问地址
转发过程中,可以通过request对象传递数据
语法
request.getRequestDispatcher("ServletB的URL").forward(request,response);
request.setAttribute("stuNum","10001");
request.setAttribute("stuAge",21);
request.setAttribute("stu",new Student(...));
request.getRequestDispatcher("ServletB的URL").forward(request,response);
String snum = (String)request.getAttribute("stuNum");
int sage = (Integer)request.getAttribute("stuAge");
Student stu = (Studennt)request.getAttribute("stu")
注意:转发前请求方式是什么,转发后请求方式不变
重定向
流程
![](https://img-blog.csdnimg.cn/6877546ea2c94379a0bd5c6261206c71.png)
特点
重定向是客户端的行为
浏览器对服务器发送了两次请求
重定向是由浏览器再次发送请求,浏览器地址会改变为转发的地址
不能通过request对象将ServletA中的数据传递给ServletB
语法
response.sendRedirect("/ServletB访问URL");
response.sendRedirect("/项目名/ServletB?key=value");
String value = request.getParameter("key");
示例
@WebServlet("/s1")
public class Servlet1 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request,response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
String username = request.getParameter("username");
username = UrlUtil.encode(username,"utf-8");
String info = UrlUtil.encode("Servlet1传递的信息","utf-8");
response.sendRedirect(request.getContextPath()+"/s2?username="+username+"&key="+info);
}
}
@WebServlet("/s2")
public class Servlet2 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request,response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
String username = request.getParameter("username");
String info = request.getParameter("key");
username = UrlUtil.decode(username,"utf-8");
info = UrlUtil.decode(info,"utf-8");
PrintWriter writer = response.getWriter();
writer.println("<html>");
writer.println("<head>");
writer.println("<title>Servlet2</title>");
writer.println("<mate charset='utf-8'>");
writer.println("</head>");
writer.println("<body>");
writer.println("<p>"+username+"</p>");
writer.println("<p>"+info+"</p>");
writer.println("</body>");
writer.println("</html>");
writer.flush();
writer.close();
}
}
经验:
当两个Servlet需要传递数据时,选择forward转发,不建议使用sendRedirect进行传递
重定向的请求方式是get
Cookie与HttpSession
Cookie
简介
![](https://img-blog.csdnimg.cn/7567d020700e43c39cacbf86d4e69135.png)
Cookie是在浏览器访问web服务器上的某个资源时,由web服务器在响应浏览器时通过响应头附带的传送给浏览器并存储在浏览器端的一小段数据
一旦web浏览器保存了来自于某个服务器的Cookie,那么当浏览器再次访问这个服务器的时候,会通过请求头将cookie传递给web服务器
浏览器访问服务器的时候,只会携带由当前服务器存储在客户端的cookie
Cookie中缓存的数据是以键值对形式存储的(name-value)
方法
Cookie ck=new Cookie(key, value);
ck.setPath("/当前项目名");
ck.setMaxAge(-1);
response.addCookie(ck);
Cookie[] cks=request.getCookies();
for(Cookie ck:cks){
if(ck.getName().equals(key)){
String value=ck.getValue();
break;
}
}
Cookie ck=new Cookie(key, value);
ck.setPath("/当前项目名");
ck.setMaxAge(0);
response.addCookie(ck);
注意:Cookie不支持存储中文,所以如果需要使用Cookie存储中文需要对其进行编码与解码
Cookie cookie = new Cookie(
URLEncoder.encode("姓名", "UTF-8"),
URLEncoder.encode("张三", "UTF-8"));
response.addCookie(cookie);
for(Cookie cc : request.getCookies()){
String cookieName = URLDecoder.decode(cc.getName(), "UTF-8");
String cookieValue = URLDecoder.decode(cc.getValue(), "UTF-8");
response.getWriter.println(cookieName + "=" + cookieValue + ";");
}
优点和限制
优点:
可以灵活的配置过期规则
简洁,cookie中存储的是最简单的键值对格式的文本数据
数据的持久性,保存到浏览器中的cookie在过期之前是持久存储的
限制:
存储数据的大小有限,大多浏览器支持4k、8k的数据存储
用户可以通过浏览器设置禁用cookie,因此限制了cookie的使用场景
cookie是存储在客户端浏览器中的,有被纂改的风险,有潜在的安全隐患
HttpSession
简介
Session对象,就是当浏览器向服务器发送请求建立连接后,由服务器创建的存储在服务器端的用于记录用户状态对象。
原理
![](https://img-blog.csdnimg.cn/4b3f1e9d38fa4431903b5c3f3f292018.png)
特性
服务器会为每个客户端连接分配一个Session对象,存储在服务器上
同一个浏览器发送多次请求,同属于一次会话,共享同一个Session对象
首次使用到Session时,服务器会自动创建Session,并创建Cookie存储SessionId发送回客户端
作用域:拥有存储数据的空间,作用范围是一次会话有效
一次会话是使用同一浏览器发送的多次请求。一旦浏览器关闭,则结束会话
可以将数据存入Session中,在一次会话的任意位置进行获取
可传递任何数据(基本数据类型、对象、集合、数组)
生命周期
开始:第一次使用到Session的请求产生,则创建Session
结束:
浏览器关闭,则失效
Session超时,则失效
session.setMaxInactiveInterval(seconds);//设置最大有效时间(单位:秒)
手工销毁,则失效
session.invalidate();//登录退出、注销
方法
HttpSession session = request.getSession();
String sessionId = session.getId();
session.setAttribute("key1","Hello");
session.setAttribute("key2","Java");
String s1 = (String) session.getAttribute("key1");
System.out.println("Servlet:"+s1);
session.setAttribute("key1","Hi");
session.removeAttribute("key1");
Session与Request的区别
request是一次请求有效,请求改变,则request改变
session是一次会话有效,浏览器改变,则session改变
ServletConfig
简介: Servlet的配置信息类
注意: 由Tomcat创建,一个Servlet对应一个ServletConfig
作用:
1.获取servlet-name的值
2.获取初始化参数init-param
3.获取ServletContext对象
public class HelloServlet extends HttpServlet {
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
System.out.println(config.getServletName());
System.out.println(config.getInitParameter("key1"));
System.out.println(config.getInitParameter("key2"));
ServletContext context = config.getServletContext();
}
}
<servlet>
<servlet-name>Hello</servlet-name>
<servlet-class>com.demo.HelloServlet</servlet-class>
<init-param>
<param-name>key1</param-name>
<param-value>值1</param-value>
</init-param>
<init-param>
<param-name>key2</param-name>
<param-value>值2</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>Hello</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
ServletContext
概念
![](https://img-blog.csdnimg.cn/9aae4a36634f4db2be712e277b12133b.png)
全局对象,也拥有作用域,对应一个Tomcat中的Web应用
当Web服务器启动时,会为每一个Web应用程序创建一块共享的存储区域(ServletContext)。
ServletContext在Web服务器启动时创建,服务器关闭时销毁。
作用: 全局共享数据
方法
ServletContext servletContext = getServletContext();
String contextPath = servletContext.getContextPath();
String realPath = servletContext.getRealPath("/");
servletContext.setAttribute("ckey",value);
Object v = servletContext.getAttribute("ckey");
servletContext.removeAttribute("ckey");
<!-- 配置上下文参数 : 键值对 -->
<context-param>
<param-name>key1</param-name>
<param-value>Hello</param-value>
</context-param>
<context-param>
<param-name>key2</param-name>
<param-value>World</param-value>
</context-param>
Enumeration<String> keys = servletContext.getInitParameterNames();
String v1 = servletContext.getInitParameter("key1");
Filter
概念
处于客户端与服务器目标资源之间的一道过滤技术
![](https://img-blog.csdnimg.cn/6bdcaa9ec32e4de3aa628a6684183b91.png)
作用
执行地位在Servlet之前,客户端发送请求时,会先经过Filter,再到达目标Servlet中;响应时,会根据执行流程再次反向执行Filter
可以解决多个Servlet共性代码的冗余问题(例如:乱码处理、登录验证)
创建
1.创建一个类实现Filter接口
2.重写方法
示例:
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("过滤器初始化了........init..."+filterConfig);
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("过滤前........doFilter");
chain.doFilter(request, response);
System.out.println("过滤后.......doFilter");
}
@Override
public void destroy() {
System.out.println("销毁了.....destroy");
}
}
过滤器配置
方案1: web.xml中配置
<filter>
<filter-name>MyFilter</filter-name>
<filter-class>com.demo.filter.MyFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>MyFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
方案2: 注解配置
@WebFilter("/*")
public class MyFilter implements Filter {
}
路径规则
精确过滤: 配置过滤器拦截指定的请求url
例如: /FirstServlet, /index.html
后缀过滤: 配置过滤器拦截指定的后缀的请求url
例如: *.jsp 、 *.html
通配符过滤:
- /* 拦截所有请求
- /aaa/bbb/* 拦截项目名后 当前项目下/aaa/bbb/FirstServlet 或者 当前项目下/aaa/bbb/a.html