简介
此处,只梳理AOP的基本概念和应用。底层原理是动态代理和反射,不作探究。
aop 面向切面 :大白话来说就是针对一些类的一些方法需要追加一些功能,这些功能和原来的逻辑无关。
切面编程的三个要素:
这些功能可以单独定义一个类来实现称之为通知。
另外再通过一个类实现追加,这个类称之为切面。
那么这些待追加功能的方法就是称为切入点。
1实战
1.1 依赖
spring boot项目中使用AOP 需要添加maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
1.2 aop实现
1.2.1 基于注解切入
1 自定义一个注解 为了标识需要增强的位置
package com.wang.seckill.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 注解,用于标识切入点,添加该注解的方法,
* 执行前后会先执行通知
*/
@Retention(RetentionPolicy.RUNTIME) //什么时期
@Target(ElementType.METHOD)///用于方法
public @interface Validate {
}
2 通知定义--对原有功能增强
实质上还是一个类,承载了功能逻辑。
package com.wang.seckill.advice;
import org.springframework.stereotype.Component;
/**
*
* 通知类,通过切面对其他方法增强
*/
@Component
public class CheckUserServiceImpl implements CheckUserService{
@Override
public void check() {
System.out.println("我是一个通知方法,我现在执行了哦");
}
}
3 切面实现---
使用@Aspect
package com.wang.seckill.aspect;
import com.wang.seckill.advice.CheckUserService;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
*
*切面: 把通知和需要增强的方法结合起来
*/
@Aspect //开启切面功能
@Component
public class CheckUserAspect {
@Autowired
private CheckUserService checkUserService;
//@annotation 切入点,被Validate注解的方法,就会有通知
@Pointcut("@annotation(com.wang.seckill.annotation.Validate)")
public void point(){
}
@Before("point()")///通知类型,关联到切入点,
public void checkBefore(){
// 通知逻辑
checkUserService.check();
}
}
测试:
exeSelect() 执行之前,会先执行通知方法:
/**
* 查询所有秒杀商品
* @return
*/
@Override
@Validate
public List<TbSeckillGoods> exeSelect() {
return redisTemplate.opsForHash().values(TbSeckillGoods.class.getSimpleName());
}
前台查询结果:
可以看到通知方法被执行了:
1.2.2 基于表达式切入
1 通知
package com.wang.seckill.advice;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Component
public class TestExecutionAdvice {
public void exeBefore(){
System.out.println("我是通过切面表达式触发的切面方法");
}
}
2 切面
切面表达式execution()
package com.wang.seckill.aspect;
import com.wang.seckill.advice.TestExecutionAdvice;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class AspectExecution {
@Autowired
private TestExecutionAdvice testExecutionAdvice;
//切面表达式 com.wang.seckill.service包下面所有类的所有方法 任意返回类型
@Pointcut("execution(* com.wang.seckill.service.*.*(..))")
public void point(){
}
@Before("point()")
public void exeBefore(){
testExecutionAdvice.exeBefore();
}
}
测试
package com.wang.seckill.service; //包中查询一个上面的方法已经被增强
/**
* 根据id从redis查到商品返回
* @param id
* @return
*/
public TbSeckillGoods findOne(long id) {
return (TbSeckillGoods) redisTemplate.boundHashOps(TbSeckillGoods.class.getSimpleName()).get(id);
}
查询一个商品:
通知执行:
通知类型
@Before
@after
@Round
@AfterReturning
@AfterThrowing
在一个方法加了所有通知之后执行:
方法类
package com.wang.seckill.advice;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Component
public class TestExecutionAdvice {
public void exeBefore(){
System.out.println("我是@Before通过切面表达式触发的切面方法");
}
public void exeAfter(){
System.out.println("我是@After我在方法执行之后执行");
}
public Object exeRound(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("我是@Around我环绕执行");
return proceedingJoinPoint.proceed();
}
public void throwing(){
System.out.println("我是@AfterThrowing 发生异常我就执行");
}
public void returning(){
System.out.println("我是@Returning方法返回后执行");
}
}
切面
package com.wang.seckill.aspect;
import com.wang.seckill.advice.TestExecutionAdvice;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class AspectExecution {
@Autowired
private TestExecutionAdvice testExecutionAdvice;
//切面表达式 com.wang.seckill.service包下面所有类的所有方法 任意返回类型
@Pointcut("execution(* com.wang.seckill.service.*.*(..))")
public void point(){
}
@Before("point()")
public void exeBefore(){
testExecutionAdvice.exeBefore();
}
@After("point()")
public void exeAfter(){testExecutionAdvice.exeAfter();}
@AfterReturning("point()")
public void exeReturn(){testExecutionAdvice.returning();}
@Around("point()")
public void exeRound(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {testExecutionAdvice.exeRound(proceedingJoinPoint);}
@AfterThrowing("point()")
public void exeThrowing(){
testExecutionAdvice.throwing();
}
}
正常通知:
发生异常的通知:
问题
@Around 增强的方法有返回,那么通知也要有返回值
org.springframework.aop.AopInvocationException: Null return value from advice does not match primitive return type for: public int com.wang.seckill.service.SeckillServiceImpl.testAspect()