前提概要
web开发使用Controller基本能解决大部分的需求,但是有时候我们也需要使用Servlet,因为相对于拦截和监听来说,有时候原生的还是比较好用的。
因此本文就主要介绍web三大组件Servlet、Filter、Listener在SpringBoot中的使用做个介绍。本文重点做使用介绍,以及剖析SpringBoot是如何支持和解析这些方式的
Spring boot 的主 Servlet 为 DispatcherServlet,其默认的url-pattern为“/”。也许我们在应用中还需要定义更多的Servlet,该如何使用SpringBoot来完成呢?
三种方式
本文主要以使用Servelt为例子进行讲解,使用其余组件的方式也差不多。差异性比较大的地方我会尽量指出来,请举一反三
方式一: @ServletComponentScan 扫描的方式(推荐)
看看这个注解的javadoc
* Enables scanning for Servlet components ({@link WebFilter filters}, {@link WebServlet * servlets}, and {@link WebListener listeners}). Scanning is only performed when using an * embedded web server.
从javadoc中可以看出,该扫描的方式只支持嵌入式的web容器
使用此种扫描方式,显然是基于Servlet3.0的注解的方式。而该注解的解析由SpringBoot提供的@ServletComponentScan
来驱动的。
案例如下:
@ServletComponentScan @SpringBootApplication public class Boot2Demo1Application { public static void main(String[] args) { SpringApplication.run(Boot2Demo1Application.class, args); } } /** * @author fangshixiang * @description * @date 2019-01-28 14:39 */ @WebServlet(urlPatterns = "/servlet/demo", asyncSupported = false) public class DemoServlet extends HttpServlet { @Override public void init() throws ServletException { System.out.println("init my servlet"); super.init(); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("this is my servlet"); super.doGet(req, resp); } }
请求地址:http://localhost:8080/servlet/demo
控制台输出:
init my servlet this is my servlet
有的朋友可能会问:为毛init方法也是此时才输出呢???为了答疑,这里之直接贴出来答案吧:
init 方法是随 Servlet 实例化而被调用的,因为 load-on-startup 就是用来设置Servlet 实例化时间的。因此,init 方法执行的时刻有两种:
- load-on-startup 的值大于等于0,则伴随 Servlet 实例化后执行。
- load-on-startup 的值小于0 或者 不配置(默认行为), 则在第一次 Servlet 请求的时候执行。
备注:看下面启动日志
2019-01-28 15:51:40.926 INFO 16144 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean : Servlet com.sayabc.boot2demo1.servlet.DemoServlet mapped to [/servlet/demo]
可以看出,这种方式的底层原理还是转换成了ServletRegistrationBean,从而交给Spring容器管理了。同理:另外两大组件可以参照这个方式来定义和书写。
此方式优点:完全还原了源生servlet、filter、listener等功能,程序员自己也很方便的、更加细粒度。
方式二:通过ServletRegistrationBean进行组件注册
依托于SpringBoot提供的三个Bean:
ServletRegistrationBean FilterRegistrationBean ServletListenerRegistrationBean
实例代码:
@Bean public ServletRegistrationBean MyServlet1() { return new ServletRegistrationBean(new DemoServlet(), "/servlet/*"); }
访问路径:http://localhost:8080/servlet/demo控制台有对应日志输出(显然这种方式,init方法不会再执行了,因为自己new的嘛);
启动日志如下:
2019-01-28 15:59:37.412 INFO 14332 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean : Servlet demoServlet mapped to [/servlet/*]
1
优点:内部可以直接@Autowired注入Spring的Bean,也可配合@Order调整优先级
方式三:@Component
采用此种方式是最简单的方式。Spring Boot也非常友好的给支持了。
示例:
@Component public class DemoServlet extends HttpServlet { @Override public void init() throws ServletException { System.out.println("init my servlet"); super.init(); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("this is my servlet"); super.doGet(req, resp); } }
日志输出:
2019-01-28 17:20:19.601 INFO 8756 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean : Servlet demoServlet mapped to [/]
显然可以看出,直接使用@Component虽然方便,但是无法自定义urlPatterns,还是收到很多局限的。
备注:别想着这么做能达到效果(虽然我建议SpringBoot支持,哈哈)
@Component @WebServlet(urlPatterns = "/servlet/demo", asyncSupported = false) public class DemoServlet extends HttpServlet {}
这样直接做是不生效的。还是只能映射到跟路径。因为@WebServlet等注解是依赖于
@ServletComponentScan才能驱动的,因此此种方式虽然方便,但是还是有很多局限性的。
但是,但是,对应Filter,一般我们都希望他拦截所有的请求,因此这么来做是很方便的。比如TokenFilter
@Order(1) @Component public class TokenFilter implements Filter{}
这样默认拦截的路径为:Mapping filter: 'tokenFilter' to: [/*]
,从而会过滤所有的请求。
小知识点:
< url-pattern>/</url-pattern> 会匹配到/login这样的路径型url,不会匹配到模式为*.jsp这样的后缀型url < url-pattern>/*</url-pattern> 会匹配所有url:路径型的和后缀型的url(包括/login,*.jsp,*.js和*.html等)
所以如果以后发现总是有404错误的时候,别忘了check一下 /的配置是否是/*.
优点:内部可以直接@Autowired注入Spring的Bean,也可配合@Order调整优先级
简单总结
虽然Spring已经足够强大,几乎可以屏蔽我们对Servlet的Api。(Spring4以及以下版本的底层原理还是Servlet技术,但到了Spring5以后,servlet从必选项已经成为可选项了)
但是有的时候我们自己使用原生的方案更为妥当,因此本文针对于此提出一些方案和原理分析,仅供参考