Spring进阶:如何优雅地获取用户信息

933 阅读2分钟

1.前言

Java开发中,多数接口需要用户登录才能执行业务逻辑,因此会有大量重复代码用于校验用户登录和获取用户信息,不免有些繁琐,也容易遗漏。

2.Spring如何实现请求参数注入

public class User {
    private Long id;
    private String name;
}

@GetMapping("param")
public String param(User user) {
    System.out.println(user);
    return "param";
}

Spring中,如果要获取queryString的参数,只要对Controller的方法添加同名的参数或者具有同名field的对象即可。这种方式既优雅,又隔离了HttpRequest的API,不失为一种妙计。
Spring实现这种优雅的注入方式,就依赖于HandlerMethodArgumentResolver这个扩展接口

3.扩展HandlerMethodArgumentResolver

public interface HandlerMethodArgumentResolver {

	boolean supportsParameter(MethodParameter parameter);

	@Nullable
	Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;

}

HandlerMethodArgumentResolver定义了两个方法,supportsParameter用于判断是否要执行参数注入,resolveArgument则执行参数解析并注入到Controller方法。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface UserInfo {

    boolean required() default true;

}

首先我们需要定义一个注解@UserInfo,用于标记我们要注入的对象,注解需要在运行时生效。@UserInfo有一个reqiured属性,标记了接口是否必须要用户登录才能访问,用于一些根据用户登录与否展示不同数据的接口。

@GetMapping("/required")
public String required(@UserInfo User user) {
    System.out.println(user);
    return "required";
}

@GetMapping("notRequired")
public String notRequired(@UserInfo(required = false) User user) {
    System.out.println(user);
    return "not required";
}

为Controller添加User参数,并用@UserInfo标记

public class Token2UserResolver implements HandlerMethodArgumentResolver {

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        //方法参数有@UserInfo标记
        return parameter.hasParameterAnnotation(UserInfo.class);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        //获取@UserInfo
        UserInfo userInfo = parameter.getParameterAnnotation(UserInfo.class);
        //从header获取access-token
        String token = webRequest.getHeader("access-token");
        if ((token == null || token.isEmpty())) {
            if (userInfo.required()) {
                //token不存在且必须登录访问的接口,抛出异常,配合统一异常处理返回相应状态码给前段
                throw new RuntimeException("401");
            } else {
                //非必须登录则不处理
                return null;
            }
        }
        //将token解析为User对象
        User user = null;
        if (token.equals("token-a")) {
            user = new User(1L, "user-A");
        } else if (token.equals("token-b")) {
            user = new User(2L, "user-B");
        }
        return user;
    }

}

实现HandlerMethodArgumentResolver,这里简单模拟了一个token解析的过程,实际开发中可以用redis或者jwt之类的方案实现。
需要注意的是,这里完成的功能并非只有注入用户信息,还有强制接口登录的功能。@UserInfo的required属性为true时,如果没登录将抛出异常,配合统一异常处理可以实现访问控制和优雅的响应。

@Configuration
public class MyConfig implements WebMvcConfigurer {

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(new Token2UserResolver());
    }
}

最后通过Spring提供的WebMvcConfigurer将Token2UserResolver添加到解析器链中。如此,一个优雅的用户信息解析工具就完成了。业务开发者便可以专注业务的实现,无需再操心用户信息的获取和校验了。