文章目录

浏览器与服务器交互方式分析

原理示意图:

访问地址URL的构成:

则访问该资源的查找过程是:

①根据主机地址找到一个具体的服务器
②根据Web应用虚拟路径找到Web应用
③根据Web应用下资源的虚拟路径访问具体资源
[1]如果资源是静态的,那么Tomcat会返回资源本身
注:静态资源也需要使用到servlet,只不过是Tomcat里面已经定义好了一个 DefaultServlet
[2]如果资源是动态的,例如Servlet,那么Tomcat会先执行Servlet程序,返回Servlet程序的运行结果
④无论访问的目标资源是不是静态的,浏览器最终得到的都是静态数据:图片或字符串。

什么是Servlet

Servlet是一个java程序,运行在我们的web服务器上,用于接收和响应客户端的http请求。

Servlet规范是Sun公司制定的一套技术标准,包含与Web应用相关的一系列接口,是Web应用实现方式的宏观解决方案。
Servlet是服务器端的一个组件,Servlet的实例对象由Servlet容器负责创建;Servlet的方法由容器在特定情况下调用;Servlet容器会在Web应用卸载时销毁Servlet对象的实例。

Servlet的应用:通过网页驱动服务器端的Java程序,在网页上显示Java程序返回的数据。

如何使用Servlet

1.需要有一个服务器,即搭建Web开发环境,然后创建动态Web工程。
2.创建javax.servlet.Servlet接口的实现类:

import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class HelloServlet implements Servlet {
	@Override
	public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
		//在控制台输出,证明方法是否调用
		System.out.println("HelloServlet service");
		//向浏览器发送响应消息
		PrintWriter writer=res.getWriter();
		writer.write("HelloServlet Response");
		writer.close();
	}
	@Override
	public void init(ServletConfig config) throws ServletException {
	}
	@Override
	public ServletConfig getServletConfig() {
		return null;
	}
	@Override
	public String getServletInfo() {
		return null;
	}
	@Override
	public void destroy() {
	}
}

3.注册配置Servlet :告诉服务器,我们的应用有这个servlet。
在webContent/WEB-INF/web.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">
  <display-name>01_ServletTest</display-name>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>
  
  <!-- 声明一个Servlet,向tomcat声明, 这个应用里面有这个servlet, 名字叫做HelloServlet , 具体的路径是com.mycode01.servlet.HelloServlet-->
  <servlet>
  	<!-- 为Servlet指定一个名称,以便于引用 -->
    <servlet-name>HelloServlet</servlet-name>
    <!-- 注册Servlet实现类的全类名 -->
    <servlet-class>com.mycode01.servlet.HelloServlet</servlet-class>
  </servlet>
  
   	<!-- 建立一个从虚拟路径到Servlet之间的映射关系 -->
  <servlet-mapping>
 	<!-- 引用Servlet名称 -->
    <servlet-name>HelloServlet</servlet-name>
    <!-- 映射到Servlet的虚拟路径:“/HelloServlet” -->
    <url-pattern>/ServletTest</url-pattern>
  </servlet-mapping>
  
</web-app>

4.在WebContent目录下创建index.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
		<a href="ServletTest">通过网页驱动服务器端的Java程序,在网页上显示Java程序返回的数据</a>
		<!-- href="ServletTest"  对应<servlet-mapping><url-pattern>的值 -->
</body>
</html>

5.把web 应用程序部署到服务器,点击超链接(通过网页驱动服务器端的Java程序,在网页上显示Java程序返回的数据)测试Servlet

Servlet执行过程分析

通过上面的测试分析

URL地址(请求地址):http://localhost:8080/01_ServletTest/ServletTest

Servlet接口的实现类

Servlet的配置

执行过程:

1.找到Tomcat应用
2.找到项目(01_ServletTest)
3.找web.xml, 查找是否有url-pattern的内容是/ServletTest
4. 如果有url-pattern的内容是/ServletTest,便找到对应servlet-mapping中的那个servlet-name(HelloServlet)
5.再找到servlet元素中的servlet-name(HelloServlet),找到下面定义了的servlet-class,然后开始创建该类的对象,执行该类的service方法

Servlet技术体系


1.servlet接口

2.GenericServlet抽象类(实现servlet接口)

对Servlet功能进行了封装和完善,将service(ServletRequest req, ServletResponse res)保留为抽象方法,让使用者仅关心业务实现即可。


3.HttpServlet抽象类(继承GenericServlet):用于处理http的请求

对GenericServlet进行了进一步的封装和扩展,更贴近HTTP协议下的应用程序编写,在service(ServletRequest req, ServletResponse res)方法中,根据不同HTTP请求类型调用专门的方法进行处理。
Servlet的通用写法:实际使用中继承HttpServlet抽象类创建自己的Servlet实现类即可。重写doGet(HttpServletRequest req, HttpServletResponse resp)和doPost(HttpServletRequest req, HttpServletResponse resp)方法实现请求处理,不再需要重写service(ServletRequest req, ServletResponse res)方法。

Servlet的通用写法

定义一个类,继承HttpServlet 重写doGet 和 doPost,注册配置该Servlet。

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/** * Servlet implementation class HelloHttpServlet */
public class HelloHttpServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
       
    /** * @see HttpServlet#HttpServlet() */
    public HelloHttpServlet() {
        super();
    }

	/** * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		response.getWriter().append("Served at: ").append(request.getContextPath());
	}

	/** * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) */
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doGet(request, response);
	}
}

Servlet配置说明

1.web3.0以前的版本,Servlet的配置使用web.xml

2.web3.0以上版本,Servlet的配置使用的是注解@WebServlet
如: 创建Servlet2,在@WebServlet注解中添加urlPatterns = “/test”,作为请求路径。

对以上接口的说明:

ServletConfig接口:封装了Servlet配置信息
ServletContext接口:封装了当前Web应用上下文信息
HttpServletRequest接口:封装了HTTP请求信息,ServletRequest的子接口
HttpServletResponse接口:封装了HTTP响应信息,ServletResponse的子接口

ServletConfig接口

ServletConfig接口封装了Servlet配置信息,当前Web应用的ServletContext对象也封装到了ServletConfig对象中,使ServletConfig对象可以获取ServletContext对象。

ServletConfig对象的主要功能:

1.获取Servlet名称
2.获取Servlet初始化参数
3.获取ServletContext对象
代码示例

Servlet的配置:

  <servlet>
    <servlet-name>HelloHttpServlet</servlet-name>
    <servlet-class>com.mycode01.servlet.HelloHttpServlet</servlet-class>
    
    <!-- 配置Servlet初始化参数 -->
    <init-param>
  		<param-name>MyName</param-name>
    	<param-value>MyValue</param-value>
    </init-param>
    
     <init-param>
  		<param-name>age</param-name>
    	<param-value>18</param-value>
    </init-param>
    
  </servlet>
  <servlet-mapping>
    <servlet-name>HelloHttpServlet</servlet-name>
    <url-pattern>/HttpServletTest</url-pattern>
  </servlet-mapping>

测试ServletConfig方法:

public class HelloHttpServlet extends HttpServlet {
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		//得到servletConfig对象
		ServletConfig servletConfig = getServletConfig();
		
		//获取配置servlet里面servlet-name的文本内容
		String servletName = servletConfig.getServletName();
		System.out.println("servletName="+servletName);
		
		System.out.println("------------------------------------");
		
		//获取具体的某一个参数。 
		String age = servletConfig.getInitParameter("age");
		System.out.println("age="+age);
		
		System.out.println("------------------------------------");
		
		//3.获取所有的参数名称
		Enumeration<String> names = servletConfig.getInitParameterNames();
		//遍历取出所有的参数名称
		while (names.hasMoreElements()) {
			String key = (String) names.nextElement();
			String value = servletConfig.getInitParameter(key);
			System.out.println("key==="+key + " value="+value);
		}
	}
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doGet(request, response);
	}
}

运行结果:

扩展:ServletConfig的另一个作用

我们自己开发一些应用,使用到了一些技术,或者一些代码,我们不会。 但是有人写出来了。它的代码放置在了自己的servlet类里面。
刚好这个servlet 里面需要一个数字或者叫做变量值。 但是这个值不能是固定了。 所以要求使用到这个servlet的公司,在注册servlet的时候,必须要在web.xml里面,声明init-params

Servlet的配置:

  <servlet>
    <servlet-name>ServletConfigTest</servlet-name>
    <servlet-class>com.mycode01.servlet.ServletConfigTest</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>ServletConfigTest</servlet-name>
    <url-pattern>/ServletConfigTest</url-pattern>
  </servlet-mapping>

ServletConfigTest 类:

public class ServletConfigTest extends HttpServlet {
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		ServletConfig config = getServletConfig();	
		//获取number这个参数。
		String  number = config.getInitParameter("number");		
		if(number == null){
			throw new IllegalArgumentException("servlet在配置的时候,要写number这个参数");
		}else{		
			System.out.println("number....");
		}	
	}
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doGet(request, response);
	}

}

运行访问:http://localhost:8080/01_ServletTest/ServletConfigTest

ServletContext接口

一个Web应用程序中的所有Servlet都共享同一个ServletContext对象,所以ServletContext对象也被称为 application 对象(Web应用程序对象)。
在应用程序中能够获取运行环境或容器信息的对象通常称之为“上下文对象”。

ServletContext对象的主要功能:

1.获取Web应用程序的全局配置参数
  • 设置Web应用初始化参数(在web.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">
  <display-name>01_ServletTest</display-name>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>
  
  <!-- 配置全局参数 -->
  <context-param>
  	<param-name>num</param-name>
  	<param-value>123</param-value>
  </context-param>

    <servlet>
    <servlet-name>ServletContextTest</servlet-name>
    <servlet-class>com.mycode01.servlet.ServletContextTest</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>ServletContextTest</servlet-name>
    <url-pattern>/ServletContextTest</url-pattern>
  </servlet-mapping>
  
</web-app>
  • 获取Web应用初始化参数
public class ServletContextTest extends HttpServlet {
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		//获取ServletContext对象
		ServletContext servletContext = getServletContext();
		//获取Web应用初始化参数
		String num = servletContext.getInitParameter("num");
		//在控制台输出
		System.out.println("num="+num);	
	}
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doGet(request, response);
	}
}

访问:http://localhost:8080/01_ServletTest/ServletContextTest

2.获取虚拟路径所映射的本地路径(在服务器上的绝对路径)

虚拟路径:浏览器访问Web应用中资源时所使用的路径。
本地路径:资源在文件系统中的实际保存路径。即在服务器上的绝对路径

public class ServletContextTest2 extends HttpServlet {
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		//1.获取ServletContext对象
		ServletContext servletContext = getServletContext();
		//2.0获取index.html的本地路径(在服务器上的绝对路径)
				//index.html的虚拟路径是“/index.html”,其中“/”表示当前Web应用的根目录,即WebContent目录
// String realPath = servletContext.getRealPath("/index.html");
// System.out.println("realPath="+realPath);
		// realPath=D:\tomcat\tomcat01\apache-tomcat-8.5.50\wtpwebapps\01_ServletTest\index.html
		
		//2.1获取ServletContextTest的本地路径 
		String realPath = servletContext.getRealPath("/ServletContextTest");
		System.out.println("realPath="+realPath);
		// realPath=D:\tomcat\tomcat01\apache-tomcat-8.5.50\wtpwebapps\01_ServletTest\ServletContextTest
			
	}
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doGet(request, response);
	}
}


扩展1: 使用ServletContext 获取Web应用程序中的文件资源

Web工程下的资源,通过一般路径和FileInputStream是获取不到文件的,因为Web工程jre是由tomcat管理的,而文件资源的相对路径是根据jre来确定的。

如要获取以下资源文件

代码实现(方式一):servletContext.getRealPath(“file/cofig.properties”);

public class ServletContextTest3 extends HttpServlet {
	@SuppressWarnings("resource")
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		//获取ServletContext对象
		ServletContext servletContext = getServletContext();
		
		//获取项目的根路径
		String itemRootPath = servletContext.getRealPath("");
		System.out.println("itemRootPath="+itemRootPath);
// itemRootPath=D:\tomcat\tomcat01\apache-tomcat-8.5.50\wtpwebapps\01_ServletTest\
		
		System.out.println("-----------------------------");
		
		
		//获取cofig.properties文件的本地路径 (在服务器上的绝对路径)
		String realPath = servletContext.getRealPath("file/cofig.properties");
		System.out.println("realPath="+realPath);
// realPath=D:\tomcat\tomcat01\apache-tomcat-8.5.50\wtpwebapps\01_ServletTest\file\cofig.properties
		
		
		System.out.println("-----------------------------");
	
		//创建属性对象
		java.util.Properties properties = new java.util.Properties();
		InputStream fileInputStream = new FileInputStream(realPath);
		properties.load(fileInputStream);
		
		//获取key属性的值
		String value = properties.getProperty("key");
		//在控制台输出
		System.out.println("value="+value);
// value=value
		
	}
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doGet(request, response);
	}
}

访问http://localhost:8080/01_ServletTest/ServletContextTest3后控制台输出:

代码实现(方式二):servletContext.getResourceAsStream(“file/cofig.properties”);

public class ServletContextTest4 extends HttpServlet {
	@SuppressWarnings("resource")
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		//获取ServletContext对象
		ServletContext servletContext = getServletContext();
	
		//创建属性对象
		java.util.Properties properties = new java.util.Properties();
		//根据web工程下的文件资源路径,得到一个流对象
		//现在(ServletContextTest4 )所处路径是项目的根路径:D:\tomcat\tomcat01\apache-tomcat-8.5.50\wtpwebapps\01_ServletTest; 所以getResourceAsStream的参数路径可以是一个相对路径
		InputStream fileInputStream =servletContext.getResourceAsStream("file/cofig.properties");
		properties.load(fileInputStream);
		
		//获取key属性的值
		String value = properties.getProperty("key");
		//在控制台输出
		System.out.println("value="+value);
// value=value
		
	}
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doGet(request, response);
	}
}
扩展2:使用ClassLoader获取Web应用程序中的文件资源
public class ServletContextTest5 extends HttpServlet {
	
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		//创建属性对象
		java.util.Properties properties = new java.util.Properties();
		//this.getClass() 获取该java文件的class getClassLoader() 获取加载这个class到虚拟机中的类加载器对象
		InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("../../file/cofig.properties");
		//关于路径问题:
		/* * 使用ClassLoader的根路径:D:\tomcat\tomcat01\apache-tomcat-8.5.50\wtpwebapps\01_ServletTest\WEB-INF\classes * cofig.properties的路径:D:\tomcat\tomcat01\apache-tomcat-8.5.50\wtpwebapps\01_ServletTest\file\cofig.properties * * getResourceAsStream("../../file/cofig.properties");参数使用的是相对路径 * ../../ 返回上上层到 D:\tomcat\tomcat01\apache-tomcat-8.5.50\wtpwebapps\01_ServletTest * ../../file/cofig.properties 实质是 D:\tomcat\tomcat01\apache-tomcat-8.5.50\wtpwebapps\01_ServletTest\file\cofig.properties */
		
		properties.load(resourceAsStream);
		
		//获取key属性的值
		String value = properties.getProperty("key");
		//在控制台输出
		System.out.println("value="+value);
// value=value
		
		resourceAsStream.close();
		
	}
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doGet(request, response);
	}
}
3.存储数据,servlet间共享数据

一个案例:定义一个login.html登录界面,点击登录,通过网页驱动服务器端的Java程序,执行LoginServlet判断是否登录成功,通过ServletContext 记录登录成功次数并跳转到login_success.html界面,点击login_success.html界面的超链接执行CountServlet通过ServletContext 获取登录的次数,显示到浏览器页面上。体现了LoginServlet与CountServlet共享同一个ServletContext对象。

1).login.html登录界面

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<!-- 关于 action="LoginServlet" 路径问题
	LoginServlet的访问路径:http://localhost:8080/01_ServletTest/LoginServlet
	login.html的访问路径:http://localhost:8080/01_ServletTest/login.html
	故 action的路径可以写相对路径LoginServlet
	 -->
	<form action="LoginServlet" method="get">
	用户名:<input type="text" name="user">
	密码:<input type="text" name="password">
	<input type="submit" value="登录">
	</form>
</body>
</html>

2).点击登录执行LoginServlet

public class LoginServlet extends HttpServlet {
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //如果是post请求,设置request对象使用UTF-8字符集对请求体中的内容进行解码,这样如果提交过来的用户名等是中文,则不会乱码
		request.setCharacterEncoding("UTF-8");

		//获取请求数据
		String user = request.getParameter("user");
		String password = request.getParameter("password");
		System.out.println(user+" "+password);
	
		//1.指定数据输出到客户端时,使用UTF-8编码
		response.setCharacterEncoding("UTF-8");
		//2.规定浏览器解码数据时使用什么编码(因为这里服务器使用UTF-8编码,所以浏览器解码规定使用UTF-8)
		response.setHeader("Content-Type","text/html;charset=utf-8");
		
		PrintWriter writer = response.getWriter();
		
		if("乘零".equals(user)&&"1".equals(password)){
			//登录成功,记录登录次数并跳转到login_success.html页面
			
			//1.记录登录次数
			//返回具有给定名称的servlet容器属性,如果没有该名称的属性,则返回空值。
			Object attribute = getServletContext().getAttribute("count");
			int totalCount=0;
			if(attribute!=null){
				totalCount=(int) attribute;
			}
			getServletContext().setAttribute("count", totalCount+1);
			
			//2.跳转到login_success.html页面
			//设置状态码,重新定位
			response.setStatus(302);
			//定位跳转的位置是那个界面 
			response.setHeader("Location", "login_success.html");
			
			//跳转到百度
// response.setHeader("Location", "https://www.baidu.com");
			
		}else{
			writer.write("登录失败");
		}
	}
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doGet(request, response);
	}
}

3).执行LoginServlet定位跳转到login_success.html界面

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<a href="CountServlet">获取登录总数</a>
</body>
</html>

4).点击login_success.html界面的超链接执行ServletContext 获取登录的次数

public class CountServlet extends HttpServlet {
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		//设置响应数据类型是html文本,并告诉浏览器使用UTF-8解码
		response.setContentType("text/html;charset=utf-8");
		int count = (int) getServletContext().getAttribute("count");
		PrintWriter writer = response.getWriter();
		writer.write("成功登录"+count+"次");
	}
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doGet(request, response);
	}
}

HttpServletRequest接口

该接口是ServletRequest接口的子接口,封装了HTTP请求的相关信息,由Servlet容器创建其实现类对象并传入service(ServletRequest req, ServletResponse res)方法中。以下所说的HttpServletRequest对象指的是容器提供的HttpServletRequest实现类对象。

HttpServletRequest对象的主要功能:

1. 获取请求参数
	//获取客户端提交上来的数据( 获取单值的表单参数),如果没有该名称的属性,则返回空值。
	String user = request.getParameter("user");
	
	//获取多值的表单参数
	String[] values = request.getParameterValues(String name);
	
	//获取所有的表单参数
		//map的key:是表单项的name map的value:是表单项的值
	Map<String, String[]> map = request.getParameterMap();
	
	//获取所有参数,得到一个枚举集合
	Enumeration<String> parameterNames = request.getParameterNames();
2.request是一个域对象,在请求域中存取数据
//域对象:由Servlet规范提供的,可以临时存取数据的对象。域对象里的数据,在其作用范围里共享.

//request域对象
//何时创建:一次请求开始
//何时销毁:一次请求结束
//作用范围:一次请求中

//把所有学生数据存储到作用域中
request.setAttribute("allStudent", allStudent);
3.将请求转发给另外一个URL地址(见请求的转发)
4.获取 服务器IP,服务器软件端口,项目虚拟路径(web项目的context路径)
服务器IP
 String serverName = request.getServerName();
 端口
 int serverPort = request.getServerPort();
                  
 String contextPath=request.getContextPath()
 
//以访问路径为:http://localhost:8080/test/index.html为例
//request.getServerName() 得到的就是 localhost
// request.getServerPort() 得到的就是 8080
//request.getContextPath()得到的就是 /test
5.获取头信息
public class HttpServletRequestTest extends HttpServlet {
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
	
		//获取请求数据所有头信息得到一个枚举集合
		Enumeration<String> headerNames = request.getHeaderNames();
		while (headerNames.hasMoreElements()) {
			String name = (String) headerNames.nextElement();
			//返回指定请求头的值。如果请求没有包含指定名称的头,则此方法返回空值。如果有多个同名的头,则此方法返回请求中的第一个头.
			String header = request.getHeader(name);
			System.out.println(name+"="+header);
		}
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doGet(request, response);
	}
}

访问:http://localhost:8080/01_ServletTest/HttpServletRequestTest

HttpServletRequest中文乱码问题(请求参数中含有中文)

请求由浏览器发送给服务器

  • 浏览器编码:浏览器使用HTML页面指定的字符集编码,只需要将HTML页面的字符集指定为UTF-8即可
  • 服务器解码:默认字符集是IOS-8859-1

解决方案:

  • 对于GET请求:GET请求是在URL地址栏中传递请求参数的(如通过UTF-8编码),它会被Tomcat服务器自动解码,而Tomcat服务器默认的字符集也是ISO-8859-1,所以我们需要修改Tomcat服务器的字符集为UTF-8。

方案一:由于请求参数是包含在请求行内的,无法通过request对象进行设置,所以需要在Server.xml文件中修改Connector标签,添加URIEncoding="utf-8"属性,则get请求过来的数据Tomact都是使用UTF-8解码。

方案二:如果是将请求参数输出到控制台,则可不修改Connector标签,在该Servlet中执行以下方法再输出

String name="中文";
//使用ISO-8859-1将此字符串编码为一个字节序列,再通过指定的字符集解码指定的字节数组来构造新字符串
String name=new String(name.getBytes("ISO-8859-1"),"UTF-8");
System.out.println(name);
  • 对于POST请求:post请求在Servlet中解码,默认的字符集是ISO-8859-1,不支持中文,所以我们需要修改Servlet的字符集为UTF-8。使用request.setCharacterEncoding(“UTF-8”);设置request对象使用UTF-8字符集对请求体中的内容进行解码。但需要注意的是,这个操作一定要在调用getParameter()方法之前进行。

使用情况:如解决登录时post请求用户名为中文的问题。

HttpServletResponse接口

该接口是ServletResponse接口的子接口,封装了HTTP响应的相关信息,由Servlet容器创建其实现类对象并传入service(ServletRequest req, ServletResponse res)方法中。以下所说的HttpServletResponse对象指的是容器提供的HttpServletResponse实现类对象。

HttpServletResponse对象的主要功能

1.浏览器输出数据(设置响应体)
//以字符流写数据
	response.getWriter().write("<h3>可以写html</h3>");
//以字节流写数据
	response.getOutputStream().write("字节流".getBytes());
2.实现请求重定向(见请求的重定向)。
3.案例:HttpServletResponse下载文件
  • 新建一个download.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<!-- 点击链接时,执行DownloadServlet,手动编码提供下载 -->
	<a href="DownloadServlet?filename=logo.png">执行DownloadServlet,手动编码提供下载logo.png</a> <br>
	<!--点击链接时,执行DefaultServlet(tomcat默认的Servlet,专门用于处理服务器上的静态资源),在页面展示文件 -->
	<a href="file/logo.png">执行DefaultServlet(tomcat默认的Servlet,专门用于处理服务器上的静态资源)展示logo.png</a>
</body>
</html>

访问download.html,点击第一个链接,执行DownloadServlet

  • DownloadServlet.java
public class DownloadServlet extends HttpServlet {
	@SuppressWarnings("resource")
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		//获取客户端提交上来的数据(logo.png)
		String filename = request.getParameter("filename");
		
		//用户访问该资源时,使浏览器以下载的方式提醒用户,而不是直接展示,filename为下载弹框中文件的名称
		response.setHeader("Content-Disposition", "attachment;filename="+filename);
		
		//获取logo.png文件的本地路径 (在服务器上的绝对路径)
		String realPath = getServletContext().getRealPath("file/"+filename);
		//转化为输入流
		InputStream inputStream = new FileInputStream(realPath);
		
		//获取filename的输入流
		//InputStream is = context.getResourceAsStream("WEB-INF/file/" + filename);
		
		ServletOutputStream outputStream = response.getOutputStream();
		
		int len=0;
		byte[] buffer=new byte[1024];
		while((len=inputStream.read(buffer))!=-1){
			outputStream.write(buffer,0,len);
		}
		inputStream.close();
		outputStream.close();
	}
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doGet(request, response);
	}
}

浏览器以下载的方式提醒用户

3.1 下载时文件名称为中文的处理


index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<!-- 点击链接时,执行DownloadServlet,手动编码提供下载 -->
<a href="DownloadServlet?filename=01.jpg">01.jpg</a> <br>
</body>
</html>

DownloadServlet(不做处理)

@WebServlet(name = "DownloadServlet",urlPatterns = "/DownloadServlet")
public class DownloadServlet extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        //获取客户端提交上来的数据(01.jpg)
        String filename = request.getParameter("filename");

        //用户访问该资源时,使浏览器以下载的方式提醒用户,而不是直接展示
        response.setHeader("Content-Disposition", "attachment;filename=图片.jpg");
// response.setHeader("Content-Disposition", "attachment;filename="+DownloadUtils.encodeFilename(request, "图片"));
   

        //获取logo.png文件的本地路径 (在服务器上的绝对路径)
        String realPath = getServletContext().getRealPath("img/"+filename);

        //转化为输入流
        InputStream inputStream = new FileInputStream(realPath);

        ServletOutputStream outputStream = response.getOutputStream();

        int len=0;
        byte[] buffer=new byte[1024];
        while((len=inputStream.read(buffer))!=-1){
            outputStream.write(buffer,0,len);
        }
        inputStream.close();
        outputStream.close();
    }
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
}


DownloadServlet(使用工具类DownloadUtils处理)

@WebServlet(name = "DownloadServlet",urlPatterns = "/DownloadServlet")
public class DownloadServlet extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        //获取客户端提交上来的数据(logo.png)
        String filename = request.getParameter("filename");

        //用户访问该资源时,使浏览器以下载的方式提醒用户,而不是直接展示,filename文件名称为中文
        	//编码:response编码响应头(ISO-8859-1进行编码); 解码:浏览器解码,不同浏览器解码方式不同,而且不能更改
       		//在服务端文件名称是中文,则把中文转换成不带中文的字符串,到客户端由浏览还原成中文的字符串。
        response.setHeader("Content-Disposition", "attachment;filename="+DownloadUtils.encodeFilename(request, "图片.jpg"));
// response.setHeader("Content-Disposition", "attachment;filename=图片");

        //获取logo.png文件的本地路径 (在服务器上的绝对路径)
        String realPath = getServletContext().getRealPath("img/"+filename);

        //转化为输入流
        InputStream inputStream = new FileInputStream(realPath);

        ServletOutputStream outputStream = response.getOutputStream();

        int len=0;
        byte[] buffer=new byte[1024];
        while((len=inputStream.read(buffer))!=-1){
            outputStream.write(buffer,0,len);
        }
        inputStream.close();
        outputStream.close();
    }
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
}

DownloadUtils(中文文件名称乱码解决的工具类)——来源:网络

public class DownloadUtils {
	/** * 解决下载时,中文文件名称乱码问题<br> * * @param request request对象 * @param filename 你想让用户保存的文件名称 */
	public static String encodeFilename(HttpServletRequest request, String filename){ 
		try{
			String agent = request.getHeader("User-Agent");
			if (agent.contains("MSIE")) {
				// IE浏览器
				filename = URLEncoder.encode(filename, "utf-8");
				filename = filename.replace("+", " ");
			} else if (agent.contains("Firefox")) {
				// 火狐浏览器
				//BASE64Encoder base64Encoder = new BASE64Encoder();
				Base64.Encoder base64Encoder = Base64.getEncoder();
				filename = base64Encoder.encodeToString(filename.getBytes("utf-8"));
				filename = "=?utf-8?B?"+ filename + "?=";
			} else {
				// 其它浏览器
				filename = URLEncoder.encode(filename, "utf-8");				
			}
		}catch(Exception e){
			e.printStackTrace();
		}

		return filename;
	}
}

(代解决:原本是中文名称资源的如何下载)

HttpServletResponse中文乱码问题(服务器响应中含有中文)

响应由服务器发送给浏览器

  • 服务器编码:默认使用ISO-8859-1进行编码
  • 浏览器解码:默认使用GBK进行解码

解决方案

需要在在response.getWriter() , response.getOutputStream()之前进行设置

方案一:指定服务器编码方式,设置响应头

		//1.指定数据输出到客户端时,使用UTF-8编码
		response.setCharacterEncoding("UTF-8");
		//2.规定浏览器解码数据时使用什么编码(因为这里服务器使用UTF-8编码,所以浏览器解码规定使用UTF-8)
		response.setHeader("Content-Type","text/html;charset=utf-8");

方案二:设置响应数据类型

//设置响应数据类型是html文本,并告诉浏览器使用UTF-8解码
response.setContentType("text/html;charset=utf-8");

注:默认的getOutputStream输出时是使用UTF-8码表。

请求的转发与重定向

请求的转发与重定向是Servlet控制页面跳转的主要方法。

请求的转发

1.Servlet接收到浏览器端请求后,进行一定的处理,先不进行响应,而是在服务器端内部“转发”给其他Servlet程序继续处理。在这种情况下浏览器端只发出了一次请求,浏览器地址栏不会发生变化,用户也感知不到请求被转发了。
2.转发请求的Servlet和目标Servlet共享同一个request对象。

代码示例:

protected void doGet(HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {
		//1.使用RequestDispatcher对象封装目标资源的虚拟路径
		RequestDispatcher dispatcher=request.getRequestDispatcher("/index.html");
		//2.调用RequestDispatcher对象的forward()方法“前往”目标资源
		//注意:传入的参数必须是传递给当前Servlet的service方法的那两个ServletRequest和ServletResponse对象
		dispatcher.forward(request, response);
	}

请求的重定向

1.Servlet接收到浏览器端请求并处理完成后,给浏览器端一个特殊的响应,这个特殊的响应要求浏览器去请求一个新的资源,整个过程中浏览器端会发出两次请求,且浏览器地址栏会改变为新资源的地址。
2.重定向的情况下,原Servlet和目标资源之间就不能共享请求域数据了( 后续的请求,不能使用上一次request存储的数据,因为这是两次不同的请求。)。

代码示例:

protected void doGet(HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {
		//1.调用HttpServletResponse对象的sendRedirect()方法
		//2.传入的参数是目标资源的虚拟路径
		response.sendRedirect("index.html");


		//扩展:和重定向效果相同的写法:
			//设置状态码,重新定位
			response.setStatus(302);
			//定位跳转的位置是那个界面 
			response.setHeader("Location", "index.html");
	}

请求的转发与重定向示意图:

对比请求的转发和重定向 转发 重定向
浏览器地址栏 不改变 改变
发送请求次数 1 2
能否共享request对象数据
目标资源:WEB-INF下的资源 能访问 不能访问
跳转路径 自己项目的资源路径 可跳转到任意路径

如果有数据要传递给下一个资源,就要使用请求转发;否则使用重定向。

Servlet生命周期

0)Servlet对象的创建

默认情况下,Servlet容器第一次收到HTTP请求时创建对应Servlet对象。容器之所以能做到这一点是由于我们在注册Servlet时提供了全类名,容器使用反射技术创建了Servlet的对象。

1)Servlet对象初始化(init方法)

Servlet容器创建Servlet对象之后,会调用init(ServletConfig config)方法,对其进行初始化。在javax.servlet.Servlet接口中,public void init(ServletConfig config)方法要求容器将ServletConfig的实例对象传入,这也是我们获取ServletConfig的实例对象的根本方法。

2)处理请求(service方法)

在javax.servlet.Servlet接口中,定义了service(ServletRequest req, ServletResponse res)方法处理HTTP请求,同时要求容器将ServletRequest对象和ServletResponse对象传入。

3)Servlet对象销毁(destroy方法)

服务器重启或服务器停止执行时会销毁Servlet对象,而销毁之前为了执行一些诸如释放缓存、关闭连接、保存数据等操作,所以设计了public void destroy()方法。

扩展:让Servlet 提前创建实例

默认情况下,Servlet容器创建Servlet对象之后,才会执行init方法。 有的时候可能需要在这个方法里面执行一些初始化工作,做一些比较耗时的逻辑。 那么我们可以让这个初始化的操作提前。

在注册Servlet的时候, 使用load-on-startup元素来指定, 给定的数字越小,启动的时机就越早。 一般不写负数, 从2开始即可。

	<servlet>
	  	<servlet-name>HelloHttpServlet</servlet-name>
	  	<servlet-class>com.mycode01.servlet.HelloHttpServlet</servlet-class>
	  	<load-on-startup>2</load-on-startup>
	</servlet>