Springboot自定义注解注入参数
使用spring security时经常会使用注解
@AuthenticationPrincipal
获取用户信息。在想要注入用户信息的Controller接口上加上注解,即可方便的获取访问用户的信息。而且Controller还“非常的智能”,比如接口上写上参数HttpServletRequest request
,就能自动注入http请求信息
需求
现在有一种需求(案例简化的需求),很多接口都需要查询用户的部门Department
。每次可能都有类似如下硬编码
1@GetMapping("/")
2public String getSomeThing(@AuthenticationPrincipal Account account) {
3 Department department = departmentManager.findOne(account.getDepartmentId());
4 // doSomeThing with department ...
5}
这样也不是不可以,但是总感觉不够优雅,特别是有时业务不像案例这么简单一个查询就OK,可能有一大段逻辑要处理才查询出部门信息。
解决
spring自身有如此智能的参数注入机制,猜想这个优秀的框架肯定为我们留下了可扩展的方法,让我们可以实现类似的功能。经过查找资料,找到了自定义的办法
自定义注解
我们写一个自定义注解@GetDepartment
,稍后为所有有该注解的Controller接口方法参数上注入部门信息
1@Documented
2@Target({ElementType.PARAMETER})
3@Retention(RetentionPolicy.RUNTIME)
4public @interface GetDepartment {
5}
自定义http方法参数解析器
实现自定义解析器实现HandlerMethodArgumentResolver
。这里因为需要用到@Autowired
注入一个部门查询的Bean,所以加了@Component
注解。其实不需要注入时不加@Component
注解也是可以的。推荐加注解交给Spring管理。
1@Component
2public class GetDepartmentHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
3 @Autowired
4 private DepartmentManager departmentManager;
5
6 private static final Class<?> commandClass = Department.class;
7
8
9 @Override
10 public boolean supportsParameter(MethodParameter parameter) {
11 // 过滤出符合条件的参数,这里指的是加了GetDepartment注解的参数。
12 // 其他不符合条件的参数不会使用该解析器处理
13 return parameter.hasParameterAnnotation(GetDepartment.class);
14 }
15
16
17
18 @Override
19 public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
20 // 该方法解析器处理逻辑
21 Class<?> klass = parameter.getParameterType();
22 // 定义部门信息必须用Department类接收,不允许用其他的class接收
23 if (!klass.isAssignableFrom(commandClass)) {
24 throw new RuntimeException("部门获取失败");
25 }
26 // 获取用户信息
27 CurrentUserDetails account = (CurrentUserDetails) mavContainer.getDefaultModel().get("currentAccount");
28 // 查询部门
29 Department department = departmentManager.findOne(account.getDepartmentId());
30 return department;
31 }
32}
需要重写两个方法:
supportsParameter
方法主要过滤出需要注入部门的参数,放行不满足条件的参数resolveArgument
方法是真正做自己业务逻辑,比如这里获取部门信息,并返回给框架,框架会帮我们注入到方法上
resolveArgument
方法自带4个参数,经过debug,发现这从这4个参数中,我们可以获取大部分想要的信息,去继续下面的业务逻辑。
ModelAndViewContainer
参数中带有用户的登录信息,即和使用@AuthenticationPrincipal
注解时获取的用户信息一样。MethodParameter
是本次http请求的接口方法信息。NativeWebRequest
里有HttpRequest一些信息,比如可以从中获取cookie,获取方法参数等。WebDataBinderFactory
暂时没用上
把自定义解析器添加到spring管理
仅仅把解析器生成Spring Bean是不够的,还需要把该对象添加到特定的解析器队列中。需要自定义一个类继承WebMvcConfigurerAdapter
,重写addArgumentResolvers
方法,把刚才的Bean添加进去队列。
1@Configuration
2public class CustomWebMvcConfigurerAdapter extends WebMvcConfigurerAdapter {
3 @Autowired
4 private GetDepartmentHandlerMethodArgumentResolver getDepartmentHandlerMethodArgumentResolver;
5
6 @Override
7 public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
8 resolvers.add(getDepartmentHandlerMethodArgumentResolver);
9 }
10}
使用
上面自定义两个类,一个注解后,配置已经完成。在使用处使用@GetDepartment Department department
接收即可,这样部门信息就自动注入进来了
1@GetMapping("/")
2public String getSomeThing(@AuthenticationPrincipal Account account,@GetDepartment Department department) {
3 // doSomeThing with department ...
4}
踩坑
在配置添加解析器到Spring中,最开始是自定义类继承WebMvcConfigurationSupport
而不是继承WebMvcConfigurerAdapter
,其实同样可以做到自定义注入参数的功能。但是后来发现自定义实现WebMvcConfigurationSupport
后,原本使用@AuthenticationPrincipal
注解获取用户信息的地方,现在都无法获取了。
1@Configuration
2public class CustomWebMvcConfigurerAdapter extends WebMvcConfigurationSupport {
3 @Autowired
4 private GetDepartmentHandlerMethodArgumentResolver getDepartmentHandlerMethodArgumentResolver;
5
6 @Override
7 public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
8 resolvers.add(getDepartmentHandlerMethodArgumentResolver);
9 }
10}
经过查看源码发现Spring提供了DelegatingWebMvcConfiguration
去实现WebMvcConfigurationSupport
。我们称代理类
1@Configuration
2public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
3
4 private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
5
6
7 @Autowired(required = false)
8 public void setConfigurers(List<WebMvcConfigurer> configurers) {
9 if (!CollectionUtils.isEmpty(configurers)) {
10 this.configurers.addWebMvcConfigurers(configurers);
11 }
12 }
13
14 @Override
15 protected void configurePathMatch(PathMatchConfigurer configurer) {
16 this.configurers.configurePathMatch(configurer);
17 }
18 // ...
19}
代理类中有个WebMvcConfigurerComposite
对象,通过set注入mvc配置类List<WebMvcConfigurer>
,并add进去。我们查看WebMvcConfigurerComposite
类,下面称之为聚合类。
1class WebMvcConfigurerComposite implements WebMvcConfigurer {
2
3 private final List<WebMvcConfigurer> delegates = new ArrayList<WebMvcConfigurer>();
4
5
6 public void addWebMvcConfigurers(List<WebMvcConfigurer> configurers) {
7 if (!CollectionUtils.isEmpty(configurers)) {
8 this.delegates.addAll(configurers);
9 }
10 }
11
12 @Override
13 public void configurePathMatch(PathMatchConfigurer configurer) {
14 for (WebMvcConfigurer delegate : this.delegates) {
15 delegate.configurePathMatch(configurer);
16 }
17 }
18 // ...
19}
我们发现对代理类的方法调用,都会转为对聚合类中相同方法的调用,而聚合类会把配置类集合List<WebMvcConfigurer>
循环,一一对每个WebMvcConfigurer
调用相同的方法。比如上面的configurePathMatch
方法
现在找寻代理类的使用地方,找到了WebMvcAutoConfiguration
,可以看出这个类是SpringMvc缺省的自动配置类,而这个类有个很重要的一行@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
。意思只有不存在WebMvcConfigurationSupport
bean实例的时候,才会初始化下面这些bean,包括spring mvc 缺省一个自动配置WebMvcAutoConfigurationAdapter
,还有实现了刚才代理类的子类EnableWebMvcConfiguration
和下面其他的一些配置。这些统统不生效,实现了WebMvcConfigurationSupport
的bean只能存在一个
1@Configuration
2@ConditionalOnWebApplication
3@ConditionalOnClass({ Servlet.class, DispatcherServlet.class,WebMvcConfigurerAdapter.class })
4@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
5@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
6@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,ValidationAutoConfiguration.class })
7public class WebMvcAutoConfiguration {
8
9 @Configuration
10 @Import({ EnableWebMvcConfiguration.class, MvcValidatorRegistrar.class })
11 @EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
12 public static class WebMvcAutoConfigurationAdapter extends WebMvcConfigurerAdapter {
13 // ...
14 }
15
16 @Configuration
17 public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements InitializingBean {
18 // ...
19 }
20 // ...
21}
总结
WebMvcConfigurationSupport
注释如下
1/** 2 * This is the main class providing the configuration behind the MVC Java config... 3 */
WebMvcConfigurationSupport
是spring mvc主要配置类,所以一开始我们自定义实现WebMvcConfigurationSupport
时,原先的spring mvc一些缺省配置都不会自动配置,也不会配置代理类,自然不会有 调用代理类->调用聚合类->聚合类调用每一个配置类
这样一个调用链条,相当于所有的WebMvcConfigurer
实现类都不生效。相关的mvc一些缺省配置自然不生效。