• 面试篇之HR问什么是静态代理?什么是动态代理?


    面试篇之HR问什么是静态代理?什么是动态代理?

    何为代理?

    Java中的代理,开源理解为通过代理去访问实际的目标对象,比如呢?我们平常买卖二手车的中间商,就可以看作一个代理类,不过你也可以直接去和二手车的主人买卖。

    那这种情况,在Java中就被称之为代理,代理类除了去实现目标对象外,他还可以去在其中增加许多额外功能。

    理论扩展:

    主要解决的问题:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。

    代理模式的必要条件:共同接口、代理对象、目标对象。

    宏观特性:对客户端只暴露出接口,不暴露它以下的架构。

    优点:中间隔离了一层,更加符合开闭原则

    代理是一种?

    代理是一种设计模式

    • 他并非一种自带的功能,而是一种设计模式。

    • 在代理模式中,一个类代表另一个类的功能。

    • 这种类型的设计模式属于结构型模式。

    • 在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。

    • 目的:为其他对象提供一种代理以控制对这个对象的访问。

    代理模式分类

    代理模式分为两种类型:

    • 静态代理
    • 动态代理

    实现静态代理

    理论不多说,从代码中理解。

    创建接口

    首先,我们创建一个接口,一般我们一个功能都会去设计一个接口,包括我们的三层架构也是这样,所以我这也写一个接口。

    1. /**
    2. * @author JanYork
    3. * @date 2022/10/25 8:59
    4. * @description 假设Demo是买东西的接口
    5. */
    6. public interface Demo {
    7. /**
    8. * 买东西
    9. * @param name 东西的名字
    10. * @param price 东西的价格
    11. * @return 买东西的结果
    12. */
    13. String buy(String name, int price);
    14. }

    那我们就,假设这个Demo是买东西的接口。

    也是提供一个简简单单的购买东西的方法(实际上就是输出测试一下)。

    实现接口

    我们还要去实现这个接口,也就是我们的接口实现类。

    1. /**
    2. * @author JanYork
    3. * @date 2022/10/25 9:03
    4. * @description 真实的Demo类(被代理类)
    5. */
    6. public class RealDemo implements Demo {
    7. public RealDemo(String name, int price) {
    8. this.name = name;
    9. this.price = price;
    10. }
    11. private String name;
    12. private int price;
    13. @Override
    14. public String buy(String name, int price) {
    15. System.out.println("买了" + name + "花了" + price + "元");
    16. return "买了" + name + "花了" + price + "元";
    17. }
    18. }

    给他两个参数,一个构造,然后重写接口提供的方法,这个不需要多说。

    代理类

    1. /**
    2. * @author JanYork
    3. * @date 2022/10/25 9:05
    4. * @description Demo的代理类
    5. */
    6. public class ProxyDemo implements Demo {
    7. private RealDemo realDemo;
    8. public ProxyDemo(String name, int price) {
    9. this.realDemo = new RealDemo(name, price);
    10. }
    11. @Override
    12. public String buy(String name, int price) {
    13. System.out.println("-----代理类开始买东西------");
    14. String result = realDemo.buy(name, price);
    15. System.out.println("------代理类买东西结束------");
    16. return result;
    17. }
    18. }

    创建代理后我们也需要去实现这个Demo接口。

    然后需要注入实现接口的类的对象(也就是真实类)。

    1. private RealDemo realDemo;

    然后实现构造:

    1. public ProxyDemo(String name, int price) {
    2. this.realDemo = new RealDemo(name, price);
    3. }

    然后在重写方法里面调用,可以在调用方法前后干一些事情。

    然后我们创建一个Test类测试:

    静态代理缺陷

    问:既然静态代理可以方便的达到目的,那他有什么缺点吗?

    静态代理在代码运行之前就需要创建好代理类,因此对于每一个代理对象都需要建一个代理类去代理。如果说,你需要代理的对象很多,那就需要创建很多代理类降低程序的可维护性

    问:那如何解决这个缺陷呢?

    动态构建代理类,也就是动态代理。

    动态代理

    动态代理的代理类是在运行过程中产生的。

    Java提供了两种实现动态代理的方式:

    • 基于JDK的动态代理。

    • 基于Cglib的动态代理。

    特点:字节码随用随创建,随用随加载。

    作用:在不修改源码的基础上对方法增强。

    基于JDK实现

    实现Jdk的动态代理需要实现InvocationHandler接口,然后实现其中的invoke方法。

    我们创建一个类,实现InvocationHandler(来自java.lang.reflect反射包下)接口。

    这个类下,有非常丰富的详细的解释,可以看看。

    实现了接口后,我们给他用Object来替代原先静态代理类中的这一段:

    1. private RealDemo realDemo;
    2. public ProxyDemo(String name, int price) {
    3. this.realDemo = new RealDemo(name, price);
    4. }

    也就是这样:

    1. private Object target;
    2. public ProxyHandler(Object target) {
    3. this.target = target;
    4. }

    然后重写invoke方法:

    1. @Override
    2. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    3. System.out.println("-----动态代理开始------");
    4. Object invoke = method.invoke(target, args);
    5. System.out.println("------动态代理结束------");
    6. return invoke;
    7. }

    里面有多个参数,稍后说明。

    调用

    首先还是创建真实对象,并且给出构造。

    1. RealDemo realDemo = new RealDemo("苹果", 10);

    然后创建动态代理对象,将正式对象传输过去。

    1. ProxyHandler proxyHandler = new ProxyHandler(realDemo);

    然后我们调用,实际上就是使用Proxy这个类来调用newProxyInstance这个方法。

    1. Proxy.newProxyInstance(realDemo.getClass().getClassLoader(), realDemo.getClass().getInterfaces(), proxyHandler);

    这个方法是一个静态方法,参数如下。

    1. newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)

    第一个是一个类加载器,第二个是一个带泛型的Class数组,并且要求是一个interfaces,第三个参数就是实现了InvocationHandler的对象了。

    那我们调用时给出的参数就是:

    1. //获取真实类的类加载器
    2. realDemo.getClass().getClassLoader(),
    3. //获取真实类所实现的接口
    4. realDemo.getClass().getInterfaces(),
    5. //实现InvocationHandler接口的对象
    6. proxyHandler

    这部分对反射这方面知识有很多设计,不懂的还是建议去折腾折腾。

    那我们整个调用动态对象的方法,如下:

    1. public class TestDemo {
    2. public static void main(String[] args) {
    3. // ProxyDemo proxyDemo = new ProxyDemo("苹果", 10);
    4. // proxyDemo.buy("苹果", 10);
    5. //调用动态代理
    6. RealDemo realDemo = new RealDemo("苹果", 10);
    7. ProxyHandler proxyHandler = new ProxyHandler(realDemo);
    8. Demo demo = (Demo) Proxy.newProxyInstance(realDemo.getClass().getClassLoader(), realDemo.getClass().getInterfaces(), proxyHandler);
    9. demo.buy("苹果", 10);
    10. }
    11. }

    Proxy.newProxyInstance方法返回的是一个Object,我们将他转型为我们的接口对象。

    然后通过对象调用对应方法。

    效果

    优势

    一个动态代理可以对N个需要代理的对象起作用,只需要将需要代理类的参数放入Proxy.newProxyInstance方法即可。

    缺陷

    JDK只能代理接口!

    JDK动态代理确实只能代理接口,JDK动态代理是基于接口的方式,换句话来说就是代理类和目标类都实现同一个接口。如果想要代理类的话可以使用CGLibCGLib动态代理是代理类去继承目标类,然后实现目标类的方法。

    基于CGLib实现

    首先引入依赖:

    1. <dependency>
    2. <groupId>cglib</groupId>
    3. <artifactId>cglib</artifactId>
    4. <version>版本号</version>
    5. </dependency>

    如果你是Spring项目,那就不必了,已经整合了。

    我们创建一个类,用于给CGLib测试。

    1. /**
    2. * @author JanYork
    3. * @date 2022/10/25 10:32
    4. * @description 基于Cglib的动态代理的目标类
    5. */
    6. public class CgDemo {
    7. public String buy(String name, int price) {
    8. System.out.println("买了" + name + ",价格是" + price);
    9. return "买了" + name + ",价格是" + price;
    10. }
    11. }

    然后创建CGLib的代理类:

    1. /**
    2. * @author JanYork
    3. * @date 2022/10/25 10:34
    4. * @description
    5. */
    6. public class CgProxy implements MethodInterceptor {
    7. @Override
    8. public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
    9. System.out.println("-----cglib代理开始------");
    10. Object invoke = methodProxy.invokeSuper(o, objects);
    11. System.out.println("------cglib代理结束------");
    12. return invoke;
    13. }
    14. }

    调用

    1. //创建Enhancer对象
    2. Enhancer enhancer = new Enhancer();
    3. //设置父类(也就是目标类)
    4. enhancer.setSuperclass(CgDemo.class);
    5. //设置回调函数
    6. enhancer.setCallback(new CgProxy());
    7. //创建代理对象
    8. CgDemo cgDemo = (CgDemo) enhancer.create();
    9. //调用代理对象的方法
    10. cgDemo.buy("苹果", 10);

    Enhancercglib.proxy下的一个生成动态子类以启用方法拦截的类。

    Enhancer 类是 CGLib 中最常用的一个类,和 JDK 1.3 动态代理中引入的 Proxy 类差不多(Proxy 类是Java动态代理机制的主类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象)。

    和 Proxy 不同的是,Enhancer 类既能够代理普通的 class,也能够代理接口。Enhancer 创建一个被代理对象的子类并且拦截所有的方法调用(包括从 Object 中继承的 toString() 和 hashCode() 方法)。

    效果

    应用场景

    问:动态代理这么牛,平常工作中有使用到吗?

    在平常的业务代码,几乎是用不到代理的。

    但是,Spring系列框架中的AOP,以及RPC框架中都用到了动态代理。

    如:AOP通过动态代理对目标对象进行了增强,比如我们最常用的前置通知、后置通知等。

  • 相关阅读:
    JS 日期格式化
    财报解读:将低价作为“唯一性基础武器”的京东,效果在慢慢显现
    Kafka之常用参数配置整理
    《动手学深度学习 Pytorch版》 5.2 参数管理
    VR全景如何助力乡村振兴,VR全景推动农业发展
    CANanlystII 基于linux的二次开发实践
    三、appender分析
    基于YOLOv8深度学习的脑肿瘤智能检测系统【python源码+Pyqt5界面+数据集+训练代码】深度学习实战、目标检测、智慧医疗
    使用 Abp.Zero 搭建第三方登录模块(三):网页端开发
    运维 | 使用 Docker 安装 Jenkins | Jenkins
  • 原文地址:https://blog.csdn.net/m0_57042151/article/details/127766546