记录一个初次接触spring框架时容易碰见的坑:拦截器与redisTemplate混用时,redisTemplate为null
个人记录,尚且初学,可能有理解错误/误导之处,欢迎指正,理性讨论。
知识点:spring在处理@Autowired时,实例化且只实例化了一个对象(它就是Bean),如果再new一个内含@Autowired的类,是不会享受到spring的自动注入的。
在设置类配置拦截器时,由于需要在参数中添加一个实例化的拦截器;同时,如果adapter里面包含了@Autowired的属性,这个坑就很容易出现。
坑的样子
来看一个LoginInterceptor,它负责处理登录时的一些情况,里面有一个自动注入的RedisTemplate。
@Component
public class LoginInterceptor extends HandlerInterceptorAdapter {
@Autowired
protected RedisTemplate<String, String> redisTemplate;
//...其他部分不重要
}
想让这个拦截器起作用,需要在addInterceptors()方法中把它放进去,但不要采取下面这种方式:
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor());
super.addInterceptors(registry);
}
}
按这个写法写出来的程序,会在loginInterceptor用到redisTemplate时死成个500的样子给你看。
为什么?
注意registry.addInterceptor(new LoginInterceptor())这一行。
它new了一个新的LoginInterceptor,这个实例并没有享受到spring的自动注入,里面的属性自然就没有值,不出意外的话,是个亲切友善的null——那当然是会500的。
那怎么办?
思路:要让这个地方取到的LoginInterceptor,是那个被spring初始化的Bean:LoginInterceptor。
[注:A思路下的原理并没完全清楚,后面会更新掉]
[方法未在本地测试]
//todo
A思路
让这个方法也变成一个Bean,在Config实例化时一起实例化:
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(LoginInterceptor());
super.addInterceptors(registry);
}
@Bean
LoginInterceptor LoginInterceptor(){
return new LoginInterceptor();
}
都交给spring吧。
[亲测可用√]
B思路
写一个SpringUtil工具类,负责从spring上下文中拿Bean,就像这样:
别嫌它长,总得有个有个类负责这件事情的。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
public class SpringUtil implements ApplicationContextAware {
private static Logger logger = LoggerFactory.getLogger(SpringUtil.class);
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if (SpringUtil.applicationContext == null) {
SpringUtil.applicationContext = applicationContext;
}
}
//获取applicationContext
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
//通过name获取Bean
public static Object getBean(String name) {
return getApplicationContext().getBean(name);
}
//通过class获取Bean
public static <T> T getBean(Class<T> clazz) {
return getApplicationContext().getBean(clazz);
}
//通过name,以及Clazz返回指定的Bean
public static <T> T getBean(String name, Class<T> clazz) {
return getApplicationContext().getBean(name, clazz);
}
//通过Clazz返回指定的Beans
public static <T> Map<String, T> getBeansByType(Class<T> clazz) {
return getApplicationContext().getBeansOfType(clazz);
}
//通过name,读取属性
public static String getProperty(String name) {
Environment environment = applicationContext.getEnvironment();
return environment.getProperty(name);
}
}
然后,再在Config那边,把代码改成调用SpringUtil的方法
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor((HandlerInterceptor) SpringUtil.getBean("loginInterceptor"));
super.addInterceptors(registry);
}
}
bean名字是类的首字母小写。
一句话重点:spring一开始只实例一个Bean,new出来的不会被自动注入。
感谢你看到这里!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。