URL 映射

Nov 2, 2017 10:31:32 PM

作者:zozoh

为什么需要详细描述 URL 映射

Nutz.Mvc 的核心任务就是将 HTTP 请求的 URL 映射到某一个入口函数,如果你看完了 Nutz.Mvc 概述你大概应该知道,映射的配置信息是通过注解 @At 来设置的,@At 注解也非常简单,配置起来应该没有什么障碍。

但是,依然在某些时候,你会在你的应用出现 404 错误,为了能让你在编写项目是,心里非常有底,这里将详细的解释一下JSP/Servlet 以及 Nutz.Mvc 映射部分的工作原理,在你遇到讨厌的 404 时,只要通读本文,相信就能找到问题的症结。

什么是 URL

任何 URL 都由如下部分组成

http://**www.myapp.com/app/module/action.suffix**

  • http:// - 协议,也可以是 https://
  • www.myapp.com - 域名或者 IP 地址,由 DNS 服务器负责转发
  • /app - Context 的 path, 这个匹配到 server.xml 中每个 <context> 的 path 属性,由 HTTP 服务负责转发
  • /module**/action**.suffix - 从这里以后的匹配将交给相应的 Context 的 web.xml, 由HTTP 服务器根据 web.xml 来转发
    因此,我们主要研究的就是 /module**/action**.suffix 的部分是如何转发的。

web.xml 中的映射 - url-pattern

通常,在 web.xml 中,我们可以声明各种 HttpServlet 子类, 为了能让某一个子类接受 URL, 就需要配置映射,众所周知你可以通过 <servlet-mapping>,为你的 Servelet 增加一组至多组的配置:

  1. <servlet-mapping>
  2. <servlet-name>MyServletName</servlet-name>
  3. <url-pattern>/*</url-pattern>
  4. </servlet-mapping>

同样,根据 Servlet 的规范,你的 <url-pattern> 可以有如下几种形式的值:

假设你的 Context 的 URL 为 : http://localhost:8080/testweb

web.xml 中的全匹配 - /*

转发到本 Context 的任何请求都会调用这个 Servlet,比如:

  • /abc
  • /abc**/dosome**
  • /abc**/dosome**.nut
  • /index**.jsp**
  • /img**/logo**.png
    如果请求为:
  1. http://localhost:8080/testweb/abc/getlist.nut

调用 request 对象不同方法将会返回的值:

req.getRequestURL() "http://localhost:8080/testweb/abc/getlist.nut&#34;
req.getRequestURI() "/testweb/abc/getlist.nut"
req.getPathInfo() "/abc/getlist.nut"
req.getServletPath() ""

web.xml 中的目录匹配 - /abc/*

这个方式仅对于NutServlet有效!!!

转发到本 Context 的任何请求只要以 /abc/ 开头,都会调用这个 Servlet,比如:

  • /abc**/dosome**
  • /abc**/dosome**.nut
  • /abc**/index**.jsp
  • /abc**/logo**.png
    如果请求为:
  1. http://localhost:8080/testweb/abc/getlist.nut

调用 request 对象不同方法将会返回的值:

req.getRequestURL() "http://localhost:8080/testweb/abc/getlist.nut&#34;
req.getRequestURI() "/testweb/abc/getlist.nut"
req.getPathInfo() "/getlist.nut"
req.getServletPath() "/abc"

因此我们可以认为, req.getPathInfo() 的值是:

  1. http://localhost:8080/testweb/abc/getlist.nut
  2. ---------------------------------^ 匹配 /abc/*,从这个位置之后的字符串

web.xml 中的后缀匹配 - *.nut

转发到本 Context 的任何请求只要以 .nut 结尾,都会调用这个 Servlet,比如:

  • /abc**/dosome**.nut
  • /abc**.nut**
    如果请求为:
  1. http://localhost:8080/testweb/abc/getlist.nut

调用 request 对象不同方法将会返回的值:

req.getRequestURL() "http://localhost:8080/testweb/abc/getlist.nut&#34;
req.getRequestURI() "/testweb/abc/getlist.nut"
req.getPathInfo() null
req.getServletPath() "/abc/getlist.nut"

web.xml 中的精确匹配 - /abc/getlist.nut

转发到本 Context 的任何请求必须为 /abc/getlist.nut,才会调用这个 Servlet

如果请求为:

  1. http://localhost:8080/testweb/abc/getlist.nut

调用 request 对象不同方法将会返回的值:

req.getRequestURL() "http://localhost:8080/testweb/abc/getlist.nut&#34;
req.getRequestURI() "/testweb/abc/getlist.nut"
req.getPathInfo() null
req.getServletPath() "/abc/getlist.nut"

在 Nutz.Mvc 中的映射

Nutz.Mvc 通过 org.nutz.mvc.NutFilter 类将自己同一个 JSP/SERVLET 容器挂接关于挂接的方法,详细请看 如何配置 web.xml

在设计这个框架之初,我们有一个基本的设计要求:

如果用户修改了 web.xml 或者 server.xml,不需要用户重新修改 Nutz.Mvc 相关的配置

对于任意一个请求:

  • http://**www.myapp.com/app/module/action.suffix**
    Nutz.Mvc 匹配的时候,只会关心这个部分:

  • /module**/action
    发现了吗?是的,它对
    .suffix 不敏感,匹配之前,它会把 .suffix** 切去。之后,它会通过注解 '@At' 寻找合适的入口函数,

如何通过 @At 寻找入口函数

Nutz.Mvc 概述里,我提到,@At 注解可以被声明在三个地方:

  1. 主模块 - @At("/a")
  2. 子模块 - @At("/b")
  3. 入口函数 - @At("/c")

最终确定了 URL /a/b/c 要匹配的入口函数。

所以要想匹配 /a/b/c 下面几种形式都是可以的

  1. @At("/a")
  2. public class MainModule{
  3. ...
  4. @At("/b")
  5. public class SubModule{
  6. ...
  7. @At("/c")
  8. public String helle(){
  9. ...

或者

  1. public class MainModule{
  2. ...
  3. @At("/a")
  4. public class SubModule{
  5. ...
  6. @At("/b/c")
  7. public String helle(){
  8. ...

或者

  1. public class MainModule{
  2. ...
  3. public class SubModule{
  4. ...
  5. @At("/a/b/c")
  6. public String helle(){
  7. ...

当然,一般的说,很少有人在主模块上声明 @At, 严重不建议这样做!!!

注: 使用不带参数的@At()注解, 默认会使用方法名/类名的小写做为入口地址!

  1. // 比如
  2. @At
  3. public String getPet() {
  4. ...
  5. // 与下面的代码是等效的
  6. @At("/getpet")
  7. public String getPet() {
  8. ...

通过上面的内容我们可以知道,只要有一个 URL,我们就知道如何设置注解 '@At',但是你确定我们要匹配的 URL 就是

  • /module**/action**
    吗? 不,这同时也得参考 web.xml 的匹配方式:

假设你的 Context 的 URL 为 : http://localhost:8080/testweb

Nutz 中的全匹配 - /*

如果请求为:

  1. http://localhost:8080/testweb/abc/getlist.nut

对于 Nutz.Mvc 我们需要匹配的部分为:

  • /abc/getlist

Nutz 中的目录匹配 - /abc/*

仅对NutServlet有效!!

如果请求为:

  1. http://localhost:8080/testweb/abc/getlist.nut

对于 Nutz.Mvc 我们需要匹配的部分为:

  • /getlist
    这里需要说明的是,可能人们会怀疑,为什么目录匹配的时候,只匹配 getlist 而不匹配 abc/getlist 呢?因为,你在你的 web.xml 声明了这样的 url-pattern:
  1. ...
  2. <url-pattern>/abc/*</url-pattern>
  3. ...

显然,你希望在 web.xml 来决定你的 URL 前面那部分,所以后面一部分就交给 Nutz.Mvc 来匹配吧。否则,你修改了web.xml 的时候,你还需要修改 Nutz.Mvc 的配置,这与显然我们设计的初衷不符,Nutz.Mvc 设计的基本要求就是:

如果用户修改了 web.xml 或者 server.xml,不需要用户重新修改 Nutz.Mvc 相关的配置

Nutz 中的后缀匹配 - *.nut

如果请求为:

  1. http://localhost:8080/testweb/abc/getlist.nut

对于 Nutz.Mvc 我们需要匹配的部分为:

  • /abc/getlist

Nutz 中的精确匹配 - /abc/getlist.nut

如果请求为:

  1. http://localhost:8080/testweb/abc/getlist.nut

对于 Nutz.Mvc 我们需要匹配的部分为:

  • /abc/getlist
    这种映射方式基本是不会发生的,因为你需要让 Nutz.Mvc 控制一批 URL 而不是单个 URL。所以,你如果选择了这种模式我就没话讲了,在 Nutz.Mvc 中你就全部匹配吧,惩罚你,哼!

Nutz 中的Mvc注解继承

自1.r.55 起,Nutz中加入了注解继承功能,举个栗子:

  1. // 父类
  2. @Ok("jsp")
  3. public abstract class RestfulAction{
  4. @At @GET
  5. public void list() {
  6. }
  7. @At @GET @Ok("json")
  8. public QueryResult search() {
  9. return null;
  10. }
  11. }
  12. // 子类
  13. @At("/items") //父类的@Ok("jsp")在这里生效
  14. public class MyAction extends RestfulAction {
  15. @Override
  16. public void list() {} //父类的@At @GET在这里生效
  17. @Override
  18. public QueryResult search() {} //父类的@At @GET @Ok("json")在这里生效
  19. }

看到了吧,是不是很容易,父类的所有Mvc注解,都对应的在子类上生效,这个功能的初衷是可以让你少写那么一点配置信息,毕竟整天搬砖都是CRUD,虽然Nutz已经足够简便了,但配置写多了也会吐 ^_^注意:这个版本中,子类还是要复写一下父类的方法,才能获得对应的入口方法映射,复写哪个,就会获得哪个

自1.r.61起,wendal 加入了默认继承所有父类入口方法的功能,比如上面的例子,父类不变,子类这样写

  1. @At("/items")
  2. public class MyAction extends RestfulAction {
  3. }

即可继承两个入口方法:

  • /items/list
  • /items/search

鉴于默认继承的特性,杀伤力太大,所以1.r.63中,增加了入口方法决断机制你只要自己实现一个org.nutz.mvc.EntryDeterminer,然后在MainModule上申明下注解@Determiner使用它就好了,比如这样:

  1. @Determiner(MyDeterminer.class)
  2. public class MainModule {}

当然,EntryDeterminer也支持从Ioc中获取,像这样

  1. @Determiner(value = MyDeterminer.class, args = {"ioc:myDeterminer"})
  2. public class MainModule {}

其中 "myDeterminer" 就是你这个决断期在 Ioc 容器中的名字。

本页面的文字允许在知识共享 署名-相同方式共享 3.0协议GNU自由文档许可证下修改和再使用。

原文: http://nutzam.com/core/mvc/url_mapping.html