四、Servlet
4.1 Servlet简介
Servlet是Server Applet的简称,称为服务端小程序,是JavaEE平台下的技术标准,基于Java语言编写的服务端程序。 Web 容器或应用服务器实现了Servlet标准所以Servlet需要运行在Web容器或应用服务器中。Servlet主要功能在于能够在服务器中执行并生成数据。
4.1.1 Servlet技术特点
4.1.2 Servlet在应用程序中的位置
4.2 编写第一个Servlet
package it.com.servlet; import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class HelloWorld extends HttpServlet{ public void doGet(HttpServletRequest request,HttpServletResponse response)throws ServletException,IOException{ PrintWriter pw = response.getWriter(); pw.println("<!DOCTYPE html>"); pw.println("<html lang=en>"); pw.println("<head>"); pw.println("<meta charset=UTF-8>"); pw.println("<title>Document</title>"); pw.println("</head>"); pw.println("<body>"); pw.println("<font color=blue>HelloWorld</font>"); pw.println("</body>"); pw.println("</html>"); pw.flush(); pw.close(); } }
4.3编译servlet
Java中需要将源文件(.java)通过Javac编译为.class的字节文件才能运行。
D:\>javac oneServlet.java
出错的原因是servlet是JavaEE实现的规范不是JavaSE实现的,在servlet包中的类全部在Tomcat中有实现。因此servlet程序要放在Tomcat中运行。因此需要指定这些对象的位置(也就是tomcat中servlet包的位置)
D:\>javac -classpath "D:\Java\apache-tomcat-9.0.75\lib\servlet-api.jar" HelloWorld.java
执行后D盘产生字节文件Hello World.class
4.4 创建web.xml
什么是web.xml
Web项目的部署描述文件,是JavaWeb工程的配置文件,通过web.xml文件可以配置servlet、filter等技术。Tomcat启动时会先解析该配置文件获取项目的配置信息。
web.xml文件中的头信息
<?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"> </web-app>
在web.xml文件中配置Servlet
<?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的全名,tomcat会根据全名实例化servlet,从而使用servlet对象的方法 --> <servlet> <servlet-name>HelloWorld</servlet-name> <servlet-class>it.com.servlet.HelloWorld</servlet-class> </servlet> <!-- servlet资源映射,访问/helloworld.do即可访问到servlet资源 --> <servlet-mapping> <servlet-name>HelloWorld</servlet-name> <url-pattern>/helloworld.do</url-pattern> </servlet-mapping> </web-app>
注意路径必须加 /
4.5 部署运行Servlet
Web工程目录结构
访问Servlet
http://localhost:8888/servletdemo/helloworld.do
注意:没有在host下配置context时资源名默认为项目名。
4.6 Tomcat处理请求过程
- 用户访问localhost:8888/test/helloword.do,请求被发送到Tomcat,被监听8888端口并处理 HTTP/1.1 协议的Connector获得。
- Connector把该请求交给它所在的Service的Engine来处理,并等待Engine的回应。
- Engine获得请求localhost/test/helloword.do,匹配所有的虚拟主机Host。
- Engine匹配到名为localhost的Host虚拟主机来处理/test/helloword.do请求(即使匹配不到会请求交给默认Host处理)。
- 匹配到的Context获得请求/helloword.do。
- 构造HttpServletRequest对象和HttpServletResponse对象,作为参数调用HelloWorld的doGet()或doPost().执行业务逻辑、数据存储等程序。
- Context把执行完之后的结果通过HttpServletResponse对象返回给Host。
- Host把HttpServletResponse返回给Engine。
- Engine把HttpServletResponse对象返回Connector。
- Connector把HttpServletResponse对象返回给客户Browser。
4.7 Servlet继承结构
Servlet接口
- init(),创建Servlet对象后立即调用该方法完成一些初始化工作。
- service(),处理客户端请求,执行业务操作,利用响应对象响应客户端请求。
- destroy(),在销毁Servlet对象之前调用该方法,释放资源。
- getServletConfig(),ServletConfig是容器向servlet传递参数的载体。(可以获取配置文件web.xml的信息)
- getServletInfo(),获取servlet相关信息。
ServletConfig接口
- String getServletName(),返回 Servlet 的名字,即 web.xml 中 元素的值。
- ServletContext getServletContext(),返回一个代表当前 Web 应用的 ServletContext 对象。
- String getInitParameter(String name),根据初始化参数名返回对应的初始化参数值。
- Enumeration getInitParameterNames(),返回一个 Enumeration 对象,其中包含了所有的初始化参数名。
GenericServle抽象类
GenericServlet是实现了Servlet接口的抽象类。在GenericServlet中进一步的定义了Servlet接口的具体实现,其设计的目的是为了和应用层协议解耦,在GenericServlet中包含一个Service抽象方法。
HttpServlet类
继承自 GenericServlet,针对于处理 HTTP 协议的请求所定制。在 HttpServlet的service() 方法中已经把 ServletReuqest 和 ServletResponse 转为 HttpServletRequest 和 HttpServletResponse。 直接使用 HttpServletRequest 和 HttpServletResponse, 不再需要强转。实际开发中, 直接继承 HttpServlet, 并根据请求方式复写 doXxx() 方法即可。
Tomcat实现了servlet技术规范,所以在tomcat的lib中的servlet-api.jar中可以查看这些接口和实现类。使用反编译小工具jd
4.8 Servlet的生命周期
Servlet的生命周期是由容器(tomcat,即servlet是由tomcat实例化的)管理的,分别经历三各阶段:
init():初始化
service():服务
destroy():销毁
(注意面试)当客户端浏览器第一次请求Servlet时,容器会实例化这个Servlet,然后调用一次init方法,并在新的线程中执行service方法处理请求。service方法执行完毕后容器不会销毁这个Servlet而是做缓存处理,当客户端浏览器再次请求这个Servlet时,容器会从缓存中直接找到这个Servlet对象,并再一次在新的线程中执行Service方法(单例模式)。当容器在销毁Servlet之前对调用一次destroy方法。
4.9 Servlet处理请求的原理
当浏览器基于get方式请求我们创建Servlet时,我们自定义的Servlet中的doGet方法会被执行。doGet方法能够被执行并处理get请求的原因是,容器在启动时会解析web工程中WEB-INF目录中的web.xml文件,在该文件中我们配置了Servlet与URI的绑定,容器通过对请求的解析可以获取请求资源的URI,然后找到与该URI绑定的Servlet并做实例化处理(注意:只实例化一次,如果在缓存中能够找到这个Servlet就不会再做次实例化处理)。在实例化时会使用Servlet接口类型作为引用类型的定义,并调用一次init方法,由于GenericServlet中重写了该方法所以最终执行的是GenericServlet中init方法(GenericServlet中的Init方法是一个空的方法体),然后在新的线程中调用service方法。由于在HttpServlet中重写了Service方法所以最终执行的是HttpServlet中的service方法。在service方法中通过request.getMethod()获取到请求方式进行判断如果是Get方式请求就执行doGet方法,如果是POST请求就执行doPost方法。如果是基于GET方式提交的,并且在我们的Servlet中又重写了HttpServlet中的doGet方法,那么最终会根据Java的多态特性转而执行我们自定义的Servlet中的doGet方法。
4.10 Servlet的作用
- 获取用户提交的数据
- 获取浏览器附加的信息
- 处理数据(访问数据库或调用接口)
- 给浏览器产生一个响应
- 在响应中添加附加信息
4.12 在Idea中创建web工程
三个步骤:
- 创建Web工程
- 添加servlet-api.jar
- 配置Tomcat
新建一个Java项目
项目名称右键选择第二个命令
勾选Web Application
为项目添加依赖
配置tomcat
4.13 在web工程中编写Servlet
新建MyServlet类,继承HttpServlet,重写doGet();方法。配置web.xml文件。启动tomcat在浏览器访问即可。
public class MyServlet extends HttpServlet { //处理get请求 protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { PrintWriter pw = resp.getWriter(); pw.println("<!DOCTYPE html>"); pw.println("<html lang=en>"); pw.println("<head>"); pw.println("<meta charset=UTF-8>"); pw.println("<title>Document</title>"); pw.println("</head>"); pw.println("<body>"); pw.println("<font color=blue>Hello Servlet</font>"); pw.println("</body>"); pw.println("</html>"); pw.flush(); pw.close(); } }
<?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> <servlet-name>MyServlet</servlet-name> <servlet-class>cn.it.servlet.MyServlet</servlet-class> </servlet> <!--URL映射--> <servlet-mapping> <servlet-name>MyServlet</servlet-name> <url-pattern>/MyServlet.do</url-pattern> </servlet-mapping> </web-app>
4.14 Idea中的web项目部署详解
在Idea中默认的并不会把web项目真正的部署到Tomcat的webapps目录中,而是通过为每个web项目创建一个独立的Tomcat副本并在Tomcat副本中通过的Tomcat的Context组件完成项目的目录指定,在Context组件的docBase属性中会指定Idea对web项目编译后的目录out/artifacts/.....。
默认部署方式
Idea会在C:\Users\Administrator\AppData\Local\JetBrains\IntelliJIdea2020.2\tomcat中为每个Web项目创建一个独立的Tomcat副本。
Idea通过执行Tomcat的catalina.bat启动脚本启动Tomcat,通过启动参数来指定启动Tomcat副本运行指定目录中的web项目。
Idaa在启动Tomcat之前会先在操作系统中设置一些临时环境变量,这些变量会被Tomcat的启动脚本所读取。
4.15 将web项目部署到Tomcat的webapps中
点击项目结构选项
指定输出artifacts的目录为Tomcat的webapps中的demo目录。
启动Tomcat,查看demo目录中的内容。
4.16 获取请求信息
4.16.1 HttpServletRequest对象
HttpServletRequest对象代表客户端浏览器的请求,当客户端浏览器通过HTTP协议访问服务器时,HTTP请求中的所有信息都会被Tomcat所解析并封装在这个对象中,通过这个对象提供的方法,可以获得客户端请求的所有信息。
获取请求信息
req.getRequestURL()
返回客户端浏览器发出请求时的完整URL。
req.getRequestURI()
返回请求行中指定资源部分。
req.getRemoteAddr()
返回发出请求的客户机的IP地址。
req.getLocalAddr()
返回WEB服务器的IP地址。
req.getLocalPort()
返回WEB服务器处理Http协议的连接器所监听的端口。
package cn.it.servlet; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; //获取请求信息 public class GetRequestInfoServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //获取浏览器发出请求的完整的url StringBuffer requestURL = req.getRequestURL(); String s = requestURL.toString(); //获取请求行中指定的资源部分 String requestURI = req.getRequestURI(); //获取发送请求的客户机的IP地址 String remoteAddr = req.getRemoteAddr(); //获取服务端的IP地址 String localAddr = req.getLocalAddr(); //获取服务端监听的端口 int localPort = req.getLocalPort(); PrintWriter writer = resp.getWriter(); writer.println("浏览器发出请求的完整的url:"+s); writer.println("求行中指定的资源部分:"+requestURI); writer.println("发送请求的客户机的IP地址:"+remoteAddr); writer.println("服务端的IP地址:"+localAddr); writer.println("服务端监听的端口:"+localPort); } }
<servlet> <servlet-name>getRequestInfoServlet</servlet-name> <servlet-class>cn.it.servlet.GetRequestInfoServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>getRequestInfoServlet</servlet-name> <url-pattern>/getRequestInfo.do</url-pattern> </servlet-mapping>
4.16.2 获取请求数据(表单)
根据key获取指定value
req.getParameter("key");
根据key获取对应的value,返回一个字符串。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <!-- action="getRequestData.do" 不能加 “/”--> <form action="getRequestData.do" method="post"> <input type="text" name="username"/> <input type="text" name="password"/> <input type="submit" value="确定"/> </form> </body> </html>
注意:html要在web下创建要不然不能直接访问到。action="getRequestData.do" 不能加 “/”
package cn.it.servlet; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; //获取表单数据 public class GetRequestData extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String username = req.getParameter("username"); String password = req.getParameter("password"); PrintWriter writer = resp.getWriter(); writer.println("username=" + username); writer.println("password=" + password); writer.flush(); writer.close(); } }
<servlet> <servlet-name>getRequestData</servlet-name> <servlet-class>cn.it.servlet.GetRequestData</servlet-class> </servlet> <servlet-mapping> <servlet-name>getRequestData</servlet-name> <url-pattern>/getRequestData.do</url-pattern> </servlet-mapping>
4.16.3 获取复选框(checkbox组件)中的值
String[] userlikes = req.getParameterValues("checkboxkey");
获取复选框(checkbox组件)中的值,返回一个字符串数组。
String[] userLikes = req.getParameterValues("userLike"); List<String> userLikesList = Arrays.asList(userLikes); //将数组转换为集合
4.16.4 获取所有提交数据的key
req.getParameterNames()
获取请求中所有数据的key,该方法返回一个枚举类型。
Enumeration<String> parameterNames = req.getParameterNames();
//获取参数的key Enumeration<String> parameterNames = req.getParameterNames(); List<String> parameterNamesList = new ArrayList<String>(); while (parameterNames.hasMoreElements()) { parameterNamesList.add(parameterNames.nextElement()); }
4.17 设置请求编码
请求的数据包基于字节在网络上传输,Tomcat接收到请求的数据包后会将数据包中的字节转换为字符。在Tomcat中使用的是ISO-8859-1的单字节编码完成字节与字符的转换,所以数据中含有中文就会出现乱码,可以通过req.setCharacterEncoding("utf-8")方法来对提交的数据根据指定的编码方式重新做编码处理。
req.setCharacterEncoding("UTF-8");
4.18 资源访问路径
绝对路径
绝对路径访问资源表示直接以”/”作为项目的Context Path。该方式适用于以”/”作为项目的Context Path。
<form action="/getInfo.do" method="post">
相对路径
相对路径访问资源表示会相对于项目的Context Path作为相对路径。该方式适用于为项目指定的具体的Context Path。
<form action="getInfo.do" method="post">
4.19 获取请求头信息
获取请求头信息
根据请求头中的key获取对应的value。
String headerValue = req.getHeader("headerKey");
获取请求头中所有的key,该方法返回枚举类型。
Enumeration<String> headerNames = req.getHeaderNames();
4.19.1 获取请求头案例
需求:编写一个Servlet,如果浏览器的语言是zh-CN,显示“你好,聪明的中国人!”,如果浏览器的语言设置为en-US,那么则显示“Hello,American”。
package cn.it.servlet; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; public class Practise extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String header = req.getHeader("Accept-Language"); resp.setContentType("text/html;charset=UTF-8"); PrintWriter writer = resp.getWriter(); if (header.startsWith("zh-CN")) { writer.println("你好"); }else if (header.startsWith("en-US")) { writer.println("hello"); }else { writer.println("####"); } writer.flush(); writer.close(); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doPost(req, resp); } }