聊聊springmvc中controller的方法的参数注解方式

绪论

相信接触过springmvc的同学都知道,在springmvc的控制层中,我们在方法的参数中可以使用注解标识。比如下面例子:

public Map<String, Object> login(@PathVariable("loginParams") String loginParams)

@PathVariable注解就标识了这个参数是作为一个请求地址模板变量的(不清楚的同学可以先学习一下restful设计风格)。这些注解都是spring内置注解,那么 我们可不可以自定义注解来实现自己的业务逻辑处理呢? 答案是可以的,spring团队的一大设计哲学思想就是让自己的系统有无限可能性的拓展。 spring框架底层又是如何解析这些参数的注解的呢?

那么在学习自定义参数注解之前,我们先了解一下spring底层是怎么来解析这些注解参数的。实际上,这些处理过程是要涉及到配置文件的加载和解析以及一堆的各种处理,小弟功力尚浅,就分析不到那么多了,只是简单过一下。

内置参数注解的解析

下面,我们从源码角度来分析:

首先,sping定义了一个统一的方法参数注解解析接口HandlerMethodArgumentResolver,所有方法参数解析类都需要实现这个接口,接口很简单,定义了两个方法:

public interface HandlerMethodArgumentResolver {

  /**
   * 判断方法参数是否包含指定的参数注解
   * 含有返回true,不含有返回false
   */
  boolean supportsParameter(MethodParameter parameter);

  /**
   * 在给定的具体的请求中,把方法的参数解析到参数值里面,返回解析到的参数值,没有返回null
   * 只有在supportsParameter返回true的时候,resolveArgument方法才会执行
   */
  Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
      NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception;

}

现在,带着大家看看@PathVariable参数注解的解析具体过程,源代码如下:

public class PathVariableMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver
    implements UriComponentsContributor {

    /*
     * 这里省略其它方法
     *
     /

  @Override
    public boolean supportsParameter(MethodParameter parameter) {
      // 不含有PathVariable注解,返回false
      if (!parameter.hasParameterAnnotation(PathVariable.class)) {
        return false;
      }
      // PathVariable注解的参数类型是Map类型
      if (Map.class.isAssignableFrom(parameter.getParameterType())) {
        String paramName = parameter.getParameterAnnotation(PathVariable.class).value();
        return StringUtils.hasText(paramName);
      }
      return true;
    }

   // PathVariableMethodArgumentResolver没有重写resolveArgument,直接使用AbstractNamedValueMethodArgumentResolver默认行为
   /*
   * 如果supportsParameter返回true,在这里真正处理参数
   *
   */
   protected void handleResolvedValue(Object arg, String name, MethodParameter parameter,
         ModelAndViewContainer mavContainer, NativeWebRequest request) {

       String key = View.PATH_VARIABLES;
       int scope = RequestAttributes.SCOPE_REQUEST;
       Map<String, Object> pathVars = (Map<String, Object>) request.getAttribute(key, scope);
       if (pathVars == null) {
         pathVars = new HashMap<String, Object>();
         request.setAttribute(key, pathVars, scope);
       }
       // 把参数的key-value放进请求域,也就是把值赋给了方法参数,比如请求路径是: api/v1/task/{id},方法参数@PathVariable("id") String taskId,那么此时name=taskId, org=id的值
       // 当然,怎么把请求地址中对应的值获取出来,不在这篇博客的讨论范畴。大家只要记得参数注解是这样解析处理的就可以了
       pathVars.put(name, arg);
     }

}

AbstractNamedValueMethodArgumentResolver的resolveArgument方法如下

public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
      NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {

    Class<?> paramType = parameter.getParameterType();
    // 获取请求参数的key-value
    NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
    // 解析参数名
    Object arg = resolveName(namedValueInfo.name, parameter, webRequest);
    if (arg == null) {
      if (namedValueInfo.defaultValue != null) {
        arg = resolveDefaultValue(namedValueInfo.defaultValue);
      }
      else if (namedValueInfo.required && !parameter.getParameterType().getName().equals("java.util.Optional")) {
        handleMissingValue(namedValueInfo.name, parameter);
      }
      arg = handleNullValue(namedValueInfo.name, arg, paramType);
    }
    else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
      arg = resolveDefaultValue(namedValueInfo.defaultValue);
    }
    // 数据绑定
    if (binderFactory != null) {
      WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
      try {
        arg = binder.convertIfNecessary(arg, paramType, parameter);
      }
      catch (ConversionNotSupportedException ex) {
        throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
            namedValueInfo.name, parameter, ex.getCause());
      }
      catch (TypeMismatchException ex) {
        throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
            namedValueInfo.name, parameter, ex.getCause());

      }
    }

    /*
     * 最后的处理是交给handleResolvedValue,handleResolvedValue方法是抽象方法,我们回来看看一下PathVariableMethodArgumentResolver的handleResolvedValue方法是抽象方法的具体实现
     *
     */
    handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);

    return arg;
  }

可以知道,@PathVariable标识的参数,会被对应参数解析器把对应值解析到一个Map结构中保存到request scope。

总的来说,实现处理注解参数思路还是比较简单的,定义一个类实现HandlerMethodArgumentResolver接口,在对应方法里面进行处理就可以了。接下来我们就来一次自定义注解参数解析的实战。

自定义注解参数解析演练

我们模拟一下获取当前任务信息。

首先我们定义一个注解

package top.mingzhijie.demo.springmvc.anntation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/** 代表当前任务
 * @author wunanliang
 * @date 2017/10/21
 * @since 1.0.0
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface CurrentTask {
  String value() default "";
}

接着模拟一个业务逻辑处理服务类

package top.mingzhijie.demo.springmvc.method.arguments.anntation;

import top.mingzhijie.demo.springmvc.entity.Task;

import java.util.HashMap;
import java.util.Map;

/**
 * 模拟任务业务类
 *
 * @author wunanliang
 * @date 2017/10/21
 * @since 1.0.0
 */
public class TaskService {

  private static Map<String, Task> taskMap = new HashMap<String, Task>();

  static {
    taskMap.put("001", new Task("task1", 10, true));
    taskMap.put("002", new Task("task2", 1, false));
    taskMap.put("003", new Task("task3", 20, false));
  }

  public static Task findTaskById(String taskId) {
    return taskMap.get(taskId);
  }

}

编写任务类

package top.mingzhijie.demo.springmvc.entity;

/**
 * @author wunanliang
 * @date 2017/10/21
 * @since 1.0.0
 */
public class Task {

  private String name;
  private int resolvedCount; // 参与人数
  private boolean allowStudent;

  public Task(){}

  public Task(String name, int resolvedCount, boolean allowStudent) {
    this.name = name;
    this.resolvedCount = resolvedCount;
    this.allowStudent = allowStudent;
  }

  public boolean isAllowStudent() {
    return allowStudent;
  }

  public void setAllowStudent(boolean allowStudent) {
    this.allowStudent = allowStudent;
  }

  public int getResolvedCount() {
    return resolvedCount;
  }

  public void setResolvedCount(int resolvedCount) {
    this.resolvedCount = resolvedCount;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  @Override
  public String toString() {
    return "Task{" +
        "name='" + name + '\'' +
        ", resolvedCount=" + resolvedCount +
        ", allowStudent=" + allowStudent +
        '}';
  }
}

编写注解参数处理类

package top.mingzhijie.demo.springmvc.method.arguments.anntation;

import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import top.mingzhijie.demo.springmvc.anntation.CurrentTask;
import top.mingzhijie.demo.springmvc.entity.Task;

/**
 * @author wunanliang
 * @date 2017/10/21
 * @since 1.0.0
 */
public class TaskHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {

  public boolean supportsParameter(MethodParameter methodParameter) {

    boolean hasAnn = methodParameter.hasParameterAnnotation(CurrentTask.class);
    return hasAnn;
  }

  public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {

    Task task = null;
    String curTaskId = (String) nativeWebRequest.getParameter("cur_task_id");
    if (curTaskId != null && !"".equals(curTaskId)) {
      task = TaskService.findTaskById(curTaskId);
    }

    if (task == null) {
      System.out.println("为找到对应的任务");
    } else {
      if (task.isAllowStudent()) {
        System.out.println("当前任务不允许学生参加哦");
      } else {
        System.out.println("学生可以参加当前任务哦");
      }
    }
    return task;
  }
}

编写前端控制类

package top.mingzhijie.demo.springmvc.method.arguments.anntation;

import org.springframework.web.bind.annotation.*;
import top.mingzhijie.demo.springmvc.anntation.CurrentTask;
import top.mingzhijie.demo.springmvc.entity.Task;

import java.util.HashMap;
import java.util.Map;

/**
 * @author wunanliang
 * @date 2017/10/21
 * @since 1.0.0
 */
@RestController
@RequestMapping("/tasks")
public class TaskController {

  // 这里使用@CurrentTask来表示Task参数
  @RequestMapping(value = "/join", method = RequestMethod.GET)
  @ResponseBody
  public Map<String, Task> gJoinTask(@RequestParam("cur_task_id") String taskId, @CurrentTask Task task) {
    System.out.println(task);
    Map<String, Task> map = new HashMap<String, Task>();
    map.put("cur_task", task);
    return map;
  }

}

配置文件配置注解参数解析bean

<mvc:annotation-driven>
    <mvc:argument-resolvers>
      <bean class="top.mingzhijie.demo.springmvc.method.arguments.anntation.TaskHandlerMethodArgumentResolver"/>
    </mvc:argument-resolvers>
  </mvc:annotation-driven>

运行,输入地址 http://localhost:8888/demospringmvc/tasks/join?cur_task_id=001

获取到任务信息json数据:

{
  "cur_task": {
    "name": "task1",
    "resolvedCount": 10,
    "allowStudent": true
  }
}

可以看到,@CurrentTask标识的参数Task,在方法中就可以获取到经过TaskHandlerMethodArgumentResolver处理过的任务

使用场景

在我们web请求中,往往需要客户端待会token来进行身份验证,这样我们可以自定义参数注解来在指定的注解解析类里面来进行token的合法性的判断。这篇文章就到这里了~~

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • SpringMVC基于注解的Controller详解

    概述 继 Spring 2.0 对 Spring MVC 进行重大升级后,Spring 2.5 又为 Spring MVC 引入了注解驱动功能.现在你无须让 Controller 继承任何接口,无需在 XML 配置文件中定义请求和 Controller 的映射关系,仅仅使用注解就可以让一个 POJO 具有 Controller 的绝大部分功能 -- Spring MVC 框架的易用性得到了进一步的增强.在框架灵活性.易用性和扩展性上,Spring MVC 已经全面超越了其它的 MVC 框架,伴随

  • 详解SpringMVC Controller介绍及常用注解

    一.简介 在SpringMVC 中,控制器Controller 负责处理由DispatcherServlet 分发的请求,它把用户请求的数据经过业务处理层处理之后封装成一个Model ,然后再把该Model 返回给对应的View 进行展示.在SpringMVC 中提供了一个非常简便的定义Controller 的方法,你无需继承特定的类或实现特定的接口,只需使用@Controller 标记一个类是Controller ,然后使用@RequestMapping 和@RequestParam 等一些注

  • 聊聊springmvc中controller的方法的参数注解方式

    绪论 相信接触过springmvc的同学都知道,在springmvc的控制层中,我们在方法的参数中可以使用注解标识.比如下面例子: public Map<String, Object> login(@PathVariable("loginParams") String loginParams) @PathVariable注解就标识了这个参数是作为一个请求地址模板变量的(不清楚的同学可以先学习一下restful设计风格).这些注解都是spring内置注解,那么 我们可不可以自

  • 关于SpringMVC在Controller层方法的参数解析详解

    目录 自定义参数解析器 实现效果 实现和配置 Spring提供解析器 一些使用Tricky 总结 使用版本: spring-boot: 2.1.6.RELEASE sping: 5.1.8.RELEASE java: openjdk 11.0.13 自定义参数解析器 为了能够实现自定义参数解析器,只需要实现 HandlerMethodArgumentResolver 并将添加到 WebMvcConfigurer#addArgumentResolvers 即可. 实现效果 下面以添加一个获取请求头

  • SpringMVC中controller接收json数据的方法

    本文实例为大家分享了SpringMVC中controller接收json数据的方法,供大家参考,具体内容如下 1.jsp页面发送ajax的post请求: function postJson(){ var json = {"username" : "imp", "password" : "123456"}; $.ajax({ type : "post", url : "<%=basePath

  • SpringMVC中controller返回json数据的方法

    本文实例为大家分享了SpringMVC中controller返回json数据的2种方法,供大家参考,具体内容如下 1.jsp的ajax请求: function getJson(){ $.ajax({ type:"get", dataType:"json", url:"<%=basePath %>getJson", success:function(data){ for(var i=0;i<jsonData.length;i++)

  • Python中自定义函方法与参数具有默认值的函数

    目录 一.Python中自定义函数的方法 1自定义函数的语法 2自定义函数的实现 3自定义函数的调用 二.Python中参数具有默认值的函数 1参数是常量默认值的函数 2参数是常量默认值的函数 一.Python中自定义函数的方法 在Python编程中,可以使用已经定义好的函数,也可以自定义函数实现某些特殊的功能. 1 自定义函数的语法 在Python中,自定义函数的语法如下所示: def 函数名(参数):     函数体 其中,def是关键字:之后跟的是函数名,通过函数名来调用该函数:函数名之后

  • tomcat 通过占位符设置端口的方法(即参数指定方式)

    工作需要,找了网上很多资料,都是复制粘贴,并没有解决实际问题,弄了好久终于搞定了. 共有三种方式: 第一种 通过启动命令行启动参数调用 1.在conf/server.xml中进行设置如下:port="${http.port}" 2.在bin/catalina.xml中进行设置如下:JAVA_OPTS="JAVA_OPTS" -Dhttp.port='50000'"(你自己想要设定的端口号) 3.重启tomcat 4.IP+端口访问 表示设置成功 5.最后正

  • SpringMVC中MultipartFile转File的两种方式

    在spring上传文件中,一般都使用了MultipartFile来接收,但是有需要用到File的地方,这里只介绍两种转为File的方法,当然也有一些其他的方法,我试了有些错误,所以就不提了: transferTo() org.apache.commons.io.FileUtils.copyInputStreamToFile() 代码如下: public void upload(@RequestParam(value = "file") MultipartFile file) {   

  • SpringMVC中Controller类数据响应的方法

    目录 1. 方法返回值类型 2. 页面跳转 2.1 直接返回字符串 2.2 返回 ModelAndView 对象 2.3 视图前缀和后缀 2.3 重定向和转发 3. 回写数据 3.1 直接返回字符串 3.2 返回对象或集合 上篇博客我们了解了请求参数的获取,那么获取到请求参数之后,需要对参数进行出来,然后进行数据响应.那么这篇博客我们就来了解 Controller 类如何进行数据响应. 1. 方法返回值类型 在 web 阶段我们也了解过数据响应,我们可以简单的将数据响应分为:页面跳转和回写数据

  • 浅谈springMVC中controller的几种返回类型

    Controller方法的返回值可以有以下几种: 1.返回ModelAndView 返回ModelAndView时最常见的一种返回结果.需要在方法结束的时候定义一个ModelAndView对象,并对Model和View分别进行设置. 2.返回String 1):字符串代表逻辑视图名 真实的访问路径="前缀"+逻辑视图名+"后缀" 注意:如果返回的String代表逻辑视图名的话,那么Model的返回方式如下: public String testController(

  • 详解springmvc 中controller与jsp传值

    在springmvc中的controller所对应的函数中,如果需要从*.jsp页面中获取数据,可以自行在函数括号中写,springmvc会自动封装传过来的值. spring-mvc.xml 中加入如下语句: <!-- 自动扫描 --> <context:component-scan base-package="cn.itcast.springmvc.service,cn.itcast.springmvc.web.controller"/> <!-- 注解

随机推荐