代理相关的知识集

目录

说到代理我们要先知道代理的分类

代理大方向分为两类:

Ⅰ静态代理

Ⅱ动态代理

        jdk动态代理

        cglib动态代理

下面我们来看看各种代理之间的区别和实现方式吧

        

静态代理            

静态代理的实现方法:

静态代理的实际应用:

动态代理

        JDK动态代理

                 实现原理:

说到代理我们要先知道代理的分类

        代理大方向分为两类:

                Ⅰ静态代理

                Ⅱ动态代理

                        jdk动态代理
                        cglib动态代理

下面我们来看看各种代理之间的区别和实现方式吧

        

            

静态代理            

静态代理是一种代理模式,它通过手动编写代理类的代码,将被代理对象的方法调用委托给代理类,从而实现对被代理对象的控制。静态代理的实现方法包括两个关键角色:代理类和被代理类。

静态代理的实现方法:

  1. 定义接口: 定义一个接口,包含被代理对象和代理对象都需要实现的方法。

    public interface Subject { 
        void request(); 
    }

  2. 实现被代理类: 被代理类实现接口,提供具体的业务逻辑。

    public class RealSubject implements Subject { 
        @Override 
        public void request(){ 
        System.out.println("RealSubject: Handling request."); 
        } 
    }
  3. 实现代理类: 代理类也实现接口,并在其中包含一个对被代理对象的引用,代理类中的方法实际上是对被代理对象方法的调用,可以在调用前后添加额外的逻辑。

    public class ProxySubject implements Subject {
        private RealSubject realSubject;
    
        public ProxySubject(RealSubject realSubject) {
            this.realSubject = realSubject;
        }
    
        @Override
        public void request() {
            System.out.println("ProxySubject: Before request.");
            realSubject.request();
            System.out.println("ProxySubject: After request.");
        }
    }

  4. 使用代理: 在客户端代码中使用代理对象。

    public class Client {
        public static void main(String[] args) {
            RealSubject realSubject = new RealSubject();
            ProxySubject proxy = new ProxySubject(realSubject);
            proxy.request();
        }
    }

静态代理的实际应用:

  • 安全检查: 在调用被代理对象的方法前进行权限或安全检查。
  • 日志记录: 在方法调用前后记录日志信息。
  • 性能监控: 统计方法的执行时间等性能指标。

动态代理

        动态代理的作用

                1)减少代码的重复
                2)不修改代码增加功能,符合开闭原则(对扩展开放,对修改关闭)
                3)降低耦合,减少业务代码和非业务代码的关系
                4)更加注重业务代码的实现

        JDK动态代理

                 实现原理:

                        使用 java.lang.reflect.Proxy 类和 java.lang.reflect.接口。

                        在运行时动态生成一个实现被代理接口的代理类。

                        通过 InvocationHandler 接口的 invoke 方法进行方法的调用处理。

                适用场景:

                        被代理类必须实现接口。

                        适用于横切关注点与业务逻辑之间耦合较弱的情况。

                实现方法:

        1.定义一个被代理类



/**
 * @author tong
 */
public class Calculator {

    public int add(int i, int j) {
        int result = i + j;
        System.out.println("方法内部 result = " + result);
        return result;
    }

    public int sub(int i, int j) {
        int result = i - j;
        System.out.println("方法内部 result = " + result);
        return result;
    }

    public int mul(int i, int j) {
        int result = i * j;
        System.out.println("方法内部 result = " + result);
        return result;
    }

    public int div(int i, int j) {
        int result = i / j;
        System.out.println("方法内部 result = " + result);
        return result;
    }
}

       2.生产代理对象的工厂类 ProxyFactory:

/**
 * 动态代理有两种:
 * 1. jdk动态代理,要求必须有接口,最终生成的代理类和目标类实现相同的接口,在com.sun.proxy包下,类名为$proxy+数字 (例如:$proxy6)
 * 2. cglib动态代理,最终生成的代理类会继承目标类,并且和目标类在相同的包下
 */
public class ProxyFactory {
    private Object target;
    public ProxyFactory(Object target) {
        this.target = target;
    }
    // 通过该方法可以生成任意目标类所对应的代理类
    public Object getProxy(){
        /**
         * newProxyInstance():创建一个代理实例
         * 其中有三个参数:
         * 1、classLoader:指定加载动态生成的代理类的类加载器(注:所有引入的第三方类库以及
        自己编写的java类 都是由 应用类加载器 负责加载的)
         【根类加载器(Bootstrap)> 扩展类加载器(Extension)> 系统类加载器(System)
         系统类加载器又称为应用类加载器】
         * 2、interfaces:获取目标对象实现的所有接口的class对象所组成的数组
         * 3、invocationHandler:设置代理对象实现目标对象的接口的方法的过程,即代理类中如何
        重写接口中的抽象方法
         */
        //第一个参数,获取代理对象的类加载器 (类加载器是程序中默认的类加载器,一般来说,Java
        应用的类都是由它来完成加载。所以,此处通过代理类或者被代理类获取到的类加载器都是同一个,
        或通过任何一个类获取到的类加载器都是同一个。)
        ClassLoader classLoader = this.getClass().getClassLoader();
        //第二个参数,被代理对象实现的所有接口数组
        Class<?>[] interfaces = target.getClass().getInterfaces();
        //当通过代理类的对象发起对被重写的方法的调用时,都会转换为对如下的invoke方法的调用
        //代理实例的调用处理程序
        //第三个参数InvocationHandler的实现类,这里用了匿名内部类的方式
        InvocationHandler invocationHandler = new InvocationHandler() {
             //重写InvocationHandler的invoke方法,他有三个参数可以供我们使用
            @Override
            public Object invoke(Object proxy, Method method, Object[] args){
                /**
                 * proxy:表示代理对象
                 * method:表示要执行的方法(代理对象需要实现的抽象方法,即其中需要重写的和
        目标类同名的方法)
                 * args:表示要执行的方法的参数列表(method所对应方法的参数列表)
                 */
                Object result = null;
                try {
                    //method.getName(): 返回此method对象表示的方法的名称,作为字符串
                    System.out.println("[动态代理][日志] "+method.getName()+",参数:"+                     Arrays.toString(args));
                     //通过反射调用method对象所表示的方法,并获取该方法的返回值
                     //在具有指定参数的指定对象上调用此method对象表示的底层方法。
                     //此处就是通过target来确定调用的是具体哪个类中的方法
                     result = method.invoke(target, args);
                     System.out.println("[动态代理][日志] "+method.getName()+",结 果:"+ result);
                  } catch (Exception e) {
                   e.printStackTrace();
                   System.out.println("[动态代理][日志] "+method.getName()+",异常:"+e.getMessage());
                  } finally {
                      System.out.println("[动态代理][日志] "+method.getName()+",方法执行完毕");
                  }
                  return result;
               }   
            }; 
        //返回指定接口的代理类的实例,该实例将方法调用分派给指定的调用处理程序。
        return Proxy.newProxyInstance(classLoader,interfaces,invocationHandler);
      }
}                                     

3.测试类ProxyTest

package com.tong.proxy;

import com.tong.spring.calculator.Calculator;
import com.tong.spring.calculator.proxy.ProxyFactory;
import org.junit.Test;

/**
 * @author tong
 */
public class ProxyTest {
    @Test
    public void testDynamicProxy(){
        ProxyFactory factory = new ProxyFactory(new Calculator());
        Calculator proxy = factory.getProxy();
        /*创建好了代理对象,代理对象就可以执行被代理类实现的接口的方法;当通过代理类的对象
        发起对被重写的方法的调用时,都会转换为对调用处理器实现类中的invoke方法的调用,
        invoke方法中就可以对被代理类进行功能增强.*/
        proxy.add(1, 5);
//        proxy.div(1,0);
    }
}

CGLIB动态代理

        实现原理

                使用第三方库,通过修改字节码生成目标类的子类。 
                在子类中对方法进行增强
                由于生成的是类的子类,因此可以代理没有实现接口的类。

        适用场景

                被代理类不需要实现接口。
                适用于横切关注点与业务逻辑之间耦合较紧密的情况。

        实现方式

        1.创建被代理类

package com.tong;

/**
 * @author tong
 * @Description:被代理类
 */
public class Star {
    public void sing() {
        System.out.println("唱.......");
    }

    public void dance() {
        System.out.println("跳......");
    }
}

2.生成代理工厂

package com.tong;

import com.oracle.jrockit.jfr.Producer;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * @author tong
 * @Description:生成代理类的工厂
 */
public class ProxyFactory {
    private Object target;

    public ProxyFactory(Object target) {
        this.target = target;
    }

    // 通过该方法可以生成任意目标类所对应的代理类
    public static Object getProxy(Object target) {
        // proxy就是我们创建的代理对象,这个对象可以执行被代理类中所有的方法,并且我们可以
        在代理对象中对被代理类的方法进行增强
        Object proxy = Enhancer.create(target.getClass(), new MethodInterceptor() {
            /**
             * 这一步是整个过程的关键,代理类的实现要通过Enhancer类,我们需要通过Enhancer
        类中的create方法创建一个代理对象
             * @param o 是一个代理对象的引用 (即:增强对象)
             * @param method 是当前执行,即被拦截的被代理类方法
             * @param objects 是当前执行方法所用的参数,索引顺序即为方法定义时参数的顺序
             * @param methodProxy 指的是当前执行的方法的代理对象
             * @return 通过反射调用method对象所表示的方法, 并获取该方法的返回值
             */
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) {
                Object result = null;
                try {
                    // 提供增强代码
                    System.out.println("[cglib动态代理][日志] " + method.getName() +"
        ,参数:" + Arrays.toString(objects));
                    //通过反射调用method对象所表示的方法,并获取该方法的返回值
                    //在具有指定参数的指定对象上调用此method对象表示的底层方法。
                    //此处就是通过target来确定调用的是具体哪个类中的方法
                    result = method.invoke(target, objects);
                    System.out.println("[cglib动态代理][日志] " + method.getName() + ",
        结果
        :" + result);
                } catch (Exception e) {
                    e.printStackTrace();
                    System.out.println("[cglib动态代理][日志] " + method.getName() + ",
        异常:
        " + e.getMessage());
                } finally {
                    System.out.println("[cglib动态代理][日志] " + method.getName() + ",
        方法执行完毕");
                }
                return result;
            }
        });
        // 返回代理对象
        return proxy;
    }
}

3.生成测试类

package com.tong;

import org.junit.Test;

/**
 * @author tong
 * @Description:测试类
 */
public class ProxyTest {

    @Test
    public void test(){
        Star star = new Star();
        /* proxy就是我们创建的代理对象,这个对象可以执行被代理类中所有的方法,
        并且我们可以在代理对象中对被代理类的方法进行增强,
        注意这里使用了强转,因为getProxy方法的返回值是Object类型的对象*/
        Star proxy = (Star)ProxyFactory.getProxy(star);
        proxy.sing();
        System.out.println("-----------------分割线------------------");
        proxy.dance();
        System.out.println("-----------------分割线------------------");
        
    }
}

4.运行结果

JDK动态代理和CGLIB动态代理的区别

实现原理:

①JDK 动态代理是基于接口的代理,生成的代理类实现了被代理接口。
②CGLIB 动态代理是通过生成目标类的子类,代理类继承了被代理类。

适用场景:

①JDK 动态代理适用于被代理类实现了接口的情况。
②CGLIB 动态代理适用于被代理类没有实现接口的情况。

性能:

①JDK 动态代理的性能较好,因为它是基于接口的代理,直接使用 java.lang.reflect 包中的方法。
②CGLIB 动态代理生成的字节码较大,可能在性能上略逊于 JDK 动态代理,但在某些场景下性能差异并不明显。

使用方式:

①JDK 动态代理需要实现 InvocationHandler 接口,并使用 Proxy.newProxyInstance 方法创建代理对象。
②CGLIB 动态代理直接使用第三方库,不需要实现接口,通过 Enhancer.create 方法创建代理对象。

CGlib比JDK快?

①使用CGLiB实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类, 在jdk6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的方法进行代理, 因为CGLib原理是动态生成被代理类的子类。

②在jdk6、jdk7、jdk8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLIB代理效率。只有当进行大量调用的时候,jdk6和jdk7比CGLIB代理效率低一点,但是到jdk8的时候,jdk代理效率高于CGLIB代理,总之,每一次jdk版本升级,jdk代理效率都得到提升,而CGLIB代理消息确有点跟不上步伐。

Spring如何选择用JDK还是CGLIB?

①当Bean实现接口时,Spring就会用JDK的动态代理。

②当Bean没有实现接口时,Spring使用CGlib实现。

③可以强制使用CGlib

总体来说,选择使用 JDK 动态代理还是 CGLIB 动态代理取决于具体的业务场景和需求。如果被代理类已经实现接口,推荐使用 JDK 动态代理;如果被代理类没有实现接口或者想要在运行时对类进行增强,可以考虑使用 CGLIB 动态代理。

静态代理和动态代理的区别

实现方式:

①静态代理: 手动编写代理类的代码。
②动态代理: 在运行时生成代理类的字节码,无需手动编写代理类。

灵活性:

①静态代理: 代理类在编译时就确定,不够灵活。
②动态代理: 代理类在运行时生成,可以根据需要动态地创建代理类。

维护成本:

①静态代理: 每添加一个被代理类,都需要手动编写一个对应的代理类。
②动态代理: 通过动态生成代理类,可以更容易地维护和扩展。

性能:

①静态代理: 在编译时就确定了代理类,运行时性能相对较好。
②动态代理: 生成代理类的过程可能会带来一些性能开销。

动态代理常用的实现方式有基于java.lang.reflect.Proxy和基于CGLIB的动态代理。在实际应用中,根据需要选择静态代理或动态代理。

  • 55
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值