spring boot拦截方式

阅读数:217 评论数:0

跳转到新版页面

分类

python/Java

正文

一、过滤器filter

可以获取http请求和响应,但无法获取与spring框架相关的信息,主要用于内容上的过滤,如敏感字替换、非法请求过滤。

1、Filter接口

init() Filter只会初始化一次。需要设置初始化参数的时候,可以写到init()方法中。
doFilter 拦截要执行的请求,对请求和响应进行处理
destroy 关闭容器时调用 destroy() 销毁 Filter 的实例
//@Component
public class TimeFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) {
        System.out.println("过滤器初始化");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("过滤器执行了");
        long start2 = System.currentTimeMillis();
        filterChain.doFilter(servletRequest, servletResponse);
        long time = System.currentTimeMillis() - start2;
        System.out.println("过滤器执行的时间是 :" + time);
        System.out.println("过滤器执行结束");
    }

    @Override
    public void destroy() {
        System.out.println("过滤器销毁了");
    }
}

2、@WebFilter

通过 @WebFilter 注解,将类声明为 Bean 过滤器类。此时可以指定要拦截的url , 但是不能指定过滤器执行顺序。

@Slf4j
@WebFilter(urlPatterns = "/*")
public class MyFilter implements Filter {

    @Resource
    private RedisTemplate redisTemplate;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        //获取访问 ip 地址
        String ipAddr = getIpAddr(request);
        // 存入缓存10s不允许访问
        String key = new StringBuilder().append("bizKey").append(ipAddr).toString();
        
        if (redisTemplate.hasKey(key)) {
            // 访问次数自增1
            redisTemplate.opsForValue().increment(key, 1);
            log.warn("访问过快,存在强刷行为!key={}", key);
        } else {
            // 第一次访问
            redisTemplate.opsForValue().set(key, 1, 10,
                    TimeUnit.SECONDS);
        }
        try {
            filterChain.doFilter(servletRequest, servletResponse);
        } catch (Exception e) {
            log.warn("认证失败,e:{},url:{},parameters:{}", e,request.getRequestURL(),request.getParameterMap());
            servletResponse.setContentType("application/json");
            servletResponse.setCharacterEncoding("UTF-8");
            servletResponse.getWriter().write(JSONUtil.toJsonStr(Result.fail("业务执行报错~")));
        }
    }

    @Override
    public void destroy() {
        Filter.super.destroy();
    }

    public static String getIpAddr(HttpServletRequest request){
        String ipAddress = null;
        try {
            ipAddress = request.getHeader("X-Forwarded-For");
            if (ipAddress != null && ipAddress.length() != 0 && !"unknown".equalsIgnoreCase(ipAddress)) {
                // 多次反向代理后会有多个ip值,第一个ip才是真实ip
                if (ipAddress.indexOf(",") != -1) {
                    ipAddress = ipAddress.split(",")[0];
                }
            }
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getHeader("Proxy-Client-IP");
            }
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getHeader("WL-Proxy-Client-IP");
            }
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getHeader("HTTP_CLIENT_IP");
            }
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getRemoteAddr();
            }
        }catch (Exception e) {

        }
        return ipAddress;
    }
}

3、@Component

使用@Component注解后,可以使用@Order注解保证过滤器执行顺序,@Order 注解用于指定组件的执行顺序,其中值越小的组件优先执行。不使用@Order注解,则按照filter类名的字母顺序来执行。虽然可以保证执行顺序, 但是过滤器不能指定拦截的url , 只能默认拦截全部

@Component
@Order(1)
public class MyFilter1 implements Filter {
    // ...
}
@Component
@Order(2)
public class MyFilter2 implements Filter {
    // ...
}

4、@Configuration+@Bean

使用 @Configuration + @Bean 配置类,注解声明Bean,交由 Spring 容器管理。此方式既能拦截Url,也能指定执行顺序

public class MyFilter1 implements Filter {
    // ...
}

public class MyFilter2 implements Filter {
    // ...
}
@Configuration
public class DemoConfiguration {
    @Bean
    public FilterRegistrationBean RegistTest1(){
        //通过FilterRegistrationBean实例设置优先级可以生效
        //通过@WebFilter无效
        FilterRegistrationBean bean = new FilterRegistrationBean();
        bean.setFilter(new Test1Filter());//注册自定义过滤器
        bean.setName("flilter1");//过滤器名称
        bean.addUrlPatterns("/*");//过滤所有路径
        bean.setOrder(1);//优先级,最顶级
        return bean;
    }
    @Bean
    public FilterRegistrationBean RegistTest2(){
        //通过FilterRegistrationBean实例设置优先级可以生效
        //通过@WebFilter无效
        FilterRegistrationBean bean = new FilterRegistrationBean();
        bean.setFilter(new Test2Filter());//注册自定义过滤器
        bean.setName("flilter2");//过滤器名称
        bean.addUrlPatterns("/test/*");//过滤所有路径
        bean.setOrder(6);//优先级,越低越优先
        return bean;
    }
}

二、拦截器interceptor

除了获取 http请求和响应,还可以获取请求的类名、方法名,但拦截器无法获取请求参数值,主要用于对公共的一些拦截获取,例如IP地址等。

拦截器是通过Java反射机制来拦截web请求。拦截器,在AOP中用于在某个方法或字段被访问之前,进行拦截,然后在之前或之后加入某些操作。拦截是AOP的一种实现策略。

filter基于filter接口中的doFilter回调函数 interceptor基于java的反射机制
filter依赖于servlet容器 interceptor与servlet无关
filter过滤范围更大,除了请求外还可以保护图片、文件等 只对action起作用

1、HandlerInterceptor接口

preHandle 在业务处理器处理请求之前被调用。预处理,可以进行编码、安全控制、权限校验等处理;
postHandle 在业务处理器处理请求执行完成后,生成视图之前执行。
afterCompletion 在DispatcherServlet完全处理完请求后被调用,可用于清理资源等
@Component
public class TimeInterceptor implements HandlerInterceptor {

    // 在controller调用之前调用,通过返回true或者false决定是否进入Controller层
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        System.out.println("preHandle");
        System.out.println(((HandlerMethod)handler).getBean().getClass().getName());
        System.out.println(((HandlerMethod)handler).getMethod().getName());
        request.setAttribute("startTime", new Date().getTime());
        return true;
    }
 
    // 在请求进入控制层之后调用,但是在处理请求抛出异常时不会调用
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
            ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle");
        Long start = (Long)request.getAttribute("startTime");
        System.out.println("time interceptor 耗时:"+ (new Date().getTime() - start));
    }
 
    // 在请求处理完成之后,也就是在DispatherServlet渲染了视图之后执行,也就是说这个方法必定是执行,包含异常信息,它的主要作用就是清理资源
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        System.out.println("afterCompletion");
        Long start = (Long) request.getAttribute("startTime");
        System.out.println("time interceptor 耗时:"+ (new Date().getTime() - start));
        System.out.println("ex is "+ex);
    }
 
}

2、自定义拦截器

(1)实现HandlerInterceptor接口

(2)注册到容器中

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
 
     public void addInterceptors(InterceptorRegistry registry) {
         // 指定拦截器
         HandlerInterceptor handlerInterceptor = new RequestInterceptor();
         // 指定拦截的url
         String[] paths = {"/**"};
         // 指定不拦截的url
         String[] excludePath ={"/health"};
         registry.addInterceptor(handlerInterceptor).addPathPatterns(paths).excludePathPatterns(excludePath);
    }
}

三、aspect(切片)

能获取到方法请求的参数、方法名以及方法返回的json数据,更多的是用于数据的处理,如对操作进行记录、对返回的json中的一些特殊数据进行替换。

interceptor也是一种aop思想,使用场合比aop小,但使用简便,只拦截action请求,它可以阻止代码执行下去(当preHandle返回false) aspect 不能阻止代码执行下去

四、执行顺序

1、正常情况下

过滤器-》拦截器-》切片

2、异常报错

切片-》ControllerAdvice注解类-》拦截器-》过滤器

五、WebMvcConfigurer

WebMvcConfigurer配置类是Spring内部的一种配置方式,可以自定义一些Handler、Interceptor、ViewResolver、MessageConverter。

在Spring Boot 1.5时都是靠重写WebMvcConfigurerAdapter的方法来添加自定义拦截器,消息转换器等,从SpringBoot 2.0以后,推荐直接实现WebMvcConfigurer或者直接继承WebMvcConfigurationSupport。

1、常用方法

2、addInterceptors(拦截器)

@Override
public void addInterceptors(InterceptorRegistry registry) {
    super.addInterceptors(registry);
    registry.addInterceptor(new TestInterceptor())
          .addPathPatterns("/**")
          .excludePathPatterns("/emp/toLogin","/emp/login","/js/**","/css/**","/images/**");
}
addInterceptor 需要一个实现HandlerInterceptor接口的拦截器实例
addPathPatterns 用于设置拦截器的过滤路径规则
excludePathPatterns 用于设置不需要拦截的过滤规则

3、addViewControllers(页面跳转)

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/toLogin").setViewName("login");
    }

这里重写的addViewController方法,并不会覆盖WebMvcAutoConfiguration中的addViewControllers(在此方法中,springboot将“/”映射到index.html),也就是自己的配置和springboot的自动配置同时有效。

4、addResourceHandlers(静态资源 )

比如,我们想自定义静态资源映射目录的话,只需重写addResourceHandlers方法即可。

注:如果继承WebMvcConfigurationSupport类实现配置时必须要重写该方法.

@Configuration
public class monitorWebMvcConfigurerAdapter implements WebMvcConfigurer {
 
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/monitorfile/**")
                .addResourceLocations("file:D:\\");
    }
}
addResoureHandler 指的是对外暴露的访问路径
addResourceLocations 指的是内部文件放置的目录

5、configureDefaultServletHandling(默认静态资源处理器)

@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
        configurer.enable("defaultServletName");
}

此时会注册一个默认的Handler:DefaultServletHttpRequestHandler,这个Handler也是用来处理静态文件的,它会尝试映射/。当DispatcherServelt映射/时(/ 和/ 是有区别的),并且没有找到合适的Handler来处理请求时,就会交给DefaultServletHttpRequestHandler 来处理。注意:这里的静态资源是放置在web根目录下,而非WEB-INF 下。

6、视图解析器

/**
 * 配置请求视图映射
 * @return
 */
@Bean
public InternalResourceViewResolver resourceViewResolver()
{
	InternalResourceViewResolver internalResourceViewResolver = new InternalResourceViewResolver();
	//请求视图文件的前缀地址
	internalResourceViewResolver.setPrefix("/WEB-INF/jsp/");
	//请求视图文件的后缀
	internalResourceViewResolver.setSuffix(".jsp");
	return internalResourceViewResolver;
}
 
/**
 * 视图配置
 * @param registry
 */
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
	super.configureViewResolvers(registry);
	registry.viewResolver(resourceViewResolver());
	/*registry.jsp("/WEB-INF/jsp/",".jsp");*/
}

7、addCorsMappings(跨域)

@Override
public void addCorsMappings(CorsRegistry registry) {
    super.addCorsMappings(registry);
    registry.addMapping("/cors/**")
            .allowedHeaders("*")
            .allowedMethods("POST","GET")
            .allowedOrigins("*");
}

8、configureMessageConverters(信息转换器)

 
/**
* 消息内容转换配置
 * 配置fastJson返回json转换
 * @param converters
 */
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    //调用父类的配置
    super.configureMessageConverters(converters);
    //创建fastJson消息转换器
    FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
    //创建配置类
    FastJsonConfig fastJsonConfig = new FastJsonConfig();
    //修改配置返回内容的过滤
    fastJsonConfig.setSerializerFeatures(
            SerializerFeature.DisableCircularReferenceDetect,
            SerializerFeature.WriteMapNullValue,
            SerializerFeature.WriteNullStringAsEmpty
    );
    fastConverter.setFastJsonConfig(fastJsonConfig);
    //将fastjson添加到视图消息转换器列表内
    converters.add(fastConverter);
 
}

9、addArgumentResolvers自定义方法参数解析器

该方法可以用在对于Controller中方法参数传入之前对该参数进行处理。然后将处理好的参数在传给Controller中的方法。

10、configurePathMatch 匹配路由请求规则

@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
    super.configurePathMatch(configurer);
    /*
     * 1.ServletMappings 设置的是 "/" 2.setUseSuffixPatternMatch默认设置为true,
     * 那么,"/user" 就会匹配 "/user.*",也就是说,"/user.html" 的请求会被 "/user" 的 Controller所拦截.
     * 3.如果该值为false,则不匹配
     */
    configurer.setUseSuffixPatternMatch(false);

    /*
     * setUseTrailingSlashMatch的默认值为true
     * 也就是说, "/user" 和 "/user/" 都会匹配到 "/user"的Controller
     */
    configurer.setUseTrailingSlashMatch(true);
}

11、addFormatters,注册自定义的Formatter和Convert

@Bean
public EnumConverterFactory enumConverterFactory() {
    return new EnumConverterFactory();
}

/**
 * 注册自定义的Formatter和Convert,例如, 对于日期类型,枚举类型的转化.
 * 不过对于日期类型,使用更多的是使用
 *
 * @DateTimeFormat(pattern = "yyyy-MM-dd")
 * private Date createTime;
 */
@Override
public void addFormatters(FormatterRegistry registry) {
    super.addFormatters(registry);
    registry.addConverterFactory(enumConverterFactory());
}

/**
 * SpringMVC支持绑定枚举值参数。
 * 匹配规则 :
 * 字符串则尝试根据Enum#name()转换。
 * 如果找不到匹配的则返回null
 */
public class EnumConverterFactory implements ConverterFactory<String, Enum> {
    
    @Override
    public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) {
        return new String2EnumConverter(targetType);
    }

    class String2EnumConverter<T extends Enum<T>> implements Converter<String, T> {

        private Class<T> enumType;

        private String2EnumConverter(Class<T> enumType) {
            this.enumType = enumType;
        }

        @Override
        public T convert(String source) {
            if (source != null && !source.isEmpty()) {
                try {
                    return Enum.valueOf(enumType, source);
                } catch (Exception e) {
                }
            }
            return null;
        }
    }
}



相关推荐

java.io.FilenameFilter是文件名过滤器,用来过滤不符合规格的文件名,并返回合格的文件; 一般地: (1)String[] fs = f.list(

一、概述 springboot中有两种方式使用kafka时,直接使用kafka-client连接kafka服务;另一种是使用spring-kafka框架来连接kafka。 1、版本兼容 使用时要注意版

当然可以自己写redis的工具类,或者使用第三方开源jar包或代码,这里使用spring boot的集成类。 一、pom依赖 <dependency> <gro

websocket协议基于tcp的网络协议,它实现浏览器与器全双工通信。 spring boot2 +websocket 1、添加依赖 <pre clas

BloomFilter主要用于检索一个元素是否在集合中,优点是空间效率和查询效率比较高。缺点是存在误差率。 由上图我们可以看出,此时A、B、C、D四个数据各自经过f1和f2方法进行两次hash算法,

背景: 之前用spring boot+mybatis+oracle,现在要改成spring boot_mybatis+postgresql。 为了想让一套代码即可

共同点 都是用来表示Spring某个类是否可以接收HTTP请求。 不同点 @Controller标识一个spring类是Spring MVC c

系统异常捕获 参见:spring boot 2 全局异常处理 @ControllerAdvice(annotations = {RestController.class}) public class

从SSH(Structs/Spring/Hibernate)到SSM(SpringMVC/Spring/MyBatis),到现在一个S(Spring)就够的年代,可见Spring越来越重要了。<

@ConditionalOnMissingBean只能在@Bean注解的方法上使用。 可以给该注解传入参数例如@ConditionOnMissingBean(name = "exa