设计模式-05.代理模式

代理模式

在代理模式(Proxy Pattern)中,一个类代表另一个类的功能,指为其他对象提供一种代理,以控制对这个对象的访问。

代理对象在客户端和目标对象之间起到中介作用。

属于结构型模式

  • 掌握代理模式的应用场景和实现原理

  • 了解静态代理和动态代理的区别

  • 了解CGlib和JDK Proxy的根本区别

  • 手写实现定义的动态代理

优点

  1. 职责清晰,增强了目标对象的职责。
  2. 高扩展性,一定程度上降低了系统的耦合程度。
  3. 将代理对象与目标对象(被代理对象)分离。
  4. 可以起到保护目标对象的作用。

缺点

  1. 由于在客户端和目标对象之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。
  2. 实现代理模式需要额外的工作,有些代理模式的实现非常复杂,增加了系统的复杂度。

实例

  1. 买火车票不一定在火车站买,也可以去代售点。
  2. 房产中介,代理卖方。
  3. 微商,代理卖产品。
  4. spring aop。

使用场景

  1. 远程代理。

  2. 虚拟代理。

  3. Copy-on-Write 代理。

  4. 保护(Protect or Access)代理。

  5. Cache代理。

  6. 防火墙(Firewall)代理。

  7. 同步化(Synchronization)代理。

  8. 智能引用(Smart Reference)代理。

  9. 保护目标对象,增强目标对象。

注意事项

  1. 和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。
  2. 和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。

静态代理

  • 显式声明被代理对象

  • 场景模拟:

    • 张三(需求方)想吃肯德基,因为懒不想出门,找李四(代理方)代买
IPerson.java
    
public interface IOrde {
    /**
     * 订单:下单,接单
     */
    void orde();
}
Zhangsan.java
    
/**
 * @author healk
 */
public class ZhangSan implements IOrde {

    public void orde() {
        System.out.println("张三要吃肯德基");
    }
}
Lisi.java
    
public class Lisi implements IOrde {

    private ZhangSan zhangsan;

    //构造方法
    public Lisi(ZhangSan zhangsan) {
        this.zhangsan = zhangsan;
    }

    public void orde() {
        System.out.println("李四,接收张三的需求");
        zhangsan.orde();
        System.out.println("完成需求");
    }
}
public class Test {
    public static void main(String[] args) {
        Lisi lisi = new Lisi(new ZhangSan());
        lisi.orde();
    }
}

动态代理

场景模拟:

  • 张三想吃肯德基,因为懒不想出门,所以找跑腿代买(代理)。
  • 赵六想吃水果,也想找跑腿帮买。

JDK Proxy

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

/**
 * @author healk
 * @description 实现InvocationHandler接口
 */
public class JdkPaotui implements InvocationHandler {
    private IOrde target;
    
    /**
     * 拿到目标对象,并创建代理对象
     * @param target
     * @return
     */
    public IOrde getInstance(IOrde obj){
        //通过反射获取目标对象
        this.target = obj;
        Class<?> clazz =  target.getClass();
        
        //java.lang.reflect中的Proxy代理类
        //此处的this表示InvocationHandler,所以需要去实现InvocationHandler接口
        return (IOrde) Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
    }

    /**
    * 重写InvocationHandler接口中invoke
    */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        //方法调用:动态代用客户端调取的方法,如:zhangsan.orde();
        Object result = method.invoke(this.target,args);
        after();
        return result;
    }

    private void after() {
        System.out.println("完成订单");
    }

    private void before() {
        System.out.println("跑腿,接收等订单");
    }
}
public class Test {
    public static void main(String[] args) {
        JdkPaotui jdkPaotui = new JdkPaotui();
        IOrde zhangsan = jdkPaotui.getInstance(new ZhangSan());
        zhangsan.orde();

        IOrde zhaoliu = jdkPaotui.getInstance(new ZhaoLiu());
        zhaoliu.orde();

    }
}
public interface IOrde {
    /**
     * 订单:下单,接单
     */
    void orde();

}
public class ZhangSan implements IOrde {

    public void orde() {
        System.out.println("张三要吃肯德基");
    }
}
public class ZhaoLiu implements IOrde {

    public void orde() {
        System.out.println("赵六要吃水果");
    }
}
  • JdkPaotui代理类方法中只针对IOrde这一个接口,若需要其他接口时,则不支持。
  • 方法升级
public class JdkProxy implements InvocationHandler {
    //将目标对象改为范围更大的Object对象
    private Object target;

    /**
     * 拿到目标对象
     * @param target 将目标对象改为范围更大的Object对象
     * @return
     */
    public IOrde getInstance(Object obj){
        this.target = obj;
        Class<?> clazz =  target.getClass();
        return (IOrde) Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
    }

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

    private void after() {
        System.out.println("完成订单");
    }

    private void before() {
        System.out.println("跑腿,接收等订单");
    }
}

cglib Proxy

  • 引入依赖:
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib-nodep</artifactId>
    <version>2.2</version>
</dependency>
public CglibProxy implements MethodeInterceptor {
    
    public Object getInstance(Class<?> clazz){
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        return enhancer.create();
    }
    
    /**
    * 	重写MethodeInterceptor接口的intercept方法,增强代理类
    */
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        before();
        Object obj = methodProxy.invokeSuper(o,objects);
        after();
        return obj;
    }
    
    private void before(){
        System.out.println("跑腿,已接单");
        System.out.println("已购买");
    }

    private void after(){
        System.out.println("已完成订单");
    }
}
public class Customer {

    public void orde(){
        System.out.println("客户需求:买水果");
    }
}
public class CglibTest {
    public static void main(String[] args) {
        try {
           // System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"E://cglib_proxy_classes");
            Customer obj = (Customer) new CGlibProxy().getInstance(Customer.class);
            System.out.println(obj);
            obj.orde();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

jdkProxy与cglib

    • JDK是采用读取接口的信息

    • CGLib覆盖父类方法(生成被代理类的子类)

    • 目的:都是生成一个新的类,去实现增强代码逻辑的功能

    • JDK Proxy 对于用户而言,必须要有一个接口实现,目标类相对来说复杂
    • CGLib 可以代理任意一个普通的类,没有任何要求
    • CGLib 生成代理逻辑更复杂,效率,调用效率更高,生成一个包含了所有的逻辑的FastClass,不再需要反射调用
    • JDK Proxy生成代理的逻辑简单,执行效率相对要低,每次都要反射动态调用
    • CGLib 有个坑,CGLib不能代理final的方法,因为CGLib原理是动态生成被代理类的子类,而final方法不会被覆盖。
  1.  Spring中当Bean有实现接口是,Spring会使用Jdk动态代理
     当Bean没有实现接口时,Spring选择CGLib
     Spring可以通过配置强制使用CGLib,只需在Spring配置文件中加入代码
    
      <aop:aspectj-autoproxy proxy-target-class="true"/>

总结

  • JDK中的动态代理是通过反射类Proxy以及InvocationHandler回调接口实现的,但是,JDK中所要进行动态代理的类必须要实现一个接口,也就是说只能对该类所实现接口中定义的方法进行代理,这在实际编程中具有一定的局限性,而且使用反射的效率也并不是很高
  • 使用CGLib实现动态代理,完全不受代理类必须实现接口的限制,而且CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类。
posted @ 2020-05-04 23:19  嗨!阿克  阅读(235)  评论(0编辑  收藏  举报