1

记录一个初次接触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出来的不会被自动注入。

感谢你看到这里!


禹安
4 声望0 粉丝

热爱技术本身。


下一篇 »
[Git]Git速查表