Java 动态代理作用

默认排序
305 人赞同了该回答
① 首先你要明白静态代理的作用
我们有一个字体提供类,有多种实现(从磁盘,从网络,从系统)
public interface FontProvider {
    Font getFont(String name);
}

public abstract class ProviderFactory {
    public static FontProvider getFontProvider() {
        return new FontProviderFromDisk();
    }
}

public class Main() {
    public static void main(String[] args) {
        FontProvider fontProvider = ProviderFactory.getFontProvider();
        Font font = fontProvider.getFont(“微软雅黑”);
        ……
    }
}
现在我们希望给他加上一个缓存功能,我们可以用静态代理来完成
public class CachedFontProvider implements FontProvider {
    private FontProvider fontProvider;
    private Map<String, Font> cached;

    public CachedFontProvider(FontProvider fontProvider) {
        this.fontProvider = fontProvider;
    }

    public Font getFont(String name) {
        Font font = cached.get(name);
        if (font == null) {
            font = fontProvider.getFont(name);
            cached.put(name, font);
        }
        return font;
    }
}


/* 对工厂类进行相应修改,代码使用处不必进行任何修改。
   这也是面向接口编程以及工厂模式的一个好处 */
public abstract class ProviderFactory {
    public static FontProvider getFontProvider() {
        return new CachedFontProvider(new FontProviderFromDisk());
    }
}

当然,我们直接修改FontProviderFromDisk类也可以实现目的,但是我们还有FontProviderFromNet, FontProviderFromSystem等多种实现类,一一修改太过繁琐且易出错。
况且将来还可能添加日志,权限检查,异常处理等功能显然用代理类更好一点。

② 然而为什么要用动态代理?
考虑以下各种情况,有多个提供类,每个类都有getXxx(String name)方法,每个类都要加入缓存功能,使用静态代理虽然也能实现,但是也是略显繁琐,需要手动一一创建代理类。
public abstract class ProviderFactory {
    public static FontProvider getFontProvider() {...}
    public static ImageProvider getImageProvider() {...}
    public static MusicProvider getMusicProvider() {...}
    ......
}
使用动态代理怎么完成呢?
public class CachedProviderHandler implements InvocationHandler {
    private Map<String, Object> cached = new HashMap<>();
    private Object target;

    public CachedProviderHandler(Object target) {
        this.target = target;
    }

    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable {
        Type[] types = method.getParameterTypes();
        if (method.getName().matches("get.+") && (types.length == 1) &&
                (types[0] == String.class)) {
            String key = (String) args[0];
            Object value = cached.get(key);
            if (value == null) {
                value = method.invoke(target, args);
                cached.put(key, value);
            }
            return value;
        }
        return method.invoke(target, args);
    }
}

public abstract class ProviderFactory {
    public static FontProvider getFontProvider() {
        Class<FontProvider> targetClass = FontProvider.class;
        return (FontProvider) Proxy.newProxyInstance(targetClass.getClassLoader(),
            new Class[] { targetClass },
            new CachedProviderHandler(new FontProviderFromDisk()));
    }
}
③ 这也是为什么Spring这么受欢迎的一个原因
Spring容器代替工厂,Spring AOP代替JDK动态代理,让面向切面编程更容易实现。
在Spring的帮助下轻松添加,移除动态代理,且对源代码无任何影响。
30515 条评论
分享
收藏感谢收起
231 人赞同了该回答

最近正好在看,特来挖坟。
关于动态代理设计模式很可能题主就在不知不觉中使用了,例如Spring中的AOP,Struts2中的拦截器等。

先来看静态代理模式代码:
package test;

public interface Subject   
{   
  public void doSomething();   
}

package test;

public class RealSubject implements Subject   
{   
  public void doSomething()   
  {   
    System.out.println( "call doSomething()" );   
  }   
}  

package test;

public class SubjectProxy implements Subject
{
  Subject subimpl = new RealSubject();
  public void doSomething()
  {
     subimpl.doSomething();
  }
}

package test;

public class TestProxy 
{
   public static void main(String args[])
   {
       Subject sub = new SubjectProxy();
       sub.doSomething();
   }
}

刚开始我会觉得SubjectProxy定义出来纯属多余,直接实例化实现类完成操作不就结了吗?后来随着业务庞大,你就会知道,实现proxy类对真实类的封装对于粒度的控制有着重要的意义。但是静态代理这个模式本身有个大问题,如果类方法数量越来越多的时候,代理类的代码量是十分庞大的。所以引入动态代理来解决此类问题。

先看代码:
package test;

public interface Subject   
{   
  public void doSomething();   
}

package test;

public class RealSubject implements Subject   
{   
  public void doSomething()   
  {   
    System.out.println( "call doSomething()" );   
  }   
}  

package test;

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

public class ProxyHandler implements InvocationHandler
{
    private Object tar;

    //绑定委托对象,并返回代理类
    public Object bind(Object tar)
    {
        this.tar = tar;
        //绑定该类实现的所有接口,取得代理类 
        return Proxy.newProxyInstance(tar.getClass().getClassLoader(),
                                      tar.getClass().getInterfaces(),
                                      this);
    }    

    public Object invoke(Object proxy , Method method , Object[] args)throws Throwable
    {
        Object result = null;
        //这里就可以进行所谓的AOP编程了
        //在调用具体函数方法前,执行功能处理
        result = method.invoke(tar,args);
        //在调用具体函数方法后,执行功能处理
        return result;
    }
}

public class TestProxy
{
    public static void main(String args[])
    {
           ProxyHandler proxy = new ProxyHandler();
           //绑定该类实现的所有接口
           Subject sub = (Subject) proxy.bind(new RealSubject());
           sub.doSomething();
    }
}

看完代码,现在我来回答,动态代理的作用是什么:
  1. Proxy类的代码量被固定下来,不会因为业务的逐渐庞大而庞大;
  2. 可以实现AOP编程,实际上静态代理也可以实现,总的来说,AOP可以算作是代理模式的一个典型应用;
  3. 解耦,通过参数就可以判断真实类,不需要事先实例化,更加灵活多变。

欢迎补充。
23127 条评论
分享
收藏感谢收起
53 人赞同了该回答

主要用来做方法的增强,让你可以在不修改源码的情况下,增强一些方法,在方法执行前后做任何你想做的事情(甚至根本不去执行这个方法),因为在InvocationHandler的invoke方法中,你可以直接获取正在调用方法对应的Method对象,具体应用的话,比如可以添加调用日志,做事务控制等。

还有一个有趣的作用是可以用作远程调用,比如现在有Java接口,这个接口的实现部署在其它服务器上,在编写客户端代码的时候,没办法直接调用接口方法,因为接口是不能直接生成对象的,这个时候就可以考虑代理模式(动态代理)了,通过Proxy.newProxyInstance代理一个该接口对应的InvocationHandler对象,然后在InvocationHandler的invoke方法内封装通讯细节就可以了。具体的应用,最经典的当然是Java标准库的RMI,其它比如hessian,各种webservice框架中的远程调用,大致都是这么实现的。
537 条评论
分享
收藏感谢
49 人赞同了该回答

巧了,周一发了一篇文章《Java帝国之动态代理

4910 条评论
分享
收藏感谢
35 人赞同了该回答

首先动态代理是设计模式当中代理模式的一种。

首先介绍下代理模式,再介绍Java中的动态代理实践。

1,什么是代理模式?

代理模式的作用是:为其他对象提供一种代理以控制对这个对象的访问。

2,代理模式有什么好处?

在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

3,代理模式一般涉及到的角色有:

抽象角色:声明真实对象和代理对象的共同接口,这样一来在任何可以使用目标对象的地方都可以使用代理对象。

代理角色:代理对象内部含有目标对象的引用,从而可以在任何时候操作目标对象;代理对象提供一个与目标对象相同的接口,以便可以在任何时候替代目标对象。代理对象通常在客户端调用传递给目标对象之前或之后,执行某个操作,而不是单纯地将调用传递给目标对象,同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。

真实角色:定义了代理对象所代表的目标对象代理角色所代表的真实对象,是我们最终要引用的对象,定义了代理对象所代表的目标对象。

public abstract class AbstractObject {
    //操作
    public abstract void operation();
}

public class RealObject extends AbstractObject {
    @Override
    public void operation() {
        //一些操作
        System.out.println(“一些操作”);
    }
}


public class ProxyObject extends AbstractObject{
    RealObject realObject = new RealObject();
    @Override
    public void operation() {
        //调用目标对象之前可以做相关操作
        System.out.println("before");        
        realObject.operation();        
        //调用目标对象之后可以做相关操作
        System.out.println("after");
    }
}

public class Client {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        AbstractObject obj = new ProxyObject();
        obj.operation();
    }

}

静态代理事实上就是装饰者模式,区别是装饰者模式一般都是传入一个被装饰类在构造器中,而静态代理则是自己创建一个类作为成员变量;

———————————待续

续来了,之前的答主已经将动态代理的的作用和实现方式说的很清楚了,我再来说个例子吧!

用过Mybatis的可能都知道,如果我们使用Mapper的方式来调用mapper映射文件里的sql语句,可以避免在代码中写那些生硬的string类型的id,但是我们想一下,我们只是调用了一个空方法而已,为什么会返回相应的结果,实现相应的功能?

答案就是动态代理,也可以理解为一层装饰,我们在空方法调用之前做了一系列的处理,将sql操作添加进去;

事实上装饰类我们并没有手动去写或者改动,并且所有的Mapper类都添加了类似的功能,方便简洁;

再稍后一些时候我补上Mybatis使用Mapper做代理的方法。

——————————

再续

在MyBatis中还有一处使用动态代理的地方十分优秀;

MyBatis中org.apache.ibatis.datasources.pooled.PooledDataSource是一个数据库连接池,org.apache.ibatis.datasources.pooled.PooledConnection类时Connection对象的代理类,在调用方法时进行了提前预处理,将Connection的Close方法调整为数据库连接池中的”close()”,在不修改任何真正调用对象的前提下实现了对方法功能的增强或者修改;

这也是动态代理和装饰者模式以及继承扩展功能的重要区别,对client端的使用者而言,没有任何变化;

摘抄一段:

装饰器模式关注于在一个对象上动态的添加方法,然而代理模式关注于控制对对象的访问。换句话 说,用代理模式,代理类(proxy class)可以对它的客户隐藏一个对象的具体信息。因此,当使用代理模式的时候,常常在一个代理类中创建一个对象的实例。并且,当使用装饰器模 式的时候,通常的做法是将原始对象作为一个参数传给装饰者的构造器。


——-

代理模式的作用是为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。静态代理模式中,真实角色必须是事先已经存在的,并将其作为代理对象的内部属性。

3512 条评论
分享
收藏感谢收起
阅读更多
文章标签: java 动态代理
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭