servlet基础浅谈

本篇将介绍如下几点java

  1. 介绍Servlet为什么物?
  2. servlet拥有什么样的生命周期,做为开发者能够利用生命周期作点什么?
  3. 在整个应用的启动、运行、销毁时,servlet技术对外暴露了什么样的事件钩子,让开发者能够干预利用这些事件?
  4. servlet、filter、listener如何经过配置让servlet容器来感知并利用?
  5. spring是如何基于这些知识点构建起一个MVC框架的?

什么是servlet?

Servlet是基于Java技术的web组件,容器托管的,用于生成动态内容。像其余基于Java的组件技术同样, Servlet也是基于平台无关的Java类格式,被编译为平台无关的字节码,能够被基于Java技术的webserver 动态加载并运行。容器(平时咱们所使用的tomcat就是其中一种servlet容器),有时候也叫作servlet引擎,是webserver为支持servlet功能扩展的部分。客户端 经过Servlet容器实现的请求/应答模型与Servlet交互。(引用自oracle官方servlet3.1规范文档)web

servlet如何处理一个客户端请求?

servlet的生命周期?

servlet按照一个严格定义的生命周期被管理,该生命周期包括:如何被加载?实例化?初始化?处理客户端请求?什么时候结束服务? 该生命周期能够经过Servlet接口中的API来表示:init、service、destroyspring

加载和实例化阶段

servlet容器负责加载和实例化servlet,加载和实例化能够发生在容器启动时,或者延迟初始化直到容器有请求须要处理时。(经过开发者配置来肯定)编程

初始化阶段

servlet容器必须在处理客户端请求以前,对servlet实例进行初始化(即调用Servlet.init接口)。能够完成一些读取持久化配置数据、初始化资源等一次性的动做。tomcat

处理客户端请求

完成初始化以后,servlet容器可使用该servlet来处理客户端请求。(容器经过开发者的配置,即servlet-mapping来寻找适合当前请求的servlet)客户端请求由ServletRequest类型来封装表示、Servlet响应由ServletResponse类型来封装表示。这两个类型的对象都由容器进行实例化,在调用Servlet处理客户端请求时传递给Servlet的service方法。在Http请求的场景下,容器提供的实现对应为HttpServletRequest、HttpServletResponse。一个servlet实例应对多个客户端请求的状况,致使了咱们须要在处理请求时保证线程安全。安全

servlet技术中的其余组件?

在servlet技术中,除了Servlet接口用于处理请求这个组件接口外,还存在Filter、Listener这两个重要的组件接口。 其中Filter是一种代码重用的技术,运行运行过程当中改变进入资源的请求和资源返回的响应中的有效负载和header信息。便可以在分发请求给servlet处理以前对请求进行拦截,以后再servlet完成处理,返回响应后对响应进行拦截。能够用于日志记录、验证等需求。 和servlet生命周期同样,应用一样存在生命周期。监听应用生命周期事件可让开发人员更好的控制ServletContext、HTTPSession和ServletRequest的生命周期,能够更好的进行代码分解。Servlet事件监听器支持在ServletContext、HTTPSession和ServletRequest状态改变时进行事件通知。oracle

Filter

实现本身的Filter能够经过实现接口javax.servlet.Filter来完成,以后经过web.xml或者注解配置到Servlet容器中,让容器在处理请求时应用此时配置的Filterapp

public class FirstFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println(getClass().getName() + " init()");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println(getClass().getName() + " doFilter()");
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {

    }
}
复制代码

Listener

Listener的种类能够分为:框架

ServletContext相关

  • ServletContextListener,spring中利用该监听器初始化web应用
  • ServletContextAttributeListener

HttpSession相关

  • HttpSessionListener
  • HttpSessionAttributeListener
  • HttpSessionIdListener
  • HttpSessionActivationListener
  • HttpSessionBindingListener,这个监听器跟HttpSessionAttributeListener有点相似,可是HttpSessionAttributeListener是针对全部HttpSession#setAttribute.setAttribute(key, value)而言的,value能够是任意值,而且经过@WebListener注册到容器。而HttpSessionBindingListener是针对HttpSession#setAttribute.setAttribute(key, value),当value为HttpSessionBindingListener的实现类实例才会调用事件方法,无需经过@WebListener配置到容器

ServletRequest相关

  • ServletRequestListener
  • ServletRequestAttributeListener
  • AsyncListener,监听异步事件,超时、链接终止、完成异步处理

如何映射请求到servlet?

URL路径映射优先级

在收到客户端请求时,web容器肯定转发到哪一个web应用(获取servlet上下文路径),以后用于映射到servlet的路径是请求对象的请求URL减去上下文和路径参数部分,以后应用如下步骤来找出servlet来处理请求,短路原则,一旦找到匹配的servlet,以后的步骤直接跳过异步

  1. 精确匹配
  2. 容器递归地尝试匹配最长路径前缀,用"/"字符做为路径分隔符,最长匹配肯定选择的servlet
  3. 若是URL最后一部分包含一个扩展名,servlet容器将试图匹配为扩展名处理请求的servlet。
  4. 若是前三个原则都没法找出一个servlet来处理请求,则交给"default"servlet来处理,即servlet-mapping为"/"的servlet。

配置Listener、Filter、Servlet

使用注解的方式来配置容器,下面看看若是使用:

  1. @WebListener来配置Listener
  2. @WebServlet配置Servlet
  3. @WebFilter配置Filter

经过注解@WebListener,结合ServletContext编程式API来注册Servlet、Filter、Listener

经过@WebListener注解配置ServletContextListener实现类,在容器初始化servlet上下文时,调用ServletContext的API来注册

@WebListener
public class FirstServletContextListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        ServletContext servletContext = sce.getServletContext();
        ServletRegistration.Dynamic dynamic = servletContext.addServlet("firstServlet", FirstServlet.class);
        dynamic.addMapping("/first", "/first/*");
        dynamic.setLoadOnStartup(1);

        ServletRegistration.Dynamic dynamic2 = servletContext.addServlet("secondServlet", SecondServlet.class);
        dynamic2.addMapping("/second", "/second/*");
        dynamic2.setLoadOnStartup(1);

        // 异步servlet
        ServletRegistration.Dynamic firstAsyncServlet = servletContext.addServlet("firstAsyncServlet", FirstAsyncServlet.class);
        firstAsyncServlet.setLoadOnStartup(1);
        firstAsyncServlet.setAsyncSupported(true);
        firstAsyncServlet.addMapping("/async/first", "/async/first/*");

        // TODO Filter调用顺序???
        FilterRegistration.Dynamic filterRegistration = servletContext.addFilter("secondFilter", SecondFilter.class);
        filterRegistration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD),
                true, "/first");

        FilterRegistration.Dynamic filterDynamic = servletContext.addFilter("firstFilter", FirstFilter.class);
        filterDynamic.addMappingForUrlPatterns(
                EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD), false, "/first");


        // 非阻塞IO servlet
        ServletRegistration.Dynamic firstNoBIOServlet = servletContext.addServlet("firstNoBIOServlet", FirstNoBlockIOServlet.class);
        firstNoBIOServlet.setLoadOnStartup(1);
        firstNoBIOServlet.setAsyncSupported(true);
        firstNoBIOServlet.addMapping("/nobio/first", "/nobio/first/*");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {

    }
}
复制代码

利用基于SPI机制的ServletContainerInitializer来初始化容器

  1. 实现接口ServletContainerInitializer,用@HandlesTypes注解在类级别上指定初始化类
  2. 在META-INF/services/javax.servlet.ServletContainerInitializer文件中指定实现类 完成以上两步以后,容器在启动时,将回调实现类的public void onStartup(Set<Class<?>> c, ServletContext ctx)方法,参数是容器为咱们收集的在classpath下全部HandlesTypes注解指定的类。具有了这些条件,咱们就能够在该方法中,调用这些类来初始化容器了(映射servlet、配置filter、配置Listener等等)

为何可使用注解直接来配置Listener、Filter、Servlet,又提供了ServletContainerInitializer这种初始化容器的机制?

  1. 使用注解的方式,不可避免的须要扫描全部classpath下的全部类,为了提升启动速度采用ServletContainerInitializer机制
  2. 若是部署的应用中,存在web.xml(部署描述文件,在servlet2.5以前必须存在)。若是web.xml文件中指定了metadata-complete="true"时,将不会启用注解扫描的配置方式。为了兼容性,提倡使用ServletContainerInitializer机制来初始化容器。(这也是spring的作法)

springMVC是如何应用servlet技术的

在spring中利用基于SPI机制的ServletContainerInitializer来初始化容器,具体实现方式是:

  1. 类SpringServletContainerInitializer实现了接口ServletContainerInitializer,并指定了WebApplicationInitializer做为初始化类(AbstractAnnotationConfigDispatcherServletInitializer做为其便利的抽象类,开发者能够继承该类做为初始化容器的配置类)。如:
public class WebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        // 指定ROOT ApplicationContext的配置类
        return new Class[]{RootConfig.class};
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        // 指定Web ApplicationContext的配置类
        return new Class[]{WebConfig.class};
    }

    @Override
    protected String[] getServletMappings() {
        // 指定DispatcherServlet的servlet-mapping,此处指定为default servlet。任何未找到映射的请求都会由DispatcherServlet来处理请求
        return new String[]{"/"};
    }

    @Override
    protected Filter[] getServletFilters() {
        // 配置Filter
        return super.getServletFilters();
    }
}
复制代码
  1. spring中利用监听器ServletContextListener初始化web应用的父ApplicationContext

总结

这篇文章中梳理了Servlet中的经常使用技术,主要涉及Servlet、Filter、Listener的知识点和配置细节。以后引伸出springMVC是如何利用这些知识点来构建一个web框架的。servlet做为java web开发中的基石是每一个开发者都必须掌握的技能。关于springMVC中的更多原理细节将在后续文章整理发出,期待你的关注

相关文章
相关标签/搜索