• 代理,反射,AOP


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


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


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

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

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

    public interface IRentHouse {
        void rentHouse();
    }
    
    • 1
    • 2
    • 3

    定义租房的实现类

    public class RentHouse implements IRentHouse {
        @Override
        public void rentHouse() {
            System.out.println("租了一间房子。。。");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

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

    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("中介负责维修管理");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

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

    再main方法中测试

    public class Main {
    
        public static void main(String[] args){
            //定义租房
            IRentHouse rentHouse = new RentHouse();
            //定义中介
            IRentHouse intermediary = new IntermediaryProxy(rentHouse);
            //中介租房
            intermediary.rentHouse();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

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

    动态代理是反射的一个非常重要的应用场景。动态代理常被用于一些 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;
    }
    
    • 1
    • 2
    • 3
    • 4

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

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

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

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

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

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

    参数说明:

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

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

    public interface Subject {
        void hello(String str);
        String bye();
    }
    
    • 1
    • 2
    • 3
    • 4

    接着,定义了一个类来实现这个接口,这个类就是我们的真实对象,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";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    下一步,我们就要定义一个动态代理类了,前面说个,每一个动态代理类都必须要实现 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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    最后,来看看我们的 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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

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

    public class Dog{
        
        final public void run(String name) {
            System.out.println("狗"+name+"----run");
        }
        
        public void eat() {
            System.out.println("狗----eat");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    方法拦截器

    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;
        }  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    反射源码解析


    下面我们来看看 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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
  • 相关阅读:
    各省、市转移支付数据集-分专项转移支付、一般转移支付、税收返还
    C#里的var和dynamic区别到底是什么,你真的搞懂了嘛
    树链剖分 点权下放边权
    Mysql8与mariadb的安装与常用设置
    内网穿透什么意思?内网穿透基础知识原理内网穿透服务器搭建可以干嘛服务器端口映射无需公网IP教程方法
    SQL Server报错:数据库"YourDatabaseName"的事务日志已满,原因为"LOG_BACKUP"
    鹿蜀:一个基于日常开发任务体现开发人员工作状况的系统
    python经典百题之矩阵对角线之和
    会员积分商城系统的功能介绍
    AndroidStudio最下方显示不出来Terminal等插件
  • 原文地址:https://blog.csdn.net/xiaocaij_icai/article/details/127758719