spring boot拦截方式
阅读数:326 评论数:0
跳转到新版页面分类
python/Java
正文
一、过滤器filter
可以获取http请求和响应,但无法获取与spring框架相关的信息,主要用于内容上的过滤,如敏感字替换、非法请求过滤。
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("过滤器销毁了");
}
}
通过 @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;
}
}
使用@Component注解后,可以使用@Order注解保证过滤器执行顺序,@Order 注解用于指定组件的执行顺序,其中值越小的组件优先执行。不使用@Order注解,则按照filter类名的字母顺序来执行。虽然可以保证执行顺序, 但是过滤器不能指定拦截的url , 只能默认拦截全部。
@Component
@Order(1)
public class MyFilter1 implements Filter {
// ...
}
@Component
@Order(2)
public class MyFilter2 implements Filter {
// ...
}
使用 @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。
@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 | 用于设置不需要拦截的过滤规则 |
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/toLogin").setViewName("login");
}
这里重写的addViewController方法,并不会覆盖WebMvcAutoConfiguration中的addViewControllers(在此方法中,springboot将“/”映射到index.html),也就是自己的配置和springboot的自动配置同时有效。
比如,我们想自定义静态资源映射目录的话,只需重写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 下。
/**
* 配置请求视图映射
* @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");*/
}
@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;
}
}
}