从servlet开始说起

484 阅读4分钟

如何理解servlet

servlet是一个基于java实现的和平台无关的web组件,说人话就是其实他封装了客户端请求和响应的细节,降低我们的开发难度,我们编写的一个个servlet不就是为了处理并响应客户端发来的一个个请求吗,里面包含了任意我们想在服务端做的操作,该组件由servlet容器托管,也叫做web容器,比如耳熟能详的tomcat,所有的servlet容器实现都必须严格遵守servlet规范。 所有 Servlet 容器必须支持基于 HTTP 协议的请求/响应模型,而且应该让servlet组件运行在一个安全的环境中。
一个典型的流程就是:

  • web容器接收原始的http请求,然后将请求封装为servet认识的serveltRequest
    
  • 然后容器根据请求找到对应的servlet实现,将请求交给该实现来处理
    
  • 等到servlet处理完请求然后将响应交给容器
    

servet的顶级接口便是javax.servlet.servlet,该接口提供了5个方法,其javadoc也很明确的表明了init(),service(),destory()这3个方法都是由容器来调用,我们无需干扰,只需要给容器配置好相应的资源文件,然后启动容器就行了。

  • init(ServletConfig config) servlet规范要求容器在实例化一个servlet之后立刻调用该方法,并且必须保证在接收请求之前该方法一定是成功完成的,但是可以通过load-on-startup来决定该servlet是否延迟加载
  • service(ServletRequest req, ServletResponse res) 对某个servlet的请求到达容器之后都会调用该方法进行处理,同时并进行响应
  • destory(),当某个servlet的生命周期结束后都会执行该方法,释放相应资源 然后还有一个getServletConfig()方法,这个就是给我们使用者返回某个servelt()的详细信息的,比如名称,初始化参数,以及上下文参数,其中这个上下文ServletContext很重要,后面再说

一个简单的例子

先通过一个简单的例子来直观感受一下servlet具体的表现,这里引入javax.servlet-api这个依赖就行了,(先别引入太多其他比如springmvc的东西,不然点开顶级接口看实现会发现有很多,你都不知道看哪一个),然后可以看到Servlet的实现只有两个,分别是HttpServletGenericServlet,而GenericServlet里面啥也没做,就只是里面保存了init方法传入的ServletConfig,不用想这个config里面的东西自然就是容器帮我们从某个地方读取并通过反射写进来的,后面再分析这个实现。所以就只有去看HttpServlet了,可以看到这里面重写了service方法,而前面说过该方法会在客户端请求某一个servlet的时候会调用,所以我们的处理逻辑就只需要重写该方法就行了,但是HttpServlet又帮我们处理了一番,可以看到这里面有很多doXX方法,这不就是对应的http请求方式吗,看其实现确实是根据不同的请求方式调用不同的具体实现,这也侧面说明了一个servlet可以同时处理所有仅有请求方式不同的所有请求所以我们只需要重写对应的doXX方法就行了。

package me.ppx.mvc;

import com.google.gson.Gson;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;

public class WelcomeServlet extends HttpServlet {
    private final Gson gson = new Gson();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse response) throws ServletException, IOException {
        ServletConfig config = getServletConfig();
        ServletContext context = config.getServletContext();
        HashMap<String, String> map = new HashMap<>();

        map.put("servletName", config.getServletName());
        map.put("initParameterNames", gson.toJson(config.getInitParameterNames()));
        map.put("contextPath",context.getContextPath());
        map.put("serverInfo",context.getServerInfo());
        map.put("reqPathInfo",req.getPathInfo());
        map.put("reqMethod",req.getMethod());
        map.put("reqURI",req.getRequestURI());
        map.put("reqAddr",req.getRemoteAddr());

        response.setContentType("application/json");
        response.setCharacterEncoding("UTF-8");
        response.getWriter().write(gson.toJson(map));
        System.out.println("queryString: "+req.getQueryString());
    }
}

这就是一个简单的servlet例子,配置好tomcat和contextPath(URI=contextPath+url-pattern),同时将这个servlet通过web.xml进行注册

<servlet>
    <servlet-name>welcomeServlet</servlet-name>
    <servlet-class>me.ppx.mvc.WelcomeServlet</servlet-class>
</servlet>

<servlet-mapping>
    <servlet-name>welcomeServlet</servlet-name>
    <url-pattern>/welcome</url-pattern>
</servlet-mapping>

然后就能直接访问localhost:8080/URI,当然还可以直接使用注解完成servlet注册,@WebServlet("/url-pattern"),这样就不用在web.xml里面进行配置,到此一个最简单的java web服务器就算搭建好了

一个超级重要的伏笔

由于servlet3.0规范推出来至今,算是最普及的,其中里面新增了一个接口ServletContainerInitializer,通过SPI的形式暴露给其他需要的第三方组件,可以用来注册三大组件,Servlet,Filter,Listener,无须在web.xml里面配置,接入方只需要在自己的文件中按规定声明一个文件,META-INF/services/javax.servlet.ServletContainerInitializer,同时将自己的实现类的全类名写到文件中,然后Servlet容器在启动的时候就会扫描所有jar包中的该文件内容并将其实例化,并调用他的onStartup()方法,同时借助@HandlesTypes注解可以传入某个接口的所有实现类作为该方法的参数,这个参数c就是传进来的这个注解指定接口的所有实现类

public interface ServletContainerInitializer {
    public void onStartup(Set<Class<?>> c, ServletContext ctx)
        throws ServletException; 
}

看个简单的例子

image.png 最后的输出结果包含了抽象类在内的所有实现

image.png 仅仅作为一个伏笔,先了解一下就好