JavaSE - 反射-静态代理与动态代理

JavaSE - 反射-静态代理与动态代理,第1张

JavaSE - 反射-静态代理与动态代理 JavaSE - 反射-静态代理与动态代理

本节学习目标:

  • 了解代理模式;
  • 了解并掌握静态代理的实现方式;
  • 了解动态代理的概念;
  • 了解动态代理与Java反射的联系与实现。
1. 代理模式 1.1 代理模式简介

代理模式(Proxy Pattern)是23种设计模式之一,它的思想是用一个类代表另一个类的功能。即使用一个代理对象将原对象包装起来,
然后用该代理对象取代原对象,任何对原对象的调用都要通过代理。由代理对象决定是否以及何时将方法调用转到原对象。

1.2 代理模式能解决的问题

代理模式主要为其他对象提供一种代理对象以控制对这个对象的访问,或者想在访问一个类时做一些控制,可以解决在直接访问对象所带来的问题。
比如要访问的对象在远程计算机上;或者因为某些原因(如创建对象的资源开销很大;进行 *** 作需要安全性控制;或者需要进程外的访问等)
导致直接访问原对象会给用户和计算机带来麻烦等;

1.3 代理模式的优缺点

优点:

  • 职责清晰;
  • 高扩展性;
  • 智能化。

缺点:

  • 由于代理对象的存在,可能会导致处理速度变慢;
  • 实现代理模式需要额外的工作,有些实现会很复杂。
2. 静态代理

从厂家直接购买商品一般按原价购买,但从电商处购买有时会打折,或者送些小礼品。可以用Java代码实现。

编写Buyable接口,描述厂家类和电商类都能购买的特性:

public interface Buyable {
    void buy();
}

编写Factory类,实现Buyable接口:

public class Factory implements Buyable {
    private String product;

    public Factory(String product) {
        this.product = product;
    }

    @Override
    public void buy() {
        System.out.println("购买了" + product);
    }
}

编写Shop类,也实现Buyable接口,内部封装一个Factory类的对象,同时对Factory类的方法进行扩充与增强:

public class Shop implements Buyable {
    private Factory factory;

    public Shop(String product) {
        this.factory = new Factory(product);
    }

    @Override
    public void buy() {
        // 通过代理对象访问原对象
        factory.buy();
        // 对原对象的特性进行扩展
        System.out.println("在电商处购买,获得了小礼品");
    }
}

对以上代码进行测试,比较使用原对象与代理对象之间的异同:

public class Test {
    public static void main(String[] args) {
        Buyable buyable1 = new Factory("手机");
        buyable1.buy();
        System.out.println("---------");
        Buyable buyable2 = new Shop("手机");
        buyable2.buy();
    }
}


分析以上代码,可知代理模式可以在不修改被代理对象的基础上,通过扩展代理类,对被代理对象的特性进行一些功能上的附加与增强。

需要注意的是代理类与被代理类一定有某种关联,如上述例子中Factory类与Shop类都实现了Buyable接口。

这种关联一般有两种情况:

  • 代理类与被代理类均实现了同一个接口;
  • 代理类与被代理类均为同一个类的子类。

为什么称之为静态代理:上述代码都是写好的,功能在编译时就已经确定了,无法体现动态性。

3. 动态代理

在实际开发中,静态代理的局限性就被体现出来了:

  • 代理类与被代理类在编译期间就已经确定了,不利于程序的扩展;
  • 每个代理类只能服务一个被代理类,那么实际开发中必然会产生过多的代理类;

所以动态代理就应运而生,动态代理可以在运行期间才确定代理类的功能并创建代理类。

Java在JDK1.3加入了Proxy类与InvocationHandler接口,位于java.lang.reflect包下,这两个结构为Java实现动态代理提供支持。
可以使用反射,在运行期间创建代理类并实例化,从而动态地对原对象的方法进行增强。同时避免了每个代理类只能服务一个被代理类的麻烦。
Java的动态代理实现方式为接口方式。

3.1 Proxy 类

Proxy类为Java为动态代理技术提供的类,它是所有由动态代理生成的代理类的父类,同时提供了创建动态代理类和实例的静态方法。

Proxy类的核心方法为newProxyInstance():

  • Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h):创建一个代理类
    • loader:定义代理类的类加载器,通常使用被代理类的类加载器;
    • interfaces:代理类要实现的接口列表,通常使用被代理类的接口(如Buyable接口);
    • h:对被代理类的方法进行扩充或增强的实现逻辑(如Factory类的buy()方法);
    • 返回值:创建的代理对象。
3.2 InvocationHandler 接口

InvocationHandler接口为Java为动态代理技术提供的接口,它定义了如何对被代理类的方法进行扩充或增强的实现方法invoke():

  • Object invoke(Object proxy, Method method, Object[] args):扩充与增强方法
    • proxy:代理对象;
    • method:接口中要进行扩充或增强的方法;
    • args:调用代理类的方法时,传入被代理类的方法的参数;
    • 返回值:被代理类的方法执行完毕后的返回值。

InvocationHandler接口只有一个方法invoke(),因此可以作为函数式接口,可使用lambda表达式以内部类形式编写实现类。

3.3 动态代理的举例

比如使用动态代理技术将章节2中的例子修改,编写生产代理对象的工厂类ProxyFactory:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class ProxyFactory {
    
    public static Object getProxyInstance(Object subject) {
        // 获取代理对象的类加载器
        ClassLoader loader = subject.getClass().getClassLoader();
        // 获取代理对象实现的接口
        Class[] interfaces = subject.getClass().getInterfaces();
        // 获取对被代理对象的方法进行扩充或增强的逻辑
        InvocationHandler handler = null;
        // 使用上述参数生成代理对象并返回
        return Proxy.newProxyInstance(loader, interfaces, handler);
    }
}

对于将被代理对象的方法进行扩充或增强的逻辑(即InvocationHandler的实现类),可以使用内部类形式编写:

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

public class ProxyFactory {
    
    public static Object getProxyInstance(Object subject) {
        // 获取代理对象的类加载器
        ClassLoader loader = subject.getClass().getClassLoader();
        // 获取代理对象实现的接口
        Class[] interfaces = subject.getClass().getInterfaces();
        // 获取对被代理对象的方法进行扩充或增强的逻辑
        InvocationHandler handler = methodEnhance(subject);
        // 使用上述参数生成代理对象并返回
        return Proxy.newProxyInstance(loader, interfaces, handler);
    }

    
    private static InvocationHandler methodEnhance(Object subject) {
        // 使用lambda表达式编写
        // return (proxy, method, args) -> {
        //     System.out.println("在电商处购买,获得了小礼品");
        //     return method.invoke(subject, args);
        // }
        return new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                
                // 执行被代理类的方法,接收返回值
                Object result = method.invoke(subject, args);
                
                // 这里使用2章节的扩充或增强的逻辑
                System.out.println("在电商处购买,获得了小礼品");
                // 最后返回执行方法后的返回值即可
                return result;
            }
        };
    }
}

编写代码进行测试:

public class Test {
    public static void main(String[] args) {
        Buyable buyable1 = new Factory("手机");
        buyable1.buy();
        System.out.println("-----------");
        // 由于Proxy类中的newProxyInstance()方法创建的代理对象类型为Object,所以需要进行强转
        Buyable buyable2 = (Buyable) ProxyFactory.getProxyInstance(new Factory("手机"));
        // 这里调用的就是代理类中已经扩充或增强过的方法
        buyable2.buy();
    }
}


3.4 动态代理与 AOP 技术

动态代理解决了静态代理中的问题,在编译时并不确定代理类及代理对象的结构;
而且上面的例子可以复用,传入不同的被代理对象即可,无需对每一个被代理类都编写一个对应的代理类。

在实际开发中,通常一个接口有很多个实现类,比如一个排序接口,实现类可能有插入排序,冒泡排序,快速排序等,这些实现类中可能有相同功能的代码,
如获取要排序的对象 *** 作等。我们可以将这些代码抽取出来,用动态代理技术对每个实现类的排序方法进行增强,增强的逻辑就写这些相同功能的代码。

上述思想被称为面向切面编程思想(Aspect Orient Programming,简称AOP)。AOP的术语:

  • 通知(Advice):指对被代理对象的方法进行扩充或增强的逻辑,有五种通知类型:
    • 前置通知:在被代理对象的方法执行前进行扩充或增强的逻辑;
    • 后置通知:在被代理对象的方法执行后进行扩充或增强的逻辑;
    • 返回通知:在被代理对象的方法返回后进行扩充或增强的逻辑;
    • 异常通知:被代理对象的方法执行时发生异常后进行扩充或增强的逻辑;
    • 环绕通知:在被代理对象的方法执行前后都进行扩充或增强的逻辑。
  • 连接点(Join point):在应用执行过程中能够插入切面的一个点,指被代理对象的方法;
  • 切点(Pointcut):切点定义了需要在被代理对象的哪些方法上进行扩充与增强;
  • 切面(Aspect):通知与切点的结合;
  • 引入(Introduction):向被代理类中添加新的属性或方法;
  • 织入(Weaving):将切面在连接点处织入到目标对象的过程,即把扩充或增强逻辑应用到被代理对象的过程。

AOP的织入方式有很多种,而动态代理是其中的一种。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5611680.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-15
下一篇 2022-12-15

发表评论

登录后才能评论

评论列表(0条)

保存