设计模式(11):代理模式

一、概念

1、定义:为一个对象提供替身或占位符以控制对这个对象的访问

2、类型;结构型

3、适用环境:

  • 远程代理:为不同地址空间的地址对象提供本地的代理对象
  • 虚拟代理:创建资源消耗较大的对象时,先创建一个消耗较小的对象来代替真实对象,而真实的对象只有被用到才创建
  • Copy-On-Write代理:属于虚拟代理的一种,把克隆操作推迟到客户端真正需要时才执行

4、优缺点

优点

  • 协调调用者和被调用者,一定程度上降低耦合
  • 远程代理使本地客户端能访问远程机器上的对象
  • 虚拟代理减少了系统的资源消耗
  • 保护代理控制了对真实对象的使用权限

缺点

  • 由于增加代理对象,可能造成请求处理速度变慢
  • 一些代理模式的实现较为复杂

5、代理模式和装饰者模式的异同点?

代理模式和装饰者模式很像,对于装饰者模式来说,装饰者(decorator)和被装饰者(decoratee)都实现同一个接口,能够动态的增加行为;对于代理模式,代理类(proxy class)和真实处理的类(real class)都实现同一个接口,可以增强方法,不论我们使用哪种模式,都能自定义一些方法,这是相同的地方。

不同之处在于:装饰器模式关注于在一个对象上动态的添加方法,然而代理模式关注在控制对对象的访问,代理类可以对它的客户隐藏具体信息,使用代理模式的时候,要创建对象的实例,而使用装饰器模式的时候,我们是将原始对象作为参数传递给装饰者构造器的。

再简洁一点:

  • 代理模式强调控制,装饰者模式强调增强
  • 代理模式强调透明访问,装饰者模式强调自由构建

二、静态代理

实现一:继承,重写方法
缺点:一定会产生类,产生类爆炸,不符合OCP

class Target{
    public void print() {
        System.out.println("target");
    }
}

class Proxy extends Target{
    @Override
    public void print() {
        System.out.println("proxy");
        super.print();
    }
}

public class test {
    public static void main(String[] args) {
        Proxy proxy = new Proxy();
        proxy.print();
    }
}

实现二:组合,利用接口(本质是装饰者模式),也会产生类爆炸,但比继承少一点

  • 真实对象和代理对象实现同一个接口。
  • 代理对象要包含真实对象
interface Dao {
    void print();
}

class Target implements Dao {

    @Override
    public void print() {
        System.out.println("target");
    }
}

class Proxy implements Dao {
    private Target target;

    Proxy(Target target) {
        this.target = target;
    }

    @Override
    public void print() {
        System.out.println("proxy");
        target.print();
    }
}

public class test {
    public static void main(String[] args) {
        Dao proxy = new Proxy(new Target());
        proxy.print();
    }
}

三、动态代理

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

  • 静态代理只能通过手动完成代理操作,如果被代理类增加新的方法,代理类需要同步新增,违背开闭原则。
  • 动态代理采用在运行时动态生成代码的方式,取消了对被代理类的扩展限制,遵循开闭原则。
  • 若动态代理要对目标类的增强逻辑扩展,结合策略模式,只需要新增策略类便可完成,无需修改代理类的代码。

模拟动态代理
不需要手动创建类文件(因为一旦手动创建类文件,就会产生类爆炸),通过接口反射生成一个类文件,然后调用第三方的编译技术,动态编译这个产生的类文件成class文件,继而利用UrlclassLoader(因为这个动态产生的class不在工程当中所以需要使用UrlclassLoader)把这个动态编译的类加载到jvm当中,最后通过反射把这个类实例化。

JDK动态代理

//1.接口
public interface Dao {
    void add();
}

//2.被代理的对象
public class DaoImpl implements Dao {
    @Override
    public void add() {
        System.out.println("add");
    }
}

//3.自定义Hander类
public class MyInvocation implements InvocationHandler {
    Dao target;

    public MyInvocation(Dao target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("before-----");
        return method.invoke(target, args);
    }
}

//4.测试类
public class Test {
    public static void main(String[] args) {
        Dao target = new DaoImpl();
        Dao proxy = (Dao) Proxy.newProxyInstance(Test.class.getClassLoader(), target.getClass().getInterfaces(), new MyInvocation(target));
        proxy.add();
    }
}
原理:

通过接口反射得到字节码,然后把字节码转成class ,底层是一个native方法,是openJDK ,c++写的

JDK Proxy 生成对象的步骤如下:
1、拿到被代理类的引用,并且获取它的所有的接口(反射获取)。
2、JDK Proxy类重新生成一个新的类,实现了被代理类所有接口的方法。
3、动态生成Java代码,把增强逻辑加入到新生成代码中。
4、编译生成新的Java代码的class文件。
5、加载并重新运行新的class,得到类就是全新类。

以上这个过程就叫字节码重组。JDK 中有一个规范,在 ClassPath 下只要是$开头的 class文件一般都是自动生成的。

CGLib动态代理

//1.被代理的对象
class Target {
    void find() {
        System.out.println("target");
    }
}

//2.处理类
public class CGlibMeipo implements MethodInterceptor {
    public Object getInstance(Class<?> clazz) throws Exception {
        //相当于Proxy,代理的工具类
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        return enhancer.create();
    }

    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("before");
        Object obj = methodProxy.invokeSuper(o, objects);
        System.out.println("after");;
        return obj;
    }
}

//3.测试类
public class Test {
    public static void main(String[] args) {
        try {
            Target obj = (Target) new CGlibMeipo().getInstance(Target.class);
            obj.find();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
CGLib和JDK动态代理对比

1.JDK是采用读取接口的信息,CGLib覆盖父类方法。

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

3.JDK Proxy 对于用户而言,必须要有一个接口实现,目标类相对来说复杂,CGLib 可以代理任意一个普通的类,没有任何要求。

4.CGLib 生成代理逻辑更复杂,效率,调用效率更高,生成一个包含了所有的逻辑的FastClass,不再需要反射调用。

JDK Proxy生成代理的逻辑简单,执行效率相对要低,每次都要反射动态调用。

5.CGLib 有个坑,CGLib不能代理final的方法。

优点

1、代理模式能将代理对象与真实被调用的目标对象分离。
2、一定程度上降低了系统的耦合度,扩展性好。
3、可以起到保护目标对象的作用。
4、可以对目标对象的功能增强。

缺点

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

Spring 中的代理选择原则

Spring 利用动态代理实现 AOP 有两个非常重要的类,一个是 JdkDynamicAopProxy 类和 CglibAopProxy

1、当 Bean 有实现接口时,Spring 就会用 JDK 的动态代理
2、当 Bean 没有实现接口时,Spring 选择 CGLib。
3、Spring 可以通过配置强制使用 CGLib,只需在 Spring 的配置文件中加入如下代码:

<aop:aspectj-autoproxy proxy-target-class="true"/>

发布了43 篇原创文章 · 获赞 6 · 访问量 3907

猜你喜欢

转载自blog.csdn.net/weixin_44424668/article/details/103260027
今日推荐