• Spring AOP(面向切面编程)


    一、AOP简介

      1.AOP概念:Aspect Oriented Programming 面向切面编程

      2.作用:本质上来说是一种简化代码的方式

          继承机制

          封装方法

          动态代理

          ……

      3.情景举例

        ①数学计算器接口[MathCalculator]

          int add(int i,int j);

          int sub(int i,int j);

          int mul(int i, int j);

          int div(int i,int j);

        ②提供简单实现[EasyImpl]

        ③在简单实现的基础上让每一个计算方法都能够打印日志[LoginImpl]

        ④缺陷

          [1]手动添加日志繁琐,重复

          [2]统一修改不便

          [3]对目标方法本来要实现的核心功能有干扰,使程序代码很臃肿,不易于开发维护

        ⑤使用动态代理实现

          [1]创建一个类,让这个类能够提供一个目标对象的代理对象

          [2]在代理对象中打印日志

      4.AOP术语!

        AOP概述

          ●AOP(Aspect-Oriented Programming,面向切面编程):是一种新的方法论,是对传统 OOP(Object-Oriented Programming,面向对象编程)的补充。

          ●Spring的AOP既可以使用xml配置的方式实现,也可以使用注解的方式来实现!

        4.1          横切关注点

            从每个方法中抽取出来的同一类非核心业务。(抽离到方法中处理非核心业务)

        4.2          切面(Aspect)

            封装横切关注点信息的类,每个关注点体现为一个通知方法。

        4.3          通知(Advice)

            切面必须要完成的各个具体工作

        4.4          目标(Target)

            被通知的对象

        4.5          代理(Proxy)

            向目标对象应用通知之后创建的代理对象

        4.6          连接点(Joinpoint)

            横切关注点在程序代码中的具体体现,对应程序执行的某个特定位置。例如:类某个方法调用前、调用后、方法捕获到异常后等。

            在应用程序中可以使用横纵两个坐标来定位一个具体的连接点:

        4.7          切入点(pointcut):

            定位连接点的方式。每个类的方法中都包含多个连接点,所以连接点是类中客观存在的事物。

            如果把连接点看作数据库中的记录,那么切入点就是查询条件——AOP可以通过切入点定位到特定的连接点。

            切点通过org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。

      5.在Spring中使用AOP实现日志功能

        ①Spring中可以使用注解或XML文件配置的方式实现AOP。

        ②导入jar包

    com.springsource.net.sf.cglib -2.2.0.jar
    com.springsource.org.aopalliance-1.0.0 .jar
    com.springsource.org.aspectj.weaver-1.6.8 .RELEASE.jar
    commons-logging-1.1.3. jar
    spring-aop-4.0.0.RELEASE.jar
    spring-aspects-4.0.0.RELEASE.jar
    spring-beans-4.0.0.RELEASE.jar
    spring-context-4.0.0.RELEASE.jar
    spring-core-4.0.0.RELEASE.jar
    spring-expression-4.0.0.RELEASE. jar

        ③开启基于注解的AOP功能

            < aop:aspectj-autoproxy />

        ④声明一个切面类,并把这个切面类加入到IOC容器中

          @Aspect//表示这是一个切面类

          @Component//加入IOC容器

          public class LogAspect {}

        ⑤在切面类中声明通知方法

          [1]前置通知:@Before

          [2]返回通知:@AfterReturning

          [3]异常通知:@AfterThrowing

          [4]后置通知:@After

          [5]环绕通知:@Around :环绕通知是前面四个通知的集合体!
            

     1         @Aspect//表示这是一个切面类
     2         @Component//将本类对象加入到IOC容器中!
     3         public class LogAspect {
     4           @Before(value="execution(public int com.neuedu.aop.target.MathCalculatorImpl.add(int, int))")
     5           public void showBeginLog(){
     6             System.out.println("AOP日志开始");
     7           }
     8           @After(value="execution(public int com.neuedu.aop.target.MathCalculatorImpl.add(int, int))")
     9           public void showReturnLog(){
    10             System.out.println("AOP方法返回");
    11           }
    12           @AfterThrowing(value="execution(public int com.neuedu.aop.target.MathCalculatorImpl.add(int, int))")
    13           public void showExceptionLog(){
    14             System.out.println("AOP方法异常");
    15           }
    16           @AfterReturning(value="execution(public int com.neuedu.aop.target.MathCalculatorImpl.add(int, int))")
    17           public void showAfterLog(){
    18             System.out.println("AOP方法结束");
    19           }
    20         }

        ⑥被代理的对象也需要加入IOC容器

          @Component//加入IOC容器

     1           public class MathCalculatorImpl {
     2             public int add(int i,int j){
     3               int result = i+j;
     4               return result;
     5             }
     6             public int sub(int i,int j){
     7               int result = i-j;
     8               return result;
     9             }
    10             public int multi(int i,int j){
    11               int result = i*j;
    12               return result;
    13             }
    14             public int divide(int i,int j){
    15               int result = i/j;
    16               return result;
    17             }
    18           }

    二、切入点表达式:

      1.上述案例通过junit测试,会发现,我们调用目标类的四个方法只有add方法被加入了4个通知,

        如果想所有的方法都加上这些通知,可以在切入点表达式处,将execution(public int com.neuedu.aop.target.MathCalculatorImpl.add(int, int)) 换成:

          execution(public int com.neuedu.aop.target.MathCalculatorImpl.*(int, int))这样只要是有两个参数,且参数类型为int的方法在执行的时候都会执行其相应的通知方法!

      2.①切入点表达式的语法格式

          execution([权限修饰符] [返回值类型] [简单类名/全类名] [方法名]([参数列表]))

          2.1          作用

            通过表达式的方式定位一个或多个具体的连接点。

          2.2          语法细节

            ①切入点表达式的语法格式

    execution([权限修饰符] [返回值类型] [简单类名/全类名] [方法名]([参数列表]))

            ②举例说明

    表达式

    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类型的方法

     

            ③在AspectJ中,切入点表达式可以通过 “&&”、“||”、“!”等操作符结合起来。

    表达式

    execution (* *.add(int,..)) || execution(* *.sub(int,..))

    含义

    任意类中第一个参数为int类型的add方法或sub方法


      execution(*  *.*(..))

      1.任意参数,任意类型

      2.任意返回值

      3.用@PointCut注解统一声明,然后在其它通知中引用该统一声明即可!

        需要注意的是:权限是不支持写通配符的,当然你可以写一个*表示所有权限所有返回值!

      4.最详细的切入点表达式:
        execution(public int com.neuedu.aop.target.MathCalculatorImpl.add(int, int))
      5.最模糊的切入点表达式:
        execution (* *.*(..))

      6..统一声明切入点表达式
        @Pointcut(value= "execution(public int com.atguigu.aop.target.EazyImpl.add(int,int))")
        public void myPointCut(){}

      3、通知方法的细节

        ①在通知中获取目标方法的方法名和参数列表

          [1]在通知方法中声明一个JoinPoint类型的形参

          [2]调用JoinPoint对象的getSignature()方法获取目标方法的签名

          [3]调用JoinPoint对象的getArgs()方法获取目标方法的实际参数列表

        ②在返回通知中获取方法的返回值

          [1]在@AfterReturning注解中添加returning属性

            @AfterReturning (value="myPointCut()", returning= "result")

          [2]在返回通知的通知方法中声明一个形参,形参名和returning属性的值一致

            showReturnLog(JoinPoint joinPoint, Object result)

        ③在异常通知中获取异常对象

          [1]在@ AfterThrowing注解中添加throwing属性

            @AfterThrowing (value="myPointCut()",throwing= "throwable" )

          [2]在异常通知的通知方法中声明一个形参,形参名和throwing属性值一致

            showExceptinLog(JoinPoint joinPoint, Throwable throwable)

      根据接口类型获取target对象时,实际上真正放在IOC容器中的对象是代理对象,而并不是目标对象本身!

      4、.环绕通知:@Around

        4.1.环绕通知需要在方法的参数中指定JoinPoint的子接口类型ProceedingJoinPoint为参数

          @Around(value="pointCut()")

          public void around(ProceedingJoinPoint joinPoint){

          }
        4.2.环绕通知会将其他4个通知能干的,自己都给干了!

          注意:@Around修饰的方法一定要将方法的返回值返回!本身相当于代理!

     1         @Around(value="pointCut()")
     2         public Object around(ProceedingJoinPoint joinPoint){
     3           Object[] args = joinPoint.getArgs();
     4           Signature signature = joinPoint.getSignature();
     5           String methodName = signature.getName();
     6           List<Object> list = Arrays.asList(args);
     7           Object result = null;
     8           try {
     9             //目标方法之前要执行的操作
    10             System.out.println("[环绕日志]"+methodName+"开始了,参数为:"+list);
    11             //调用目标方法
    12             result = joinPoint.proceed(args);
    13             //目标方法正常执行之后的操作
    14             System.out.println("[环绕日志]"+methodName+"返回了,返回值为:"+result);
    15           } catch (Throwable e) {
    16 
    17             //目标方法抛出异常信息之后的操作
    18             System.out.println("[环绕日志]"+methodName+"出异常了,异常对象为:"+e);
    19             throw new RuntimeException(e.getMessage());
    20           }finally{
    21             //方法最终结束时执行的操作!
    22             System.out.println("[环绕日志]"+methodName+"结束了!");
    23           }
    24           return result;
    25         }

    三、切面的优先级

        对于同一个代理对象,可j以同时有多个切面共同对它进行代理。

        可以在切面类上通过@Order (value=50)注解来进行设置,值越小优先级越高!

     1    @Aspect
     2    @Component
     3    @Order(value=40)
     4    public class TxAspect {
     5 
     6     @Around(value="execution(public * com.neuedu.aop.target.MathCalculatorImpl.*(..))")
     7     public Object around(ProceedingJoinPoint joinPoint){
     8       Object[] args = joinPoint.getArgs();
     9       Signature signature = joinPoint.getSignature();
    10       String methodName = signature.getName();
    11       List<Object> list = Arrays.asList(args);
    12       Object result = null;
    13       try {
    14         //目标方法之前要执行的操作
    15         System.out.println("[事务日志]"+methodName+"开始了,参数为:"+list);
    16         //调用目标方法
    17         result = joinPoint.proceed(args);
    18 
    19         //目标方法正常执行之后的操作
    20         System.out.println("[事务日志]"+methodName+"返回了,返回值为:"+result);
    21       } catch (Throwable e) {
    22         //目标方法抛出异常信息之后的操作
    23         System.out.println("[事务日志]"+methodName+"出异常了,异常对象为:"+e);
    24         throw new RuntimeException(e.getMessage());
    25       }finally{
    26         //方法最终结束时执行的操作!
    27         System.out.println("[事务日志]"+methodName+"结束了!");
    28       }
    29       return result;
    30     }
    31 
    32   }

    四、通过配置方式实现AOP

      1、注意:上面的AOP都是通过注解实现的,AOP实际上也可以通过xml配置的方式实现!

        ①<!-- 1.将需要加载到IOC容器中的bean配置好 -->

          <bean id="logAspect" class="com.neuedu.aop.proxy.LogAspect"></bean>

          <bean id="txAspect" class="com.neuedu.aop.target.TxAspect"></bean>

          <bean id="calculator" class="com.neuedu.aop.target.MathCalculatorImpl"></bean>

        ②<!-- 2.配置AOP,需要导入AOP名称空间 -->

          <aop:config>

        ③<!-- 声明切入点表达式 -->

            <aop:pointcut expression="execution(* com.neuedu.aop.target.MathCalculatorImpl.*(..))" id="myPointCut"/>

        ④<!-- 配置日志切面类,引用前面的类 ,通过order属性控制优先级-->

            <aop:aspect ref="logAspect" order="25">

        ⑤<!-- 通过method属性指定切面类的切面方法,通过pointcut-ref指定切入点表达式 -->

              <aop:before method="showBeginLog" pointcut-ref="myPointCut"/>

              <aop:after method="showAfterLog" pointcut-ref="myPointCut"/>

              <aop:after-throwing method="showExceptionLog" pointcut-ref="myPointCut" throwing="ex"/>

              <aop:after-returning method="showReturnLog" pointcut-ref="myPointCut" returning="result"/>

              <aop:around method="around" pointcut-ref="myPointCut"/>

            </aop:aspect>

        ⑥<!-- 配置事务切面类,引用前面的类 -->

            <aop:aspect ref="txAspect" order="20">

              <aop:around method="around" pointcut-ref="myPointCut"/>

            </aop:aspect>

          </aop:config>

      需要知道的是:事务的管理是和AOP是有很大关系的,即声明式事务的底层是用事务实现的!

  • 相关阅读:
    POJ 2195 Going Home(费用流)
    HDU 2485 Destroying the bus stations(费用流)
    POJ 3281 Dining(最大流)
    POJ 1122 FDNY to the Rescue!(最短路+路径输出)
    HDU 4747 Mex(线段树)
    POJ 2337 Catenyms
    UVa 10328 Coin Toss(Java大数+递推)
    HDU 1811 Rank of Tetris(拓扑排序+并查集)
    ZOJ 3747 Attack on Titans
    UVa 11404 回文子序列(LCS求最长回文串长度)
  • 原文地址:https://www.cnblogs.com/java-zmj/p/8021693.html
Copyright © 2020-2023  润新知