Java打卡:第98天
javaWeb — Servlet
内容导航
Java EE
Servlet
昨天已经分享了欢迎页面和一般Servlet的书写,GenericServlet;使用模板方法模式和适配器模式
其实系统已经定义好一个之前的实现的GenericServlet;可以直接使用import javax.servlet.GenericServlet;
可以看一下源码
public abstract class GenericServlet implements Servlet, ServletConfig,
java.io.Serializable {
private static final long serialVersionUID = 1L;
private transient ServletConfig config;
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
/**
* A convenience method which can be overridden so that there's no need to
* call <code>super.init(config)</code>.
* <p>
* Instead of overriding {@link #init(ServletConfig)}, simply override this
* method and it will be called by
* <code>GenericServlet.init(ServletConfig config)</code>. The
* <code>ServletConfig</code> object can still be retrieved via
* {@link #getServletConfig}.
*
* @exception ServletException
* if an exception occurs that interrupts the servlet's
* normal operation
*/
public void init() throws ServletException {
// NOOP by default
}
实现的思路和之前写的是相同的,使用了模板方法模式
HttpServlet
这里假如有一个需求: 验证请求方式;表单提交的方式为POST;如果直接通过地址栏访问则拦截
分析:要想拦截,那就要获得请求提交的方式method;这里的service方法中有Request类型的,这里的req变量没有getMethod方法;==但是Request下面的子接口HTTPrequest有;
所以这里使用下转型可以获得对象【java其实不支持真正的下转型,只有超类对象是由子类对象上转型得到,才可以进行下转型】 因为通信支持的是Http协议,所以这里就可以想到method应该与Http相关。
package cfeng;
import java.io.IOException;
import javax.servlet.*;
import javax.servlet.http.*;
@SuppressWarnings("serial")
public class HttpServlet extends GenericServlet {
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
//这里使用下转型
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
//获取提交的method
String method = request.getMethod();
//操作method 使用equals方法一般将常量放在前面,这里可以避免空指针异常
if("POST".equals(method)) {
doPost();
}else if("GET".equals(method)) {
doGET();
}
}
public void doGET()
System.out.println("非法操作,不允许访问");
}
public void doPost() {
System.out.println("合法操作,允许访问");
}
}
12月 19, 2021 5:30:52 下午 org.apache.catalina.loader.WebappClassLoaderBase checkThreadLocalsForLeaks
警告: 在Java 9上运行时,需要在JVM命令行参数中添加“-add opens=Java.base/Java.lang=ALL-UNNAMED”,以启用线程本地内存泄漏检测。或者,可以通过禁用ThreadLocal内存泄漏检测来抑制此警告。
12月 19, 2021 5:30:52 下午 org.apache.catalina.core.StandardContext reload
信息: 已完成重新加载名为/proj1的上下文
合法操作,允许访问
这样我们就实现了对于请求的处理,可以识别是正常的请求或者非法的请求
那么就有疑问了,操作请求是很常见的一种操作,会比较频繁使用,系统是否实现了这个类呢?就像GenericServlet一样,答案是肯定的;
当需要对请求进行操作的时候,可以不继承GenericServlet,可以直接继承HttpServlet
//这里可以看一下HttpServlet的源码
public abstract class HttpServlet extends GenericServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
String msg = lStrings.getString("http.method_get_not_supported");
sendMethodNotAllowed(req, resp, msg);
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String msg = lStrings.getString("http.method_post_not_supported");
sendMethodNotAllowed(req, resp, msg);
}
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String method = req.getMethod(); //获取提交method
if (method.equals(METHOD_GET)) { //判断method
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;
try {
ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
} catch (IllegalArgumentException iae) {
// Invalid date header - proceed as if none was set
ifModifiedSince = -1;
}
if (ifModifiedSince < (lastModified / 1000 * 1000)) {
// 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);
}
@Override //这里重写自GenericServlet的方法
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException {
HttpServletRequest request;
HttpServletResponse response;
try {
request = (HttpServletRequest) req;
response = (HttpServletResponse) res;
} catch (ClassCastException e) {
throw new ServletException(lStrings.getString("http.non_http"));
}
service(request, response);
}
}
可以看到这里的实现方法和上面的思路基本是一致的,倒时候需要使用的时候就继承该类;并且重写doGet和doPost方法
所以之后实现的时候就不要再继承GenericServlet,而是直接继承HttpServlet;操作就是重写两个方法
package cfeng;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.*;
public class loginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doPost(req, resp);
System.out.println("合法操作");
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doGet(req, resp);
doPost(req,resp); //既支持POST,也支持GET
}
}
这样当表单POST提交的时候就会执行该方法
快捷创建Servlet
经过前面的分析,以后创建Servlet的时候,就不用再继承Servlet接口或者GenericServlet抽象类了,可以直接继承HttpServlet类
并且可以使用Eclipse快捷操作,直接new的时候就创建Servlet;而不是创建Class;这样的好处就是不用手动配置
Ctrl + N 新建输入Servlet;输入名称,next;选择url-pattern;next选择方法就可快捷创建Servlet
这样就可以直接创建一个Servlet类
package cfeng;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet implementation class Some
*/
@WebServlet("/Some")
public class Some extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public Some() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
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 {
// TODO Auto-generated method stub
doGet(request, response);
}
}
像这样之后就可以访问了
package cfeng;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet(description = "this is just a test class", urlPatterns = { "/TestServlet" })
public class TestServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("hello");
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
注意:这里的Servlet配置没有再web.xml中配置,而是直接使用注解websocket配置,推荐使用这种方式,更加方便动态
@WebServlet(description = “this is just a test class”, urlPatterns = { “/TestServlet” })
在Servlet类的前面加上这个,和在web.xml中配置的效果相同
HttpServletRequest
HttpServletRequest是SUN指定的Servlet规范,表示请求,父接口是ServletRequest;HTTP请求协议的完整的内容都要封装request对象中;Tomcat对于该接口进行了实现;之前使用抓包工具抓取的请求内容就是封装在HttpServletRequest中;
这里比如
用HttpWatch来进行观察,当使用表单POST提交的请求的内容
POST /Test/TestServlet HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:35.0) Gecko/20100101 Firefox/35.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Referer: http://localhost:8080/Test/
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 23
user=%E5%BC%A0%E4%B8%89
这里出现了请求的中文乱码问题,稍后会进行分析;HttpServletRequest就是将上面的内容封装起来,方便直接进行操作。
request = org.apache.catalina.connector.RequestFacade@57a3f54a
这个对象就是Tomcat调用的时候对于HttpServletRequest接口的实现使用的就是该实现类的对象
请求的生命周期
当请求到达服务器的时候Tomcat服务器就会解析这个请求,按照HTTP协议解析之后就会封装成一个HttpServletRequest对象,这样就初始化完成。请求对象是由服务器创建。
当服务器向客户端响应结束后,HttpServletRequest实例对象由服务器销毁
一次请求对应一个请求对象
,也就是对应一个Request对象;另外的对应另外一个对象,与之前的请求对象没有任何关系,所以Request对象的生命周期很短暂
HTTP是无状态【数据】协议,不同的请求之间是没有关系的
请求参数parameter
HttpServletRequest的父接口ServletRequest中就有关于请求参数的方法比如
- getParameter: Returns the value of a request parameter as a
String
, ornull
if the parameter does not exist.
这里的请求参数是指的是提交表单的时候提交的参数,比如表单中的name就是,value就是参数的值
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("hello");
System.out.println("request = " + request);
String user = request.getParameter("user");
System.out.println("user = " + user);
}
已完成重新加载名为/Test的上下文
hello
request = org.apache.catalina.connector.RequestFacade@6ed310ae
user = hh
- getParameterNames 返回的就是一个name的枚举集合,和之前的config类中的获取初始化参数 相同
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("hello");
System.out.println("request = " + request);
Enumeration<String> parameterNames = request.getParameterNames();
//遍历枚举类型
while(parameterNames.hasMoreElements()) {
String name = parameterNames.nextElement();
System.out.println(request.getParameter(name));
}
}
还是可以输出,和上面的结果相同的
HttpServletRequest对于请求所携带的参数是以Map的形式接收的,并且这个Map的key是String,value是String[]类型的,为什么是数组类型,而不是String类型?
表单中的复选框的类型比如checkbox,这里的value有多个值
<form action="/login" method="get">
爱好:
<input type="checkbox" name="hobby" value="soccer"/>足球
<input type="checkbox" name="hobby" value="basketball"/>篮球
<br>
</form>
这里的复选框就有多个值,那么获取的时候如果是一个String类型,是不能正常执行的,这里是一个Map类型的
user=zhangsan&hobby=soccer&hobby=basketball&hobby=tennis
//上面是请求的请求报文
hello
request = org.apache.catalina.connector.RequestFacade@6ed310ae
user=zhangsan
hobby=soccer
这里看到普通的根据name获取的参数值就是hobby=soccer;就是使用getParameter输出的只是一个value
getParameter本质上等同于getParameterValues(“name”)[0];
所以这里要将所有的value给取出来就要使用Map;之后使用getParameterValues;如果没有复选框,就使用getParameter就可以了
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("hello");
System.out.println("request = " + request);
Map<String, String[]> map = request.getParameterMap();
for (String key : map.keySet()) {//keySet就是Map的键的集合
String[] values = request.getParameterValues(key); //获取map的value集合
System.out.print(key + " = ");
for(String value : values) {
System.out.print(value + " ");
}
System.out.print("\n");
}
}
user = zhangsan
hobby = soccer basketball tennis
这样子就可以获取复选框这种有多个值的name
域属性 attribute
dispatcher 调度程序,分配器 wrapper 包装
之前分析config的时候就提到了初始化参数和域属性;那里的域属性是所有的Servlet共享,就是一个应用程序对应一个应用程序;complex
在HttpRequest中也有域属性空间,用来存放有名称的数据,该数据只是在当前的request的请求中可以进行访问;只要请求还存在,就可以访问域属性空间的数据; 所以域属性的作用就是用来传递数据的
和之前的complex相同,直接get,set就可
ServletRequest的一个方法是getRequestDispatch方法 就是获得一个请求的分配器;就是正常来说一个请求提交给一个Servlet,可能完成不了任务;这个时候就要通过dispatcher转发给其他的servlet处理
- getRequestDispatcher (String path) 将一个当前Servlet的请求转发给另外一个path;path可对应一个servlet 得到一个对应终点路径的调度器
- forward: Forwards a request from a servlet to another resource (servlet, JSP file, or HTML file) on the server. [forward 向前、传递] 就是将当前路径的请求传递给另外一个resource
- include : Includes the content of a resource (servlet, JSP page, HTML file) in the response. 包含一个相应的所有的内容
在其中一个请求没有结束的时候,被分配的Servlet就可以 访问操作域属性;和Complex是相同的;比如remove,set和get
package cfeng;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet(description = "just for testing", urlPatterns = { "/some" })
public class someServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println(this.getServletName());
String author = (String)request.getAttribute("author");
System.out.println("author = " + author);
System.out.println("addree = " + request.getAttribute("address"));
}
这里就是被分配的类,配置的信息就是在webServlet注释中,不需要再xml中配置
package cfeng;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Map;
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;
@WebServlet(description = "this is just a test class", urlPatterns = { "/TestServlet" })
public class TestServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setAttribute("author", "Cfeng");
request.setAttribute("address", "Peking");
Map<String, String[]> map = request.getParameterMap();
request.getRequestDispatcher("/some").forward(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);//这里POST提交的时候也会执行GET中的方法
}
}
这样就和之前讲解的域属性是类似的,就是Servlet之间实现属性共享,可以设置,访问和删除;这是这里的域属性必须是请求有效区间。♉️