nutz-MVC注解

Nutz.Mvc 根据 @At 注解,将一个 HTTP 请求映射到了一个函数,函数只有一个参数,会被路径参数 (1045) 填充。

  • Nutz.Mvc 最基本的想法,就是通过注解 @At 将一个HTTP请求路径同一个 Java 函数关联起来。
  • 并且,@At 支持你写多个路径

  • 声明了 @At 的函数被称为 入口函数
  • 任何一个请求,都会经过四道工序
    1. A - 过滤: 你通过 @Filters 注解可以为你的入口函数定义任意多的过滤器
    2. B - 适配: 这个过程将 HTTP 输入流转换成入口函数的参数数组
      • 默认的,它认为输入流是传统的名值对方式
      • 更多的适配方式请参看 关于适配器
      • 文件上传也是一种适配方式,请参看 UploadAdpto
    3. 什么是适配器?

      将 HTTP 参数转换成一个函数参数的过程是一个典型适配过程,执行这个过程的对象被称为适配器了。 Nutz.Mvc 提供了 org.nutz.mvc.HttpAdaptor 接口,隔离了这种行为。

      在每一个入口函数上,你都可以通过注解 @AdaptBy 来声明如何适配 HTTP 参数。当然,你 没必要在每一个入口函数上都声明,在子模块类上声明,或者在整个应用的主模块上声明均可。

      如何使用适配器?

      1.默认Nutz.Mvc 会采用 org.nutz.mvc.adaptor.PairAdaptor (也就是名值对的方式) 来适配你的 HTTP 参数。                                                                                                                                      2.通过构造函数获得适配器:通过 @AdaptBy 注解来改变任何一个入口函数的适配方式。                      
      @AdaptBy(type=JsonAdaptor.class)
       @AdaptBy 注解还支持一个属性 args,你可以通过这个属性 为你的适配器设置构造函数参数                   3.通过 Ioc 容器获得适配器:如果你希望你的适配器是交由 Ioc 容器管理的 
      @AdaptBy(type=JsonAdaptor.class, args={"ioc:objName"}) // 一定不可以是单例
         如果你的参数数组长度为一,并且,由 "ioc:" 开始,那么这个适配器会交付 Ioc 容器管理,你可以在容器的 配置文件中详细规定这个适配器的各个属性。当然,你需要在整个应用启用 Ioc 容器,
    4. 一个 Mvc 框架可以通过 Ioc 接口同一个 Ioc 容器挂接,挂接的方法很简单: 在主模块上声明 @IocBy
      @IocBy(args={"*js", "ioc/",
                   "*anno", "net.wendal.nutzbook",
                   "*async",
                   "*tx"
                   })
      public class MainModule {
          ...

      复合加载器的使用方法

      推荐使用ComboIocProvider,组合使用各种IocLoader.
       注意: IocBy的默认type就是ComboIocProvider,可以不写. 
      ComboIocLoader loader = new ComboIocLoader("*js",
                                                     "ioc/dao.js",
                                                     "ioc/service.js",
                                                     "*anno",
                                                     "com.myapp.module",
                                                     "com.myapp.service",
                                                     "*tx",
                                                     // @Async注解,异步执行.
                                                     "*async");

      由 Ioc 容器管理子模块

      通过 @IocBy 为整个应用声明了 Ioc 容器,那么如何使用呢。 实际上,你的每一个模块都可以来自容器,只要 你在模块上声明 @Inject。 当然,在主模块声明这个注解是没有意义的。

      @IocBean("petM")
      public class PetModule {
         ...
      如果你声明了这个注解, Nutz.Mvc 构造你的这个模块的时候,会通过 Ioc 容器获取,而不直接调用默认构造函数了
    5. 内置的适配器

      Nutz.Mvc 为你内置了 4 个最常用的适配器,可以让支持用如下四种方式适配 HTTP 参数:

      名值对 (默认) - PairAdaptor

      一般方式

      @AdaptBy(type=PairAdaptor.class)
      

      这种方式,是传统的 HTTP 参数方式。关键的问题是如何将 HTTP 的参数表同入口函数的参数对应起来。 为此,它支持一个新的注解 @Param,你可以:

      public String someFunc(    @Param("pid") int petId,
                              @Param("pnm") String petName){
          ...
      

      表单方式 - Form Bean

      有些时候,你需要入口函数接受一个对象,比如一个表单对象

      public String someFunc(    @Param("..") Pet pet){
          ...
      

      ".." 有特殊含义,表示当前的这个对象,需要对应整个的 HTTP 参数表。 所以, Nutz.Mvc 会将 HTTP 参数表中的 参数一个个的按照名字设置到 Pet 对象的字段里。 但是如果 Pet 对象的字段名同 HTTP 参数不符怎么办? 你可以在 Pet 字段上声明 @Param。

      前缀表单方式

      进行比较复杂的 HTTP 交互时,大家都比较偏爱名值对的方式提交数据,可能是因为数据组织比较方便 -- 通过<form> 即可。 但是如果在一个表单里混合上两个甚至多个表单项,那么 HTTP 的参数就会有点复杂,虽然这种情况下我更推荐采用 Json 输入流,但是并不是所有人都那么喜欢它,对吗?

      比如有一个表单,它希望提交两个对象的数据, User 以及 Department,这HTTP 请求的参数格式可能是这样的:

      user.id = 23
      user.name = abc
      user.age = 56
      
      dep.id = 15
      dep.name = QA
      
      dep.users[1].id = 23
      dep.users[1].name = abc
      dep.users[1].age = 56
      
      dep.users[10001].id = 22
      dep.users[10001].name = abcd
      dep.users[10001].age = 26
      
      dep.users:50001.id = 22
      dep.users:50001.name = abcd
      dep.users:50001.age = 26
      
      dep.children(abc).id = 13
      dep.children(abc).name = ABC
      
      dep.children(jk).id = 25
      dep.children(jk).name = JK
      
      dep.children.nutz.id = 1
      dep.children.nutz.name = NUTZ
      

      怎样在入口函数内声明这样的表单项呢?我们可以采用前缀方式:

      public String someFunc( @Param("::user.") User user,
                              @Param("::dep.") Department dept){
          ...
      }
      

      关键就是这个 `@Param("::user.")` 符号 '::' 表示这个参数是一个表单对象,并且它有统一的前缀 'user.' 表示前缀,Nutz.Mvc 会查看一下 User, Department 类所有的字段:

      public class User {
          private int id;
          private String name;
          private int age;
      }
      public class Department {
          private List<User> users;
          private Map<String, User> children;
      }
      

      那么, id 会对应到 HTTP 参数中的 'user.id', 其他的字段同理. 眼尖的你肯定发现了有点异样的地方, 对了, 那就是我们 nutz 对集合的支持. 在此, 你不仅可以对一般的属性进行注入, 还能对list, set, map集合以及对象数组进行注入. 在此我们提供了两种书写方式:

      1. 对象.list索引 = 值

        对象.list索引.属性 = 值

        对象.map(key) = 值 对象.map(key).属性 = 值

      2. 对象.list:索引 = 值

        对象.list:索引.属性 = 值

        对象.map.key = 值 对象.map.key.属性 = 值

      两种方式是完全等价的(小声透露一下, 其实代码里面就是把第一种方式转换成第二种方式实现的哦...). 并且都可以包含多层集合. 同时需要注意的是, 在进行 list 注入的时候需要注意, 出于内存方面的考虑, 所提供的 "索引" 只做为一个组装对象的参考字段(必需,不然不能组装对象), 不做为真实list的索引使用. 因此, list 的索引可以是任意大小的数字, 以及字符, 或字符串组成.

      从现在开始, nutz 参数的类型不再只支持单纯的 Object 对象注入了, 同时也提供了 List, Map, Set 以及对象数组. 

      更更更强大的功能, nutz开始支持泛型了, 直接来例子, 懒得解释:

          class Abc<T>{
              T obj;
          }
          class jk{
              String name;
          }
          public void test(@Param("::abc.")Abc<jk> abc){}
      

      如果要写test的参数, 你可以直接写 abc.obj.name = "nutz" , 我们的nutz就会非常智能的生成jk对象.

      混合方式

      值得一说的是,按照这个约定,实际上,一个入口函数,是可以支持多个 POJO 的,也可以写成这样

      public String someFunc(    @Param("pid") int petId,
                              @Param("..") Pet pet,
                              @Param("..") Food food){
          ...
      

      JSON 的支持

      你的 HTTP 参数也可以是一个 JSON 字符串

      public String someFunc(    @Param("pid") int petId,
                              @Param("pet") Pet pet,
                              @Param("foods") Food[] food){
          ...
      

      HTTP 参数的值都是字符串,比如上例的第二个参数,Nutz.Mvc 会看看 HTTP 参数表中的 "pet" 的值,如果它用 "{" 和 "}" 包裹,则会试图将其按照 JSON 的方式解析成 Pet 对象。当然,如果你传入的参数格式有问题,会解析失败,抛出异常。

      第三个参数,是一个数组,Nutz.Mvc 会看看 HTTP 参数表中的 "foods" 的值,如果用 "[" 和 "]" 包裹,则会试图将其 转换成一个数组。 如果你 JSON 字符串的格式有问题,它也会抛出异常。

      参数类型如果是列表(java.util.List),同数组的处理方式相同。但是它不知道列表元素的类型,所以转换出的元素只可能是

      • 布尔
      • 数字
      • 字符串
      • 列表
      • Map

      JSON 输入流 - JsonAdaptor

      如果你要通过 HTTP 传给服务器一个比较复杂的对象,通过名值对的方式可能有点不方便。因为它很难同时传两个对象。 并且一个对象如果还嵌入了另外一个对象,也很难传入,你必须要自己定义一些奇奇怪怪的格式,在 JS 里组织字符串, 在服务器端,手工解析这些字符串。

      针对这个问题, JSON 流是一个比 XML 流更好的解决方案,它足够用,并且它更短小。

      如果你的 HTTP 输入流就是一个 JSON 串,你可以这样:

      @AdaptBy(type=JsonAdaptor.class)
      public String someFunc( Pet pet ){
          ...
      

      如果你的 JSON 流是一个数组

      @AdaptBy(type=JsonAdaptor.class)
      public String someFunc( Pet[] pet ){
          ...
      

      如果你的 JSON 流类似:

      {
          fox : {
              name : "Fox",
              arg : 30
          },
          fox_food : {
              type : "Fish" ,
              price : 1.3
          }
      }
      

      你希望有两个 POJO (Pet 和 Food) 分别表示这两个对象,你可以:

      @AdaptBy(type=JsonAdaptor.class)
      public String someFunc(    @Param("fox") Pet pet,
                              @Param("fox_food") Food food){
          ...
      

      实际上,Nutz.Mvc 会将 HTTP 输入流解析成一个 Map,然后从 Map 里取出 "fox" 和 "fox_food" 这 两个子 Map,分别转换成 Pet 对象和 Food 对象。

      js通常这样写

      var data = {id:1,name:'陆离',age:19,sex:'女',relation:'媳妇'};
      $.ajax({
          url:'/HelloNutz/jsonAdapter',
          "data": JSON.stringify(data), // 注意要转为json,除非data本身就是json字符串
          dataType:'json',
          type : 'POST',
          success:function(re){
              console.log(re);
          }
      });
      

      什么都不干 - VoidAdaptor

      某些特殊的情况,你需要彻底控制输入流的解析,同时你又不想使用任何适配器,你可以

      @AdaptBy(type=VoidAdaptor.class)
      public String someFunc(HttpServletRequest req){
          ...
      

      VoidAdaptor 什么都不会干,不会碰 HTTP 请求对象的输入流。

      上传文件 - UploadAdaptor

      NutzMvc 内置了 org.nutz.mvc.upload.UploadAdaptor。关于文件上传详细的说明,请参看: 文件上传

      特殊参数

      某些时候,你可能需要得到 HttpSession,或者你需要得到 Ioc 容器的一个引用。因为你想做点更高级的 事情,你想出搞点小花样。Nutz.Mvc 完全支持你这样做。

      你只要在你的入口函数里声明你希望得到的对象类型即可,比如:

      @At("/myfunc")
      public String someFunc(    @Param("pid") int petId,
                              Ioc ioc,
                              HttpServletRequest req){
          ...
      
      • 第一个参数会从 HTTP 参数表中取出赋给入口函数
      • 第二个参数,Nutz.Mvc 会把自身使用的 Ioc 容器赋给入口函数,
      • 第三个参数,当前请求对象也会直接赋给入口函数。

      那么 Nutz.Mvc 到底支持多少类似这样的特殊参数类型呢?

      Nutz.Mvc 支持的特殊参数类型

      • ServletRequest & HttpServletRequest
      • ServletResponse * HttpServletResponse
      • HttpSession
      • ServletContext
      • Ioc & Ioc2
      • Map ServletRequest.getParameterMap()的返回值

      还有就是@Attr注解,可以用于获取req或session的attr

      • 默认先查找Request,然后找Session
      • 找不到就返回null

      示例代码:

      @Ok("json")
      public Object listAllUser(@Attr("me")User user) {
          if (user == null || !user.isAdmin())
              return new HttpStatusView(500);
          return dao.query(User.class, null);
      }
      

      如果你还想支持更多的类型,那么你就需要定制你自己的适配器了,稍后会有详细描述。

      路径参数

      某些时候,你可能觉得这样的 URL 很酷

      /my/article/1056.nut
      

      起码比

      /my/article.nut?id=1056
      

      看起来要顺眼一些。

      Nutz.Mvc 支持将路径作为参数吗? 你可以在路径中增加通配符,在运行时,Nutz.Mvc 会将路径对应的内容依次变成你的 入口函数的调用参数。通配符有两种:

      • '?' - 单层通配符,后面你可以继续写路径和其他的通配符
      • '*' - 多层通配符,后面个不能再有任何内容

      单层通配符

      @At("/topic/?/comment/?")
      public String getComment(int topicId, int commentId){
          // 如果输入的 URL 是: /topic/35/comment/171
          // 那么 topicId 就是 35
          // 而 commentId 就是 171
      }
      

      如果你有这种需求,我想不用我废话了,不解释,你懂的。

      多层通配符

      @At("/article/*")
      public String getArticle(String author, int articleId){
          // 如果输入的 URL 是: /article/zozoh/1352
          // 那么 author 就是 "zozoh"
          // 而 articleId 就是 1352
      }
      

      Nutz.Mvc 在一层一层解析路径的时候,碰到了 '*', 它就会将这个路径从此处截断, 后面的字串按照字符 '/' 拆分成一个字符串数组。 为入口函数填充参数的时候,会优先将这个路径参数数组按照顺序填充成参数。 之后,如果它发现入口函数还有参数没有被填充完全,它才应用适配器的内部逻辑,填充其余的参数。

      单层多层通配符混用

      @At("/user/?/topic/?/comment/*")
      public String getComment(String author, int topicId, int commentId){
          // 如果输入的 URL 是: /user/zozoh/topic/35/comment/171
          // 那么 author 就是 "zozoh"
          // 而 topicId 就是 35
          // 而 commentId 就是 171
      }
      

      通配符的限制

      总之,在 @At 注解中通过通配符,你可以声明你的路径参数,但是你的通配符必须是一层路径,但是它们有限制:

      你不能这么写
      /article/a?/topic/*
      也不能这么写
      /article/y*
      

      如果你这么写了,匹配的时候很可能出一些奇奇怪怪的问题。因此你记住了,通配符如果在路径中出现:

      • 左边一定有一个字符 '/'
      • 右侧可能没有字符,但是如果有,也一定是 '/'

      当然,通配符声明的路径参数仍然可以同 @Param 以及 特殊参数 混用,只是请记得,将入口函数 中的路径参数排在前面

      错误处理

      这是1.b.45及之后的版本才有的功能

      在以前的版本中,由用户输入导致的类型转换错误(例如字符串转数字,非法日期),都只能通过@Fail处理

      故,现在引入了AdaptorErrorContext,用于解决这一直以来被骂的缺陷

      仅当入口方法的最后一个参数为AdaptorErrorContext(其子类也行),才会触发这个错误处理机制

      看以下代码:

      // 传入的id,会是一个非法的字符串!!
      @At({"/err/param", "/err/param/?"})
      @Fail("http:500")
      public void errParam(@Param("id") long id, AdaptorErrorContext errCtx) {
          TestCase.assertNotNull(errCtx); // 当没有异常产生时, errCtx为null
          TestCase.assertNotNull(errCtx.getErrors()[0]);
      }
      

      当用户输入的参数id,为"Nutz"时,自然会导致异常, 而这个方法的最后一个参数是AdaptorErrorContext, 所以,仍将进入这个方法, 且errCtx参数不为null

      AdaptorErrorContext类本身很简单, 但它也是一个很不错的扩展点. 因为最后一个参数只要求是AdaptorErrorContext 或其子类,所以,你可以自定义一个AdaptorErrorContext,覆盖其核心方法 setError,以实现你需要的纠错逻辑

      定制自己的适配器

      先来看看适配器的接口:

      public interface HttpAdaptor {
          void init(Method method);
          Object[] adapt( HttpServletRequest request, HttpServletResponse response, String[] pathArgs);
      }
      

      你如果实现自己的适配器,你需要知道:

      • 你的适配器,对每个入口函数,只会有一份实例 -- Nutz.Mvc 只会创建一遍
        • 如果你的适配器是从 Ioc 容器中取得的,那么也只会被取出一次
      • init 函数是 Nutz.Mvc 在创建你的适配器以后,马上就要调用的一个方法,你可以在这个方法里初始化一些逻辑
      • adapt 方法的第三个参数,是 Nutz.Mvc 为你准备好的路径参数,它有可能为 null。 你的适配器 将决定是不是应用这个路径参数
      • 推荐继承 AbstractAdaptor
    6. C - 调用: 调用入口函数,你在里面需要调用相关的业务层代码。
      • 如果你的业务比较复杂,为了解耦合,你可能需要 Ioc 容器的帮助,请参看 同 Ioc 容器一起工作 一节
    7. D - 渲染: 根据入口函数的返回,渲染 HTTP Response。
      • 如果返回是个 View,则用这个 View 来渲染 null (null? 是的,你没看错,这种情况 View 接口第三个参数会是 null)
        • 你可以用 org.nutz.mvc.view.ViewWrapper 将你的返回对象以及要返回的视图组合在一起返回,ViewWrapper 也是一个 View
      • 否则用函数的 @Ok 注解声明的 View 来渲染入口函数的返回对象
      • 如果你的函数处理过程中抛出了异常,用 @Fail 注解声明的 View 来渲染异常对象
      • 返回值会保存在request的attr中,名字是obj                                                                                                                                                                                                                                                                                                                                                                             

        主模块

        任何一个类都可以作为主模块,只要你将其配置在 web.xml 中,这样的设计主要是为了方便 一些非 JSP/Servlet 标注的 web 服务器整合 Nutz.mvc 框架。

        在主模块中,你可以声明如下的注解:

        在 web.xml 中,一个比较典型的例子(推荐使用):

        <filter>
            <filter-name>nutz</filter-name>
            <filter-class>org.nutz.mvc.NutFilter</filter-class>
            <init-param>
                <param-name>modules</param-name>
                <param-value>net.wendal.nutzbook.MainModule</param-value>
            </init-param>
        </filter>
        
        <filter-mapping>
            <filter-name>nutz</filter-name>
            <url-pattern>/*</url-pattern>
            <dispatcher>REQUEST</dispatcher>
            <dispatcher>FORWARD</dispatcher>
        </filter-mapping>
        
        • 如果你没有声明 'modules' 参数,它会抛异常!!
        • 默认的,只要被正则表达式 `"^(.+[.])(jsp|png|gif|jpg|js|css|jspx|jpeg|swf|ico|map)$"` 匹配上的 URL 都不会被 Nutz 过滤器处理
        • 你可以为 NutFilter 设置参数 "ignore",来重新设置这个正则表达式
        • 如果你的值是 "null" (不区分大小写),所有的请求都会转发到 Nutz.mvc 进行处理
        • 如果 Nutz.mvc 没有找到合适入口函数处理,将会继续 chain.doFilter
                        

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值