代理模式

一.什么是代理模式

1.定义

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

2.作用

控制访问

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

功能增强

对一个系统中对已有的功能模块中,对功能的拓展或者增加,此时修改源代码的代价比较大时,就可以通过动态代理不改变已有的代码,对方法的增强与扩展

3.实现方法

  • 使用java.lang.reflect.Proxy类创建代理对象。这种方法需要手动编写代理类的代码,但实现简单。
  • 使用第三方库如CGLIB或Spring AOP。这些库提供了更高级的功能,如方法拦截、切面编程等。

两者区别:

1.JDK 动态代理只能代理实现了接口的类或者直接代理接口,而 CGLIB 可以代理未实现任何接口的类。 另外, CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法。

2.JDK 动态代理只能代理实现了接口的类或者直接代理接口,而 CGLIB 可以代理未实现任何接口的类。 另外, CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法.

4.优点与缺点

代理模式的主要优点有:

  • 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
  • 代理对象可以扩展增强目标对象的功能;
  • 代理模式能将客户端与目标对象分离,职责清晰,在一定程度上降低了系统的耦合度,增加了程序的可扩展性

其主要缺点是:

  • 代理模式会造成系统设计中类的数量增加
  • 在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;
  • 增加了系统的复杂度;

动态代理可以 解决这种问题

静态代理与动态代理的区别:

  1. 灵活性:动态代理更加灵活,不需要必须实现接口,可以直接代理实现类,并且可以不需要针对每个目标类都创建一个代理类。另外,静态代理中,接口一旦新增加方法,目标对象和代理对象都要进行修改,这是非常麻烦的!
  2. JVM 层面:静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。而动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。

# 5. 总结


著作权归JavaGuide(javaguide.cn)所有 基于MIT协议 原文链接:https://javaguide.cn/java/basis/proxy.html

如下Cilent调用Source的method()方法,实际上是Proxy来调用method()方法,静态代理中Source跟Proxy都要实现接口Sourceable。

image-20231021222623319

二.静态代理

因为java中的限制,需要实现接口,因此这里需要先定义一个接口

有一个接口方法是food,通过厨子你可以得到food,但是厨子无法直接给你,因此有服务员,这叫做代理,也就是我们常说的翻墙,原理也是代理

代码简单实现:

public interface Food {
    void eat();
}


public class Chief implements Food{
//目标类
    @Override
    public void eat() {
        System.out.println("厨子做饭");
    }
}


public class Waiter implements Food {

    Chief chief;
    public Waiter(Chief chief){
        this.chief = chief;
    }
    @Override
    public void eat() {
        chief.eat();
        System.out.println("代理:"+"厨子做饭");
    }
}

public class TestStaticProxy {
    public static void main(String[] args) {
        //目标类
        Chief chief = new Chief();
        //代理
        Waiter waiter = new Waiter(chief);
        //
        chief.eat();
        waiter.eat();

    }
}

但是这种静态代理的方法有个很大的缺陷:

当我们需要的方法不止一种也就是food,很多时,我们需要大量的类似wait类代理,资源占用会很大

当我们对food方法改动时,实现这个方法的类都需要改动,维护代价比较大

三.动态代理

方法介绍:

1.proxy

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

参数说明:

  • loader:类加载器,用于加载代理对象。
  • interfaces:代理对象实现的接口数组。
  • h:调用处理器,实现了InvocationHandler接口的对象

返回值:

  • 返回一个代理对象,该对象的类型是interfaces数组中指定的接口类型。

2.InvocationHandler

InvocationHandler接口是Java动态代理的核心,它只有一个方法:

invoke(Object proxy, Method method, Object[] args)

该方法的作用是在目标对象的方法被调用时执行一些操作,通常用于日志记录、性能监控、事务管理等场景。

具体来说,invoke方法接收三个参数:

  • proxy:代理对象,表示要调用目标方法的对象。
  • method:要调用的目标方法的Method对象。
  • args:目标方法的参数数组。

3.method

  • 返回值类型:method.getReturnType()
  • 方法名:method.getName()
  • 参数列表:method.getParameterTypes()
  • 注解:method.getAnnotations()

四.动态代理实现举例一

使用背景:使用JDBC时,当我们想提前储存多条sql语句时候,一种方法是定义常量,另一种方法:

通过注解获取,这种思路也是mybaits等一些框架的原理实现方法

这里的实例是通过注解以及方法返回值的类型决定执行

  QueryRunner queryRunner = new QueryRunner(DruidUtils.getDatasource());
        //创建代理对象

        DbTools dbTools = (DbTools) Proxy.newProxyInstance(UserDataSource.class.getClassLoader(), new Class[]{DbTools.class}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //得到sql
                Select annotation = method.getAnnotation(Select.class);
                if (annotation != null) {
                    String sql = annotation.sql();

                    Class<?> returnType = method.getReturnType();
                    if (returnType == List.class) {
                        //获取返回参数类型
                        ParameterizedType genericReturnType = (ParameterizedType) method.getGenericReturnType();
                        //获取泛型类型
                        Type actualTypeArgument = genericReturnType.getActualTypeArguments()[0];
                        Class<?> classs = Class.forName(actualTypeArgument.getTypeName());
                        //通配符接收

                        return queryRunner.query(sql, new BeanListHandler<>(classs));

                    }
                } else {
//这里省略,这是另一种返回值时的处理
                }


                return null;
            }
        });

        List<User> list = dbTools.selectAll();
        System.out.println(list);
    }
public interface DbTools {
    /**
     *
     * @return 返回用户对象集合
     */
    @Select(sql =  "SELECT id AS id, user_name AS userName, nick_name AS nickName, password AS password, status AS status, email AS email, phonenumber AS phonenumber, sex AS sex, avatar AS avatar, user_type AS userType, create_by AS createBy, create_time AS createTime, update_by AS updateBy, update_time AS updateTime, del_flag AS delFlag FROM sys_user")
    List<User> selectAll();

}

//定义select注解
@Meta
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Select {
    String sql();
}

分析:

这里是使用了动态代理,动态代理对象执行DbTools中的方法,根据不同的返回值,或者不同的注释,可以有不同的执行结果 ,可以大大的简化代码