浅谈servlet-mapping的机制(二)

本篇文章将结合源代码介绍servlet-mapping的映射机制。

​1 DefaultServlet

Tomcat包中有个类的名字叫DefaultServlet,顾名思义就是默认的Servlet。那这个类有何作用呢?我们看下它的Javadoc描述:

DefaultServlet的javadoc

简单翻译下就是说DefaultServlet会默认映射到/路径下,它的作用是处理所有的静态资源请求。

Web应用启动后默认有两个文件夹下的文件是不能直接被客户端访问的,一个是META-INF,另一个是WEB-INF,其余的路径则可以被客户端正常访问。也就是说,DefaultServlet可以支持客户端通过request指定相对路径,访问除上述两个路径之外的所有其他路径的文件。可以看出来,DefaultServlet的访问权限其实还是蛮高的……

另外,这个/大家应该很熟悉。我们在Spring Web MVC中,会将DispatcherServletservlet-mapping配置成/。那这两者有什么关系呢?

@Override
protected String[] getServletMappings() {
  return new String[]{"/"};
}

我们在Spring Web MVC配置方式汇总(二)中曾有介绍过DispatcherServletWebApplicationContextServletContext三者之间的关系,结合上一篇文章中给出的Tomcat核心对象之间的关联,我们现在可以写一段测试代码,来看看Web应用启动后,它里面包含了哪些对象。

private void printServlet() {
  WebApplicationContext webApplicationContext = (WebApplicationContext)applicationContext;
  ServletContext servletContext = webApplicationContext.getServletContext();
  // servletContext.context.context.children --> 拿到所有的servlet对象
  ApplicationContext applicationContext = (ApplicationContext)getFieldValue("context", servletContext);
  StandardContext standardContext = (StandardContext)getFieldValue("context", applicationContext);
  HashMap children = (HashMap)getFieldValue("children", standardContext);
  children.forEach((key, value) -> {
    StandardWrapper servlet = (StandardWrapper)value;
    System.out.println("name: " + key + ", mapping: " + Arrays.toString(servlet.findMappings())  + ", type: " + servlet.getServletClass());
  });
}

上面这段代码,会将Tomcat中加载了的所有servlet的名称、类型、servlet-mapping打印出来。

当我们使用/DispatcherServlet进行配置时,输出的结果如下:

正常配置

可以看出来,DefaultServlet还是会被初始化,只不过servlet-mapping变成空的了。这意味着没有任何请求会转给DefaultServlet对象。

那若使用/*DispatcherServlet进行配置会出现什么情况呢?

使用/*进行配置

此时DefaultServlet对应的servlet-mapping/,而DispatcherServlet对应的则是/*。如果使用/**DispatcherServlet进行配置的话,则DispatcherServletservlet-mapping会是/**

那这些配置是不是都是有效配置,它们具体又有什么区别呢?

2 配置有效性

现在我们需要回头分析下上篇文章中提到的internalMapWrapper方法了。

这个方法的作用,是根据传入的request对象来匹配到处理该请求的具体的servlet。匹配逻辑为:

  1. 首先找request的请求地址和servlet的mapping值完全相同的(完全匹配);

  2. 若未找到,则使用前缀匹配算法,比如请求/foo/bar/hello.do会找到servlet-mapping是/foo/bar/*的servlet;

  3. 还未找到,则是用扩展名匹配算法查找,默认支持*.jsp和*.jpx;

  4. 还未找到,则检查request是不是/,若是则确认系统是否有配置欢迎页面,默认查找index.htmlindex.htmindex.jsp三个;

  5. 还未找到,则确认是否有配置/的Servlet,如果有则直接将请求交由它处理;

此处需要说明一点:Tomcat的映射支持使用*,但不支持使用**/*对于Tomcat来讲是前缀匹配/**则是完全匹配/**的这种配置,只能处理http://localhost:8080/**这一个请求,但**在Spring框架中又有着特殊含义,这样的请求基本可以认为是无法被框架正确处理的。因此在Spring框架中,使用/**来配置servlet-mapping其实是无效的。

3 应用场景

讨论Tomcat的DefaultServlet的特性的原因,主要是为了考虑在Spring Web MVC应用中该怎样处理静态资源。有三种处理静态资源的的备选方案:

3.1 使用ResourceHandler处理静态资源

如下是启用ResourceHandler处理静态资源的标准配置方法:

@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
  @Override
  public void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("/resources/**").addResourceLocations("/public/").setCachePeriod(31556926);
  } 
}

这种方式是向DispatcherServlet中注入了一个SimpleUrlHandlerMapping对象。它利用了DispatcherServlet类的doService方法在处理请求时,会根据条件选择合适的HandlerMapping对象,并调起该对象处理请求的机制。所以在访问静态资源时,SimpleUrlHandlerMapping最终会被调起,获取到静态资源并返回给客户端。

3.2 使用DefaultServletHttpRequestHandler

就算我们将DispatcherServlet映射到/上,Tomcat依然会创建一个DefaultServlet。可以通过在DispatcherServlet中注册一个RequestMapping对象,然后让该对象将request请求转给DefaultServlet。这样,即使DefaultSevlet没有注册任何servlet-mapping,依然会有request被转发给它。

根据这个思路,可以使用第二种处理静态资源的方法。Spring Web MVC框架已经将上述过程完整封装,只需使用configurer.enable()开启功能即可。

@Configuration
@EnableWebMvc
public class AppConfig implements WebMvcConfigurer {
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }
}

4.3 直接使用DefaultServlet访问静态资源

DispatcherServlet映射到/*,而不是/,这样DispatcherServletDefaultServlet便都会生效。因为/对应的是默认匹配规则,而/*对应的是前缀匹配,前缀匹配的优先级高于默认匹配,所以也能保证优先使用DispatcherServlet

但这有个弊端:

  • 请求http://localhost:8080/hello.pdf会被DispatcherServlet处理,因为/hello.jsp会匹配到/*【错误】;

  • 请求http://localhost:8080/static/hello.pdf会被DefaultServlet处理,因为/static/hello.jsp不能匹配"/*"正确】;

  • 请求http://localhost:8080/api/hello.do会给到DefaultServlet处理,但其不是一个静态资源的访问地址【错误】;

4 总结

结合上一篇文章的分析可得结论:

  1. DispatcherServlet应该直接映射到"/"上,其他的选项都存在问题;

  2. 静态资源的处理,优先使用ResourceHandler,其他的方式也都存在弊端

也就是说,虽然Tomcat仍然会初始化一个DefaultServlet,但它基本已经没啥用了,我们就直接忘了它吧。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

镜悬xhs

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值