Spring拦截器中@Resource注入为null的问题
前言
最近在跟着黑马的视频学SpringBoot项目,遇到了一个很奇怪的问题。在写登录拦截器的时候,明明用了@Resource
注解,但是StringRedisTemplate
一直是null,调试了好久才找到原因。记录一下这个坑,希望能帮到同样遇到问题的你们。
问题描述
我按照平时的写法,想在拦截器里注入Redis模板:
java
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Resource
private StringRedisTemplate stringRedisTemplate; // 这里一直是null!
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// 运行到这里就报空指针异常
stringRedisTemplate.opsForHash().entries("test");
return true;
}
}
配置类是这样写的:
java
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor()); // 问题就在这里!
}
}
为什么会这样?
刚开始我以为是Redis配置有问题,因为在Service层用同样的注解是可以的。问题出在对Spring工作原理的理解上。
Spring依赖注入的条件
Spring只会对它自己管理的Bean进行依赖注入。什么意思呢?
- 当我写
@Component
的时候,Spring确实会创建一个LoginInterceptor
的Bean - 但是在
MvcConfig
里,我用的是new LoginInterceptor()
,这是手动创建的对象 - 手动创建的对象和Spring管理的Bean是两个不同的实例!
- Spring只会对容器里的那个Bean注入依赖,对手动创建的对象不管
所以就出现了这种情况:
java
// Spring容器里有一个LoginInterceptor,StringRedisTemplate被正确注入
LoginInterceptor springBean = 容器中的实例; // stringRedisTemplate ✓
// 我手动创建了另一个LoginInterceptor,Spring不管它
LoginInterceptor myObject = new LoginInterceptor(); // stringRedisTemplate = null ✗
解决方法
方法一:构造函数传参(推荐的方法)
java
public class LoginInterceptor implements HandlerInterceptor {
private StringRedisTemplate stringRedisTemplate;
// 通过构造函数接收StringRedisTemplate
public LoginInterceptor(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// 现在可以正常使用了
stringRedisTemplate.opsForHash().entries("test");
return true;
}
}
配置类改成这样:
java
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 把StringRedisTemplate传给拦截器
registry.addInterceptor(new LoginInterceptor(stringRedisTemplate));
}
}
方法二:注入拦截器Bean
java
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Resource
private StringRedisTemplate stringRedisTemplate;
// 其他代码不变
}
配置类这样写:
java
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Resource // 直接注入Spring管理的LoginInterceptor
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 用注入的实例,不要new
registry.addInterceptors(loginInterceptor);
}
}
为什么选择方法一?
为什么不用方法二
- 更直观:一看就知道拦截器需要什么依赖
- 更灵活 :不需要给拦截器加
@Component
- 更好测试:写单元测试的时候容易mock
- 避免错误:不容易忘记加注解而导致注入失败
确实,构造函数的方式让依赖关系很清楚,代码也更容易理解。
学到的经验
- Spring只对自己管理的Bean注入依赖 ,手动
new
的对象不行 - 看到
@Resource
或@Autowired
为null,先检查对象是不是Spring管理的 - 拦截器、过滤器这些需要手动创建的组件,通过构造函数传依赖比较好
- 遇到问题先想想Spring的工作原理,很多问题都能迎刃而解
总结
如果你也遇到了类似的依赖注入问题,可以按这个思路检查:
- 这个对象是Spring管理的吗?
- 如果不是,怎么让Spring管理它?
- 或者换个方式传递依赖?