1,AOP的概念:

A,* AOP的开发思想:

  1. - 在执行代码的时候将共性功能与非共性的功能织入到一起,生成代理对象;
  2. - **简单来说,就是将复用性高的功能提取出来,做成切面类在程序执行的时候动态地添加到业务程序中去,使得这个切面类可以在不同的地方复用;**
  3. - 提取到的地方,写好实现该功能的代码,我们称其为切面代码,在以后需要调用该功能的时候,只需要利用代理模式调用就好了;

B,AOP的主要目的及应用场景:

  - AOP真正目的是:你写代码的时候,只需考虑主流程,而不用考虑那些不重要的,但又必须要写的其它相同的代码,这些其它的相同代码所在的类就是切面类。
  - 主要应用场景:
     - 事务处理;
     - 日志记录;
     - 用户权限(登录);
     - 等等……

2,* AOP的结构术语:

结构术语: 作用:
JoinPoint(连接点) 在程序执行过程中的某个阶段点,就是指主业务方法的调用,它是客观存在的。指的是接口或实现类中所有的方法;
Pointcut(切入点) 满足某一规则的类或方法都是切入点,通过切入点表达式来制定规则。是连接点中的部分方法。
Advice(通知) 切入点处所要执行的程序代码,即要执行的公共方法。通知的类型有: 前置通知、后置通知、异常通知、最终通知、环绕通知。
Aspect(切面) 切面指的是切入点(规则)和通知(织入方法)的类。切面类 = 切入点规则+通知方法;
Target(目标对象) 被代理的对象,就是主业务类;如:被经纪人代理的明星;
Weaving(织入) 织入指的是:把新增的功能用于目标对象,创建代理对象的过程;
Proxy(代理) 一个类被AOP织入增强产生的结果类,就是代理类;如:为明星代理的经纪人;
  - **案例图:**

AOP & 声明式事务 - 图1


3,* AOP的创建步骤:

A,导入所需依赖:(pom.xml)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns = "http://maven.apache.org/POM/4.0.0"
         xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation = "http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>top.jztice5</groupId>
    <artifactId>SSM-day03</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <spring.version>5.3.16</spring.version>
    </properties>

    <dependencies>
        <!-- 导入spring框架 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <!-- 导入AOP,aspect框架 -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.6</version>
        </dependency>

        <!-- junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
            <scope>test</scope>
        </dependency>

        <!-- spring-test -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.12</version>
        </dependency>
        <!--        spring-jdbc事务-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.3.6</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.5</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.46</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>2.0.5</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.20</version>
        </dependency>
    </dependencies>

</project>

B,定义业务层(service)接口与实现类:

/**
 * 业务层实现类
 */
@Service("bookService")
public class BookServiceImpl implements BookService {
    @Override
    public void UpdateBook () {
        System.out.println("这是一个连接点");
    }
}
/**
 * 业务层接口
 */
public interface BookService {

    void UpdateBook();

}

C,定义通知类,创建通知(Aspect):

public class MyAspect {

    //定义切入点
    @Pointcut("execution(public void top.jztice5.service.BookService.UpdateBook())")
    public void pt(){}

    //创建通知并与切入点进行绑定
    @Before("pt()")
    public void before(){
        System.out.println(System.currentTimeMillis());
    }

}

D,定义通知类受Spring容器管理,并定义为切面类:

/**
 * 切面类
 */
//放到Spring容器中
@Component
//表明该类是一个切面类
@Aspect
public class MyAspect {

    //定义切入点
    @Pointcut("execution(public void top.jztice5.service.BookService.UpdateBook())")
    public void pt(){}

    //创建通知并与切入点进行绑定
    @Before("pt()")
    public void before(){
        System.out.println(System.currentTimeMillis());
    }

}

注意:没有使用@Aspect注解的情况下,该类依然是切面类,且未被定义为切面类之前,无法实现AOP操作;

E,在核心配置类中开启AOP注解驱动支持:

  - **@EnableAspectJAutoProxy        :启用切面类自动代理模式**
@Configuration
//设置要扫描的包路径
@ComponentScan("top.jztice5")
@Import({JdbcConfig.class,MyBatisConfig.class})
//使用AOP就一定要加上这个:切面自动代理
@EnableAspectJAutoProxy
public class SpringConfig {


}

F,启用AOP:

这里使用测试类进行举例;使用整合的Junit4

//Junit提供的方法
@RunWith (SpringJUnit4ClassRunner.class)
@ContextConfiguration (classes = SpringConfig.class)
public class AopTest01 {

    @Autowired
    private BookService bookService;

    @Test
    public void setTextService(){
        bookService.UpdateBook();
    }

}

4,AOP的切入点表达式:

execution:后面写切入点函数;

image.png
image.png

  - **AOP使用通配符描述的切入点表达式:**

image.png

A,例:

image.png


5,* AOP的通知类型:

A,5种通知类型:

通知类型 描述
1,前置通知 原始方法执行前执行,如果通知中抛出异常,阻止原始方法运行
2,后置通知 原始方法正常执行完毕并返回结果后执行,如果原始方法中抛出异常,无法执行
3,最终通知 原始方法执行后执行,无论原始方法中是否出现异常,都将执行通知
4,异常通知 原始方法抛出异常后执行,如果原始方法没有抛出异常,无法执行
5,环绕通知 在原始方法执行前后均有对应执行,还可以阻止原始方法的执行

B,* 通知类型的注解 (环绕通知:重点):

所有通知注解都放在方法的上方;

  - **前置通知:@Before**
  - **后置通知:@AfterReturning**
  - *** 环绕通知(重点,常用):@Around**
     - **@Around 注意事项**:
        - 1,环绕通知必须依赖形参ProceedingJoinPoint才能实现对原始方法的调用,进而实现原始方法调用前后同时添加通知
        - 2,通知中如果未使用ProceedingJoinPoint对原始方法进行调用将跳过原始方法的执行
        - 3,对原始方法的调用可以不接收返回值,通知方法设置成void即可,如果接收返回值,必须设定为Object类型
        - 4,原始方法的返回值如果是void类型,通知方法的返回值类型可以设置成void,也可以设置成Object
        - 5,由于无法预知原始方法运行后是否会抛出异常,因此环绕通知方法必须抛出Throwable对象
  - **最终通知:@After**
  - **异常通知:@AfterThrowing**

C,* 环绕通知的使用:

@Component
@Aspect
public class UrlAspect {

    @Around("execution(Boolean top.jztice5.service.URLService.setUrl(String,String))")
    //ProceedingJoinPoint:进行连接点接口 (环绕通知与该接口的配合使用)
    public Object setTextUrl(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {

        //将传来的password字符串封装到目标数组
        Object[] args = proceedingJoinPoint.getArgs();
        //遍历数组
        for (int i = 0 ; i < args.length ; i++) {
            //Object类型的元素转为字符串并将前后的空格去除
            args[i]=args[i].toString().trim();
        }
        //返回处理后的结果
        return proceedingJoinPoint.proceed(args);
    }

}

6,AOP通知获取数据:

A,AOP通知获取目标方法的数据:

B,AOP通知获取参数数据

C,AOP通知获取返回值数据:

D,AOP通知获取异常数据(了解):

aop主要使用在业务层;

定义切入点的空方法唯一的作用就是定义切入点表达式;
绑定切入点与通知的关系
image.png

@Aspect:定义为切面类

image.png
image.png
环绕通知
image.png
image.png

事务:

@Transactional

propagation :设置事务传播行为