一.原因
- 这两天在写代码的时候遇到一个问题,为什么使用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没有生效.
/** * ======================== * * @author 小龙虾 * Created with IntelliJ IDEA. * Date:2018/1/22 * Time:20:02 * ======================== */ 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
/** * ======================== * * @author 小龙虾 * Created with IntelliJ IDEA. * Date:2018/1/23 * Time:11:51 * ======================== */ @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; } //拦截器三个方法略... }
/** * ======================== * * @author 小龙虾 * Created with IntelliJ IDEA. * Date:2018/1/23 * Time:11:51 * ======================== */ @Configuration public class LoggerConfiguration extends WebMvcConfigurerAdapter { //@Bean //public LoggerInterceptor loggerInterceptor(){ // System.out.println("嘻嘻嘻嘻") // return new LoggerInterceptor(loggerDao); //} @Autowired private LoggerJpa loggerDao; /** * LoggerInterceptor,形成拦截链 * @param registry 拦截器注册类 */ @Override public void addInterceptors(InterceptorRegistry registry){ //利用构造方法注入 registry.addInterceptor(new LoggerInterceptor(loggerDao)).addPathPatterns("/**"); System.out.println("呵呵呵呵"); } }