- 执行顺序是 过滤器 -> 拦截器 -> AOP
- AOP 的全称是“Aspect Oriented Programming”,即面向切面编程,它将业务逻辑的各个部分进行隔离,使开发人员在编写业务逻辑时可以专心于核心业务,从而提高了开发效率
- 就是为了更清晰的结构,一方面AOP可以让你的业务逻辑去关注业务本身,而不需要处理与业务不相关的事情。这些其他的事情是重复调用的,例如:安全,事物,日志等。另一方面,例如日志,在不同的地方实现的是同一套逻辑,这样就可以抽取出来,作为一个通知,然后引入到各个模块。
- 整个程序就像一个西瓜, 把西瓜竖着切成几块, 横截面就是切面, 我们可以在切面中间放入通用代码 (比如日志)
- filter 和 interceptor 都是AOP的具体实现
- filter是作用在interceptor(拦截器)之前,filter主要是依赖serlvet容器
- filter的三个方法
- init(): 此方法在只在过滤器创建的时候执行一次,用于初始化过滤器的属性
- doFilter(): doFilter(): 该方法会对请求进行拦截,用户需要在该方法中自定义对请求内容以及响应内容进行过滤的,调用该方法的入参 FilterChain对象的 doFilter 方法对请求放行执行后面的逻辑,若未调用 doFilter 方法则本次请求结束,并向客户端返回响应失败
- destroy(): 此方法用于销毁过滤器,过滤器被创建以后只要项目一直运行,过滤器就会一直存在,在项目停止时,会调用该方法销毁过滤器
- FilterChain: 一般filter都是一个链,web.xml 里面配置了几个就有几个。一个一个的连在一起
request -> filter1 -> filter2 ->filter3 -> …. -> request resource.
/**
* 系统后台访问权限控制过滤器
*/
public class SystemManagerAccessController implements Filter {
private static Logger logger = Logger.getLogger(SystemManagerAccessController.class);
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)req;
String url = request.getServletPath();
String ip = request.getRemoteAddr();
logger.debug("Access URL=" + url + ", From IP=" + ip);
HttpSession session = request.getSession();
Object obj = session.getAttribute("LOGIN_USER");
if(obj != null) {
if(obj instanceof com.mln.system.beans.Manager){
chain.doFilter(req, resp);
return;
}
}
//记录无效访问信息
logger.error("无效访问,[url=" + url +", From IP=" + ip + "]。");
return;
}
@Override
public void init(FilterConfig config) throws ServletException {
}
}
package com.daniel.wiki.interceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 拦截器:Spring框架特有的,常用于登录校验,权限校验,请求日志打印 /login
*/
@Component
public class LogInterceptor implements HandlerInterceptor {
private static final Logger LOG = LoggerFactory.getLogger(LogInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 打印请求信息
LOG.info("------------- LogInterceptor 开始 -------------");
LOG.info("请求地址: {} {}", request.getRequestURL().toString(), request.getMethod());
LOG.info("远程地址: {}", request.getRemoteAddr());
long startTime = System.currentTimeMillis();
request.setAttribute("requestStartTime", startTime);
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
long startTime = (Long) request.getAttribute("requestStartTime");
LOG.info("------------- LogInterceptor 结束 耗时:{} ms -------------", System.currentTimeMillis() - startTime);
}
}
过滤器和拦截器的区别
- 过滤器和拦截器都属于面向切面编程的具体实现。而两者的主要区别包括以下几个方面:
1、Filter是依赖于Servlet容器,属于Servlet规范的一部分,而Interceptor则是独立存在的,可以在任何情况下使用。
2、Filter的执行由Servlet容器回调完成,而Interceptor通常通过动态代理的方式来执行。
3、Filter的生命周期由Servlet容器管理,而Interceptor则可以通过IoC容器来管理,因此可以通过注入等方式来获取其他Bean的实例,因此使用会更方便
- 目前最流行的 AOP 框架有两个,分别为 Spring AOP 和 AspectJ。
- Spring AOP 使用纯 Java 实现,基于动态代理. 不需要专门的编译过程和类加载器,在运行期间通过代理方式向目标类植入增强的代码。Spring
- AspectJ 是一个基于 Java 语言的 AOP 框架,基于静态代理. 从 Spring 2.0 开始,Spring AOP 引入了对 AspectJ 的支持。AspectJ 扩展了 Java 语言,提供了一个专门的编译器,在编译时提供横向代码的植入
相关概念:
- Joinpoint(连接点) 指那些被拦截到的点,在 Spring 中,可以被动态代理拦截目标类的方法。
- Pointcut(切入点) 指要对哪些 Joinpoint 进行拦截,即被拦截的连接点。
- Advice(通知) 指拦截到 Joinpoint 之后要做的事情,即对切入点增强的内容。
- Target(目标) 指代理的目标对象。
- Weaving(植入) 指把增强代码应用到目标上,生成代理对象的过程。
- Proxy(代理) 指生成的代理对象。
- Aspect(切面) 切入点和通知的结合
补充:通知(Advice)的类型:
- 前置通知
@Before
(Before advice):
在某个连接点(Join point)之前执行的通知,但这个通知不能阻止连接点的执行(除非它抛出一个异常)。- 返回后通知
@AfterRunning
(After returning advice):
在某个连接点(Join point)正常完成后执行的通知。例如,一个方法没有抛出任何异常正常返回。- 抛出异常后通知
@AfterThrowing
(After throwing advice):
在方法抛出异常后执行的通知。- 后置通知
@After
(After(finally)advice):
当某个连接点(Join point)退出的时候执行的通知(不论是正常返回还是发生异常退出)。- 环绕通知
@Around
(Around advice):
包围一个连接点(Join point)的通知,如方法调用。这是最强大的一种通知类型。环绕通知可以在方法前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行.
- 打印接口参数, 请求参数, 返回参数
package com.daniel.wiki.aspect;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.support.spring.PropertyPreFilters;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
@Aspect //要加这个注释
@Component
public class LogAspect {
private final static Logger LOG = LoggerFactory.getLogger(LogAspect.class);
/** 定义一个切点 */
//针对所有controller的所有方法
@Pointcut("execution(public * com.daniel.*.controller..*Controller.*(..))")
public void controllerPointcut() {}
//前置通知
//也可以放进环绕通知里
@Before("controllerPointcut()")
public void doBefore(JoinPoint joinPoint) throws Throwable {
// 开始打印请求日志
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
Signature signature = joinPoint.getSignature();
String name = signature.getName();
// 打印请求信息
LOG.info("------------- 开始 -------------");
LOG.info("请求地址: {} {}", request.getRequestURL().toString(), request.getMethod());
LOG.info("类名方法: {}.{}", signature.getDeclaringTypeName(), name);
LOG.info("远程地址: {}", request.getRemoteAddr());
// 打印请求参数
Object[] args = joinPoint.getArgs();
// LOG.info("请求参数: {}", JSONObject.toJSONString(args));
Object[] arguments = new Object[args.length];
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof ServletRequest
|| args[i] instanceof ServletResponse
|| args[i] instanceof MultipartFile) {
continue;
}
arguments[i] = args[i];
}
// 排除字段,敏感字段或太长的字段不显示
String[] excludeProperties = {"password", "file"};
PropertyPreFilters filters = new PropertyPreFilters();
PropertyPreFilters.MySimplePropertyPreFilter excludefilter = filters.addFilter();
excludefilter.addExcludes(excludeProperties);
LOG.info("请求参数: {}", JSONObject.toJSONString(arguments, excludefilter));
}
//环绕通知
@Around("controllerPointcut()")
public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
//执行实际代码
Object result = proceedingJoinPoint.proceed();
// 排除字段,敏感字段或太长的字段不显示
String[] excludeProperties = {"password", "file"};
PropertyPreFilters filters = new PropertyPreFilters();
PropertyPreFilters.MySimplePropertyPreFilter excludefilter = filters.addFilter();
excludefilter.addExcludes(excludeProperties);
LOG.info("返回结果: {}", JSONObject.toJSONString(result, excludefilter));
LOG.info("------------- 结束 耗时:{} ms -------------", System.currentTimeMillis() - startTime);
return result;
}
}