Java中的代理模式

1 篇文章 0 订阅

什么是代理模式

定义:代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介。
      举个例子来说明:假如说我现在想买一辆二手车,虽然我可以自己去找车源,做质量检测等一系列的车辆过户流程,但是这确实太浪费我得时间和精力了。我只是想买一辆车而已为什么我还要额外做这么多事呢?于是我就通过中介公司来买车,他们来给我找车源,帮我办理车辆过户流程,我只是负责选择自己喜欢的车,然后付钱就可以了。用图表示如下:
在这里插入图片描述

为什么要用

  1. 让客户端类只关注接口,不需要关注接口内部。
  2. 开闭原则,增加功能:代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后对返回结果的处理等。代理类本身并不真正实现服务,而是同过调用委托类的相关方法,来提供特定的服务。真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共的服务。例如我们想给项目加入缓存、日志这些功能,我们就可以使用代理类来完成,而没必要打开已经封装好的委托类。

什么场景用

      真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共的服务。例如我们想给项目加入缓存、日志这些功能,我们就可以使用代理类来完成,而没必要打开已经封装好的委托类。

设计模式六大原则

  1. 单一职责原则(SRP, Single Responsibility Principle):对一个类(也可以是一个方法)而言,应该只有一个引起它代码发生变化的原因。
  2. 开放、封闭原则(OCP,Open Closed Principle):软件实体(类、模块、方法等)应该可以扩展,但不可以修改。也就是对扩展开放(Open for extension),对更改封闭(Closed for modificatioin)
  3. 依赖倒转原则(DIP,Dependency Inversion Principle):高层模块不应该依赖于低层模块,二者都应该依赖于抽象。抽象不应该依赖于具体实现细节,而具体实现细节应该依赖于抽象。
  4. 里氏替换原则(LSP,Liskov Substitution Principle):子类必须能够替换掉它们的父类。这样父类才能真正被复用,而子类也能够在父类的基础上增加自己新的功能。
  5. 接口分离原则(ISP,Interface Segregation Principle):客户类不应被强迫依赖那些它们不需要的接口。
    当客户类被强迫依赖那些它们不需要的接口时,则这些客户类不得不受制于这些接口。这无意间就导致了所有客户类之间的耦合。换句话说,如果一个客户类依赖了一个类,这个类包含了客户类不需要的接口,但这些接口是其他客户类所需要的,那么当其他客户类要求修改这个类时,这个修改也将影响这个客户类。通常我们都是在尽可能的避免这种耦合,所以我们需要竭尽全力地分离这些接口。
  6. 最少知识原则(LKP,Least Knowledge Principle)也叫迪米特法则(Law of Demeter):只和自己直接的 “朋友” 交谈。打个比方,人可以命令一条狗行走,但是不应该直接指挥狗的腿行走,应该由狗去指挥它的腿行走。

实现分类

按照代理创建的时期来进行分类,可分为静态代理和动态代理两类。


以下是一个接口和该接口的实现类,作为代理实例中被代理的对象。
一、创建一个接口

/**
 * @ClassName: IService
 * @Description: 一个普通接口
 */
public interface IService {
    void say();
}

二、这个接口的实现类

/**
 * @ClassName: ServiceImpl
 * @Description: 接口的实现类,实现相应的业务
 */
public class ServiceImpl implements IService {
    private static final Logger LOGGER = LogManager.getLogger(ServiceImpl.class);

    @Override
    public void say() {
        LOGGER.info("接口的真正实现类:{}", this.getClass().getSimpleName());
        // do something
    }
}

静态代理

  1. 含义
          静态代理是由程序员创建或特定工具自动生成源代码,在对其编译。在程序员运行之前,代理类.class文件就已经被创建了。
  2. 实现步骤

a、接口代理实现类,实现了上面的接口IService,同时在构造函数中要传入上面的接口,在override的方法中,调用接口中的方法

public class ProxyServiceImpl implements IService {
    private static final Logger LOGGER = LogManager.getLogger(ProxyServiceImpl.class);

    private IService iService;
	
	/**
     * @Title: ProxyServiceImpl
     * @Description: 构造函数中必须传入被代理的接口实例
     * @param: iService
     */
    public ProxyServiceImpl(IService iService) {
        this.iService = iService;
    }

	/**
     * @Title: say
     * @Description: 实现了接口的方法
     */
    @Override
    public void say() {
        LOGGER.info("实现相同接口的代理类:{},准备开始真正的接口实现类", this.getClass().getSimpleName());
        //调用接口中的方法
        iService.say();
        LOGGER.info("实现相同接口的代理类:{},真正的接口实现类方法运行完成", this.getClass().getSimpleName());
    }
}

b、应用

public class Main {
    private static final Logger LOGGER = LogManager.getLogger(Main.class);

    public static void main(String[] args) {
        IService service = new ServiceImpl();
        service.say();

        LOGGER.info("-----");

        IService proxyService = new ProxyServiceImpl(service);
        proxyService.say();
    }
}
  1. 总结:静态代理的实现就是:一个接口被多次实现,而在某个实现中,又会调用这个接口。
  2. 优点:可以做到在符合开闭原则的情况下对目标对象进行功能扩展。
  3. 缺点:需要为每一个服务都得创建代理类,工作量太大,不易管理。同时接口一旦发生改变,代理类也得相应修改。

动态代理

  1. 动态代理是在程序运行时通过反射机制动态创建的。
  2. 动态代理根据底层实现不同又分为JDK和Cglib。
JDK的动态代理
  1. 被代理的类、方法,必须是实现了某个接口,否则将报错:java.lang.IllegalArgumentException: object is not an instance of declaring class
  2. 底层其实是根据ProxyGenerator.java类及传入的参数等内容,创建出一个class类,也就是通过Java的反射,创建出一个临时实现接口的类,这个类就像我们写静态代理时的那个代理
  3. 实现步骤(实现的接口是上面的IService)

a、JDK的动态代理实现类

/**
 * @ClassName: JdkDynamicProxyHandler
 * @Description: 这个是JDK的动态代理
 */
public class JdkDynamicProxyHandler implements InvocationHandler {
    private static final Logger LOGGER = LogManager.getLogger(JdkDynamicProxyHandler.class);

    private Object targetObject;

	/**
     * @Title: JdkDynamicProxyHandler
     * @Description: 构造函数
     * @param: targetObject: 这个是被代理的类
     */
    public JdkDynamicProxyHandler(Object targetObject) {
        this.targetObject = targetObject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //执行目标方法前
        LOGGER.info("--> JDK的代理开始,before");

		byte[] b = ProxyGenerator.generateProxyClass(proxy.getClass().getSimpleName(), proxy.getClass().getInterfaces());
        FileOutputStream out = new FileOutputStream("./" + proxy.getClass().getSimpleName() + ".class");
        out.write(b);
        out.flush();
        out.close();

        LOGGER.info("--> JDK的代理,{}", method.getName());

        //执行目标方法
        Object result = method.invoke(targetObject, args);

        //执行目标方法后
        LOGGER.info("--> JDK的代理结束,after");
        return result;
    }
}

b、应用

public class Main {

    public static void main(String[] args) {
        //获取被代理的类加载器
        ClassLoader classLoader = ServiceImpl.class.getClassLoader();
        //获取被代理的类的接口类型
        Class<?>[] interfaces = ServiceImpl.class.getInterfaces();
        //指定动态处理器,这样能触发调用invoke()方法
        JdkDynamicProxyHandler proxyHandler = new JdkDynamicProxyHandler(new ServiceImpl());

        IService service = (IService) Proxy.newProxyInstance(classLoader, interfaces, proxyHandler);
        service.say();
    }
}
  1. 总结:实现了java.lang.reflect.InvocationHandler这个JDK中的接口,重写invoke方法,在该方法中通过method.invoke来调用真正被代理的类方法。
  2. 优点:符合开闭原则、同时也不需要写太多的代理类。
  3. 缺点:被代理的类必须实现了一个接口(而且只能是接口,抽象类都不行),通过这个接口才能完成代理。
  4. 在IDEA中debug这块代码时,会出现invoke方法中的内容会重复执行多次,即LOGGER.info("--> JDK的代理,{}", method.getName());这行会输出--> JDK的代理,toString,是因为调试时IDEA会调用被代理类的toString()方法,代理类会代理该类的所有方法(包括toString)。这里是别的人回答。
  5. 注意,其实在重写invoke方法时,第一个参数是没有用到的,因为第一个参数是JDK动态代理生成的临时类对象$Proxy0.class,在后续的操作过程中,都是通过这个类对象来完成,以下就是生成的代理类
public final class $Proxy0 extends Proxy implements IService {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public $Proxy0(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);
        }
    }

	//这个是方法是重写了接口中的方法,可见第一个参数为this,第二个参数为接口中的方法
    public final void say() throws  {
        try {
            super.h.invoke(this, m3, (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);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.my.springdemo.proxydemo.service.IService").getMethod("say");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}
  1. 为什么必须要有接口?
  • 根据上面生成的class类可以看到,这个临时类继承了Proxy.class,并实现了我们定义的接口,通过实现接口来建立与接口连接
  • java中为单继承、多实现
  • 从临时生成的代码可知道,运用了java中的多态和反射来生成这个class类
  • 这个类与Proxy.class相关,而Proxy又与InvocationHandler是组合关系,增强的方法就是InvocationHandler的实现类中
  • 这个类也与我们定义的接口相关,来获取接口中的方法及参数,不需要获取接口的真正实现类方法
Cglib的动态代理
  1. 导入包cglib
	<dependency>
		<groupId>cglib</groupId>
		<artifactId>cglib</artifactId>
		<version>3.2.10</version>
	</dependency>
  1. 代理类
/**
 * @ClassName: CgLibDynamicProxyHandler
 * @Description: CGLib的动态代理实现类
 */
public class CgLibDynamicProxyHandler implements MethodInterceptor {
    private static final Logger LOGGER = LogManager.getLogger(CgLibDynamicProxyHandler.class);

    /**
     * @Title: getInstance
     * @Description: 通过传来的对象获取代理对象,也就是说被代理的对象是生成代理类对象的父类
     * @param: targetObject
     * @return: java.lang.Object
     */
    public Object getInstance(Object targetObject) {
    	//实例化一个增强器,也就是cglib中的一个class generator
        Enhancer enhancer = new Enhancer();
        //设置目标类
        enhancer.setSuperclass(targetObject.getClass());
        //设置代理对象
        enhancer.setCallback(this);
        //生成代理类并返回一个实例
        return enhancer.create();;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        //执行目标方法前
        LOGGER.info("--> JDK的代理开始,before");

        //执行目标方法,也就是代理类调用父类的方法
        Object result = methodProxy.invokeSuper(o, args);

        //执行目标方法后
        LOGGER.info("--> JDK的代理结束,after");
        return result;
    }
}
  1. 应用
public class Main {
    public static void main(String[] args) {
        //可以把生成的代理类输出出来
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "./");

        CgLibDynamicProxyHandler cglibProxy = new CgLibDynamicProxyHandler();
        IService instance = (IService) cglibProxy.getInstance(new ServiceImpl());
        instance.say();
    }
}
  1. 流程:
    a、实现MethodInterceptor 这个cglib中的接口中的方法
    b、在方法中通过methodProxy.invokeSuper()来调用被代理的类方法
    c、通过getInstance()方法来获取生成的代理类对象
  2. 特点:被代理的是一个类(不是接口),而且这个类不能是final,因为要产成子类,这个子类就是代理对象类
  3. 工作原理:采用了FastClass的机制来实现对被拦截方法的调用。FastClass机制就是对一个类的方法建立索引,通过索引来直接调用相应的方法
  4. 与JDK代理对比:
    a、CGLIB创建的动态代理对象比JDK创建的动态代理对象的性能更高
    b、对于单例的对象,因为无需频繁创建对象,用CGLIB合适
    c、CGLib由于是采用动态创建子类的方法,对于final修饰的方法无法进行代理
  5. 实际在Spring中使用CGLib时,不需要导入CGLib的包,因为从spring3.2以后,spring框架本身不在需要cglib这个jar包了,cjlib.jar已经被spring项目的jar包集成进去。为了防止项目中其他对cglib版本依赖不一样的冲突。

代理形式分为两种:
静态代理(手动通过实现接口)和动态代理(通过接口生成类文件)

动态代理的实现分为两种:
JDK代理(被代理的类必须实现了接口)和Cglib代理(只要类不是final的就可以)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值