Spring Boot基础功能:拦截器与过滤器
拦截器(Inteceptor)与过滤器(Filter)都用于在请求处理过程中执行特定操作,他们的实现方式、作用范围和使用场景上有一定的区别。
1 拦截器
拦截器是一种动态拦截调用方法的机制,它可以在Spring Mvc中动态拦截控制器方法的执行,是基于Spring MVC框架的一部分。
实现方式: 拦截器通过实现HandlerInterceptor接口或继承HandlerInterceptorAdapter类来创建自定义拦截器。
作用范围: 拦截器作用与具体的控制器方法,用于拦截和处理请求以及对响应进行处理。拦截器可以访问Spring Mvc的上下文,包括处理器方法的参数、请求和响应对象,以及Spring的其他功能(如依赖注入)。
适用范围: 拦截器可以进行权限验证、日志记录、参数处理、异常处理等操作。拦截器适用于对请求进行细粒度的拦截和处理,如验证用户身份、记录请求日志等。
1.1 拦截器的执行顺序
● preHandle
■ return true
□ controller
□ postHandle
□ afterCompltion
■ return false
□ 拦截,流程结束
1.2 创建拦截器
创建拦截器LoginInterceptor.java
:
/**
* 登录检查
* 1.配置到拦截器要拦截哪些请求
* 2.把这些配置放在容器中
*
* 实现HandlerInterceptor接口
*/
public class LoginInterceptor implements HandlerInterceptor {
/**
* 目标方法执行之前
* 登录检查写在这里,如果没有登录,就不执行目标方法
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 获取进过拦截器的路径
String requestURI = request.getRequestURI();
// 登录检查逻辑
HttpSession session = request.getSession();
Object loginUser = session.getAttribute("loginUser");
if(loginUser !=null){
// 放行
return true;
}
// 拦截 就是未登录,自动跳转到登录页面,然后写拦截住的逻辑
return false;
}
/**
* 目标方法执行完成以后
* @param request
* @param response
* @param handler
* @param modelAndView
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
/**
* 页面渲染以后
* @param request
* @param response
* @param handler
* @param ex
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
配置到容器里``:
@Configuration
//定制SpringMVC的一些功能都使用WebMvcConfigurer
public class AdminWebConfig implements WebMvcConfigurer {
/**
* 配置拦截器
* @param registry 相当于拦截器的注册中心
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 下面这句代码相当于添加一个拦截器 添加的拦截器就是我们刚刚创建的
registry.addInterceptor(new LoginInterceptor())
// addPathPatterns()配置我们要拦截哪些路径 addPathPatterns("/**")表示拦截所有请求,包括我们的静态资源
.addPathPatterns()
// excludePathPatterns()表示我们要放行哪些(表示不用经过拦截器)
// excludePathPatterns("/","/login")表示放行“/”与“/login”请求
// 如果有静态资源的时候可以在这个地方放行
.excludePathPatterns("/","/login");
}
}
1.3 拦截器的请求参数
1.3.1 HttpServletRequest
HttpServletRequest源于Servlet, 可以获取请求数据
获取请求头Content-Type
的示例
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String contenType = request.getHeader("Content-Type");
System.out.println("preHandle..."+contenType);
// 通过
return true;
}
1.3.2 HttpServletResponse
HttpServletResponse源于ServLet, 可用于设置响应数据
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
response.setCharacterEncoding("UTF-8");
// 通过
return true;
}
1.3.4 Object
Object参数是被调用的处理器对象,本质上是一个方法对象,对反射技术中的Method对象进行再包装。
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String contenType = request.getHeader("Content-Type");
// System.out.println(handler);
HandlerMethod hm = (HandlerMethod)handler;
// 通过hm.getMethod()就可以拿到原始执行的对象,拿到这个对象就可以进行反射
hm.getMethod();
System.out.println("preHandle..."+contenType);
// 放行
return true;
}
1.3.5 ModelAndView
封装了SpringMVC跳转的相关数据。
1.3.6 Exception ex
通过ex可以拿到原始程序执行过程中出现的异常。
假设controller层抛了异常,在这里是可以拿到异常对象的,但是我们有异常处理机制,所以这里就没有那么大的需求了
如果处理器执行过程中出现异常对象,可以针对异常情况进行单独处理
1.4 拦截器的执行顺序
拦截器采用管道模式,preHandle与配置顺序相同,postHandle于配置顺序相反,afterCompletion与配置顺序相反。
1.5 常用拦截器操作
1.5.1 配置静态资源拦截器
/**
* 在MyInterceptorConfig中重写addResourceHandlers方法,重新指定静态资源
* 推荐在前后端分离时使用,后台不需要访问静态资源
*
* @param registry
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**").addResourceLocations(
"classpath:/static/");
super.addResourceHandlers(registry);
}
/**
* 将MyInterceptorConfig类由继承WebMvcConfigurationSupport改为实现WebMvcConfigurer
* 推荐在非前后端分离时使用,后台需要访问静态资源
*
*/
@Configuration
public class MyInterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**"); // 拦截所有内容:/** 拦截部分内容:/admin/**
}
}
1.5.2 配置转换页面
application.yml
:
spring:
thymeleaf:
prefix: classpath:/templates/ #视图前缀
suffix: .html #视图后缀
encoding: UTF-8 #编码
cache: false #缓存(默认开启,开发时建议关闭)
配置转换页面:
registry.addViewController("/index").setViewName("index");
1.5.3 匹配路径
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
1.5.4 例外路径
String[] excepts = new String[]{"/404.html","/posts/**","/webjars/**"};
registry.addInterceptor(new MyInterceptor()).excludePathPatterns("/**");
1.5.5 取消拦截操作
第一步:自定义注解用于不需要拦截的方法
/**
* 自定义注解用来指定某个方法不用拦截
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UnInterception {
}
第二步:修改配置,判断例外的注解
/**
* 在请求匹配controller之前执行
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
UnInterception unInterception = method.getAnnotation(UnInterception.class);
if(null != unInterception) {
logger.info("不需要拦截,可以执行");
return true;
}
// 返回true才会执行方法,返回false不会执行
return false;
}
第三步:在不需要拦截的控制器方法上加上注解
@Controller
@RequestMapping("/interceptor")
public class InterceptorController {
@UnInterception
@RequestMapping("/test")
public String test() {
return "hello";
}
}
1.6 MethodInterceptor
MethodInterceptor是Aop项目中的拦截器,拦截目标是方法,不局限于Controller类中的方法。
1.6.1 定义拦截器
先自定义一个函数接口:
package org.aopalliance.intercept;
@FunctionalInterface
public interface MethodInterceptor extends Interceptor {
Object invoke(MethodInvocation var1) throws Throwable;
}
在定义一个拦截器,实现自定义函数接口MethodInterceptor
的invoke
方法:
package com.wang.chao.micro.interceptor;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("MyMethodInterceptor "+methodInvocation.getMethod().getName());
return methodInvocation.proceed();
}
}
1.6.2 使用AspectJExpressionPointcut构造切点并注册
package com.wang.chao.micro.interceptor.config;
import com.wang.chao.micro.interceptor.MyMethodInterceptor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class InterceptorConfig {
public static final String traceExecution = "execution(* com.wang.chao.micro..*.*(..))";
@Bean
public DefaultPointcutAdvisor defaultPointcutAdvisor(){
MyMethodInterceptor methodInterceptor = new MyMethodInterceptor();
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(traceExecution);
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
advisor.setPointcut(pointcut);
advisor.setAdvice(methodInterceptor);
return advisor;
}
}
1.6.3 使用JdkRegexpMethodPointcut构造切点
package com.wang.chao.micro.interceptor.config;
import com.wang.chao.micro.interceptor.MyMethodInterceptor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.JdkRegexpMethodPointcut;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class InterceptorConfig {
public static final String traceExecution = "execution(* com.wang.chao.micro..*.*(..))";
@Bean
public DefaultPointcutAdvisor defaultPointcutAdvisor() {
MyMethodInterceptor methodInterceptor = new MyMethodInterceptor();
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
//case 1
// AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
// pointcut.setExpression(traceExecution);
//case 2
JdkRegexpMethodPointcut pointcut2 = new JdkRegexpMethodPointcut();
pointcut2.setPattern("com.wang.chao.micro.*");
advisor.setPointcut(pointcut2);
advisor.setAdvice(methodInterceptor);
return advisor;
}
1.6.4 注解自定义注解AspectJExpressionPointcut
package com.wang.chao.micro.interceptor.annotation;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface InterceptorAnnotation {
}
package com.wang.chao.micro.interceptor.config;
import com.wang.chao.micro.interceptor.MyMethodInterceptor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.JdkRegexpMethodPointcut;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class InterceptorConfig {
public static final String traceAnnotationExecution = "@annotation(com.wang.chao.micro.interceptor.annotation.InterceptorAnnotation)";
@Bean
public DefaultPointcutAdvisor defaultPointcutAdvisor() {
MyMethodInterceptor methodInterceptor = new MyMethodInterceptor();
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
//case 3
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(traceAnnotationExecution);
advisor.setPointcut(pointcut);
advisor.setAdvice(methodInterceptor);
return advisor;
}
1.6.5 自定义注解 AnnotationMatchingPointcut
package com.wang.chao.micro.service;
import org.springframework.stereotype.Service;
@Service
public class UserService {
public void saveUser(String name, String sex) {
System.out.println("name = " + name + ", sex = " + sex+" id =");
sayHello(name);
}
public void sayHello(String name){
System.out.println("Hello name = " + name+" Welcome in Spring Interceptor world");
}
}
1.6.6 测试
测试类
package com.wang.chao.micro.service;
import org.springframework.stereotype.Service;
@Service
public class UserService {
public void saveUser(String name, String sex) {
System.out.println("name = " + name + ", sex = " + sex+" id =");
sayHello(name);
}
public void sayHello(String name){
System.out.println("Hello name = " + name+" Welcome in Spring Interceptor world");
}
}
请求调用测试
package com.wang.chao.micro.controller;
import com.wang.chao.micro.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
@Autowired
private UserService userService;
@RequestMapping(value="/" ,method = RequestMethod.GET)
public void saveUser(String name,String sex){
userService.saveUser(name,sex);
}
}
启动调用测试
package com.wang.chao.micro;
import com.wang.chao.micro.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
@SpringBootApplication
public class Application {
@Autowired
private ApplicationContext applicationContext;
public static void main(String[] args) {
ApplicationContext applicationContext = SpringApplication.run(Application.class, args);
UserService userService=(UserService) applicationContext.getBean("userService");
userService.saveUser("wagnchao","nan");
userService.sayHello("wangchaochao");
}
}
2 过滤器Filter
过滤器可以过滤所有的请求,对请求前后进行处理,可以获取并修改请求和响应中的信息,所以可以用来对一些无效的路径进行过滤或者进行URL级别的权限控制,同时可以对请求的信息进行敏感词过滤以防止SQL注入,但是无法调用IOC容器。
过滤器由Servlet提供,基于函数回调实现链式对网络请求与响应的修改,其可以对WEB服务器管理的几乎所有所有资源进行拦截修改(jsp, image, html, css ...)。
Filter的生命周期:
- init() 初始化Filter实例,Filter生命周期与Servlet相同,也就是当web容器(tomcat)启动时,调用init()方法初始化实例,Filter只会初始化一次,需要初始化参数的时候,可以放到init()中;
- doFilter() 业务处理,拦截执行的请求,对请求和响应进行处理,一般需要处理的业务操作都在这个方法中实现;
- destory() 销毁实例,关闭容器时调用Filter销毁实例。
2.1 实现Filter接口实现过滤器
在启动类添加@ServletComponentScan
注解, 让Spring Boot可以发现。
@SpringBootApplication
@ServletComponentScan
public class NosocomiumApplication {
public static void main(String[] args) {
SpringApplication.run(NosocomiumApplication.class, args);
}
}
通过 @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;
}
}
2.2 使用@Component注解实现
使用@Component
注解后,可以使用@Order
注解来制定过滤器的顺序,值越小越优先执行。
@Component
@Order(1)
public class MyFilter1 implements Filter {
// ...
}
@Component
@Order(2)
public class MyFilter2 implements Filter {
// ...
}
- 不制定order顺序默认按过滤器名称的字母顺序执行;
- 不使用
@WebFilter
注解则默认过滤所有路径。
发表回复