如何让Servlet一次处理多个请求

如何让Servlet一次处理多个请求

Scroll Down

1 关于Servlet

1.1 什么是Servlet

Servlet是sun公司提供的一门用于开发动态web资源的技术。 Sun公司在其API中提供了一个servlet接口,用户若想用发一个动态web资源(即开发一个Java程序向浏览器输出数据),需要完成以下2个步骤:

  1. 编写一个Java类,实现servlet接口。
  2. 把开发好的Java类部署到web服务器中。

按照一种约定俗成的称呼习惯,通常我们也把实现了servlet接口的java程序,称之为Servlet.

1.2 Servlet的工作流程

Servlet程序是由WEB服务器调用,web服务器收到客户端的Servlet访问请求后:

  1. Web服务器首先检查是否已经装载并创建了该Servlet的实例对象。如果是,则直接执行第④步,否则,执行第二步。
  2. 装载并创建该Servlet的一个实例对象。
  3. 调用Servlet实例对象的init()方法。
  4. 创建一个用于封装HTTP请求消息的HttpServletRequest对象和一个代表HTTP响应消息的HttpServletResponse对象,然后调用Servlet的service()方法并将请求和响应对象作为参数传递进去。
  5. WEB应用程序被停止或重新启动之前,Servlet引擎将卸载Servlet,并在卸载之前调用Servlet的destroy()方法。

一般情况下,首次访问时被创建,服务器关闭时被销毁。

image.png

1.3 实现Servlet的方式

  1. 实现javax.servlet.Servelt接口 重写五个抽象方法 public void init(ServletConfig servletConfig) public ServletConfig getServletConfig() public void service(ServletRequest servletRequest, ServletResponse servletResponse) public void destroy() public String getServletInfo()

其中,init,service,destroy为Servlet生命周期方法。 image.png

  1. 继承javax.servlet.GenericServlet类 观察第一种方式发现整个方法种并没有注入ServletConfig,一种方法是给我们自己的Servelt添加一个ServletConfig属性,在init()方法被调用后手动注入servletConfig对象到ServletConfig属性中。 image.png 由此得来了GenericServlet,我们只要继承该类,上面的工作都不需要我们去做了,并且只需要实现service方法即可,还能得到ServletConfig的诸多可调用方法。需要注意的是GenericServlet是抽象类,只有service是抽象方法,其他是空实现。继承后只需覆盖service,其他方法看情况选择性覆盖。
import java.io.IOException;
import java.io.Serializable;
import java.util.Enumeration;

public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {
    private static final long serialVersionUID = 1L;
    private transient ServletConfig config;

    public GenericServlet() {
    }

    public void destroy() {
    }

    public String getInitParameter(String name) {
        return this.getServletConfig().getInitParameter(name);
    }

    public Enumeration<String> getInitParameterNames() {
        return this.getServletConfig().getInitParameterNames();
    }

    public ServletConfig getServletConfig() {
        return this.config;
    }

    public ServletContext getServletContext() {
        return this.getServletConfig().getServletContext();
    }

    public String getServletInfo() {
        return "";
    }

    public void init(ServletConfig config) throws ServletException {
        this.config = config;
        this.init();
    }

    public void init() throws ServletException {
    }

    public void log(String msg) {
        this.getServletContext().log(this.getServletName() + ": " + msg);
    }

    public void log(String message, Throwable t) {
        this.getServletContext().log(this.getServletName() + ": " + message, t);
    }

    public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

    public String getServletName() {
        return this.config.getServletName();
    }
}

3.继承javax.servlet.http.HttpServlet类 这是一个更为强大的Servlet类,与HTTP协议相关,当接收到请求时,会由service方法判断请求方式,依此来选择调用doget方法或者dopost方法。

由下图可以看出HttpServlet继承了GenderServlet并将原来的ServletRequest和ServletResponse强制转换成了与HTTP协议相关的request和response。 image.png

时序图 image.png

2 Servlet处理请求

2.1 为什么要使用一个Servlet来处理多个请求?

当浏览器发送了一次请求到服务器时,servlet容器会根据请求的url-pattern找到对应的Servlet类,执行对应的doPost或doGet方法,再将响应信息返回给浏览器,这种情况下,一个具体的Servlet类只能处理对应的web.xml中配置的url-pattern请求,一个Servlet类,一对配置信息。如果业务扩展,需要三个Servlet来处理请求,就需要再加上两个具体的Servlet类,两对配置信息,如果继续向上扩展,就会写n个Servelt类,在web.xml中配置n次ServletConfig,效率非常低下,并且会浪费更多的资源.

为了避免重复的操作(多次编写配置文件,多次新建具体的Servlet类)影响效率,就衍生出一套简单的操作来提高效率,一次配置,多次使用;一个Servlet具体类,处理多个请求。

2.2 解决思路

  1. 根据请求的地址,截取其中的具体方法名,然后使用if-else判断匹配,再执行具体的方法。
  2. 根据截取出来的方法名,使用反射,来执行具体的方法。

显然第一种方法当方法很多的时候,会很繁琐。故此肯定选择利用反射的方式来解决。

2.3 BaseServlet

public abstract class BaseServlet extends HttpServlet {
	public void service(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		/*
		 * 1. 获取参数,用来识别用户想请求的方法
		 * 2. 然后判断是哪一个方法,是哪一个我们就调用哪一个
		 */
		String methodName = req.getParameter("method");
		
		if(methodName == null || methodName.trim().isEmpty()) {
			throw new RuntimeException("您没有传递method参数!无法确定您想要调用的方法!");
		}
		
		/*
		 * 得到方法名称,是否可通过反射来调用方法?
		 * 1. 得到方法名,通过方法名再得到Method类的对象!
		 *   * 需要得到Class,然后调用它的方法进行查询!得到Method
		 *   * 我们要查询的是当前类的方法,所以我们需要得到当前类的Class
		 */
		Class c = this.getClass();//得到当前类的class对象
		Method method = null;
		try {
			// c.getMethod(name, parameterTypes):需要传入方法名以及方法的参数类型
			method = c.getMethod(methodName, 
					HttpServletRequest.class, HttpServletResponse.class);
		} catch (Exception e) {
			throw new RuntimeException("您要调用的方法:" + methodName + "(HttpServletRequest,HttpServletResponse),它不存在!");
		}
		
		/*
		 * 调用method表示的方法
		 */
		try {
			String result = (String)method.invoke(this, req, resp);
			/*
			 * 获取请求处理方法执行后返回的字符串,它表示转发或重定向的路径!
			 * 帮它完成转发或重定向!
			 */
			/*
			 * 如果用户返回的是字符串为null,或为"",那么我们什么也不做!
			 */
			if(result == null || result.trim().isEmpty()) {
				return;
			}
			/*
			 * 查看返回的字符串中是否包含冒号,如果没有,表示转发
			 * 如果有,使用冒号分割字符串,得到前缀和后缀!
			 * 其中前缀如果是f,表示转发,如果是r表示重定向,后缀就是要转发或重定向的路径了!
			 */
			if(result.contains(":")) {
				// 使用冒号分割字符串,得到前缀和后缀
				int index = result.indexOf(":");//获取冒号的位置
				String s = result.substring(0, index);//截取出前缀,表示操作
				String path = result.substring(index+1);//截取出后缀,表示路径
				if(s.equalsIgnoreCase("r")) {//如果前缀是r,那么重定向!
					resp.sendRedirect(req.getContextPath() + path);
				} else if(s.equalsIgnoreCase("f")) {
					req.getRequestDispatcher(path).forward(req, resp);
				} else {
					throw new RuntimeException("你指定的操作:" + s + ",当前版本还不支持!");
				}
			} else {//没有冒号,默认为转发!
				req.getRequestDispatcher(result).forward(req, resp);
			}
		} catch (Exception e) {
			System.out.println("您调用的方法:" + methodName + ", 它内部抛出了异常!");
			throw new RuntimeException(e);
		}
	}
}

当我们在使用SpringMVC的时候,一个Controller下面就可以写多个方法处理不同的请求,还能自动封装入参,而使用原生Servlet的时候则需要我们手动封装入参,还要写个BaseServelt来使一个Servelt可以处理多个请求。 嗯,框架确实减少了我们的工作量,使得我们能够专注于业务层面的工作。