设计模式-代理模式

代理模式:

给某一个对象提供一个代理,并由代理对象来控制对真实对象的访问。代理模式是一种结构性设计模式

代理模式角色分为3种:
Subject(抽象主题角色):定义代理类和真实主题的公共对外方法,也是代理类代理真实主题的方法;
RealSubject(真实主题角色):真正实现业务逻辑的类;
Proxy(代理主题角色):用来代理和封装真实主题;

代理模式的结构比较简单,其核心就是代理类,为了让客户端能够一致性地对待真实对象和代理对象,在代理模式种引入了抽象层
在这里插入图片描述

代理模式根据字节码的创建时机来分类,可以分为静态代理和动态代理:

  • 所谓静态代理也就是在程序运行前就已经存在代理类的字节码文件,代理类和真实主题角色的关系在运行前就确定了
  • 动态代理的源码实在程序运行期间由JVM根据反射等机制动态的生成,所以在运行前并不存在代理类的字节码文件

静态代理

编写一个接口 UserService ,以及该接口的一个实现类 UserServiceImpl

public interface UserService {
    public void select();   
    public void update();
}

public class UserServiceImpl implements UserService {  
    public void select() {  
        System.out.println("查询 selectById");
    }
    public void update() {
        System.out.println("更新 update");
    }
}

我们将通过静态代理对 UserServiceImpl 进行功能增强,在调用 selectupdate 之前记录一些日志。写一个代理类 UserServiceProxy,代理类需要实现 UserService

public class UserServiceProxy implements UserService {
    private UserService target; // 被代理的对象

    public UserServiceProxy(UserService target) {
        this.target = target;
    }
    public void select() {
        before();
        target.select();    // 这里才实际调用真实主题角色的方法
        after();
    }
    public void update() {
        before();
        target.update();    // 这里才实际调用真实主题角色的方法
        after();
    }

    private void before() {     // 在执行方法之前执行
        System.out.println(String.format("log start time [%s] ", new Date()));
    }
    private void after() {      // 在执行方法之后执行
        System.out.println(String.format("log end time [%s] ", new Date()));
    }
}

客户端测试

public class Client1 {
    public static void main(String[] args) {
        UserService userServiceImpl = new UserServiceImpl();
        UserService proxy = new UserServiceProxy(userServiceImpl);

        proxy.select();
        proxy.update();
    }
}

输出

log start time [Thu Dec 20 14:13:25 CST 2018] 
查询 selectById
log end time [Thu Dec 20 14:13:25 CST 2018] 
log start time [Thu Dec 20 14:13:25 CST 2018] 
更新 update
log end time [Thu Dec 20 14:13:25 CST 2018] 

作者:小旋锋
链接:https://juejin.cn/post/6844903744954433544
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

通过静态代理,我们达到了功能增强的目的,而且没有侵入原代码,这是静态代理的一个优点

静态代理的缺点

  1. 当需要代理多个类的时候,由于代理对象要实现与目标对象一致的接口,由两种方式:
    • 只维护一个代理类,由这个代理类实现多个接口,但是这样就导致代理类过于庞大
    • 新建多个代理类,每个目标对象对应一个代理类,但是这样会产生过多的代理类
  2. 当接口需要增加、删除、修改方法的时候,目标对象与代理类都要同时修改,不易维护

JDK动态代理

jdk动态代理主要涉及两个类:java.lang,reflect.Proxyjava.lang.reflect.InvocationHandler

编写一个调用逻辑处理器 LogHandler 类,提供日志增强功能,并实现 InvocationHandler 接口;在 LogHandler 中维护一个目标对象,这个对象是被代理的对象(真实主题角色);在invoke 方法中编写方法调用的逻辑处理

public class LogHandler implements InvocationHandler {

    Object target; // 被代理的对象,实际的方法执行者

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object invokeResult = method.invoke(target, args);
        after();
        return invokeResult;
    }

    // 调用invoke方法之前执行
    private void before() {
        System.out.println(String.format("log start time [%s]", new Date()));
    }
    // 调用invoke方法之后执行
    private void after() {
        System.out.println(String.format("log end time [%s]", new Date()));
    }
}

编写客户端,获取动态生成的代理类的对象须借助 Proxy 类的 newProxyInstance 方法,具体步骤可见代码和注释

public class Client2 {

    public static void main(String[] args) {
        // 1. 创建被代理的对象,UserService接口的实现类
        UserServiceImpl userServiceImpl = new UserServiceImpl();
        // 2. 获取对应的classloader
        ClassLoader classLoader = userServiceImpl.getClass().getClassLoader();
        // 3. 获取所有接口的Class,这里的UserServiceImpl只实现了一个接口userService
        Class<?>[] interfaces = userServiceImpl.getClass().getInterfaces();
        // 4. 创建一个将传给代理类的调用处理器,处理所有的代理对象的方法调用
        //        这里是自定义的日志处理器,须传入实际的执行对象userServiceImpl
        LogHandler logHandler = new LogHandler(userServiceImpl);
        /*
            5. 根据上面提供的信息,创建代理对象
               在这个过程中:
                   a. JDK会通过根据传入的参数信息动态的在内存中创建和.class文件等同的字节码
                   b. 然后根据相应的字节码转换成对应的class
                   c. 然后调用newInstance() 创建实例
         */
        UserService proxy = (UserService) Proxy.newProxyInstance(classLoader, interfaces, logHandler);

        // 调用代理方法
        proxy.select();
        proxy.update();

    }
}

运行结果:

start time [Fri Mar 10 14:15:23 GMT+08:00 2023]
查询 select
end time [Fri Mar 10 14:15:23 GMT+08:00 2023]
start time [Fri Mar 10 14:15:23 GMT+08:00 2023]
更新 update
end time [Fri Mar 10 14:15:23 GMT+08:00 2023]

拓展:将上述方式更改为匿名内部类的形式和Lambda形式

// 匿名内部类形式
public class Client3 {

    public static void main(String[] args) {

        // 要代理的类
        UserServiceImpl userServiceImpl = new UserServiceImpl();
        // 要代理的类实现的接口
        Class<?>[] interfaces = userServiceImpl.getClass().getInterfaces();

        UserService proxyInstance = (UserService)Proxy.newProxyInstance(userServiceImpl.getClass().getClassLoader(), interfaces, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println(String.format("start time [%s]", new Date()));
                Object result = method.invoke(userServiceImpl, args);
                System.out.println(String.format("end time [%s]", new Date()));
                return result;
            }
        });

        // 调用代理方法
        proxyInstance.select();
        proxyInstance.update();

    }
}




// Lambda形式
public class Client4 {
    public static void main(String[] args) {
        // 创建需要被代理的类
        UserServiceImpl userServiceImpl = new UserServiceImpl();
        // 获取类实现的接口
        Class<?>[] interfaces = userServiceImpl.getClass().getInterfaces();
        // 获取类加载器
        ClassLoader classLoader = userServiceImpl.getClass().getClassLoader();

        UserService userServiceProxy = (UserService) Proxy.newProxyInstance(classLoader, interfaces, (Object proxy, Method method, Object[] paramArgs) -> {
            System.out.println(String.format("start time [%s]", new Date()));
            Object result = method.invoke(userServiceImpl, paramArgs);
            System.out.println(String.format("end time [%s]", new Date()));
            return result;
        });
        userServiceProxy.select();
        userServiceProxy.update();
    }
}

invocationHandler 和 Proxy的主要方法介绍:

  • java.lang.reflect.InvocationHandler

    1. public Object invoke(Object proxy, Method method, Object[] args) 定义了代理对象调用方法时希望执行的动作,用于集中处理在动态代理对象上的方法调用
  • java.lang.reflect.Proxy

    1. public static InvocationHandler getInvocationHandler(Object proxy) 用于获取指定代理对象所关联的调用处理器
    2. public static Class<?> getProxyClass(ClassLoader loader, Class<?>… interfaces) 返回指定接口的代理类
    3. public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 构造实现指定接口的代理类的一个新实例,所有方法会调用给定处理器对象的invoke方法
    4. static boolean isProxyClass(Class<?> cl) 返回 cl 是否为一个代理类

代理类的调用过程

将代理类通过下面的工具类保存下来,探究调用过程

public class ProxyUtils {
    /**
     * 将根据类信息动态生成的二进制字节码保存到硬盘中,默认的是clazz目录下
     * params: clazz 需要生成动态代理类的类
     * proxyName: 为动态生成的代理类的名称
     */
    public static void generateClassFile(Class clazz, String proxyName) {
        // 根据类信息和提供的代理类名称,生成字节码
        byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces());
        String paths = clazz.getResource(".").getPath();
        System.out.println(paths);
        FileOutputStream out = null;
        try {
            //保留到硬盘中
            out = new FileOutputStream(paths + proxyName + ".class");
            out.write(classFile);
            out.flush();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

然后在 Client2 测试类的main的最后面加入一行代码

ProxyUtils.generateClassFile(userServiceImpl.getClass(), "userServiceProxy");

IDEA 再次运行之后就可以在 target 的类路径下找到 UserServiceProxy.class,双击后IDEA的反编译插件会将该二进制class文件
在这里插入图片描述
UserServiceProxy 的代码如下所示:

import com.yorange.pattern.proxy.dynamic.UserService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class userServiceProxy extends Proxy implements UserService {
    private static Method m1;
    private static Method m2;
    private static Method m4;
    private static Method m0;
    private static Method m3;

    public userServiceProxy(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void select() throws  {
        try {
            super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void update() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m4 = Class.forName("com.yorange.pattern.proxy.dynamic.UserService").getMethod("select");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            m3 = Class.forName("com.yorange.pattern.proxy.dynamic.UserService").getMethod("update");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

从 UserServiceProxy 的代码中我们可以发现:

  • UserServiceProxy 继承了 Proxy 类,并且实现了被代理的所有接口,以及equals、hashCode、toString等方法
  • 由于 UserServiceProxy 继承了 Proxy 类,所以每个代理类都会关联一个 InvocationHandler 方法调用处理器
  • 类和所有方法都被 public final 修饰,所以代理类只可被使用,不可以再被继承
  • 每个方法都有一个 Method 对象来描述,Method 对象在static静态代码块中创建,以 m + 数字 的格式命名
  • 调用方法的时候通过 super.h.invoke(this, m1, (Object[])null); 调用,其中的 super.h.invoke 实际上是在创建代理的时候传递给 Proxy.newProxyInstance 的 LogHandler 对象,它继承 InvocationHandler 类,负责实际的调用处理逻辑

而 LogHandler 的 invoke 方法接收到 method、args 等参数后,进行一些处理,然后通过反射让被代理的对象 target 执行方法

@Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object result = method.invoke(target, args);       // 调用 target 的 method 方法
        after();
        return result;  // 返回方法的执行结果
    }

JDK动态代理执行的方法调用的过程简图如下
在这里插入图片描述

CGLIB动态代理

maven引入CGLIB包,然后编写一个UserDao类,它没有接口,只有两个方法,select() 和 update()

<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>
public class UserDao {
    public void select() {
        System.out.println("UserDao 查询 selectById");
    }
    public void update() {
        System.out.println("UserDao 更新 update");
    }
}

编写一个 UserDaoProxy,继承了 MethodInterceptor,用于方法的拦截回调

public class UserDaoProxy implements MethodInterceptor {
    /**
     *
     * @param o 表示要进行增强的对象
     * @param method 表示拦截的方法
     * @param objects 数组表示参数列表,基本数据类型需要传入其包装类型,如int-->Integer、long-Long、double-->Double
     * @param methodProxy 表示对方法的代理,invokeSuper方法表示对被代理对象方法的调用
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        before();
        Object result = methodProxy.invokeSuper(o, objects);// 注意这里是调用 invokeSuper 而不是 invoke,否则死循环,methodProxy.invokeSuper执行的是原始类的方法,method.invoke执行的是子类的方法
        after();
        return result;
    }

    private void before() {
        System.out.println(String.format("log start time [%s] ", new Date()));
    }
    private void after() {
        System.out.println(String.format("log end time [%s] ", new Date()));
    }

}

测试

public class CglibTest {

    public static void main(String[] args) {

        UserDaoProxy userDaoProxy = new UserDaoProxy();
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(UserDao.class);// 设置超类,cglib是通过继承来实现的
        enhancer.setCallback(userDaoProxy);
        // 创建代理类
        UserDao userDao = (UserDao) enhancer.create();
        userDao.select();
        userDao.update();
    }
}

输出

log start time [Fri Mar 10 16:24:32 GMT+08:00 2023] 
UserDao 查询 selectById
log end time [Fri Mar 10 16:24:32 GMT+08:00 2023] 
log start time [Fri Mar 10 16:24:32 GMT+08:00 2023] 
UserDao 更新 update
log end time [Fri Mar 10 16:24:32 GMT+08:00 2023] 

CGLIB 创建动态代理类的模式是:

  1. 查找目标类上的所有非final的public类型的方法定义;
  2. 将这些方法的定义转换成字节码;
  3. 将组成的字节码转换成相应的代理的class对象
  4. 实现MethodIntercept接口,用来处理代理类上的所有方法的请求

JDK动态代理与CGLIB动态代理对比

  • JDK动态代理
    基于java反射机制实现,必须要实现了接口的业务类才能用这种方法生成代理对象

  • cglib动态代理
    基于ASM机制实现,通过生成业务类的子类作为代理类

JDK Proxy优势

  • 最小依赖关系,减少依赖意味着简化开发和维护,JDK本身的支持,可能比cglib更加可靠
  • 平滑进行JDK版本升级,而字节码类库通常需要进行更新以保证再新版java上能够使用
  • 代码实现简单

cglib优势

  • 无需实现接口,达到代理类无侵入
  • 只操作我们关心的类,而不必为其他相关类增加工作量
  • 高性能

描述动态代理的几种实现方式?分别说出相应的优缺点

静态代理:
代理对象和实际对象都继承了同一个接口,在代理对象中指向的是实际对象的实例,这样对外暴露的是代理对象而真正调用的是 Real Object

  • 优点: 可以很好的保护实际对象的业务逻辑对外暴露,从而提高安全性。
  • 缺点: 不同的接口要有不同的代理类实现,会很冗余

jdk动态代理
为了解决静态代理中,生成大量的代理类造成的冗余;
JDK 动态代理只需要实现 InvocationHandler 接口,重写 invoke 方法便可以完成代理的实现
jdk的代理是利用反射生成代理类 Proxyxx.class 代理类字节码,并生成对象
jdk动态代理之所以只能代理接口是因为代理类本身已经extends了Proxy,而java是不允许多重继承的,但是允许实现多个接口

  • 优点: 解决了静态代理中冗余的代理实现类问题。
  • 缺点: JDK 动态代理是基于接口设计实现的,如果没有接口,会抛异常。

CGLIB 代理:
由于 JDK 动态代理限制了只能基于接口设计,而对于没有接口的情况,JDK方式解决不了;
CGLib 采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑,来完成动态代理的实现。
实现方式实现 MethodInterceptor 接口,重写 intercept 方法,通过 Enhancer 类的回调方法来实现。
但是CGLib在创建代理对象时所花费的时间却比JDK多得多,所以对于单例的对象,因为无需频繁创建对象,用CGLib合适,反之,使用JDK方式要更为合适一些。
同时,由于CGLib由于是采用动态创建子类的方法,对于final方法,无法进行代理。

  • 优点: 没有接口也能实现动态代理,而且采用字节码增强技术,性能也不错。
  • 缺点: 技术实现相对难理解些。

CGlib 对接口实现代理?

/**
 * 创建代理类的工厂 该类要实现 MethodInterceptor 接口。
 * 该类中完成三样工作:
 * (1)声明目标类的成员变量,并创建以目标类对象为参数的构造器。用于接收目标对象
 * (2)定义代理的生成方法,用于创建代理对象。方法名是任意的。代理对象即目标类的子类
 * (3)定义回调接口方法。对目标类的增强这在这里完成
 */
public class CGLibFactory implements MethodInterceptor {
    // 声明目标类的成员变量
    private UserService target;

    public CGLibFactory(UserService target) {
        this.target = target;
    }
    // 定义代理的生成方法,用于创建代理对象
    public UserService myCGLibCreator() {
        Enhancer enhancer = new Enhancer();
        // 为代理对象设置父类,即指定目标类
        enhancer.setSuperclass(UserService.class);
        /**
         * 设置回调接口对象 注意,只所以在setCallback()方法中可以写上this,
         * 是因为MethodIntecepter接口继承自Callback,是其子接口
         */
        enhancer.setCallback(this);
        return (UserService) enhancer.create();// create用以生成CGLib代理对象
    }
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("start invoke " + method.getName());
        Object result = method.invoke(target, args);
        System.out.println("end invoke " + method.getName());
        return result;
    }
}

从代理模式再出发!Proxy.newProxyInstance的秘密

Proxy.newProxyInstance的秘密:https://blog.csdn.net/lovejj1994/article/details/78080124

文章转载出处:

文章转载出处:https://juejin.cn/post/6844903744954433544#heading-9

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java设计模式是一组经过实践验证的面向对象设计原则和模式,可以帮助开发人员解决常见的软件设计问题。下面是常见的23种设计模式: 1. 创建型模式(Creational Patterns): - 工厂方法模式(Factory Method Pattern) - 抽象工厂模式(Abstract Factory Pattern) - 单例模式(Singleton Pattern) - 原型模式(Prototype Pattern) - 建造者模式(Builder Pattern) 2. 结构型模式(Structural Patterns): - 适配器模式(Adapter Pattern) - 桥接模式(Bridge Pattern) - 组合模式(Composite Pattern) - 装饰器模式(Decorator Pattern) - 外观模式(Facade Pattern) - 享元模式(Flyweight Pattern) - 代理模式(Proxy Pattern) 3. 行为型模式(Behavioral Patterns): - 责任链模式(Chain of Responsibility Pattern) - 命令模式(Command Pattern) - 解释器模式(Interpreter Pattern) - 迭代器模式(Iterator Pattern) - 中介者模式(Mediator Pattern) - 备忘录模式(Memento Pattern) - 观察者模式(Observer Pattern) - 状态模式(State Pattern) - 策略模式(Strategy Pattern) - 模板方法模式(Template Method Pattern) - 访问者模式(Visitor Pattern) 4. 并发型模式(Concurrency Patterns): - 保护性暂停模式(Guarded Suspension Pattern) - 生产者-消费者模式(Producer-Consumer Pattern) - 读写锁模式(Read-Write Lock Pattern) - 信号量模式(Semaphore Pattern) - 线程池模式(Thread Pool Pattern) 这些设计模式可以根据问题的特点和需求来选择使用,它们提供了一些可复用的解决方案,有助于开发高质量、可维护且易于扩展的软件系统。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值