spring boot中支持使用java Web三大組件(servlet、listener和filter),但是坑比較多,主要是spring boot內嵌tomcat和獨立tomcat服務器有一些細節上的不同,踩完之后,特有此記。
首先說明下,需要實現的功能,網站中有一些需要進行中英文對照的東西,一開始是寫在properties配置文件中,經常修改的話,非常麻煩,還需要重啟服務器。
初步考慮覺得采用ServletConetext監聽器的觸發,從mysql表中加載所以中英文對照數據,存入map,保存在ServletContext中,這種方式簡單有效,缺點在於如果一旦改動,就需要重啟tomcat。
進一步的想法是采用HttpSessionListener監聽器,每個用戶開始會話的session創建階段,獲取一次map,保存到ServletContext,所有的request都從全局變量ServletContext取,只存一份,實時更新,即使有變化也無需重啟服務器。
一、spring boot內嵌服務器
1.session監聽器的實現
@WebListener public class MyHttpSessionListener implements HttpSessionListener { @Autowired private AppMetaService appMetaService; @Override public void sessionCreated(HttpSessionEvent httpSessionEvent) { Map<String, String> keyMap = appMetaService.getKeyMap(); ServletContext servletContext = httpSessionEvent.getSession().getServletContext(); servletContext.setAttribute("keyMap",keyMap); } @Override public void sessionDestroyed(HttpSessionEvent httpSessionEvent) { } }
2.session監聽無效
這里有一個問題,發現controller中的請求並不會主動生成session,默認情況下session監聽器什么也監聽不到。所以需要在每次請求時,手動調用request.getSession觸發session的創建。
由於每次請求都需要這些動作,因此我把它加入到了一個過濾器中。
@WebFilter(urlPatterns = "/*") public class MyFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } /** * 在servlet過濾器中觸發getSession方法,一次編寫,多處使用 */ @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) servletRequest; request.getSession(true); filterChain.doFilter(request, servletResponse); } @Override public void destroy() { } }
3.內嵌服務器支持
Servlet、Filter、Listener可以直接通過@WebServlet、@WebFilter、@WebListener注解自動注冊,無需其他代碼。唯一需要的就是在項目的入口類中加一個注解@ServletComponentScan,顧名思義,它會掃描所有的
@WebServlet、@WebFilter、@WebListener完成對象的注入。
@SpringBootApplication @ServletComponentScan public class DataboardApplication { public static void main(String[] args) { SpringApplication.run(DataboardApplication.class, args); } }
到此為止,簡潔明了,不廢話,本地測試成功,直接上tomcat服務器。
二、獨立tomcat服務器
最大的坑來了,在本地跑的很正常的程序,在服務器上跑不動,最奇怪的是有些接口能用,有些不能用,經過多方觀察,發現用到servlet、listener和filter的都掛了。
搜了很多資料之后,還是在spring boot 2.01的文檔中找到這么一句話:
Tip
@ServletComponentScan has no effect in a standalone container, where the container’s builtin
discovery mechanisms are used instead.
翻譯一下就是@ServletComponentScan這個注解在獨立tomcat服務器不可用。
只好退求其次,采用@Bean注入的方式
1.對應的監聽器代碼
@Component public class MyHttpSessionListener implements HttpSessionListener { @Autowired private AppMetaService appMetaService; @Override public void sessionCreated(HttpSessionEvent httpSessionEvent) { Map<String, String> keyMap = appMetaService.getKeyMap(); ServletContext servletContext = httpSessionEvent.getSession().getServletContext(); servletContext.setAttribute("keyMap",keyMap); } @Override public void sessionDestroyed(HttpSessionEvent httpSessionEvent) { } }
2.對應過濾器代碼
@Component public class MyFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } /** * 在servlet過濾器中觸發getSession方法,一次編寫,多處使用 */ @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) servletRequest; request.getSession(true); filterChain.doFilter(request, servletResponse); } @Override public void destroy() { } }
3.啟動類代碼
@SpringBootApplication public class DataboardApplication { @Bean public ServletListenerRegistrationBean sessionHandler() { return new ServletListenerRegistrationBean<>(new MyHttpSessionListener()); } @Bean public FilterRegistrationBean myFilter() { FilterRegistrationBean myFilter = new FilterRegistrationBean(new MyFilter()); myFilter.addUrlPatterns("/*"); return myFilter; } public static void main(String[] args) { SpringApplication.run(DataboardApplication.class, args); } }
這里變化最大,去掉了@ServletComponentScan,在類內部增加了兩個生成bean的方法。