Servlet --- HttpServlet

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 --- HttpServlet

这样就可以直接创建一个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, or null 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之间实现属性共享,可以设置,访问和删除;这是这里的域属性必须是请求有效区间。♉️

上一篇:手把手的numpy入门教程,从此数据处理不再慌


下一篇:压力测试软件-siege的安装使用