[Spring] Spring5——AOP 简介

目录

[一、AOP 简介](#一、AOP 简介)

[1、什么是 AOP](#1、什么是 AOP)

[二、AOP 底层原理](#二、AOP 底层原理)

1、动态代理原理

[2、基于接口的 JDK 动态代理](#2、基于接口的 JDK 动态代理)

[3、基于继承的 CGLib 动态代理](#3、基于继承的 CGLib 动态代理)

[三、底层原理实现------ JDK 动态代理](#三、底层原理实现—— JDK 动态代理)

[1、使用 Proxy 类的方法创建代理对象](#1、使用 Proxy 类的方法创建代理对象)

[2、JDK 动态代理示例](#2、JDK 动态代理示例)

[四、AOP 操作术语](#四、AOP 操作术语)

1、连接点

2、切入点

3、通知(增强)

4、切面

[五、基于 AspectJ 实现 AOP 操作(注解)](#五、基于 AspectJ 实现 AOP 操作(注解))

1、准备工作

[2、基于 AspectJ 注解方式](#2、基于 AspectJ 注解方式)

3、其他通知

4、公共切入点提取

[5、多个 Proxy 类增强同一个方法](#5、多个 Proxy 类增强同一个方法)

6、完全注解开发

[六、基于 AspectJ 实现 AOP 操作(配置文件方式)](#六、基于 AspectJ 实现 AOP 操作(配置文件方式))

1、示例


一、AOP 简介

1、什么是 AOP

(1)AOP 就是面向切面编程

利用 AOP 可以对业务逻辑的各个部分进行隔离 ,从而使得业务逻辑各部分之间的耦合度降低提高程序的可重用性 ,同时提高了开发的效率

(2)简单来说,就是不需要修改源代码,但依然可以为原来的代码添加新功能

比如在登录功能的基础上,添加一个权限检查的模块。通过某些配置,将这个模块(或部分实现代码)添加到登录功能上。

二、AOP 底层原理

1、动态代理原理

利用Java的反射技术(Java Reflection),在运行时 创建一个实现某些给定接口的新类(也称"动态代理类")及其实例(对象)。代理的是接口(Interfaces),不是类(Class),也不是抽象类。

AOP 底层是通过动态代理实现的,而动态代理是基于反射来设计的。动态代理有两种情况

  • 有接口,则使用基于接口的 JDK 动态代理
  • 没有接口,则使用基于继承的 CGLib 动态代理

2、基于接口的 JDK 动态代理

创建接口实现类的代理(Proxy)对象,使用这个对象的 invoke 方法来增强接口实现类的方法(无论调用哪个方法都会增强)。

3、基于继承的 CGLib 动态代理

创建子类的代理对象,增强类的方法。

三、底层原理实现------ JDK 动态代理

1、使用 Proxy 类的方法创建代理对象

使用 newProxyInstance() 返回 指定接口的代理类的实例 ,将该接口实例方法调用 分配给指定的调用处理程序

经此步骤,在原本的方法的基础上,就会添加上增强的部分。

(1)newProxyInstance 方法的三个参数:

  • ClassLoader类加载器
  • interfaces ,需要增强的方法所在的接口类,支持多个接口(数组形式);
  • InvocationHandler,调用处理器(程序);

(2)对第一个参数的理解

上文提到动态代理的原理,而这个类加载器其实就是基于这个原理,将增强部分与原部分得到的结果赋予这个新类,那么我们调用这个新类的方法就可以得到我们想要的增强效果。

(3)对第二个参数(特别是多个接口的情况)的理解

newProxyInstance 是为一个实现类的实例来添加增强部分的,因为明确了具体哪一个实现类,也就明确了具体的方法。

又因为一个实现类很可能是多个接口的实现类,那么在这种情况下,就需要把所有接口都传入。

(4)对第三个参数的理解

调用处理器,它其实是一个接口。

我们实现这个接口,比如叫做 A,将实现类的实例传递给 A,在 invoke 方法中进行具体操作。

2、JDK 动态代理示例

目的:增强 UserDao 里的方法。先编写好基本的接口和实现类,然后给实现类增加新的方法。

(1)代码

(1-1)创建接口,定义方法

java 复制代码
package com.demo.dao;

public interface UserDao {
    public int add(int a,int b);
    public String update(String id);
}

(1-2)创建接口实现类,实现方法

java 复制代码
package com.demo.dao.impl;

import com.demo.dao.UserDao;

public class UserDaoImpl implements UserDao {
    @Override
    public int add(int a, int b) {
        return a + b;
    }

    @Override
    public String update(String id) {
        return id;
    }
}

(1-3)使用 Proxy 类创建接口代理对象

java 复制代码
package com.demo.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

// 创建代理对象的代码
public class JDKProxy implements InvocationHandler {
    // 创建的是谁的代理对象,就把谁传递过来,一般用有参构造
    private Object obj;
    public JDKProxy(Object obj) {
        this.obj = obj;
    }

    @Override // invoke 放在在代理对象创建后马上调用
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 增强之前
        System.out.println(method.getName() + " 增强之前");

        Object res = method.invoke(obj, args);

        // 增强之后
        System.out.println(method.getName() + " 增强之后");

        return res;
    }

}

(1-4)测试代码

java 复制代码
import com.demo.dao.UserDao;
import com.demo.dao.impl.UserDaoImpl;
import com.demo.proxy.JDKProxy;
import org.junit.Test;

import java.lang.reflect.Proxy;

public class ProxyTest {
    @Test
    public void test() {
        Class[] interfaces = {
                UserDao.class
        };
        UserDao userDao = new UserDaoImpl();
        UserDao userDaoProxy = (UserDao) Proxy.newProxyInstance(userDao.getClass().getClassLoader(), interfaces, new JDKProxy(userDao));
        System.out.println("res = " + userDaoProxy.add(2, 3));
        System.out.println("res = " + userDaoProxy.update("114514"));
    }
}

(2)输出结果

四、AOP 操作术语

1、连接点

可以被增强的类方法,就称为连接点。

2、切入点

实际被增强的类方法,称为切入点(通过切入点表达式确定,后面会讲)。

3、通知(增强)

实际被增强的逻辑部分(代码),就称为通知。

通知有 5 种类型:

  • 前置通知,原方法之前执行;
  • 后置通知,原方法之后执行;
  • 环绕通知,原方法之前和之后都执行;
  • 异常通知,原方法异常时执行;
  • 最终通知,类似 finally;

4、切面

切面是一个动作,是一个把通知(增强)应用到切入点的过程。(比如:把权限判断加入到登录这一过程,就是切面)

五、基于 AspectJ 实现 AOP 操作(注解)

前面所讲的 JDK 动态代理,是为了说明 AOP 是如何实现的。在实际应用中,不会使用这种方式实现 AOP 操作,而是通过 AspectJ 注解莱实现,对象的获取还是通过 IOC 来实现。

1、准备工作

(1)Spring 框架一般都是基于 AspectJ 实现 AOP 操作

  • AspectJ 不是 Spring 组成部分,而是一个独立的 AOP 框架,一般把 AspectJ 和 Spirng 框架一起使用,进行 AOP 操作。

(2)基于 AspectJ 实现 AOP 操作的两种方式

  • 基于 xml 配置文件实现;
  • 基于注解方式实现(常用);

(3)引入相关依赖(仅写出了 AOP 部分所需依赖)

XML 复制代码
<dependencies>
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>5.3.22</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>5.3.22</version>
    </dependency>

    <dependency>
        <groupId>net.sourceforge.cglib</groupId>
        <artifactId>com.springsource.net.sf.cglib</artifactId>
        <version>2.2.0</version>
    </dependency>

    <dependency>
        <groupId>aopalliance</groupId>
        <artifactId>aopalliance</artifactId>
        <version>1.0</version>
    </dependency>

    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.6.8</version>
    </dependency>

</dependencies>

(4)切入点表达式

(4-1)切入点表达式的作用

  • 知道对哪个类里面的哪个方法进行增强。

(4-2)语法结构

cpp 复制代码
execution([权限修饰符][返回类型][类全路径][方法名称]([参数列表]))
  • 权限修饰符:private、public、......;
  • 返回类型:void、int、省略、......;
  • 参数列表:..(两个点表示方法中的参数);
  • *:表示任意权限修饰符、类、方法;

(4-3)例子

注意 * 后的空格是不能省略的,它代表了返回类型。

  • 对 com.demo.dao.BookDao 类里面的 add() 进行增强:
cpp 复制代码
execution(* com.demo.dao.BookDao.add(..))
  • 对 com.demo.dao.BookDao 类里面的所有的方法进行增强:
cpp 复制代码
execution(* com.demo.dao.BookDao.*(..))
  • 对 com.demo.dao 包里面所有类,类里面所有方法进行增强:
cpp 复制代码
execution(* com.atguigu.dao.*.*(..))

2、基于 AspectJ 注解方式

(1)开启注解扫描以及生成代理对象

  • 可以使用配置类,也可以使用配置文件。两个标签的作用类似,就是寻找给定范围内的类是否包含对应标签。
XML 复制代码
<?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.xsd
                           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!-- 注解扫描 -->
    <context:component-scan base-package="com.demo"></context:component-scan>

    <!-- 开启Aspect生成代理对象 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

(2)创建类,定义方法

java 复制代码
package com.demo.dao.impl;

import com.demo.dao.UserDao;
import org.springframework.stereotype.Component;

@Component
public class UserDaoImpl implements UserDao {
    @Override
    public void add() {
        System.out.println("add()......");
    }
}

(3)创建 Proxy 类(编写增强逻辑),并添加注解 @Aspect

  • 创建不同通知类型的方法,添加对应的注解(注解是 aspect 包中的注解),并使用切入点表达式确定目标方法。
java 复制代码
package com.demo.proxy;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class UserDaoProxy {
    @Before(value = "execution(* com.demo.dao.impl.UserDaoImpl.add(..))")
    public void before() { // 前置通知
        System.out.println("before()......");
    }
}

(4)测试代码

java 复制代码
import com.demo.dao.UserDao;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AspectBeanTest {
    @Test
    public void test() {
        ApplicationContext context = new ClassPathXmlApplicationContext("AspectBean.xml");
        UserDao userDao = context.getBean("userDaoImpl", UserDao.class);
        userDao.add();
    }
}

(5)输出结果

3、其他通知

(1)代码

java 复制代码
package com.demo.proxy;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class UserDaoProxy {
    @Before(value = "execution(* com.demo.dao.impl.UserDaoImpl.add(..))")
    public void before() { // 前置通知
        System.out.println("前置通知......");
    }

    @After(value = "execution(* com.demo.dao.impl.UserDaoImpl.add(..))")
    public void after() { // finally 通知
        System.out.println("finally 通知......");
    }

    @AfterReturning(value = "execution(* com.demo.dao.impl.UserDaoImpl.add(..))")
    public void afterReturning() { // 后置通知
        System.out.println("后置通知......");
    }

    @AfterThrowing(value = "execution(* com.demo.dao.impl.UserDaoImpl.add(..))")
    public void afterThrowing() { // 异常通知
        System.out.println("异常通知......");
    }

    @Around(value = "execution(* com.demo.dao.impl.UserDaoImpl.add(..))")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { // 环绕通知
        System.out.println("环绕通知之前......");
        proceedingJoinPoint.proceed();
        System.out.println("环绕通知之后......");
    }
}

(2)输出结果

(3)出现异常的输出结果

4、公共切入点提取

上面的示例代码中的切入点的 value 值都一样,可以将他们提取出来。

java 复制代码
@Pointcut(value = "execution(* com.demo.dao.impl.UserDaoImpl.add(..))")
public void AddPoint() {
    
}

@Before(value = "AddPoint()")
public void before() { // 前置通知
    System.out.println("前置通知......");
}

5、多个 Proxy 类增强同一个方法

如果出现多个 Proxy 增强类都含有多同一个方法的增强,那么可以通过设置优先级来确定它们的执行(增强)顺序。

(1)在 Proxy 增强类上添加注解 @Order

  • @Order(整数值),其中整数值越小,该增强类的优先级越大。

(2)代码

java 复制代码
package com.demo.proxy;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Aspect
@Order(1)
public class UserDaoProxy {
    @Before(value = "execution(* com.demo.dao.impl.UserDaoImpl.add(..))")
    public void before() { // 前置通知
        System.out.println("前置通知......");
    }
}

@Component
@Aspect
@Order(0)
class Person {
    @Before(value = "execution(* com.demo.dao.impl.UserDaoImpl.add(..))")
    public void before() {
        System.out.println("person 的前置通知");
    }
}

(3)输出结果

6、完全注解开发

(1)创建配置类,不需要创建 xml 配置文件

java 复制代码
package com.demo.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@ComponentScan(basePackages = {"com.demo"})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class Config {

}

(2)测试代码

java 复制代码
import com.demo.config.Config;
import com.demo.dao.UserDao;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class AspectBeanTest {
    @Test
    public void test() {
        ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
        UserDao userDao = context.getBean("userDaoImpl", UserDao.class);
        userDao.add();
    }
}

(3)输出结果

六、基于 AspectJ 实现 AOP 操作(配置文件方式)

Proxy类和目标增强类的对象的创建就是 IOC 里讲的操作,重点在于 AOP 部分的配置。

1、示例

(1)代码

(1-1)Book 类和 BookProxy 类

java 复制代码
package com.demo.pojo;

public class Book {
    public void buy() {
        System.out.println("but()......");
    }
}
java 复制代码
package com.demo.proxy;

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

(1-2)配置文件

XML 复制代码
<?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.xsd
                           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!-- 创建 bean 对象 -->
    <bean id="book" class="com.demo.pojo.Book"></bean>
    <bean id="bookProxy" class="com.demo.proxy.BookProxy"></bean>

    <!-- 配置 AOP 增强 -->
    <aop:config>
        <!-- 配置切入点 -->
        <aop:pointcut id="pc" expression="execution(* com.demo.pojo.Book.buy(..))"/>

        <!-- 配置切面 -->
        <aop:aspect ref="bookProxy">
            <!-- 配置增强作用的具体方法 -->
            <aop:before method="before" pointcut-ref="pc"></aop:before> <!-- 表示把 before() 作用到 pc 指向的方法上 -->
        </aop:aspect>
    </aop:config>
</beans>

(1-3)测试代码

java 复制代码
import com.demo.pojo.Book;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class XmlTest {
    @Test
    public void test() {
        ApplicationContext context = new ClassPathXmlApplicationContext("Bean01.xml");
        Book book = context.getBean("book", Book.class);
        book.buy();
    }
}

(2)输出结果

相关推荐
KotlinKUG贵州2 分钟前
Spring开发,从Kotlin开始
spring boot·spring·kotlin
codeRichLife22 分钟前
Mybatisplus3.5.6,用String处理数据库列为JSONB字段
java·数据库
来自星星的猫教授28 分钟前
Java 文件注释规范(便于生成项目文档)
java·注释
zhimeng331 分钟前
自己学习原理
java
程序员鱼皮34 分钟前
学 Java 还是 Go 语言?这事儿很简单!
java·后端·计算机·程序员·开发·编程经验·自学编程
消失在人海中37 分钟前
oracle与MySQL数据库之间数据同步的技术要点
数据库·mysql·oracle
镜花水月6037 分钟前
🐬 MySQL 慢查询分析与 InnoDB 参数优化实战:以 io_global_by_wait_by_latency 为例
mysql
Lanqing_076039 分钟前
淘宝商品详情图API接口返回参数说明
java·服务器·前端·api·电商
矮油0_o1 小时前
第一部分 -- ①语法分析的概要
java·编译器·解释器·语法分析
写bug写bug1 小时前
Dubbo中SPI机制的实现原理和优势
java·后端·dubbo