聊聊springmvc中controller的方法的參數註解

緒論

相信接觸過springmvc的同窗都知道,在springmvc的控制層中,咱們在方法的參數中可使用註解標識。好比下面例子:前端

public Map<String, Object> login(@PathVariable("loginParams") String loginParams)複製代碼

@PathVariable註解就標識了這個參數是做爲一個請求地址模板變量的(不清楚的同窗能夠先學習一下restful設計風格)。這些註解都是spring內置註解,那麼咱們可不能夠自定義註解來實現本身的業務邏輯處理呢?答案是能夠的,spring團隊的一大設計哲學思想就是讓本身的系統有無限可能性的拓展。spring框架底層又是如何解析這些參數的註解的呢?
那麼在學習自定義參數註解以前,咱們先了解一下spring底層是怎麼來解析這些註解參數的。實際上,這些處理過程是要涉及到配置文件的加載和解析以及一堆的各類處理,小弟功力尚淺,就分析不到那麼多了,只是簡單過一下。java

內置參數註解的解析

下面,咱們從源碼角度來分析:
首先,sping定義了一個統一的方法參數註解解析接口HandlerMethodArgumentResolver,全部方法參數解析類都須要實現這個接口,接口很簡單,定義了兩個方法:web

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參數註解的解析具體過程,源代碼以下:spring

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方法以下json

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接口,在對應方法裏面進行處理就能夠了。接下來咱們就來一次自定義註解參數解析的實戰。api

自定義註解參數解析演練

咱們模擬一下獲取當前任務信息。
首先咱們定義一個註解bash

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 "";
}複製代碼

接着模擬一個業務邏輯處理服務類restful

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);
    }

}複製代碼

編寫任務類mvc

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 + '}'; } }複製代碼

編寫註解參數處理類app

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的合法性的判斷。好啦這篇文章就到這裏了~~關於spring如何綁定參數值不在這篇博客討論的範疇,這裏只是簡單和你們聊聊spring如何獲取註解的參數進行處理,以及給處一個學習的demo。

相關文章
相關標籤/搜索