代理模式二:Java动态代理

Java动态代理

基于jdk1.8.0_172

1、定义一个公共接口Greeting.java:

package aop;

public interface Greeting {

    void sayHello(String name);
}

2、定义真实业务逻辑类GreetingImpl.java,实现Greeting接口,重写sayHello方法:

package aop.demo3;

import aop.Greeting;

public class GreetingImpl implements Greeting {

    public void sayHello(String name) {
        System.out.println("Hello! " + name);
    }
}

3、定义代理类JDKDynamicProxy的逻辑,实现InvocationHandler接口,重写invoke方法,在invoke中添加额外逻辑before()和after(),并调用真实业务方法method.invoke(target,args);

调用Proxy.newProxyInstance方法生成代理类对象,客户端实际使用的是这个对象:

package aop.demo3;

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

public class JDKDynamicProxy implements InvocationHandler {

    private Object target;

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

    //生成代理对象,传入真实业务类的classloader、真实业务类实现的接口、InvocationHandler 
    @SuppressWarnings("unchecked")
    public <T> T getProxy() {
        return (T) Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            this
        );
    }

    //增强真实业务类的调用
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object result = method.invoke(target, args);
        after();
        return result;
    }

    private void before() {
        System.out.println("Before");
    }

    private void after() {
        System.out.println("After");
    }
}

4、客户端使用动态代理,通过构造器注入真实业务类对象,调用getProxy方法获得代理类对象,调用代理方法sayHello:

package aop.demo3;

import aop.Greeting;

/**
 * 3. JDK 动态代理
 */
public class Client {

    public static void main(String[] args) {
        Greeting service = new GreetingImpl();
        Greeting greeting = new JDKDynamicProxy(service).getProxy();
        greeting.sayHello("Jack");
    }
}

5、执行结果:

下面看看Java动态代理的内部原理:

代理类的对象生成过程在Proxy.newProxyInstance方法中,主要四个步骤:

1、从缓存中获取指定代理类,传入加载真实业务类的类加载器和真实业务类实现的接口

/*
 * 查找或生成指定代理类
 * Look up or generate the designated proxy class.
 */
Class<?> cl = getProxyClass0(loader, intfs);
/**
 * Generate a proxy class.  Must call the checkProxyAccess method
 * to perform permission checks before calling this.
 */
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
    return proxyClassCache.get(loader, interfaces);
}

2、ProxyGenerator.generateProxyClass生成代理类的字节码

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

内部调用generateClassFile()生成字节码的byte数组

final byte[] var4 = var3.generateClassFile();

3、调用defineClass0这个native方法加载字节码,返回Class对象

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

4、最后调用Constructor.newInstance方法生成对应代理类的对象

final Constructor<?> cons = cl.getConstructor(constructorParams);
//省略部分代码
return cons.newInstance(new Object[]{h});

以上过程生成了一个代理真实业务类的Class对象,并最终调用newInstance方法生成了一个代理对象,这个生成字节码的过程是在内存中进行的,为了进一步研究这个代理类,我们把它输出来,反编译看一下第2步ProxyGenerator.generateProxyClass生成的字节码是什么样的内容。

在Client.java中加入下面的内容:

byte[] classFile = ProxyGenerator.generateProxyClass("com.sun.proxy.$Proxy1", service.getClass().getInterfaces());
FileOutputStream out = new FileOutputStream("com.sun.proxy.$Proxy1.class");
out.write(classFile);
out.flush();

文件会生成在项目根目录:

用反编译工具看这个.class文件,内容如下:

package com.sun.proxy.$Proxy;

import aop.Greeting;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy1
  extends Proxy
  implements Greeting
{
  private static Method m1;
  private static Method m3;
  private static Method m2;
  private static Method m0;
  
  public $Proxy1(InvocationHandler paramInvocationHandler)
  {
    super(paramInvocationHandler);
  }
  
  public final boolean equals(Object paramObject)
  {
    try
    {
      return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  public final void sayHello(String paramString)
  {
    try
    {
      this.h.invoke(this, m3, new Object[] { paramString });
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  public final String toString()
  {
    try
    {
      return (String)this.h.invoke(this, m2, null);
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  public final int hashCode()
  {
    try
    {
      return ((Integer)this.h.invoke(this, m0, null)).intValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  static
  {
    try
    {
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m3 = Class.forName("aop.Greeting").getMethod("sayHello", new Class[] { Class.forName("java.lang.String") });
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
    }
  }
}

分析上面的代码:

1、生成的$Proxy1继承自Proxy类,并实现了Greeting接口;

2、重写了Object对象的equals、toString、hashCode三个方法;

3、最关键的实现了Greeting接口的sayHello方法,客户端执行sayHello方法,实际上是调用这个代理类的this.h.invoke(this, m3, new Object[] { paramString });,h.invoke就是我们实现的InvocationHandler.invoke方法。传入了当前代理对象、当前执行的sayHello方法、参数。

 

存在的问题:

Java动态代理只能代理接口,不能代理没有接口的类。(为什么只能代理接口?从Proxy.newProxyInstance可以看出,这个方法是生成代理类对象用的,第二个参数要求传递真实业务类的接口,并且最终生成的代理类和真实业务类实现了同样的接口。)

假如我想要增强一个没有接口的类,怎么办呢?CGLib类库可以代理没有接口的类。

代理模式三:CGLib动态代理

参考文章及案例代码:

https://www.jianshu.com/p/a1d094fc6c00  参考了狼哥文章的行文逻辑和写作思路,主要是跟着敲了一遍加深了印象

https://my.oschina.net/huangyong/blog/161338 参考了代码

 

 

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值