Spring框架-AOP底层实现原理

文章目录

    • AOP底层实现原理
      • AOP实现原理分析
      • Java设计模式(代理模式)
        • 静态代理
        • JDK动态代理
        • CGLIB动态代理
    • AOP操作术语

AOP底层实现原理

AOP实现原理分析

1、AOP采取横向抽取机制,取代传统的纵向抽取机制(继承关系)。

2、AOP底层实现原理是动态代理(JDK动态代理+CGLIB动态代理),就是在不修改原有类对象方法的源代码基础上,通过代理对象实现对原有类对象方法的增强,也就是拓展原有类对象的功能。

Java设计模式(代理模式)

Java中的设计模式-代理模式

  • 静态代理(定义一个平级的实现类完成代理模式)

  • 动态代理

    • JDK动态代理(定义拦截器实现InvocationHandler接口,通过Proxy产生代理对象)

    • CGLIB动态代理(定义拦截器实现MethodInterceptor接口,通过Enhancer类生成代理对象)

静态代理

代理对象和实际对象都继承了同一个接口,在代理对象中指向的是实际对象的实例,这样对外暴露的是代理对象而真正调用的是 Real Object.

静态代理就是通过创建平级的实现类的形式完成代理,能够实现代理的优势,但是同时缺点也很多:

以下就是基于静态代理进行的操作。静态代理存在如下几个缺点:

1、如果项目中存在N个接口,那么就会产生N个对应的实现类。我们的代理类也会随着实现类的增加而增加。

2、随着接口中的功能的增加,实现类中的功能也会同步增加。我们的代理类中的增强方法也会增加。

3、从严格意义上来讲:我们定义的增强的内容(事务处理)。其实是没有达到彻底复用。

4、优点:就是通过代理的方式,完成了目标对象的代理,并且在方法中置入了增强的处理,而且没有针对源代码进行修改。

  • 需要代理的接口
package com.sin.service;

/**
 * @author sin
 * @date 2022/10/27
 * @apiNote
 */
public interface ProxySubject {
    public String inrtroduce(String name);
}

  • 实现类
package com.sin.service.impl;

import com.sin.service.ProxySubject;

/**
 * @author sin
 * @date 2022/10/27
 * @apiNote
 */
public class ProxySubjectImpl implements ProxySubject {
    @Override
    public String inrtroduce(String name) {
        System.out.println("我的名字是:" + name);
        return name;
    }
}
  • 代理类
package com.sin.proxy;

import com.sin.service.ProxySubject;
import com.sin.service.impl.ProxySubjectImpl;

/**
 * @author sin
 * @date 2022/10/27
 * @apiNote
 */
public class StaticProxy implements ProxySubject {

    private ProxySubjectImpl proxySubject;


    public StaticProxy(ProxySubjectImpl proxySubject){
        this.proxySubject = proxySubject;
    }

    @Override
    public void inrtroduce(String name) {
        System.out.println("调用前业务");
        proxySubject.inrtroduce(name);
        System.out.println("调用后的业务");
    }
}
  • 测试
package com.sin.test.proxy;

import com.sin.proxy.StaticProxy;
import com.sin.service.ProxySubject;
import com.sin.service.impl.ProxySubjectImpl;
import org.junit.Test;

/**
 * @author sin
 * @date 2022/10/27
 * @apiNote
 */
public class StaticProxyTest {
    @Test
    public void test(){
        ProxySubjectImpl  proxySubject = new ProxySubjectImpl();

        StaticProxy staticProxy = new StaticProxy(proxySubject);
        staticProxy.inrtroduce("张三");
    }
}
  • 测试结果

在这里插入图片描述

JDK动态代理

动态代理就是,在程序运行期,创建目标对象的代理对象,并对目标对象中的方法进行功能性增强的一种技术。在生成代理对象的过程中,目标对象不变,代理对象中的方法是目标对象方法的增强方法。可以理解为运行期间,对象中方法的动态拦截,在拦截方法的前后执行功能操作。

Java1.3 以后, JAVA 实现了动态代理技术,允许开发者在运行期创建接口的代理实例 。

JDK 的动态代理主要使用的是 java.lang.reflect 包中的Proxy 和 InvocationHandler。

  • InvocationHandler 是接口,可以通过实现该接口来定义横切逻辑,并通过反射机制调用目标类的代码,动态地将横切逻辑和业务逻辑编织在一起 。
  • Proxy 利用 InvocationHandler 动态创建出一个实现某一接口的实例,来生成目标类的代理对象 。
//自定义定获取被代理接口实现类的实例对象
public <T> T getProxy() {
	        return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), 
                                              target.getClass().getInterfaces(), 
                                              this);
}

提示!

jdk动态代理必须要依赖于接口。

  • 需要动态代理的接口
package com.sin.service;

/**
 * 需要动态代理的接口
 * @author sin
 * @date 2022/10/27
 * @apiNote
 */
public interface JDKAgentSubjectService {
    /**
     * 你好
     * @param name
     * @return
     */
    public String sayHello(String name);

    /**
     * 再见
     * @return
     */
    public String sayGoodBye();
}

  • 需要代理的实际对象
package com.sin.service.impl;

import com.sin.service.JDKAgentSubjectService;

/**
 * 实例接口对象
 *
 * @author sin
 * @date 2022/10/27
 * @apiNote
 */
public class JDKAgentSubjectServiceImpl implements JDKAgentSubjectService {
    /**
     * 你好
     *
     * @param name
     * @return
     */
    @Override
    public String sayHello(String name) {
        return "你好" + name;
    }

    /**
     * 再见
     *
     * @return
     */
    @Override
    public String sayGoodBye() {
        return "再见";
    }
}

  • 调用处理器实现类
package com.sin.proxy;

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

/**
 * @author sin
 * @date 2022/10/27
 * @apiNote
 */
public class InvocationHandlerImpl  implements InvocationHandler {

    //要代理的对象
    private Object subject;

    public InvocationHandlerImpl(Object subject){
        this.subject = subject;
    }

    /**
     * 负责集中处理动态代理上的所有方法调用
     * @param proxy 代理类实例
     * @param method 被调用的方法对象
     * @param args 调用参数
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("模拟的添加操作");

        System.out.println("Method" + method);

        Object value  = method.invoke(subject,args);

        System.out.println("接收后模拟的添加操作");

        return value;
    }
}

  • 测试
package com.sin.test.proxy;

import com.sin.proxy.InvocationHandlerImpl;
import com.sin.service.JDKAgentSubjectService;
import com.sin.service.impl.JDKAgentSubjectServiceImpl;
import org.junit.Test;

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

/**
 * @author sin
 * @date 2022/10/27
 * @apiNote
 */
public class InvocationHandlerTest {

    @Test
    public void testReal(){
        //代理的对象
         Object  subjectService =new JDKAgentSubjectServiceImpl();

        /**
         * InvocationHandlerImpl 实现了InvocationHandler接口,实现方法调用从代理类到委托类的发派转发
         * 内部包含了指向委托类实例的引用,用于真正执行分派转发过来的方法调用
         * 要代理那个对象就将那个对象传进来,最后通过该对象来调用其方法
         */
        InvocationHandler handler=  new InvocationHandlerImpl(subjectService);

        ClassLoader loader = subjectService.getClass().getClassLoader();
        Class[] interfaces = subjectService.getClass().getInterfaces();

        JDKAgentSubjectService subject = (JDKAgentSubjectService) Proxy.newProxyInstance(loader,interfaces,handler);

        System.out.println("要动态代理对象的类型:"+subject.getClass().getName());

        String hello = subject.sayHello("张三");
        System.out.println(hello);

        String bye = subject.sayGoodBye();
        System.out.println(bye);
    }
}
  • 测试结果

在这里插入图片描述

CGLIB动态代理

采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑,来完成动态代理的实现。

实现方式实现 MethodInterceptor 接口,重写 intercept 方法,通过 Enhancer 类的回调方法来实现。

  • 要代理对象接口
package com.sin.service;

/**
 * @author sin
 * @date 2022/10/27
 * @apiNote
 */
public interface UserServiceCGlib {
    public void addUser();
}
  • 要代理对象接口实现类
package com.sin.service.impl;

import com.sin.service.UserServiceCGlib;

/**
 * @author sin
 * @date 2022/10/27
 * @apiNote
 */
public class UserServiceCGlibImpl implements UserServiceCGlib {
    @Override
    public void addUser() {
        System.out.println("增加了一条用户");
    }
}
  • CGlib动态代理实现类
package com.sin.proxy;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * CGlib 动态代理实现类
 * @author sin
 * @date 2022/10/27
 * @apiNote
 */
public class MethodInterceptorImpl implements MethodInterceptor {

    //CGlib需要代理的对象
    private Object object;

    public Object createProxyObject( Object o){
         this.object = o;

         //通过CGlib动态代理获取代理对象的过程
        Enhancer enhancer = new Enhancer();

        //设置Enhancer对象的父类
        enhancer.setSuperclass(o.getClass());

        //设置Enhancer的回顾对象
        enhancer.setCallback(this);

        //设置Enhancer对象的父类
        Object o1 = enhancer.create();
        return o1;
    }


    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        Object obj ;
        System.out.println("调用前的业务");
        obj = method.invoke(object,objects);
        System.out.println("调用后的业务");
        return obj;
    }
}
  • 测试
package com.sin.test.proxy;

import com.sin.proxy.MethodInterceptorImpl;
import com.sin.service.impl.UserServiceCGlibImpl;
import org.junit.Test;

/**
 * @author sin
 * @date 2022/10/27
 * @apiNote
 */
public class MethodInterceptorTest {
    @Test
    public void test(){
        UserServiceCGlibImpl userServiceCGlib = new UserServiceCGlibImpl();

        MethodInterceptorImpl methodInterceptorimpl = new MethodInterceptorImpl();

        UserServiceCGlibImpl serviceCGlib  = (UserServiceCGlibImpl)methodInterceptorimpl.createProxyObject(userServiceCGlib);

        serviceCGlib.addUser();
    }
}
  • 测试结果

在这里插入图片描述

静态代理

  • 静态代理模式并没有做到事务的重用
  • 假设dao有100个类,100个proxy,接口中有多少方法,在proxy层就得实现多少方法,有多少方法就要开启和提交多少事务
  • 如果一个proxy实现了多个接口,如果其中的一个接口发生变化(添加了一个方法),那么proxy也要做相应改变

JDK动态代理总结

动态代理类:在程序运行时,运用反射机制动态创建而成。
JDK的动态代理必须具备四个条件:1、目标接口 2、目标类 3、拦截器 4、代理类

  • 因为利用JDKProxy生成的代理类实现了接口,所以目标类中所有的方法在代理类中都有。
  • 生成的代理类的所有的方法都拦截了目标类的所有的方法。而拦截器中invoke方法的内容正好就是代理类的各个方法的组成体。
  • 利用JDKProxy方式必须有接口的存在。
  • invoke方法中的三个参数可以访问目标类的被调用方法的API、被调用方法的参数、被调用方法的返回类型。
  • 缺点:
    • 在拦截器中除了能调用目标对象的目标方法以外,功能是比较单一的,在这个例子中只能处理事务
    • 使用JDK创建代理有一个限制,即JDK动态代理只能为接口创建代理。

CGLIB动态代理总结:

  • CGlib是一个强大的,高性能,高质量的Code生成类库。它可以在运行期扩展Java类与实现Java接口。
  • 用CGlib生成代理类是目标类的子类。
  • 用CGlib生成 代理类不需要接口
  • 用CGLib生成的代理类重写了父类的各个方法。
  • 拦截器中的intercept方法内容正好就是代理类中的方法体

JDK代理和CGLib 代理区别

  • JDK动态代理只能针对接口实现类生成代理实例,而不能针对类;也就是说它是面向接口的
  • CGLIB是针对类实现代理,主要是对指定的类生成一个子类,并覆盖其中方法实现增强,但是因为采用的是继承,
  • JDK 代理是java 语言自带的,无需加载第三方类去实现,CGLIB是一个基于ASM实现的
  • 使用动态代理的对象必须实现一个或多个接口
  • 使用cglib代理的对象则无需实现接口,达到代理类无侵入。

AOP操作术语

  • 目标对象(target)

    • 目标对象指要增加的对象,即包含业务逻辑的类对象
  • 连接点(Joinpoint)

    • 在一个类中,可以被做增强处理的那些方法,称为“连接点”。
  • 代理对象(Proxy)

    • AOP中会通过代理的方式,对目标生成一个代理对象,代理对象中会加入需要增强功能,通过代理对象来间接的方式目标对象,起到增强目标对象的效果;
  • 通知(Advice)

    • 需要在目标对象中增加的功能,通知中有两个重要信息:方法的什么地方,执行什么操作
  • 切入点(Pointcut)

    • 用来指定需要将通知使用到哪些地方,比如需要用在哪些类的哪些方法上,切入点就时做这个配置的
  • 切面(Aspect)

    • 通知(Advice)和切入点(Pointcut)的组合。切面来定义在哪些地方(Pointcut)执行什么操作(Advice)。
  • 顾问(Advisor)

    • Advisor(顾问) 其实它就是 Pointcut(切入) 与 Advice(通知) 的组合,Advice (通知) 是要增强的逻辑,而增强的逻辑要在什么地方执行是通过Pointcut(点)来指定的,所以 Advice(顾问) 必需与 Pointcut(切入点) 组合在一起,这就诞生了 Advisor(顾问) 这个类,spring Aop中提供了一个Advisor(顾问)接口将Pointcut切入点(切入点) 与 Advice (通知)的组合起来。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

腾讯云一键部署搭建幻兽帕鲁联机服务器教程

幻兽帕鲁&#xff08;Palworld&#xff09;是一款多人在线游戏&#xff0c;为了获得更好的游戏体验&#xff0c;许多玩家选择自行搭建游戏联机服务器&#xff0c;但是如何搭建游戏联机服务器成为一个难题&#xff0c;腾讯云提供了游戏联机服务器一键部署方案&#xff0c;让大家…

Java笔记 --- 五、File

五、File 概述 将字符串变成File对象&#xff0c;再去使用里面的方法 父级路径&#xff1a;除了文件本身的路径 C:\Users\Desktop 子级路径&#xff1a;文件名 m.txt 常见的成员方法 判断、返回 length 只能获取文件的大小(字节数量) 创建、删除 delete方法默认只能删除…

搜索<2>——记忆化搜索与剪枝

Part 1:记忆化搜索 记忆化搜索其实就是拿个数组记录下已经得到的值&#xff0c;这样再遇到的时候直接调用即可。 P1464: 虽然此题好像不用记忆化也行&#xff0c;但我们还是老老实实写个记忆化吧。没什么困难的地方&#xff0c;就是它叫你怎么干你就怎么干&#xff0c;记得开…

【Java 数据结构】栈和队列

栈和队列 1. 栈(Stack)1.1 概念1.2 栈的使用1.3 栈的模拟实现1.4 栈的应用场景1.5 概念区分 2. 队列(Queue)2.1 概念2.2 队列的使用2.3 队列模拟实现2.4 循环队列 3. 双端队列 (Deque)4. 面试题 1. 栈(Stack) 1.1 概念 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在…

Cyberdog2 docker环境软件源无法被验证问题

搭建docker系统后更新软件源sudo apt-get update出现异常 经过查询GPT&#xff0c;使用如下方式成功解决 从keyserver.ubuntu.com获取缺失的公钥&#xff0c;并添加到apt-key中。具体命令如下&#xff1a; gpg --keyserver keyserver.ubuntu.com --recv-keys F42ED6FBAB17C6…

怎么把文章变成视频?原来这么简单

大家有没有发现&#xff0c;在各个平台浏览文章的时候总会发现很多图文相结合的长篇文章&#xff0c;对于不喜欢看长图文的人来说&#xff0c;长篇的图文会带来很多的负担&#xff0c;于是就有很多人想要把长篇的图文转换成视频&#xff0c;那么该如何转换呢&#xff1f; 首先&…

CMake简明教程 笔记

推荐B站视频&#xff1a;1.1 Cmake构建项目的流程_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1xa4y1R7vT?p1&vd_sourcea934d7fc6f47698a29dac90a922ba5a3 >>目录 1&#xff09;CMake初体验 CMake构建流程Windows下使用CMake构建项目Linux下使用CMake构…

C#,数据检索算法之插值搜索(Interpolation Search)的源代码

数据检索算法是指从数据集合&#xff08;数组、表、哈希表等&#xff09;中检索指定的数据项。 数据检索算法是所有算法的基础算法之一。 本文提供插值搜索&#xff08;Interpolation Search&#xff09;的源代码。 1 文本格式 using System; namespace Legalsoft.Truffer.…

08.Elasticsearch应用(八)

Elasticsearch应用&#xff08;八&#xff09; 1.为什么需要相关性算分 我们在文档搜索的时候&#xff0c;匹配程度越高的相关性算分越高&#xff0c;算分越高的越靠前&#xff0c;但是有时候我们不需要算分越高越靠前我们可能需要手动影响算分来控制顺序比如广告&#xff08…

一文搞懂Secure Boot (安全启动)

何为安全启动&#xff1f; 随着汽车新四化的发展&#xff0c;尤其是网联化及自动驾驶的推进&#xff0c;汽车网络信息安全显得越来越重要。 随着高级驾驶辅助(ADAS)及自动驾驶的推出&#xff0c;车辆动力及制动控制需要部分或全部授权给智能驾驶系统&#xff0c;而车辆又暴露…

怎么测试app?app的测试技巧是什么?

前言 今天笔者想和大家来唠唠app测试&#xff0c;现在的app有非常的多&#xff0c;这些app都是需要经过测试之后才能发布到应用市场中&#xff0c;app已经成为了我们日常生活中不可或缺的一部分了&#xff0c;但它的功能必须强大&#xff0c;才能受到消费者的重视&#xff0c;…

WordPress如何自定义日期和时间格式?附PHP日期和时间格式字符串

WordPress网站在很多地方都需要用到日期和时间&#xff0c;那么我们应该在哪里设置日期和时间呢&#xff1f;又如何自定义日期和时间格式呢&#xff1f;下面boke112百科就跟大家一起来学习一下PHP标准化的日期和时间格式字符串。 特别说明&#xff1a;格式字符是标准化的&#…

【控制算法笔记】卡尔曼滤波(一)——基本概念和一维卡尔曼估计实现(python,C++)

本文是个人学习笔记&#xff0c;包含个人理解&#xff0c;如有错误欢迎指正。 前言–关于Kalman Filter 在工程实践中卡尔曼滤波器的应用场景非常丰富&#xff0c;尤其是针对需要大量连续数据处理的自动驾驶和工业现场控制场景中&#xff0c;几乎离不开卡尔曼滤波的踪迹。 在多…

类和对象 第五部分第二小节:左移运算符重载

作用&#xff1a;可以输出自定义数据类型 代码案例&#xff1a; 1.成元函数重载&#xff1a; 利用成员函数重载写出来的代码为 void operate<<(cout)等于p<<cout&#xff0c;与预期效果不符。因此我们不会利用成员函数重载<<运算符&#xff0c;因为无法实现c…

06.领域驱动设计:使用DDD分层架构,可以有效降低层与层之间的依赖

目录 1、概述 2、什么是DDD分层架构 1.用户接口层 2.应用层 3.领域层 4.基础层 3、DDD分层架构最重要的原则是什么 4、DDD分层架构如何推动架构演进 1.微服务架构的演进 2.微服务内服务的演进 5、三层架构如何演进到DDD分层架构 我们该怎样转向DDD分层架构 6、总结…

0127-2-Vue深入学习5—Vue-Router路由模式

1、Vue-Router三种路由模式&#xff1a; hash&#xff1a;#️⃣使用URL hash 值来做路由&#xff0c;支持所有路由器&#xff1b;history:&#x1f4d6;依赖HTML5 History API和服务器配置&#xff1b;abstract:⛓支持所有JS运行环境&#xff0c;Node.js服务端&#xff1b; 1.1…

陪诊小程序开发:让医疗服务更贴心

随着社会的发展和人口老龄化的加剧&#xff0c;医疗服务的需求日益增长。在这个背景下&#xff0c;陪诊小程序的开发应运而生&#xff0c;为医疗服务提供了更加便捷、高效的解决方案。本文将探讨陪诊小程序开发的意义、功能、优势以及未来发展趋势。 一、陪诊小程序开发的意义…

ES -倒排索引

倒排索引 在学习ES中的映射之前&#xff0c;我们先学习一下ES中的倒排索引。 定义 倒排索引就是单词到文档id的关系&#xff0c;如下所示&#xff0c;左边是一个正排索引&#xff0c;右边就是一个单词到文档id的倒排索引&#xff1a; 倒排表以字或词为关键字进行索引&#x…

XCTF:Normal_RSA[WriteUP]

从题目中获取到两个文件 flag.enc内容是通过rsa加密了的密文 pubkey.pem是rsa公钥&#xff0c;加密者利用这个文件对flag原文进行了加密 如果对rsa加密算法不了解的可以补一下教学视频 数学不好也能听懂的算法 - RSA加密和解密原理和过程_哔哩哔哩_bilibili 使用openssl对公…

【前端web入门第二天】02 表单-input标签-单选框-多选框

表单 文章目录: 1.input标签基本使用 1.1 input标签占位文本1.2 单选框 radio 1.3 多选框 checkbox 作用:收集用户信息。 使用场景: 登录页面注册页面搜索区域 1.input标签基本使用 input标签type属性值不同&#xff0c;则功能不同。 <input type"..."&g…