Filter,Interceptor,aspect应该如何选择
前言
大家应该都清除过滤器,拦截器,切面,他们都能起到阶段拦截的作用,但是不管是自己业务上的选择,还是面试官的提问,我们应该如何取舍
Filter 过滤器
-
过滤器可以拦截到方法的请求和响应(ServletRequest request,ServletResponse response),并对请求响应做出过滤操作
-
过滤器依赖servlet容器,在实现上,基于函数回调,他可以对几乎所有的请求进行过滤,一个过滤器实例只能在容器初始化时调用一次
-
使用过滤器的目的是用来做一些过滤操作,获取我们想要获取的数据,比如:在过滤器中修改字符编码;在过滤器中修改HttpServletRequest的一些参数,包括:过滤低俗文字,危险字符等
代码
/**
* @PROJECT_NAME: 杭州
* @DESCRIPTION:
* @author: 徐子木
* @DATE: 2021/2/2 10:04 上午
*/
public class MyFilter implements Filter {
private static final Logger LOGGER = LoggerFactory.getLogger(MyFilter.class);
@Override
public void init(FilterConfig filterConfig) throws ServletException {
LOGGER.debug("filter init");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
LOGGER.debug("do filter Wu Yue Ge Ge");
chain.doFilter(request,response);
}
@Override
public void destroy() {
}
}
加载
- 第一种方式加@Component
- 第二种(推荐)
@Bean
@Order(1)
public FilterRegistrationBean configFilter(){
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(new MyFilter());
filterRegistrationBean.addUrlPatterns("/*");
filterRegistrationBean.setName("loginFilter");
return filterRegistrationBean;
}
Filter随web应用的启动而启动,只初始化一次,随web应用的停止而销毁
- 启动服务器加载过滤器的实例,并调用init()方法来初始化实例;
- 每一次请求时都只调用方法doFilter()进行处理;
- 停止服务器时调用destroy()方法,销毁实例.
注意
- filter里面是能够获取到请求的参数和响应的数据;但此方法是无法知道哪一个Controller类中的哪个方法被执行.
- 还有一点需要注意的是filter中是没发使用注入的bean的,也就是无法使用@Autowired, 为什么呢? 其实Spring中,web应用的启动顺序是: Listener->filter->servlet 先初始化listener,然后再来初始化filter的,再接着才到我们的dispathServlet的初始化,因此当我们需要在filter里注入一个bean时,就会注入失败,因为filter初始化时,注解的bean还没初始化,没法注入
Interceptor拦截器
依赖于web框架,在SpringMVC中就是依赖于SpringMVC框架.在实现上,基于Java的反射机制,属于面向切面编程(AOP)的一种运用,就是在一个方法前调用一个方法,或者在方法后调用一个方法
/**
* @PROJECT_NAME: 杭州
* @DESCRIPTION:
* @author: 徐子木
* @DATE: 2021/2/2 10:23 上午
*/
@Slf4j
public class LoginInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
if (request.getMethod().equals("OPTIONS")) {
return true;
}
Optional<CurrentUser> optionalCurrentUser = LoginUserUtil.get(request);
if (optionalCurrentUser.isPresent()) {
CurrentUserHolder.set(optionalCurrentUser.get());
} else {
throw new AuthException("not login!");
}
return super.preHandle(request, response, handler);
}
}
加载
@Bean
public WebMvcConfigurer webMvcConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://babyface-test.galalive.vip","http://localhost:8000","http://console.galalive.vip","https://console.galalive.vip")
.allowedMethods("*")
.allowedHeaders("*")
.allowCredentials(true);
}
};
}
拦截器中可以获取到Controller对象
HandlerMethod method = (HandlerMethod) handler;
//类名
String name = method.getBean().getClass().getName();
//方法名
method.getMethod().getName();
注意
我们获取不到方法的参数值,为什么呢? 在DispatcherServlet类中,方法doDispatch
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
applyPreHandle这个方法执行,就是执行拦截器的preHandler方法,但这个过程中,controller方法没有从request中获取请求参数,组装方法参数;而是ha.handle这个方法的时候,才会组装参数,虽然没法得到方法的参数,但是可以获得IOC的bean
postHandler方法的执行,当Controller内部有异常,posthandler方法时不会执行的.
afterCompletion方法,不管controller内部是否有异常,都会执行此方法;此方法还会有个Exception ex这个参数,如果有异常,ex会有异常值;没有异常 此值为null,并且controoler内部有异常,但异常被@ControllerAdvice异常统一捕获的话,ex也会为null
Aspect切片
AOP操作可以对操作进行横向的拦截,最大的优势在于他可以获取执行方法的参数,对方法进行统一的处理.常见使用日志,事务,请求参数安全验证等
/**
* @program: 杭州品茗信息技术有限公司
* @description
* @author: 徐子木
* @create: 2020-06-10 20:10
**/
@Aspect
@Slf4j
@Component
public class PermissionAspect {
private final CaServerService caServerService;
private final SystemFunctionService systemFunctionService;
private final BidOpenProperties bidOpenProperties;
private final OperatingRecordService recordService;
public PermissionAspect(CaServerService caServerService, SystemFunctionService systemFunctionService,
BidOpenProperties bidOpenProperties, OperatingRecordService recordService) {
this.caServerService = caServerService;
this.systemFunctionService = systemFunctionService;
this.bidOpenProperties = bidOpenProperties;
this.recordService = recordService;
}
@Pointcut("@annotation(cn.pinming.bidopening.aop.PermissionCheck)")
public void permissionAnnotationCut() {
}
@Around("permissionAnnotationCut()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
if (!(joinPoint instanceof MethodInvocationProceedingJoinPoint)) {
return joinPoint.proceed();
}
Signature signature = joinPoint.getSignature();
if (!(signature instanceof MethodSignature)) {
return joinPoint.proceed();
}
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
PermissionCheck check = method.getAnnotation(PermissionCheck.class);
if (check == null) {
return joinPoint.proceed();
}
// url 的 path参数不好equals ,直接用methodName
String methodName = method.getName();
log.debug("permission check method: {}" ,methodName);
UserRoleEnum[] roles = check.value();
Boolean hasPerm = Boolean.FALSE;
String userGroupId = BidopeningContext.getUserGroupId();
if (StringUtils.isEmpty(userGroupId)) {
throw new AccountException("请重新登录");
}
if(!systemFunctionService.isChecked(SystemFunctionEnum.CA_VERIFY.getCode())) {
return joinPoint.proceed();
}
List<String> methodCollection = systemFunctionService.findByParentCode(SystemFunctionEnum.CA_VERIFY.getCode())
.get()
.stream()
.filter(x->x.getChecked())
.map(SystemFunctionResponse::getConfigValue)
.collect(Collectors.toList());
if (userGroupId.equals(UserRoleEnum.BID.getCode()) && methodCollection.contains(methodName)) {
if (!caVerify(methodName)) {
throw new PermissionException("请检查CA签名是否与开发平台签名一致");
}
}
if (roles != null && roles.length > 0) {
for (UserRoleEnum s : roles) {
if (userGroupId.equals(s.getCode()) || userGroupId.equals(UserRoleEnum.JGR.getCode())
|| userGroupId.equals(UserRoleEnum.TENDER.getCode()) || userGroupId.equals(UserRoleEnum.ADMIN.getCode())) {
hasPerm = Boolean.TRUE;
}
}
}
if (!hasPerm) {
log.debug("当前用户无权进行该请求,方法名,方法名:{},当前角色:{},用户:{}",method,userGroupId,BidopeningContext.getCurrentEntId());
throw new PermissionException("当前用户无权进行该请求");
}
return joinPoint.proceed();
}
private boolean caVerify(String methodName) {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes sra = (ServletRequestAttributes) requestAttributes;
HttpServletRequest request = sra.getRequest();
String caVerify = request.getHeader("caVerify");
CaSignInfo caSignInfo = JsonUtils.parseToObj(caVerify, CaSignInfo.class);
if (caSignInfo == null || StringUtils.isNullOrEmpty(caSignInfo.getSignData()) || StringUtils.isNullOrEmpty(caSignInfo.getOriginData())) {
log.warn("CA操作签名无效 methodName:{}", methodName);
return false;
}
ApiResult apiResult = caServerService.verifyCert(caSignInfo);
if (!apiResult.equalsSuccess()) {
log.error("验签失败,签名信息:{},原因:{}",caSignInfo,apiResult.getMsg());
return false;
}
OperatingRecord operatingRecord = OperatingRecord.builder()
.supplierId(BidopeningContext.getCurrentEntId())
.method(methodName)
.signCert(caSignInfo.getSignCert())
.signData(caSignInfo.getSignData())
.originData(caSignInfo.getOriginData())
.caCode(caSignInfo.getCaCode())
.build();
recordService.create(operatingRecord);
return true;
}
}
上述操作是本人所在公司要对方法进行权限验证或者CA锁信息验证,并结合注解定义切面使用
总结
这里最后总结一下过滤器,拦截器,Aspect的区别
\ | Filter | Interceptor | Aspect |
---|---|---|---|
参数 | servletRequest request,ServletResponse response | HttpServletRequest request,HttpServletResponse response,Object handler | ProceedingJoinPoint pjp |
解释 | 可以拿到原始的Http请求,但无法获取请求控制器和控制器中的方法信息 | 可以拿到请求控制器和方法,但拿不到方法参数 | 可以拿到方法参数,但拿不到http请求和响应参数 |
他们的执行顺序是
filter->interceptor->ControllerAdvice->aspect->controller
返回值顺序,或异常返回顺序
Controller->aspect->ControllerAdvice->Interceptor->Filter
网友评论