设计模式与应用:代理模式(三种详解)

本文介绍代理模式核心思想,静态代理、jdk动态代理、cglib动态代理的分别实现与对比选用,代理模式与装饰模式的对比

  • 简介
  • 应用场景
  • 结构图
  • 角色和职责
    • 静态代理
      • 静态代理实现
    • JDK动态代理
      • jdk动态代理实现
    • CGLIB动态代理
      • cglib动态代理实现
  • 静态代理、jdk动态代理、cglib动态代理对比
    • 静态代理
    • jdk动态代理
    • CGLIB动态代理
    • 静态代理、jdk动态代理、cglib动态代理对比
  • 代理模式与装饰模式对比

简介

Proxy代理模式,是构造型的设计模式之一

代理模式为其他对象提供代理以控制这个对象的访问。

所谓代理,是指具有与代理元(被代理对象)具有相同接口的类。client需要通过代理与被代理的目标类交互,代理类就是在交互的过程中(前后)进行特殊处理。

注:这里讨论的代理都是对接口方法的代理。目前实现代理的方式:静态代理和jdk动态代理都是对接口方法的增强。而对于没有接口的类的方法,增强就需要使用CGLIB的实现方式。

应用场景

  • 对于不改变原方法代码情况

成熟框架正在使用:

  • springAOP核心实现就是动态代理
  • hibernate等一些ORM框架大量使用
  • 一些web层框架也有使用

结构图

代理模式的核心是对目标方法/类进行功能增强!无论哪种代理方式他的核心思想来源都是于此结构
设计模式与应用:代理模式(三种详解)_第1张图片

角色和职责

静态代理

  • subject(抽象主题角色):真实主题与代理主题的共同接口
  • RealSubject(真实主题角色):定义代理角色所代表的真实对象
  • Proxy(代理主题角色):含有对真实主题角色的引用,代理角色通常在将客户端调用传递给真实主题对象之前或之后执行某些操作,而不是单纯返回真实对象。

静态代理实现

设计模式与应用:代理模式(三种详解)_第2张图片
subject接口

package com.mym.designmodel.proxy.staticProxy;

/**
 * 职责: Subject 真实对象与代理对象的共同接口
 */
public interface Subject {

    public void eat();
}

真实实现类

package com.mym.designmodel.proxy.staticProxy;

/**
 * 职责:RealSubject 真实对象的代理类
 */
public class RealSubject implements Subject{
    @Override
    public void eat() {
        System.out.println("吃饭!");
    }
}

代理实现类

package com.mym.designmodel.proxy.staticProxy;

/**
 * 职责 ProxySubject 代理对象的实现类
 */
public class ProxySubject implements Subject{
    RealSubject realSubject = null;

    @Override
    public void eat() {
        if(realSubject == null){
            realSubject = new RealSubject();
        }
        prepare();
        realSubject.eat();
        clean();
    }

    private void prepare(){
        System.out.println("准备碗筷!");
    }

    private void clean(){
        System.out.println("清洗碗筷!");
    }
}

测试

package com.mym.designmodel.proxy.staticProxy;

/**
 * 测试静态代理
 */
public class MainClass {
    public static void main(String[] args) {
        ProxySubject proxySubject = new ProxySubject();
        proxySubject.eat();
    }
}

结果:

准备碗筷!
吃饭!
清洗碗筷!

JDK动态代理

jdk 有动态代理支持.参看jdk文档中的Proxy类
jdk动态代理的原理我先一句话概括下:通过类装载器拿到真实实现类真实实现类的接口字节码文件,然后构造生成一个代理类(是有真实类字节码文件的)。生成完后此时又回归待静态代理的uml结构上了。
jdk动态代理深层源码剖析可以参考:http://rejoy.iteye.com/blog/1627405

  • InvocationHandler 接口
  • invoke方法
  • Proxy.newProxyIntance()

jdk动态代理实现

设计模式与应用:代理模式(三种详解)_第3张图片

代理接口

package com.mym.designmodel.proxy.dynamicProxy;

/**
 * 职责: Subject 真实对象与代理对象的共同接口
 */
public interface Subject {

    public void eat();
}

真实实现类

package com.mym.designmodel.proxy.dynamicProxy;

/**
 * 职责:RealSubject 真实对象的代理类
 */
public class RealSubject implements Subject {
    @Override
    public void eat() {
        System.out.println("吃饭!");
    }
}

动态代理 方法处理类

package com.mym.designmodel.proxy.dynamicProxy;

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

/**
 * proxy 处理
 */
public class ProxyHandler implements InvocationHandler{

    private RealSubject realSubject = null;

    public void setRealSubject(RealSubject realSubject) {
        this.realSubject = realSubject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        prepare();
        Object result = method.invoke(realSubject, args);
        clean();
        return result;
    }

    private void prepare(){
        System.out.println("dynamic 准备碗筷!");
    }

    private void clean(){
        System.out.println("dynamic 清洗碗筷!");
    }
}

测试

package com.mym.designmodel.proxy.dynamicProxy;

import java.lang.reflect.Proxy;

/**
 * 测试动态代理
 */
public class MainClass {

    public static void main(String[] args) {
        ProxyHandler proxyHandler = new ProxyHandler();
        proxyHandler.setRealSubject(new RealSubject());
        Subject subject = (Subject) Proxy.newProxyInstance(RealSubject.class.getClassLoader(), RealSubject.class.getInterfaces(), proxyHandler);
        subject.eat();
    }
}

结果

dynamic 准备碗筷!
吃饭!
dynamic 清洗碗筷!

CGLIB动态代理

cglib是一个框架,需要额外导入包。使用的是与jdk动态代理不同的技术实现,即asm的字节码技术。

cglib动态代理也是字节码层面的代理实现方式。原理概括一下:
CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。

cglib动态代理实现

若使用maven等版本控制来管理依赖则去maven仓库搜索cglib引入依赖即可。
若没有依赖管理则需要把cglib、asm的jar包引入到工程classpath下才可以进行测试使用。jar包同样可以上maven仓库搜索然后下载jar并加入到工程path中就可以。
笔者测试使用的版本是:
- cglib-3.2.4.jar
- asm-5.1.jar

设计模式与应用:代理模式(三种详解)_第4张图片

被代理类

package com.mym.designmodel.proxy.cglib;

/**
 * 职责:RealSubject 真实对象
 */
public class RealSubject{
    public void eat() {
        System.out.println("吃饭!");
    }
}

代理实现

package com.mym.designmodel.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 CglibProxy implements MethodInterceptor {
    private Enhancer enhancer = new Enhancer();

    /*
    * getProxy(SuperClass.class)方法通过入参即父类的字节码,通过扩展父类的class来创建代理对象。
    * intercept()方法拦截所有目标类方法的调用,
    *   obj表示目标类的实例,
    *   method为目标类方法的反射对象,
    *   args为方法的动态入参,
    *   proxy为代理类实例。
    * proxy.invokeSuper(obj, args)通过代理类调用父类中的方法
    * */
    public Object getProxy(Class clazz){
        //设置需要创建子类的类
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        //通过字节码技术动态创建子类实例
        return enhancer.create();
    }

    //实现MethodInterceptor接口方法
    public Object intercept(Object obj, Method method, Object[] args,
                            MethodProxy proxy) throws Throwable {
        prepare();
        Object result = proxy.invokeSuper(obj, args);//通过代理类调用父类中的方法
        clean();
        return result;
    }

    private void prepare(){
        System.out.println("CGLIB 准备碗筷!");
    }

    private void clean(){
        System.out.println("CGLIB 清洗碗筷!");
    }
}

测试:

package com.mym.designmodel.proxy.cglib;

/**
 * 测试CGLIB代理
 */
public class MainClass {
    public static void main(String[] args) {
        CglibProxy cglibProxy = new CglibProxy();
        RealSubject proxy = (RealSubject) cglibProxy.getProxy(RealSubject.class);
        proxy.eat();
    }
}

执行:

CGLIB 准备碗筷!
吃饭!
CGLIB 清洗碗筷!

静态代理、jdk动态代理、cglib动态代理对比

静态代理

缺点:

  • 冗余:由于代理对象要实现与目标对象一致的接口,会产生过多的代理类。
  • 不易维护:一旦接口增加方法,目标对象与代理对象都要进行修改。

jdk动态代理

  • 不需要实现接口,但是要求目标对象必须实现接口,否则不能使用动态代理

CGLIB动态代理

  • 可以代理没有实现接口的类。
  • CGLIB是一个强大的高性能的代码生成包,它可以在运行期扩展Java类与实现Java接口。它广泛的被许多AOP的框架使用, 例如Spring AOP和dynaop,为他们提供方法的interception(拦截)。
  • CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。不鼓励直接使用ASM,因为它需要你对JVM内部结构包括class文件的格式和指令集都很熟悉

静态代理、jdk动态代理、cglib动态代理对比

  • 静态代理在编译时就已经实现,编译完成后代理类是一个实际的class文件
  • 静态代理实现简单,但只能为一个目标对象服务。如果目标对象过多,则会产生很多代理类

  • JDK动态代理需要目标对象实现业务接口,代理类只需实现InvocationHandler接口

  • JDK动态代理是在运行时动态生成的,即编译完成后没有实际的class文件,而是在运行时动态生成类字节码,并加载到JVM中
  • 而JDK动态代理须实现InvocationHandler接口,通过反射代理方法,比较消耗系统性能,但可以减少代理类的数量,使用更灵活。

  • cglib代理无需实现接口,通过生成类字节码实现代理,比反射稍快,不存在性能问题,但cglib会继承目标对象,需要重写方法,所以目标对象不能为final类

  • cglib代理的对象无需实现接口,达到代理类无侵入

代理模式与装饰模式对比

代理模式和装饰模式都是对 目标 方法/类 进行功能增强。
详细对比请参看笔者对装饰模式的介绍,这里就不赘述
设计模式与应用:装饰模式

你可能感兴趣的:(设计模式与应用)