(一)前期准备
添加POM依赖:
<!--aop-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
(二)使用
1.在GirlController类中的所有方法调用之前调用“开始记录日志”这句话
@Component
@Aspect
public class HttpAspect {
/**
* 拦截GirlController类中的所有方法
* 方法详解:
* “@Before”:在拦截的目标方法之前调用标注有该注解的方法
* "execution(public * org.pc.girl.controller.GirlController.*(..))":execution表达式
* public * org.pc.girl.controller.GirlController.*(..)
* public:目标方法的访问权限
* * :目标方法的返回类型(*是通配符,所有类型都行)
* org.pc.girl.controller.GirlController:目标方法所在的类,必须是全限定名
* * :目标方法名,若是通配符“*”,则拦截所有方法;若是某个方法名,则只拦截这个方法,比如只拦截
* 删除方法(removeGirl()),那就得写成“removeGirl”
* (..) :目标方法的参数
*/
@Before("execution(public * org.pc.girl.controller.GirlController.*(..))")
public void logAll(){
System.out.println("开始记录日志");
}
}
2.在GirlController类中的所有方法调用之后调用“结束记录日志”这句话
@After("execution(public * org.pc.girl.controller.GirlController.*(..))")
public void logAfter(){
System.out.println("结束记录日志");
}
问题:此时就会发现“execution”表达式重复了,这样反复调用这么一长串的代码,一旦我们需要对“execution”表达式进行更改,那么就需要做大量的重复劳动,而且非常有可能出错,怎么办?
解决办法:定义一个切点“@Pointcut”,标注在一个公用方法之上,然后复用公用方法:
@Pointcut("execution(public * org.pc.girl.controller.GirlController.*(..))")
public void log(){
}
@Before("log()")
public void logBefore(){
System.out.println("开始记录日志");
}
@After("log()")
public void logAfter(){
System.out.println("结束记录日志");
}
3.在切面方法中获取所拦截方法的请求信息和被拦截类、方法信息
(1)获取所拦截方法的请求信息和被拦截类、方法信息
@Before("log()")
public void logBefore(JoinPoint joinPoint){
System.out.println("开始记录日志");
//获取请求对象
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (requestAttributes != null){
HttpServletRequest request = requestAttributes.getRequest();
//获取请求url
LOGGER.info("url={}", request.getRequestURI());
//获取请求方法
LOGGER.info("method={}", request.getMethod());
//获取请求ip
LOGGER.info("ip={}", request.getRemoteAddr());
//获取请求端口
LOGGER.info("port={}", request.getServerPort());
}
/*----------------------- 获取拦截的类信息、方法信息的关键参数是JoinPoint对象 ----------------------------*/
//获取拦截类名
String className = joinPoint.getSignature().getDeclaringTypeName();
LOGGER.info("class={}", className);
//获取拦截类下的方法名
String methodName = joinPoint.getSignature().getName();
LOGGER.info("method={}", methodName);
//获取拦截的方法的参数
Object[] params = joinPoint.getArgs();
LOGGER.info("params={}", params);
}
(2)获取所拦截方法的返回值
/**
* 获取拦截方法的返回值
* “@AfterReturning”:先执行“@After”,再执行“@AfterReturning”
* “returning”:返回值将注入到哪个变量中
* “pointcut” :切点
* Object object:和“returning”规定的字段相同,才能保证方法的返回值会注入到该变量中
*/
@AfterReturning(returning = "object", pointcut = "log()")
public void doAfter(Object object){
LOGGER.info("response={}", object.toString());
}
(3)@Around注解
@Around注解既可在@Before之前执行,又可同时在原方法执行之后,@After执行方法之前执行。代码如下:
/**
* pjp.proceed()执行之后得到的返回值就是所拦截方法执行之后的返回值,若在这里不返回pjp.proceed(),那么方法将
* 不再有返回值。比如,我们在“@AfterReturning”标注的方法中,就无法得到方法的返回值,会报空指针异常!
*/
@Around("log()")
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
//在“@Before”标注的拦截方法之前执行
LOGGER.info("调用了环绕方法");
//执行原方法,返回原方法的返回值
Object object = pjp.proceed();
//在被拦截的方法执行之后,“@After”标注的拦截方法之前执行
LOGGER.info("又调用了环绕方法");
return object;
}
pjp.proceed()
之前的逻辑在“@Before”之前执行,pjp.proceed()
之前的逻辑在原方法执行之后,“@After”之前执行。
注意:pjp.proceed()
这里执行的逻辑就是执行原方法,返回值也就是原方法的返回值,若我们还希望对原方法的返回值进行处理,那么我们就必须return pjp.proceed()
,否则后续我们将无法取到方法的返回值!!!
(三)关于AOP中各个注解的生命周期
AOP中共有4个注解:@Before、@After、@AfterReturning、@Around四大注解,他们的的生命周期如下:
第一步:执行@Around
第二步:执行@Before
第三步:执行原方法
第四步:执行@Around
第五步:执行@After
第六步:执行@AfterReturning