问题场景
在Web开发过程中,我们经常会使用拦截器,做登录权限校验,一般在拦截器里,我们也会用到各种Service方法
草稿代码如下:
public class LoginInterceptor extends HandlerInterceptorAdapter {
@Autowired
private UserLoginManager userLoginManager;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
//权限校验
userLoginManager.verifyLoginToken(accessToken)
return true;
}
@Configuration
public class LoginWebFilter extends WebMvcConfigurerAdapter {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**")
}
}
导致空指针问题
如果我们代码如上所写,会发现userLoginManager;在使用过程中,@Autowired注入失败,空指针
java.lang.NullPointerException:
null at com.test.LoginInterceptor.preHandle(LoginInterceptor.java:53)
如图:
空指针原因分析
我们先看一下广大网友的分析解决 Springboot中Interceptor 或者说 如果我直接在拦截器拦截器中依赖注入失败
或者这篇Spring中的Interceptor拦截器中使用@Autowired注解,在运行时会出现空指针
相信大家看完都还是很疑惑,例如什么叫做: 在WebAppConfigure类中,是new出来的TestInterceptor对象,因此不会触发spring的自动注入,导致空指针异常? 看的很模糊大概
或者说 如果我直接在拦截器LoginInterceptor加一个spring注解@component 会不会成功?(答案是失败的)
这里带着我的疑惑,来探讨下
先看springboot的api访问局部流程
1 自定义拦截器LoginWebInterceptor
2 将拦截器对象 通过WebMvcConfigurerAdapter注册到Spring拦截器内存List<InterceptorRegistration> registrations
@Configuration
public class LoginWebFilter extends WebMvcConfigurerAdapter {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**")
3 API访问,进入springmvc 拦截器流程中,从spring内存中取出拦截器对象
也就是步骤2 保存注册的拦截器对象new LoginInterceptor()
4 步骤3获取的拦截器对象,执行拦截器方法 preHandle()
也就是new LoginInterceptor().preHandle()
相信看到这里大家应该懂了
也就是我拦截器内存集合注册的对象是 new LoginInterceptor()
所以我拦截器执行目标方法的时候,执行姿势就是new LoginInterceptor().preHandle()这样去执行的
如果我拦截器LoginInterceptor(),即使有其他service参数
如
private UserLoginManager userLoginManager;
我这 new LoginInterceptor() 不就new 出的是空对象? 空指针,毛都没有
所有如果我想走spring service 方法那一套,就不能是new LoginInterceptor().preHandle()这样的姿势
就必须是 spring的IOC对象
也就是我向spring拦截器内存注册的一定是 spring容器创建出来的bean对象loginInterceptor(),
而不是我自己搞出来的 new LoginInterceptor() 这种方式
解决方案一
通过上述分析,解决就很简单了
向spring拦截器内存注册的对象搞成spring bean对象,而不是new LoginInterceptor()这种
如下
@Component
public class LoginInterceptor extends HandlerInterceptorAdapter {
@Autowired
private UserLoginManager userLoginManager;
@Configuration
public class LoginWebFilter extends WebMvcConfigurerAdapter {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**")
验证:
解决方案二
向spring拦截器内存注册的对象搞成spring bean对象,而不是new LoginInterceptor(
public class LoginInterceptor extends HandlerInterceptorAdapter {
@Autowired
private UserLoginManager userLoginManager;
@Configuration
public class LoginWebFilter extends WebMvcConfigurerAdapter {
@Bean
public LoginInterceptor getLoginInterceptor(){
return new LoginInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(getLoginInterceptor())
.addPathPatterns("/**")
验证:
解决方案三 (可用来装逼)
骚操作:
既然在拦截器LoginInterceptor中, UserLoginManager userLoginManager 是空的,没有注入进来
直接手写一个SpringContextUtil implements ApplicationContextAware 直接从spring IOC容器里面拿userLoginManager 这个成熟bean对象
弄成动态获取,不就完事,还能秀一手你的 "单例模式", TL code review 档次,高级不就来了?(滑稽脸)
public class LoginInterceptor extends HandlerInterceptorAdapter {
private static volatile UserLoginManager userLoginManager;
public static UserLoginManager getUserLoginManager() {
if (userLoginManager == null) {
synchronized (UserLoginManager.class) {
if (userLoginManager == null) {
userLoginManager = (UserLoginManager) SpringContextUtil.getBean("userLoginManagerImpl");
}
}
}
return userLoginManager;
}