Spring AOP

AOP(Aspect Oriented Programming)

面向切面编程:基于OOP(Object Oriented Programing基础之上的编程思想)
指在程序运行期间,将某段代码动态的切入到指定的位置进行运行的编程方式

场景:计算器运行计算方法时进行日志记录

  1. 直接写在方法内部,不推荐,修改维护麻烦
    日志记录:辅助功能
    业务逻辑:核心功能
    耦合
  2. 我们希望的是:
    业务逻辑(核心功能);日志功能在运行期间,自己动态加上
    运行的时候日志加上
    可以使用 动态代理
    动态代理的问题
    1. 当没有实现任何接口时无法使用动态代理,代理对象和被代理对象唯一能产生的关联就是实现了同一接口
    2. 实现起来困难
  3. Spring AOP功能实现,底层使用动态代理
    1. 利用Spring一句代码都不写的去创建代理

      实现简单,而且额没有强制目标对象必须实现接口
      将某段代码 动态的切入(不把日志代码写死在业务逻辑方法中)指定方法(加减乘除)指定的位置(方法开始、结束…) 进行运行的编程方式

      Spring AOP的专业术语

如何将LoggerUtils这个类(切面类)中的通知方法动态的在目标方法运行的各个位置切入

将动态代理的装换为Spring AOP

  1. 导包

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    核心包
    commons-logging-1.1.3.jar
    spring-aop-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
    切面编程
    spring-aspects-4.0.0.RELEASE.jar
    增强版切面编程(即使目标对象没有实现任何接口也能创建代理)
    下载地址https://github.com/lyhcc/resourses/tree/master/Spring/extra
    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
  2. 写配置

    1. 将目标类和切面类(封装了通知方法(在目标方法这行前后直线的方法))加入IOC容器中
    2. 告诉Spring哪个是切面类@Aspect
    3. 告诉Spring,里面的通知方法什么时候运行
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      try{
      @Before
      meethod.invoke
      @AfterReturning
      }catch(e){
      @AfterThrowing
      }finally{
      @After
      }
      @Before 目标方法运行之前 前置通知
      @After 目标方法运行之后 后置通知
      @AfterReturning 在目标方法正常返回之后 返回通知
      @AfterThrowing 目标方法跑出异常时 异常通知
      @Around 环绕 环绕通知

      指定在哪个方法运行
      execution(访问权限 返回值 方法签名)
      @Before("execution(方法签名)")
      1. 开启基于注解的AOP
        1
        2
        <context:component-scan base-package="xzy.lyhcc"></context:component-scan>
        <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

AOP细节

细节一 IOC容器保存的是代理对象

AOP的底层就是动态代理,容器保存的组件就是代理对象$Proxy16,并不是本类的类型

1
2
3
4
5
6
7
8
Calculator calculator = ioc.getBean(Calculator.class);
calculator.add(10, 2);
System.out.println(calculator);
System.out.println(calculator.getClass());
/*
xyz.lyhcc.calculate.MathCalculator@7f485fda
class com.sun.proxy.$Proxy16
*/

在Spring中如果结果加上组件注解,不会创建对象
只是告诉ioc容器中可能有这种类型的组件


细节二 cglib为没有接口的类创建代理对象

没有接口,cglib帮我们创建代理对象,

1
class xyz.lyhcc.calculate.MathCalculator$$EnhancerByCGLIB$$59ae67da

细节三 切入点表达式(通配符)

固定格式 execution(访问权限符 返回值类型 方法全类名(参数表))
通配符
*
1) 匹配一个或者多个字符 execution(public int xyz.lyhcc.ab.MyClass.(int, int))
2) 匹配任意一个参数 execution(public int xyz.lyhcc.ab.MyClass.(int, ))
3) 只能匹配一层路径
4) 权限位置不能表示, 表示任意时不写即可
..
1) 匹配任意多个参数,任意类型参数execution(public int xyz.lyhcc.ab.My
Class.
(..))
2) 匹配任意多层路径:
execution(public int xyz.lyhcc..MyClass.(int, int))
&&、||、!
1) && 切入的位置满足这两边的表达式
2) || 切入点满足两边的一个
3) ! 只要不是当前的就行

1
2
3
记住两种:
最精确的:execution(public int xyz.lyhcc.ab.MyClass.*(int, int))
最模糊的:execution(* *(..)), 别写

细节三 通知方法的执顺序

  1. 正常执行:@Before –>@After –> @AfterReturning
  2. 异常执行:@Before –>@After –> @AfterThrowing

细节四 jointPoint获取通知方法的信息(参数方法名获取等)

1) 只需为通知方法参数列表上写一个参数

1
2
3
4
5
org.aspectj.lang.JoinPoint
JointPoint 封装了目标方法的详细信息

joinPoint.getSignature().getName()
joinPoint.getArgs()

细节五 通知方法返回值获取、异常获取

在通知方法的参数列表中添加一个参数, 需要告诉Spring这个参数是用来接收返回值的

1
2
3
4
5
@AfterReturning(value="execution(public int xyz.lyhcc.calculate.MathCalculator.*(int, int))",returning="result")
public static void logFinished(JoinPoint joinPoint, Object result)

@AfterThrowing(value="execution(public int xyz.lyhcc.calculate.MathCalculator.*(int, int))",throwing="exception")
public static void logError(JoinPoint joinPoint, Exception exception)

细节六 Spring对通知方法的要求

Spring 对通知方法要求不严格 就算是有返回值,私有的方法也能正常执行
唯一的要求是参数不能乱写
 通知方法是Spring利用反射调用的,每次方法调用得确定这个方法的参数表的值,参数表的参数,Spring都得知道是什么
JointPoint认识
不知道的参数要告诉Spring
Exception e :指定通知方法可以接收哪些异常

细节七 抽取切入点表达式

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

@Before("myPointcut()")
public static void logStart(JoinPoint joinPoint)

细节八 环绕通知

  • 环绕通知 (本身就是一个动态代理,四合一) 是所有通知类型中 功能最为强大 的,能够全面地控制连接点,甚至可以控制是否执行连接点。
  • 对于环绕通知来说,连接点的 参数类型必须是ProceedingJoinPoint 。它是 JoinPoint的子接口,允许控制何时执行,是否执行连接点。
  • 在环绕通知中需要明确调用ProceedingJoinPoint的 proceed()方法 (就相当于method.invoke())来执行被代理的方法。如果忘记这样做就会导致通知被执行了,但目标方法没有被执行。
  • 注意:环绕通知的方法 需要返回目标方法执行之后的结果 ,即调用 joinPoint.proceed();的返回值,否则会出现空指针异常。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    	@Around(value="myPointcut()")
    public static Object aroundMethod(ProceedingJoinPoint point) throws Throwable {
    System.out.println("环绕通知正在执行,当前运行的方法是"+point.getSignature().getName()+",参数是"+Arrays.asList(point.getArgs()));

    try {

    proceed = point.proceed();
    System.out.println("环绕通知:运行结果是"+proceed);
    } catch (Exception e) {
    System.out.println("环绕通知:异常"+e);
    throw new RuntimeException(e);
    }finally {
    System.out.println("环绕通知:方法运行完成");
    }
    return proceed;
    }
    }

    细节九 环绕通知和普通通知同时存在,异常需要抛出普通通知才能接收到

    环绕通知优先于普通通知执行
    执行顺序: (环绕前置–>普通前置)–>目标方法执行–>环绕返回或异常–>环绕后置–>普通后置–>普通返回或异常(出现异常想要普通通知知道需要 抛异常

细节十 多个切面的运行顺序

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

先进去的后出来

环绕在哪个切面就在哪个切面的普通切面之前运行

AOP的应用场景

1) AOP加日志到数据库
2) AOP做权限验证
3) AOP做安全检查
4) AOP做事务控制

基于注解的AOP

基于注解的AOP

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×