Skip to content

Commit 1a16461

Browse files
committed
feat: add new project
1 parent 87fa69d commit 1a16461

File tree

14 files changed

+996
-0
lines changed

14 files changed

+996
-0
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
HELP.md
2+
target/
3+
!.mvn/wrapper/maven-wrapper.jar
4+
!**/src/main/**
5+
!**/src/test/**
6+
7+
### STS ###
8+
.apt_generated
9+
.classpath
10+
.factorypath
11+
.project
12+
.settings
13+
.springBeans
14+
.sts4-cache
15+
16+
### IntelliJ IDEA ###
17+
.idea
18+
*.iws
19+
*.iml
20+
*.ipr
21+
22+
### NetBeans ###
23+
/nbproject/private/
24+
/nbbuild/
25+
/dist/
26+
/nbdist/
27+
/.nb-gradle/
28+
build/
29+
30+
### VS Code ###
31+
.vscode/
Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
## SpringMvc数据绑定-自定义注解
2+
3+
> SpringMVC中给我们提供了很多方便的注解用于绑定数据,比如`@RequestParam``@PathVariable`,就可以把接收到的参数进行绑定。但在实际场景中会有自定义注解的需求,比如权限校验,在每个controller方法中都需要根据请求的header去获取token,根据token做自己的业务逻辑。
4+
5+
### 开始
6+
7+
- 既然SpringMVC中给我们实现了那么多的默认注解,那就看看SpringMVC是怎么做到的,下面是`PathVariableMethodArgumentResolver`的部分源码:
8+
9+
```java
10+
/**
11+
* Resolves method arguments annotated with an @{@link PathVariable}.
12+
*
13+
* <p>An @{@link PathVariable} is a named value that gets resolved from a URI template variable.
14+
* It is always required and does not have a default value to fall back on. See the base class
15+
* {@link org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver}
16+
* for more information on how named values are processed.
17+
*
18+
* <p>If the method parameter type is {@link Map}, the name specified in the annotation is used
19+
* to resolve the URI variable String value. The value is then converted to a {@link Map} via
20+
* type conversion, assuming a suitable {@link Converter} or {@link PropertyEditor} has been
21+
* registered.
22+
*
23+
* <p>A {@link WebDataBinder} is invoked to apply type conversion to resolved path variable
24+
* values that don't yet match the method parameter type.
25+
*
26+
* @author Rossen Stoyanchev
27+
* @author Arjen Poutsma
28+
* @author Juergen Hoeller
29+
* @since 3.1
30+
*/
31+
public class PathVariableMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver
32+
implements UriComponentsContributor {
33+
34+
private static final TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(String.class);
35+
36+
37+
@Override
38+
public boolean supportsParameter(MethodParameter parameter) {
39+
if (!parameter.hasParameterAnnotation(PathVariable.class)) {
40+
return false;
41+
}
42+
if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
43+
PathVariable pathVariable = parameter.getParameterAnnotation(PathVariable.class);
44+
return (pathVariable != null && StringUtils.hasText(pathVariable.value()));
45+
}
46+
return true;
47+
}
48+
49+
@Override
50+
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
51+
PathVariable ann = parameter.getParameterAnnotation(PathVariable.class);
52+
Assert.state(ann != null, "No PathVariable annotation");
53+
return new PathVariableNamedValueInfo(ann);
54+
}
55+
56+
@Override
57+
@SuppressWarnings("unchecked")
58+
@Nullable
59+
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
60+
Map<String, String> uriTemplateVars = (Map<String, String>) request.getAttribute(
61+
HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
62+
return (uriTemplateVars != null ? uriTemplateVars.get(name) : null);
63+
}
64+
65+
@Override
66+
protected void handleMissingValue(String name, MethodParameter parameter) throws ServletRequestBindingException {
67+
throw new MissingPathVariableException(name, parameter);
68+
}
69+
70+
@Override
71+
@SuppressWarnings("unchecked")
72+
protected void handleResolvedValue(@Nullable Object arg, String name, MethodParameter parameter,
73+
@Nullable ModelAndViewContainer mavContainer, NativeWebRequest request) {
74+
75+
String key = View.PATH_VARIABLES;
76+
int scope = RequestAttributes.SCOPE_REQUEST;
77+
Map<String, Object> pathVars = (Map<String, Object>) request.getAttribute(key, scope);
78+
if (pathVars == null) {
79+
pathVars = new HashMap<>();
80+
request.setAttribute(key, pathVars, scope);
81+
}
82+
pathVars.put(name, arg);
83+
}
84+
85+
@Override
86+
public void contributeMethodArgument(MethodParameter parameter, Object value,
87+
UriComponentsBuilder builder, Map<String, Object> uriVariables, ConversionService conversionService) {
88+
89+
if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
90+
return;
91+
}
92+
93+
PathVariable ann = parameter.getParameterAnnotation(PathVariable.class);
94+
String name = (ann != null && StringUtils.hasLength(ann.value()) ? ann.value() : parameter.getParameterName());
95+
String formatted = formatUriValue(conversionService, new TypeDescriptor(parameter.nestedIfOptional()), value);
96+
uriVariables.put(name, formatted);
97+
}
98+
...
99+
}
100+
```
101+
102+
> 可以看到这个类的注释上说明了这是实现`@PathVariable`注解的类,它的父类是`AbstractNamedValueMethodArgumentResolver``AbstractNamedValueMethodArgumentResolver`实现了`HandlerMethodArgumentResolver`,正是`HandlerMethodArgumentResolver`这个类,用来实现了自定义的注解。
103+
104+
- 那么`PathVariableMethodArgumentResolver`这个类在什么时候用的呢?可以参考`RequestMappingHandlerAdapter`:
105+
106+
```java
107+
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
108+
implements BeanFactoryAware, InitializingBean {
109+
@Override
110+
public void afterPropertiesSet() {
111+
// Do this first, it may add ResponseBody advice beans
112+
initControllerAdviceCache();
113+
114+
if (this.argumentResolvers == null) {
115+
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
116+
this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
117+
}
118+
if (this.initBinderArgumentResolvers == null) {
119+
List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
120+
this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
121+
}
122+
if (this.returnValueHandlers == null) {
123+
List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
124+
this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
125+
}
126+
}
127+
128+
private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
129+
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();
130+
131+
// Annotation-based argument resolution
132+
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
133+
resolvers.add(new RequestParamMapMethodArgumentResolver());
134+
resolvers.add(new PathVariableMethodArgumentResolver());
135+
resolvers.add(new PathVariableMapMethodArgumentResolver());
136+
resolvers.add(new MatrixVariableMethodArgumentResolver());
137+
resolvers.add(new MatrixVariableMapMethodArgumentResolver());
138+
resolvers.add(new ServletModelAttributeMethodProcessor(false));
139+
resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
140+
resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));
141+
resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
142+
resolvers.add(new RequestHeaderMapMethodArgumentResolver());
143+
resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
144+
resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
145+
resolvers.add(new SessionAttributeMethodArgumentResolver());
146+
resolvers.add(new RequestAttributeMethodArgumentResolver());
147+
148+
// Type-based argument resolution
149+
resolvers.add(new ServletRequestMethodArgumentResolver());
150+
resolvers.add(new ServletResponseMethodArgumentResolver());
151+
resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
152+
resolvers.add(new RedirectAttributesMethodArgumentResolver());
153+
resolvers.add(new ModelMethodProcessor());
154+
resolvers.add(new MapMethodProcessor());
155+
resolvers.add(new ErrorsMethodArgumentResolver());
156+
resolvers.add(new SessionStatusMethodArgumentResolver());
157+
resolvers.add(new UriComponentsBuilderMethodArgumentResolver());
158+
159+
// Custom arguments
160+
if (getCustomArgumentResolvers() != null) {
161+
resolvers.addAll(getCustomArgumentResolvers());
162+
}
163+
164+
// Catch-all
165+
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
166+
resolvers.add(new ServletModelAttributeMethodProcessor(true));
167+
168+
return resolvers;
169+
}
170+
}
171+
```
172+
173+
> 可以看到`RequestMappingHandlerAdapter`实现了Spring生命周期中的`InitializingBean`接口,并且重写了`afterPropertiesSet()`方法,这里面调用了`getDefaultArgumentResolvers()`,这个方法把默认的解析器都添加了进去,`@PathVariable`注解的解析器就是这里加进去的,`RequestMappingHandlerAdapter`是SpringMVC中一个很重要的类,SpringMVC中的大多数组件都是在这里进行配置的,比如Converter,ViewResolver。
174+
175+
- 看了默认的实现,现在我们来看看`HandlerMethodArgumentResolver`这个类,这个方法中只有方法:
176+
177+
```java
178+
public interface HandlerMethodArgumentResolver {
179+
180+
/**
181+
* 用于判断是否支持对某种参数的解析
182+
*/
183+
boolean supportsParameter(MethodParameter parameter);
184+
185+
/**
186+
* 将请求中的参数值解析为某种对象
187+
*/
188+
@Nullable
189+
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
190+
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
191+
192+
}
193+
```
194+
195+
- 编写自定义的HandlerMethodArgumentResolver
196+
197+
```java
198+
@Target(ElementType.PARAMETER)
199+
@Retention(RetentionPolicy.RUNTIME)
200+
@Documented
201+
public @interface Token {
202+
}
203+
```
204+
205+
```java
206+
@Component
207+
public class UserArgumentResolver implements HandlerMethodArgumentResolver {
208+
@Autowired
209+
private RedisService redisService;
210+
211+
@Override
212+
public boolean supportsParameter(MethodParameter methodParameter) {
213+
// 判断是否有Token这个注解
214+
return methodParameter.hasParameterAnnotation(Token.class);
215+
}
216+
217+
@Override
218+
public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer,
219+
NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
220+
String token;
221+
if (methodParameter.getParameterType().equals(User.class) && Objects.nonNull(token =
222+
nativeWebRequest.getNativeRequest(HttpServletRequest.class).getHeader("token"))) {
223+
return redisService.get(token);
224+
}
225+
return null;
226+
}
227+
}
228+
```
229+
> 这个自定义解析器会判断是否是`@Token`这个注解,然后从header中取出token,并且转换为User对象。
230+
231+
- 编写一个业务类模拟redis操作
232+
233+
```java
234+
/**
235+
* @author luoliang
236+
* @date 2019/10/8
237+
* 模拟redis操作业务类
238+
*/
239+
@Service
240+
public class RedisService {
241+
242+
public Object get(String key) {
243+
if (StringUtils.isEmpty(key)) {
244+
return null;
245+
}
246+
return User.builder().id(key).name("二哈").build();
247+
}
248+
249+
public void set(String key, Object value) {
250+
// todo
251+
}
252+
}
253+
```
254+
255+
- 做完这些工作之后需要把自定义解析器加入到配置里
256+
257+
```java
258+
@SpringBootConfiguration
259+
public class WebMvcconfig extends WebMvcConfigurationSupport {
260+
private final UserArgumentResolver userArgumentResolver;
261+
262+
public WebMvcconfig(UserArgumentResolver userArgumentResolver) {
263+
this.userArgumentResolver = userArgumentResolver;
264+
}
265+
266+
@Override
267+
protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
268+
argumentResolvers.add(userArgumentResolver);
269+
}
270+
}
271+
```
272+
273+
- 编写Controller,参数就可以使用自定义的注解了
274+
275+
```java
276+
@GetMapping("/user")
277+
public ResponseEntity<User> getUser(@Token User user) {
278+
return ResponseEntity.ok(user);
279+
}
280+
```
281+
282+
### 总结
283+
284+
本篇文章主要记录了SpringMVC中自定义解析器的使用,同时举了一个真实场景的例子,旨在于知道怎么使用SpringMVC给我们带来的便利的同时,知道其原理。

0 commit comments

Comments
 (0)