Spring中AOP详解

news2024/11/17 14:27:11

目录

一、AOP的概念

二、AOP的底层实现原理

2.1 JDK的动态代理

2.1.1 invocationhandler接口

2.1.2 代理对象和原始类实现相同的接口 interfaces

2.1.3 类加载器ClassLoador

2.1.4 编码实现

2.2 Cglib动态代理

2.2.1 Cglib动态代理编码实现

三、AOP如何通过原始对象的id获取到代理对象

3.1 BeanPostProcessor

3.2 编码实现


一、AOP的概念

AOP(Aspect Oriented Programing)即面向切面编程,以切面为基本单位的程序开发,通过切面间的彼此协同,相互调用,完成程序的构建。这里的 切面 = 切入点 + 额外功能,所以我们常说的AOP也就等同于Spring中的动态代理开发!那么什么是切面呢?当在不同的ServiceImpl中,需要添加同一个额外功能的时候,这几个类的方法中所添加的相同额外功能就会由点构成面,所以就将这个称为是切面

二、AOP的底层实现原理

2.1 JDK的动态代理

由于这里是探索AOP底层的实现原理,所以我们这里先摒弃Spring框架。首先我们需要了解代理创建的三个要素(1.原始对象 2.额外功能 3.代理对象和原始对象实现相同的接口),有了这三个要素之后就能创建出一个代理对象,接下来画图分析

首先将这个原始对象创建出来,在添加额外功能和实现相同的接口的时,使用JDK的Proxy类中的newProxyInstance(动态字节码技术)方法来完成。要了解一个类中方法的具体使用,就需要了解这个类中参数的具体含义

2.1.1 invocationhandler接口

这里的invocationhandler接口就是完成额外功能的,实现这个接口时要实现这个invoke方法,提到这个invoke方法是不是就联想到了Spring中的拦截器MethodInterceptor中的invoke方法?其实MethodInterceptor中的invoke方法就是对这一系列的操作进行了封装

invocationHandler接口中的invoke方法有三个参数,其中proxy忽略掉

method:额外功能所增加给的原始方法

args:原始方法的参数

method调用其invoke方法使得原始方法运行起来,那么我们想要添加额外功的就只需要添加在method.invoke的前后即可。这样额外功能的添加就完成了

2.1.2 代理对象和原始类实现相同的接口 interfaces

这里的参数interfaces是获取到原始对象实现的那个接口。通过获取类文件在获取接口来实现

2.1.3 类加载器ClassLoador

在一般创建对象的过程都是通过类加载器将对应的字节码文件加载到JVM,同时类加载器创建类的class对象,进而创建出这个类的对象。其中CL表示类加载器,同时获取这个类加载器也不需要我们担心,每一个类的.class文件都会自动分配一个

但是在创建动态代理类的时候是没有源文件的,它是通过动态字节码技术(Proxy.newProxyInstance)去创建字节码的。由于动态代理技术是直接将字节码文件写入JVM中的,并没有这个类加载器,但是我们又需要使用类加载器去帮我们创建代理类对象,那这个时候怎么办呢?借一个嘛!所以这就是参数中需要一个类加载器的原因

2.1.4 编码实现

创建接口

public interface UserService {
    void register();
    boolean login();
}

原始方法实现这个类 

public class UserServiceImpl implements UserService{
    @Override
    public void register() {
        System.out.println("register核心功能正在执行");
    }

    @Override
    public boolean login() {
        System.out.println("login核心功能正在执行");
        return false;
    }
}

添加额外功能 

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

public class TestJDKProxy {
    public static void main(String[] args) {
        // 创建原始对象
        UserService userService = new UserServiceImpl();
        // 以下是JDK动态代理创建
        
        // 实现InvocationHandler接口,为了方便演示采取内部类的方式
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 原始方法运行
                Object ret = method.invoke(userService,args);
                // 在原始方法后面添加额外功能
                System.out.println("aop底层实现----额外功能添加在原始功能后面----log");
                return ret;
            }
        };
        // TestJDKProxy.class.getClassLoader()借用一个类加载器,借谁的无所谓
        // userService.getClass().getInterfaces() 拿到原始类的接口
        // 使用相同的接口接收代理类
        UserService userServiceProxy =
                (UserService) Proxy.newProxyInstance(TestJDKProxy.class.getClassLoader(),userService.getClass().getInterfaces(),handler);

        // 调用核心方法 观察额外功能是否添加完成
        userServiceProxy.login();
        userServiceProxy.register();
    }
}

至此,JDK的动态代理原理就已经全部分析完了

2.2 Cglib动态代理

首先在开始Cglib动态代理之前,我们在回顾以下JDK动态代理的过程。JDK动态代理类通过与原始类实现同一个接口从而完成额外功能的添加。但是在现实开发的过程中有没有一种可能这个原始类没有实现任何的接口,那这个时候该怎么办呢?这个时候就需要使用Cglib动态代理来完成了

Cglib是怎么完成这个代理类的实现的呢?Cglib是采取了继承的方式来完成代理类的实现的

由于这里的Cglib动态代理的实现与JDK动态代理的实现是高度一致的,这里就只介绍二者的区别了,而不再介绍相同点了

2.2.1 Cglib动态代理编码实现

Cglib动态代理的实现中是通过Enhancer类中的一系列方法来完成的,通过setClassLoder方法去设置类加载器,通过setSuperClass方法设置父类对象,通过setCallback方法设置额外功能,当然这个额外功能也要去实现接口,这里的接口是MethodInterceptor(这个并不是Spring提供的那个接口,而是Cglib包中的接口),最后通过create方法创建动态代理对象

public class UserServiceImpl{
    public void register() {
        System.out.println("register核心功能正在执行");
    }

    public boolean login() {
        System.out.println("login核心功能正在执行");
        return false;
    }
}
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class TestCglib {
    public static void main(String[] args) {
        // 创建原始对象
        UserServiceImpl userService = new UserServiceImpl();
        // 以下是Cglib创建动态代理对象
        Enhancer enhancer = new Enhancer();
        // TestCglib.class.getClassLoader() 借用的类加载器
        enhancer.setClassLoader(TestCglib.class.getClassLoader());
        // userService.getClass() 获取到的父类对象
        enhancer.setSuperclass(userService.getClass());

        MethodInterceptor interceptor = new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                // 添加额外功能
                System.out.println("Cglib底层实现------额外功能添加在方法执行前---log");
                // 原始方法执行
                Object ret = method.invoke(userService, args);
                return ret;
            }
        };
        // 设置额外功能
        enhancer.setCallback(interceptor);
        // 创建动态代理对象
        UserServiceImpl userServiceCglib = (UserServiceImpl) enhancer.create();
        userServiceCglib.register();
        userServiceCglib.login();
    }
}

 

三、AOP如何通过原始对象的id获取到代理对象

3.1 BeanPostProcessor

在Spring中提供了一个接口BeanPostProcessor,这个接口是用来加工Spring通过配置文件创建的对象的。通过实现接口中的postProcessorAfterInitialization方法,就可以实现通过原始对象的id值获取到代理对象了(也就是通过这个接口对原始类进行再加工)

3.2 编码实现

首先在Spring的配置文件中创建UserServiceImpl的对象,这里是实现的接口,所以动态代理应该使用JDK动态代理的方式

public class UserServiceImpl implements UserService{
    @Override
    public void register() {
        System.out.println("register核心功能正在执行");
    }

    @Override
    public boolean login() {
        System.out.println("login核心功能正在执行");
        return false;
    }
}
<bean id="userService" class="com.gl.demo.proxy.UserServiceImpl"/>

创建好对象以后,创建一个类实现BeanPostProcessor接口为原始类进行加工,进而将代理类返回给用户

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

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

public class BeanPostProcessorTest implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        // 在这个方法中对需要添加额外功能的类进行加工
        // 这里采取JDK动态代理的方式进行加工
        // BeanPostProcessorTest.class.getClassLoader() 借用的类加载器
        // bean.getClass().getInterfaces()获取原始类的接口
        // 实现InvocationHandler接口添加额外功能
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object ret = method.invoke(bean, args);
                System.out.println("spring底层实现动态代理----额外功能添加在原始方法后---log");
                return ret;
            }
        };
        // 将代理类返回给用户而不是原始类
        return Proxy.newProxyInstance(BeanPostProcessorTest.class.getClassLoader(),bean.getClass().getInterfaces(),handler);
    }
}

最后将加工的好的代理对象配置在Spring的配置文件中

<bean id="proxyBeanProcessor" class="com.gl.demo.proxy.BeanPostProcessorTest"/>

这时候,用户通过原始类的id值拿到的是代理类而不是原始类了,进而完成了动态代理的过程

public void test4() {
    ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config2.xml");
    UserService userService = (UserService) ctx.getBean("userService");
    userService.register();
    userService.login();
}

至此,AOP底层原理就已经全部分析完毕了!以上的工作Spring其实都给我们封装好了,在日后的开发过程中直接使用就可以了,不用这么麻烦!

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

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

相关文章

PCL入门1之点云读取及可视化

0 引言 本文主要记录在Ubuntu系统的PCL点云库安装过程&#xff0c;以及PCL点云读取和可视化的c代码示例。 1 PCL安装 本文是安装了pcl1.8大版本&#xff0c;可先下载 下载pcl 1.8.1 版本&#xff08;点击Source code(zip&#xff09; 先安装pcl1.8.1所需的依赖库&#xff1…

【在英伟达nvidia的jetson-orin-nx和PC电脑ubuntu20.04上-装配ESP32开发调试环境-基础测试】

【在英伟达nvidia的jetson-orin-nx和PC电脑ubuntu20.04上-装配ESP32开发调试环境-基础测试】 1、概述2、实验环境3、 物品说明4、参考资料与自我总结5、实验过程1、创建目录2、克隆下载文件3、 拉取子目录安装和交叉编译工具链等其他工具4、添加环境变量6、将样例文件拷贝到桌面…

使用el-tree问题之清空勾数据不生效

一、问题场景描述 在做角色菜单按钮权限时&#xff0c;多数采用树结构勾选数据&#xff0c;这里使用了element中的el-tree。如下图&#xff1a; 1、我给角色1勾选了权限列表数据的前三行&#xff0c; 点击弹框的确定 2、紧接着点击角色2的权限按钮&#xff0c;给角色2分配修…

Lvs +keepalivede : 高可用集群

keepalived为Ivs应运而生的高可用服务。Ivs的调度器无法做高可用&#xff0c;于是keepalived这个软件。 实现的是调度器的高可用。 但是: keepalived不是专为Ivs集群服务的&#xff0c;也可以做其他代理服务器的高可用。 lvs的高可用集群&#xff1a;主调度器和备调度器&#…

[springboot源码分析]-Conditional

https://www.baeldung.com/spring-conditional-annotations Condition元数据 1 org.springframework.context.annotation.Conditional 1.1Conditional定义 Target({ElementType.TYPE, ElementType.METHOD}) Retention(RetentionPolicy.RUNTIME) Documented public interface…

区块链技术在现代商业中的应用:打造透明与信任的新经济体系

区块链技术以其独特的不可篡改和去中心化特点&#xff0c;在全球范围内受到了广泛的关注和讨论。从金融、供应链管理到版权保护和身份验证&#xff0c;区块链技术正在逐步改变着传统商业运营模式&#xff0c;为企业和消费者带来更加透明、安全和高效的商业环境。本文将深入探讨…

计算机网络第一章笔记

b站深入浅出计算机网络 微课视频 第一章 概述 因特网概述 区别&#xff1a; 若干节点和链路互连形成网络若干网络通过路由器互连形成互连网&#xff08;互联网&#xff09;因特网是当今世界上最大的互联网 发展的三个阶段&#xff1a; 1969年&#xff0c;第一个分组交换网…

MQTT协议和边缘计算

1.基本概念 MQTT是基于TCP/IP协议栈构建的异步通信消息协议&#xff0c;是一种轻量级的发布、订阅信息传输协议。可以在不可靠的网络环境中进行扩展&#xff0c;适用于设备硬件存储空间或网络带宽有限的场景。使用MQTT协议&#xff0c;消息发送者与接收者不受时间和空间的限制…

【AI视野·今日CV 计算机视觉论文速览 第273期】Mon, 23 Oct 2023

AI视野今日CS.CV 计算机视觉论文速览 Mon, 23 Oct 2023 Totally 73 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Computer Vision Papers Using Human-like Mechanism to Weaken Effect of Pre-training Weight Bias in Face-Recognition Convolutional Neural N…

力扣每日一题62:不同路径

题目描述&#xff1a; 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish” &#xff09;。 问总共有多少条不同的路径&#xff1f; 示例 1&#xff1a; 输入&#xff1a;m 3, n 7 输出&#xff1a;28 示例 2&#xff…

《红蓝攻防对抗实战》八.利用OpenSSL对反弹shell流量进行加密

前文推荐&#xff1a; 《红蓝攻防对抗实战》一. 隧道穿透技术详解《红蓝攻防对抗实战》二.内网探测协议出网之TCP/UDP协议探测出网《红蓝攻防对抗实战》三.内网探测协议出网之HTTP/HTTPS协议探测出网《红蓝攻防对抗实战》四.内网探测协议出网之ICMP协议探测出网《红蓝攻防对抗…

cmp云管平台专业厂商哪家好?有什么优势?

企业上云后&#xff0c;使用CMP云管平台就至关重要了&#xff0c;不仅方便云管理&#xff0c;还能节约云成本&#xff0c;还能保障云资源安全。但很多小伙伴不知道cmp云管平台专业厂商哪家好&#xff1f;有什么优势&#xff1f;这里就简单回答一下。 cmp云管平台专业厂商哪家好…

【QT】其他常用控件2

新建项目 lineEdit 什么都不显示&#xff08;linux password&#xff09; password textEdit和plainTextEdit spinBox和doubleSpinBox timeEdit、dateEdit、dateTimeEdit label 显示图案&#xff0c;导入资源&#xff1a;【QT】资源文件导入_复制其他项目中的文件到qt项目中_St…

医学图像分割利器:U-Net网络详解及实战

1 U-Net网络介绍 1.1 U-Net由来 2015年U-Net的出现使得原先需要数千个带注释的数据才能进行训练的深度学习神经网络大大减少了训练所需要的数据量&#xff0c;并且其针对神经网络在图像分割上的应用开创了先河。当时神经网络在图像分类任务上已经有了较好的成果&#xff0c;但…

出现了一个全新的编程语言——Mojo

最近&#xff0c;编程领域又一个黑马忽然冲进了开发者们的视野并正式开放下载。 Mojo 的简介 Mojo是一种新推出的编程语言&#xff0c;它将Python的简单性与Rust的速度和内存安全性结合在一起。 它处于开发的早期阶段&#xff0c;为用户提供了一个在线游乐场来探索其功能。 …

【JAVA学习笔记】 30 - Object类详解(equal,hashCode,toString)

项目代码 https://github.com/yinhai1114/Java_Learning_Code/tree/main/IDEA_Chapter08/src/com/yinhai/object_ 一、equal方法 和equal的对比 1.既可以判断基本类型&#xff0c;又可以判断引用类型&#xff0c;返回boolean值 2. 如果判断基本类型&#xff0c;判断的值是否相…

Vue 项目进行 SEO 优化

SSR 服务器渲染 服务端渲染, 在服务端 html 页面节点, 已经解析创建完了, 浏览器直接拿到的是解析完成的页面解构 关于服务器渲染&#xff1a;Vue 官网介绍 &#xff0c;对 Vue 版本有要求&#xff0c;对服务器也有一定要求&#xff0c;需要支持 nodejs 环境。 优势: 更好的 …

Mysql视图特性用户管理

目录 一、视图基本使用 二、用户管理 2.1 用户 ①用户信息 ②创建用户 tips:(解决无法创建用户) ③删除用户 ④修改用户密码 2.2数据库的权限 ①给用户授权 ②回收权限 视图&#xff1a;视图是一种虚拟表。视图是基于一个或多个基础表中的数据所创建的一个查询结果…

ATA-8061射频功率放大器应用领域介绍

ATA-8061射频功率放大器简介 ATA-8061是一款射频功率放大器。其P1dB输出功率500W&#xff0c;饱和输出功率1000W。增益数控可调&#xff0c;一键保存设置&#xff0c;提供了方便简洁的操作选择&#xff0c;可与主流的信号发生器配套使用&#xff0c;实现射频信号的完美放大。宽…

[common c/c++] 使用 posix 共享内存 和 mmap 实现 inter process function call

正文&#xff1a; mmap 可以映射某个文件的某块内存区域&#xff0c;因此可以通过 mmap 和 共享内存的方式将两个不同进程内的函数的所有二进制码映射到共享内存里&#xff0c;以实现跨进程的函数调用。 实际上&#xff0c;linux 动态库的动态链接正是通过mmap 把动态库文件 …