緒論
相信接觸過springmvc的同學(xué)都知道,在springmvc的控制層中,我們在方法的參數(shù)中可以使用注解標(biāo)識。比如下面例子:
1
|
public Map<String, Object> login( @PathVariable ( "loginParams" ) String loginParams) |
@PathVariable注解就標(biāo)識了這個參數(shù)是作為一個請求地址模板變量的(不清楚的同學(xué)可以先學(xué)習(xí)一下restful設(shè)計風(fēng)格)。這些注解都是spring內(nèi)置注解,那么 我們可不可以自定義注解來實現(xiàn)自己的業(yè)務(wù)邏輯處理呢? 答案是可以的,spring團(tuán)隊的一大設(shè)計哲學(xué)思想就是讓自己的系統(tǒng)有無限可能性的拓展。 spring框架底層又是如何解析這些參數(shù)的注解的呢?
那么在學(xué)習(xí)自定義參數(shù)注解之前,我們先了解一下spring底層是怎么來解析這些注解參數(shù)的。實際上,這些處理過程是要涉及到配置文件的加載和解析以及一堆的各種處理,小弟功力尚淺,就分析不到那么多了,只是簡單過一下。
內(nèi)置參數(shù)注解的解析
下面,我們從源碼角度來分析:
首先,sping定義了一個統(tǒng)一的方法參數(shù)注解解析接口HandlerMethodArgumentResolver,所有方法參數(shù)解析類都需要實現(xiàn)這個接口,接口很簡單,定義了兩個方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public interface HandlerMethodArgumentResolver { /** * 判斷方法參數(shù)是否包含指定的參數(shù)注解 * 含有返回true,不含有返回false */ boolean supportsParameter(MethodParameter parameter); /** * 在給定的具體的請求中,把方法的參數(shù)解析到參數(shù)值里面,返回解析到的參數(shù)值,沒有返回null * 只有在supportsParameter返回true的時候,resolveArgument方法才會執(zhí)行 */ Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception; } |
現(xiàn)在,帶著大家看看@PathVariable參數(shù)注解的解析具體過程,源代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
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沒有重寫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)請求域,也就是把值賦給了方法參數(shù),比如請求路徑是: api/v1/task/{id},方法參數(shù)@PathVariable("id") String taskId,那么此時name=taskId, org=id的值 // 當(dāng)然,怎么把請求地址中對應(yīng)的值獲取出來,不在這篇博客的討論范疇。大家只要記得參數(shù)注解是這樣解析處理的就可以了 pathVars.put(name, arg); } } |
AbstractNamedValueMethodArgumentResolver的resolveArgument方法如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { Class<?> paramType = parameter.getParameterType(); // 獲取請求參數(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方法是抽象方法,我們回來看看一下PathVariableMethodArgumentResolver的handleResolvedValue方法是抽象方法的具體實現(xiàn) * */ handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest); return arg; } |
可以知道,@PathVariable標(biāo)識的參數(shù),會被對應(yīng)參數(shù)解析器把對應(yīng)值解析到一個Map結(jié)構(gòu)中保存到request scope。
總的來說,實現(xiàn)處理注解參數(shù)思路還是比較簡單的,定義一個類實現(xiàn)HandlerMethodArgumentResolver接口,在對應(yīng)方法里面進(jìn)行處理就可以了。接下來我們就來一次自定義注解參數(shù)解析的實戰(zhàn)。
自定義注解參數(shù)解析演練
我們模擬一下獲取當(dāng)前任務(wù)信息。
首先我們定義一個注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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 "" ; } |
接著模擬一個業(yè)務(wù)邏輯處理服務(wù)類
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
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); } } |
編寫任務(wù)類
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
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 + '}' ; } } |
編寫注解參數(shù)處理類
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
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( "為找到對應(yīng)的任務(wù)" ); } else { if (task.isAllowStudent()) { System.out.println( "當(dāng)前任務(wù)不允許學(xué)生參加哦" ); } else { System.out.println( "學(xué)生可以參加當(dāng)前任務(wù)哦" ); } } return task; } } |
編寫前端控制類
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
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參數(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
1
2
3
4
5
|
< 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
獲取到任務(wù)信息json數(shù)據(jù):
1
2
3
4
5
6
7
|
{ "cur_task" : { "name" : "task1" , "resolvedCount" : 10, "allowStudent" : true } } |
可以看到,@CurrentTask標(biāo)識的參數(shù)Task,在方法中就可以獲取到經(jīng)過TaskHandlerMethodArgumentResolver處理過的任務(wù)
使用場景
在我們web請求中,往往需要客戶端待會token來進(jìn)行身份驗證,這樣我們可以自定義參數(shù)注解來在指定的注解解析類里面來進(jìn)行token的合法性的判斷。這篇文章就到這里了~~
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持服務(wù)器之家。
原文鏈接:https://juejin.im/post/59edd553f265da432b49fddc