简单了解静态代理,JDK提供的动态代理和cglib的动态代理

静态代理:

定义一个公共接口和公共方法,

package proxy;

public interface Work {
	void sayHello();

	String getName();
}

创建一个被代理类,实现公共接口和方法;

package proxy;

public class People implements Work {

	@Override
	public void sayHello() {
		System.out.println("my name is A");
	}

	@Override
	public String getName() {

		return "A";
	}

}

创建一个代理类,实现公共接口和方法,这样它们就具有了相同的方法。 

代理类中,需要持有一个被代理类对象,可以当做代理类的成员变量,在代理类的构造方法中传入并赋值。

package proxy;

public class StaticProxyPeople implements Work {
	People A = new People();

	public StaticProxyPeople(People A) {
		this.A = A;
	}

	@Override
	public void sayHello() {
		System.out.println("代理方法开始:");
		A.sayHello();
		System.out.println("代理方法结束。");
	}

	@Override
	public String getName() {
		String name = A.getName();
		name = "代理处理过的name:" + name;
		return name;
	}

}

此时代理类和被代理类拥有相同的方法,然后在代理类的方法中,可以调用被代理类的方法,达到操作被代理类的目的。而在调用方法前后,又可以定义自己的特殊逻辑。

package proxy;

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

public class Test {
	public static void main(String[] args) {

		People people = new People();
		StaticProxyPeople staticProxyPeople = new StaticProxyPeople(people);

		staticProxyPeople.sayHello();
		System.out.println(staticProxyPeople.getName());
    }
}

输出结果:
代理方法开始:
my name is A
代理方法结束。
代理处理过的name:A

这样子当外界希望调用被代理类的方法时,可以创建一个代理类的对象,通过对代理类对象的方法的调用,达到操作被代理类对象的目的。

缺点:一个代理类,只能针对一个被代理类进行代理。

JDK实现的动态代理:

在java的动态代理机制中,有两个重要的类或接口,一个是 InvocationHandler接口、另一个则是Proxyle类,这一个类和接口是实现我们动态代理所必须用到的。

1、InvocationHandler

举例:

package proxy;

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

public class WrokHandler implements InvocationHandler {
	private Object subject; // 这个就是我们要代理的真实对象,也就是真正执行业务逻辑的类


	public WrokHandler(Object subject) {
		this.subject = subject;
	}

	/**
	 * proxy:	指代JDK动态生成的最终代理对象 
	 * method: 指代的是我们所要调用真实对象的某个方法的Method对象
	 * args:	指代的是调用真实对象某个方法时接受的参数
	 */
	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		
		Object result = null;
		System.out.println("调用方法:" + method.getName()+ "之前");
		
		// 当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
		result = method.invoke(subject, args);// 需要指定被代理对象和传入参数
		System.out.println("返回值:" + result);
		System.out.println("调用方法:" + method.getName()+ "之后");
		
		return result;
	}

}


package proxy;

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

public class SleepHandler implements InvocationHandler {
	private Object subject; // 这个就是我们要代理的真实对象,也就是真正执行业务逻辑的类

	public SleepHandler(Object subject) {
		this.subject = subject;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		Object result = null;
		System.out.println("SleepHandler.invoke 调用方法:" + method.getName() + "之前");

		// 当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
		result = method.invoke(subject, args);// 需要指定被代理对象和传入参数
		System.out.println("SleepHandler.invoke 返回值:" + result);
		System.out.println("SleepHandler.invoke 调用方法:" + method.getName() + "之后");
		return result;
	}

}

解析:

每一个代理类都必须要实现InvocationHandler这个接口,

同时,也要持有一个被代理类真实对象。

代理类实现了InvocationHandler接口后,会重写invoke方法。

三个参数分别是:

proxy:    指代JDK动态生成的最终代理对象 

method: 指代的是我们所要调用真实对象的某个方法的Method对象

args:    指代的是调用真实对象某个方法时接受的参数

 

而在invoke方法中,我们通过method.invoke(subject, args); 来调用真实被代理对象的方法。

简单来说,就是当定义了以上WorkHandler后,对代理类对象方法的调用,都会由invoke方法来处理,才会传达到被代理对象。

2.Proxy

举例:

package proxy;

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

public class Test {
	public static void main(String[] args) {
		// 被代理的对象
		People A = new People();

		// 创建代理对象,代理对象持有一个被代理对象
		InvocationHandler handler = new WrokHandler(A);
		
		// 获得被代理类的,类加载器Classloader
		// 作用:
		// 负责将 Class 加载到 JVM 中
		// 审查每个类由谁加载(父优先的等级加载机制)
		// 将 Class 字节码重新解析成 JVM 统一要求的对象格式
		ClassLoader loader = A.getClass().getClassLoader();
		
		// 获得被代理类 实现的所有接口
		Class[] interfaces = A.getClass().getInterfaces();

		// 需要指定类装载器、一组接口及调用处理器生成动态代理类实例
		Work proxy = (Work) Proxy.newProxyInstance(loader, interfaces, handler);
		
		System.out.println("动态代理对象的类型:" + proxy.getClass().getName());
		proxy.sayHello();
		System.out.print(proxy.getName());
		
		
		InvocationHandler handler1 = new SleepHandler(A);
		Sleep proxy1 = (Sleep) Proxy.newProxyInstance(loader, interfaces, handler1);
		System.out.println("动态代理对象的类型:" + proxy.getClass().getName());
		proxy1.goToSleep();
		proxy1.getUp();

	}
}

输出结果:
动态代理对象的类型:$Proxy0
调用方法:sayHello之前
my name is A
返回值:null
调用方法:sayHello之后
调用方法:getName之前
返回值:A
调用方法:getName之后
A动态代理对象的类型:$Proxy0
SleepHandler.invoke 调用方法:goToSleep之前
i am go to sleep
SleepHandler.invoke 返回值:null
SleepHandler.invoke 调用方法:goToSleep之后
SleepHandler.invoke 调用方法:getUp之前
i am get up
SleepHandler.invoke 返回值:null
SleepHandler.invoke 调用方法:getUp之后

解析:

代理类同样需要持有一个真实代理对象

当我们希望创建一个代理类实例时,需要调用 Proxy.newProxyInstance(loader, interfaces, handler);方法

三个参数:

loader 被代理对象的类加载器,这里存有真实对象的父类,子类关系,和其他类的优先加载关系等等。

interfaces 被代理对象实现的所有接口,JDK提供的动态代理,说白了,就是让代理类也去实现被代理类的所有接口,重写被代理类的所有方法来实现的。

handler 我们自定义的代理类实例,它实现了InvocationHandler接口。用来处理Proxy中所有方法的调用 

首先解释一下JDK动态代理的大致流程,JDK主要会做以下工作: 

  1. 创建一个被代理类的实例
  2. 通过被代理类的实例,获取被代理类的加载器,和实现所有接口
  3. 调用Proxy.newProxyInstance()方法,需要传入被代理类的加载信息和它实现的接口信息,还有一个handler实例,这个实例决定创建出的代理类实例,在代理过程中,走哪个InvocationHandler实例的invoke方法,传入是的哪个handler实例,就走哪个实例的invoke方法。
  4. 同时Proxy类,也在代码中动态创建该代理类的字节码; 即代理类是在jvm的运行中创建的。  将对应的字节码转换为对应的class 对象; 
  5. Proxy 的class对象 以传入的handler对象为参数,实例化一个代理类对象;
  6. 在接收实例化的代理类对象时,我们无法具体指导代理类对象的类名,只能将它向上造型为某个业务接口,即我们希望代理类实例,能代理哪些方法,我就让它强转为哪个接口类型。我们希望代理类实例,在代理过程中,执行哪些特定逻辑,我们在Proxy.newProxyInstance()方法中传入哪个handler实例。

整理一下逻辑:

代理类实例创建之前,需要传入一个自定义的handler实例,而自定义的handler类需要实现InvocationHandler接口,重写invoke方法,在重写的invoke方法中,增加自定义逻辑。

代理类实例创建时,是Proxy.newInstance(loader, interface[], handler)方法根据传入的被代理类的类加载器信息,实现的所有接口,在jvm中动态的生成字节码文件和.class文件。

代理类实例创建后,又需要强转类型,向上造型为被代理类的某个接口类型,强制转换的是被代理类实现的某个接口类型,就可以调用这个接口的方法。如果强转的类型不是被代理类实现过的,就会有异常。

问题:

JDK是如何生成最终的代理类的,其实最根本的是通过动态生成代理类的字节码,然后通过这些字节码实现代理类的生成。

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

public static byte[] generateProxyClass(final String name,  
                                           Class[] interfaces)  
   {  
       ProxyGenerator gen = new ProxyGenerator(name, interfaces);  
    // 这里动态生成代理类的字节码
       final byte[] classFile = gen.generateClassFile();  

    // 如果saveGeneratedFiles的值为true,则会把所生成的代理类的字节码保存到硬盘上  
       if (saveGeneratedFiles) {  
           java.security.AccessController.doPrivileged(  
           new java.security.PrivilegedAction<Void>() {  
               public Void run() {  
                   try {  
                       FileOutputStream file =  
                           new FileOutputStream(dotToSlash(name) + ".class");  
                       file.write(classFile);  
                       file.close();  
                       return null;  
                   } catch (IOException e) {  
                       throw new InternalError(  
                           "I/O exception saving generated file: " + e);  
                   }  
               }  
           });  
       }  

    // 返回代理类的字节码  
       return classFile;  
   }  

代理类实例是怎么调用hanler的invoke方法的?我们来看一下代理类的class文件

 public final void hello(String paramString)
  {
    try
    {
      this.h.invoke(this, m3, new Object[] { paramString });
      //就是调用我们自定义的InvocationHandlerImpl的 invoke方法:
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

注意这里的this.h.invoke中的h,它是类Proxy中的一个属性

 protected InvocationHandler h;

因为这个代理类继承了Proxy,所以也就继承了这个属性,而这个属性值就是我们定义的

InvocationHandler handler = new InvocationHandlerImpl(realSubject);

同时我们还发现,invoke方法的第一参数在底层调用的时候传入的是this,也就是最终生成的代理对象ProxySubject,这是JVM自己动态生成的,而不是我们自己定义的代理对象。

JDK的动态代理通过ProxyGenerator.generateProxyClass()生成代理类的字节码,由Proxy.defineClass0()加载,可以看到这个代理类 extends Proxy implements Interface1, ... {...}。

而在代理类中,与Proxy有关的只有Proxy的变量protected InvocationHandler h;这个InvocationHandler实例的引用,在调用接口方法时实际调用的是super.h.invoke(this, method, args)。

缺点:使用JDK动态代理,目标类必须实现的某个接口,如果某个类没有实现接口则不能生成代理对象。

 

CGLIB

(Code Generation Library)是一个开源项目,是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口,通俗说cglib可以在运行时动态生成字节码。

使用cglib完成动态代理,大概的原理是:cglib继承被代理的类,重写方法,织入通知,动态生成字节码并运行,因为是继承所以final类和方法是没有办法动态代理的。具体实现如下

创建被代理类:

package proxy;

public class Person {
	public void sayHello() {
		System.out.println("my name is Person");
	}

	public String getName() {

		return "Person";
	}
}

创建代理类:

package proxy;

import java.lang.reflect.Method;

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

/*
 * 动态代理类
 * 实现了一个方法拦截器接口
 */
public class CglibProxyIntercepter implements MethodInterceptor {

	/*
	 * sub:cglib生成的代理对象,method:被代理对象方法,objects:方法入参,methodProxy:代理方法
	 */
	@Override
	public Object intercept(Object proxy, Method method, Object[] args,
			MethodProxy methodProxy) throws Throwable {
		System.out.println("执行前...");
		// 调用方法
		Object result = methodProxy.invokeSuper(proxy, args);
		System.out.println("执行后...");
		return result;
	}

	// 返回目标对象的代理对象
	public Object newProxy(Object target) {
		// 增强类,用于生成代理类的class文件
		Enhancer enhancer = new Enhancer();
		// 设置代理类的父类为被代理类
		enhancer.setSuperclass(target.getClass());
		// 设置代理类的回调方法,即这里指定了哪个类,就会执行哪个类的intercept方法
		enhancer.setCallback(this);
		// 设置被代理类的加载器,保证生成类的顺序和关系
		enhancer.setClassLoader(target.getClass().getClassLoader());
		// 生成代理类并返回实例
		return enhancer.create();
	}

}

解析:

所有的代理类,都需要实现MethodIntercpet接口,重写 intercept()方法,在方法中编写自定义逻辑。

且通过methodProxy.invokeSuper()方法执行被代理类方法。

Enhancer是一个增强类,通过设置被代理类为superClass,并指定由哪个方法拦截器来成为回调函数callBack,在调用creat()方法,动态生成代理类实例。

public class Test {
	public static void main(String[] args) {
		Person test = new Person();
		CglibProxyIntercepter methodIntercpt= new CglibProxyIntercepter();
		Person proxy2 = (Person) methodIntercpt.newProxy(test);
		proxy2.sayHello();
		proxy2.getName();
}
}


输出结果:
执行前...
my name is Person
执行后...
执行前...
执行后...

代理类实例也需要强制转型,才可以使用。可以转型为被代理类类型,和被代理类的接口类型,父类型。因为代理类是被代理类的子类。

生成代理类:

下面的源码是粘贴别人的,我们来看一下,生成一个代理类会生成三个class文件

1289596-20180122143632647-49186095.png

public class PersonService$$EnhancerByCGLIB$$eaaaed75
  extends PersonService
  implements Factory
{
  private boolean CGLIB$BOUND;
  private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
  private static final Callback[] CGLIB$STATIC_CALLBACKS;
  private MethodInterceptor CGLIB$CALLBACK_0;//拦截器
  private static final Method CGLIB$setPerson$0$Method;//被代理方法
  private static final MethodProxy CGLIB$setPerson$0$Proxy;//代理方法
  private static final Object[] CGLIB$emptyArgs;
  private static final Method CGLIB$finalize$1$Method;
  private static final MethodProxy CGLIB$finalize$1$Proxy;
  private static final Method CGLIB$equals$2$Method;
  private static final MethodProxy CGLIB$equals$2$Proxy;
  private static final Method CGLIB$toString$3$Method;
  private static final MethodProxy CGLIB$toString$3$Proxy;
  private static final Method CGLIB$hashCode$4$Method;
  private static final MethodProxy CGLIB$hashCode$4$Proxy;
  private static final Method CGLIB$clone$5$Method;
  private static final MethodProxy CGLIB$clone$5$Proxy;
  
  static void CGLIB$STATICHOOK1()
  {
    CGLIB$THREAD_CALLBACKS = new ThreadLocal();
    CGLIB$emptyArgs = new Object[0];
    Class localClass1 = Class.forName("com.demo.proxy.cglib.PersonService$$EnhancerByCGLIB$$eaaaed75");//代理类
    Class localClass2;//被代理类PersionService
    Method[] tmp95_92 = ReflectUtils.findMethods(new String[] { "finalize", "()V", "equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;" }, (localClass2 = Class.forName("java.lang.Object")).getDeclaredMethods());
    CGLIB$finalize$1$Method = tmp95_92[0];
    CGLIB$finalize$1$Proxy = MethodProxy.create(localClass2, localClass1, "()V", "finalize", "CGLIB$finalize$1");
    Method[] tmp115_95 = tmp95_92;
    CGLIB$equals$2$Method = tmp115_95[1];
    CGLIB$equals$2$Proxy = MethodProxy.create(localClass2, localClass1, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$2");
    Method[] tmp135_115 = tmp115_95;
    CGLIB$toString$3$Method = tmp135_115[2];
    CGLIB$toString$3$Proxy = MethodProxy.create(localClass2, localClass1, "()Ljava/lang/String;", "toString", "CGLIB$toString$3");
    Method[] tmp155_135 = tmp135_115;
    CGLIB$hashCode$4$Method = tmp155_135[3];
    CGLIB$hashCode$4$Proxy = MethodProxy.create(localClass2, localClass1, "()I", "hashCode", "CGLIB$hashCode$4");
    Method[] tmp175_155 = tmp155_135;
    CGLIB$clone$5$Method = tmp175_155[4];
    CGLIB$clone$5$Proxy = MethodProxy.create(localClass2, localClass1, "()Ljava/lang/Object;", "clone", "CGLIB$clone$5");
    tmp175_155;
    Method[] tmp223_220 = ReflectUtils.findMethods(new String[] { "setPerson", "()V" }, (localClass2 = Class.forName("com.demo.proxy.cglib.PersonService")).getDeclaredMethods());
    CGLIB$setPerson$0$Method = tmp223_220[0];
    CGLIB$setPerson$0$Proxy = MethodProxy.create(localClass2, localClass1, "()V", "setPerson", "CGLIB$setPerson$0");
    tmp223_220;
    return;
  }

我们通过代理类的源码可以看到,代理类会获得所有在父类继承来的方法,并且会有MethodProxy与之对应,比如 Method CGLIB$setPerson$0$Method、MethodProxy CGLIB$setPerson$0$Proxy;

方法的调用

  //代理方法(methodProxy.invokeSuper会调用)
   final void CGLIB$setPerson$0() {
      super.setPerson();
   }
   //被代理方法(methodProxy.invoke会调用,这就是为什么在拦截器中调用methodProxy.invoke会死循环,一直在调用拦截器)
   public final void setPerson() {
      MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
      if(this.CGLIB$CALLBACK_0 == null) {
         CGLIB$BIND_CALLBACKS(this);
         var10000 = this.CGLIB$CALLBACK_0;
      }

      if(var10000 != null) {
         //调用拦截器
         var10000.intercept(this, CGLIB$setPerson$0$Method, CGLIB$emptyArgs, CGLIB$setPerson$0$Proxy);
      } else {
         super.setPerson();
      }
   }

调用过程:代理对象调用this.setPerson方法->调用拦截器->methodProxy.invokeSuper->CGLIB$setPerson$0->被代理对象setPerson方法

缺点:cglib通过继承被代理类实现代理。final修饰的类和方法,没办法代理。

转载于:https://my.oschina.net/xiaoyoung/blog/3032659

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值