聊聊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,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,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,ex.getCause());
      }
      catch (TypeMismatchException ex) {
        throw new MethodArgumentTypeMismatchException(arg,ex.getCause());

      }
    }

    /*
     * 最后的处理是交给handleResolvedValue,handleResolvedValue方法是抽象方法,我们回来看看一下PathVariableMethodArgumentResolver的handleResolvedValue方法是抽象方法的具体实现
     *
     */
    handleResolvedValue(arg,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的合法性的判断。这篇文章就到这里了~~

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


开发过程中是不可避免地会出现各种异常情况的,例如网络连接异常、数据格式异常、空指针异常等等。异常的出现可能导致程序的运行出现问题,甚至直接导致程序崩溃。因此,在开发过程中,合理处理异常、避免异常产生、以及对异常进行有效的调试是非常重要的。 对于异常的处理,一般分为两种方式: 编程式异常处理:是指在代
说明:使用注解方式实现AOP切面。 什么是AOP? 面向切面编程,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。 通俗描述:不通过修改源代码方式,在主干功能里面添加新功能。 AOP底层使用动态代理。 AOP术语 连接点
Spring MVC中的拦截器是一种可以在请求处理过程中对请求进行拦截和处理的机制。 拦截器可以用于执行一些公共的操作,例如日志记录、权限验证、数据转换等。在Spring MVC中,可以通过实现HandlerInterceptor接口来创建自定义的拦截器,并通过配置来指定拦截器的应用范围和顺序。 S
在 JavaWeb 中,共享域指的是在 Servlet 中存储数据,以便在同一 Web 应用程序的多个组件中进行共享和访问。常见的共享域有四种:ServletContext、HttpSession、HttpServletRequest、PageContext。 ServletContext 共享域:
文件上传 说明: 使用maven构建web工程。 使用Thymeleaf技术进行服务器页面渲染。 使用ResponseEntity实现下载文件的功能。 @Controller public class FileDownloadAndUpload { @GetMapping(&quot;/file/d
创建初始化类,替换web.xml 在Servlet3.0环境中,Web容器(Tomcat)会在类路径中查找实现javax.servlet.ServletContainerInitializer接口的类,如果找到的话就用它来配置Servlet容器。 Spring提供了这个接口的实现,名为SpringS
在 Web 应用的三层架构中,确保在表述层(Presentation Layer)对数据进行检查和校验是非常重要的。正确的数据校验可以确保业务逻辑层(Business Logic Layer)基于有效和合法的数据进行处理,同时将错误的数据隔离在业务逻辑层之外。这有助于提高系统的健壮性、安全性和可维护
什么是事务? 事务(Transaction)是数据库操作最基本单元,逻辑上一组操作,要么都成功,要么都失败,如果操作之间有一个失败所有操作都失败 。 事务四个特性(ACID) 原子性 一组操作要么都成功,要么都失败。 一致性 一组数据从事务1合法状态转为事务2的另一种合法状态,就是一致。 隔离性 事
什么是JdbcTemplate? Spring 框架对 JDBC 进行封装,使用 JdbcTemplate 方便实现对数据库操作。 准备工作 引入jdbcTemplate的相关依赖: 案例实操 创建jdbc.properties文件,配置数据库信息 jdbc.driver=com.mysql.cj.
SpringMVC1.MVC架构MVC是模型(Model)、视图(View)、控制器(Controller)的简写,是一种软件设计规范是将业务逻辑、数据、显示分离的方法来写代码MVC主要作用是:降低了视图和业务逻辑之间的双向耦合MVC是一个架构模型,不是一种设计模式。1.model(模型)数据模型,提供要展示的数据,因此包
SpringMVC学习笔记1.SpringMVC应用1.1SpringMVC简介​SpringMVC全名叫SpringWebMVC,是⼀种基于Java的实现MVC设计模型的请求驱动类型的轻量级Web框架,属于SpringFrameWork的后续产品。​MVC全名是ModelViewController,是模型(model)-视图(view)-控制器(co
11.1数据回显基本用法数据回显就是当用户数据提交失败时,自动填充好已经输入的数据。一般来说,如果使用Ajax来做数据提交,基本上是没有数据回显这个需求的,但是如果是通过表单做数据提交,那么数据回显就非常有必要了。11.1.1简单数据类型简单数据类型,实际上框架在这里没有
一、SpringMVC简介1、SpringMVC中重要组件DispatcherServlet:前端控制器,接收所有请求(如果配置/不包含jsp)HandlerMapping:解析请求格式的.判断希望要执行哪个具体的方法.HandlerAdapter:负责调用具体的方法.ViewResovler:视图解析器.解析结果,准备跳转到具体的物
1.它们主要负责的模块Spring主要应用于业务逻辑层。SpringMVC主要应用于表现层。MyBatis主要应用于持久层。2.它们的核心Spring有三大核心,分别是IOC(控制反转),DI(依赖注入)和AOP(面向切面编程)。SpringMVC的核心是DispatcherServlet(前端控制器)。MyBatis的核心是ORM(对
3.注解开发Springmvc1.使用注解开发要注意开启注解支持,2.注解简化了,处理映射器和处理适配器,只用去管视图解析器即可案例代码:1.web.xml,基本不变可以直接拿去用<!--调用DispatcherServlet--><servlet><servlet-name>springmvc</servlet-name>
拦截器概述SpringMVC的处理器拦截器类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理和后处理。开发者可以自己定义一些拦截器来实现特定的功能。**过滤器与拦截器的区别:**拦截器是AOP思想的具体应用。过滤器servlet规范中的一部分,任何javaweb工程都可以使用
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:context="http://www.springframework.org/schema/context"xmlns:mvc="http://www.springframework.org/schema/mvc"xmlns:xsi="
学习内容:1、SSH&SSM2、Spring3、Struts2&SpringMVC4、Hibernate&MyBatis学习产出:1.SSH和SSM都是有Spring框架的,他们两个差不多。2.Spring分为四个模块,持久层,表示层,检测层,还有核心层,核心层分为2个关键核心功能。分别为,控制反转(IOC),依赖注入(DI),和面向切面编程
一、SpringMVC项目无法引入js,css的问题具体原因是css和js等被SpringMVC拦截了:解决方案:在spring-mvc.xml中配置<mvc:default-servlet-handler/><?xmlversion="1.0"encoding="UTF-8"?><beansxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
开发环境:Eclipse/MyEclipse、Tomcat8、Jdk1.8数据库:MySQL前端:JavaScript、jQuery、bootstrap4、particles.js后端:maven、SpringMVC、MyBatis、ajax、mysql读写分离、mybatis分页适用于:课程设计,毕业设计,学习等等系统介绍