代理,反射,AOP

news2025/1/27 12:44:37

这篇文章主要讲三个点
1.设计模式中的代理模式
2.JAVA中的反射,因为用到了动态代理,这里举一下JDK代理和GCLIB代理的例子
3.介绍一下spring的aop是怎么用到了代理


1.设计模式中的代理模式


代理模式解决的问题:
在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。
和适配器模式的区别:
适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。 2、和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。

静态代理:
静态代理的实现思想就是说代理类持有一个被代理类
以租房为例,我们一般用租房软件、找中介或者找房东。这里的中介就是代理者。

首先定义一个提供了租房方法的接口。

public interface IRentHouse {
    void rentHouse();
}

定义租房的实现类

public class RentHouse implements IRentHouse {
    @Override
    public void rentHouse() {
        System.out.println("租了一间房子。。。");
    }
}

我要租房,房源都在中介手中,所以找中介

public class IntermediaryProxy implements IRentHouse {

    private IRentHouse rentHouse;

    public IntermediaryProxy(IRentHouse irentHouse){
        rentHouse = irentHouse;
    }

    @Override
    public void rentHouse() {
        System.out.println("交中介费");
        rentHouse.rentHouse();
        System.out.println("中介负责维修管理");
    }
}

这里中介也实现了租房的接口。

再main方法中测试

public class Main {

    public static void main(String[] args){
        //定义租房
        IRentHouse rentHouse = new RentHouse();
        //定义中介
        IRentHouse intermediary = new IntermediaryProxy(rentHouse);
        //中介租房
        intermediary.rentHouse();
    }
}

动态代理:
随着业务复杂度的增加,我们不可能创建非常多的代理类,对每一个业务都增加一个代理,就要提供通用的代理方法,这就要通过动态代理来实现了

动态代理是反射的一个非常重要的应用场景。动态代理常被用于一些 Java 框架中。例如 Spring 的 AOP ,Dubbo 的 SPI 接口,就是基于 Java 动态代理实现的。

动态代理的方式有两种:

JDK动态代理

利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
优点

JDK动态代理是JDK原生的,不需要任何依赖即可使用;
通过反射机制生成代理类的速度要比CGLib操作字节码生成代理类的速度更快;
缺点

如果要使用JDK动态代理,被代理的类必须实现了接口,否则无法代理;
JDK动态代理无法为没有在接口中定义的方法实现代理,假设我们有一个实现了接口的类,我们为它的一个不属于接口中的方法配置了切面,Spring仍然会使用JDK的动态代理,但是由于配置了切面的方法不属于接口,为这个方法配置的切面将不会被织入。
JDK动态代理执行代理方法时,需要通过反射机制进行回调,此时方法执行的效率比较低;

CGLIB动态代理

利用asm开源包,对代理对象类的class文件加载进来**,通过修改其字节码生成子类来处理。**
优点
使用CGLib代理的类,不需要实现接口,因为CGLib生成的代理类是直接继承自需要被代理的类;
CGLib生成的代理类是原来那个类的子类,这就意味着这个代理类可以为原来那个类中,所有能够被子类重写的方法进行代理;
CGLib生成的代理类,和我们自己编写并编译的类没有太大区别,对方法的调用和直接调用普通类的方式一致,所以CGLib执行代理方法的效率要高于JDK的动态代理;
缺点
由于CGLib的代理类使用的是继承,这也就意味着如果需要被代理的类是一个final类,则无法使用CGLib代理;
由于CGLib实现代理方法的方式是重写父类的方法,所以无法对final方法,或者private方法进行代理,因为子类无法重写这些方法;
CGLib生成代理类的方式是通过操作字节码,这种方式生成代理类的速度要比JDK通过反射生成代理类的速度更慢
JDK动态代理和CGLIB字节码生成的区别?

JDK动态代理只能对实现了接口的类生成代理,而不能针对类。
CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法。
因为是继承,所以该类或方法最好不要声明成final 。

JDK动态代理:
在 Java 的动态代理机制中,有两个重要的类(接口),一个是 InvocationHandler 接口、另一个则是 Proxy 类,这一个类和一个接口是实现我们动态代理所必须用到的。

public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

我们来看看 InvocationHandler 这个接口的唯一一个方法 invoke 方法:

Object invoke(Object proxy, Method method, Object[] args) throws Throwable

proxy - 代理的真实对象。
method - 所要调用真实对象的某个方法的 Method 对象
args - 所要调用真实对象某个方法时接受的参数

Proxy 这个类的作用就是用来动态创建一个代理对象的类,它提供了许多的方法,但是我们用的最多的就是 newProxyInstance 这个方法:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,  InvocationHandler h)  throws IllegalArgumentExcept

这个方法的作用就是得到一个动态的代理对象。

参数说明:

loader - 一个 ClassLoader 对象,定义了由哪个 ClassLoader 对象来对生成的代理对象进行加载。
interfaces - 一个 Interface 对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
h - 一个 InvocationHandler 对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个 InvocationHandler 对象上

JDK动态代理示例
首先我们定义了一个 Subject 类型的接口,为其声明了两个方法:

public interface Subject {
    void hello(String str);
    String bye();
}

接着,定义了一个类来实现这个接口,这个类就是我们的真实对象,RealSubject 类:

public class RealSubject implements Subject {

    @Override
    public void hello(String str) {
        System.out.println("Hello  " + str);
    }

    @Override
    public String bye() {
        System.out.println("Goodbye");
        return "Over";
    }
}

下一步,我们就要定义一个动态代理类了,前面说个,每一个动态代理类都必须要实现 InvocationHandler 这个接口,因此我们这个动态代理类也不例外:

public class InvocationHandlerDemo implements InvocationHandler {
    // 这个就是我们要代理的真实对象
    private Object subject;

    // 构造方法,给我们要代理的真实对象赋初值
    public InvocationHandlerDemo(Object subject) {
        this.subject = subject;
    }

    @Override
    public Object invoke(Object object, Method method, Object[] args)
        throws Throwable {
        // 在代理真实对象前我们可以添加一些自己的操作
        System.out.println("Before method");

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

        // 当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
        Object obj = method.invoke(subject, args);

        // 在代理真实对象后我们也可以添加一些自己的操作
        System.out.println("After method");
        System.out.println();

        return obj;
    }
}

最后,来看看我们的 Client 类:

public class Client {
    public static void main(String[] args) {
        // 我们要代理的真实对象
        Subject realSubject = new RealSubject();

        // 我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的
        InvocationHandler handler = new InvocationHandlerDemo(realSubject);

        /*
         * 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数
         * 第一个参数 handler.getClass().getClassLoader() ,我们这里使用handler这个类的ClassLoader对象来加载我们的代理对象
         * 第二个参数realSubject.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实行的接口,表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了
         * 第三个参数handler, 我们这里将这个代理对象关联到了上方的 InvocationHandler 这个对象上
         */
        Subject subject = (Subject)Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject
                .getClass().getInterfaces(), handler);

        System.out.println(subject.getClass().getName());
        subject.hello("World");
        String result = subject.bye();
        System.out.println("Result is: " + result);
    }
}

GCLIB代理:
目标类(一个公开方法,另外一个用final修饰):

public class Dog{
    
    final public void run(String name) {
        System.out.println("狗"+name+"----run");
    }
    
    public void eat() {
        System.out.println("狗----eat");
    }
}

方法拦截器

public class MyMethodInterceptor implements MethodInterceptor {

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("这里是对目标类进行增强!!!");
        //注意这里的方法调用,不是用反射哦!!!
        Object object = proxy.invokeSuper(obj, args);
        return object;
    }  
}

反射源码解析


下面我们来看看 JDK 的 invoke 方法到底做了些什么。

进入 Method 的 invoke 方法我们可以看到,一开始是进行了一些权限的检查,最后是调用了 MethodAccessor 类的 invoke 方法进行进一步处理,如下图红色方框所示。
在这里插入图片描述在这里插入图片描述在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

Method 类的 invoke 方法整个流程可以表示成如下的时序图:
在这里插入图片描述


spring的aop是怎么用到了代理


举个例子,如果我想要写一个注解,在方法执行前后各做一些操作,这时候就可以使用切面变成的方式去进行,这中间一样用到了动态代理,时间原因就不多写了

@Aspect
public class TestAspect {

    @Pointcut("execution(* *(..))")
    public void myPointcut() {}
    
    // 环绕通知:方法执行前后添加额外功能
    @Around(value = "myPointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("方法执行前打印~");
        Object result = joinPoint.proceed();
        System.out.println("方法执行后打印~");
        return result;
    }
}

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

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

相关文章

2022C语言知识点大全【详细、必备】

C语言期末必背知识点汇总【全】C语言最重要的知识点&#xff08;一&#xff09;《图片彩版》《C语言程序设计》必背基本知识点&#xff08;二&#xff09;C语言程序设计复习资料&#xff08;三&#xff09;C语言最重要的知识点&#xff08;一&#xff09;《图片彩版》 建议收藏…

Linux | 进程间通信 | system V共享内存 | 介绍和使用

文章目录system V共享内存介绍共享内存的创建shmget共享内存的获取shmctl&#xff0c;可用于共享内存的删除shmat && shmdt共享内存的使用Linux对system V的设计思路system V共享内存介绍 进程间通信的前提是&#xff1a;使不同进程看到同一份资源&#xff0c;在使用匿…

循环中的闭包

目录 1. 什么是闭包&#xff1f;闭包的作用&#xff1f; 1.1 可以访问 外部作用域 中变量的内部函数 1.2 闭包可以访问外部作用域中的变量及传参 2. 异步操作中 变量 的生命周期&#xff0c;取决于 闭包 的生命周期 2.1 Timer 定时器&#xff08;保留到 定时器回调执行完…

阿里内部总结的微服务笔记,从入门到精通,初学者也能学的会

前言 随着互联网的发展&#xff0c;网站应用的规模也在不断的扩大&#xff0c;进而导致系统架构也在不断的进行变化。 一、系统架构演变 从互联网早起到现在&#xff0c;系统架构大体经历了下面几个过程: 单体应用架构--->垂直应用架构--->分布式架构--->SOA 架构-…

Flutter 单元测试例子

Flutter 单元测试例子 原文 https://medium.com/app-dev-community/flutter-unit-testing-with-simple-examples-9c07499e4079 前言 执行单元测试来验证软件的每个组件。因此&#xff0c;我们需要尽可能多地测试每个单独的微 widget 。这些都是由开发人员在开发阶段完成的。单元…

回归分析(2) 一元回归模型

如上所述&#xff0c;为了易于确定回归函数μ(x)中的未知参数&#xff0c;我们来讨论变量Y与x之间存在着线性相关关系的情形 散布在某一条直线的周围&#xff0e;于是&#xff0c;我们可以用线性回归方程 来描述Y与x之间的相关关系&#xff0c;并假设相应的误差&#xff08;称为…

Go-zero框架学习+xorm+gorm配置

Go-zero框架学习xormgorm配置 文章目录Go-zero框架学习xormgorm配置一、框架介绍二、go-zero快速搭建1.下载go-zero2.安装goctl3.项目初始化4.测试5.项目结构6.快速生成rpc服务7.快速添加api接口8.快速生成model服务8.快速生成Dockerfile9.快速生成K8s部署文件三.golang的ORM框…

[go学习笔记.第十七章.redis的使用] 1.redis的使用

1.redis基本介绍 (1).Redis 是 NoSQL 数据库&#xff0c;不是传统的关系型数据库,官网: https://redis.io/ 和http://redis.cn/ (2).Redis: REmote Dlctionary Sever&#xff08;远程字典服务器&#xff09;, Redis 性能非常高&#xff0c;单机能够达到 15w qps,通常适合做缓存…

刷爆力扣之盛最多水的容器

刷爆力扣之盛最多水的容器 HELLO&#xff0c;各位看官大大好&#xff0c;我是阿呆 &#x1f648;&#x1f648;&#x1f648; 今天阿呆继续记录下力扣刷题过程&#xff0c;收录在专栏算法中 &#x1f61c;&#x1f61c;&#x1f61c; 该专栏按照不同类别标签进行刷题&#xff…

安卓的分区一点有用知识:super、lpunpack、lpdump

我们知道这个安卓的镜像分区有很多个。 那么这个文章要介绍什么呢&#xff1f; 三个点&#xff1a; 一是现在的android支持动态分区&#xff0c;很多的东西都被放到super分区里面了&#xff0c;这个应该是可以配置的。然后super里面有比如system、vendor这种比较大的分区。那…

教务排课系统毕业设计,大学排课系统设计与实现,排课系统论文作品参考

功能清单 【后台管理员功能】 录入分院&#xff1a;录入分院名称&#xff0c;简介&#xff0c;详情 分院管理&#xff1a;管理已经录入分院&#xff0c;支持修改和删除 老师录入&#xff1a;录入老师姓名、联系方式、头像、老师简介 老师管理&#xff1a;管理所有已经录入老师…

基于Matlab模拟用于海况海洋学研究的 X 波段雷达系统(附源码)

目录 一、定义雷达系统参数 二、对海面进行建模 三、配置雷达收发器 四、生成数据多维数据集 五、处理海面回波 六、总结 七、程序 海事雷达系统在充满挑战的动态环境中运行。为了改进对感兴趣目标的检测并评估系统性能&#xff0c;必须了解海面返回的性质。 在本例中&a…

【操作系统】2.4 死锁

这一节也非常重要 2.4.1 死锁的概念 2.4.1 死锁的概念_StudyWinter的博客-CSDN博客 在并发环境下&#xff0c;各种进程因竞争资源而造成的一种互相等待对方手里的资源&#xff0c;导致各进程都阻塞&#xff0c;都无法向前推进的现象。这就是死锁&#xff0c;死锁发生后&#…

Jest API使用方法

如上面的知识图谱所示&#xff0c;一个常见的测试框架通常需要实现这些功能: ● before/after 钩子函数: 如beforeEach&#xff0c;afterEach&#xff0c; ● Mock方法&#xff1a; 函数Mock&#xff0c;时间mock等。 ● 断言: 判断一个描述是否正确&#xff0c;在Jest中常为 e…

你心心念念的RabbitMQ个人实践来了来了它来了

前言 MQ&#xff08;Message Queue&#xff09;就是消息队列&#xff0c;其有点有很多&#xff1a;解耦、异步、削峰等等&#xff0c;本文来聊一下RabbitMQ的一些概念以及使用。 RabbitMq 案例 Springboot整合RabbitMQ简单案例 基本概念 Exchange&#xff1a;消息交换机&a…

云原生系列 【基于CCE Kubernetes编排实战二】

✅作者简介&#xff1a; CSDN内容合伙人&#xff0c;全栈领域新星创作者&#xff0c;阿里云专家博主&#xff0c;阿里云问答板块版主&#xff0c;华为云享专家博主&#xff0c;掘金后端评审团成员 &#x1f495;前言&#xff1a; 最近云原生领域热火朝天&#xff0c;那么云原生…

Hystirx限流:信号量隔离和线程池隔离

背景&#xff1a; 最近工作中要处理服务高并发的问题&#xff0c;大流量场景下限流熔断降级可以说是必不可少的&#xff0c;打算对限流做一次改造&#xff0c;所以要先了解一下hytrix相关内容&#xff0c;比如了解一下线程池隔离和信号量隔离的区别。 **信号量&#xff1a;**信…

[网络工程师]-应用层协议-DHCP

BOOTP是最早的主机配置协议&#xff0c;动态主机配置协议&#xff08;Dynamic Host Configuration Protocol&#xff0c;DHCP&#xff09;则是在其基础上进行了改良的协议&#xff0c;是一种用于简化主机IP配置管理的IP管理标准。通过DHCP协议&#xff0c;DHCP服务器为DHCP客户…

集合学习笔记——Collection 全家桶

Collection是我们日常开发中使用频率非常高的集合&#xff0c;它的主要实现有List和Set,区别是List是有序的&#xff0c;元素可以重复;Set是无序的&#xff0c;元素不可以重复&#xff0c;我们简单看下继承关系&#xff1a; List的实现类主要线程不安全的ArrayList和LinkedList…

推挽输出和开漏输出-三极管-mos管

一、推挽输出 1.1推挽输出的概念 推挽&#xff08;push-pull&#xff09;输出是由两个MOS或者三极管组成&#xff0c;两个管子始终保持一个导通&#xff0c;另一个截止的状态。 图1 推挽电路示意图 当输入高电平时&#xff0c;叫做推&#xff1b; 上管Q1导通&#xff0c;下管…