引子:上一篇文章已经讲了spring的核心思想Ioc,那作为spring的两大核心思想的另一个思想Aop,当然也不能缺席啦。
那么,什么是Aop呢?
我们都知道Java的核心思想是面向对象OOP,而OOP的核心是封装、继承、多态。
什么是封装?
封装就是把对象的属性和操作(或服务)结合为一个独立的整体,并尽可能隐藏对象的内部实现细节。
由此可见封装的好处在于把整个项目中相同的业务抽成成一个个独立的类/方法,使得相同的代码有复用性,这样的做法可以降低代码的复杂度,但是也存在一些问题,比如我们都知道日志是开发中必不可少的逻辑,当我们把一块块的代码封装到不同的类中之后,一旦需要添加日志时,就需要在每个类中都写入相应的日志代码,有人会说,可以把日志代码封装成一个类来调用,在这些需要日志的类中调用日志收集的方法即可,但这样一来,日志类和业务逻辑就产生了耦合,当日志类发生变化时,导致业务逻辑的代码都需要修改。
这..怎么办呢?
当OOP满足不了的时候,AOP就来救场了。
AOP:面向切面编程,作为OOP编程的一种补充,它的核心思想是在运行时动态地将代码切入到类的指定方法、指定位置上。
因此问题就解决,我们把日志收集逻辑作为一个切面,切入到指定的类/方法中,按照我们需求执行即可,我们的业务逻辑既不会因为这个日志类产生变动而收到影响,也不会因为它的存在而产生耦合现象。
知道原理就好办了,下面我们来举个栗子:
例如:很多时候,API的每个接口,我们都需要知道它的执行时间,这样方便我们做性能监控和优化
下面是常规操作:
1publicclassApi{
2publicvoidtest(){
3// 开始
4longbegin=(newDate()).getTime();
5System.out.println("开始执行....");
6
7//执行业务逻辑
8
9// 结束
10longend=(newDate()).getTime();
11//统计耗时
12System.out.println("结束执行....");
13System.out.println(" 执行完成,耗时:"+(end-begin)+"毫秒");
14}
15}
可以看到,这样确实可以获取到执行时长,但问题是,每个方法都要加入这些代码,那就很悲催了。。
下面,我们用spring的AOP来实现同样的功能
首先,创建一个切面的类:
1packagecom.jaybril.aoptest;
2
3importjava.util.Date;
4
5importorg.aspectj.lang.ProceedingJoinPoint;
6importorg.aspectj.lang.annotation.Around;
7importorg.aspectj.lang.annotation.Aspect;
8importorg.aspectj.lang.annotation.Pointcut;
9importorg.springframework.stereotype.Component;
10importorg.springframework.util.StopWatch;
11
12
13@Component
14@Aspect
15publicclassAopAspect{
16//within 用于匹配指定类型内的方法执行
17//设置切入点
18@Pointcut("within(AopService)")
19//切点签名方法,作用是使得通知的注解可以通过这个切点签名方法连接到切点,
20//通过解释切点表达式找到需要被切入的连接点。
21//最终的目的都是为了找到需要被切入的连接点
22publicvoidpointcut(){
23System.out.println("I am pointcut...");
24}
25// @Before:前置通知,在调用目标方法之前执行通知定义的任务
26// @After:后置通知,在目标方法执行结束后,无论执行结果如何都执行通知定义的任务
27// @After-returning:后置通知,在目标方法执行结束后,如果执行成功,则执行通知定义的任务
28// @After-throwing:异常通知,如果目标方法执行过程中抛出异常,则执行通知定义的任务
29// @Around:环绕通知,在目标方法执行前和执行后,都需要执行通知定义的任务
30@Around("pointcut()")
31publicObjectinvokeMethod(ProceedingJoinPoint pjp)throwsThrowable{
32// 开始
33longbegin=(newDate()).getTime();
34System.out.println("开始执行....");
35//执行业务逻辑
36Object retVal = pjp.proceed();
37// 结束
38longend=(newDate()).getTime();
39//统计耗时
40System.out.println("结束执行....");
41System.out.println("方法:"+pjp.getSignature().toShortString()+" 执行完成,耗时:"+(end-begin)+"毫秒");
42returnretVal;
43}
44
45}
其次,创建一个业务类:
1packagecom.jaybril.aoptest;
2importorg.springframework.stereotype.Service;
3@Service
4publicclassAopService{
5publicvoidtest(){
6System.out.println("执行业务逻辑。。。");
7}
8}
接下来,我们测试一下:
1packagecom.jaybril.aoptest;
2importjavax.annotation.PostConstruct;
3importorg.springframework.beans.factory.annotation.Autowired;
4importorg.springframework.boot.SpringApplication;
5importorg.springframework.boot.autoconfigure.SpringBootApplication;
6importorg.springframework.context.annotation.EnableAspectJAutoProxy;
7
8@EnableAspectJAutoProxy//开启AOP
9@SpringBootApplication//把启动类注入到容器
10publicclassAopTest{
11@Autowired
12AopService service;
13publicstaticvoidmain(String[] args){
14SpringApplication.run(AopTest.class, args);
15}
16@PostConstruct//用于在依赖关系注入完成之后需要执行的方法上,以执行任何初始化
17publicvoidtest(){
18service.test();
19}
20@PostConstruct
21publicvoidtest2(){
22service.test();
23}
24}
看看输出:
1开始执行....
2执行业务逻辑。。。
3结束执行....
4方法:AopService.test() 执行完成,耗时:17毫秒
5开始执行....
6执行业务逻辑。。。
7结束执行....
8方法:AopService.test() 执行完成,耗时:0毫秒
可以清楚的看到,它按照我们的预想输出了,而我们的业务类,没有任何日志的代码侵入,并且日志类的变动,也不会对业务类产生影响,因此,这就是AOP的优势之处。
本文只是用了一些通俗的话语来简述AOP的原理,并且用一个最常见最简单的例子来分析理解AOP,实际上AOP还有非常多的知识点和用途,这就要求大家多学多写了。
觉得本文对你有帮助?请分享给更多人
关注「编程无界」,提升装逼技能
![image](http://upload-images.jianshu.io/upload_images/13318651-42fdcc48cd3a1eca?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)