1. 什么是Servlet?
- Servlet就是运行在服务端的一个java程序,通常运行在Tomcat容器中,每当一个HTTP请发发送到服务器时,Tomcat负责创建对应的Servlet对象,并由这个对象来处理当前的HTTP请求;
2. 实现Servlet接口处理HTTP请求
-
创建Servlet实现类ServletImpl类并实现Servlet中的方法,我们发现有5个方法需要实现:
- init():创建Servlet对象后默认调用的初始化方法;
- service():处理HTTP请求并返回给浏览器;
- destroy():当Tomcat关闭或Servlet对象销毁的时候调用此方法;
- getServletConfig():获取Web.xml中的Servlet配置;
- getServletInfo():获取Servlet信息;
public class ServletImpl implements Servlet {
@Override
public void init(ServletConfig config) throws ServletException {
System.out.println("MyServlet被初始化");
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
System.out.println("MyServlet被访问");
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
System.out.println("MyServlet被销毁");
}
}
-
在web.xml中配置接口映射,为什么要有这个web.xml文件呢?
- 当接收到一个HTTP请求的时候,Tomcat会去web.xml文件中去查找(Servlet映射)中的(映射路径)/hello;
- 当找到匹配的映射路径后,就会查看该路径对应的(映射路径对应的Servlet名称)MyServlet;
- 然后通过Servlet名称去(Servlet配置)中去找该名称对应的Servlet类编译后的class文件路径com.servlet.ServletImpl;
- 执行ServletImpl类;
<servlet>
<servlet-name>ServletImpl</servlet-name>
<servlet-class>com.servlet.ServletImpl</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ServletImpl</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
- 当浏览器第一次访问/hello接口时调用init和service方法,结果如下:
ServletImpl被初始化
ServletImpl被访问
- 当浏览器第二次访问/hello接口时不再调用init方法,只调用service方法,结果如下:
ServletImpl被访问
- 当关闭Tomcat时,调用destroy方法,结果如下:
ServletImpl被销毁
3. 继承HttpServlet处理HTTP请求
大多数情况下,我们只需要处理HTTP请求,并不关心Servlet对象的创建和销毁,每创建一个新的Servlet接口都要实现5个方法,这样做非常麻烦。所以,我们只需要继承HttpServlet类来重写对应的方法即可,而且HttpServlet本身也实现了Servlet接口并添加了许多处理HTTP请求的方法,比如处理Get请求、Post请求等等。
实现
继承HttpServlet处理HTTP请求的方法如下:
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("处理http的get请求");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("处理http的post请求");
}
}
<servlet>
<servlet-name>MyServlet</servlet-name>
<servlet-class>com.servlet.MyServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>MyServlet</servlet-name>
<url-pattern>/hello1</url-pattern>
</servlet-mapping>
那么在HttpServlet中的service方法都干了什么呢?
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) {
// servlet doesn't support if-modified-since, no reason
// to go through further expensive logic
doGet(req, resp);
} else {
long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
if (ifModifiedSince < lastModified) {
// If the servlet mod time is later, call doGet()
// Round down to the nearest second for a proper compare
// A ifModifiedSince of -1 will always be less
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 {
//
// Note that this means NO servlet supports whatever
// method was requested, anywhere on this server.
//
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);
}
}
我们可以看出,service方法先获取到request请求中的方法,判断这个方法属于哪种方法,如果是GET方法,则调用我们重写的doGet方法进行处理。
4. Servlet的生命周期
Servlet的生命周期以下3个过程:
- 创建Servlet对象:当Http请求第一次访问Tomcat的时候,Tomcat会创建一个对应的Servlet实例,创建这个实例后默认调用init方法,之后的相同的Http请求不会再次创建对象,而是获取已有的对象,所以也不会再次调用init方法,只会调用service方法;
- 处理Http请求:当浏览器发送了Http请求后,Servlet会调用service方法进行处理;
- 销毁Servlet实例:当Tomcat关闭或者检测到某个Servlet对象长时间没有被访问的时候,Tomcat会自动销毁这个Servlet实例,释放掉占用的资源,并调用destroy方法。
5. ServletConfig对象
- ServletConfig对象是一个Map容器;
- 我们可以通过ServletConfig对象读取web.xml中配置的初始化参数,比如:
<servlet>
<servlet-name>MyServlet</servlet-name>
<servlet-class>com.servlet.MyServlet</servlet-class>
<init-param>
<param-name>key1</param-name>
<param-value>value1</param-value>
</init-param>
</servlet>
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("处理http的get请求");
String value = getServletConfig().getInitParameter("key1");
System.out.println(value);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("处理http的post请求");
}
}
访问/hello1接口后,控制台打印出:
处理http的get请求
value1
6. ServletContext对象
- ServletContext对象是一个Map容器;
- ServletContext对象只有一个,所有Servlet都共享着这个对象,相当于一个共享变量,所以Servlet之间可以通过ServletContext对象进行通信,示例代码如下:
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("访问MyServlet");
String value = (String) getServletContext().getAttribute("key3");
System.out.println(value);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("处理http的post请求");
}
}
```java
public class MyServlet2 extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("访问MyServlet2");
getServletContext().setAttribute("key3", "MyServlet2设置的值");
}
}
访问MyServlet2
访问MyServlet
MyServlet2设置的值
- ServletConfig对象配置的是单个Servlet的信息,而ServletContext对象配置的是所有Servlet都具备的信息, 即整个web的配置信息,示例代码如下:
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("处理http的get请求");
String value = getServletContext().getInitParameter("key2");
System.out.println(value);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("处理http的post请求");
}
}
<context-param>
<param-name>key2</param-name>
<param-value>我是共享信息</param-value>
</context-param>
处理http的get请求
我是共享信息
- 读取资源文件,将资源文件放在WEB-INF同级目录下,并通过一下方法获取:
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
InputStream resourceAsStream = getServletContext().getResourceAsStream("test.xlsx");
System.out.println(resourceAsStream);
}
}
7. HttpServletRequest对象
- HttpServletRequest对象封装了HTTP请求的所有信息,包括请求行、请求头、请求数据,我们可以通过该对象的方法获取到HTTP协议中的信息;
- 常用方法如下:
获得浏览器基本信息:
getRequestURL⽅法返回客户端发出请求时的完整URL。
getRequestURI⽅法返回请求⾏中的资源名部分。
getQueryString ⽅法返回请求⾏中的参数部分。
getPathInfo⽅法返回请求URL中的额外路径信息。额外路径信息是请求URL中的位于Servlet的路
径之后和查询参数之前的内容,它以“/”开头。
getRemoteAddr⽅法返回发出请求的客户机的IP地址
getRemoteHost⽅法返回发出请求的客户机的完整主机名
getRemotePort⽅法返回客户机所使⽤的⽹络端⼝号
getLocalAddr⽅法返回WEB服务器的IP地址。
getLocalName⽅法返回WEB服务器的主机名
获得请求头:
getHeader⽅法
getHeaders⽅法
getHeaderNames⽅法
获得请求参数(客户端提交的数据)
getParameter⽅法
getParameterValues(String name)⽅法
getParameterNames⽅法
getParameterMap⽅法
8. HttpServletResponse对象
-
HttpServletResponse对象封装了HTTP响应协议的所有内容,包括:响应行、响应头、返回数据;
-
当返回字符串数据时,指定返回的内容类型和字符类型,再通过Writer对象写入数据:
response.setContentType("application/json; charset=utf-8"); response.getWriter().write("中国");
-
当实现文件下载时,做法如下:
public class MyServlet2 extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
InputStream resourceAsStream = getServletContext().getResourceAsStream("test.xlsx");
response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode("下载文件.xlsx", "UTF-8"));
//将读取的内容发送给浏览器
int len = 0;
byte[] bytes = new byte[1024];
ServletOutputStream outputStream = response.getOutputStream();
while ((len = resourceAsStream.read(bytes)) > 0) {
outputStream.write(bytes, 0, len);
}
outputStream.close();
resourceAsStream.close();
}
}
9. 转发与重定向
转发与重定向的应用场景:
- 转发:访问Servlet处理业务逻辑,执行request.getRequestDispatcher("/index.html").forward(request, response)跳转到前台index.html文件,并发request对象带过去,在index.html中把数据取出来进行页面渲染,此时浏览器的URL不变;
- 重定向:提交登录表单,登录验证通过后执行response.sendRedirect("/web应用名/index.html")跳转到应用首页,此时浏览器的URL变了;
转发与重定向的区别:
-
实际发生的位置不同,地址栏不同
- 转发是在服务器进行跳转的,浏览器的地址栏是灭有变化的,当访问servlet1的时候,即使跳到了servlet2的页面,浏览器的地址还是servlet1,所以转发只是一次的http请求; - 重定向是由浏览器进行跳转的,进行重定向的时候,浏览器的地址栏会发生变化,重定向的本质是:response的状态码为301 + 响应头里增加Location属性,值为要重定向的地址。所以重定向需要浏览器发出两次http请求;
-
用法不同
- 转发的用法: request.getRequestDispatcher("/URI").forward(request, response); - 重定向的用法:response.sendRedirect("/web应用/URI");
-
能过去往的URL的范围不同:
- 转发是服务器跳转,所以只能去当前web应用的资源; - 重定向是浏览器跳转,可以去往任何资源;
-
传递的数据类型不同:
- 转发的request对象可以传递任意类型的数据; - 重定向只能传递字符串;
-
跳转到时间不同:
- 转发的跳转时间是执行完这个语句就转发; - 重定向需要整个页面执行完才进行跳转;