概念
HttpServlet类是专门为HTTP协议准备的,比GenericServlet更加适合HTTP协议下的开发。
HttpServlet在jakarta.servlet.http.HttpServlet包下。
到目前为止接触的servlet规范中的接口:
- javax/jakarta.servlet.Servlet //核心接口(接口)
- javax/jakarta.servlet.ServletConfig //Servlet配置信息接口(接口)
- javax/jakarta.servlet.ServletContext //Servlet上下文接口(接口)
- javax/jakarta.servlet.ServletRequest //Servlet请求接口(接口)
- javax/jakarta.servlet.ServletResponse //Servlet响应接口(接口)
- javax/jakarta.servlet.ServletException //Servlet异常(类)
- javax/jakarta.servlet.GenericServlet //标准通用的Servlet类(抽象类)
http包下的类和接口 javax/jakarta.servlet.http.*;
- javax/jakarta.servlet.http.HttpServlet //HTTP协议专用的Servlet类,抽象类
- javax/jakarta.servlet.http.HttpServletRequest //HTTP协议专用的请求对象
- javax/jakarta.servlet.http.HttpServletResponse //HTTP协议专用的响应对象
HttpServletRequest中封装了请求协议的全部内容。
- Tomcat服务器(WEB服务器)将“请求协议”中的数据全部解析出来,然后将这些数据全部封装到request对象当中了。
HttpServletResponse对象是专门用来响应HTTP协议到浏览器的。
HttpServlet源码分析
public class HelloServlet extends HttpServlet {
// 用户第一次请求,创建HelloServlet对象的时候,会执行这个无参数构造方法。
public HelloServlet() {
}
//override 重写 doGet方法
//override 重写 doPost方法
}
public abstract class GenericServlet implements Servlet, ServletConfig,java.io.Serializable {
// 用户第一次请求的时候,HelloServlet对象第一次被创建之后,这个init方法会执行。
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
// 用户第一次请求的时候,带有参数的init(ServletConfig config)执行之后,会执行这个没有参数的init()
public void init() throws ServletException {
// NOOP by default
}
}
// HttpServlet模板类。
public abstract class HttpServlet extends GenericServlet {
// 用户发送第一次请求的时候这个service会执行
// 用户发送第N次请求的时候,这个service方法还是会执行。
// 用户只要发送一次请求,这个service方法就会执行一次。
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
HttpServletRequest request;
HttpServletResponse response;
try {
// 将ServletRequest和ServletResponse向下转型为带有Http的HttpServletRequest和HttpServletResponse
request = (HttpServletRequest) req;
response = (HttpServletResponse) res;
} catch (ClassCastException e) {
throw new ServletException(lStrings.getString("http.non_http"));
}
// 调用重载的service方法。
this.service(request, response);
}
// 这个service是一个模板方法,这个方法的两个参数都是带有Http的。
// 在该方法中定义核心算法骨架,具体的实现步骤延迟到子类中去完成。
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取请求方式
// request.getMethod()方法获取的是请求方式,可能是七种之一: GET POST PUT DELETE HEAD OPTIONS TRACE
String method = req.getMethod();
long lastModified;
// 如果请求方式是GET请求,则执行doGet方法。
if (method.equals(METHOD_GET)) {
lastModified = this.getLastModified(req);
if (lastModified == -1L) {
this.doGet(req, resp);
} else {
long ifModifiedSince;
try {
ifModifiedSince = req.getDateHeader("If-Modified-Since");
} catch (IllegalArgumentException var9) {
ifModifiedSince = -1L;
}
if (ifModifiedSince < lastModified / 1000L * 1000L) {
this.maybeSetLastModified(resp, lastModified);
this.doGet(req, resp);
} else {
resp.setStatus(304);
}
}
} else if (method.equals("HEAD")) {
lastModified = this.getLastModified(req);
this.maybeSetLastModified(resp, lastModified);
this.doHead(req, resp);
} else if (method.equals("POST")) {
// 如果请求方式是POST请求,则执行doPost方法。
this.doPost(req, resp);
} else if (method.equals("PUT")) {
this.doPut(req, resp);
} else if (method.equals("DELETE")) {
this.doDelete(req, resp);
} else if (method.equals("OPTIONS")) {
this.doOptions(req, resp);
} else if (method.equals("TRACE")) {
this.doTrace(req, resp);
} else {
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[]{method};
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(501, errMsg);
}
}
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 报405错误
String msg = lStrings.getString("http.method_get_not_supported");
this.sendMethodNotAllowed(req, resp, msg);
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 报405错误
String msg = lStrings.getString("http.method_post_not_supported");
this.sendMethodNotAllowed(req, resp, msg);
}
/*
通过以上源代码分析:
假设前端发送的请求是get请求,后端程序员重写的方法是doPost
假设前端发送的请求是post请求,后端程序员重写的方法是doGet
通过以上源代码可以知道:会发生405这样的一个错误。
405表示前端的错误,发送的请求方式不对,和服务器不一致,不是服务器需要的请求方式。
怎么避免405的错误呢?
后端重写了doGet方法,前端一定要发get请求。
后端重写了doPost方法,前端一定要发post请求。
这样可以避免405错误。
*/
Servlet的开发步骤
- 第一步:编写一个Servlet类,直接继承HttpServlet
- 第二步:重写doGet方法或者重写doPost方法。
- 第三步:将Servlet类配置到web.xml文件当中。
- 第四步:准备前端的页面(form表单),form表单中指定请求路径即可。
HttpServletRequest接口
HttpServletRequest是一个接口,全限定名称:javax/jakarta.servlet.http.HttpServletRequest
HttpServletRequest接口是Servlet规范中的一员。
HttpServletRequest接口的父接口:ServletRequest
public interface HttpServletRequest extends ServletRequest {}
org.apache.catalina.connector.RequestFacade 实现了 HttpServletRequest接口
public class RequestFacade implements HttpServletRequest {}
HttpServletRequest对象
是Tomcat服务器负责创建的,这个对象中封装了HTTP的请求协议
。
request和response对象的生命周期:这两个对象都只在当前请求中有效。一次请求对应一个request。1对1
HttpServletRequest接口中常用的方法:
// 获取前端浏览器用户提交的数据
// 获取Map
Map<String,String[]> getParameterMap()
// 获取Map集合中所有的key
Enumeration<String> getParameterNames()
// 根据key获取Map集合的value
String[] getParameterValues(String name)
// 获取value这个一维数组当中的第一个元素。"最常用"
String getParameter(String name)
// 获取客户端的IP地址
String remoteAddr = request.getRemoteAddr();
// get请求在请求行上提交数据。
// post请求在请求体中提交数据。
// 设置请求体的字符集,处理POST请求的乱码问题。(从浏览器接受数据的乱码)
request.setCharacterEncoding("UTF-8");
// 解决响应(发送数据到浏览器)的乱码问题
response.setContentType("text/html;charset=UTF-8");
// 获取应用的根路径
String contextPath = request.getContextPath();
// 获取请求方式
String method = request.getMethod();
// 获取请求的URI(带项目名的路径)
String uri = request.getRequestURI();
// 获取servlet path(不带项目名的路径)
String servletPath = request.getServletPath();
前端表单中提交的数据格式:
username=admin&password=123
后端一般采用Map集合来存储前端提交的数据
request对象实际上又称为“请求域”对象。
ServletContext被称为Servlet上下文对象,也叫“应用域”对象。
请求域对象和应用域对象中都有这三个操作域的方法:
void setAttribute(String name, Object obj); // 向域当中绑定数据。
Object getAttribute(String name); // 从域当中根据name获取数据。
void removeAttribute(String name); // 将域当中绑定的数据移除
“请求域”对象与“应用域”对象
- “请求域”对象要比“应用域”对象范围小很多,生命周期短很多。
- 请求域只在一次请求内有效,一个请求对象request对应一个请求域对象。一次请求结束之后,这个请求域就销毁了。
- 应用域在服务器启动阶段创建,在服务器关闭的时候销毁。
- 尽量使用小的域对象,小的域对象占用的资源较少。
转发与重定向
// 转发,一直在同一个请求域中(由WEB服务器来控制的)
// request.getRequestDispatcher("/路径") 获取请求转发域对象
// forward(request,response) 调用forward方法完成转发
request.getRequestDispatcher("/路径").forward(request,response);
// 重定向,会发送一次新的请求(由浏览器来控制)
response.sendRedirect("/项目名/路径");
转发和重定向应该如何选择?
- 如果在上一个Servlet当中向request域当中绑定了数据,希望从下一个Servlet当中把request域里面的数据取出来,使用转发机制。
- 剩下所有的请求均使用重定向(使用较多)。
关于request对象中两个非常容易混淆的方法
// 第一个方法:获取的是用户在浏览器上提交的数据。
// uri?username=zhangsan&userpwd=123&sex=1
String username = request.getParameter("username");
// 第二个方法:获取的是请求域当中绑定的数据。
// request.setAttribute("name", new Object())
Object obj = request.getAttribute("name");