Spring进阶: 细谈在Spring中使用Interceptor拦截器 , @Autowired注入失败空指针问题

1,644 阅读3分钟

问题场景

在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)
 如图: 

wecom-temp-2ad6fd2302cc6302b72df843a0aa49e1.png

空指针原因分析

我们先看一下广大网友的分析解决 Springboot中Interceptor 或者说 如果我直接在拦截器拦截器中依赖注入失败

或者这篇Spring中的Interceptor拦截器中使用@Autowired注解,在运行时会出现空指针

相信大家看完都还是很疑惑,例如什么叫做: 在WebAppConfigure类中,是new出来的TestInterceptor对象,因此不会触发spring的自动注入,导致空指针异常? 看的很模糊大概

或者说 如果我直接在拦截器LoginInterceptor加一个spring注解@component 会不会成功?(答案是失败的)
  
这里带着我的疑惑,来探讨下

先看springboot的api访问局部流程

wecom-temp-9a37145c0939bd3da64bc2916b7611cf.png

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("/**")

wecom-temp-332890efa16b874bba4a466d84916a9c.png

wecom-temp-7fdd6346178444bf8eb7a88093119520.png

3 API访问,进入springmvc 拦截器流程中,从spring内存中取出拦截器对象
  也就是步骤2 保存注册的拦截器对象new LoginInterceptor()
  
4 步骤3获取的拦截器对象,执行拦截器方法 preHandle()  
  也就是new LoginInterceptor().preHandle()   

wecom-temp-1a8bc75755b82f76155ca9f63d420e3e.png

  相信看到这里大家应该懂了
  也就是我拦截器内存集合注册的对象是 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("/**")
验证:  

wecom-temp-06d3db2ca165d465eea40d23c8d2730f.png

解决方案二

 向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("/**")
验证:  

wecom-temp-6eb8396f1cde2cc9c1fce0803c25032c.png

解决方案三 (可用来装逼)

骚操作:

既然在拦截器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;
 }