在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方
式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个
热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑
的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高
了开发的效率。

然后我们举一个比较容易理解的例子(来自:Spring 之 AOP):

要理解切面编程,就需要先理解什么是切面。用刀把一个西瓜分成两瓣,切开的切口就是切面;炒菜,锅与炉子共同来完成炒菜,锅与炉子就是切面。web层级设计中,web层->网关层->服务层->数据层,每一层之间也是一个切面。编程中,对象与对象之间,方法与方法之间,模块与模块之间都是一个个切面。

我们一般做活动的时候,一般对每一个接口都会做活动的有效性校验(是否开始、是否结束等等)、以及这个接口是不是需要用户登录。

按照正常的逻辑,我们可以这么做。  

java aop切面如何获取requestheader spring aop切面定义_jar包

这有个问题就是,有多少接口,就要多少次代码copy。对于一个“懒人”,这是不可容忍的。好,提出一个公共方法,每个接口都来调用这个接口。这里有点切面的味道了。

 

java aop切面如何获取requestheader spring aop切面定义_AOP_02

同样有个问题,我虽然不用每次都copy代码了,但是,每个接口总得要调用这个方法吧。于是就有了切面的概念,我将方法注入到接口调用的某个地方(切点)。

java aop切面如何获取requestheader spring aop切面定义_AOP_03

这样接口只需要关心具体的业务,而不需要关注其他非该接口关注的逻辑或处理。
红框处,就是面向切面编程。

 

一、到底什么是AOP(面向切面编程)?

下面我举个例子给大家说明一下:

有A,B,C三个方法,但是在调用每一个方法之前,要求打印一个日志:某一个方法被开始调用了!

在调用每个方法之后,也要求打印日志:某个方法被调用完了!

一般人会在每一个方法的开始和结尾部分都会添加一句日志打印吧,这样做如果方法多了,就会有很多重复的代码,显得很麻烦,这时候有人会想到,为什么不把打印日志这个功能封装一下,然后让它能在指定的地方(比如执行方法前,或者执行方法后)自动的去调用呢?如果可以的话,业务功能代码中就不会掺杂这一下其他的代码,所以AOP就是做了这一类的工作,比如,日志输出,事务控制,异常的处理等。。

如果把AOP当做成给我们写的“业务功能”增添一些特效,就会有这么几个问题:

1.我们要制作哪些特效

2.这些特效使用在什么地方

3.这些特效什么时候来使用
 

 

二、有了这三个疑问,加上上面的讲解,下面我们来说一下AOP的一些术语(一下看不懂不要紧,慢慢理解)
1.通知(Advice)

就是你想要的功能,也就是上面说的 安全,事物,日志等。你给先定义好把,然后在想用的地方用一下。
    2.连接点(JoinPoint)

这个更好解释了,就是spring允许你使用通知的地方,那可真就多了,基本每个方法的前,后(两者都有也行),或抛出异常时都可以是连接点,spring只支持方法连接点.其他如aspectJ还可以让你在构造器或属性注入时都行,不过那不是咱关注的,只要记住,和方法有关的前前后后(抛出异常),都是连接点。
    3.切入点(Pointcut)

上面说的连接点的基础上,来定义切入点,你的一个类里,有15个方法,那就有几十个连接点了对把,但是你并不想在所有方法附近都使用通知(使用叫织入,以后再说),你只想让其中的几个,在调用这几个方法之前,之后或者抛出异常时干点什么,那么就用切点来定义这几个方法,让切点来筛选连接点,选中那几个你想要的方法。
    4.切面(Aspect)

切面是通知和切入点的结合。现在发现了吧,没连接点什么事情,连接点就是为了让你好理解切点,搞出来的,明白这个概念就行了。通知说明了干什么和什么时候干(什么时候通过方法名中的before,after,around等就能知道),而切入点说明了在哪干(指定到底是哪个方法),这就是一个完整的切面定义。
    5.引入(introduction)

允许我们向现有的类添加新方法属性。这不就是把切面(也就是新方法属性:通知定义的)用到目标类中吗
    6.目标(target)

引入中所提到的目标类,也就是要被通知的对象,也就是真正的业务逻辑,他可以在毫不知情的情况下,被咱们织入切面。而自己专注于业务本身的逻辑。
    7.代理(proxy)

怎么实现整套aop机制的,都是通过代理,这个一会给细说。
    8.织入(weaving)

把切面应用到目标对象来创建新的代理对象的过程。有3种方式,spring采用的是运行时,为什么是运行时,后面解释。

关键就是:切点定义了哪些连接点会得到通知
 

三、为了便于大家理解,我写了个简单的代码示例:

java aop切面如何获取requestheader spring aop切面定义_jar包_04

上面jar包引入成功,并且配置完毕以后,咱们随便创建一个接口和实现类(这个就跟平时创建service接口和实现类一样)

这里我就不展示代码了,比如QueryService接口和QueryServiceImpl实现类,里面有一个queryList()查询方法!

接下来我们要创建具体的切面类:

1.先创建一个类,比如:MyAspect.java

2.在类上使用 @Aspect 注解 使之成为切面类

3.在类上使用 @Component 注解 把切面类加入到IOC容器中,或者在spring配置文件中创建bean也可以,也可以在它上面加@Service注解,目的就是让它实例化

 

请看创建的切面类,类中的方法名随意取,关键是方法上面的注解,用@Aspect注解方式来实现前置通知、返回通知、后置通知、异常通知、环绕通知!!!!!!!

import java.util.Arrays;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class MyAspect {
    
    /**     * 前置通知:目标方法执行之前执行以下方法体的内容      * @param jp     */ 
    @Before("execution(* com.qcc.beans.aop.*.*(..))")   
    public void beforeMethod(JoinPoint jp){        
        String methodName = jp.getSignature().getName();        
        System.out.println("【前置通知】the method 【" + methodName + "】 begins with " + Arrays.asList(jp.getArgs()));  
        }    
    
    
/**     * 返回通知:目标方法正常执行完毕时执行以下代码     * @param jp     * @param result     */  
    @AfterReturning(value="execution(* com.qcc.beans.aop.*.*(..))",returning="result") 
    public void afterReturningMethod(JoinPoint jp, Object result){        
        String methodName = jp.getSignature().getName();        
        System.out.println("【返回通知】the method 【" + methodName + "】 ends with 【" + result + "】");    
        }    
    
    
    /**     * 后置通知:目标方法执行之后执行以下方法体的内容,不管是否发生异常。     * @param jp     */    
    @After("execution(* com.qcc.beans.aop.*.*(..))")    
    public void afterMethod(JoinPoint jp){        
        System.out.println("【后置通知】this is a afterMethod advice...");   
        }     
    
    
    /**     * 异常通知:目标方法发生异常的时候执行以下代码     */   
    @AfterThrowing(value="execution(* com.qcc.beans.aop.*.*(..))",throwing="e")   
    public void afterThorwingMethod(JoinPoint jp, NullPointerException e){     
        String methodName = jp.getSignature().getName();    
        System.out.println("【异常通知】the method 【" + methodName + "】 occurs exception: " + e);  
        
    
    }


}

上面加粗的几个注解大家结合注释都应该明白什么意思,这些注解决定了方法在什么时间点被执行;

注解中的execution用来表示这个切面类中的该方法在哪里执行,也就是作用的目标,看到这里的同学,应该能明白,其实AOP简单的来说就是这么回事!

注意:这里的execution中我作用的路径是某个实现类下面的全部方法,还可以具体作用到某一个方法,详情请看:

看一下执行的结果:

java aop切面如何获取requestheader spring aop切面定义_连接点_05

因为没发生异常,所以异常通知没有打印!AOP原理的简单实现就这么简单,讲到这里再说一下spring事务的开启,提交或回滚,是不是也是AOP的前置-后置或者环绕的体现呢,哈哈!趁热打铁,咱们下一章讲一下关于AOP和spring事务的内容,会重点讲到aop代理机制、spring事务如何开启和同一个service两个方法调用事务失效的问题!!!
 

3、Advice通知类型介绍:

(1)Before:在目标方法被调用之前做增强处理,@Before只需要指定切入点表达式即可

(2)AfterReturning:在目标方法正常完成后做增强,@AfterReturning除了指定切入点表达式后,还可以指定一个返回值形参名returning,代表目标方法的返回值

(3)AfterThrowing:主要用来处理程序中未处理的异常,@AfterThrowing除了指定切入点表达式后,还可以指定一个throwing的返回值形参名,可以通过该形参名

来访问目标方法中所抛出的异常对象

(4)After:在目标方法完成之后做增强,无论目标方法时候成功完成。@After可以指定一个切入点表达式

(5)Around:环绕通知,在目标方法完成前后做增强处理,环绕通知是最重要的通知类型,像事务,日志等都是环绕通知,注意编程中核心是一个ProceedingJoinPoint

 

4、AOP使用场景:

Authentication 权限
Caching 缓存
Context passing 内容传递
Error handling 错误处理
Lazy loading 懒加载
Debugging  调试
logging, tracing, profiling and monitoring 记录跟踪 优化 校准
Performance optimization 性能优化
Persistence  持久化
Resource pooling 资源池
Synchronization 同步
Transactions 事务

二、关于jar包依赖:

首先,使用aop依赖包除了Spring提供给开发者的jar包外,还需额外上网下载两个jar包:
1、aopalliance.jar
2、aspectjweaver.jar
我用的是maven管理jar,具体如下:
pom.xml: