【Spring AOP】知识点详细介绍

AOP


简介

AOP(Aspect Oriented Programming):面向切面编程

OOP(Object Oriented Programming):面向对象编程

面向切面编程:基于OOP基础之上新的编程思想;指在运行期间,将某段代码动态的切入指定方法指定位置进行运行的这种编程方式


动态代理

可以使用动态代理 在目标方法执行前后进行执行

  • 缺点
    • jdk默认的动态代理,如果目标对象没有实现任何的接口,是无法为他创建代理对象的
    • 实现起来比较繁琐
//这是帮Calculator生成的代理对象类
//getProxy(Calculator calculator) ---calculator:被代理的对象
public class CalculatorProxy{
    public static Calculator getProxy(final Calculator calculator){
        //loader:类加载器
        ClassLoader loader = calculator.getClass().getClassLoader();
        //类所实现的所有接口
        Class<?>[] interfaces = calculator.getClass().getInterfaces();
        //方法执行器,帮我们目标对象执行目标方法
        InvocationHandler h = new InvocationHandler(){
            //Object proxy:代理对象,不要动这个对象
            //Method method:当前将要执行的目标对象的方法
            //Object[] args:方法调用时外界传入的参数值
            
            @Override
            public Object invoke(Object proxy,Method method,Object[] args)
                throws Throwable{
                //利用反射执行目标方法
                //result:目标方法执行后的返回值
                System.out.println("方法执行前,我被输出了")
                Object result = method.invoke(calculator,args);
                System.out.println("方法执行后,我被输出了")
                //返回值必须返回出去,外界才能拿到真正执行后的返回值
                return result;
            }
        };
        
        
        //Proxy为目标对象创建代理对象
        Object proxy = Proxy.newProxyInstance(loader,interfaces,h);
        return (Calculator)proxy;
    }
}

注意!

综上缺点,spring实现了AOP功能,底层就是动态代理,实现简单,而且没有强制要求目标对象必须实现接口


AOP专业术语

连接点(Join point):

  • 能够被拦截的地方:Spring AOP是基于动态代理的,所以是方法拦截的。每个成员方法都可以称之为连接点~

切点(Poincut):

  • 具体定位的连接点:上面也说了,每个方法都可以称之为连接点,我们具体定位到某一个方法就成为切点

增强/通知(Advice):

  • 表示添加到切点的一段逻辑代码,并定位连接点的方位信息。
    • 简单来说就定义了是干什么的,具体是在哪干
    • Spring AOP提供了5种Advice类型给我们:前置、后置、返回、异常、环绕给我们使用!

织入(Weaving):

  • 增强/通知添加到目标类的具体连接点上的过程。

引入/引介(Introduction):

  • 引入/引介允许我们向现有的类添加新方法或属性。是一种特殊的增强!

切面(Aspect):

  • 切面由切点和增强/通知组成,它既包括了横切逻辑的定义、也包括了连接点的定义。

AOP使用场景

  • AOP加日志保存到数据库
  • AOP做权限验证
  • AOP做安全检查
  • AOP做事务控制

基于注解的AOP配置

使用步骤

  1. 步骤一:导包
  2. 步骤二:配置
  3. 步骤三:测试
  • Spring支持面向切面编程的包

    基础版

    • spring-aspects-4.0.0

      加强版

    • com.springsource.net.sf.cglib-2.2.0

    • com.springsource.org.aspectj.weaver-1.6.8

    • com.springsource.org.aopalliance-1.0.0

  • 配置

    1. 将目标类和切面类(封装了通知方法的类)加入到ioc容器中

    2. 告诉spring哪一个是切面类

      • 在切面类上面加上 @Aspect 注解
    3. 告诉spring,切面类里面的方法是 何时何地 运行

      5个通知注解

      • 在目标方法执行之前执行:@Before(前置通知)

      • 在目标方法正常返回之后执行:@AfterReturning(返回通知)

      • 在目标方法出现异常的时候执行:@AfterThrowing(异常通知)

      • 在目标方法结束的时候执行:@After(后置通知)

      • @Around(环绕通知)
        切入点表达式——execution(访问权限符 返回值类型 方法签名)

      • eg:@Before("execution(public int com.Calculator.add(int,int))")

    4. 开启AOP注解的配置

      <!--首先需要导入aop命名空间-->
      <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
      
  • 测试

    • 第一种情况:如果目标对象有实现的接口,容器中保存的是jdk为我们创建的代理类

      注意,如果目标对象有实现的接口,从IOC容器中获取到目标对象的时候,如果想要用类型,一定要用他的接口类型,不要用它本类

      Calculator的接口 bean = ioc.getBean(Calculator的接口.class);
      //或者
      Calculator的接口 bean2 = (Calculator的接口)ioc.getBean(Calculator.class);
      
    • 第二种情况:如果目标对象没有实现接口,容器中保存的是cglib为我们创建的代理类,这时候按照类型获取bean的时候就可以使用本类类型

      Calculator bean = ioc.getBean(Calculator.class);
      

切入点表达式写法(通配符)

固定格式:execution(访问修饰符 返回值类型 方法全类名(参数表))

通配符
*:匹配一个或多个字符
eg:execution(public int com.My. (int, ))——以My开头的类并且第一个参数是int类型,第二个参数是任意类型的所有 方法
注意!访问修饰符位置不能使用 “ * ”表示任意
访问修饰符位置不写就表示任意,但是也只能匹配public的,其他访问修饰符不能匹配
.. :匹配任意多个参数,任意类型参数
eg:
execution(public int com.My. (..))*——以My开头的类的所有方法(参数类型不限制)

  • 最模糊的匹配规则: execution( (..))
    第一个*表示任意返回值,第二个 *表示任意包任意类任意方法
    或者 execution( . (..))
    第一个
    表示任意返回值,第二个 *表示任意包任意类,第三个 *表示任意方法

  • 最精确的匹配规则execution(public int com.Calculator.add(int,int))

  • 切入表达式之间 可以使用“&&”“||”“!”

    • “&&”:同时满足
    • “||”:满足其中一个即可
    • “!”:非

通知方法的执行顺序

try{
    @Before
    method.invoke(obj,args);
    @AfterReturning
}catch(){
    @AfterThrowing
}finally{
    @After
}
  • 正常执行
    • @Before(前置通知)——@After(后置通知)——@AfterReturning(返回通知)
  • 异常执行
    • @Before(前置通知)——@After(后置通知)——@AfterThrowing(异常通知)

JoinPoint获取目标方法的信息

我们可以在通知方法运行的时候,拿到目标方法的详细信息

  1. 只需要为通知方法的参数列表上写上一个参数
@Before("execution(public int com.Calculator.add(int,int))")
public static void  log(JoinPoint joinPoint){
    //获取到目标方法运行时使用的参数列表
    joinPoint.getArgs();
    //获取到目标方法的签名
    Signature signature = joinPoint.getSignature();
        //获取到目标方法的方法名
        signature.getName();
        //
}
  1. 接受目标方法抛出的异常和返回的值

    通知注解中加入 returning 指定哪个参数接受目标方法的返回值

    通知注解中加入 throwing指定哪个参数接受目标方法抛出的异常

@Before(value="execution(public int com.Calculator.add(int,int))",
       returning="result",
       throwing="exception")
public static void log(JoinPoint joinPoint,Object result,Exception exception){
    //获取到目标方法运行时使用的参数列表
    joinPoint.getArgs();
    //获取到目标方法的签名
    Signature signature = joinPoint.getSignature();
        //获取到目标方法的方法名
        signature.getName();
        //
}

注意!通知方法唯一的要求就是通知方法的参数列表一定不能乱写,因为通知方法是spring利用反射调用的,每次调用方法需要确定这个方法的参数表的值,所以参数表上的每一个参数,spring都要知道是什么


环绕通知@Around(以及通知的执行顺序)

  • @Around:环绕通知是spring中强大的通知
try{
    @Before
    method.invoke(obj,args);
    @AfterReturning
}catch(){
    @AfterThrowing
}finally{
    @After
}
  • @Before、@AfterReturning、@AfterThrowing、@After四合一通知就是环绕通知
  • 环绕通知
@Around("execution(public int com.Calculator.add(int,int))")
public Object myAround(ProceedingJoinPoint pjp){
    //就是利用反射调用目标方法,就是 method.invoke(obj,args)
    Object[] args = pjp.proceed();
    try{
        System.out.println("前置通知")
        //方法的返回值
        Object proceed = pjp.proceed(args)
        System.out.println("返回通知")
    }catch(){
        System.out.println("异常通知")
    }finally{
        System.out.println("后置通知")
    }
    
    //方法的返回值也一定要返回出去
    return proceed;
}
  • 注意!环绕通知中的代码是优先进行,它优先于普通通知执行

  • 执行顺序

    1. 【环绕通知前置】

    2. 【普通通知前置】

    3. 目标方法执行

    4. 【环绕通知返回/环绕通知异常】

    5. 【环绕后置】

    6. 【普通通知后置】

    7. 【普通通知返回/普通通知异常】

    • 注意!【环绕通知前置】和【普通通知前置】这两个执行顺序随机

重用抽取切入表达式

  1. 随便声明一个没有实现的返回void的空方法
  2. 给方法上标注一个@Pointcut注解
@Pointcut("execution(public int com.Calculator.add(int,int))")
public void MyPointCut(){
    
}
//别的方法如果想使用
@Before("MyPointCut()")
public void log(){
    ...
}


多切面运行顺序

假设有两个切面类 A和B

@Order:指定切面进入顺序,数值越小,优先级越大;如果不设置默认按照类名首字母顺序先后进入

@Component
@Aspect
@Order(1)
public class A {
    ...(A切面通知方法)
}

@Component
@Aspect
@Order(2)
public class B {
    ...(B切面通知方法)
}
多切面通知.png
  • 第一种情况:两个切面都没有环绕通知

    • 执行顺序为
      1. 【A切面普通前置通知】
      2. 【B切面普通前置通知】
      3. 目标方法执行
      4. 【B切面普通后置通知】
      5. 【B切面普通异常通知/B切面普通返回通知】
      6. 【A切面普通后置通知】
      7. 【A切面普通异常通知/A切面普通返回通知】

    注意!先进后出

  • 第二种情况:两个切面中A切面有环绕通知

    • 执行顺序为
      1. 【A切面环绕前置通知】
      2. 【A切面普通前置通知】
      3. 【B切面普通前置通知】
      4. 目标方法执行
      5. 【B切面普通后置通知】
      6. 【B切面普通异常通知/B切面普通返回通知】
      7. 【A切面环绕异常通知/A切面环绕返回通知】
      8. 【A切面环绕后置】
      9. 【A切面后置通知】
      10. 【A切面异常通知/A切面返回通知】

    注意!环绕只是影响当前切面,并不会影响其他切面


基于配置文件的AOP配置

配置步骤

  1. 步骤一:在ioc容器中注册切面类
<bean id="A" class="com.component.A"></bean>
<bean id="B" class="com.component.B"></bean>
  1. 步骤二:在容器中指定哪个bean是切面类

    需要引入aop命名空间

<aop:config>
    <aop:aspect ref="A"></aop:aspect>
    <aop:aspect ref="B"></aop:aspect>
</aop:config>
  1. 步骤三:指明切面类中的通知方法都何时何地运行
<aop:config>
    <aop:pointcut expression="execution(* com.component.*(..))" id="mypoint"/>
    <aop:aspect ref="A">
        <aop:before method="logBefore" pointcut="execution(* com.component.*(..))"/>
        <aop:after method="logAfter" pointcut="mypoint"/>
        <aop:after-returning method="logAfterReturning" pointcut-ref="mypoint" returning="result"/>
        <aop:after-throwing method="logException" pointcut-ref="mypoint" throwing="exception"/>     
    </aop:aspect>
    <aop:aspect ref="B"></aop:aspect>
</aop:config>

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,907评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,987评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,298评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,586评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,633评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,488评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,275评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,176评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,619评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,819评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,932评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,655评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,265评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,871评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,994评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,095评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,884评论 2 354

推荐阅读更多精彩内容