代理模式

代理模式

定义:给目标对象提供一个代理对象,并由代理对象控制对目标对象的引用

代理模式有三种不同的形式:静态代理动态代理(JDK代理、接口代理)Cglib 代理(可以在内存中动态的创建对象,而不需要实现接口,属于动态代理的范畴)

目的:1. 通过引入代理对象的方式来间接访问目标对象,防止直接访问目标对象给系统带来的不必要的复杂性

  1. 通过代理对象对原来的业务进行增强

抽象对象:声明了真实对象和代理对象的公共接口

真实对象:代理对象所代理的真实对象,最终被引用的对象。被代理的对象可以是远程对象、创建开销大的对象或者需要安全控制的对象。实现了抽象对象接口

代理对象:包含真实对象,从而操作真实对象,想当于访问者与真实对象之间的中介。实现了抽象对象接口

静态代理

静态代理在使用时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一起实现相同的接口或者继承相同的父类。

代码示例

抽象对象
package top.gldwolf.proxy.staticproxy;

/**
 * 抽象对象,代理对象和被代理对象都要实现这个接口
 */
public interface ITeacherDao {
    void teach();
}
被代理对象
package top.gldwolf.proxy.staticproxy;

/**
 * 被代理的对象(目标对象、真实对象)
 */
public class TeacherDao implements ITeacherDao {
    @Override
    public void teach() {
        System.out.println("老师正在授课中......");
    }
}
代理对象
package top.gldwolf.proxy.staticproxy;

/**
 * 代理对象
 */
public class TeacherDaoProxy implements ITeacherDao {
    // 将被代理对象聚合到代理对象里来
    private ITeacherDao teacherDao;

    /**
     * 通过构造器将被代理对象聚合到代理对象中
     * @param target
     */
    public TeacherDaoProxy(ITeacherDao target) {
        this.teacherDao = target;
    }

    @Override
    public void teach() {
        // 扩展的功能可以在真正方法执行前后操作
        System.out.println("代理开始......");
        
        // 调用被代理对象的方法
        teacherDao.teach();
        
        System.out.println("代理结束......");
    }
}
测试类
package top.gldwolf.proxy.staticproxy;

import org.junit.Test;

public class TestDriver {
    @Test
    public void testStaticProxy() {
        // 创建目标对象
        TeacherDao target = new TeacherDao();

        // 创建代理对象,同时将被代理对象传递给代理对象
        TeacherDaoProxy teacherDaoProxy = new TeacherDaoProxy(target);

        // 通过代理对象,调用被代理对象的方法
        teacherDaoProxy.teach();
    }
}

静态代理的优缺点

  • 优点:在不修改目标对象的功能前提下,能通过代理对象对目标功能扩展
  • 缺点
    • 因为代理对象需要与目标对象实现一样的接口,所以需要很多的代理类
    • 一旦接口增加方法,目标对象与代理对象都需要维护

动态代理

  1. 代理对象不需要实现接口,但是目标对象要实现接口,否则不能使用动态代理
  2. 代理对象的生成是利用 JDK 的 API,动态的在内存中构建代理对象
  3. 动态代理也叫做:JDK 代理、接口代理

JDK 中生成代理对象的 API

  1. 代理类所在的包:java.lang.reflect.Proxy
  2. JDK 实现代理只需要使用 newProxyInstance() 方法,但是该方法需要接收三个参数,完整的写法是:static Object newProxyInstance(ClassLoader loader, Class<?>[] interface, InvocationHandler h)

实现动态代理要包含以下几个组件:

  • 抽象接口
  • 目标对象(被代理的对象,实现了抽象接口)
  • 代理工厂(聚合了一个目标对象,有一个方法返回一个代理对象,不需要实现抽象接口)

代码示例

抽象接口
package top.gldwolf.proxy.dynamic;

/**
 * 动态代理也要有一个接口,但是只有被代理类需要实现这个接口
 */
public interface IteacherDao {
    void teach();
}
目标对象
package top.gldwolf.proxy.dynamic;

/**
 * 被代理类(目标类)
 */
public class TeacherDao implements ITeacherDao {
    @Override
    public void teach() {
        System.out.println("老师正在授课......");
    }
}
代理工厂
package top.gldwolf.proxy.dynamic;

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

/**
 * 代理工厂类
 */
public class ProxyFactory {
    // 维护一个目标对象(注意,类型为 Object)
    private Object target;

    // 通过构造器,对 target 进行初始化
    public ProxyFactory(Object target) {
        this.target = target;
    }

    /**
     * 给目标对象生成一个代理对象
     *
     * 根据传入的对象(TeacherDao),即目标对象
     * 利用反射机制,返回一个代理对象
     * 然后通过代理对象,使用目标方法
     * @return 代理对象执行目标方法后的返回值
     */
    public Object getProxyInstance() {
        /**
         * 说明:
         *      public static Object newProxyInstance(ClassLoader loader,
         *                                  Class<?>[] interface,
         *                                  InvocationHandler h)
         *      1. ClassLoader loader: 指定当前目标对象使用的类加载器,获取类加载器的方式是固定的:obj.getClass().getClassLoader();
         *      2. Class<?>[] interfaces: 目标对象实现的接口类型,使用泛型方法确定类型
         *      3. InvocationHandler h: 事件处理器,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行的目标对象的方法作为参数传入
         */
        // 获取目标对象的类加载器
        ClassLoader classLoader = target.getClass().getClassLoader();

        // 获取目标对象实现的所有接口
        Class<?>[] interfaces = target.getClass().getInterfaces();

        // 创建一个事件处理器
        InvocationHandler handler = new InvocationHandler() {
            // 这个 proxy 就是 newProxyInstance() 方法返回的代理类对象
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("动态代理开始......");

                // 利用反射,调用目标对象(被代理对象)的目标方法
                // result: 目标对象执行方法的返回值
                Object result = method.invoke(target, args);

                System.out.println("动态代理结束......");
                return result;
            }
        };

        // 返回代理对象
        return Proxy.newProxyInstance(classLoader, interfaces, handler);
    }
}
测试类
package top.gldwolf.proxy.dynamic;

import top.gldwolf.proxy.dynamic.ITeacherDao;

/**
 * JDK 动态代理测试类
 */
public class DynamicProxyClient {
    public static void main(String[] args) {
        // 创建一个目标类实例
        TeacherDao target = new TeacherDao();

        // 创建一个代理工厂,并将目标对象传入进去
        ProxyFactory proxyFactory = new ProxyFactory(target);

        // 调用 getProxyInstance() 方法来获取代理对象
        ITeacherDao proxyInstance = (ITeacherDao) proxyFactory.getProxyInstance();

        // 使用代理对象,调用目标对象的方法
        proxyInstance.teach();
        proxyInstance.hello();
    }
}

Cglib 代理

  1. 静态代理和 JDK 代理模式都要求目标对象是实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候可以使用目标对象子类来实现代理,这个代理就是 Cglib 代理。
  2. Cglib 代理也叫作子类代理,它是在内存中构建一个子类对象,从而实现对目标对象功能扩展,有些地方也将 Cglib 代理归属到动态代理的范畴。
  3. Cglib 是一个强大的高性能的代码生成包,它可以在运行期扩展 Java 类与实现 Java 接口,被许多的 AOP 框架所使用,例如 Spring AOP,实现方法拦截等功能。

在 AOP 编程中如何选择代理模式:

  • 目标对象需要实现接口,用 JDK 代理
  • 目标对象不需要实现接口,用 Cglib 代理

Cglib 包的底层是通过使用字节码处理框架 ASM 来转换字节码并生成新的类
注意:

  • 被代理的类不能是用 final 修饰的类,否则会报错
  • 目标对象的方法如果是 final/static,那么就不会被拦截,即:不会执行目标对象方法外的其它业务方法

实现 Cglib 代理要包含以下几个组件:

  • 目标对象(被代理的对象)
  • 代理工厂
    • 聚合了一个目标对象
    • 需要实现 MethodInterceptor 接口,需要实现 intercept() 方法来完成对被代理对象(目标对象)方法的调用
    • 需要写一个 getProxyInstance() 方法,来返回一个代理对象

代码示例

目标对象
package top.gldwolf.proxy.cglib;

/**
 * 被代理对象(目标对象)
 */
public class TeacherDao {
    public void teach() {
        System.out.println("老师正在授课......");
    }
}
代理工厂
package top.gldwolf.proxy.cglib;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * Cglib 代理工厂类
 */
public class ProxyFactory implements MethodInterceptor {
    // 聚合一个被代理对象
    private Object target;

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

    /**
     * 返回一个代理对象
     * @return 目标对象的代理对象
     */
    public Object getProxyInstance() {
        // 1. 创建一个工具类
        Enhancer enhancer = new Enhancer();
        // 2. 设置父类
        enhancer.setSuperclass(target.getClass());
        // 3. 设置回调函数
        enhancer.setCallback(this);
        // 4. 创建并返回子类对象,即代理对象
        return enhancer.create();
    }

    /**
     * 重写 intercept() 方法,会调用目标对象的方法
     *
     * 所有创建的被代理的方法都调用这个方法而不是原来的方法,
     * 原来的方法要么使用反射通过 Method 对象来调用,
     * 要么使用方法代理来调用
     * @param obj 自身,被 enhance 的对象
     * @param method 被拦截的方法
     * @param args 参数列表
     * @param methodProxy 用来调用原始的方法(未被代理的方法),可以根据需要调用任意多次
     * @return 任意与原始方法兼容的返回值类型的值,如果原始方法为 void,则会忽略这个值
     */
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("Cglib 代理开始......");
        Object result = method.invoke(target, args);
        System.out.println("Cglib 代理结束......");
        return result;
    }
}

测试类
package top.gldwolf.proxy.cglib;

/**
 * Cglib 代理的测试客户端
 */
public class CglibProxyClient {
    public static void main(String[] args) {
        // 创建目标对象
        TeacherDao target = new TeacherDao();
        // 创建代理工厂对象
        ProxyFactory proxyFactory = new ProxyFactory(target);
        // 获取代理对象
        TeacherDao proxyInstance = (TeacherDao) proxyFactory.getProxyInstance();
        // 使用代理对象调用目标方法
        proxyInstance.teach();
    }
}

代理模式的变体

防火墙代理

内网通过代理穿透防火墙,实现对公网的访问

缓存代理

比如:当请求图片文件等资源时,先到缓存代理中取,如果取到资源则返回,如果取不资源,再到公网或者数据库中取,然后缓存

远程代理

远程对象的本地代表,通过它可以把远程对象当本地对象来调用。远程代理通过网络和真正的远程对象沟通信息

同步代理

主要使用在多线程中,完成多线程间同步的工作

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值