- 通知(Advice)
切面的工作被称为通知。通知定义了切面是什么以及何时使用。除了描述切面要完成的工作,通知还解决了何时执行这个工作的问题。
5种通知类型:
前置通知(Before):在目标方法被调用之前调用通知功能
后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么
返回通知(After-returning):在目标方法成功执行之后调用通知
异常通知(After-throwing):在目标方法抛出异常后调用通知
环绕通知(Around):通知包裹了被通知的方法,在被通知的方
法调用之前和之后执行自定义的行为
后置通知和返回通知的区别是,后置通知是不管方法是否有异常,都会执行该通知;而返回通知是方法正常结束时才会执行。
- 连接点(Join point)
连接点是在应用执行过程中能够插入切面的一个点。 - 切点(Pointcut)
一个切面并不需要通知应用的所有连接点,切点有助于缩小切面所通知的连接点范围。如果说通知定义了切面的“什么”和“何时”的话,那么切点就定义了“何处”。因此,切点其实就是定义了需要执行在哪些连接点上执行通知。 - 切面(Aspect)
切面是通知和切点的结合。通知和切点共同定义了切面的全部内容——它是什么,在何时和在何处完成其功能。 - 引入(Introduction)
引入允许我们向现有的类添加新方法或属性。 - 织入(Weaving)
织入是把切面应用到目标对象并创建新的代理对象的过程。切面在指定的连接点被织入到目标对象中。在目标对象的生命周期中有很多个点可以进行织入:
编译期:切面在目标类编译时被织入。这种方式需要特殊的编译器。AspectJ的织入编译器就是以这种方式织入切面的。
类加载期:切面在目标类加载到JVM时被织入。这种方式需要特殊的类加载器,它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ 5的加载时织入就支持这种方式织入切面。
运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态的创建一个代理对象。Spring AOP就是以这种方式织入切面的。
以下是Spring AOP所支持的AspectJ切点指示器
AspectJ指示器 | 描述 |
---|---|
arg() | 限制连接点匹配参数为指定类型的执行方法 |
@args() | 限制连接点匹配参数由指定注解标注的执行方法 |
execution() | 用于匹配是连接点的执行方法 |
this() | 限制连接点匹配AOP代理的bean引用为指定类型的类 |
target | 限制连接点匹配目标对象为指定类型的类 |
@target | 限制连接点匹配特定的执行对象,这些对象对应的类要具有指定类型的注解 |
within() | 限制连接点匹配指定的类型 |
@within() | 限制连接点匹配指定注解所标注的类型(当使用Spring AOP时,方法定义在由指定的注解所标注的类里) |
@annotation | 限定匹配带有指定注解的连接点 |
java代码:
package com.xkcoding.log.aop.controller;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.StrUtil;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* <p>
* 测试 Controller
* </p>
*
* @package: com.xkcoding.log.aop.controller
* @description: 测试 Controller
* @author: yangkai.shen
* @date: Created in 2018/10/1 10:10 PM
* @copyright: Copyright (c) 2018
* @version: V1.0
* @modified: yangkai.shen
*/
@RestController
public class TestController {
/**
* 测试方法
*
* @param who 测试参数
* @return {@link Dict}
*/
@GetMapping("/test")
public Dict test(String who) {
return Dict.create().set("who", StrUtil.isBlank(who) ? "me" : who);
}
}
package com.xkcoding.log.aop.aspectj; import cn.hutool.json.JSONUtil; import eu.bitwalker.useragentutils.UserAgent; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import java.util.Map; import java.util.Objects; /** * <p> * 使用 aop 切面记录请求日志信息 * </p> * * @package: com.xkcoding.log.aop.aspectj * @description: 使用 aop 切面记录请求日志信息 * @author: yangkai.shen * @date: Created in 2018/10/1 10:05 PM * @copyright: Copyright (c) 2018 * @version: V1.0 * @modified: yangkai.shen */ @Aspect @Component @Slf4j public class AopLog { private static final String START_TIME = "request-start"; /** * 切入点 */ @Pointcut("execution(public * com.xkcoding.log.aop.controller.*Controller.*(..))") public void log() { } /** * 前置操作 * * @param point 切入点 */ @Before("log()") public void beforeLog(JoinPoint point) { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = Objects.requireNonNull(attributes).getRequest(); log.info("【请求 URL】:{}", request.getRequestURL()); log.info("【请求 IP】:{}", request.getRemoteAddr()); log.info("【请求类名】:{},【请求方法名】:{}", point.getSignature().getDeclaringTypeName(), point.getSignature().getName()); Map<String, String[]> parameterMap = request.getParameterMap(); log.info("【请求参数】:{},", JSONUtil.toJsonStr(parameterMap)); Long start = System.currentTimeMillis(); request.setAttribute(START_TIME, start); } /** * 环绕操作 * * @param point 切入点 * @return 原方法返回值 * @throws Throwable 异常信息 */ @Around("log()") public Object aroundLog(ProceedingJoinPoint point) throws Throwable { Object result = point.proceed(); log.info("【返回值】:{}", JSONUtil.toJsonStr(result)); return result; } /** * 后置操作 */ @AfterReturning("log()") public void afterReturning() { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = Objects.requireNonNull(attributes).getRequest(); Long start = (Long) request.getAttribute(START_TIME); Long end = System.currentTimeMillis(); log.info("【请求耗时】:{}毫秒", end - start); String header = request.getHeader("User-Agent"); UserAgent userAgent = UserAgent.parseUserAgentString(header); log.info("【浏览器类型】:{},【操作系统】:{},【原始User-Agent】:{}", userAgent.getBrowser().toString(), userAgent.getOperatingSystem().toString(), header); } }