透过代理模式探究JAVA的静态代理和动态代理

一、代理

代理是英文 Proxy 翻译过来的。我们在生活中见到过的最常见的代理大概就是顾客和商家的关系了

按理说,顾客可以直接从厂家购买产品,但是现实生活中,很少有这样的销售模式。一般都是厂家委托给代理商进行销售,顾客跟代理商打交道,而不直接与产品实际生产者进行关联。所以,代理就有一种中间人的味道。

接下来,我们说说软件中的代理模式。

二、代理模式

代理模式是面向对象中比较常见的一种设计模式了。

这是代理模式常见的UML图,代理模式可以为其他对象提供一种代理以控制对这个对象的访问,比如像对一个类的访问做一些控制或加强的时候都可以用到代理模式(AOP就是基于此发展而来的)。

对于代理模式的理解需要注意的有下面几点:
(1) 用户只关心接口功能,而不在乎谁提供了功能。上图中接口是 Subject。
(2)接口真正实现者是上图的 RealSubject,但是它不与用户直接接触,而是通过代理。
(3)代理就是上图中的 Proxy,由于它实现了 Subject 接口,所以它能够直接与用户接触。
(4)用户调用 Proxy 的时候,Proxy 内部调用了 RealSubject。所以,Proxy 是中介者,它可以增强\控制 RealSubject 操作。

光说概念比较难以理解,下面我用示例来说明。值得注意的是,代理可以分为静态代理动态代理两种。先从静态代理讲起。

三、静态代理

我们平常去电影院看电影的时候,在电影开始的阶段是不是经常会放广告呢?

电影是电影公司委托给影院进行播放的,但是影院可以在播放电影的时候,产生一些自己的经济收益,比如卖爆米花、可乐等,然后在影片开始结束时播放一些广告。

现在用代码来进行模拟。

首先得有一个接口,通用的接口是代理模式实现的基础。这个接口我们命名为 Movie,代表电影播放的能力。

public interface Movie{
    void play();
}

然后,我们要有一个真正的实现这个 Movie 接口的类,和一个只是实现接口的代理类。

public class RealMovie implements Movie{
    
    @Override
    public void play(){
        System.out.println("播放电影....");
    }
}

这个类实现了Movie接口的play方法,只要调用就可以播放,接下来还需要一个代理类来代理这个”电影“

public class MovieProxy implements Movice{
    Movie movie;
    
    public Client(Movie movie){
        this.movie=movie;
    }
    
    @Override
    public void play(){
        System.out.println("电影马上开始了,爆米花、可乐、口香糖9.8折,快来买啊!");
        System.out.println("播放广告...");
        movie.play();
        System.out.println("电影马上结束了,爆米花、可乐、口香糖9.8折,买回家吃吧!");
    }
    
}

这里的MovieProxy就是一个代理类它有一个 play() 方法。不过调用 play() 方法时,它进行了一些相关利益的处理,那就是广告。现在,测试一下

public class Client{
    
    public static void main(String[] args){
        RealMovie movie=new RealMovie();
        MovieProxy proxy=new MovieProxy(movie);
        proxy.play();
    }
    
}

执行结果:

从执行的结果可以看到,代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。值得注意的是,代理类和被代理类应该共同实现一个接口,或者是共同继承某个类。

上面这种代理方式因为它的类型是我们程序员或接助工具事先预定好的,比如上面代码中的 MovieProxy这个类。所以他被称为静态代理模式。下面要介绍的内容就是动态代理。

四、动态代理

在Java开发中,很多技术和框架都用到了动态代理,它与静态代理的区别就是动态与静态的区别:上一节代码中 MovieProxy类是代理,需要程序员手动编写代码让 MovieProxy实现 Movie 接口,而在动态代理中,我们可以让程序在运行的时候自动在内存中创建一个实现 Movie 接口的代理,而不需要去定义 MovieProxy这个类。这就是它被称为动态的原因。下面用一个例子来说明:

假设有一个大商场,商场有很多的柜台,有一个柜台卖茅台酒。我们进行代码的模拟。

/**
*买酒
*/
public interface SellWine {
    void sell();
}

SellWine是一个买酒的就扣,实现它就可以买酒了,类似于许可证。之后比如我们要买茅台酒,那就实现SellWine获取许可

public class Moutai implements SellWine {

    @Override
    public void sell() {
        System.out.println("我买的是飞天茅台!");
    }
}

之后按照静态代理就要写一个代理对象了,可是动态代理不需要了,在Java中我们可以使用Proxy.newProxyInstance()方法来动态的创建代理对象。

/**
 * 柜台,动态生成买酒的代理对象
 *
 * @author :huangxin
 * @modified :
 * @since :2020/06/04 16:01
 */
public class Counter {

    public static SellWine proxySellWine(final SellWine banner) {
        SellWine proxyObj = (SellWine) Proxy.newProxyInstance(
                banner.getClass().getClassLoader(),
                banner.getClass().getInterfaces(), 
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("销售开始");
                        Object obj =method.invoke(banner, args);
                        System.out.println("销售结束");
                        return obj;
                    }
                });
        return proxyObj;
    }
}

之后客户端通过代理 对象买酒就可以了

public class Client{
   
    public static void main(String[] args){
        Moutai moutai=new Moutai();
        Counter.proxySellWine(moutai).sell();
    }
}

执行结果:

可以看到,我并没有像静态代理那样为 SellWine 接口实现一个代理类,但最终它仍然实现了相同的功能,这其中的差别,就是之前讨论的动态代理所谓“动态”的原因。

五、Java对动态代理的支持

1、newProxyInstance方法

Java中动态代理涉及到一个非常重要的类 Proxy。正是通过 Proxy 的静态方法 newProxyInstance 才会动态创建代理。

@CallerSensitive                                            
public static Object newProxyInstance(ClassLoader loader,   
                                      Class<?>[] interfaces,
                                      InvocationHandler h)                       

说一下它三个参数的含义:

  • loader:类加载器
  • interfaces:需要代理的接口
  • h:代理类的具体实现,InvocationHandler是一个接口,需要有一个具体的实现类,在这个实现类中可以定义动态生成的代理类可以有哪些行为
2、InvocationHandler接口

InvocationHandler 内部只是一个 invoke() 方法,正是这个方法决定了怎么样处理代理传递过来的方法调用。

  • proxy:代理对象
  • method:代理对象调用的方法
  • args:调用的方法中的参数

在了解了基本的语法之后,我们加大难度,现在我扩大经营了,我还要买五粮液。

public class Wuliangye implements SellWine {

    @Override
    public void sell() {
        System.out.println("我买的是五粮液!");
    }
}

Wuliangye 这个类也实现了 SellWine 这个接口,说明它也拥有卖酒的许可证,同样把它放到 柜台(Counter)上售卖。

public class Client{
   
    public static void main(String[] args){
        //卖茅台
        Moutai moutai=new Moutai();
        Counter.proxySellWine(moutai).sell();
        //卖五粮液
        Wuliangye wuliangye=new Wuliangye();
        Counter.proxySellWine(wuliangye).sell();
    }
}

执行结果:

可以看到,结果符合预期。使用动态代理扩展业务十分的快捷,这一切还得归功于Java的反射机制。到这里,脑子灵的同学就会产生好奇了,为什么一个newProxyInstance方法就可以实现动态生成代理对象?它在底层是不是主动帮我们new了一个对象?

那么事实是不是这样子呢?直接查看它们的源码好了。(需要说明的是,我当前查看的源码是 jdk 1.8 版本。)

@CallerSensitive                                                          
public static Object newProxyInstance(ClassLoader loader,                 
                                      Class<?>[] interfaces,              
                                      InvocationHandler h)                
    throws IllegalArgumentException{                                                                         
    Objects.requireNonNull(h);                                            
                                                                          
    final Class<?>[] intfs = interfaces.clone();                          
    final SecurityManager sm = System.getSecurityManager();               
    if (sm != null) {                                                     
        checkProxyAccess(Reflection.getCallerClass(), loader, intfs);     
    }                                                                     
                                                                          
    /*                                                                    
     * Look up or generate the designated proxy class. 
     * 翻译:查找或生成指定的代理类
     */                                                                   
    Class<?> cl = getProxyClass0(loader, intfs);                          
                                                                          
    /*                                                                    
     * Invoke its constructor with the designated invocation handler.  
     * 翻译:使用指定的调用处理程序调用其构造函数。
     */                                                                   
    try {                                                                 
        if (sm != null) {                                                 
            checkNewProxyPermission(Reflection.getCallerClass(), cl);     
        }                                                                 
                                                                          
        final Constructor<?> cons = cl.getConstructor(constructorParams); 
        final InvocationHandler ih = h;                                   
        if (!Modifier.isPublic(cl.getModifiers())) {                      
            AccessController.doPrivileged(new PrivilegedAction<Void>() {  
                public Void run() {                                       
                    cons.setAccessible(true);                             
                    return null;                                          
                }                                                         
            });                                                           
        }                                                                 
        return cons.newInstance(new Object[]{h});                         
    } catch (IllegalAccessException|InstantiationException e) {           
        throw new InternalError(e.toString(), e);                         
    } catch (InvocationTargetException e) {                               
        Throwable t = e.getCause();                                       
        if (t instanceof RuntimeException) {                              
            throw (RuntimeException) t;                                   
        } else {                                                          
            throw new InternalError(t.toString(), t);                     
        }                                                                 
    } catch (NoSuchMethodException e) {                                   
        throw new InternalError(e.toString(), e);                         
    }                                                                     
}                                                                         

查找或生成指定的代理类:

private static Class<?> getProxyClass0(ClassLoader loader,                  
                                       Class<?>... interfaces) {            
    if (interfaces.length > 65535) {                                        
        throw new IllegalArgumentException("interface limit exceeded");     
    }                                                                       
                                                                            
    // If the proxy class defined by the given loader implementing          
    // the given interfaces exists, this will simply return the cached copy;
    // otherwise, it will create the proxy class via the ProxyClassFactory  
    //翻译:如果由实现了给定接口的给定加载器定义的代理类存在,则将简单地返回缓存的副本; 
    //否则,它将通过ProxyClassFactory创建代理类
    return proxyClassCache.get(loader, interfaces);                         
}                                                                           

有上面的源码可知,在newProxyInstance方法中它确实为我们创建了一个代理类的对象,但是不是简单的new,而是通过反射创建了一个实例。另外,需要我们注意的是:它不会一上来就通过反射创建代理对象的实例,而是先在给定类加载器中检查,如果发现有实现了给定接口的代理类存在时,它就会简单地返回这个代理对象,否者通过ProxyClassFactory类创建代理类的实例。

ProxyClassFactory是Proxy类的内部类

private static final class ProxyClassFactory                                               implements BiFunction<ClassLoader, Class<?>[], Class<?>>  {                                                
    // prefix for all proxy class names  
    //翻译:所有代理类名称的前缀  $Proxy
    private static final String proxyClassNamePrefix = "$Proxy";                           
    // next number to use for generation of unique proxy class names  
    //翻译:下一个用于生成唯一代理类名称的数字 ,这里它使用了原子类AtomicLong
    private static final AtomicLong nextUniqueNumber = new AtomicLong();                  
    @Override                                                          
    public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {               
        Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);          
        for (Class<?> intf : interfaces) {                                                       
            /*                                                                       
             * Verify that the class loader resolves the name of this                 
             * interface to the same Class object.                                   
             */                                                                                  
            Class<?> interfaceClass = null;                                                      
            try {                                        
                interfaceClass = Class.forName(intf.getName(), false, loader);       
            } catch (ClassNotFoundException e) {                                     
            }  
            if (interfaceClass != intf) {                                             
                throw new IllegalArgumentException(intf + " is not visible from class loader");                     
            }                                                                         
            /*                                                                       
             * Verify that the Class object actually represents an                   
             * interface.                                                             
             */                                                                       
            if (!interfaceClass.isInterface()) {       
                throw new IllegalArgumentException(interfaceClass.getName() + " is not an interface");           
            }                                                                                    
            /*                                                                       
             * Verify that this interface is not a duplicate.                         
             */                                                                       
            if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {             
                throw new IllegalArgumentException(  "repeated interface: " + interfaceClass.getName());               
            }                                                                        
        }                                                                                
        String proxyPkg = null;     // package to define proxy class in               
        int accessFlags = Modifier.PUBLIC | Modifier.FINAL;                             
        /*                                                                          
         * Record the package of a non-public proxy interface so that the         
         * proxy class will be defined in the same package.  Verify that            
         * all non-public proxy interfaces are in the same package.                   
         */                                                                           
        for (Class<?> intf : interfaces) {                                           
            int flags = intf.getModifiers();                                         
            if (!Modifier.isPublic(flags)) {                                         
                accessFlags = Modifier.FINAL;
                String name = intf.getName();                                         
                int n = name.lastIndexOf('.');                                       
                String pkg = ((n == -1) ? "" : name.substring(0, n + 1));             
                if (proxyPkg == null) {                                               
                    proxyPkg = pkg;                                                   
                } else if (!pkg.equals(proxyPkg)) {                                   
                    throw new IllegalArgumentException(                               
                        "non-public interfaces from different packages");         
                }                                        
            }                                                                         
        }                                                                                        
    
        if (proxyPkg == null) {                            
            // if no non-public proxy interfaces, use com.sun.proxy package
            proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
        }                                                                                          
        /*                                                                         
         * Choose a name for the proxy class to generate.                             
         */                                                             
        long num = nextUniqueNumber.getAndIncrement();                                           
        String proxyName = proxyPkg + proxyClassNamePrefix + num;                                
        /*                             
         * Generate the specified proxy class.                                  
         */                           
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(        
            proxyName, interfaces, accessFlags);   
        try {                                                                         
            return defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);      
        } catch (ClassFormatError e) {                                               
            /*                                  
             * A ClassFormatError here means that (barring bugs in the    
             * proxy class generation code) there was some other   
             * invalid aspect of the arguments supplied to the proxy  
             * class creation (such as virtual machine limitations
             * exceeded).          
             */                                                                
            throw new IllegalArgumentException(e.toString());            
        }                                                            
    }               
}                                                                                    

这个类的注释可以了解到,它通过指定的 ClassLoader 和 接口数组 用工厂方法生成 proxy class。 然后这个 proxy class 的名字是:

// Proxy class 的前缀是 “$Proxy”,
private static final String proxyClassNamePrefix = "$Proxy";

long num = nextUniqueNumber.getAndIncrement();
//代理类名:包名+$Proxy+id序号
String proxyName = proxyPkg + proxyClassNamePrefix + num;

所以,动态生成的代理类名称是包名+$Proxy+id序号。生成的过程,核心代码如下:

byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);


return defineClass0(loader, proxyName,
                    proxyClassFile, 0, proxyClassFile.length);

到这里,我们J只需要知道Java动态动态代理就是通过反射实现的以及动态代理生成的类名就是包名+$Proxy+id序号即可。下面我们来验证一下动态生成的代理类的名字是不是包名+$Proxy+id序号

public class Client{
   
    public static void main(String[] args){
        Moutai moutai=new Moutai();
        SellWine proxy1 = Counter.proxySellWine(moutai);
        proxy1.sell();
        System.out.println(proxy1.getClass().getName());
        
        Wuliangye wuliangye=new Wuliangye();
        SellWine proxy2 = Counter.proxySellWine(wuliangye);
        proxy2.sell();
        System.out.println(proxy2.getClass().getName());
    }
}

执行结果:

六、动态代理的应用

动态代理的一个显著作用就是可以在不修改被代理对象的源码上,进行功能的增强。熟悉Spring的同学对这个观念应该不陌生,这就是AOP的作用,AOP就可以使用JDK的动态代理来实现(但是Spring采用了另一种方法,详情参考我的另一篇博客Spring从入门到精通—AOP与AspectJ的关系?原生JDK和CGLib手动实现AOP?)。

因此,关于动态代理应用,一个比较重要的方向就是实现AOP编程,还有一个就是MyBatis通过动态代理生成Mapper接口的实现类……还有很多,总之凡是想在访问一个类时做一些控制都可以使用代理。

总结

  1. 代理分为静态代理和动态代理两种。
  2. 静态代理,代理类需要自己编写代码写成。
  3. 动态代理,代理类通过 Proxy.newInstance() 方法生成。
  4. 不管是静态代理还是动态代理,代理与被代理者都要实现两样接口,它们的实质是面向接口编程。
  5. 静态代理和动态代理的区别是在于要不要开发者自己定义 Proxy 类。
  6. 动态代理通过 Proxy 动态生成 proxy class,但是它也指定了一个 InvocationHandler 的实现类。
  7. 代理模式本质上的目的是为了增强现有代码的功能。


转载来自:https://blog.csdn.net/briblue/article/details/73928350

留言区

还能输入500个字符