12.11 AOP 的一些术语、简单配置

12.11 AOP 的一些术语、简单配置

需要加日志的地方:方法开始、方法返回、方法异常、方法结束 每个方法都是4个位置。

image-20191211113246680

横切关注点:把方法横向来看

切面类:存放横切关注点、通知方法的类,也就是LogUtils

连接点:每一个方法的每一个位置都是一个连接点,一共是16个连接点。

切入点:真正需要执行日志记录的地方,切入点是从众多连接点中选出我们感兴趣的地方

切入点表达式:在众多连接点中选出我们感兴趣的地方

弹幕说,AOP=切入点+通知

AOP使用步骤

  1. 导包

    • aspect包(基础 版)
    • cglib aopalliance aspectj(加强版)
  2. 配置

    • 将目标类添加到容器中
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Service//一定要有这个 告诉Spring是一个目标
    public class CalculatorImpl implements Calculator {
    @Override
    public int add(int i,int j ){
    return i+j;
    }
    @Override
    public int sub(int i,int j){
    return i-j;
    }

    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <?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-3.0.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop-3.1.xsd">
    <context:component-scan base-package="com.runsstudio"></context:component-scan>
    <!-- 开启基于注解的AOP空间:aop名称空间-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    </beans>

    xml里面 aop context 命名空间都得添加

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    //LogUtils
    package com.runsstudio;

    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.Signature;
    import org.aspectj.lang.annotation.*;
    import org.springframework.stereotype.Component;

    import java.util.Arrays;

    @Aspect//告诉Spring这个类是切面类
    @Component
    public class LogUtils {
    /**
    * 告诉Spring每个方法都什么时候运行
    * try{
    * @Before
    * Method.invoke(obj,args)
    * @AfterReturning
    * }catch(e){
    * @AfterThrowing
    * }finally{
    * @After
    * }
    *
    * 通知注解:
    * @Before 前置通知
    * @After 后置通知
    * @AfterReturning 在目标方法正常返回之后 返回通知
    * @AfterThrowing 在目标方法抛出异常之后运行 异常通知
    * @Around: 环绕通知
    */
    //想在执行目标方法之前,需要表达式写清楚执行哪个类
    //execution(权限修饰符 返回值 方法签名) 告诉Spring 何时何地运行
    @Before(value="execution(public int com.runsstudio.impl.CalculatorImpl.*(int,int))")//在目标方法运行之前运行
    public static void logBeforeMethod(JoinPoint joinPoint){
    Object[] args = joinPoint.getArgs();//获取目标方法运行参数
    System.out.println("args:"+ Arrays.toString(args));
    Signature signature = joinPoint.getSignature();//获取方法签名
    String name = signature.getName();
    System.out.println(name+":beforeMethodLog....");
    }
    @After("execution(public int com.runsstudio.impl.CalculatorImpl.*(int,int))")//在目标方法运行结束之后运行
    public static void logAfterMethod(JoinPoint joinPoint){
    Object[] args = joinPoint.getArgs();//获取目标方法运行参数
    System.out.println("args:"+ Arrays.toString(args));
    Signature signature = joinPoint.getSignature();//获取方法签名
    String name = signature.getName();
    System.out.println(name+"afterMethodLog....");
    }
    // @Around("execution(public int com.runsstudio.impl.CalculatorImpl.*(int,int))")
    // public static void logSurroundMethod(){
    // System.out.println("logSurroundMethodLog....");
    // }
    @AfterThrowing(value = "execution(public int com.runsstudio.impl.CalculatorImpl.*(int,int))",throwing = "exception")
    public static void logExceptionMethod(JoinPoint joinPoint, Exception exception){
    System.out.println(exception.getMessage());
    System.out.println("发生异常啦....");
    }
    @AfterReturning(value = "execution(public int com.runsstudio.impl.CalculatorImpl.*(int,int))" ,returning = "result")
    public static void logAfterReturningMethod(JoinPoint joinPoint,Object result){
    Object[] args = joinPoint.getArgs();//获取目标方法运行参数
    System.out.println("args:"+ Arrays.toString(args));
    Signature signature = joinPoint.getSignature();//获取方法签名
    String name = signature.getName();
    System.out.println(name+",result="+result.toString());
    }
    }

  3. 使用

  4. 开启基于注解的AOP功能

使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.runsstudio.test;

import com.runsstudio.Bean.Calculator;
import com.runsstudio.impl.CalculatorImpl;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AOPTest {
ApplicationContext ioc=new ClassPathXmlApplicationContext("conf/applicationContext.xml");

@Test
public void testMethod(){
/*// 这个是JUNIT自带的 应该从ioc容器中拿目标对象
Calculator calculator=new Calculator();
calculator.add(2,3);*/
// STEP1":从ioc容器中拿到目标对象,注意:如果要用类型,一定要用接口类,不要用本类
// CalculatorImpl bean = ioc.getBean(CalculatorImpl.class); 不能用接口类,因为是用的代理对象
Calculator bean2 = ioc.getBean(Calculator.class);
// bean.add(2,1);
// bean.sub(2,3);
bean2.add(2,3);
System.out.println(bean2.getClass());//class com.sun.proxy.$Proxy22
}
}

IOC容器中保存的都是代理对象

CGLIB的作用:为没有接口的类创建代理对象,有接口的类用JDK创建

一些细节:

  1. 切入点表达式(访问权限符 返回值 方法全类名(参数列表))
    • *匹配一个或多个字符 比如
    • .星号还可以匹配任意一个参数(int,*) 第一个是int类型,第二个是任意的 double或int都可以
    • .. 任意多个参数和任意多个类型参数 (..) 任意多个参数
    • 全类名写com.runsstudio.* 可以匹配任意包
    • public 是想写也行 不想写也行
    记住:
    • 最精确的:execution(public int com.runsstudio.impl.CalculatorImpl.add(int,int))
    • 最模糊的:execution(* *. *()) 任意包、任意类、任意方法 (千万别写)
    表达式之间可以用&& || !表达当两个表达式同时满足、或满足一个、或不满足等条件

执行顺序:

1
2
3
4
5
6
7
8
9
* try{
* @Before
* Method.invoke(obj,args)
* @AfterReturning
* }catch(e){
* @AfterThrowing
* }finally{
* @After
* }

各个方法 对权限修饰符没有要求,但是 每个参数的意义都得告诉Spring 比如上面after returning 的Object result 顺便一说 如果写String result 也是可以的 但是这样的话返回int就接受不到了

细节7 可以将Execution 抽取出来(抽取可重用Execution)
  1. 随便声明一个没有实现返回的空方法
  2. 在这个方法上 写一个注解@PointCut
  3. 然后抽取,案例如下
1
2
3
4
5
6
7
8
9
10
11
12
@Pointcut("execution(public int com.runsstudio.impl.CalculatorImpl.*(int,int))")
public void pointCut1(){}
//想在执行目标方法之前,需要表达式写清楚执行哪个类
//execution(权限修饰符 返回值 方法签名) 告诉Spring 何时何地运行
@Before(value="pointCut1()")//在目标方法运行之前运行
public static void logBeforeMethod(JoinPoint joinPoint){
Object[] args = joinPoint.getArgs();//获取目标方法运行参数
System.out.println("args:"+ Arrays.toString(args));
Signature signature = joinPoint.getSignature();//获取方法签名
String name = signature.getName();
System.out.println(name+":beforeMethodLog....");
}
细节8 环绕通知

相当于四合一

环绕通知在普通通知前面执行 环绕的代码是优先的

细节9 多个切面同时切入

先切入的先切出,后切入的后切出,用@Order(1) 数字小的在前面,如果不指定的话就是第一个字母 在前面的先执行

-------------文章已结束~感谢您的阅读-------------
穷且益坚,不堕青云之志。