Spring4深入理解AOP02----AOP简介,AspectJ,AOP基于注解和XML配置(5种通知,切面优先级)

参考代码下载github:https://github.com/changwensir/java-ee/tree/master/spring4

一、AOP简介

    • AOP(Aspect-Oriented Programming, 面向切面编程 ): 是一种新的方法论 , 是对传统 OOP(Object-OrientedProgramming, 面向对象编程 ) 的补充 .
    • AOP 的主要编程对象是 切面 (aspect), 切面模块化横切关注点 .
    • 在应用 AOP 编程时 , 仍然 需要 定义 公共功能 , 但可以明确的定义这个功能在哪里 , 以什么方式应用 , 并且不必修改受影响的类 . 这样一来 横切关注点就被模块化到特殊的对象 ( 切面 ) .
    • AOP 的好处 :
      – 每个事物逻辑位于一个位置 , 代码不分散 , 便于维护和升级
     – 业务模块更简洁 , 只包含核心业务代码 .

AOP 术语

    • 切面 (Aspect):  横切关注点 ( 跨越应用程序多个模块的功能 ) 被模块化的特殊对象,把横切关注点的代码抽象到切面的类中
    • 通知 (Advice):  切面必须要完成的工作,就是功能对应的方法
    • 目标 (Target): 被通知的对象
    • 代理 (Proxy): 向目标对象应用通知之后创建的对象
    • 连接点( J oinpoint ): 程序 执行的某个特定位置 :如类某个方法调用前、调用后、方法抛出异常后等 连接点 由两个信息确定:方法表示的程序执行点;相对点表示的方位 。例如 ArithmethicCalculator#add () 方法执行 前的连接点,执行点为 ArithmethicCalculator#add () ;方位为该方法执行前的 位置
    • 切点( pointcut ): 每个 类都拥有多个连接点 :例如 ArithmethicCalculator 的所有方法实际上都是连接点,即 连接点是程序类中 客观存在 的事务 AOP 通过切点定位到特定的连接点 。类比 :连接点相当于数据库中的记录,切点相当于查询条件 。切点和连接点不是一对一的关系,一个切点匹配多个 连接点, 切点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的 查询条件

二、AspectJ

AspectJ Java 社区里最完整最流行的 AOP 框架 .
Spring2.0 以上版本 , 可以 使用基于 AspectJ 注解或基于 XML 配置的 AOP
1).在 Spring中启用AspectJ注解支持

    • 要在 Spring 应用中使用 AspectJ 注解 , 必须在 classpath 下包含 AspectJ 类库 : aopalliance.jar aspectj.weaver.jar spring-aspects.jar
    • aop Schema 添加到 <beans> 根元素中 .
    • SpringIOC 容器中启用 AspectJ 注解支持 , 只要 Bean 配置文件中定义一个空的 XML 元素 < aop:aspectj-autoproxy >
    • SpringIOC 容器侦测到 Bean 配置文件中的 < aop:aspectj-autoproxy > 元素时 , 会自动为与 AspectJ 切面匹配的 Bean 创建代理 .
2).用 AspectJ注解声明切面
    • 要在 Spring 中声明 AspectJ 切面 , 只需要在 IOC 容器中将切面声明为 Bean 实例 . 当在 SpringIOC 容器中初始化 AspectJ 切面之后 ,Spring IOC 容器就会为那些与 AspectJ 切面相匹配的 Bean 创建代理 .
    • AspectJ 注解中 , 切面只是一个带有 @Aspect 注解的 Java .
    • 通知是标注有某种注解的简单的 Java 方法 .
    • AspectJ 支持 5 种类型的通知注解 :
      – @Before: 前置通知 , 在方法执行之前执行
      – @After: 后置通知 , 在方法执行之后执行
      – @ AfterRunning : 返回通知 , 在方法返回结果之后 执行
      – @ AfterThrowing : 异常通知 , 在方法抛出异常之后
      – @Around: 环绕通知 , 围绕着方法执行
3).利用方法签名编写AspectJ切入点表达式
最典型的切入点表达式时根据方法的签名来匹配各种方法 :
execution * com.atguigu.spring.ArithmeticCalculator . * (..): 匹配 ArithmeticCalculator 中声明的所有方法 , 第一个 * 代表任意修饰符及任意返回值 . 第二个 * 代表任意方法 . .. 匹配任意数量的参数 . 若目标类与接口与该切面在同一个包中 , 可以省略包名 .
execution public * ArithmeticCalculator.*(..): 匹配 ArithmeticCalculator 接口的 所有公有方法 .
execution public double ArithmeticCalculator.*(..): 匹配 ArithmeticCalculator 返回 double 类型数值的方法
execution public double ArithmeticCalculator.*( double ,..): 匹配第一个参数为 double 类型的方法 ,.. 匹配任意数量任意类型的参数
execution public double ArithmeticCalculator.*( double , double ): 匹配参数类型为 double,double 类型的方法 .

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <context:component-scan base-package="Spring4_AOP.aopAnnotation"/>

    <!--使AspectJ注解起作用:自动为匹配的类生成代理对象-->
    <aop:aspectj-autoproxy/>
</beans>
ArithmeticCalculatorImpl在参见一篇博客 http://blog.csdn.net/ochangwen/article/details/52557459
所以的类都放个这个目录下Spring4_AOP.aopAnnotation

日志切面

* 4. 编写切面类(把横切关注点的代码抽象到切面的类中):
 * 4.1 一个一般的 Java 类
 * 4.2 在其中添加要额外实现的功能.
 *
 * 5. 配置切面
 * 5.1 切面必须是 IOC 中的 bean: 实际添加了 @Component 注解
 * 5.2 声明是一个切面: 添加 @Aspect
 * 5.3 声明通知: 即额外加入功能对应的方法!!.

@Aspect
@Component
public class LoggingAspect {
    /**
     * 重用切入点定义
     * 定义一个方法,用于声明切入点表达式,一般地,该方法中再不需要添入其它的代码
     */
    @Pointcut("execution(* Spring4_AOP.aopAnnotation.*.*(..))")
    public void declareJoinPointerExpression() {}

    //1、前置通知: 在目标方法开始之前执行(就是要告诉该方法要在哪个类哪个方法前执行)
    //@Before("execution(public int Spring4_AOP.aopAnnotation.*.*(int ,int))")
    @Before("declareJoinPointerExpression()")
    public void beforeMethod(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        Object [] args = joinPoint.getArgs();

        System.out.println("The method " + methodName + " begins with " + Arrays.asList(args));
    }

    //2、后置通知:在目标方法执行后(无论是否发生异常),执行的通知
    //注意,在后置通知中还不能访问目标执行的结果!!!,执行结果需要到返回通知里访问
    //@After("execution(* Spring4_AOP.aopAnnotation.*.*(..))")
    @After("declareJoinPointerExpression()")
    public void afterMethod(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("The method " + methodName + " ends");
    }

    //无论连接点是正常返回还是抛出异常, 后置通知都会执行. 如果只想在连接点返回的时候记录日志, 应使用返回通知代替后置通知.

    //3、返回通知:在方法正常结束后执行的代码,返回通知是可以访问到方法的返回值的!!!
    //@AfterReturning(pointcut = "execution(* Spring4_AOP.aopAnnotation.*.*(..))", returning = "result")
    @AfterReturning(value = "declareJoinPointerExpression()", returning = "result")
    public void afterReturning(JoinPoint joinPoint, Object result){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("The method " + methodName + " AfterReturning ends with " + result);
    }

    //4、异常通知:在目标方法出现异常 时会执行的代码,可以访问到异常对象:且可以!!指定在出现特定异常时在执行通知!!,如果是修改为nullPointerException里,只有空指针异常才会执行
//    @AfterThrowing(pointcut = "execution(* Spring4_AOP.aopAnnotation.*.*(..))", throwing = "except")
    @AfterThrowing(value = "declareJoinPointerExpression())", throwing = "except")
    public void afterThrowing(JoinPoint joinPoint, Exception except){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("The method " + methodName + "  occurs exception " + except);
    }

    /**
     * 5、环绕通知 需要携带 ProceedingJoinPoint 类型的参数.
     * 环绕通知类似于动态代理的全过程: ProceedingJoinPoint 类型的参数可以决定是否执行目标方法.
     * 且环绕通知必须有返回值, 返回值即为目标方法的返回值
     */
    //   @Around("execution(* Spring4_AOP.aopAnnotation.*.*(..))")
    @Around("declareJoinPointerExpression()")
    public Object aroundMethod(ProceedingJoinPoint pjd){

        Object result = null;
        String methodName = pjd.getSignature().getName();

        try {
            //前置通知
            System.out.println("The method " + methodName + " begins with " + Arrays.asList(pjd.getArgs()));
            //执行目标方法
            result = pjd.proceed();
            //返回通知
            System.out.println("The method " + methodName + " ends with " + result);
        } catch (Throwable e) {
            //异常通知
            System.out.println("The method " + methodName + " occurs exception:" + e);
            throw new RuntimeException(e);
        }
        //后置通知
        System.out.println("The method " + methodName + " ends");

        return result;
    }

}
注意:

AspectJ , 切入点表达式可以通过操作符 &&,||, ! 结合起来 .
@Pointcut("execution(* Spring4_AOP.aopAnnotation.*.add(..)) ||execution(* Spring4_AOP.aopAnnotation.*.sub(..))")
    public void declareJoinPointerExpression() {}
返回通知:• 无论连接点是正常返回还是抛出异常 , 后置通知都会执行 . 如果只想在连接点返回的时候记录日志 , 应使用返回通知代替后置通知 .
在返回通知中访问连接点的返回值
在返回通知中 , 只要将 returning 属性添加到 @ AfterReturning 注解中 , 就可以访问连接点的返回值 . 该属性的值即为用来传入返回值的参数名称 .
必须在通知方法的签名中添加一个 同名参数 . 在运行时 ,Spring AOP 会通过这个参数传递返回值 .
原始的 切点 表达式需要出现在 pointcut 属性中
2).异常通知

只在连接点抛出异常时才执行异常通知
throwing 属性添加到 @ AfterThrowing 注解中 , 也可以访问连接点抛出的异常 . Throwable 是所有错误和异常类的超类 . 所以在异常通知方法可以捕获到任何错误和异常 .
如果只对某种特殊的异常类型感兴趣 , 可以将参数声明为其他异常的参数类型 . 然后通知就只在抛出这个类型及其子类的异常时才被执行

3).环绕通知

环绕通知是所有通知类型中功能最为强大的 , 能够全面地控制连接点 . 甚至 可以控制是否执行连接点 .
对于环绕通知来说 , 连接点的参数类型必须是 ProceedingJoinPoint . 它是 JoinPoint 的子接口 , 允许控制何时执行 , 是否执行连接点 .
在环绕通知中需要明确调用 ProceedingJoinPoint proceed() 方法来执行被代理的方法 . 如果忘记这样做就会导致通知被执行了 , 但目标方法没有被执行 .
注意 : 环绕通知的方法需要返回目标方法执行之后的结果 , 即调用 joinPoint.proceed (); 的返回值 , 否则会出现空指针异常
    @Test
    public void testAOPAnnotation() {
        //1、创建Spring的IOC的容器
        ApplicationContext ctx = new ClassPathXmlApplicationContext("Spring4_AOP/applicationContext-aop.xml");

        //2、从IOC容器中获取bean的实例
        ArithmeticCalculator arithmeticCalculator = (ArithmeticCalculator) ctx.getBean("arithmeticCalculator");

        //3、使用bean
        int result = arithmeticCalculator.div(3, 1);
        System.out.println("result:" + result);
    }
2-1.指定切面的优先级

    • 在同一个连接点上应用不止一个切面时 , 除非明确指定 , 否则它们的优先级是不确定的 .
    • 切面的优先级可以通过实现 Ordered 接口或利用 @Order 注解指定 .
    • 实现 Ordered 接口 , getOrder () 方法的返回值越小 , 优先级越高,切面出的越在前面
    • 若使用 @Order 注解 , 序号出现在注解中
写一个验证切面

@Order(1)
@Aspect
@Component
public class ValidationAspect {
    @Before("execution(public int Spring4_AOP.aopAnnotation.*.*(int ,int))")
    public void validateArgs(JoinPoint joinPoint){
        System.out.println("-->validate:" + Arrays.asList(joinPoint.getArgs()));
    }
}
日志切面,使用上面的那个类,只增加Order
@Order(2)
@Aspect
@Component
public class LoggingAspect {
   ......
}

2-3.重用切入点定义

    • 在编写 AspectJ 切面时 , 可以直接在通知注解中书写切入点表达式 . 但同一个切点表达式可能会在多个通知中重复出现 .
    • AspectJ 切面中 , 可以 通过 @ Pointcut 注解将一个切入点声明成 简单的方法 . 切入点的方法体通常是空的 , 因为将切入点定义与应用程序逻辑混在一起是不合理的 .
    • 切入点方法的访问控制符同时也控制着这个切入点的可见性 . 如果切入点要在多个切面中共用 , 最好将它们集中在一个公共的类中 . 在这种情况下 , 它们必须被声明为 public. 在引入这个切入点时 , 必须将类名也包括在内 . 如果类没有与这个切面放在同一个包中 , 还必须包含包名 .
    • 其他通知可以通过方法名称引入该切入点 .

三 、用基于 XML 的配置声明切面

     • 除了使用 AspectJ 注解声明切面 ,Spring 也支持在 Bean 配置文件中声明切面 . 这种声明是通过 aop schema 中的 XML 元素完成的 .
     • 正常情况下 , 基于注解的声明要优先于基于 XML 的声明 . 通过 AspectJ 注解 , 切面可以与 AspectJ 兼容 , 而基于 XML 的配置则是 Spring 专有的 . 由于 AspectJ 得到越来越多的 AOP 框架支持 , 所以以注解风格编写的切面将会有更多重用的机会 .
1).基于 XML---- 声明切面

    • 当使用 XML 声明切面时 , 需要在 <beans> 根元素中导入 aop Schema
    • Bean 配置文件中 , 所有的 SpringAOP 配置都必须定义在 < aop:config > 元素内部 . 对于每个切面而言 , 都要创建一个 < aop:aspect > 元素来为具体的切面实现引用后端 Bean 实例 .
    • 切面 Bean 必须有一个标示符 , < aop:aspect > 元素引用
2).基于 XML---- 声明切入点

    • 切入点使用 < aop:pointcut > 元素声明
    • 切入点必须定义在 < aop:aspect > 元素下 , 或者直接定义在 < aop:config > 元素下 .
      – 定义在 < aop:aspect > 元素下 : 只对当前切面有效
      – 定义在 < aop:config > 元素下 : 对所有切面都有效
    • 基于 XML AOP 配置不允许在切入点表达式中用名称引用其他切入点 .
3).基于 XML---- 声明通知

    • aop Schema , 每种通知类型都对应一个特定的 XML 元素 .
    • 通知元素需要使用 < pointcut -ref> 来引用切入点 , 或用 < pointcut > 直接嵌入切入点表达式 method 属性指定切面类中通知方法的名称 .
4).声明引入

  • 可以利用 <aop:declare-parents> 元素在切面内部声明引入

ArithmeticCalulator2与上面ArithmeticCalulator接口内容 一样

public class ArithmeticCalculatorImplXML implements ArithmeticCalculator2 {
    public int add(int i, int j) {
        return i + j;
    }

    public int sub(int i, int j) {
        return i - j;
    }

    public int mul(int i, int j) {
        return i * j;
    }

    public int div(int i, int j) {
        return i / j;
    }
}
public class LoggingAspectXML {
    public void beforeMethod(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        Object [] args = joinPoint.getArgs();

        System.out.println("The method " + methodName + " begins with " + Arrays.asList(args));
    }

    public void afterMethod(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("The method " + methodName + " ends");
    }

    public void afterReturning(JoinPoint joinPoint, Object result){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("The method " + methodName + " ends with " + result);
    }

    public void afterThrowing(JoinPoint joinPoint, Exception e){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("The method " + methodName + " occurs excetion:" + e);
    }
}
public class ValidationAspectXML {

    public void validateArgs(JoinPoint joinPoint){
        System.out.println("-->validate:" + Arrays.asList(joinPoint.getArgs()));
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

       <!-- 配置 bean -->
       <bean id="arithmeticCalculatorXML" class="Spring4_AOP.aopXML.ArithmeticCalculatorImplXML">
       </bean>

       <!-- 配置切面的 bean. -->
       <bean id="loggingAspect" class="Spring4_AOP.aopXML.LoggingAspectXML">
       </bean>
       <bean id="validationAspect" class="Spring4_AOP.aopXML.ValidationAspectXML">
       </bean>

       <!-- 配置 AOP -->
       <aop:config>
              <!-- 配置切点表达式 -->
              <aop:pointcut id="pointcut"
                            expression="execution(* Spring4_AOP.aopXML.ArithmeticCalculatorImplXML.*(int ,int))"/>
              <!-- 配置切面及通知 -->
              <aop:aspect ref="loggingAspect" order="2">
                     <aop:before method="beforeMethod" pointcut-ref="pointcut"/>
                     <aop:after method="afterMethod" pointcut-ref="pointcut"/>
                     <aop:after-returning method="afterReturning" returning="result" pointcut-ref="pointcut"/>
                     <aop:after-throwing method="afterThrowing" throwing="e" pointcut-ref="pointcut"/>
              </aop:aspect>

              <aop:aspect ref="validationAspect" order="1">
                     <aop:before method="validateArgs" pointcut-ref="pointcut"/>
              </aop:aspect>
       </aop:config>
</beans>
    @Test
    public void testXML() {
        //1、创建Spring的IOC的容器
        ApplicationContext ctx = new ClassPathXmlApplicationContext("Spring4_AOP/applicationContext-xml.xml");

        //2、从IOC容器中获取bean的实例
        ArithmeticCalculatorImplXML arithmeticCalculator = (ArithmeticCalculatorImplXML) ctx.getBean("arithmeticCalculatorXML");

        //3、使用bean
        int result = arithmeticCalculator.add(3, 3);
        System.out.println("result:" + result);

        arithmeticCalculator.div(10, 0);
    }




  • 10
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值