大家好!我是码锅(mbb)!一个乐于分享的全栈(自封)程序员。
作为一名Java后端开发,AOP这个牛皮哄哄的名词一定听过、用过;但是对于刚入行的新手,甚至部分有几年开发经验的程序员来说,在初次理解它的时候,都会有点点的吃力;因为和我们一开始就接触的面向对象编程(OOP)思想有些出入,加上概念比较的空泛,导致很多人一直以来都有那么点似懂非懂的感觉;(懂了,但只懂了一点点...)
今天,咱就以一个生活中的故事场景,一起来好好理解一下;
在讲故事场景之前,还是得先说一下AOP常用到的一些概念、名词;说在前面,方便一会儿带入场景理解!
AOP (Aspect Orient Programming),直译过来就是 面向切面编程。AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。AOP的目的是实现关注点的分离;
就这么不长不短的一句话,可以看出AOP的作用还是非常重要的。但是懵逼也就从这里开始了;
什么是关注点分离?
莫慌!还有更多难理解的呢,慢慢往后面看,到时候一起来解释。
好!这些都是官方文档中给到的解释;
AOP不好理解往往就是被上面这几个概念给带到沟里去了;因为实在是太空泛了,第一次看到完全不知道在说什么鬼,这些字全部都认识,可组成的词儿放这里一个都不懂了,下面就以一个生活中的场景来理解一下;
码锅是一个土生土长的农村娃,从记事开始,老家的家人就是吃着山头的一个天然矿泉水,到现在几十年过去了,那股山泉依然在,但是前些年,家里盖房子,换了宅基地,山泉的海拔比新的宅基地低,自然山泉水就无法到家了!
刚刚好的是,国家政策正好扶持发展农村的基础建设,村里面建起了自来水水库,国家出资将水管牵到每家每户;大家免费用水。
可是,免费的东西,大家总是不那么的珍惜,有些家庭常年把水开着,导致自来水严重浪费,想用水的用户用不上,导致村委很难管理;
为了抵制这种现象,村委决定对自来水进行象征性的收费,每户自来水入户的水管上都装上水表,自己充卡买水,费用用完,自来水就停了,一下子就从根本上解决了自来水的浪费问题;
简单的故事说完了,我们来想一下,给每户装水表的动作就是一个典型的AOP场景;
好了,结合场景的理解说完了,我想到这里,你对AOP的各个关键名词应该理解的差不多了,如果还不理解,建议把上面的场景再读一下;
既然概念都说通了,代码要咋实现呢?理解之后,代码部分其实是非常简单的。
创建一个SpringBoot项目并引入AOP的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
定义一个放水的接口
@RestController
@RequestMapping("/family")
public class FamilyController {
@GetMapping("tapwater")
public String tapWater() {
Random ran = new Random();
// 模拟一个出水时长
int i = ran.nextInt(10);
System.out.println("水来了.....时长:" + i);
// 模拟异常出水,大于5为异常情况
if (i > 5) {
throw new RuntimeException();
}
return "水来了.....";
}
}
访问接口:127.0.0.1:8080/family/tapwater 会出现以下日志;
定义一个水表切面
通俗的理解,就是安装一个水表
@Aspect
@Component
public class MeterAspect {
/**
* 方法执行之前
* * com.lupf.tapwater.rest.FamilyController.*(..) 指定了切点
* @Before 指定了连接点 也就是执行时机
*/
@Before("execution(* com.lupf.tapwater.rest.FamilyController.*(..))")
public void balance(){
System.out.println("出水前,检查余额");
}
/**
* 方法成功执行之后的增强
*/
@AfterReturning("execution(* com.lupf.tapwater.rest.FamilyController.*(..))")
public void afterReturning(){
System.out.println("正常出水,扣除水费");
}
/**
* 方法执行之后 成功异常都执行的增强
*/
@After("execution(* com.lupf.tapwater.rest.FamilyController.*(..))")
public void deduction(){
System.out.println("出水操作执行完成");
}
/**
* 异常之后的增强
*/
@AfterThrowing("execution(* com.lupf.tapwater.rest.FamilyController.*(..))")
public void afterThrowing(){
System.out.println("出水异常,关闭阀门");
}
}
execution(* com.lupf.tapwater.rest.FamilyController.*(..))
测试
此时再次执行:127.0.0.1:8080/family/tapwater 就会出现以下日志;
image-20210704202807730
环绕通知
@Aspect
@Component
public class MeterAspect {
/**
* 方法执行之前
* * com.lupf.tapwater.rest.FamilyController.*(..) 指定了切点
* @Around 指定了连接点 也就是执行时机为环绕通知
*/
@Around("execution(* com.lupf.tapwater.rest.FamilyController.*(..))")
public Object around(ProceedingJoinPoint pj) throws Throwable{
Throwable throwable;
try {
System.out.println("检查余额");
Object proceed = pj.proceed();
System.out.println("正常出水,扣除水费");
return proceed;
} catch (Throwable t) {
System.out.println("出水异常,关闭阀门");
throwable = t;
}
System.out.println("出水操作执行完成");
throw throwable;
}
}
最终执行的效果和上面测试的情况一样
通过上面的示例,我们不妨来思考一下,AOP可以帮助我们做哪些事情?是不是一切关注点分离的事情都可以做了,比如:
只要是和具体的业务无关,且大家都在关注的事情,那么都可以通过AOP去抽离这些关注点并将其统一维护,提高代码的复用性;
AOP的概念真正理解之后,会对你日常业务开发带来很多的便捷;难就难在理解它的这个过程,也只有真正理解了,才能发挥出其独有的魅力;那些繁琐的官方概念确实给我们的理解上挖了不少的坑,但是看了这篇,有没有助你拨开那层迷雾呢?