Spring Boot 监听器、拦截器以及过滤器

Spring Boot 监听器、拦截器以及过滤器的使用

监听器

在一些业务场景中,当容器初始化完成之后,需要处理一些操作,比如一些数据的加载、初始化缓存、特定任务的注册等等。这个时候我们就可以使用Spring提供的ApplicationListener来进行操作。

拦截器

Interceptor 在AOP(Aspect-Oriented Programming)中用于在某个方法或字段被访问之前,进行拦截然后在之前或之后加入某些操作。比如日志,安全等。一般拦截器方法都是通过动态代理的方式实现。可以通过它来进行权限验证,或者判断用户是否登陆,或者是像12306 判断当前时间是否是购票时间。

它依赖于web框架,在SpringMVC中就是依赖于SpringMVC框架。在实现上,基于Java的反射机制,属于面向切面编程(AOP)的一种运用,就是在service或者一个方法前,调用一个方法,或者在方法后,调用一个方法,比如动态代理就是拦截器的简单实现,在调用方法前打印出字符串(或者做其它业务逻辑的操作),也可以在调用方法后打印出字符串,甚至在抛出异常的时候做业务逻辑的操作。由于拦截器是基于web框架的调用,因此可以使用Spring的依赖注入(DI)进行一些业务操作,同时一个拦截器实例在一个controller生命周期之内可以多次调用。但是缺点是只能对controller请求进行拦截,对其他的一些比如直接访问静态资源的请求则没办法进行拦截处理。

过滤器

Filter是Servlet技术中最实用的技术,它依赖于servlet容器。在实现上,基于函数回调,Web开发人员通过Filter技术,对web服务器管理的所有web资源:例如Jsp, Servlet, 静态图片文件或静态 html 文件等进行拦截,从而实现一些特殊的功能。例如实现URL级别的权限访问控制、过滤敏感词汇、压缩响应信息等一些高级功能。它主要用于对用户请求进行预处理,也可以对HttpServletResponse进行后处理。使用Filter的完整流程:Filter对用户请求进行预处理,接着将请求交给Servlet进行处理并生成响应,最后Filter再对服务器响应进行后处理。

使用过滤器的目的,是用来做一些过滤操作,获取我们想要获取的数据,比如:在Javaweb中,对传入的request、response提前过滤掉一些信息,或者提前设置一些参数,然后再传入servlet或者Controller进行业务逻辑操作。通常用的场景是:在过滤器中修改字符编码(CharacterEncodingFilter)、在过滤器中修改HttpServletRequest的一些参数(XSSFilter(自定义过滤器)),如:过滤低俗文字、危险字符等。

1. 监听器的使用

创建一个Spring Boot就不用说了,现在假设有这么一个场景:我需要再Spring启动的时候在用户表里insert条数据,便于启动完成后去读取,那就可以使用监听器,看如下代码:

import org.springframework.stereotype.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.boot.context.event.ApplicationPreparedEvent;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.ContextStartedEvent;
import org.springframework.context.event.ContextStoppedEvent;

@Component
public class AppListener implements ApplicationListener {

    private static final Logger LOGGER = LoggerFactory.getLogger(AppListener.class);
    @Autowired
    private UserService userService;


    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        // 在这里可以监听到Spring Boot的生命周期
        if (event instanceof ApplicationEnvironmentPreparedEvent) {
            // 初始化环境变量
            LOGGER.info("**************onApplicationEvent 初始化环境变量 **************");

        } else if (event instanceof ApplicationPreparedEvent) {
            // 初始化完成
            LOGGER.info("**************onApplicationEvent 初始化完成  **************");
        } else if (event instanceof ContextRefreshedEvent) {
            // 应用刷新
            init((ContextRefreshedEvent) event);
            LOGGER.info("**************onApplicationEvent 应用刷新完成  **************");

        } else if (event instanceof ApplicationReadyEvent) {
            //初始化操作
            User user= new User ();
            user.setName("超级管理员");
            user.setPassWord("123");
            userService.insert(user);
        }
            // 应用已启动完成
            LOGGER.info("**************onApplicationEvent 应用启动完成  **************");
        } else if (event instanceof ContextStartedEvent) {
            // 应用启动,需要在代码动态添加监听器才可捕获
            LOGGER.info("**************onApplicationEvent ContextStartedEvent 完成  **************");
        } else if (event instanceof ContextStoppedEvent) {

            LOGGER.info("**************onApplicationEvent 应用停止完成  **************");

        } else if (event instanceof ContextClosedEvent) {
            // 应用关闭
            LOGGER.info("**************onApplicationEvent 应用关闭完成  **************");
        } else {
            //LOGGER.info("**************onApplicationEvent 其他事件未处理  **************");
        }
    }
}

从以上代码中我们可以看到,ApplicationEvent是有多个类型的事件,在不同类型的事件中做不同类型的事情,实际根据业务来做,利用好Spring Boot的生命周期,另外,添加监听器的方法有多种,普遍的是在启动类里添加,但我这个人比较懒,直接在监听器这个类上加上@Component也是可以的,但要注意的是在启动类上要加上@ComponentScan(basePackages = "com.xx")注解开启扫描

2. 拦截器的使用

需要实现HandlerInterceptor接口,实现它的三个方法

public class ApiInterceptor implements HandlerInterceptor {

	//Controller方法调用之前
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //可以在这里实现鉴权等
        String token = request.getHeader("token");
        if("".equals(token) || token == null){
        	Result result = new Result();
        	result.setResMsg("token is null");
        	result.setCode(500);
        	response.getWriter().write(JSON.toJSONString(result));
        	return false;
        }
        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("控制器被执行结束");
    }
}

添加注册拦截器

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //这里可以添加多个拦截器
        registry.addInterceptor(new ApiInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/login");
    }

}

这个拦截器做了一个简单的例子,在添加拦截器的时候,有个addPathPatterns方法,里面输入的是要按照的请求,我这里拦截所有请求,但是有个excludePathPatterns方法,意思是登录接口请求不拦截,在拦截器中,获取了请求头中的token,要是没有的话,返回false,会结束所有请求,不放行,DispatcherServlet处理器认为拦截器已经处理完了请求,而不继续执行执行链中的其它拦截器和处理器。要是有token,通过认证,返回true,会走postHandle方法,请求结束,周期完成,会走afterCompletion方法,也是在为true的情况下。

3. 过滤器的使用

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class ServiceFilter implements Filter {

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

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws IOException, ServletException {
        HttpServletResponse resp = (HttpServletResponse) response;
        HttpServletRequest req = (HttpServletRequest) request;
        // 也可以用过滤器来鉴权,除去登录接口
        if ("/login".equals(req.getRequestURI())){
            filterChain.doFilter(req, resp);
            return;
        }
        String token = resp.getHeader("token");
        if("".equals(token) || token == null){
        	Result result = new Result();
        	result.setResMsg("token is null");
        	result.setCode(500);
        	response.getWriter().write(JSON.toJSONString(result));
        	return;
        }
        filterChain.doFilter(req, resp);
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void destroy() {
    }
}

添加过滤器配置

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {

    @Bean
    public FilterRegistrationBean omsFilter() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new ServiceFilter());
        registration.addUrlPatterns("/*");
        registration.setName("MainFilter");
        registration.setAsyncSupported(true);
        return registration;
    }
}

添加配置里的addUrlPatterns指定所有请求都将会经过滤器处理,在过滤器中做了判断,如果是登录请求,直接放行,不是登录请求的其他接口请求都需要鉴权。

总结

拦截器(Interceptor)是基于Java的反射机制,而过滤器(Filter)是基于函数回调。从灵活性上说拦截器功能更强大些,Filter能做的事情,都能做,而且可以在请求前,请求后执行,比较灵活。Filter主要是针对URL地址做一个编码的事情、过滤掉没用的参数、安全校验(比较泛的,比如登录不登录之类),太细的话,还是建议用interceptor。不过还是根据不同情况选择合适的。

执行顺序:
Request请求——监听器——过滤器——拦截器——AOP——controller

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值