重新认识servlet开发中的监听器、过滤器、拦截器

学习了这个久对于以上的四个在javaweb开发中经常会用的的几个东西相信很多java开发人员都会遇到,为了能够更好的区分以上的概念以及运用场景,我将对过滤器、监听器、拦截器进行收集和整理。

概念

首先我们需要先认识下servlet这个东西,servlet是一种运行在服务器端的java应用程序,具有独立于平台和协议的特性,可以处理客户端请求,执行service,可以动态生成web页面,工作在客户端请求与服务器相应的中间层。

生命周期:

装入(服务器)-> 初始化 -> 调用 -> 销毁。
(1)、装入:启动服务器时加载Servlet的实例;
(2)、初始化:web服务器启动时或web服务器接收到请求时,或者两者之间的某个时刻启动。初始化工作有init()方法负责执行完成;
(3)、调用:从第一次到以后的多次访问,都是只调用doGet()或doPost()方法;
(4)、销毁:停止服务器时调用destroy()方法,销毁实例。

监听器(Listener)

是servlet规范中定义的一种特殊类,用于监听servletContext、HttpSession、servletRequest等域对象的创建和销毁、属性修改事件,用于在事件发生前和发生后进行处理。实现了javax.servlet.ServletContextListener 接口的服务器端程序;
随web应用的启动而启动;只初始化一次;
随web应用的停止而销毁;

作用:

做一些初始化的内容添加工作、设置一些基本的内容、比如一些参数或者是一些固定的对象等等。如SpringMVC的监听器org.springframework.web.context.ContextLoaderListener,实现了SpringMVC容器的加载、Bean对象创建、DispatchServlet初始化等。

使用示例:
1.统计在线人数和在线用户
2.系统启动时加载初始化信息
3.统计网站访问量
4.跟spring结合

使用方式:

1.基本类实现
1、监听域对象自身的创建和销毁的事件监听器创建类并且实现以下接口:
servletContextListener、HttpSessionListener、ServletRequestListener
2、监听域对象中的属性的增加和删除的事件监听器创建类并且实现以下接口:
ServletContextAttributeListener\HttpSessionAttributeListener\ServletRequestAttributeListener
3、监听绑定到HttpSession域中的某个对象的状态的事件监听器实现两个接口
HttpSessionBindingListener()\HttpSessionActivationListener()

  1. 在servlet3.0下监听器的用法

使用注解@WebListener
该注解用于将类声明为监听器,被@WebListener标注的类必须实现以下至少一个接口:

    ServletContextListener
    ServletContextAttributeListener
    HttpSessionListener
    HttpSessionAttributeListener
    ServletRequestListener
    ServletRequestAttributeListener
  1. 使用springboot进行事件监听有四种方式:
    1.手工向ApplicationContext中添加监听器
    2.将监听器装载入spring容器
    3.在application.properties中配置监听器
    4.通过@EventListener注解实现事件监听。
    示例1:
public class MyListener1 implements ApplicationListener<MyEvent>
{
    Logger logger = Logger.getLogger(MyListener1.class);
    
    public void onApplicationEvent(MyEvent event)
    {
        logger.info(String.format("%s监听到事件源:%s.", MyListener1.class.getName(), event.getSource()));
    }
}

@SpringBootApplication
public class LisenterApplication
{
    public static void main(String[] args)
    {
        ConfigurableApplicationContext context = SpringApplication.run(LisenterApplication.class, args);
        //装载监听
        context.addApplicationListener(new MyListener1());
    }
}

过滤器(Filter)

Java的过滤器能够为我们提供系统级别的过滤,也就是说,能过滤所有的web请求,这一点,是拦截器无法做到的。
在Java Web中,传入的request,response提前过滤掉一些信息,或者提前设置一些参数,然后再传入servlet或者struts的action进行业务逻辑,比如过滤掉非法url(不是login.do的地址请求,如果用户没有登陆都过滤掉),或者在传入servlet或者struts的action前统一设置字符集,或者去除掉一些非法字符(聊天室经常用到的,一些骂人的话)。

filter 流程是线性的,url传来之后,检查之后,可保持原来的流程继续向下执行,被下一个filter, servlet接收。

使用示例

  1. 过滤非法url
    2.过滤非法字符(低俗字符)
    3.做过滤操作获取我么你想要的数据(过滤器中修改字符编码)。

使用方式

1.springboot+servlet3.0注释方式

编写实现接口的类+web.xml中配置
过滤器只要实现javax.servlet.filter重写doFilter(...)、init(...)和destroy(..)方法即可    
实现doFilter方法,完成对请求或响应的过滤
实现init方法,读取过滤器的初始化参数destroy(),过滤器销毁的时候做一些操作.
@Order(1)
@WebFilter(filterName = "piceaFilter", urlPatterns = "/*" , initParams = {
        @WebInitParam(name = "URL", value = "http://localhost:8080")})
public class PiceaFilter implements Filter {

    private String url;
    /**
     * 可以初始化Filter在web.xml里面配置的初始化参数
     * filter对象只会创建一次,init方法也只会执行一次。
     * @param filterConfig
     * @throws ServletException
     */
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        this.url = filterConfig.getInitParameter("URL");
        System.out.println("我是过滤器的初始化方法!URL=" + this.url +  ",生活开始.........");
    }

    /**
     * 主要的业务代码编写方法
     * @param servletRequest
     * @param servletResponse
     * @param filterChain
     * @throws IOException
     * @throws ServletException
     */
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("我是过滤器的执行方法,客户端向Servlet发送的请求被我拦截到了");
        filterChain.doFilter(servletRequest, servletResponse);
        System.out.println("我是过滤器的执行方法,Servlet向客户端发送的响应被我拦截到了");
    }

    /**
     * 在销毁Filter时自动调用。
     */
    @Override
    public void destroy() {
        System.out.println("我是过滤器的被销毁时调用的方法!,活不下去了................" );
    }
}

启动类中增加注解,自动注册Filter
@ServletComponentScan :在SpringBootApplication上使用@ServletComponentScan注解后,Servlet、Filter、Listener可以直接通过@WebServlet、@WebFilter、@WebListener注解自动注册,无需其他代码。

拦截器

依赖于web框架,在SpringMVC中就是依赖于SpringMVC框架。在实现上基于Java的反射机制,属于面向切面编程(AOP)的一种运用。由于拦截器是基于web框架的调用,因此可以使用Spring的依赖注入(DI)进行一些业务操作,同时一个拦截器实例在一个controller生命周期之内可以多次调用。但是缺点是只能对controller请求进行拦截,对其他的一些比如直接访问静态资源的请求则没办法进行拦截处理。

使用示例:

处理所有请求共性问题:
1、乱码问题:用request,response参数去设置编码;
2、解决权限验证问题(是否登陆,取session对象查看)
拦截器与过滤器的区别
1、拦截器Interceptor依赖于框架容器,基于反射机制,只过滤请求
2、过滤器Filter依赖于Servlet容器,基于回调函数,过滤范围大

使用方式

  1. springboot的方式
    继承WebMvcConfigurerAdapter spring5.0 以弃用,不推荐
    实现WebMvcConfigurer 推荐
    继承WebMvcConfigurationSupport 会导致springboot自动配置失效。
@Configuration
public class MvcConfig implements WebMvcConfigurer {

    @Autowired
    private HandlerInterceptor urlInterceptor;

    private static List<String> myPathPatterns = new ArrayList<>();

    /**
     * 在初始化Servlet服务时(在Servlet构造函数执行之后、init()之前执行),@PostConstruct注解的方法被调用
     */
    @PostConstruct
    void init() {
        System.out.println("Servlet init ... ");
        // 添加匹配的规则, /** 表示匹配所有规则,任意路径
        myPathPatterns.add("/**");
    }

    /**
     * 在卸载Servlet服务时(在Servlet的destroy()方法之前执行),@PreDestroy注解的方法被调用
     */
    @PreDestroy
    void destroy() {
        System.out.println("Servlet destory ... ");
    }

    /**
     * 注册配置的拦截器
     * @param registry 拦截器注册器
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // addPathPatterns 用于添加拦截规则
        // excludePathPatterns 用户排除拦截
        registry.addInterceptor(urlInterceptor).addPathPatterns(myPathPatterns).excludePathPatterns("/user/login");
    }

    // 下面的方法可以选择性重写
    /**
     * 添加类型转换器和格式化器
     * @param registry
     */
    @Override
    public void addFormatters(FormatterRegistry registry) {
//        registry.addFormatterForFieldType(LocalDate.class, new USLocalDateFormatter());
    }

    /**
     * 跨域支持
     * @param registry
     */
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("*")
                .allowCredentials(true)
                .allowedMethods("GET", "POST", "DELETE", "PUT")
                .maxAge(3600 * 24);
    }

    /**
     * 添加静态资源映射--过滤swagger-api
     * @param registry
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        //过滤swagger
        registry.addResourceHandler("swagger-ui.html")
                .addResourceLocations("classpath:/META-INF/resources/");

        registry.addResourceHandler("/webjars/**")
                .addResourceLocations("classpath:/META-INF/resources/webjars/");

        registry.addResourceHandler("/swagger-resources/**")
                .addResourceLocations("classpath:/META-INF/resources/swagger-resources/");

        registry.addResourceHandler("/swagger/**")
                .addResourceLocations("classpath:/META-INF/resources/swagger*");

        registry.addResourceHandler("/v2/api-docs/**")
                .addResourceLocations("classpath:/META-INF/resources/v2/api-docs/");

    }

    /**
     * 配置消息转换器--这里用的是ali的FastJson
     * @param converters
     */
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        //1. 定义一个convert转换消息的对象;
        FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();
        //2. 添加fastJson的配置信息,比如:是否要格式化返回的json数据;
        FastJsonConfig fastJsonConfig = new FastJsonConfig();
        fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat,
                SerializerFeature.WriteMapNullValue,
                SerializerFeature.WriteNullStringAsEmpty,
                SerializerFeature.DisableCircularReferenceDetect,
                SerializerFeature.WriteNullListAsEmpty,
                SerializerFeature.WriteDateUseDateFormat);
        //3处理中文乱码问题
        List<MediaType> fastMediaTypes = new ArrayList<>();
        fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
        //4.在convert中添加配置信息.
        fastJsonHttpMessageConverter.setSupportedMediaTypes(fastMediaTypes);
        fastJsonHttpMessageConverter.setFastJsonConfig(fastJsonConfig);
        //5.将convert添加到converters当中.
        converters.add(fastJsonHttpMessageConverter);
    }


    /**
     * 访问页面需要先创建个Controller控制类,再写方法跳转到页面
     * 这里的配置可实现直接访问http://localhost:8080/toLogin就跳转到login.jsp页面了
     * @param registry
     */
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/toLogin").setViewName("login");

    }

    /**
     * 开启默认拦截器可用并指定一个默认拦截器DefaultServletHttpRequestHandler,比如在webroot目录下的图片:xx.png,
     * Servelt规范中web根目录(webroot)下的文件可以直接访问的,但DispatcherServlet配置了映射路径是/ ,
     * 几乎把所有的请求都拦截了,从而导致xx.png访问不到,这时注册一个DefaultServletHttpRequestHandler可以解决这个问题。
     * 其实可以理解为DispatcherServlet破坏了Servlet的一个特性(根目录下的文件可以直接访问),DefaultServletHttpRequestHandler
     * 可以帮助回归这个特性的
     * @param configurer
     */
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
        // 这里可以自己指定默认的拦截器
        configurer.enable("DefaultServletHttpRequestHandler");
    }

    /**
     * 在该方法中可以启用内容裁决解析器,configureContentNegotiation()方法是专门用来配置内容裁决参数的
     * @param configurer
     */
    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
        // 表示是否通过请求的Url的扩展名来决定media type
        configurer.favorPathExtension(true)
                // 忽略Accept请求头
                .ignoreAcceptHeader(true)
                .parameterName("mediaType")
                // 设置默认的mediaType
                .defaultContentType(MediaType.TEXT_HTML)
                // 以.html结尾的请求会被当成MediaType.TEXT_HTML
                .mediaType("html", MediaType.TEXT_HTML)
                // 以.json结尾的请求会被当成MediaType.APPLICATION_JSON
                .mediaType("json", MediaType.APPLICATION_JSON);
    }

}

启动顺序:

监听器 > 过滤器 > 拦截器.

三者关系

取材自:
https://blog.csdn.net/c_royi/article/details/80563131

https://segmentfault.com/a/1190000017342619?utm_source=tag-newest

https://blog.csdn.net/jacksonary/article/details/84572701

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容