静态代理与动态代理

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/huyuyang6688/article/details/52025353

  代理,就是代替原来的角色去做事,下面这张图对代理的解释很生动形象:

静态代理


  23种设计模式中,代理模式是一种很经典的模式,当我们想改变一个类的行为的时候(比如添加额外的服务像添加日志等),可以创造一个跟这个类实现相同接口的类,重写对应的方法。这就相当于为一个类创建了一个“替身”。

  代理模式的结构如下:

  比如要给原来的业务逻辑添加日志功能,则可以添加代理类,重写需要添加日志的业务类。

  用户管理接口

public interface UserManager {
    public void addUser(User user);
}

  用户管理实现类

public class UserManagerImpl implements UserManager {
    @Override
    public void addUser(User user) {
        System.out.println("---------addUser()---------");
    }
}

  用户管理代理类

public class UserManagerProxyImpl implements UserManager{
    private UserManager userManager;
    public UserManagerProxyImpl(UserManager userManager){
        this.userManager=userManager;
    }
    @Override
    public void addUser(User user) {

        userManager.addUser(user);
        //添加额外服务-日志功能
        logger.info("添加用户:"+user.getUserName());
    }
}

  此时调用这个新的方法,不会改变系统原来的稳定性。

动态代理


  上面的静态代理,每个代理类只能为一个业务类服务,如果放在实际应用中,肯定需要使用多个代理,同时会带来大量的重复代码。动态代理可以为任何类动态生成一个代理类来实现全部的代理功能。

  静态代理由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。 在程序运行时,运用反射机制动态创建而成。

  JDK和Cglib都实现了动态代理,但略有差异,下面分别说明。

  JDK动态代理

  动态代理类克服了上面静态代理类需要继承唯一接口,并且要实现相应的方法的缺陷。他可以为任何类创建代理类并且在运行时动态创建代理对象。

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

public class LogHandler implements InvocationHandler{
    //被代理的目标类
    private Object targetObject;
    //创建代理
    public Object createProxyInstance(Object targetObject){
        this.targetObject=targetObject;
        return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),targetObject.getClass().getInterfaces(),this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        //代理服务方法
        insertLog(method,args);
        //调用目标方法(原业务逻辑)
        Object ret=method.invoke(targetObject, args);
        return ret;
    }

    private void insertLog(Method method,Object[] args){
        System.out.println("logInfo:【methodName:"+method.getName()+";args:"+args+"】");
    }
}

  实现动态代理,利用Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)方法获得代理类,loader就是被代理类的类加载器,interfaces 是被代理类实现的接口,它实现了几个接口,就会为其生成几个相应的代理类。此外必须要实现InvocationHandler接口,重写其invoke方法,用java.lang.reflect.Method.invoke()方法调用目标方法(可以在调用之前或者之后添加代理服务方法)。

  客户端调用如下:

public class Client {
    public static void main(String[] args) {
        LogHandler logHandler=new LogHandler();
        UserManager userManager=(UserManager)logHandler.createProxyInstance(new UserManagerImpl());
        userManager.addUser(new User("Danny","123456"));

        OrderManager orderManager=(OrderManager)logHandler.createProxyInstance(new OrderManagerImpl());
        orderManager.addOrder(new Order("Danny","B20160726085911223004"));
    }
}

  如上,调用动态代理的时候,new一个动态代理的实例,通过动态代理来创建原业务类的代理类,然后直接调用接口就行。这样不仅可以为UserManager这一个接口服务了,其他接口的方法如果需要添加日志,都可以通过这一个动态代理类来实现。

  Cglib动态代理

  添加jar包asm-commons-2.2.2.jar、asm-util-2.2.2.jar、asm-2.2.2.jar、cglib-nodep-2.1_3.jar。

  注意:如果用了cglib-nodep-2.1_3.jar,就不能用cglib-2.2.2.jar了,这两个jar包会冲突;而且Spring的asm-2.2.2.jar和Hibernate的asm.jar也会冲突,不能共存。否则会报如下的错:

java.lang.NoSuchMethodError: org.objectweb.asm.ClassWriter.<init>(Z)V

  Cglib代理实现

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

public class CglibProxy implements MethodInterceptor {
    private Enhancer enhancer = new Enhancer();
    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 {
        //代理服务方法
        insertLog(method,args);
        // 通过代理类调用父类中的方法
        Object result = proxy.invokeSuper(obj, args);
        return result;
    }

    private void insertLog(Method method,Object[] args){
        System.out.println("logInfo:【methodName:"+method.getName()+";args:"+args+"】");
    }
}

  客户端调用如下:

public class Client {
    public static void main(String[] args) {
        CglibProxy cglibProxy=new CglibProxy();
        UserManagerImpl userManager=(UserManagerImpl)cglibProxy.getProxy(UserManagerImpl.class);
        userManager.addUser(new User("Danny","123456"));
    }
}

  最后简单总结两点JDK和Cglib实现的动态代理的区别:

  1、JDK实现动态代理的代理对象和目标对象必须实现相同的接口;Cglib实现动态代理的原理则是为目标对象创建一个子类座位代理对象。如果目标类实现了接口,则必须用JDK动态代理,否则,两个都可以用。

  2、JDK在运算量小的时候性能优于Cglib,运算量大的时候Cglib性能较优。详参考《Cglib 与 JDK动态代理的运行性能比较》


【 转载请注明出处——胡玉洋《静态代理与动态代理》】

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏WindCoder

《Linux内核分析》之计算机是如何工作的 实验总结

马马虎虎学完了Python课程,一直想学下linux,看到里面有个linux的就选上了。当初没细看,如今听完第一节课有点傻眼,竟然糊里糊涂给自己找了一科汇编语言...

1181
来自专栏偏前端工程师的驿站

Java魔法堂:类加载机制入了个门

一、前言                                 当在CMD/SHELL中输入 $ java Main<CR><LF> 后,Main程序...

2027
来自专栏生信宝典

Bash概论 - Linux系列教程补充篇

本篇是我最开始学习Linux命令时看的一篇帖子,最早见于ChinaUnix (这次查找其出处时发现2002年就有这篇)。学习过程中,遇到问题就查一下。这次看到,...

1947
来自专栏吴伟祥

Jmockdata随机模拟 Java 数据插件

     Jmockdta是一款实现模拟JAVA类型或对象的实例化并随机初始化对象的数据的工具框架。

982
来自专栏木宛城主

Unity应用架构设计(7)——IoC工厂理念先行

一谈到 『IoC』,有经验的程序员马上会联想到控制反转,将创建对象的责任反转给工厂。IoC是依赖注入 『DI』 的核心,大名鼎鼎的Spring框架就是一个非常...

2747
来自专栏皮皮之路

【JVM】浅谈双亲委派和破坏双亲委派

笔者曾经阅读过周志明的《深入理解Java虚拟机》这本书,阅读完后自以为对jvm有了一定的了解,然而当真正碰到问题的时候,才发现自己读的有多粗糙,也体会到只有实践...

1882
来自专栏大内老A

ASP.NET Core中的依赖注入(2):依赖注入(DI)

IoC主要体现了这样一种设计思想:通过将一组通用流程的控制从应用转移到框架之中以实现对流程的复用,同时采用“好莱坞原则”是应用程序以被动的方式实现对流程的定制。...

2228
来自专栏大内老A

ASP.NET MVC基于标注特性的Model验证:ValidationAttribute

通过前面的介绍我们知道ModelValidatorProviders的静态只读Providers维护着一个全局的ModelValidatorProvider列表...

21510
来自专栏眯眯眼猫头鹰的小树杈

猫头鹰的深夜翻译:理解java的classloader

Java ClassLoader是java运行系统中一个至关重要但是经常被忽略的组件。它负责在运行时寻找并加载类文件。创建自定义的ClassLoader可以彻底...

1364
来自专栏小勇DW3

自己手动写代码实现数据库连接池

池:一个高级的集合体(集合存储元素 + 管理方式–>提高效率),是对外提供同一种类型对象的集合,如(线程池、数据库连接池)  特性:复用性(每条连接可重复使用)...

1453

扫码关注云+社区