Spring的AOP功能就是面向切面编程.我们从Spring容器取出的值,就是已经被重新包装过代理对象

概念

  • 通知: 要切入的内容
  • 切点: 要切入的地方
  • 切面织入: 将切面织入类的方法中,切面=通知+切点

通知的类

在该类中声明各自通知,每个通知+切点,都能组成一个切面

public class MyAdvice {

    //前置通知
    public void before() {
        System.out.println("前置通知");
    }

    //后置通知
    public void afterReturning() {
        System.out.println("后置通知");
    }

    //最终通知
    public void after() {
        System.out.println("最终通知");
    }

    //异常通知
    public void afterThrowing() {
        System.out.println("异常通知");
    }
}

AOP的配置

  1. 注入通知类的bean
  2. 设置aop–声明切点
  3. 设置aop–织入切面
    <!-- 注入通知类的bean -->
    <bean id="myAdvice" class="dao.impl.MyAdvice"/>

    <aop:config>
        <!-- 设置1个切点by execution表达式 -->
        <aop:pointcut expression="execution(* *..*.*myDowork*(..))" id="pc"/>
        <!-- 织入4个切面 -->
        <aop:aspect ref="myAdvice">
            <aop:after method="after" pointcut-ref="pc"/>  
            <aop:after-returning method="afterReturning" pointcut-ref="pc"/>
            <aop:after-throwing method="afterThrowing" pointcut-ref="pc"/>
            <aop:before method="before" pointcut-ref="pc"/>
        </aop:aspect>
    </aop:config>
1. execution表达式

expression="execution(表达式)"
作用:定位到符合表达式的方法,将其声明为切入点;
定位的条件有五个:

  • 方法的形参类型
  • 方法的返回值类型
  • 方法所在的包
  • 方法所在的类
  • 方法的名称

这里写图片描述

例:execution(* *..*.*myDowork*(..)):
表示任意包,类,形参类型,返回值类型.方法名含有myDowork的方法.
例:execution(java.lang.String a.b.A.*(..)):
表示a.b包下的A类,返回值为String的所有方法,

注意:由于我们需要导入很多jar,所以会有很多方法名重复,尽量使用execution表达式时候,加入包名.

2. 通知类型
  • <aop:before>:前置通知,方法执行前执行
  • <aop:after-returning>:后置通知, 方法执行完执行,(出现异常不执行)
  • <aop:after>:最终通知,方法执行完执行,(出现异常依然执行)
  • <aop:after-throwing>异常通知,方法出现异常后执行
  • <aop:around> 环绕通知,可以一次性完成所有通知,可以修改形参,一般配合注解使用

AOP的注解

不用在Spring的配置文件中配置,直接在通知的类中用注解方式告诉Spring织入切面方式.
注解所在的包org.aspectj.lang.annotation.

  • 声明使用注解<aop:aspectj-autoproxy/>
  • 通知的类:@Aspect
  • 切入点的方法(方法名=id);@Pointcut(“execution表达式”)
  • 织入切面:在通知类的方法上
    • @通知类型(“切点id()”)或@通知类型(“execution表达式”的String)
    • 通知类型中的属性
      • value/pointcut=切点
      • throwing=”ex”,异常通知的Throwable 对象为ex
      • -
<!-- 注入通知类的bean -->
    <bean id="myAdvice" class="dao.impl.MyAdvice"/>

普通的切面织入

@Aspect
public class MyAdvice {

    //声明切点方式1  //static+final
    public static final String EXP="execution(* *..*.*myDowork*(..))";
    //声明切点方式2
    @Pointcut("execution(* *..*.*myDowork*(..))")
    public void pc() {}


    @Before("pc()")
    public void before() {
        System.out.println("前置通知");
    }

    @AfterReturning(EXP)
    public void afterReturning() {
        System.out.println("后置通知");
    }

    @After("pc()")
    public void after() {
        System.out.println("最终通知");
    }

    @AfterThrowing("pc()")
    public void afterThrowing() {
        System.out.println("异常通知");
    }
}

异常的切面织入

@Aspect
public class MyAdvice {
    //声明切点 
    @Pointcut("execution(* *..*.*myDowork*(..))")
    public void pc() {}

    //throwing="ex"表示声明异常的对象
    @AfterThrowing(pointcut="pc()",throwing="ex")
    public void afterThrowing(Throwable ex) {
        System.out.println("异常通知");
        System.out.println(ex.getMessage());
    }
}

环绕的切面织入

@Aspect
public class MyAdvice {
     //声明切点
    public static final String EXP="execution(* *..*.*myDowork*(..))";

    @Around(EXP)
    public Object around(ProceedingJoinPoint pjp) {
        try {
            System.out.println("前置通知");
            Object ret = pjp.proceed();    //执行目标对象的方法
            System.out.println("后置通知");
            return ret;                    //返回值
        }catch(Throwable ex) {
            System.out.println("异常通知");
            System.out.println(ex.getMessage());
        }finally {
            System.out.println("最终通知");
        }
        return null;
    }}

参数的传递
作用:通过判断参数值,来选择不同的处理机制
1. 表达式中需要加入参数
2. 利用argNames属性,声明参数
3. 对于符合execution表达式,但不符合参数类型的方法,不会被织入切面

@Aspect
public class MyAdvice {

    //声明切点方式1  static+final
    public static final String EXP="execution(* *..*.*myDowork*(..))&& args(str,i)";

    @Before(value=EXP,argNames="str,i")
    public void before2(String str,Integer i) {
        System.out.println("123");
        System.out.println(str+i);
    }



    //声明切点方式2
    @Pointcut(value="execution(* *..*.*myDowork*(..)) && args(str,i)",argNames="str,i")
    public void pc(String str,Integer i) {}

    @Before(value="pc(str,i)",argNames="str,i")
    public void before(String str,Integer i) {
        System.out.println(str+i);
    }
}

参数的修改
只有在环绕通知中可以修改参数.
1.获得参数的数组:Object[] args = pjp.getArgs();
2.修改参数的数组元素:args[i]=XXX;
3.执行目标对象方法时候,传入新数组;Object ret = pjp.proceed(args);

@Aspect
public class MyAdvice {

    public static final String EXP = "execution(* *..*.*myDowork*(..))";

    @Around(EXP)
    public Object around(ProceedingJoinPoint pjp) {
            try {
                Object[] args = pjp.getArgs();
                args[0]="hahah";
                Object ret = pjp.proceed(args);
                return ret;
            } catch (Throwable e) {
                e.printStackTrace();
            } 
            return null;
    }
}

注意点:环绕对所有符合表达式的方法,都会进行织入切面
即没有形参的方法,且符合表达式,也会被织入切面 而加入形参pjp.proceed(args);会报错.

  • 要严格写好execution表达式
  • 判断数组对象长度和大小来进行选择处理方式
  • pjp.getSignature().getDeclaringTypeName() 目标对象的权限类名
  • pjp.getSignature().getName() 目标对象调用的方法名
  • pjp.getSignature().getDeclaringType() 目标对象的字节码对象
Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐