来看看Java的动态代理,看不懂算我输(JDK动态代理)

动态代理是Java中非常重要的一个知识点,有基于JDK接口和基于继承两种方式,本章主要讨论的是JDK动态代理,将按照以下思路逐步讲解

1-动态代理能解决的问题是什么

2-如何解决的

3-解决问题的逻辑及原理

 

1-动态代理能解决的问题是什么

首先我们有一个计算器的接口和实现类如下

public interface ArithmeticCalculate {
	
	public int add(int i, int j);
	public int sub(int i, int j);
	public int mul(int i, int j);
	public int div(int i, int j);

}
public class ArithmeticCalculateImpl implements ArithmeticCalculate {

	@Override
	public int add(int i, int j) {
		int result = i + j;
		return result;
	}

	@Override
	public int sub(int i, int j) {
		int result = i - j;
		return result;
	}

	@Override
	public int mul(int i, int j) {
		int result = i * j;
		return result;
	}

	@Override
	public int div(int i, int j) {
		int result = i / j;
		return result;
	}

}

可以看到ArithmeticCalculateImpl类对ArithmeticCalculate接口的方法分别进行了实现。

此时我们接到了一个需求:

        请在每个方法中添加记录日志的功能。要求记录接收到了哪些参数和参数运算的结果。此时我们用输出到console代替日志记录,代码如下:


public class ArithmeticCalculateImpl implements ArithmeticCalculate {

	@Override
	public int add(int i, int j) {
		System.out.println("日志记录 : 接收到的参数为 " + i + " 和 " + j);
		int result = i + j;
		System.out.println("日志记录 : 得到的结果为 " + result);
		return result;
	}

	@Override
	public int sub(int i, int j) {
		System.out.println("日志记录 : 接收到的参数为 " + i + " 和 " + j);
		int result = i - j;
		System.out.println("日志记录 : 得到的结果为 " + result);
		return result;
	}

	@Override
	public int mul(int i, int j) {
		System.out.println("日志记录 : 接收到的参数为 " + i + " 和 " + j);
		int result = i * j;
		System.out.println("日志记录 : 得到的结果为 " + result);
		return result;
	}

	@Override
	public int div(int i, int j) {
		System.out.println("日志记录 : 接收到的参数为 " + i + " 和 " + j);
		int result = i / j;
		System.out.println("日志记录 : 得到的结果为 " + result);
		return result;
	}

}

相信大家可以看到,上一段代码虽然完成了增加日志的需求,但是存在两个严重的问题:

        1- 大量的重复代码且不易于维护,如果我们有100个方法要增加日志功能那可就累死了,而且功能做出来老板提出把"日志记录 :" 改成"日志记录 ==>"等任何变更需求,我们都需要改动大量代码。

        2- 业务逻辑和新增的日志功能耦合严重。原来的业务代码add/sub等函数中被硬性掺杂了其他功能,违背低耦合的设计理念。

使用动态代理就可以完美的解决以上两个问题。

 

2-如何解决的

既然叫动态代理,那我们就可以联想到实际解决问题时采用了代理设计模式:使用一个代理将对象包装起来,然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。

来看看Java的动态代理,看不懂算我输(JDK动态代理)_第1张图片

此处我们一步一步慢慢来,先大概看一下解决流程即可,先不需要每一步都理解。

既然使用代理模式,那我们首先要有一个类来生成代理对象,我们叫它ArithmeticCalculateProxy

public class ArithmeticCalculateProxy {

}

既然是对ArithmeticCalculateImpl的代理,那代理类中一定需要知道它代理的是谁。所以Proxy类中应该有一个计算器实现类的成员变量,而且不难想到,这个成员变量应该在创建Proxy对象的时候就要被提供,因为代理模式中被代理对象时必须有的。所以代码如下。

public class ArithmeticCalculateProxy {
	
	//target object
	ArithmeticCalculate target;
	
	public ArithmeticCalculateProxy(ArithmeticCalculateImpl target) {
		this.target = target;
	}
}

到此处需要强调一点。我们创建的ArithmeticCalculateProxy类时用来生成代理对象的,而不是说这个类就是代理类。我们在创建代理对象时应该是调用这个类的方法得到一个代理对象而不是创建一个该类的对象。代理对象 ≠ Proxy类对象。

所以在ArithmeticCalculateProxy类中我们还需要一个返回代理对象的方法,我们定义为getProxy().

public class ArithmeticCalculateProxy {
	
	//target object
	ArithmeticCalculate target;
	
	public ArithmeticCalculateProxy(ArithmeticCalculateImpl target) {
		this.target = target;
	}
	
	//获取代理对象的方法
	public Object getProxy() {
		Object proxy;

		return proxy;
	}

}

在构建proxy的时候我们需要用到一个类,叫做Proxy。它是所有动态代理类的父类, 专门用户生成代理类或者是代理对象。这个类中有两个重要的方法:

1-getProxyClass用来获得代理对象的类对象

public static Class getProxyClass(ClassLoader loader,Class... interfaces)

2-newProxyInstance用来获取代理对象

public static Object newProxyInstance(ClassLoader loader,Class[] interfaces, InvocationHandler h)

此时我们需要仔细分析一下如何通过这两个方法获得代理对象.

我们说过ArithmeticCalculateProxy是用来生成代理对象的而不是代理对象的类。代理对象也是对象,毋庸置疑它需要有自己所属的类,所以我们需要完成的事情有两个:构建代理类的类对象和创建一个代理对象。

1)构建一个代理类的类对象

        我们有现成的方法getProxyClass来完成这一步。要根据一个类来生成对象那首先要把类加载到内存中,所以getProxyClass需要一个类加载器classLoader。光有类加载器还不够,我们还需要知道这个类的内容,也就是要知道类中有哪些成员变量和成员方法,需要注意,我们要生成的是代理对象,代理对象主要是用来调用被代理对象的方法的,所以我们只要知道被代理类有哪些方法即可,方法的实现我们不需要关注.而通过被代理对象实现的所有接口就能知道它全部的方法列表(此处指的是面向接口的动态代理,如果被代理类在实现所有方法的基础上还单独定义了自己的方法,需要用继承的方式实现动态代理).

        在给定了classLoader和interfaces后,我们就得到了代理对象的Class对象。接下来进行第二步,根据代理Class对象创建代理对象。

2)根据构建好的类对象创建一个对象并返回

        已经有了类对象,创建对象可以通过反射的newInstance()来创建了,但遗憾的是newInstance()方法需要被反射的类有无参构造器,我们没有。只能通过创建Constructor然后创建对象了。而class.getDeclaredConstructor()方法需要一个InvocationHandler参数

Class proxyClass = Proxy.getProxyClass(loader, interfaces);
Constructor con = proxyClass.getDeclaredConstructor(InvocationHandler.class);
proxy = con.newInstance(new InvocationHandler() {
			
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
	    //将方法的调用转回到目标对象上. 
				
	    //获取方法的名字
	    String methodName = method.getName();
	    //记录日志
	    System.out.println("LoggingProxy2==> The method " + methodName+" begin with "+ Arrays.asList(args));
	    Object result = method.invoke(target, args);  // 目标对象执行目标方法. 相当于执行ArithmeticCalculatorImpl中的+ - * /
				
	    //记录日志
	    System.out.println("LoggingProxy2==> The method " + methodName  +" ends with :" +result   );
	    return result ;
	}
});

InvocationHandler是一个接口,在newInstance方法中采用了匿名内部类的方法创建了他的子类对象。该子类对象中invoke方法的第二个参数Method就是被调用的方法对象,args就是该方法对象的参数列表。不难看出来,这里的invoke方法才是最终代理逻辑执行的地方。至于为什么代理逻辑会在这里,我们后面会讲。

将上边所有的模块代码整合后得到如下代码

public class ArithmeticCalculatorProxy2 {
	//动态代理:    目标对象     如何获取代理对象      代理要做什么 
	
	//目标对象
	private  ArithmeticCalculator  target ; 
	
	public ArithmeticCalculatorProxy2(ArithmeticCalculator target) {
		this.target = target ; 
	}
	
	
	//获取代理对象的方法
	public Object  getProxy() throws Exception {
		
		//代理对象
		Object  proxy ; 
		
		ClassLoader loader = target.getClass().getClassLoader();
		Class [] interfaces = target.getClass().getInterfaces();
		
		Class proxyClass = Proxy.getProxyClass(loader, interfaces);
		
		//Class  创建对象?   newInstance()
		
		Constructor con = 
				proxyClass.getDeclaredConstructor(InvocationHandler.class);
		
		proxy = con.newInstance(new InvocationHandler() {
			
			@Override
			public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
				//将方法的调用转回到目标对象上. 
				
				//获取方法的名字
				String methodName = method.getName();
				//记录日志
				System.out.println("LoggingProxy2==> The method " + methodName+" begin with "+ Arrays.asList(args));
				Object result = method.invoke(target, args);  // 目标对象执行目标方法. 相当于执行ArithmeticCalculatorImpl中的+ - * /
				
				//记录日志
				System.out.println("LoggingProxy2==> The method " + methodName  +" ends with :" +result   );
				return result ;
			}
		});
		
		
		return proxy ;
	}
	
}

如果使用方法2 调用newProxyInstance方法则代码如下

两种代码逻辑相同,只不过newProxyInstance做了更好的封装。
 

public class ArithmeticCalculateProxy {
	
	//target object
	ArithmeticCalculate target;
	
	public ArithmeticCalculateProxy(ArithmeticCalculateImpl target) {
		this.target = target;
	}
	
	//获取代理对象的方法
	public Object getProxy() {
		Object proxy;
		ClassLoader loader = target.getClass().getClassLoader();
		Class [] interfaces = target.getClass().getInterfaces();
		proxy=Proxy.newProxyInstance(loader, interfaces, new InvocationHandler() {
			@Override
			public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
				String methodName = method.getName();
				Object result = method.invoke(target, args);
				return result;
			}
		});
		return proxy;
	}

}

我们总结一下动态代理的编码步骤

1- 创建一个类 A 用来生成代理对象

2-将被代理对象设置为A的成员变量,同时A的构造方法需要一个被代理对象

3-调用Proxy的newProxyInstance或者getProxyClass方法来生成对象,这两个方法都会用到三个参数 :

a- classLoader : 通过被代理对象可以直接获得

b- interfaces : 通过被代理对象直接获得

c- InvocationHandler : 通过匿名内部类的形式创建

 

3-解决问题的逻辑及原理

既然我们刚刚知道了代理类是在代码执行的过程中生成的,那我们去看看生成的源码一定会帮助我们更好的理解动态代理。动态生成的代理类在内存中,以下设置可以将内存中的类保存下来。

	public static void main(String[] args)  throws Exception{
		//将动态生成的代理类保存下来
		 Properties properties = System.getProperties();
		 properties.put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

		//目标对象
		ArithmeticCalculator target = new ArithmeticCalculatorImpl();
		
		//获取代理对象
		Object  obj = new ArithmeticCalculatorProxy2(target).getProxy();
		
		// 转回具体的类型.
		
		ArithmeticCalculator  proxy = (ArithmeticCalculator) obj ;
		
		System.out.println(proxy.getClass().getName());
		
		//
		int result = proxy.add(1, 1);
		
		System.out.println("Main  Result : " + result );

得到的一个叫做$Proxy0的class文件,反编译后的结果如下

package com.atguigu.spring.aop.proxy;

import com.atguigu.spring.aop.proxy.ArithmeticCalculator;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy00 extends Proxy implements ArithmeticCalculator {
	private static Method m1;
	private static Method m2;
	private static Method m4;
	private static Method m3;
	private static Method m6;
	private static Method m5;
	private static Method m0;

	public $Proxy00(InvocationHandler arg0) throws  {
      super(arg0);
   }

	public final boolean equals(Object arg0) throws  {
      try {
         return ((Boolean)super.h.invoke(this, m1, new Object[]{arg0})).booleanValue();
      } catch (RuntimeException | Error arg2) {
         throw arg2;
      } catch (Throwable arg3) {
         throw new UndeclaredThrowableException(arg3);
      }
   }

	public final String toString() throws  {
      try {
         return (String)super.h.invoke(this, m2, (Object[])null);
      } catch (RuntimeException | Error arg1) {
         throw arg1;
      } catch (Throwable arg2) {
         throw new UndeclaredThrowableException(arg2);
      }
   }

	public final int mul(int arg0, int arg1) throws  {
      try {
         return ((Integer)super.h.invoke(this, m4, new Object[]{Integer.valueOf(arg0), Integer.valueOf(arg1)})).intValue();
      } catch (RuntimeException | Error arg3) {
         throw arg3;
      } catch (Throwable arg4) {
         throw new UndeclaredThrowableException(arg4);
      }
   }

	public final int add(int arg0, int arg1) throws  {
      try {
         return ((Integer)super.h.invoke(this, m3, new Object[]{Integer.valueOf(arg0), Integer.valueOf(arg1)})).intValue();
      } catch (RuntimeException | Error arg3) {
         throw arg3;
      } catch (Throwable arg4) {
         throw new UndeclaredThrowableException(arg4);
      }
   }

	public final int sub(int arg0, int arg1) throws  {
      try {
         return ((Integer)super.h.invoke(this, m6, new Object[]{Integer.valueOf(arg0), Integer.valueOf(arg1)})).intValue();
      } catch (RuntimeException | Error arg3) {
         throw arg3;
      } catch (Throwable arg4) {
         throw new UndeclaredThrowableException(arg4);
      }
   }

	public final int div(int arg0, int arg1) throws  {
      try {
         return ((Integer)super.h.invoke(this, m5, new Object[]{Integer.valueOf(arg0), Integer.valueOf(arg1)})).intValue();
      } catch (RuntimeException | Error arg3) {
         throw arg3;
      } catch (Throwable arg4) {
         throw new UndeclaredThrowableException(arg4);
      }
   }

	public final int hashCode() throws  {
      try {
         return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
      } catch (RuntimeException | Error arg1) {
         throw arg1;
      } catch (Throwable arg2) {
         throw new UndeclaredThrowableException(arg2);
      }
   }

	static {
		try {
			m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
			m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
			m4 = Class.forName("com.atguigu.spring.aop.proxy.ArithmeticCalculator").getMethod("mul",
					new Class[]{Integer.TYPE, Integer.TYPE});
			m3 = Class.forName("com.atguigu.spring.aop.proxy.ArithmeticCalculator").getMethod("add",
					new Class[]{Integer.TYPE, Integer.TYPE});
			m6 = Class.forName("com.atguigu.spring.aop.proxy.ArithmeticCalculator").getMethod("sub",
					new Class[]{Integer.TYPE, Integer.TYPE});
			m5 = Class.forName("com.atguigu.spring.aop.proxy.ArithmeticCalculator").getMethod("div",
					new Class[]{Integer.TYPE, Integer.TYPE});
			 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
		} catch (NoSuchMethodException arg1) {
			throw new NoSuchMethodError(arg1.getMessage());
		} catch (ClassNotFoundException arg2) {
			throw new NoClassDefFoundError(arg2.getMessage());
		}
	}
}

从代码的最后可以看到,生成的类时Proxy类的子类,同时实现了被代理对象所实现的接口。这个代理类除了被代理对象的四个方法外,还额外写出了从Object继承的equals 、toString和hashCode方法,一共7个方法method0~method6。其中比较关键的代码如下

return ((Integer)super.h.invoke(this, m4, new Object[]{Integer.valueOf(arg0), Integer.valueOf(arg1)})).intValue();

代码中提前针对每个方法都构建好了方法对象,在调用代理对象的任何方法时都只是将对应的方法对象传递到invoke方法中而已.

经过查看源码,super.h就是invokeHandler子类对象,也就是我们前边调用newInstance方法时传进去的匿名内部类对象。所以在生成的类中是直接调用了我们在匿名内部类中的invoke方法。仔细观察invoke方法的参数列表,第一个是this,是代理类对象自己,第二个是方法对象,第三个是方法参数列表.也就是说在最终调用invoke的时候,方法对象是我们最终执行的方法.

基于接口的动态代理实现逻辑就是

1- 先拿到一个被代理的对象

2- 通过被代理的对象得到它所有的方法列表和一个类加载器用于生成代理类

3- 通过Proxy类动态生成一个代理类并构建一个代理对象proxy,通过反编译源码可知这个代理类是Proxy的子类同时也实现了被代理对象实现的所有接口,根据接口的方法列表提前构建好每个方法的方法对象

4- proxy方法被调用时,实际上就是对应的方法对象传递到我们自定义的InvokationHandler子类的invoke函数中,实际执行的还是target的对应方法.

饶了一大圈,就是实现了通过调用其他对象的方法最终执行的还是target的方法,但是我们可以在这个方法的前后增加其他逻辑而不用更改target(核心业务逻辑)的代码.

以上,动态代理的具体过程和原理已经描述完毕,还有一个小小的点就是,上边的代码中是为了解决ArithmeticCalculateImple类的问题的,所以我们在生成代理对象的类中,target对象直接声明为了ArithmeticCalculate类型,但实际上被代理的可以是任何类型的对象,所以此处将ArithmeticCalculate target 改为Object类型,构造方法中参数也改为Object才是最终比较灵活的可以代理所有对象的版本.

代码如下

public class ArithmeticCalculateProxy {
	
	//target object
	Object target;
	
	public ArithmeticCalculateProxy (Object target) {
		this.target = target;
	}
	
	//获取代理对象的方法
	public Object getProxy() {
		Object proxy;
		ClassLoader loader = target.getClass().getClassLoader();
		Class [] interfaces = target.getClass().getInterfaces();
		proxy=Proxy.newProxyInstance(loader, interfaces, new InvocationHandler() {
			@Override
			public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
				String methodName = method.getName();
				Object result = method.invoke(target, args);
				return result;
			}
		});
		return proxy;
	}

}

以上,感谢您的耐心阅读.如有描述不清楚的地方请留言.

你可能感兴趣的:(JAVA开发)