拦截器
-
什么是拦截器
Java里的拦截器是动态拦截Action调用的对象,它提供了一种机制可以使开发者在一个Action执行的前后执行一段代码,也可以在一个Action执行前阻止其执行,同时也提供了一种可以提取Action中可重用部分代码的方式。
拦截器的拦截,是基于Java反射机制实现的,拦截的对象只能是实现了接口的类,而不能拦截url这种链接。 -
拦截器作用
当请求到达DispatcherServlet时,它会根据HandlingMapping的机制找到处理器,这样就会返回一个HandlerExecutionChain对象,这个对象包含处理器和拦截器。这里的拦截器会对处理器进行拦截,这样通过拦截器就可以增强处理器的功能。 -
如何应用拦截器
PreAuthorize.java (伪代码)
注解,作为拦截器的"连接点"
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Documented
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface PreAuthorize {
String [] perms() default "";
String [] roles() default "";
}
PreAuthorizeInterceptor.java (伪代码)
拦截器实现,通过反射指定了要拦截的"切点"PreAuthorize
import ****.annotation.PreAuthorize;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
@Slf4j
public class PreAuthorizeInterceptor extends HandlerInterceptorAdapter {
public PreAuthorizeInterceptor() {
super();
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (!(handler instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
PreAuthorize annotation = method.getAnnotation(PreAuthorize.class);
try {
if (annotation != null) {
// 获取注解内的参数
String perms [] = annotation.perms();
String roles [] = annotation.roles();
/***process:想要在请求进入接口前做的处理工作***/
}
} catch (Exception e) {
log.error("@PreAuthorize error: {}", Throwables.getStackTraceAsString(Throwables.getRootCause(e)));
return false;
}
// TODO 增加过滤日志,对重要接口的调用打印日志:log.info("当前用户:" + userInfo.getUserName() + ",成功进入接口:" + "");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
super.afterCompletion(request, response, handler, ex);
}
@Override
public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
super.afterConcurrentHandlingStarted(request, response, handler);
}
}
WebConfig.java (伪代码)
实现了自己的拦截器后,要在spring中配置,将该拦截器加入到spring容器中
import *****.interceptor.PreAuthorizeInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(preAuthorizeInterceptor());
}
@Bean
public PreAuthorizeInterceptor preAuthorizeInterceptor() {
return new PreAuthorizeInterceptor();
}
}
最后应用拦截器时,只需要在接口前添加@PreAuthorize注解及相应的参数即可。
过滤器
-
什么是过滤器
Java的过滤器能够为我们提供系统级别的过滤,即过滤所有的web请求。(这一点是拦截器无法做到的,Java Web中传入的request, response提前过滤掉一些信息等等) -
过滤器作用
- 用户访问权限过滤;
- 日志过滤,可以记录特殊用户的特殊请求的记录等;
- 图像、数据压缩、编码格式等过滤;
- 如何应用过滤器
实现一个简单的过滤器处理逻辑:
TimeFilter.java
package com.spring.filter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
@Component
@Slf4j
public class TimeFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("初始化过滤器:{}", filterConfig.getFilterName());
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
log.info("start to doFilter");
long startTime = System.currentTimeMillis();
filterChain.doFilter(servletRequest, servletResponse);
long endTime = System.currentTimeMillis();
String url;
if (servletRequest instanceof HttpServletRequest) {
url = ((HttpServletRequest) servletRequest).getRequestURI();
} else {
url = "";
}
log.info("the request of {} consums {} ms", url, endTime - startTime);
log.info("end doFilter.");
}
@Override
public void destroy() {
log.info("销毁过滤器.");
}
}
在代码中配置我们实现的过滤器:
方式一 WebConfig.java
package com.spring.filter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.Filter;
import java.util.ArrayList;
@Configuration
public class WebConfig {
@Bean
public FilterRegistrationBean filterConfig() {
FilterRegistrationBean<Filter> filterFilterRegistrationBean = new FilterRegistrationBean<>();
filterFilterRegistrationBean.setFilter(new TimeFilter());
ArrayList<String> urls = new ArrayList<>();
//匹配所有url
urls.add("/*");
filterFilterRegistrationBean.setUrlPatterns(urls);
return filterFilterRegistrationBean;
}
}
方式二 通过注解的方式
// 在实现的过滤器前加入 @WebFilter注解
@Component
@WebFilter(urlPatterns = "/*", filterName = "timeFilter")
public class TimeFilter implements Filter {
...
}
最后启动SpringBoot,访问服务,发现过滤器成功执行:
过滤器与拦截器对比
- 区别
过滤器:
- 在servlet规范中定义,是由servlet容器支持的;
- 对request和response请求进行过滤;(取你所取)
- Filter的链式调用:filterChanin.doFilter()
拦截器: - 在Spring容器内,由Spring框架支持;
- 通过Java反射机制来拦截web请求(拒你所拒);
- Interceptor也是链式调用;
- 拦截器一般可用于(日志记录、权限检查、性能监控、通用行为)
- 总结
- 拦截器是基于Java反射机制来实现的,而过滤器是基于函数回调来实现的;
- 拦截器不依赖servlet容器,过滤器依赖于servlet容器;
- 拦截器只对Action起作用,过滤器可以对所有请求起作用;
- 拦截器可以访问Action上下文和值栈中的对象,过滤器不能;
- 在Action的生命周期中,拦截器可以多次调用,而过滤器只能在容器初始化时调用一次。
Spring AOP
-
什么是Spring AOP
面向切面编程,用于增强指定方法的处理,即将指定方法的处理过程插入到一套规定好的流程中(比如说,执行方法前要做些什么,执行过程中做些什么之类的)。 -
为什么要使用Spring AOP
-
如何使用Spring AOP
AOP中,使用注解@Pointcut来定义切点,其后面采用正则式匹配到连接点(即我们想要进行拦截增强处理的方法)
// 切点正则式
execution(* com.spring.aop.AspectTest.UserServiceImpl.printUser(..))
其中:
- execution表示在执行的时候,拦截里面的正则匹配的方法;
- *表示任意返回类型的方法;
- com.springboot.chapter4.aspect.service.impl.UserServiceImpl指定目标对象的全限定名称;
- printUser指定目标对象的方法;
- (…)表示任意参数进行匹配;
此时,Spring就可以通过此正则式知道需要对类UserServiceImpl的printUser方法进行AOP增强,它就会将正则式匹配的方法和对应切面的方法织入约定流程中(如下图)
下面是Spring AOP的一个示例:
第一,我们需要确定连接点,即我们要增强处理的目标对象
UserService.java
package com.spring.aop.AspectTest;
public interface UserService {
public void printUser(User user);
}
UserServiceImpl.java
package com.spring.aop.AspectTest;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
@Override
public void printUser(User user) {
if (user == null) {
throw new RuntimeException("检查用户参数是否为空......");
}
System.out.println("id = " + user.getId());
System.out.println("username = " + user.getUserName());
System.out.println("note = " + user.getNote());
}
}
第二,定义 切点和切面,即它向Spring描述哪些类的哪些方法需要启用AOP编程,以及如何增强这些方法
package com.spring.aop.AspectTest;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class MyAspect {
@Pointcut("execution(* com.spring.aop.AspectTest.UserServiceImpl.printUser(..))")
public void pointCut() {
}
@Before("pointCut()")
public void before() {
System.out.println("before ......");
}
@After("pointCut()")
public void after() {
System.out.println("after ......");
}
@AfterReturning("pointCut()")
public void afterReturning() {
System.out.println("afterReturning ......");
}
@AfterThrowing("pointCut()")
public void afterThrowing() {
System.out.println("afterThrowing ......");
}
@Around("pointCut()")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("around before ......");
jp.proceed();
System.out.println("around after ......");
}
}
完成了上面的连接点、切面和切点等的定义之后,就可以测试AOP,需要先搭建一个Web开发环境,开发一个Controller
package com.spring.aop.AspectTest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
/**
* spring boot项目中的Controller层
*/
@RestController
@RequestMapping("/user")
public class TestController {
@Autowired
private UserService userService;
@RequestMapping("/print")
@ResponseBody
public User printUser(@RequestParam("id") int id, @RequestParam("userName") String userName, @RequestParam("note") String note) {
User user = new User();
user.setId(id);
user.setUserName(userName);
user.setNote(note);
userService.printUser(user);
return user;
}
}
SpringBoot配置文件
package com.spring.aop.AspectTest;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
/**
* Spring boot项目中的启动类
*
*/
@SpringBootApplication(scanBasePackages = {"com.spring.aop.AspectTest"})
public class Application {
@Bean(name = "myAspect")
public MyAspect initMyAspect() {
return new MyAspect();
}
public static void main(String [] args) {
SpringApplication.run(Application.class, args);
}
}
- 多个切面
上面是实现一个完整切面的例子,下面再简单介绍一下支持多个切面运行的一些需要注意的地方:
最值得关注的就是当我们对于同一个连接点定一个了多个切面的时候,需要知道其运行的顺序:
使用注解@Order指定切面的顺序,例如:
...
@Aspect
@Order(1)
public class MyAspect1 {
...
}
使用Ordered接口也可以指定顺序,例如:
...
@Aspect
public class MyAspect1 implements Ordered {
//指定顺序
@Override
public int getOrder() {
return 1;
}
...
}
Spring interceptor与AOP之间的区别与联系
-
作用层面
拦截器只对action负责,作用曾米娜一般位于Controller层;
Spring AOP主要是拦截对Spring管理的Bean的访问,一般作用于Service层; -
联系与区别
联系:Spring AOP和拦截器一样,都是AOP的实现方式的一种,均使用代理模式实现;
区别:拦截器和过滤器相似,是链式的处理模式,这样有一个缺点是每次请求都会访问action的上下文,不够灵活;
Spring AOP的注解有@Before、@After、@AfterReturning、@AfterThrowing、@Around,可以更灵活的配置要监听处理的Bean,即可以对指定的方法等进行拦截处理,而不是每个接口等请求都需要进入注解关联的处理。
参考链接
https://juejin.im/post/5c6901206fb9a049af6dcdcf
https://www.cnblogs.com/paddix/p/8365558.html