聊聊springmvc中controller的方法的參數(shù)注解方式
緒論
相信接觸過(guò)springmvc的同學(xué)都知道,在springmvc的控制層中,我們?cè)诜椒ǖ膮?shù)中可以使用注解標(biāo)識(shí)。比如下面例子:
public Map<String, Object> login(@PathVariable("loginParams") String loginParams)
@PathVariable注解就標(biāo)識(shí)了這個(gè)參數(shù)是作為一個(gè)請(qǐng)求地址模板變量的(不清楚的同學(xué)可以先學(xué)習(xí)一下restful設(shè)計(jì)風(fēng)格)。這些注解都是spring內(nèi)置注解,那么 我們可不可以自定義注解來(lái)實(shí)現(xiàn)自己的業(yè)務(wù)邏輯處理呢? 答案是可以的,spring團(tuán)隊(duì)的一大設(shè)計(jì)哲學(xué)思想就是讓自己的系統(tǒng)有無(wú)限可能性的拓展。 spring框架底層又是如何解析這些參數(shù)的注解的呢?
那么在學(xué)習(xí)自定義參數(shù)注解之前,我們先了解一下spring底層是怎么來(lái)解析這些注解參數(shù)的。實(shí)際上,這些處理過(guò)程是要涉及到配置文件的加載和解析以及一堆的各種處理,小弟功力尚淺,就分析不到那么多了,只是簡(jiǎn)單過(guò)一下。
內(nèi)置參數(shù)注解的解析
下面,我們從源碼角度來(lái)分析:
首先,sping定義了一個(gè)統(tǒng)一的方法參數(shù)注解解析接口HandlerMethodArgumentResolver,所有方法參數(shù)解析類都需要實(shí)現(xiàn)這個(gè)接口,接口很簡(jiǎn)單,定義了兩個(gè)方法:
public interface HandlerMethodArgumentResolver {
/**
* 判斷方法參數(shù)是否包含指定的參數(shù)注解
* 含有返回true,不含有返回false
*/
boolean supportsParameter(MethodParameter parameter);
/**
* 在給定的具體的請(qǐng)求中,把方法的參數(shù)解析到參數(shù)值里面,返回解析到的參數(shù)值,沒(méi)有返回null
* 只有在supportsParameter返回true的時(shí)候,resolveArgument方法才會(huì)執(zhí)行
*/
Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception;
}
現(xiàn)在,帶著大家看看@PathVariable參數(shù)注解的解析具體過(guò)程,源代碼如下:
public class PathVariableMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver
implements UriComponentsContributor {
/*
* 這里省略其它方法
*
/
@Override
public boolean supportsParameter(MethodParameter parameter) {
// 不含有PathVariable注解,返回false
if (!parameter.hasParameterAnnotation(PathVariable.class)) {
return false;
}
// PathVariable注解的參數(shù)類型是Map類型
if (Map.class.isAssignableFrom(parameter.getParameterType())) {
String paramName = parameter.getParameterAnnotation(PathVariable.class).value();
return StringUtils.hasText(paramName);
}
return true;
}
// PathVariableMethodArgumentResolver沒(méi)有重寫(xiě)resolveArgument,直接使用AbstractNamedValueMethodArgumentResolver默認(rèn)行為
/*
* 如果supportsParameter返回true,在這里真正處理參數(shù)
*
*/
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);
}
// 把參數(shù)的key-value放進(jìn)請(qǐng)求域,也就是把值賦給了方法參數(shù),比如請(qǐng)求路徑是: api/v1/task/{id},方法參數(shù)@PathVariable("id") String taskId,那么此時(shí)name=taskId, org=id的值
// 當(dāng)然,怎么把請(qǐng)求地址中對(duì)應(yīng)的值獲取出來(lái),不在這篇博客的討論范疇。大家只要記得參數(shù)注解是這樣解析處理的就可以了
pathVars.put(name, arg);
}
}
AbstractNamedValueMethodArgumentResolver的resolveArgument方法如下
public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
Class<?> paramType = parameter.getParameterType();
// 獲取請(qǐng)求參數(shù)的key-value
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
// 解析參數(shù)名
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);
}
// 數(shù)據(jù)綁定
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方法是抽象方法,我們回來(lái)看看一下PathVariableMethodArgumentResolver的handleResolvedValue方法是抽象方法的具體實(shí)現(xiàn)
*
*/
handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
return arg;
}
可以知道,@PathVariable標(biāo)識(shí)的參數(shù),會(huì)被對(duì)應(yīng)參數(shù)解析器把對(duì)應(yīng)值解析到一個(gè)Map結(jié)構(gòu)中保存到request scope。
總的來(lái)說(shuō),實(shí)現(xiàn)處理注解參數(shù)思路還是比較簡(jiǎn)單的,定義一個(gè)類實(shí)現(xiàn)HandlerMethodArgumentResolver接口,在對(duì)應(yīng)方法里面進(jìn)行處理就可以了。接下來(lái)我們就來(lái)一次自定義注解參數(shù)解析的實(shí)戰(zhàn)。
自定義注解參數(shù)解析演練
我們模擬一下獲取當(dāng)前任務(wù)信息。
首先我們定義一個(gè)注解
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;
/** 代表當(dāng)前任務(wù)
* @author wunanliang
* @date 2017/10/21
* @since 1.0.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface CurrentTask {
String value() default "";
}
接著模擬一個(gè)業(yè)務(wù)邏輯處理服務(wù)類
package top.mingzhijie.demo.springmvc.method.arguments.anntation;
import top.mingzhijie.demo.springmvc.entity.Task;
import java.util.HashMap;
import java.util.Map;
/**
* 模擬任務(wù)業(yè)務(wù)類
*
* @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);
}
}
編寫(xiě)任務(wù)類
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; // 參與人數(shù)
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 +
'}';
}
}
編寫(xiě)注解參數(shù)處理類
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("為找到對(duì)應(yīng)的任務(wù)");
} else {
if (task.isAllowStudent()) {
System.out.println("當(dāng)前任務(wù)不允許學(xué)生參加哦");
} else {
System.out.println("學(xué)生可以參加當(dāng)前任務(wù)哦");
}
}
return task;
}
}
編寫(xiě)前端控制類
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來(lái)表示Task參數(shù)
@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;
}
}
配置文件配置注解參數(shù)解析bean
<mvc:annotation-driven>
<mvc:argument-resolvers>
<bean class="top.mingzhijie.demo.springmvc.method.arguments.anntation.TaskHandlerMethodArgumentResolver"/>
</mvc:argument-resolvers>
</mvc:annotation-driven>
運(yùn)行,輸入地址 http://localhost:8888/demospringmvc/tasks/join?cur_task_id=001
獲取到任務(wù)信息json數(shù)據(jù):
{
"cur_task": {
"name": "task1",
"resolvedCount": 10,
"allowStudent": true
}
}
可以看到,@CurrentTask標(biāo)識(shí)的參數(shù)Task,在方法中就可以獲取到經(jīng)過(guò)TaskHandlerMethodArgumentResolver處理過(guò)的任務(wù)
使用場(chǎng)景
在我們web請(qǐng)求中,往往需要客戶端待會(huì)token來(lái)進(jìn)行身份驗(yàn)證,這樣我們可以自定義參數(shù)注解來(lái)在指定的注解解析類里面來(lái)進(jìn)行token的合法性的判斷。這篇文章就到這里了~~
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
springboot controller 增加指定前綴的兩種實(shí)現(xiàn)方法
這篇文章主要介紹了springboot controller 增加指定前綴的兩種實(shí)現(xiàn)方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02
JavaFX實(shí)現(xiàn)簡(jiǎn)易時(shí)鐘效果(二)
這篇文章主要為大家詳細(xì)介紹了JavaFX實(shí)現(xiàn)簡(jiǎn)易時(shí)鐘效果的第二篇,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-11-11
java動(dòng)態(tài)構(gòu)建數(shù)據(jù)庫(kù)復(fù)雜查詢教程
這篇文章主要介紹了java動(dòng)態(tài)構(gòu)建數(shù)據(jù)庫(kù)復(fù)雜查詢的實(shí)現(xiàn)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2021-11-11
springboot?@PostConstruct無(wú)效的解決
這篇文章主要介紹了springboot?@PostConstruct無(wú)效的解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11
三分鐘帶你掌握J(rèn)ava開(kāi)發(fā)圖片驗(yàn)證碼功能方法
這篇文章主要來(lái)為大家詳細(xì)介紹Java實(shí)現(xiàn)開(kāi)發(fā)圖片驗(yàn)證碼的具體方法,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,需要的可以參考一下2023-02-02
MyBatis利用MyCat實(shí)現(xiàn)多租戶的簡(jiǎn)單思路分享
這篇文章主要給大家介紹了關(guān)于MyBatis利用MyCat實(shí)現(xiàn)多租戶的簡(jiǎn)單思路的相關(guān)資料,文中的多租戶是基于多數(shù)據(jù)庫(kù)進(jìn)行實(shí)現(xiàn)的,數(shù)據(jù)是通過(guò)不同數(shù)據(jù)庫(kù)進(jìn)行隔離,需要的朋友可以參考借鑒,下面來(lái)一起看看吧。2017-06-06
java開(kāi)發(fā)命名規(guī)范總結(jié)
包名的書(shū)寫(xiě)規(guī)范 (Package)推薦使用公司或機(jī)構(gòu)的頂級(jí)域名為包名的前綴,目的是保證各公司/機(jī)構(gòu)內(nèi)所使用的包名的唯一性。包名全部為小寫(xiě)字母,且具有實(shí)際的區(qū)分意義2013-10-10

