Servlet Servlet生命周期
Servlet的整个生命周期过程的执行,均由Web服务器负责管理。即Servlet从创建到服务到销毁的整个过程中方法的调用,都是由Web服务器负责调用执行,程序员无法控制其流程。
但程序员可以获取到Servlet的这些生命周期时间点,并可以指定让Servlet做一些具体业务相关的事情。
生命周期方法执行流程图
测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 import javax.servlet.*;import java.io.IOException;public class SomeServlet implements Servlet { public SomeServlet () { System.out.println("创建SomeServlet实例" ); } @Override public void init (ServletConfig servletConfig) throws ServletException { System.out.println("初始化SomeServlet实例" ); } @Override public ServletConfig getServletConfig () { return null ; } @Override public void service (ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { System.out.println("执行SomeServlet实例的service()方法" ); } @Override public String getServletInfo () { return null ; } @Override public void destroy () { System.out.println("销毁SomeServlet实例" ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns ="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version ="4.0" > <servlet > <servlet-name > some-servlet</servlet-name > <servlet-class > SomeServlet</servlet-class > </servlet > <servlet-mapping > <servlet-name > some-servlet</servlet-name > <url-pattern > /some</url-pattern > </servlet-mapping > </web-app >
开启tomcat服务器
访问路径http://localhost:8080/web/some
控制台输出:
Servlet特征
Servlet是单例多线程的。
一个Servlet实例只会执行一次无参构造器与init()方法,并且是在第一次访问时执行。
用户每提交一次对当前Servlet的请求,就会执行一次service()方法。
一个Servlets实例只会执行一次destroy()方法,在应用停止时执行。
由于Servlet是单例多线程的,所以为了保证其线程安全性,一般情况下是不为Servlet类定义可修改的成员变量的。因为每个线程均可修改这个成员变量,会出现线程安全问题。
默认情况下,Servlet在Web容器启动时是不会被实例化的。
Web容器启动时创建Servlet实例 当创建多个实例的时候,0的优先级最高,会先被实例化,依此往下。
Servlet中的两个map 当Servlet实例被创建后,会得到该Servlet实例的引用,存放到一个Map集合中。该Map集合的key为url,而Value为Servlet实例的引用即Map<String,Servlet>
当Web容器从用户请求中分离出URI后,到第一个Map中没有找到其所对应的Servlet实例,则会马上查找到第二个Map,从中找到其所对应类名,再根据反射机制,创建这个Servlet实例,然后再将这个创建好的Servlet的引用放入到第一个Map中
ServletConfig的使用 ServletConfig用来获取web.xml
文件中的配置信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 import javax.servlet.*;import java.io.IOException;import java.util.Enumeration;public class SomeServlet implements Servlet { private ServletConfig config; public SomeServlet () { System.out.println("创建SomeServlet实例" ); } @Override public void init (ServletConfig servletConfig) throws ServletException { this .config = servletConfig; System.out.println("config = " + config); } @Override public ServletConfig getServletConfig () { return config; } @Override public void service (ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { String servletName = config.getServletName(); System.out.println("servletName = " + servletName); ServletContext servletContext = config.getServletContext(); System.out.println("servletContext = " +servletContext); Enumeration<String> names = config.getInitParameterNames(); while (names.hasMoreElements()){ String name = names.nextElement(); String value = config.getInitParameter(name); System.out.println(name +"=" + value); } } @Override public String getServletInfo () { return null ; } @Override public void destroy () { System.out.println("销毁SomeServlet实例" ); } }
ServletContext的用法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 import javax.servlet.*;import java.io.IOException;import java.util.Enumeration;public class ServletTest01 implements Servlet { private ServletConfig config; public ServletTest01 () { } @Override public void init (ServletConfig config) throws ServletException { this .config = config; } @Override public ServletConfig getServletConfig () { return config; } @Override public void service (ServletRequest req, ServletResponse res) throws ServletException, IOException { ServletContext sc = config.getServletContext(); Enumeration<String> names = sc.getInitParameterNames(); while (names.hasMoreElements()){ String name = names.nextElement(); String value = sc.getInitParameter(name); System.out.println(name + "=" + value); } sc.setAttribute("email" ,"xhh@460" ); sc.setAttribute("mobile" ,"18881546487" ); String email = (String)sc.getAttribute("email" ); String mobile = (String)sc.getAttribute("mobile" ); System.out.println("email=" +email); System.out.println("mobile=" +mobile); } @Override public String getServletInfo () { return null ; } @Override public void destroy () { } }
< url-pattern/>的设置 (1)精确路径模式:多个url可以访问一个Servlet
(2)通配符路径模式
(3)全路径匹配模式和后缀名匹配模式
但是此处带斜杆的通配符路径模式匹配与后缀名模式不能同时使用,例如/a/*.do等。
urlPattern匹配原则
Servlet核心 GenericServlet 在通过实现Servlet接口来定义Servlet时存在一个很不方便的问题:有太多不需要的方法必须要实现。通常我们只关心service()方法,在service()方法中完成业务逻辑,但由于Servlet接口中还存在另外四个方法,所以必须也要实现。
抽象类实现接口 由于Servlet中通常只使用service()方法,其它四个方法基本不用,但也需要实现。
所以我们就定义一个抽象类,让其实现Servlet接口,并以简单方式对service()以外的其它方法进行实现,即要么是空方法体,要么返回null。
而将service()方法声明为抽象方法。这样以后再定义Servlet时就只需要继承自这个通用的抽象类即可,无需再实现Servlet接口了。
这是一种设计模式,称为”缺省构造器”设计模式。
GenericServlet.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 import javax.servlet.*;import java.io.IOException;import java.util.Enumeration;public abstract class GenericServlet implements Servlet ,ServletConfig { private ServletConfig config; @Override public void init (ServletConfig config) throws ServletException { this .config = config; } @Override public ServletConfig getServletConfig () { return config; } @Override public abstract void service (ServletRequest req, ServletResponse res) throws ServletException, IOException ; @Override public String getServletInfo () { return null ; } @Override public void destroy () { } @Override public String getServletName () { return config.getServletName(); } @Override public ServletContext getServletContext () { return config.getServletContext(); } @Override public String getInitParameter (String name) { return config.getInitParameter(name); } @Override public Enumeration<String> getInitParameterNames () { return config.getInitParameterNames(); } }
GenericServlet中的init方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 import javax.servlet.*;import java.io.IOException;import java.util.Enumeration;public abstract class GenericServlet implements Servlet ,ServletConfig { private ServletConfig config; @Override public void init (ServletConfig config) throws ServletException { this .config = config; this .init(); System.out.println("======父类init======" ); } public void init () { } @Override public ServletConfig getServletConfig () { return config; } @Override public abstract void service (ServletRequest req, ServletResponse res) throws ServletException, IOException ; @Override public String getServletInfo () { return null ; } @Override public void destroy () { } @Override public String getServletName () { return config.getServletName(); } @Override public ServletContext getServletContext () { return config.getServletContext(); } @Override public String getInitParameter (String name) { return config.getInitParameter(name); } @Override public Enumeration<String> getInitParameterNames () { return config.getInitParameterNames(); } }
以上只是探究源码的方式,真正开发直接继承内部GenericServlet类即可。
HttpServlet 自定义HttpServlet.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 import javax.servlet.GenericServlet;import javax.servlet.ServletException;import javax.servlet.ServletRequest;import javax.servlet.ServletResponse;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;public class HttpServlet extends GenericServlet { @Override public void service (ServletRequest req, ServletResponse res) throws ServletException, IOException { System.out.println("LoginServlet" ); HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; service(request,response); } public void service (HttpServletRequest request,HttpServletResponse response) { String method = request.getMethod(); System.out.println("method = " + method); if ("POST" .equals(method)) { doPost(request, response); } else if ("GET" .equals(method)) { doGet(request, response); } } public void doGet (HttpServletRequest request, HttpServletResponse response) { } public void doPost (HttpServletRequest request, HttpServletResponse response) { } }
LoginServlet继承HttpServlet 直接使用即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import javax.servlet.GenericServlet;import javax.servlet.ServletException;import javax.servlet.ServletRequest;import javax.servlet.ServletResponse;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;public class LoginServlet extends HttpServlet { public void doGet (HttpServletRequest request, HttpServletResponse response) { System.out.println("非法用户" ); } public void doPost (HttpServletRequest request, HttpServletResponse response) { System.out.println("进入用户验证身份阶段" ); } }
如果get和post请求都支持的话,使用以下写法: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;public class LoginServlet extends HttpServlet { public void doPost (HttpServletRequest request, HttpServletResponse response) { System.out.println("进入用户验证身份阶段" ); } @Override protected void doGet (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doPost(req, resp); } }
HttpServletRequest 请求的生命周期 HttpServletRequest实例对象是什么时候创建和销毁的呢?
当客户端浏览器将请求(字符序列)发送到服务器后,服务器会根据HTTP请求协议的格式对请求进行解析。同时,服务器会创建 HttpServletRequest的实现类RequestFacade的对象,即请求对象。然后调用相应的set方法,将解析出的数据封装到请求对象中。此时HttpServletRequest实例就创建并初始完毕了,也就是说,请求对象是服务器创建的。
当服务器向客户端发送响应结束后,HttpServletRequest实例对象是被服务器销毁。
一次请求对应一个请求对象,另外一次请求对应着另外一个请求对象,与之前的请求对象没有任何关系。HttpServletRequest实例的生命周期很短暂。
请求参数 HttpServletRequest对于请求中所携带的参数是以Map的形式接收的,并且该Map的key为String,value为String数组。
为很么value为String数组而不是String?因为Http请求协议允许一个请求参数具有多个值的情况出现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 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;import java.util.Enumeration;import java.util.Map;@WebServlet (name = "RegisterServlet" )public class RegisterServlet extends HttpServlet { protected void doPost (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); System.out.println("POST" ); } protected void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String name = request.getParameter("username" ); String ageStr = request.getParameter("age" ); String hobby1 = request.getParameterValues("hobby" )[1 ]; Integer age = Integer.valueOf(ageStr); System.out.println("name = " +name); System.out.println("age = " +age); System.out.println("hobby[1] = " +hobby1); Enumeration<String> names = request.getParameterNames(); while (names.hasMoreElements()){ String eleName = names.nextElement(); String eleValue = request.getParameter(eleName); System.out.println(eleName + "=" +eleValue); } String[] hobby = request.getParameterValues("hobby" ); for (String h :hobby){ System.out.println(h); } Map<String,String[]> map = request.getParameterMap(); for (String key : map.keySet()){ System.out.println(key + "==>" +request.getParameter(key)); } } }
域属性 在Request中也有域属性空间,用于存放有名称的数据。该数据只在当前Request请求中进行访问。
(1)常用方法
对于Request中的域属性操作的方法有:
void setAttribute(String name,Object object):在Request域属性空间中放入数据。其生命周期域与Request的生命周期相同。
Object getAttribute(String name):获取域属性。
SomeServlet.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 import javax.servlet.RequestDispatcher;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 (name = "SomeServlet" )public class SomeServlet extends HttpServlet { protected void doPost (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } protected void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setAttribute("username" ,"小灰灰" ); request.setAttribute("address" ,"YunProvince" ); request.removeAttribute("address" ); request.getRequestDispatcher("/other" ).forward(request,response); } }
OtherServlet.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 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;import java.util.Enumeration;@WebServlet (name = "OtherServlet" )public class OtherServlet extends HttpServlet { protected void doPost (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } protected void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String username = (String)request.getAttribute("username" ); String address = (String)request.getAttribute("address" ); Enumeration<String> names = request.getAttributeNames(); while (names.hasMoreElements()){ String name = names.nextElement(); System.out.println(name+"====" +request.getAttribute(name)); } System.out.println("username = " +username); System.out.println("address = " +address); } }
服务端相关信息 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 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 (name = "SomeServlet" )public class SomeServlet extends HttpServlet { protected void doPost (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } protected void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { StringBuffer requestUrl = request.getRequestURL(); System.out.println("requestUrl =" +requestUrl); String requestURI = request.getRequestURI(); System.out.println("requestURI = " +requestURI); String contextPath = request.getContextPath(); System.out.println("contextPath = " +contextPath); String remoteAddr = request.getRemoteAddr(); System.out.println("clientIp = " +remoteAddr); String servletPath = request.getServletPath(); System.out.println("servletPath = " +servletPath); String pathInfo = request.getPathInfo(); System.out.println("pathInfo = " +pathInfo); } }
中文乱码问题 在浏览器中表单中输入的中文,一旦由浏览器经过Http协议传输,则这些数据均以字节的形式上传给服务器。因为Http协议的底层使用的是TCP传输协议。TCP(Transmission Control Protocol),传输控制协议,是一种面向连接的、可靠的、基于字节流的、端对端的通信协议。在请求中,这些字节均以%开头,并以十六进制的形式出现,如%5A%3D等。
乱码形成的原因:
当用户通过浏览器提交一个包含UTF-8编码格式的两个字的中文的请求时,浏览器会将这两个中文字符变为六个字节(一般一个UTF-8占用三个字节),即形成六个类似%8E的字节表示形式,并将这六个字节上传至Tomcat服务器。
Tomcat服务器在接收到这六个字节之后,并不知道它们原始采用什么字符编码。而Tomcat默认的编码格式为ISO-8859-1。所以会将这六个字节按照ISO-8859-1的格式进行编码,编码后再控制台显示,所以在控制台显示,所以控制台会乱码。
解决POST提交时候的中文乱码问题 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 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 (name = "RegisterServlet" )public class RegisterServlet 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" ); String name = request.getParameter("username" ); String age = request.getParameter("age" ); System.out.println("name = " +name); System.out.println("age = " +age); } }
解决GET和POST提交时候的中文乱码问题(通用版) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 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 (name = "RegisterServlet" )public class RegisterServlet 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 { String name = request.getParameter("username" ); byte [] bytes = name.getBytes("ISO8859-1" ); name = new String(bytes,"UTF-8" ); String age = request.getParameter("age" ); System.out.println("name = " +name); System.out.println("age = " +age); } }
此方法的缺点是:当请求参数一大的时候,就会很浪费内存。
HttpServletResponse Web服务器收到一个Http请求后,会针对每一个请求创建一个HttpServletRequest对象和HttpServletResponse对象。若需要获取客户端提交请求的相关信息,则需要从HttpServletRequest对象中获取;若需要向客户端发送数据,则需要通过HttpServletResponse对象来完成。
向客户端发送数据 ServletResponse接口有一个方法getWriter(),用于获取到一个输出流对象PrintWriter(),该输出流对象是专门用于向客户端浏览器中输出字符数据的,称为标准输出流。
响应乱码的解决方案 若在PrintWriter流中写入中文字符,那么在客户端浏览器中将显示乱码。
解决方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 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;import java.io.PrintWriter;@WebServlet (name = "SomeServlet" )public class SomeServlet extends HttpServlet { protected void doPost (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } protected void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8" ); PrintWriter out = response.getWriter(); out.append("abc" ); out.write("小灰灰" ); out.close(); } }
请求转发与重定向 通过HttpServletRequest获取到的RequestDispathcer对象的forword()方法,可以完成请求转发功能。而通过HttpServletResponse的sendRedirect()方法,可以完成重定向功能。
重定向不保留第一次请求时的数据
请求转发和请求重定向均指的是请求的跳转方式。用户提交请求需要访问服务器的资源1,而资源1又需要访问资源2,那么由资源1到资源2的跳转有两种方式:请求转发与重定向。
请求转发: someServlet.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 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 (name = "SomeServlet" )public class SomeServlet extends HttpServlet { protected void doPost (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } protected void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String name = request.getParameter("username" ); String age = request.getParameter("age" ); System.out.println("name = " +name); System.out.println("age = " +age); request.setAttribute("attrName" ,name); request.setAttribute("attrAge" ,age); request.getRequestDispatcher("/other" ).forward(request,response); } }
otherServlet.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 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;import java.io.PrintWriter;@WebServlet (name = "OtherServlet" )public class OtherServlet extends HttpServlet { protected void doPost (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } protected void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String name = request.getParameter("username" ); String age = request.getParameter("age" ); System.out.println("name === " +name); System.out.println("age === " +age); String attrName = (String)request.getAttribute("attrName" ); String attrAge = (String) request.getAttribute("attrAge" ); System.out.println("attrName = " +attrName); System.out.println("attrAge = " +attrAge); PrintWriter out = response.getWriter(); out.println("this is OtherServlet" ); } }
重定向: SomeServlet.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 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 (name = "SomeServlet" )public class SomeServlet extends HttpServlet { protected void doPost (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } protected void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String name = request.getParameter("username" ); String age = request.getParameter("age" ); System.out.println("name = " +name); System.out.println("age = " +age); request.setAttribute("attrName" ,name); request.setAttribute("attrAge" ,age); response.sendRedirect(request.getContextPath()+"/other" ); } }
OtherServlet.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 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;import java.io.PrintWriter;@WebServlet (name = "OtherServlet" )public class OtherServlet extends HttpServlet { protected void doPost (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } protected void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String attrName = (String)request.getAttribute("attrName" ); String attrAge = (String) request.getAttribute("attrAge" ); System.out.println("attrName = " +attrName); System.out.println("attrAge = " +attrAge); PrintWriter out = response.getWriter(); out.println("this is OtherServlet" ); } }
因为响应的地址已经被重定向了,所以之前的SomeServlet的请求到的数据在OtherServlet中获取不到了。
如果要在重定向后获取得到之前的数据,则采用以下方式:
1 response.sendRedirect("other?pname=xhh&page=20" );
重定向时数据传递的中文乱码问题: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 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;import java.net.URLEncoder;@WebServlet (name = "SomeServlet" )public class SomeServlet extends HttpServlet { protected void doPost (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } protected void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String name = request.getParameter("username" ); String age = request.getParameter("age" ); System.out.println("name = " +name); System.out.println("age = " +age); request.setAttribute("attrName" ,name); request.setAttribute("attrAge" ,age); name = URLEncoder.encode(name,"UTF-8" ); response.sendRedirect("other?pname=" +name+"&page=" +age); } }
重定向到其它应用 重定向与请求转发重要的不同点是:重定向可以跳到其它应用中,而请求转发只能在当前应用中跳转。
请求转发与重定向的对比
请求转发
浏览器只发出一次请求,收到一次响应
请求所转发到的资源中可以直接获取到请求中携带的数据
浏览器地址栏显示的为用户所提交的请求路径
只能跳转到当前应用的资源
重定向
浏览器发出两次请求,接收两次响应
重定向到的资源不能直接获取到用户提交请求中携带的数据
浏览器地址栏显示的为重定向的请求路径,而非用户提交请求的路径。所以重定向可以防止表单重复提交
重定向不仅可以跳转到当前应用的其它资源,也可以跳转到其它应用的资源
请求转发与重定向的选择
如果要跳转到其它应用,则使用重定向。
如果是处理表单数据的Servlet要跳转到其它Servlet,则需要选择重定向。为了防止表单的重复提交。
若对某一请求进行处理的Servlet的执行需要消耗大量的服务器资源,此时Servlet执行完毕后,也需要重定向。
其它情况,一般使用请求转发。
RequestDispatcher RequestDispatcher接口有两个方法:forward()和include()。
include的转发模式:
requestDispatcher的forward()和include()的区别 forward()和include()的区别:主要表现在标准输出流的开启时间不同。
forward():表示转发,当前的请求转发给了另一个组件,响应结果由另一个组件响应。所以服务器不会在此组件内打开标准输出流。所以在当前组件中写入到流的数据是不会写入到浏览器中。
include():表示包含,说明另一个组件的请求结果将会响应到当前组件之中,并跟当前组件中的流一起输出(写入到浏览器)。
show me code:
SomeServlet.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 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;import java.io.PrintWriter;@WebServlet (name = "SomeServlet" )public class SomeServlet extends HttpServlet { protected void doPost (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } protected void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { PrintWriter out = response.getWriter(); out.println("SomeServlet:include() before" ); request.getRequestDispatcher("/other" ).include(request,response); out.println("SomeServlet:include() after" ); } }
OtherServlet.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 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;import java.io.PrintWriter;@WebServlet (name = "OtherServlet" )public class OtherServlet extends HttpServlet { protected void doPost (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } protected void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { PrintWriter out = response.getWriter(); out.println("OtherServlet:include()" ); } }
输出结果:
Servlet线程安全问题 Servlet是在单例多线程环境下运行的。其运行可能出现线程安全问题。
什么时候会出现线程安全问题?
当多个线程同时修改同一个共享数据的时候,后修改的数据会将前修改的数据覆盖,对数据先进行修改的用户读取到的不是自己修改后的数据,这就是线程安全问题。
JVM中可能存在线程安全问题的数据分析 A、栈内存数据分析 栈内存是多例的,即JVM会为每个线程创建一个栈,所以其中的数据是不能共享的。另外,方法中的局部变量存放在Stack的栈帧的,方法执行完毕,栈帧弹栈,局部变量消失。局部变量是局部的,不能共享的。所以栈内存中的数据不存在线程安全问题。
B、堆内存数据分析 一个JVM中只存在一个堆内存,堆内存是共享的。被创建出来的对象是存放在堆内存的,而存放在堆内存中的对象,实际就是对象成员变量的值的集合。即成员变量是存放在堆内存的。堆内存中的数据是多线程共享的,也就是说,堆内存中的数据是存在线程安全问题的。
C、方法区数据分析 一个JVM中只存在一个方法区。静态变量与常量存放在方法区,方法区是多线程共享的。常量是不能被修改的量,所以常量不存在线程安全的问题。静态变量是多线程共享的。所以静态变量存在线程安全问题。
线程安全的解决方案:
对于一般的类,不要定义为单例的。除非项目有特殊需求,或该类对象属于重量级对象。所谓重量级对象是指,创建该类对象时需要占用较大的系统资源。
无论类是否为单例,尽量不要使用静态变量。
若需要定义为单例类,则单例类中尽量不要使用成员变量。
若单例类中必须要使用成员变量,则对成员变量的操作,可以添加串行化synchronized,实现线程同步。不过,最好不要使用线程同步机制。因为一旦操作串行化的排队状态,将大大降低程序的执行效率。
Servlet是单例多线程并发访问的,所以其就可能会出现线程安全问题。为了避免线程安全问题的产生,对于Servlet的使用,一般是不声明成员变量的,若要真的声明成员变量,则只能通过线程同步机制synchronized避免。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 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;import java.io.PrintWriter;@WebServlet (name = "LoginServlet" )public class LoginServlet extends HttpServlet { private String username; protected void doPost (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { username = request.getParameter("username" ); System.out.println("username = " +username); PrintWriter out = response.getWriter(); out.println("username = " +username); } protected void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } }
以上代码如果进行多个http请求就会出现线程安全问题,username的值就会被第二个请求覆盖。
解决方法是在代码块中加入synchronized。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 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;import java.io.PrintWriter;@WebServlet (name = "LoginServlet" )public class LoginServlet extends HttpServlet { private String username; protected void doPost (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { synchronized (this ){ username = request.getParameter("username" ); System.out.println("username = " +username); PrintWriter out = response.getWriter(); out.println("username = " +username); } } protected void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } }