前言:Servlet是javaEE规范中的一种,javaEE中的规范很多除了Servlet还有很多我们熟悉的JSP、JDBC、RMI、XML、EJB、JTS等等。他们每个都有自己不同的角色,每一种规范在企业级java应用中都承担了不可或缺的角色。Servlet是Service + Applet的缩写,表示小服务程序。从命名就可以看出他是被用来书写服务端程序的。但是在这个很讲究开发效率的年代已久见不到原生的Servlet程序写的服务端程序了。几乎主流的框架都对他进行了封装,比如SpringMVC便是如此,比如SpringMVC的核心DispatcherServlet,SpringMVC正是基于他来将请求进行解析、适配、映射的最后到达我们想要调用的接口中。
一、Servlet
1.Servlet是什么
Servlet = Service + Applet,表示小服务程序,他是javaEE中的一种规范,狭义的Servlet是指Java语言实现的一个接口,广义的Servlet是指任何实现了这个Servlet接口的类,一般情况下,人们将Servlet理解为后者。Servlet运行于支持Java的应用服务器中比如Tomcat。从原理上讲,Servlet可以响应任何类型的请求,但绝大多数情况下Servlet只用来扩展基于HTTP协议的Web服务器。
2.Servlet的作用
从上面的介绍我们可以看出,Servlet的作用就是写服务端程序的,这是没有任何问题的,在开发者的角度这就是Servlet的作用,此外Servlet其他的作用都是相对于服务器来说的,他指定了Http服务器管理动态资源文件的规则等。java中所有的前端发送的请求都必须经过Servlet接口的实现类。我们使用的主流框架中比如SpringBoot、SpringCloud都看不到Servlet的身影,那是因为他们内置的SpringMVC中对Servlet都进行了封装。
3.ServletDemo
下面展示一个Servlet程序的写法,首先需要使用IDEA创建一个javaEE的工程,
第一步:选择Module来进行创建。
第二步:选择java企业版,然后勾选web应用即可,剩余的部分都是自己随便填好了,这样我们就可以创建出一个Servlet的应用了。
第三步:创建一个java类继承Servlet接口,并重写doGet、doPost方法。
import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; public class Servlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("执行了post请求。。。"); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("执行了get请求。。。"); } }
第四步:在web.xml中配置Servlet
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <servlet> <servlet-name>Servlet</servlet-name><!-- 为Servlet起个别名 --> <servlet-class>com.cn.controller.Servlet</servlet-class><!-- Servlet的全限定名 --> </servlet> <servlet-mapping> <servlet-name>Servlet</servlet-name><!-- 上方起的别名在下面为他配映射地址 --> <url-pattern>/test</url-pattern><!-- 为该别名配置地址,相当于我们在注解RequestMapping中配地址 --> </servlet-mapping> </web-app>
到这里为止,一个简单的Servlet就开发完了,剩下的就是部署测试了,我们需要将这个程序打成war包发布到Tomcat上,IDEA已经帮我们打好了包,我们只需要将该包加载进Tomcat即可,值得注意的是我们最好服务名从新配置一个,不然会有些长,就是图中所示位置
这一切都准备完了,我们就可以来开始启动Tomcat访问了,启动后自动弹出地址,我们加上自己配的test即可访问到刚刚的Servlet了。展示如下,可以看到IDEA中正常输出了“执行了get请求。。。”,地址栏请求都是get请求,因此我们程序是没有问题的,这样就完成了简单的前后端交互,说明这个Servlet没有问题。
4.Servlet的生命周期
说Servlet就必须提的是Servlet的声明周期,Servlet默认是在有请求到达时才会创建,可以通过配置实现在Http服务器启动时就启动,且默认也都是单利模式。然后会调用Servelt的init方法,然后就是service方法,然后service会根据是get还是post请求去调用不同的子类方法,最后在Http服务器关闭时会调用distroy方法销毁Servlet的实例。这里还涉及到了一个设计模式的使用即模板模式,我们继承HttpServlet时只要重写doGet、doPost等方法,是因为在HttpServlet中会根据请求的不同去分别调用不同的方法,我们只需要提供方法的实现就可以,逻辑都是确定的了,这便是模板模式。
生命周期:创建-init----->service----->doGet(doPost)----->distroy
二、过滤器Filter、监听器Listener
1.过滤器
Servlet是javaEE的一种规范,而过滤器Filter可以看成是Servlet的一种规范,过滤器的作用主要是过滤请求地址,过滤请求参数的信息,过滤器在实际开发中最常用的就是Xss过滤(Xss过滤就是将报文中可能产生危害的信息进行替换)。
2.监听器
与过滤器类似,监听器也是Servlet的一种规范,我们可以将它们都看出是Servlet,因为他们的实现机制都是一样的,但是监听器的用途就比较单一了。监听器主要用于监控作用域以及作用域中属性的变化。监听器总共有8种,与过滤器类似,他们都是接口都需要我们自己实现。下面列出8种监听器接口:
监听作用域的声明周期的监听器: ServletContextListener ServletRequestListener HttpSessionListener 监听作用域属性变化的监听器: ServletContextAttributeListener ServletRequestAttributeListener HttpSessionAttributeListener 监听Session的活化与钝化 HttpSessionActivationListener 监听Session与对象的绑定 HttpSessionBindingListener
3.过滤器的示例Demo
3.1实现Filter接口
写一个类继承Filter,然后重写doFilter方法即可,init和destroy重不重写没有关系
public class LoginFilter 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 { System.out.println("进入到过滤器1"); filterChain.doFilter(servletRequest,servletResponse); System.out.println("退出了过滤器1"); } @Override public void destroy() { System.out.println("销毁"); } }
3.2配置web.xml
在web.xml中配置该Filter,如下,这样就完成了Filter的开发。
<filter> <filter-name>LoginFilter</filter-name> <filter-class>com.cn.filter.LoginFilter</filter-class> </filter> <filter-mapping> <filter-name>LoginFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
3.3 展示测试结果
因为过滤器中只是打印了进入和退出,就直接放行了,所以正常就会输出进入过滤器->进入请求->退出过滤器,下面来看下结果也正如预测那样。
3.4多个Filter时,过滤器是如何执行的
这里就不展示了,我们可以看到一个过滤器时请求到达服务端是先进入过滤器,再到达接口,接口处理完再经过过滤器退出服务器的。多个过滤器时其实就是对接口进行套娃,或者说对多虑的内容进行套娃。画个丑图理解下:
如上图所示一个请求想要到达接口需要穿过所有的过滤器,进入过滤时的顺序和退出时恰恰是相反的,整个流程应该是Filter1—>Filter2—>Filter3—>进入接口执行完毕—>Filter3—>Filter2—>Filter1。这样一看这个流程是不是就像个俄罗斯套娃呢,最里面的就相当于接口,他被层层过滤器所包裹。上面所说的是多个过滤器的执行顺序固定后就是这么处理的,多个处理器到底应该先执行哪一个则是根据他们在web.xml中的配置顺序决定的。
4.比较监听器与过滤器
监听器与过滤器的实现没啥区别,就不一一赘述了,说下监听器与过滤器的区别,监听器主要作用就是监听作用域的,所有监听器都是为了监听作用域,再无他用,不能说应用场景很少,只能说开发中很少用到,写框架应该会经常用到,开发中用的确实不多,而过滤器的作用就很明显了,基本上每个项目都是必不可少的,我们可以通过过滤器对请求和响应都设置一些通用的东西,或者过滤掉有安全隐患的内容,以上是他们作用的不同,他们的相同点就是都是Servlet的规范的实现。
三、拦截器interceptor
说到过滤器和监听器不提拦截器是不合适的,因为他们是java里面的三大器,这三大器各司其职共同保证了程序的稳定运行。每当提起其中一个都会自然而然想到另外两个,那下面我们就来总结下拦截器。
1.拦截器是什么
拦截器我们使用时基本都是使用框架已经实现好的拦截器,基本不会自己写,现在常用的框架中SpringBoot、SpringCloud中我们可以去继承HandlerInterceptorAdapter类来去重写里面的preHandle、postHandle、afterHandle三个方法,如下所示:
public class LoginInterceptor extends HandlerInterceptorAdapter { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("拦截器预处理"); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("拦截器返回之前执行"); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("拦截器返回之后执行"); } }
此外我们还需要将这个拦截器交给Spring容器去管理,还需要为该拦截器配置拦截路径和排除路径,如下:
@Configuration public class InterceptorConfiguration extends WebMvcConfigurerAdapter { @Bean public LoginInterceptor getLoginInterceptor(){ return new LoginInterceptor(); } @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(getLoginInterceptor()) .addPathPatterns("/**") .excludePathPatterns("/find"); } }
这样我们就完成了拦截器的开发了,如果你使用的是最新版本的SpringBoot和SpringCloud你会发现,WebMvcConfigurerAdapter 和HandlerInterceptorAdapter 都已经被弃用了。他们的实现类上都被加了Deprecated这个注解,最新的实现方式已经不使用者两个类了,而是变成了两个接口,但是对于使用者来说实现的方式还是完全一样,对使用影响很小,最新版我们分别使用这两个接口就可以,操作什么的都还是一样:
WebMvcConfigurer、HandlerInterceptor,此外这里值得提一句的是配置过滤路径是我们一定要写成/**而不能写成/*,前者代表拦截所有请求,后者则只拦截根路径下的请求,在真实项目中使用后者基本没有意义。
2.拦截器的原理
与过滤器和监听器不同的是,拦截器的实现不是Servlet,而是通过java的动态代理来实现的。java的动态代理相信基本都会知道,如果不清楚可以翻下资料,拦截器正是使用的java的动态代理机制,来对接口执行前后来进行处理的,从而达到了对接口进出拦截的目的。
3.多个拦截器是如何执行的
前面说过多个过滤器的执行顺序就像是套娃,那拦截器呢,拦截器也是一样多个拦截器的执行也和俄罗斯套娃一样,图就不重复画了。值得注意的一点是postHandle是在方法返回之前执行的。
4.多个拦截器的执行顺序
我们在addInterceptors中可以配置多个拦截器,在该方法中配置拦截器的顺序就是拦截器执行的顺序,假如有如下的代码,那就是先执行CSRFInterceptor再执行LoginInterceptor。
@Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(getCSRFInterceptor()) .addPathPatterns("/**"); registry.addInterceptor(getLoginInterceptor()) .addPathPatterns("/**") .excludePathPatterns("/find"); }
5.拦截器的应用场景
拦截器常被用于拦截CSRF(跨站点请求伪造)的请求,还经常被应用于登录拦截,验证用户是否已经登录。这些都是比较常用的。
四、过滤器和拦截器
上面已经总结完了,过滤器、监听器、拦截器。其中监听器作用比较单一仅是被用作监听作用域(这里只讨论Servlet提供的原生监听接口),但是过滤器和拦截器好像都可以拦截用户发出的请求,那他们有什么区别呢。
区别:
1.实现原理不同,拦截器是动态代理,过滤器是Servlet
2.作用点不一样,过滤器因为是Servlet所以请求肯定先到达过滤器才能到达拦截器。
3.拦截器除了可以处理响应前后的数据,还可以对返回之前的数据进行处理,这得益于java的动态代理。
相同点
1.都可以对请求响应前后进行处理。
有人说过滤器可以过滤所有请求,而拦截器不可以拦截所有请求,这里笔者不敢苟同,他们虽然实现机制不一样,但是都是可以拦截到所有请求的,只是拦截的位置不一样,过滤器相当于第一层、然后才会走到Servelt(可以看成DispatherServlet)再然后才到拦截器最后才能进入到接口,其实他们都可以对所有请求进行拦截,只不过过滤器的拦截点更靠前,所以对于安全性要求较高的拦截还是应该使用过滤器来处理。下面画个丑图来展现下过滤器和拦截器。