今天我们就来学习一下在spring boot中如何使用自定义的servlet、filter、listener。
在现在的web应用开始中,通常我们只需要对外发布restful的风格接口即可完成大部分业务需求,不再像传统的J2EE一样将页面以及逻辑统统卸载servlet当中。看似我们只需要填写正确的路径即可访问我们想要的接口方法,这其中也是由spring的DispatcherServlet进行调度的,那么我们自己如何实现自己的servlet,filter以及listener呢?接下来讲解一下如何实现。
在这里还不了解servlet,filter以及listener的可以先看一下我的另外一篇博客:
http://blog.csdn.net/wujiaqi0921/article/details/78196744
在spring boot中添加自己的Servlet、filter以及listener有两种方法,一种是代码注册,另一种是自动注册。
一、代码注册通过ServletRegistrationBean、 FilterRegistrationBean 和 ServletListenerRegistrationBean 获得控制,也可以通过实现 ServletContextInitializer 接口直接注册。
二、在 SpringBootApplication 上使用@ServletComponentScan 注解后,Servlet、Filter、Listener 可以直接通过 @WebServlet、@WebFilter、@WebListener 注解自动注册,无需其他代码。
在这里我们讲解一下如何通过自动注册来实现自己的SFL(Servlet、filter、listener)
servlet
首先是我们的TestServlet.java类
@WebServlet(name = "myServlet", urlPatterns = "/myServlet/*", loadOnStartup = 0)
public class TestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("----------doGet()---------");
this.doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("------------doPost()-----------");
resp.setContentType("text/html");
PrintWriter out = resp.getWriter();
out.println("<html>");
out.println("<head>");
out.println("<title>Hello Servlet</title>");
out.println("</head>");
out.println("<body>");
out.println("<h1>Hello Servlet</h1>");
out.println("</body>");
out.println("</html>");
}
@Override
public void destroy() {
System.out.print("-----------销毁----------");
}
@Override
public void init() throws ServletException {
System.out.print("-----------初始化----------");
}
}
通过日志我们可以看到我们在访问自己的servlet时,首先进行初始化,接着调用我们的get方法。
———–初始化———-2017-10-11 14:30:50.141 INFO 793 — [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2017-10-11 14:30:50.148 INFO 793 — [ main] c.g.AresApplication : Started AresApplication in 6.778 seconds (JVM running for 8.336)
———-doGet()———
————doPost()———–
注意一点,我们需要在我们的启动类上加上@ServletComponentScan,开启servlet扫描。
@SpringBootApplication()
@MapperScan(value = "com.god.dao")
@ServletComponentScan
public class AresApplication {
public static void main(String[] args) {
SpringApplication.run(AresApplication.class, args);
}
}
这样我们就能在访问http://localhost:8080/myServlet/xxx时看到hello servlet输出了。
这里需要注意一下几点:
- name=”myServlet”不指定name的情况下,name默认是类全路径,即com.god.servlet.TestServlet
- urlPatterns = “”为需要拦截的内容
- loadOnStartup = 0 指在项目启动的时候加载
- 注意若另一个servlet使用了相同的urlPatterns,则根据loadOnStartup大小判断访问路径,如另外一个otherServlet的loadOnStartup为1,则会访问otherServlet,当然,urlPatterns会优先进行精确匹配,这几点可以看上一章中servlet与filter等关系中了解。
Filter
这里直接上代码,我们定义了一个TestFilter过滤器,命名为myFilter,过滤所有的url请求,并开启异步处理。
@WebFilter(filterName = "myFilter", urlPatterns = "/*", asyncSupported = true)
public class TestFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("-----------过滤器初始化-----------");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
String uri = httpServletRequest.getRequestURI();
if (!uri.contains("myServlet")) {
httpServletResponse.sendRedirect(httpServletRequest.getRequestURI() + "/myServlet/filter");
}
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
System.out.println("-----------过滤器销毁-----------");
}
}
在这里我们获取访问请求中的url地址,判断是否包含myServlet路径,若没有则加上/myServlet/filter,同时我对之前myServlet路径做了修改,在前面加上了test。这样,我们在访问http://localhost:8080/test时就会跳转到http://localhost:8080/test/myServlet/filter从而你就能在界面上看到Hello Servlet啦。如果细心一点你就会发现filter的初始化优先于servlet。看日志:
2017-10-11 15:09:40.010 INFO 824 --- [ost-startStop-1] o.s.b.w.s.ServletRegistrationBean : Mapping servlet: 'myServlet' to [/test/myServlet/*]
-----------过滤器初始化-----------
2017-10-11 15:09:41.728 INFO 824 --- [ main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@1eb6749b: startup date [Wed Oct 11 15:09:36 CST 2017]; root of context hierarchy
2017-10-11 15:09:41.856 INFO 824 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2017-10-11 15:09:41.858 INFO 824 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2017-10-11 15:09:41.904 INFO 824 --- [ main] o.s.w.s.h.SimpleUrlHandlerMapping : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-10-11 15:09:41.904 INFO 824 --- [ main] o.s.w.s.h.SimpleUrlHandlerMapping : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-10-11 15:09:42.009 INFO 824 --- [ main] o.s.w.s.h.SimpleUrlHandlerMapping : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-10-11 15:09:42.606 INFO 824 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup
-----------初始化----------2017-10-11 15:09:42.716 INFO 824 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
Listener
从上一篇博文我们可以了解到listener有包括:ServletContextListener、ServletContextAttributeListener 、HttpSessionListner、HttpSessionAttributeListener、HttpSessionBindingListener、 HttpSessionActivationListener、ServletRequestListner、ServletRequestAttributeListener 多种类型,在这里我们使用HttpSessionListner进行演示。
首先我们在之前的doFilter代码中加上
HttpSession httpSession = httpServletRequest.getSession();
httpSession.setMaxInactiveInterval(5);//设置session过期时间
接着,我们创建我们的TestListener类,这里面使用了读写锁,具体的大家可以自己去了解一下。
@WebListener(value = "myListener")
public class TestListener implements HttpSessionListener{
private static int count = 0;
ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
@Override
public void sessionCreated(HttpSessionEvent httpSessionEvent) {
ServletContext servletContext = httpSessionEvent.getSession().getServletContext();
String countStr = servletContext.getAttribute("onlineCount").toString();
reentrantReadWriteLock.writeLock().lock();
if (null != countStr && "" != countStr) {
count = Integer.parseInt(countStr);
}
count++;
reentrantReadWriteLock.writeLock().unlock();
reentrantReadWriteLock.readLock().lock();
servletContext.setAttribute("onlneCount", count);
System.out.println("在线人数: " + count);
reentrantReadWriteLock.readLock().unlock();
}
@Override
public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
ServletContext servletContext = httpSessionEvent.getSession().getServletContext();
reentrantReadWriteLock.writeLock().lock();
count--;
reentrantReadWriteLock.writeLock().unlock();
reentrantReadWriteLock.readLock().lock();
servletContext.setAttribute("onlineCount", count);
System.out.println("在线人数: " + count);
reentrantReadWriteLock.readLock().unlock();
}
}
当我们浏览器访问时,会创建session,sessionListener会监听到session创建从而执行sessionCreated方法,对count计数加1,而session销毁时则会执行sessionDestroyed方法对count计数减1。
以上就是spring boot中通过自动注册实现SFL的方式,下面我们来看下如果通过代码注入的方式实现。直接上代码
/**
* servlet注册
* @return
*/
@Bean
public ServletRegistrationBean servletRegistrationBean() {
return new ServletRegistrationBean(new TestServlet(), "/test/myServlet/*");
}
通过查看ServletRegistrationBean可以知道第一个参数是我们servlet类,第二个参数是需要拦截的路径,可以使多路径。这样就能够访问我们的servlet啦!