从spring-boot开始深入理解spring系列——spring-boot使用servletsfilterlistenerinterceptor(代码片段)

独孤文彬 独孤文彬     2022-12-10     504

关键词:

文章目录

基础概念理解

servlet

Servlet 是一种运行 服务器端 的 java 应用程序,具有 独立于平台和协议 的特性,并且可以动态的生成 web 页面,它工作在 客户端请求 与 服务器响应 的中间层。
扩展认知:同时servelet也是一种JavaEE的技术规范和标准

filter

Filter 又称为过滤器,对 用户请求 进行 预处理,接着将请求交给 Servlet 进行 处理 并 生成响应,最后 Filter 再对 服务器响应 进行 后处理。Filter 是可以复用的代码片段,常用来转换 HTTP 请求、响应 和 头信息。Filter 不像 Servlet,它不能产生 响应,而是只 修改 对某一资源的 请求 或者 响应。

listener

Listener 可以监听 web 服务器中某一个 事件操作,并触发注册的 回调函数。通俗的语言就是在 application,session,request 三个对象 创建/消亡 或者 增删改 属性时,自动执行代码的功能组件。

interceptor

Java 里的拦截器是动态拦截 action 调用的对象。它提供了一种机制可以使开发者可以定义在一个 action 执行的前后执行的代码,也可以在一个 action 执行前阻止其执行,同时也提供了一种可以提取 action 中可重用部分的方式。
类似 面向切面编程 中的 切面 和 通知,我们通过 动态代理 对一个 service() 方法添加 通知 进行功能增强。比如说在方法执行前进行 初始化处理,在方法执行后进行 后置处理。

对比

servlet和filter的关系

servlet技术规范中,定义了filter技术,并提供了接口
Servlet API中提供了一个Filter接口,开发web应用时,如果编写的Java类实现了这个接口,则把这个java类称之为过滤器Filter。通过Filter技术,开发人员可以实现用户在访问某个目标资源之前,对访问的请求和响应进行拦截。简单说,就是可以实现web容器对某资源的访问前截获进行相关的处理,还可以在某资源向web容器返回响应前进行截获进行处理。

filter和listener和interceptor的对比

interceptor和aop的关系

aop是一种编程思想和理念,java中的拦截器是AOP思想的一种简单应用,相当于阉割版的AOP。
aop框架实现了aop的思想,例如SpringAOP、jbossAOP、aspectJ等,并且都用到了反射、JDK的动态代理/静态代理技术/cglib字节码动态生成,最终通过代理的方式通过反射调用目标类的方法
而各大主流框架所实现的拦截器,相当于是一个简化版的AOP框架,例如:mybatis的插件、structs2的拦截器、SpringMVC的拦截器。(当然,你也可以定义和实现自己的拦截器。并且很容易基于这些框架的基础之上,定义一个自己的拦截器。甚至,你可以完全自己手工实现一个简版的拦截器。)

interceptor 和 filter的关系

从本质上理解:

  • 都是一种拦截方式,都能够做预处理和后处理。但,相比于Filter, 只是框架中的Interceptor的产生作用的时间和位置、以及拦截的范围不一样
  • 都是AOP思想的一种简单实现

从使用上来讲,filter能对所有的web请求,起作用,功能更加强大。而interceptor只能对action起作用。

深入理解:

原理详解

servlet

一、Servlet生命周期分为三个阶段:
1、初始化阶段,调用init()方法
2、响应客户请求阶段,调用service()方法
3、终止阶段,调用destroy()方法
二、Servlet初始化阶段,在下列时刻Servlet容器装载Servlet
1、Servlet容器启动时自动装载某些Servlet
2、在Servlet容器启动后,客户首次向Servlet发送请求
3、Servlet类文件被更新后,重新装载Servlet。
三、Servlet接收和响应客户请求的过程
1、首先客户发送一个请求。
2、Servlet容器会创建特定于这个请求的ServletRequest对象和ServletResponse对象,然后调用Servlet的service()方法对请求进行响应。
3、service()方法中,对请求的方式进行了匹配,选择调用doGet,doPost等这些方法
4、在doGet、doPost等方法中调用逻辑层的方法,实现对客户的响应

注:
在Servlet接口和GenericServlet类中是没有doGet、doPost等等这些方法的,HttpServlet中定义了这些方法,但是都是返回error信息,所以我们每次定义一个Servlet的时候,都必须实现doGet或doPost等这些方法。
Servlet接口和GenericServlet是不特定于任何协议的,而HttpServlet是特定于HTTP协议的类,所以HttpServlet中实现了service()方法,并将请求ServletRequest,ServletResponse强转为HttpRequest和HttpResponse。

filter

本质:
FilterChain是回调接口, doFilter(request,response)是回调方法,ApplicationFilterChain是实现类,里面能得到实现了Filter接口的实现类xxxFilter,在doFilter(request,response)中执行中了某个Filter实现类的doFilter(request, response, this)(this指的当前ApplicationFilterChain类)方法,在这个方法执行某些处理后需要回调ApplicationFilterChain.doFilter(request,response),这个回调会执行filter链中的下一个循环到结束 。这也解释了为什么在Filter中没有调用chain.doFilter()方法,客户请求不会到达所访问的Web组件。

原理:
HttpRequest ----> Filter ----> Servlet ----> Controller/Action/… ----> Filter ----> HttpResponse

生命周期:
Filter的创建和销毁由WEB服务器负责。 web 应用程序启动时,web 服务器将创建Filter 的实例对象,并调用其init方法,读取web.xml配置,完成对象的初始化功能,从而为后续的用户请求作好拦截的准备工作(filter对象只会创建一次,init方法也只会执行一次)。开发人员通过init方法的参数,可获得代表当前filter配置信息的FilterConfig对象。

listener

监听器就是一个实现特定接口的普通java程序,这个程序专门用于监听一个java对象的方法调用或属性改变,当被监听对象发生上述事件后,监听器某个方法将立即被执行。

简单的说,被监听对象A中,关联着B对象。事件源A类对外提供一个方法,用于设置监听器对象B到A类的某一实例变量中。在需要监听事件源的方法中,方法体的某一处先构造创建一个Event对象,将this即B与相关的动作封装进Event对象中,然后调用监听器B对象的doXXXX(event)方法,将事件对象传入方法实参中。


扩展知识点:

  • 在Servlet规范中定义了多种类型的监听器,它们用于监听的事件源分别为SerlvetConext,HttpSession和ServletRequest这三个域对象。
  • Servlet规范针对这三个对象上的操作,又把这多种类型的监听器划分为三种类型:
  • 1,监听三个域对象创建和销毁的事件监听器.
    a、如三个域对象的创建与销毁方法签名:
  • ServletRequestListener
  • HttpSessionListener
  • ServletContextListener
  • 2,监听域对象中属性的增加和删除的事件监听器
    b、三个类型对象域中增、删、改的监听器(3个)
  • ServletContextAttributeListener,
  • HttpSessionAttributeListener,
  • ServletRequestAttributeListener
  • 3,监听绑定到HttpSession域中的某个对象的状态的时间监听器。
    -c、感知型监听器(2个):监听自己何时被帮到session上,何时解绑了;何时被钝化了,何时被活化了(序列化到某个存储设置中)。
  • HttpSessionBindingListener:实现该接口的类,能检测自己何时被Httpsession绑定,和解绑
  • HttpSessionActivationListener:实现该接口的类(要求些javabean必须是实现了Serializable接口的),能监测自己何时随着HttpSession一起激活和钝化。

注意:这种监听器不需要注册。某个javabean实现这些接口后就可以监听何时被绑定、解绑或被激活或钝化。

interceptor
原理技术点

  • 反射技术
  • 代理技术
  • 职责链模式

主流框架中,对拦截器的应用

  • webMVC框架:structs2/ springMVC
  • 持久化框架:mybatis中的插件(拦截器)

执行顺序,以springmvc为例

HttpRequest ----> DispactherServlet ----> HandlerInterceptor ---->Controller----> HandlerInterceptor ----> HttpResponse

生命周期、执行顺序的理解

总结:

Filter起作用的时机是在请求到达Servlet之前,HandlerInterceptor其作用的时机是在DispactherServlet接收到用户请求完成请求到相应的Handler映射之后。

虽然都先于在具体的业务逻辑执行,但是还是存在一些差异,Filter面对的是所有的请求,而HandlerInterceptor是面对具体的Controller。Filter总是先于HandlerInterceptor发挥作用,在Filter中甚至可以中断请求,从而使它无法到达相应的Servlet。而且两者的配置也不一样,Filter是在web.xml中进行配置,HandlerInterceptor是在具体的applicationContext.xml中进行配置。

实战:

示例应用

自定义filter示例

@WebFilter(filterName = "myFilter", urlPatterns = "/*")
public class MyFilter implements Filter

    private static Logger LOGGER = LoggerFactory.getLogger(MyFilter.class);

    @Override
    public void init(FilterConfig filterConfig) throws ServletException 
LOGGER.info("my filter just init……");
    

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException 
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        String url = httpRequest.getRequestURI();
        LOGGER.info("请求的地址: ", url);
            LOGGER.info("请求的session id: ",httpRequest.getSession().getId().toString());
        LOGGER.info("before do_filter action , write your code here , before do_filter!");
//example:setMaxInactiveInterval,session will destroy after 1 min
        httpRequest.getSession().setMaxInactiveInterval(1*60);
        httpRequest.setAttribute("customArg","bill cindy alice ! they are family ! wonderful!");
        chain.doFilter(httpRequest, httpResponse);
        LOGGER.info("filter response action , write your code here, after do filter!");
        //example: addCookie and set a invalid status code to demonstrate
        // httpResponse.getOutputStream().flush();
    

    @Override
    public void destroy() 
LOGGER.info("my filter was destroy!!");
    

自定义servlet:

@WebServlet(name = "myServlet", urlPatterns = "/myServlet")
public class MyServlet extends HttpServlet 
    private static Logger logger = LoggerFactory.getLogger(MyServlet.class);
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException 
        doPost(req, resp);
    

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException 
        logger.info("发起servlet请求:",req.getPathInfo());
        ServletOutputStream writer = resp.getOutputStream();
        // writer.write("hello world!");
        String text = "Now is the winter of our discontent. How Now Brown Cow. The quick brown fox jumped over the lazy dog.\\n";
        // for (int i = 0; i < 10; i++) 
        //     text = text + text;
        // 
        // resp.addCookie(new Cookie("username","bill"));
        // resp.setStatus(403);
        byte[] data = text.getBytes(StandardCharsets.UTF_8);
        writer.write(data);

        writer.flush();



    

自定义interceptor:

@Component
public class MyInterceptor extends HandlerInterceptorAdapter 
    private static final Logger LOGGER = LoggerFactory.getLogger(MyInterceptor.class);
    public MyInterceptor() 
        super();
    

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception 
LOGGER.info("===>>>preHandle i got the first interceptor handler , yes i love spring-boot now ! just enjoy coding with spring - spring-mvc -and spring-boot !");
        return super.preHandle(request, response, handler);
    

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception 
        LOGGER.info("<<<<===postHandle i got the first interceptor handler , yes i love spring-boot now ! just enjoy coding with spring - spring-mvc -and spring-boot !");
        super.postHandle(request, response, handler, modelAndView);
    

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception 
        LOGGER.info(" afterCompletion =====》》》》 i got the first interceptor handler , yes i love spring-boot now ! just enjoy coding with spring - spring-mvc -and spring-boot !");
        super.afterCompletion(request, response, handler, ex);
    



自定义listener 之 HttpSessionListener :

@WebListener("myHttpSessionListener")
public class MyHttpSessionListener implements HttpSessionListener 
    private static Logger logger = LoggerFactory.getLogger(MyHttpSessionListener.class);

    @Override
    public void sessionCreated(HttpSessionEvent se) 

        logger.info("session创建");

    

    @Override
    public void sessionDestroyed(HttpSessionEvent se) 
        logger.info("session销毁");

    

*自定义listener 之ServletContextListener :

@WebListener("MyListener")
public class MyServletListener implements ServletContextListener 
    private static Logger LOGGER = LoggerFactory.getLogger(MyServletListener.class);

    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) 
        LOGGER.info("监听器启动");
    

    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) 
        LOGGER.info("监听器销毁");
    

自定义listener 之 ServletRequestListener :

@WebListener("MyServletRequestListener")
public class MyServletRequestListener implements ServletRequestListener 
    private static final String REQUEST_ATTRIBUTES_ATTRIBUTE = RequestContextListener.class.getName() + ".REQUEST_ATTRIBUTES";
private static final Logger LOGGER = LoggerFactory.getLogger(MyServletRequestListener.class);
    @Override
    public void requestDestroyed(ServletRequestEvent requestEvent) 
LOGGER.info("ServletRequestListener destroyed !!!!");

    

    @Override
    public void requestInitialized(ServletRequestEvent requestEvent) 
LOGGER.info("ServletRequestListener init !!!");
        if (!(requestEvent.getServletRequest() instanceof HttpServletRequest)) 
            throw new IllegalArgumentException("Request is not an HttpServletRequest: " + requestEvent.getServletRequest());
         else 
            HttpServletRequest request = (HttpServletRequest)requestEvent.getServletRequest();
            ServletRequestAttributes attributes = new ServletRequestAttributes(request);
            request.setAttribute(REQUEST_ATTRIBUTES_ATTRIBUTE, attributes);
            LocaleContextHolder.setLocale(request.getLocale());
            RequestContextHolder.setRequestAttributes(attributes);
        
    

项目实战应用场景

filter:

  • 执行目标资源之前做预处理工作,例如设置编码,这种试通常都会放行,只是在目标资源执行之前做一些准备工作;
  • 通过条件判断是否放行,例如校验当前用户是否已经登录,或者用户IP是否已经被禁用;
  • 在目标资源执行后,做一些后续的特殊处理工作,例如把目标资源输出的数据进行处理

具体场景如

  • 统一POST请求中文字符编码的过滤器
  • 控制浏览器缓存页面中的静态资源的过滤器
  • 实现URL级别的权限认证:在实际开发中我们经常把一些执行敏感操作的servlet映射到一些特殊目录中,并用filter把这些特殊目录保护起来,限制只能拥有相应访问权限的用户才能访问这些目录下的资源。从而在我们系统中实现一种URL级别的权限功能。
  • 实现用户自动登陆:首先,在用户登陆成功后,发送一个名称为user的cookie给客户端,cookie的值为用户名和md5加密后的密码。编写一个AutoLoginFilter,这个filter检查用户是否带有名称为user的cookie,如果有,则调用dao查询cookie的用户名和密码是否和数据库匹配,匹配则向session中存入user对象(即用户登陆标记),以实现程序完成自动登陆。

listener

  • 在ServletContextLintener监听器的contextInitialized方法中,进行应用级的资源初始化以便提高效率,在contextDestroyed方法中对应用级的资源进行释放。
  • web应用中,会存在会话,通常的作法是将当前登录的用户存放在session会话中。那么如何统计在线人数话,如何显示出当前登录的用户呢。如何踢出某些已登录的用户呢。就可以通过HttpSessionAttributeListener监听器的attributeAdded方法。
  • 待补充……

interceptor:

  • 1、日志记录:记录请求信息的日志,以便进行信息监控、信息统计、计算PV(Page View)等。
  • 2、权限检查:如登录检测,进入处理器检测检测是否登录,如果没有直接返回到登录页面;
  • 3、性能监控:有时候系统在某段时间莫名其妙的慢,可以通过拦截器在进入处理器之前记录开始时间,在处理完后记录结束时间,从而得到该请求的处理时间(如果有反向代理,如apache可以自动记录);
  • 4、通用行为:读取cookie得到用户信息并将用户对象放入请求,从而方便后续流程使用,还有如提取Locale、Theme信息等,只要是多个处理器都需要的即可使用拦截器实现。
    5、OpenSessionInView:如hibernate,在进入处理器打开Session,在完成后关闭Session。
    6、实现SQL查询的分页,如mybatis的分页插件

源码下载

https://github.com/bill4j/spring-boot-course/tree/develop/spring-boot-filter

参考博客

java实现拦截器
理解java中的filter和interceptor
SpringMVC中的拦截器和原理
拦截器的基本原理
mybatis的拦截器
mybatis拦截器的使用和设计原理
AOP的实现机制
java中的三大器
Springboot入门servlet、filter、listener、interceptor

深入解析spring原理

IOC的基础 下面我们从IOC/AOP开始,它们是Spring平台实现的核心部分;虽然,我们一开始大多只是在这个层面上,做一些配置和外部特性的使用工作,但对这两个核心模块工作原理和运作机制的理解,对深入... 查看详情

点燃不会从 spring-boot 2.0.5 开始 - h2 属性 NESTED_JOINS 不存在

】点燃不会从spring-boot2.0.5开始-h2属性NESTED_JOINS不存在【英文标题】:ignitewon\'tstartwithspring-boot2.0.5-h2propertyNESTED_JOINSdoesn\'texist【发布时间】:2019-03-1004:00:34【问题描述】:我使用的是Ignite2.6,它作为一个独立的Java应用程序运行良... 查看详情

spring-boot简单的理解

SpringBoot启动SpringApplication.run(MyBootApplication.class);SpringApplication.run启动SpringBoot应用,主要过程要创建Spring容器对象根据MyBootApplication注解标记功能创建Bean组件对象纳入Spring容器中(@SpringBootApplication)如果是web程序,会自动启动Tomc 查看详情

让你的spring-boot应用日志随心所欲--springboot日志深入分析(代码片段)

...用于记录日志。2.springboot日志默认配置我们启动一个空的spring-boot项目看一下控制台的日志控制台的默认配置lo 查看详情

深入理解springioc(代码片段)

深入理解IoC在一开始学习Spring的时候,我们就接触IoC了,作为Spring第一个最核心的概念,我们在解读它源码之前一定需要对其有深入的认识。IoC理论IoC全称为InversionofControl,翻译为“控制反转”,它还有一个别名为DI(DependencyInj... 查看详情

从零开始手写springioc框架,深入学习spring源码

IoCIoc是一款springioc核心功能简化实现版本,便于学习和理解原理。创作目的使用spring很长时间,对于spring使用非常频繁,实际上对于源码一直没有静下心来学习过。但是spring源码存在一个问题,那就是过于抽象,导致学习起来成... 查看详情

2死磕spring——ioc之深入理解springioc(代码片段)

...ttp://cmsblogs.com/?p=2652 「小明哥」,谢谢!在一开始学习Spring的时候,我们就接触IoC了,作为Spring第一个最核心的概念,我们在解读它源码之前势必需要对其有深入的认识,本篇为【死磕Spring】系列博客 查看详情

spring-boot第一章:快速开始

快速开始创建pom.xml文件<?xmlversion="1.0"encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance&quo 查看详情

从 Spring-Boot 测试中排除 elasticsearchTemplate

】从Spring-Boot测试中排除elasticsearchTemplate【英文标题】:ExcludeelasticsearchTemplatefromSpring-BootTest【发布时间】:2022-01-0812:14:27【问题描述】:我有一个使用Elasticsearch的应用程序,我想在测试某些控制器时禁用此集成。如何在Spring-Boo... 查看详情

从 json 文件加载 spring-boot 属性

】从json文件加载spring-boot属性【英文标题】:Loadspring-bootpropertiesfromjsonfile【发布时间】:2017-11-1718:00:54【问题描述】:是否可以从.json文件而不是.yaml或.properties文件加载spring-boot配置?从查看文档来看,这不是开箱即用的支持-... 查看详情

spring-boot 从@ConfigurationProperties 中删除位置属性

】spring-boot从@ConfigurationProperties中删除位置属性【英文标题】:spring-bootRemovelocationsattributesfrom@ConfigurationProperties【发布时间】:2017-06-2407:25:15【问题描述】:从@ConfigurationProperties中移除位置属性有什么选择?我以前也这样用过。... 查看详情

从 spring-boot:run 获取命令行参数

】从spring-boot:run获取命令行参数【英文标题】:Getcommand-lineargumentsfromspring-boot:run【发布时间】:2014-06-1213:38:27【问题描述】:从命令行启动spring-boot应用程序(mvnspring-boot:run)时有什么方法可以输入参数,然后在main()中获取它们?... 查看详情

多模块 Gradle 项目 - 从 Spring-Boot 1.5 迁移到 2.1

】多模块Gradle项目-从Spring-Boot1.5迁移到2.1【英文标题】:Multi-ModuleGradleproject-MigratefromSpring-Boot1.5to2.1【发布时间】:2019-06-2610:13:43【问题描述】:我想将多模块spring-boot1.5项目迁移到spring-boot2.1。这是一个gradle项目(4.9),但不知... 查看详情

Spring-boot千分尺定时器()它是如何工作的?

】Spring-boot千分尺定时器()它是如何工作的?【英文标题】:Spring-bootmicrometertimer()howdoesitwork?【发布时间】:2019-01-2721:19:28【问题描述】:我不熟悉使用spring-boot指标并从千分尺开始。我找不到在我的spring-boot应用程序中执行计... 查看详情

深入理解spring

参考技术A一IOC容器概述ioc类型:构造注入属性注入接口注入类装载步骤:装载验证 准备 解析 初始化使用卸载classLoader:根加载器(bootstrap) 扩展加载器(ext) 系统加载器(app)采用全盘负责委托机制反射:可以从cla... 查看详情

深入理解spring注解机制:合并注解的合成

参考技术A众所周知,spring从2.5版本以后开始支持使用注解代替繁琐的xml配置,到了springboot更是全面拥抱了注解式配置。平时在使用的时候,点开一些常见的等注解,会发现往往在一个注解上总会出现一些其他的注解,比如@Servic... 查看详情

使用 Spring-Boot 2.1 从 .yml 读取对象列表 [重复]

】使用Spring-Boot2.1从.yml读取对象列表[重复]【英文标题】:readlistofobjectsfrom.ymlwithSpring-Boot2.1[duplicate]【发布时间】:2019-08-2123:40:48【问题描述】:我需要创建@ConfigurationProperties来读取包含复杂对象列表的.yml文件。Spring-Boot似乎遇... 查看详情

如何设置 spring-boot 以允许从外部 IP 地址访问网络服务器

】如何设置spring-boot以允许从外部IP地址访问网络服务器【英文标题】:Howtosetupspring-boottoallowaccesstothewebserverfromoutsideIPaddresses【发布时间】:2016-09-2313:25:00【问题描述】:我一直在研究如何在spring-boot中设置tomcat以允许来自外部IP... 查看详情