[Spring] Spring5——AOP 简介

news2024/10/5 17:22:49

目录

一、AOP 简介

1、什么是 AOP

二、AOP 底层原理

1、动态代理原理

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

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

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

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

2、JDK 动态代理示例

四、AOP 操作术语

1、连接点

2、切入点

3、通知(增强)

4、切面

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

1、准备工作

2、基于 AspectJ 注解方式

3、其他通知

4、公共切入点提取

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

6、完全注解开发

六、基于 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)创建接口,定义方法

package com.demo.dao;

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

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

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 类创建接口代理对象

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)测试代码

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 部分所需依赖)

<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)语法结构

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

(4-3)例子

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

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

2、基于 AspectJ 注解方式

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

  • 可以使用配置类,也可以使用配置文件。两个标签的作用类似,就是寻找给定范围内的类是否包含对应标签。
<?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)创建类,定义方法

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 包中的注解),并使用切入点表达式确定目标方法。
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)测试代码

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)代码 

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 值都一样,可以将他们提取出来。

@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)代码

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 配置文件

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)测试代码

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 类

package com.demo.pojo;

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

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

(1-2)配置文件

<?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)测试代码

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)输出结果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1060510.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【算法学习】-【双指针】-【快乐数】

LeetCode原题链接&#xff1a;202. 快乐数 下面是题目描述&#xff1a; 「快乐数」 定义为&#xff1a; 对于一个正整数&#xff0c;每一次将该数替换为它每个位置上的数字的平方和。 然后重复这个过程直到这个数变为 1&#xff0c;也可能是 无限循环 但始终变不到 1。 如果…

408计网应用层总结

网络应用模型 ■客户/服务器模型&#xff08;C/S&#xff09;&#xff1a;客户是服务请求方&#xff0c;服务器是服务提供方 ■P2P模型&#xff1a;各主机都是客户&#xff0c;也都是服务器&#xff08;任意一对计算机成称为对等方&#xff09; 注&#xff1a; 1.客户…

linux入门---信号的保存和捕捉

目录标题 信号的一些概念信号的保存pending表block表handler表 信号的捕捉内核态和用户态信号的捕捉 信号的一些概念 1.进程会收到各种各样的信号&#xff0c;那么程序对该信号进行实际处理的动作叫做信号的递达。 2.我们之前说过当进程收到信号的时候可能并不会立即处理这个信…

DevEco Studio设置Nodejs提示路径只能包含英文、数字、下划线等

安装DevEco Studio 3.1.1 Release 设置Nodejs路径使用nodejs默认安装路径 &#xff08;C:\Program Files\nodejs&#xff09; 提示只能包含英文、数字、下划线等 , 不想在安装nodejs请往下看 nodejs默认路径报错 修改配置文件 1、退出DevEco Studio 2、打开配置文件 cmd控制台…

Linux高性能服务器编程 学习笔记 第十章 信号

信号是由用户、系统、进程发送给目标进程的信息&#xff0c;以通知目标进程某个状态的改变或系统异常。Linux信号可由以下条件产生&#xff1a; 1.对于前台进程&#xff0c;用户可通过输入特殊终端字符来给它发送信号&#xff0c;如输入CtrlC通常会给进程发送一个中断信号。 2…

最短路径专题5 最短路径

题目&#xff1a; 样例&#xff1a; 输入 4 5 0 2 0 1 2 0 2 5 0 3 1 1 2 2 3 2 2 输出 3 0->3->2 思路&#xff1a; 根据题目意思&#xff0c;求最短路&#xff0c;这个根据平时的Dijkstra&#xff08;堆优化&#xff09;即可&#xff0c;关键在于求路径的方法&#x…

阿里云新账户什么意思?老用户、产品首购详细说明

阿里云新账户、老账号、产品首购和同人账号什么意思&#xff1f;阿里云账号分为云新账户、老账户、产品首购、同人账号和同一用户&#xff0c;阿里云官方推出的活动很多是限制账号类型的&#xff0c;常见的如阿里云新用户&#xff0c;什么是阿里云新用户&#xff1f;是指从未在…

mysql面试题11:讲一讲MySQL主从复制模式

该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 面试官:讲一讲MySQL主从复制模式? MySQL主从复制的配置步骤如下: 在主服务器上配置: 打开主服务器的配置文件my.cnf,启用二进制日志(binary log)功…

【Linux】[gdb]Linux环境下如何调试代码

一、code.c文件 我们首先创建一个code.c文件&#xff0c;写一段简单代码&#xff0c;用于测试。 二、makefile文件 然后&#xff0c;我们可以编写makefile文件&#xff0c;使得code.c文件能够进行编译。&#xff08;当然也可以不写makefile文件&#xff0c;直接对code.c进行编…

mysql面试题14:讲一讲MySQL中什么是全同步复制?底层实现?

该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 面试官:讲一讲mysql中什么是全同步复制?底层实现? MySQL中的全同步复制(Synchronous Replication)是一种复制模式,主服务器在写操作完成后,必须等待…

PG 多表连接查询

写法&#xff1a; 使用 select 表名.键名 from 表1 join表2 on 相同的主键 构造出来一张新表 多表要用表名.键名 才能知道是哪一张表 传统写法也行 类型&#xff1a; 内 而外的要这样写

[BJDCTF2020]Mark loves cat

先用dirsearch扫一下&#xff0c;访问一下没有什么 需要设置线程 dirsearch -u http://8996e81f-a75c-4180-b0ad-226d97ba61b2.node4.buuoj.cn:81/ --timeout2 -t 1 -x 400,403,404,500,503,429使用githack python2 GitHack.py http://8996e81f-a75c-4180-b0ad-226d97ba61b2.…

虚拟机VMware的使用流程以及出现的问题附解决方法

虚拟机VMware的使用流程以及出现的问题附解决方法 下载安装 略。。。 创建虚拟机 虚拟机的设置如下&#xff1a;注意网络适配器为NAT 如果出现ip addr 命令&#xff1a;不显示IP地址的话&#xff1a; 解决方式如下&#xff1a; 首先设置网卡&#xff1a;先查看一下onboot是…

python二次开发CATIA:测量点的坐标

首先新建一个Part文件&#xff0c;插入一个几何图形集&#xff0c;在该几何图形集中插入一个点&#xff0c;坐标为&#xff08;100&#xff0c;0&#xff0c;0&#xff09;​&#xff0c;如下图所示&#xff1a; 下面通过python来测量该点的坐标​&#xff1a; import win32co…

国庆10.4

QT实现TCP服务器客户端 服务器 头文件 #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QTcpServer> //服务器头文件 #include <QTcpSocket> //客户端头文件 #include <QList> //链表容器 #include <QMe…

蓝桥杯每日一题2023.10.4

双向排序 - 蓝桥云课 (lanqiao.cn) 题目描述 题目分析 六十分解法如下&#xff1a;按照题意简单排序 #include<bits/stdc.h> using namespace std; const int N 2e5 10; int n, m, p, q, a[N]; bool cmp(int x, int y) {return x > y; } int main() {cin >&g…

C++ YAML使用

C++工程如何使用YAML-cpp 一、前期准备工作 1、已安装minGW、cmake、make等本地工具。 2、下载YAML-cpp第三方开源代码(一定要下载最新的release版本,不然坑很多)。 3、生成YAML-cpp静态库 (1)在yaml-cpp-master下建立build文件夹; (2)在该文件夹下生成MakaFile文…

wzsc_文件上传(条件竞争)

打开题目链接&#xff0c;很常见的文件上传框 经过尝试&#xff0c;发现上传东西后会调用upload.php&#xff0c;猜测文件被传到upload目录下 随便传了几个类型的文件&#xff0c;访问upload目录 发现.php文件以及.htaccess、.user.ini这种配置文件都没有传上去 但是通过抓包…

软件工程与计算总结(三)示例项目描述

本节介绍一个标准的项目描述&#xff0c;大家可以作为蓝本学习~ 目录 一.背景 二.目标 三.系统用户 四.用户访谈要点 1.收银员 2.客户经理 3.总经理 4.系统管理员 五.项目实践过程 一.背景 A是一家刚刚发展起来的小型连锁商店&#xff0c;其前身是一家独立的小百货门面…

lv7 嵌入式开发-网络编程开发 07 TCP服务器实现

目录 1 函数介绍 1.1 socket函数 与 通信域 1.2 bind函数 与 通信结构体 1.3 listen函数 与 accept函数 2 TCP服务端代码实现 3 TCP客户端代码实现 4 代码优化 5 练习 1 函数介绍 其中read、write、close在IO中已经介绍过&#xff0c;只需了解socket、bind、listen、acc…