博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Spring Aop的应用
阅读量:6502 次
发布时间:2019-06-24

本文共 11718 字,大约阅读时间需要 39 分钟。

hot3.png

AOP的基本概念

  • 连接点( Jointpoint) : 表示需要在程序中插入横切关注点的扩展点,连接点可能是类初始化、方法执行、 方法调用、字段调用或处理异常等等, Spring 只支持方法执行连接点, 在 AOP 中表示为“在哪里干” ;

  • 切入点( Pointcut) : 选择一组相关连接点的模式, 即可以认为连接点的集合,Spring 支持 perl5 正则表达式和 AspectJ 切入点模式, Spring 默认使用 AspectJ 语法, 在 AOP 中表示为“在哪里干的集合” ;

  • 通知( Advice) : 在连接点上执行的行为, 通知提供了在 AOP 中需要在切入点所选择的连接点处进行扩展现有行为的手段; 包括前置通知( before advice)、后置通知(after advice)、环绕通知( around advice), 在 Spring 中通过代理模式实现AOP,并通过拦截器模式以环绕连接点的拦截器链织入通知; 在 AOP 中表示为“干什么”;

  • 方面/切面( Aspect): 横切关注点的模块化,可以认为是通知、引入和切入点的组合; 在 Spring 中可以使用 Schema 和@AspectJ 方式进行组织实现; 在 AOP 中表示为“在哪干和干什么集合”

  • 引入( inter-type declaration) : 也称为内部类型声明, 为已有的类添加额外新的字段或方法, Spring 允许引入新的接口(必须对应一个实现)到所有被代理对象(目标对象) , 在 AOP 中表示为“干什么(引入什么) ” ;

  • 目标对象( Target Object) : 需要被织入横切关注点的对象,即该对象是切入点选择的对象,需要被通知的对象,从而也可称为“被通知对象”;由于 Spring AOP通过代理模式实现,从而这个对象永远是被代理对象, 在 AOP 中表示为“对谁干” ;

  • AOP 代理( AOP Proxy) : AOP 框架使用代理模式创建的对象,从而实现在连接点处插入通知(即应用切面) ,就是通过代理来对目标对象应用切面。在 Spring中, AOP 代理可以用 JDK 动态代理或 CGLIB 代理实现,而通过拦截器模型应用切面。

  • 织入( Weaving) : 织入是一个过程,是将切面应用到目标对象从而创建出 AOP代理对象的过程, 织入可以在编译期、类装载期、运行期进行。

Spring有哪些通知类型呢?

  • 前置通知( Before Advice) :在切入点选择的连接点处的方法之前执行的通知,该通知不影响正常程序执行流程(除非该通知抛出异常,该异常将中断当前方法链的执行而返回)。

  • 后置通知( After Advice) : 在切入点选择的连接点处的方法之后执行的通知,包括如下类型的后置通知:

    1. 后置返回通知( After returning Advice) :在切入点选择的连接点处的方法正常执行完毕时执行的通知, 必须是连接点处的方法没抛出任何异常正常返回时才调用后置通知。

    2. 后置异常通知( After throwing Advice) : 在切入点选择的连接点处的方法抛出异常返回时执行的通知, 必须是连接点处的方法抛出任何异常返回时才调用异常通知。

    3. 后置最终通知( After finally Advice) : 在切入点选择的连接点处的方法返回时执行的通知,不管抛没抛出异常都执行,类似于 Java 中的 finally 块。

  • 环绕通知( Around Advices): 环绕着在切入点选择的连接点处的方法所执行的通知,环绕通知可以在方法调用之前和之后自定义任何行为,并且可以决定是否执行连接点处的方法、替换返回值、抛出异常等等。

在 AOP 中,通过切入点选择目标对象的连接点,然后在目标对象的相应连接点处织入通知,而切入点和通知就是切面(横切关注点),而在目标对象连接点处应用切面的实现方式是通过 AOP 代理对象。

基于注解的AOP编程

基于注解的编程,需要依赖AspectJ框架(java中最流行的aop框架)。 第一步:导入AspectJ的jar包,该框架只有Spring 2.0以上才支持。

org.aspectj
aspectjrt
1.7.3
org.aspectj
aspectjweaver
1.7.3
runtime

第三步:切面类,该类有什么特点?首先它必须是IOC的bean,还要声明它是AspectJ切面,最后还可以定义切面的优先级Order(非必填)

通知有五种注解 :前置通知的注解,在目标方法执行前调用

@After:后置通知的注解, 在目标方法执行后调用,即使程序抛出异常都会调用

@AfterReturning:返回通知的注解, 在目标方法成功执行后调用,如果程序出错则不会调用

@AfterThrowing:异常通知的注解, 在目标方法出现指定异常时调用

@Around:环绕通知的注解,很强大(相当于前四个通知的组合),但用的不多。

环绕通知

import java.util.Arrays;import java.util.List;/** * @program: ssm * @description: 环绕通知 * @author: lee * @create: 2019-03-14 **/@Order(2)@Aspect@Componentpublic class AroundAspect {    /**     * 环绕通知,很强大,但用的不多。 用环绕通知测试Order的优先级看的不明显(这里是笔者的失误)     * 环绕通知需要用ProceedingJoinPoint 类型的参数     */    @Around("execution(* com.*.aspect.*.*(..))")    public Object aroundAdvice(ProceedingJoinPoint joinPoint) {        Object result = null;        String methodName = joinPoint.getSignature().getName();        List args = Arrays.asList(joinPoint.getArgs());        try {            System.out.println("@Around 前置通知 : 方法名 【 " + methodName + " 】and args are " + args);            result = joinPoint.proceed();            System.out.println("@Around 返回通知 : 方法名 【 " + methodName + " 】and args are " + args + " , result is " + result);        } catch (Throwable e) {            e.printStackTrace();            System.out.println("@Around 异常通知 : 方法名 【 " + methodName + " 】and  exception is " + e);        }        System.out.println("@Around 后置通知 : 方法名 【 " + methodName + " 】and args are " + args);        return result;    }}

拦截对象

package com.plantform.aspect;import org.springframework.stereotype.Component;/** * @program: ssm * @description: 切面类 * @author: lee * @create: 2019-03-14 **/public class AspectMethod {    public int add(int a, int b) {        System.out.println("add 方法执行了 ----> " + (a + b));        return (a + b);    }    public int division(int a, int b) {        System.out.println("division 方法执行了 ----> " + (a / b));        return (a / b);    }}

拦截通知

package com.plantform.aspect;import org.aspectj.lang.JoinPoint;import org.aspectj.lang.annotation.*;import org.springframework.core.annotation.Order;import org.springframework.stereotype.Component;import java.util.Arrays;import java.util.List;/** * @program: ssm * @Order(n) : 切面的优先级,n越小,级别越高 * @Aspect:声明该类是一个切面 * @Component:切面必须是 IOC 中的 bean * @author: lee * @create: 2019-03-14 **/@Order(1)@Aspect@Componentpublic class LogAspect {    /**     * 前置通知的注解,在目标方法执行前调用     * execution最基础的表达式语法。     * 注意点:     * 1. 方法里面不能有行参,及add(int a, int b) 这是会报错的。     * 2. int(方法的返回值),add(方法名) 可以用 * 抽象化。甚至可以将类名抽象,指定该包下的类。     * 3. (int, int) 可以用(..)代替,表示匹配任意数量的参数     * 4. 被通知的对象(Target),建议加上包的路径     */    @Before(value = "execution(* com.*.aspect.*.*(..))")    public void beforeAdvice(JoinPoint joinPoint) {        /**         * 连接点 joinPoint:add方法就是连接点         * getName获取的是方法名,是英文的,可以通过国际化转换对应的中文比较好。         */        String methodName = joinPoint.getSignature().getName();        List args = Arrays.asList(joinPoint.getArgs());        System.out.println("@Before 前置通知 : 方法名 【 " + methodName + " 】and args are " + args);    }    /**     * 后置通知的注解, 在目标方法执行后调用,即使是程序出错都会调用     * 这里将 方法的返回值 和 CalculatorImp类下所有的方法,以及方法的形参 都抽象了     */    @After(value = "execution(* com.*.aspect.*.*(..))")    public void afterAdvice(JoinPoint joinPoint) {        String methodName = joinPoint.getSignature().getName();        List args = Arrays.asList(joinPoint.getArgs());        System.out.println("@After 后置通知 : 方法名 【 " + methodName + " 】and args are " + args);    }    /**     * 重用切入点定义:声明切入点表达式。该方法里面不建议添加其他代码     */    @Pointcut("execution(* com.*.aspect.*.*(..))")    public void declareExecutionExpression(){}    /**     * 返回通知的注解, 在目标方法成功执行后调用,如果程序出错则不会调用     * returning="result" 和 形参 result 保持一致     */    @AfterReturning(value="declareExecutionExpression()", returning="result")    public void afterRunningAdvice(JoinPoint joinPoint, Object result) {        String methodName = joinPoint.getSignature().getName();        List args = Arrays.asList(joinPoint.getArgs());        System.out.println("@AfterReturning 返回通知 : 方法名 【 " + methodName + " 】and args are " + args + " , result is " + result);    }    /**     * 异常通知的注解, 在目标方法出现指定异常时调用     * throwing="exception" 和 形参 exception 保持一致 , 且目标方法出了Exception(可以是其他异常)异常才会调用。     */    @AfterThrowing(value="declareExecutionExpression()", throwing="exception")    public void afterThrowingAdvice(JoinPoint joinPoint, Exception exception) {        String methodName = joinPoint.getSignature().getName();        System.out.println("@AfterThrowing 异常通知 : 方法名 【 " + methodName + " 】and  exception is " + exception);    }}

运行方法

/** * @program: ssm * @description: * @author: lee * @create: 2019-03-14 **/@Controllerpublic class MainAspect {    @RequestMapping("/static/index")    @ResponseBody    public String main(String[] args) {        AspectMethod aspectMethod=new AspectMethod();        aspectMethod.add(11, 12);        aspectMethod.division(21, 3);        //aspectMethod.division(21, 0);        return "执行完了";    }}

输出结果

@Before 前置通知 : 方法名 【 main 】and args are [null]@Around 前置通知 : 方法名 【 main 】and args are [null]add 方法执行了 ----> 23division 方法执行了 ----> 7@Around 返回通知 : 方法名 【 main 】and args are [null] , result is 执行成功@Around 后置通知 : 方法名 【 main 】and args are [null]@After 后置通知 : 方法名 【 main 】and args are [null]@AfterReturning 返回通知 : 方法名 【 main 】and args are [null] , result is 执行成功

基于xml的AOP编程

第一步:核心文件applicationContext.xml, 首先是配置三个bean,方便是两个切面类,和一个方法类。 然后配置AOP, aop:config:注明开始配置AOP

aop:pointcut:配置切点重用表达式,expression的值是具体的表达式,id 该aop:pointcut的唯一标识,

aop:aspect:配置切面,ref的值引用相关切面类的bean,order设置优先级(也可以不设置)。

五种通知的配置:aop:before,aop:after,aop:after-returning,aop:after-throwing,aop:around。method的值就是对应的方法,poincut-ref的值要引用 aop:pointcut 的id。其中有两个比较特殊:aop:after-returning 要多配置一个returning,其中returning的值要和对应方法的形参保持一致。同理aop:after-throwing 也要多配置一个throwing,其中throwing的值也要和对应方法的形参保持一致。不然执行程序会报错。

下面几个类,就是脱去了所有注解的外衣,采用通过配置的xml,实现AOP编程。

public interface Calculator {            public int add(int a, int b);      public int division(int a, int b);    }  public class CalculatorImp implements Calculator {        @Override      public int add(int a, int b) {          System.out.println("add 方法执行了 ----> " + (a + b));          return (a + b);      }        @Override      public int division(int a, int b) {          System.out.println("division 方法执行了 ----> " + (a / b));          return (a / b);      }    }  import java.util.Arrays;  import java.util.List;  import org.aspectj.lang.JoinPoint;    public class LoggerAspect {            public void beforeAdvice(JoinPoint joinPoint) {          String methodName = joinPoint.getSignature().getName();           List args = Arrays.asList(joinPoint.getArgs());          System.out.println("Before 前置通知 : 方法名 【 " + methodName + " 】and args are " + args);      }            public void afterAdvice(JoinPoint joinPoint) {          String methodName = joinPoint.getSignature().getName();           List args = Arrays.asList(joinPoint.getArgs());          System.out.println("After 后置通知 : 方法名 【 " + methodName + " 】and args are " + args);      }            public void afterRunningAdvice(JoinPoint joinPoint, Object result) {          String methodName = joinPoint.getSignature().getName();           List args = Arrays.asList(joinPoint.getArgs());          System.out.println("AfterReturning 返回通知 : 方法名 【 " + methodName + " 】and args are " + args + " , result is " + result);      }            public void afterThrowingAdvice(JoinPoint joinPoint, Exception exception) {          String methodName = joinPoint.getSignature().getName();           System.out.println("AfterThrowing 异常通知 : 方法名 【 " + methodName + " 】and  exception is " + exception);      }    }  import java.util.Arrays;  import java.util.List;  import org.aspectj.lang.ProceedingJoinPoint;    public class AroundAspect {        public Object aroundAdvice(ProceedingJoinPoint joinPoint) {          Object result = null;          String methodName = joinPoint.getSignature().getName();           List args = Arrays.asList(joinPoint.getArgs());          try {              System.out.println("@Around 前置通知 : 方法名 【 " + methodName + " 】and args are " + args);              result = joinPoint.proceed();              System.out.println("@Around 返回通知 : 方法名 【 " + methodName + " 】and args are " + args + " , result is " + result);          } catch (Throwable e) {              e.printStackTrace();              System.out.println("@Around 异常通知 : 方法名 【 " + methodName + " 】and  exception is " + e);          }          System.out.println("@Around 后置通知 : 方法名 【 " + methodName + " 】and args are " + args);                    return result;      }        }  import org.springframework.context.support.ClassPathXmlApplicationContext;    public class Main {            public static void main(String[] args) {                    ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");          Calculator calculator = (Calculator) ctx.getBean("calculator");                    calculator.add(11, 12);          calculator.division(21, 3); // 测试时,将被除数换成0,可以测试AfterReturning ,After 和 AfterThrowing                    ctx.close();      }    }

输出结果

Before 前置通知 : 方法名 【 add 】and args are [11, 12]  add 方法执行了 ----> 23  After 后置通知 : 方法名 【 add 】and args are [11, 12]  AfterReturning 返回通知 : 方法名 【 add 】and args are [11, 12] , result is 23  Before 前置通知 : 方法名 【 division 】and args are [21, 3]  division 方法执行了 ----> 7  After 后置通知 : 方法名 【 division 】and args are [21, 3]  AfterReturning 返回通知 : 方法名 【 division 】and args are [21, 3] , result is 7

转载于:https://my.oschina.net/jiansin/blog/3022375

你可能感兴趣的文章
Lua(Codea) 中 table.insert 越界错误原因分析
查看>>
ELK 5.x日志分析 (二) Elasticserach 5.2 安装
查看>>
sbt配置nexus仓库
查看>>
一次奇怪的AP注册异常问题处理
查看>>
TableStore: 海量结构化数据分层存储方案
查看>>
Unity 4.x游戏开发技巧集锦(内部资料)
查看>>
自适应网页设计
查看>>
获取BT节点信息bittorrent-discovery
查看>>
Centos 7使用vsftpd搭建FTP服务器
查看>>
linux下SVN不允许空白日志提交
查看>>
第2周第1课
查看>>
docker制作镜像篇(基于容器)
查看>>
山寨c 标准库中的getline 函数
查看>>
shell时间
查看>>
pfSense book之2.4安装指南
查看>>
org.springframework.data.redis 一次连接获取特定key所有k-v(pipeline)
查看>>
[译稿]同步复制提议 2010-09
查看>>
windows 自动化目录大纲(各企业架构不一样,按需选择)
查看>>
我的友情链接
查看>>
【Visual C++】游戏开发笔记十三 游戏输入消息处理(二) 鼠标消息处理
查看>>