在过去spring还没有统治java的时候,spring容器的启动通常是在webapp中的xml文件中配置服务器启动监听器,然后在监听器中启动spring容器。但是现在大多数的web应用开发会选择基于spring-boot开发,这时主次顺序就变成了先启动spring的容器,等spring容器刷新得差不多了,才会来启动web容器。这种启动顺序的差异就很有可能带来一些差错。在前几天的看到同事的代码,便发现了这类似的错误。废话不多说。直接上代码
@Configuration
class MainConfig {
@Bean
public Object bean() {
return "i am a bean";
}
}
@Component
@WebServlet("/task")
public class BadTaskServlet extends HttpServlet {
@Qualifier("bean")
@Autowired
private Object bean;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
initBean();
resp.getOutputStream().write(Integer.parseInt("123"));
}
private Object initBean() {
if (bean == null) {
bean = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
}
return bean;
}
@Scheduled(cron = "*/5 * * * * ?")
public void task() {
initBean();
System.out.println(bean);
}
}
这个写法错误的一点在于initBean这个方法这里。简单地描述一下这个场景,1. 这里是打算做一个durid的监控类,这个监控的页面很简单,只需要实现一个Servlet的类就可以了。2. 但是与此同时,在这个监控的页面还跑了一个定时任务,定时任务的目的是打算定时清楚redis中的数据。 我在这里就忽略了其他的不重要的部分,直接上关键的点吗的部分!
这位同事认为这个spring的注入并不一定会生效,当容器中没有实例的时候bean就会为null。但是其实在使用@Autowired注解之后,默认情况下,在容器查找不到一个实例的时候时候,那么就会直接抛出一个找不到bean的异常,因此这个bean如果为null的话,其实这个应用是直接启动不起来的!
于是这里,代码中initBean的方法就打算在查找不到bean的时候,手动地获取一个bean。但是问题就出现在了WebApplicationContextUtils.getWebApplicationContext(getServletContext()).getBean(“bean”);;这个方法是打算通过Servlet的容器上下文来获取spring的上下文,然后再通过这个方法来手动获取bean。