为什么使用SpringBoot的时候,拦截器中使用@Autowired注入bean会报空指针.如下面代码所示,我们知道,Spring管理的bean发现有这个注解时候,它会直接注入相应的另一个Spring管理的bean.当 Spring 容器启动时,AutowiredAnnotationBeanPostProcessor (继承InstantiationAwareBeanPostProcessorAdapter)将扫描 Spring 容器中所有 Bean,当发现 Bean 中拥有@Autowired 注解时就找到和其匹配(默认按类型匹配)的 Bean,并注入到对应的地方中去。那为什么这里的注解没有生效呢?
网上传说:了解SpringBoot的都知道SpringBoot的目录格式有着明确的规定,它减轻编程人员负担的同时,更加要求了编程的规范化,SpringBoot初始化的时候,会加载com.boot.app下的bean,一层一层加载,当注册LoggerInterceptor的时候,发现LoggerInterceptor中有@Autowired注解,就会去另外一个spring管理器中索取另外一个LoggerJpa,而这时候LoggerJpa根本没有初始化.所以就无法注入LoggerJpa的bean类完成相应的操作.
注册拦截器时直接通过new LoggerInterceptor(),并没有触发Spring去管理bean,所以@Autowired没有生效.
public class LoggerInterceptor implements HandlerInterceptor {
public static final String SEND_TIME = "send_time";
public static final String DATA = "param_data";
//问题:无法注入loggerDao
@Autowired
private LoggerJpa loggerDao;
/**
* 进入springMVC的controller之前开始记录日志实体
* @param httpServletRequest request
* @param httpServletResponse response
* @param o 实体类
* @return boolean
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
//创建日记实体类
LoggerEntity entity = new LoggerEntity();
//获得sessionId
String sessionId = httpServletRequest.getRequestedSessionId();
//请求地址信息
String requestURI = httpServletRequest.getRequestURI();
//请求参数信息(利用fastJson转换参数)
String params = JSON.toJSONString(httpServletRequest.getParameterMap(),
SerializerFeature.DisableCircularReferenceDetect,
SerializerFeature.WriteMapNullValue);
//设置客户端ip
entity.setClientIp(LoggerUtil.getCliectIp(httpServletRequest));
//设置请求方法
entity.setMethod(httpServletRequest.getMethod());
//设置请求类型
entity.setType(LoggerUtil.getRequestType(httpServletRequest));
//设置请求参数
entity.setParamData(params);
//设置请求地址
entity.setUri(requestURI);
//设置sessionId
entity.setSessionId(sessionId);
//设置请求开始时间
httpServletRequest.setAttribute(SEND_TIME, System.currentTimeMillis());
//设置请求实体到request内,方便afterCompletion调用
httpServletRequest.setAttribute(DATA, entity);
return true;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
//请求状态
int status = httpServletResponse.getStatus();
//当前时间
long time = System.currentTimeMillis();
//上次请求时间
Long requestTime = Long.valueOf(httpServletRequest.getAttribute(SEND_TIME).toString());
//获取请求日记的实体
LoggerEntity entity = (LoggerEntity) httpServletRequest.getAttribute(DATA);
//设置时间差
entity.setConsuming(Long.valueOf(time-requestTime).toString());
//设置错误码
entity.setStatusCode(status+"");
//设置返回值
entity.setReturnData(JSON.toJSONString(httpServletRequest.getAttribute(LoggerUtil.LOGGER_RETURN),
SerializerFeature.DisableCircularReferenceDetect,
SerializerFeature.WriteMapNullValue));
//通过WebApplicationContextUtils获取loggerDao
//LoggerJpa loggerDao = getDao(LoggerJpa.class,httpServletRequest);
//将日记写入数据库
loggerDao.save(entity);
}
}
二.解决方法
了解到原因之后,那么如何解决呢?在这里,有两种常见的解决方法.
方法一:利用WebApplicationContextUtils去获取WebApplicationContext,然后在通过WebApplicationContext去获取相应的bean.
public <T> T getDao(Class<T> clazz,HttpServletRequest request){
//通过该方法获得的applicationContext 已经是初始化之后的applicationContext 容器
WebApplicationContext applicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(request.getServletContext());
return applicationContext.getBean(clazz);
}
方法二:在初始化LoggerInterceptor 之前就初始化LoggerJpa
@Configuration
public class LoggerConfiguration extends WebMvcConfigurerAdapter {
@Bean
public LoggerInterceptor loggerInterceptor(){
System.out.println("嘻嘻嘻嘻");
return new LoggerInterceptor();
}
/**
* LoggerInterceptor,形成拦截链
* @param registry 拦截器注册类
*/
@Override
public void addInterceptors(InterceptorRegistry registry){
//在放入拦截器之前调用loggerInterceptor(),触发LocalContainerEntityManagerFactoryBean使得拦截器的在注册之前所有的bean都持久化
registry.addInterceptor(loggerInterceptor()).addPathPatterns("/**");
System.out.println("呵呵呵呵");
}
}
方法三:构造器
public class LoggerInterceptor implements HandlerInterceptor {
private static final String SEND_TIME = "send_time";
private static final String DATA = "param_data";
private LoggerJpa loggerDao;
public LoggerInterceptor(LoggerJpa loggerDao){
this.loggerDao = loggerDao;
}
//拦截器三个方法略...
}
@Configuration
public class LoggerConfiguration extends WebMvcConfigurerAdapter {
@Autowired
private LoggerJpa loggerDao;
/**
* LoggerInterceptor,形成拦截链
* @param registry 拦截器注册类
*/
@Override
public void addInterceptors(InterceptorRegistry registry){
//利用构造方法注入
registry.addInterceptor(new LoggerInterceptor(loggerDao)).addPathPatterns("/**");
System.out.println("呵呵呵呵");
}
}
总结:
任何时候继承WebMvcConfigurationSupport进行web配置都可以,但是因为有些mvc配置只需要配置一次,所以这种方式没必要使用。
进行mvc配置也可以实现WebMvcConfigurer接口或继承WebMvcConfigurerAdapter类都是可行的,spring5.x.x版本之后直接实现WebMvcConfigurer接口即可。这种方式配置较为灵活,按需配置,固定配置交给WebMvcConfigurationSupport进行即可。