AOP面向切面编程思想及代码实现
本教程根据某站扎克蕉 视频总结,如有侵权请私信修改内容或删除
一、什么是面向切面编程
-
AOP简介
AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。
-
为什么使用AOP编程范式?
分离功能性需求和非功能性需求
集中处理某一关注点
侵入性少,增强代码可读性及可维护性
-
AOP应用场景
权限控制、缓存控制、事务控制、分布式追踪、异常处理等
二、AOP基本知识
要想使用面向对象编程的思想,首先要了解几个专有名词
• Target:目标类,即需要被代理的类。例如:UserService
• Joinpoint(连接点):所谓连接点是指那些可能被拦截到的方法。例如:所有的方法
• PointCut 切入点:已经被增强的连接点。例如:addUser()
• Advice 通知/增强,增强代码。例如:after、before
• Weaving(织入):是指把增强advice应用到目标对象target来创建新的代理对象proxy的过程.
• Proxy 代理类
• Aspect(切面): 是切入点pointcut和通知advice的结合
常见通知 五种Advice
前置通知:在我们执行方法之前运行(@Before)
后置通知:在我们目标方法运行结束之后,不管有没有异常(@After)
返回通知:在我们的目标方法正常返回值之后运行(@AfterReturning)
异常通知:在我们的目标方法出现异常后运行(@AfterThrowing)
环绕通知:动态代理,需要手动执行joinPoint.processed()(其实就是前置通知+后置通知)(@Around)
三、springboot AOP实例
1.注解方式实现AOP
Maven依赖:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
-
- 创建一个注解
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface adminOnly { }
-
- 创建一个切面
@Aspect @Component public class CheckUserAspect { @Autowired private CheckUserService checkUserService; /** * 标记注解方式拦截 * 切点里面放定义注解的reference * */ @Pointcut("@annotation(com.sipc.catalina.Annotation.adminOnly)") public void checkAdmin(){ } @Before("checkAdmin()") public void before(JoinPoint joinPoint) { checkUserService.check(); } }
@Service public class CheckUserService { public void check() throws Exception { String user = CurrentUserHolder.get(); if(!"admin".equals(user)){ throw new Exception("【异常】你不是管理员!"); } } }
-
- 使用注解实现AOP
@Service public class StudentService { @Autowired private CheckUserService checkUserService; @adminOnly public void insert() throws Exception { System.out.println("增加记录成功!"); } public void delete() throws Exception { System.out.println("删除纪录成功!"); } public void update() throws Exception { System.out.println("更新纪录成功!"); } public void find(){ System.out.println("查询成功!"); } }
-
- 测试
@RunWith(SpringRunner.class) @SpringBootTest class StudentServiceTest { @Autowired private StudentService studentService; @Test public void insert() throws Exception { CurrentUserHolder.set("abcde"); studentService.insert(); } }
使用定义的注解后,调用该方法就要运行定义好的通知/增强。例子中使用的是前置通知,就会在开始方法先先运行checkUserService.check();
可以替换其他注解,实现其他几种通知/增强。
使用注解进行AOP,侵入性强,并且要定义一个注解,我们可以通过表达式替换掉注解
2.使用表达式实现AOP
表达式方法实现AOP,不需要创建注解,只需要在切点处表明切面生效的地址即可。
@Aspect
@Component
public class CheckUserAspect {
@Autowired
private CheckUserService checkUserService;
@Pointcut("execution(* com.sipc.catalina.service.*.*(..))")
public void checkAdmin(){
}
@Before("checkAdmin()")
public void before() {
System.out.println("-------【前置通知】-------");
}
}
使用表达式后,该地址下全部方法在运行前都先输出 前置通知 。
JoinPoint(连接点)的使用
@Aspect
@Component
public class CheckUserAspect {
@Autowired
private CheckUserService checkUserService;
@Pointcut("execution(* com.sipc.catalina.service.*.*(..))")
public void checkAdmin(){
}
@Before("checkAdmin()")
public void before(JoinPoint joinPoint) {
System.out.println("-------【前置通知】-------" + joinPoint);
}
}
使用后,可以看到切点的位置信息(增强方法的reference)
3.五种通知方式
常见通知 五种Advice
前置通知:在我们执行方法之前运行(@Before)
后置通知:在我们目标方法运行结束之后,不管有没有异常(@After)
返回通知:在我们的目标方法正常返回值之后运行(@AfterReturning)
异常通知:在我们的目标方法出现异常后运行(@AfterThrowing)
环绕通知:动态代理,需要手动执行joinPoint.processed()(其实就是前置通知+后置通知)(@Around)
其中异常通知可以获取异常
@AfterThrowing(value = "checkAdmin()", throwing = "e")
public void afterThrowing(Throwable e) {
System.out.println("-------【异常通知】-------" + e.getMessage());
}
返回通知可以获取返回值
@AfterReturning(value = "checkAdmin()", returning = "userId")
public void afterReturning(Object userId) {
System.out.println("-------【返回通知】-------" + userId);
}
环绕通知需要手动执行joinPoint.processed()
@Around(value = "checkAdmin()")
public Object aroud(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("-------【环绕通知前】-------" );
Object obj = joinPoint.proceed(); //执行目标方法
System.out.println("-------【环绕通知后】--------");
return obj;
}