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 增加一组至多组的配置:
<servlet-mapping>
<servlet-name>MyServletName</servlet-name>
<url-pattern>/*</url-pattern>
</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
如果请求为:
http://localhost:8080/testweb/abc/getlist.nut
调用 request 对象不同方法将会返回的值:
req.getRequestURL() | "http://localhost:8080/testweb/abc/getlist.nut" |
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
如果请求为:
http://localhost:8080/testweb/abc/getlist.nut
调用 request 对象不同方法将会返回的值:
req.getRequestURL() | "http://localhost:8080/testweb/abc/getlist.nut" |
req.getRequestURI() | "/testweb/abc/getlist.nut" |
req.getPathInfo() | "/getlist.nut" |
req.getServletPath() | "/abc" |
因此我们可以认为, req.getPathInfo() 的值是:
http://localhost:8080/testweb/abc/getlist.nut
---------------------------------^ 匹配 /abc/*,从这个位置之后的字符串
web.xml 中的后缀匹配 - *.nut
转发到本 Context 的任何请求只要以 .nut 结尾,都会调用这个 Servlet,比如:
- /abc/dosome.nut
- /abc.nut
如果请求为:
http://localhost:8080/testweb/abc/getlist.nut
调用 request 对象不同方法将会返回的值:
req.getRequestURL() | "http://localhost:8080/testweb/abc/getlist.nut" |
req.getRequestURI() | "/testweb/abc/getlist.nut" |
req.getPathInfo() | null |
req.getServletPath() | "/abc/getlist.nut" |
web.xml 中的精确匹配 - /abc/getlist.nut
转发到本 Context 的任何请求必须为 /abc/getlist.nut,才会调用这个 Servlet
如果请求为:
http://localhost:8080/testweb/abc/getlist.nut
调用 request 对象不同方法将会返回的值:
req.getRequestURL() | "http://localhost:8080/testweb/abc/getlist.nut" |
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 注解可以被声明在三个地方:
主模块 - @At("/a")
子模块 - @At("/b")
入口函数 - @At("/c")
最终确定了 URL /a/b/c 要匹配的入口函数。
所以要想匹配 /a/b/c 下面几种形式都是可以的
@At("/a")
public class MainModule{
...
@At("/b")
public class SubModule{
...
@At("/c")
public String helle(){
...
或者
public class MainModule{
...
@At("/a")
public class SubModule{
...
@At("/b/c")
public String helle(){
...
或者
public class MainModule{
...
public class SubModule{
...
@At("/a/b/c")
public String helle(){
...
当然,一般的说,很少有人在主模块上声明 @At, 严重不建议这样做!!!
注: 使用不带参数的@At()注解, 默认会使用方法名/类名的小写做为入口地址!
// 比如
@At
public String getPet() {
...
// 与下面的代码是等效的
@At("/getpet")
public String getPet() {
...
通过上面的内容我们可以知道,只要有一个 URL,我们就知道如何设置注解 '@At',但是你确定我们要匹配的 URL 就是
- /module /action
吗? 不,这同时也得参考 web.xml 的匹配方式:
假设你的 Context 的 URL 为 : http://localhost:8080/testweb
Nutz 中的全匹配 - /*
如果请求为:
http://localhost:8080/testweb/abc/getlist.nut
对于 Nutz.Mvc 我们需要匹配的部分为:
- /abc/getlist
Nutz 中的目录匹配 - /abc/*
仅对NutServlet有效!!
如果请求为:
http://localhost:8080/testweb/abc/getlist.nut
对于 Nutz.Mvc 我们需要匹配的部分为:
- /getlist
这里需要说明的是,可能人们会怀疑,为什么目录匹配的时候,只匹配 getlist 而不匹配 abc/getlist 呢?
因为,你在你的 web.xml 声明了这样的 url-pattern:
...
<url-pattern>/abc/*</url-pattern>
...
显然,你希望在 web.xml 来决定你的 URL 前面那部分,所以后面一部分就交给 Nutz.Mvc 来匹配吧。否则,你修改了
web.xml 的时候,你还需要修改 Nutz.Mvc 的配置,这与显然我们设计的初衷不符,Nutz.Mvc 设计的基本要求就是:
如果用户修改了 web.xml 或者 server.xml,不需要用户重新修改 Nutz.Mvc 相关的配置
Nutz 中的后缀匹配 - *.nut
如果请求为:
http://localhost:8080/testweb/abc/getlist.nut
对于 Nutz.Mvc 我们需要匹配的部分为:
- /abc/getlist
Nutz 中的精确匹配 - /abc/getlist.nut
如果请求为:
http://localhost:8080/testweb/abc/getlist.nut
对于 Nutz.Mvc 我们需要匹配的部分为:
- /abc/getlist
这种映射方式基本是不会发生的,因为你需要让 Nutz.Mvc 控制一批 URL 而不是单个 URL。所以,你如果选择了这种模式
我就没话讲了,在 Nutz.Mvc 中你就全部匹配吧,惩罚你,哼!
Nutz 中的Mvc注解继承
自1.r.55 起,Nutz中加入了注解继承功能,举个栗子:
// 父类
@Ok("jsp")
public abstract class RestfulAction{
@At @GET
public void list() {
}
@At @GET @Ok("json")
public QueryResult search() {
return null;
}
}
// 子类
@At("/items") //父类的@Ok("jsp")在这里生效
public class MyAction extends RestfulAction {
@Override
public void list() {} //父类的@At @GET在这里生效
@Override
public QueryResult search() {} //父类的@At @GET @Ok("json")在这里生效
}
看到了吧,是不是很容易,父类的所有Mvc注解,都对应的在子类上生效,这个功能的初衷是可以让你少写那么一点配置信息,毕竟整天搬砖都是CRUD,虽然Nutz已经足够简便了,但配置写多了也会吐 ^_^
注意:这个版本中,子类还是要复写一下父类的方法,才能获得对应的入口方法映射,复写哪个,就会获得哪个
自1.r.61起,wendal 加入了默认继承所有父类入口方法的功能,比如上面的例子,父类不变,子类这样写
@At("/items")
public class MyAction extends RestfulAction {
}
即可继承两个入口方法:
- /items/list
- /items/search
鉴于默认继承的特性,杀伤力太大,所以1.r.63中,增加了入口方法决断机制
你只要自己实现一个org.nutz.mvc.EntryDeterminer,然后在MainModule上申明下注解@Determiner使用它就好了,比如这样:
@Determiner(MyDeterminer.class)
public class MainModule {}
当然,EntryDeterminer也支持从Ioc中获取,像这样
@Determiner(value = MyDeterminer.class, args = {"ioc:myDeterminer"})
public class MainModule {}
其中 "myDeterminer" 就是你这个决断期在 Ioc 容器中的名字。