JDK动态代理实现原理

java中动态代理主要有JDK和CGLIB两种方式。 区别主要是JDK是代理接口,而CGLIB是代理类。

JDK的动态代理调用了Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 方法。通过该方法生成字节码,动态的创建了一个代理类,interfaces参数是该动态类所继承的所有接口,而实现InvocationHandler 接口的类则是实现在调用代理接口方法前后的具体逻辑, 我们可以通过重写实现InvocationHandler 接口的类的invoke()方法,在调用调用代理接口方法前后可以添加自己的逻辑;

根据源码我们可以了解到newProxyInstance方法执行了以下几种操作:

  1. 生成一个实现了参数interfaces里所有接口且继承了Proxy的代理类的字节码,然后用参数里的classLoader加载这个代理类。
  2. 使用代理类父类的构造函数 Proxy(InvocationHandler h)来创造一个代理类的实例,将我们自定义的InvocationHandler的子类传入。
  3. 返回这个代理类实例。

1. 模拟静态代理

1.1 委托类和代理类需要实现相同的接口, 代理类通过实现该接口, 重写方法, 在方法中调用委托类的方法

package com.lic.dao;
public interface UserDao {
	public void query();
	public String query(String str);
}

1.2 委托类的实现

package com.lic.dao.impl;
import com.lic.dao.UserDao;
public class UserDaoImpl implements UserDao{
	@Override
	public void query() {
		System.out.println("模拟查询...");
	}
	@Override
	public String query(String str) {
		str+="123";
		return str;
	}
}

1.3 代理类的实现

package com.lic.dao.impl;
import com.lic.dao.UserDao;
public class Proxy implements UserDao{
    //代理类中维护了一个委托类的接口,用来接收委托类, 以便在重写的方法中调用委托类的方法
	private UserDao userDao;
	public Proxy(UserDao userDao) {
		this.userDao = userDao;
	}
	@Override
	public void query() {
		userDao.query();
		System.out.println("模拟增强");	
	}
	@Override
	public String query(String str) {
		return str+"_模拟增强";
	}
}

1.4测试类

package com.lic.test;
import com.lic.dao.impl.Proxy;
import com.lic.dao.impl.UserDaoImpl;
public class ProxyTest {
	public static void main(String[] args) {
		Proxy proxy = new Proxy(new UserDaoImpl());
		proxy.query();
	}
}

动态代理和静态代理的区别?
1、静态代理在代理前就知道要代理的是哪个对象,而动态代理是运行时才知道;
2、静态代理一般只能代理一个类,而动态代理能代理实现了接口的多个类;

静态代理需要代理类实现和委托类相同的接口,并使用构造方法传递委托类对象的引用,重写接口中的方法; 而动态代理则是在运行时生成一个实现了参数interfaces里所有接口且继承了Proxy的代理类的字节码文件,然后用参数里的classLoader加载这个代理类。

2. 模拟动态代理_V1(单纯的代理委托类, 对于代理类扩展的程序采用硬编码)

我们可以基于上面的示例模拟一个简单的动态代理, 在动态代理中Proxy类是动态生成的,而在上面的示例中是静态生成的, 那我我们创建一个类, 在运行时动态的创建一个代理类就可以了

思路:
通过接口反射生成一个类文件,然后调用第三方的编译技术,动态编译这个产生的类文件成class文件,继而利用UrlclassLoader(因为这个动态产生的class不在工程当中所以需要使用UrlclassLoader)把这个动态编译的类加载到jvm当中,最后通过反射把这个类实例化。

流程图:
在这里插入图片描述

newInstance(Object target)方法的实现:

package com.lic.util;
import java.io.File;
import java.io.FileWriter;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
public class ProxyUtil {
	public static Object newInstance(Object target) {
		Object proxy = null;
		//格式调整
		String line="\n";
		String tab="\t";
		//获取目标类的接口, 代理类需要与目标类实现统一接口
		Class<?> targetInt = target.getClass().getInterfaces()[0];
		//目标类接口名称
		String targetIntName = targetInt.getSimpleName();
		//1. 包声明
		String packageContent = "package com.lic.proxy;"+line+line;
		//2. 包导入
		String importContent = "import " + targetInt.getName() + ";"+line+line;
		//3. 类声明
		String classContent = "public class $Proxy implements "+targetIntName+"{"+line+line;
		//4. 属性声明
		String propertyDefine =tab+ "private "+targetIntName +" target;"+line+line;
		//5. 构造函数
		String constructContent =tab+ "public $Proxy ("+targetIntName+" target){"+line
								+tab+tab+"this.target = target;"+line
								+tab+ "}"+line+line;
		//6.实现接口的所有方法
		String methodsContent = "";
		//获取目标类的接口的所有方法
		Method[] methods = targetInt.getDeclaredMethods();
		//6.1 遍历每个方法
		for (Method method : methods) {
			//a. 获取方法返回类型名称
			String returnTypeName=method.getReturnType().getSimpleName();
			//b. 获取方法名称
			String methodName=method.getName();
			//c. 获取方法参数
			Class<?>[] args = method.getParameterTypes();
			//d. 拼接参数
			String argsContent="";
			String argsContent1="";
			int argCount=0;
			if(args.length>0){
				for (Class<?> arg : args) {
					//String arg0, String arg1,
					argsContent+=arg.getSimpleName()+" arg"+argCount+", ";
					//arg0,arg1,
					argsContent1+="arg"+argCount+",";
					argCount++;
				}
				argsContent=argsContent.substring(0, argsContent.lastIndexOf(","));
				argsContent1=argsContent1.substring(0,argsContent1.lastIndexOf(","));
			}
			methodsContent+=tab+ "public "+returnTypeName+" "+methodName+"("+argsContent+"){"+line
							+tab+tab+"System.out.println(\"模拟增强中...\");"+line;
							
							if("void".equals(returnTypeName)){
								methodsContent+=tab+tab+"target."+methodName+"("+argsContent1+");"+line;
							}else{
								methodsContent+=tab+tab+"return target."+methodName+"("+argsContent1+");"+line;
							}
							methodsContent+=tab+ "}"+line+line;
		}
		//7. 拼接代理类内容
		String proxyContent=packageContent+importContent+classContent+propertyDefine+constructContent+methodsContent
				+"}"+line;
		
		//8. 根据包路径生成文件夹
		File folder = new File("E:\\com\\lic\\proxy");
		folder.mkdirs();
		//9. 创建代理类的文件
		File file = new File("E:\\com\\lic\\proxy\\$Proxy.java");
		try{
		if(!file.exists()){
			file.createNewFile();
		}
        //10. 输出代理类
		FileWriter fw = new FileWriter(file);
		fw.write(proxyContent);
		fw.flush();
		fw.close();
		
		//11.根据.java文件对代理类进行编译
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

        StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);
        Iterable units = fileMgr.getJavaFileObjects(file);

        JavaCompiler.CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units);
        t.call();
        fileMgr.close();

        URL[] urls = new URL[]{new URL("file:E:\\\\")};
        URLClassLoader urlClassLoader = new URLClassLoader(urls);
        Class clazz = urlClassLoader.loadClass("com.lic.proxy.$Proxy");

        Constructor constructor = clazz.getConstructor(targetInt);

        proxy = constructor.newInstance(target);
		
		}catch(Exception e){
			e.printStackTrace();
		}
		return proxy;
	}

}

测试类:

package com.lic.test;

import com.lic.dao.UserDao;
import com.lic.dao.impl.UserDaoImpl;
import com.lic.util.MyInvocationHandler;
import com.lic.util.ProxyUtil;
import com.lic.util.ProxyUtil_V2;

public class Test {
	public static void main(String[] args) {
		UserDao proxy = (UserDao) ProxyUtil.newInstance(new UserDaoImpl());
		proxy.query();
		System.out.println("------------");
		System.out.println(proxy.query("测试"));	
	}
}

3. 模拟动态代理_V2

由于上面的模拟对于代理类扩展的程序是采用硬编码, 我们无法扩展代理类; 那么我们可以根据JDK动态代理自定义一个InvocationHandler接口, 并实现它; 在实现类中的invoke()方法中编写代理类的逻辑, 在调用newInstance()方法中传入, 在动态生成代理的java文件时, 将直接调用实例类对应的invoke()方法, 即可完成对委托类的代理, 又可以对委托类进行扩展

3.1 JDK动态代理的实现
package com.lic.util;

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

public class JDKInvocationHandler implements InvocationHandler {
	//被代理的目标对象
	private Object target;

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

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("增强中...");
		//调用目标对象的方法
		return method.invoke(target, args);
	}

}

测试类:

package com.lic.test;
import java.lang.reflect.Proxy;
import com.lic.dao.UserDao;
import com.lic.dao.impl.UserDaoImpl;
import com.lic.util.JDKInvocationHandler;

public class JDKProxy {
	
	public static void main(String[] args) {
		//利用JDK动态代理对UserDaoImpl对象进行代理
		UserDao userDao = (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), 
				new Class[]{UserDao.class}, 
				new JDKInvocationHandler(new UserDaoImpl()));

		System.out.println(userDao.query("132"));
	}

}

3.2 模拟JDK动态代理的实现

1.自定义InvocationHandler 接口

package com.lic.util;

import java.lang.reflect.Method;

public interface CustomeInvocationHandler {
	
	public Object invoke(Method method, Object[] args);

}

2. 创建InvocationHandler 接口的实现类, 重写invoke()方法

package com.lic.util;

import java.lang.reflect.Method;

public class MyInvocationHandler implements CustomeInvocationHandler {
	// 被代理的目标对象
	private Object target;

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

	@Override
	public Object invoke(Method method,Object[] args) {
		System.out.println("牛逼了...");
		try {
			//利用反射调用委托类的方法
			return method.invoke(target,args);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

}

3. 实现newInstance()方法

package com.lic.util;

import java.io.File;
import java.io.FileWriter;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

public class ProxyUtil_V2 {

	public static Object newInstance(Object target,CustomeInvocationHandler h) {
		Object proxy = null;
		
		//格式调整
		String line="\n";
		String tab="\t";
		
		//获取目标类的接口, 代理类需要与目标类实现统一接口
		Class<?> targetInt = target.getClass().getInterfaces()[0];
		//目标类接口名称
		String targetIntName = targetInt.getSimpleName();
		//1. 包声明
		String packageContent = "package com.lic.proxy;"+line+line;
		//2. 包导入
		String importContent = "import " + targetInt.getName() + ";"+line
							  +"import com.lic.util.CustomeInvocationHandler;"+line
			                  +"import java.lang.Exception;"+line
			                  +"import java.lang.reflect.Method;"+line+line;
		//3. 类声明
		String classContent = "public class $Proxy implements "+targetIntName+"{"+line+line;
		//4. 属性声明
		String propertyDefine =tab+ "private CustomeInvocationHandler h;"+line+line;
		//5. 构造函数
		String constructContent =tab+ "public $Proxy (CustomeInvocationHandler h){"+line
								+tab+tab+"this.h = h;"+line
								+tab+ "}"+line+line;
		//6.实现接口的所有方法
		String methodsContent = "";
		//获取目标类的接口的所有方法
		Method[] methods = targetInt.getDeclaredMethods();
		//6.1 遍历每个方法
		for (Method method : methods) {
			//a. 获取方法返回类型名称
			String returnTypeName=method.getReturnType().getSimpleName();
			//b. 获取方法名称
			String methodName=method.getName();
			//c. 获取方法参数
			Class<?>[] args = method.getParameterTypes();
			//d. 拼接参数
			String argsContent="";
			String argsContent1="";
			String argsClassContents="";
			int argCount=0;
			if(args.length>0){
				argsContent1+=",new Object[]{";
				for (Class<?> arg : args) {
					//String arg0, String arg1,
					argsContent+=arg.getSimpleName()+" arg"+argCount+", ";
					//arg0,arg1,
					argsContent1+="arg"+argCount+",";
					
					argsClassContents+=","+arg.getSimpleName()+".class";
					argCount++;
				}
				argsContent=argsContent.substring(0, argsContent.lastIndexOf(","));
				argsContent1=argsContent1.substring(0,argsContent1.lastIndexOf(","));
				argsContent1+="}";
			}
			String castContent="";
			if(! "void".equals(returnTypeName)){
				castContent+="("+returnTypeName+")";
			}
			
			methodsContent+= tab+ "public "+returnTypeName+" "+methodName+"("+argsContent+"){"+line
							+tab+tab+"try{"+line
							+tab+tab+tab+"Method method = Class.forName(\""+targetInt.getName()+"\").getDeclaredMethod(\""+methodName+"\""+argsClassContents+");"+line;
							if(!"void".equals(returnTypeName))
								methodsContent+=tab+tab+tab+"return "+castContent+" h.invoke(method"+argsContent1+");"+line;
							else{
								methodsContent+=tab+tab+tab+castContent+"h.invoke(method, null);"+line;
							}
							methodsContent+=tab+tab+"} catch (Exception e) {"+line
							+tab+tab+tab+"e.printStackTrace();"+line
							+tab+tab+"}"+line;
							if(!"void".equals(returnTypeName))
								methodsContent+=tab+tab+"return null;"+line;
							methodsContent+=tab+ "}"+line+line;
		}
		
		//7. 拼接代理类内容
		String proxyContent=packageContent+importContent+classContent+propertyDefine+constructContent+methodsContent
				+"}"+line;
		
		//8. 根据包路径生成文件夹
		File folder = new File("E:\\com\\lic\\proxy");
		folder.mkdirs();
		//9. 创建代理类的文件
		File file = new File("E:\\com\\lic\\proxy\\$Proxy.java");
		try{
		if(!file.exists()){
			file.createNewFile();
		}
        //10. 输出代理类
		FileWriter fw = new FileWriter(file);
		fw.write(proxyContent);
		fw.flush();
		fw.close();
		
		//11.根据.java文件对代理类进行编译
		JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

        StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);
        Iterable units = fileMgr.getJavaFileObjects(file);

        JavaCompiler.CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units);
        t.call();
        fileMgr.close();

        URL[] urls = new URL[]{new URL("file:E:\\\\")};
        URLClassLoader urlClassLoader = new URLClassLoader(urls);
        Class clazz = urlClassLoader.loadClass("com.lic.proxy.$Proxy");

        Constructor constructor = clazz.getConstructor(CustomeInvocationHandler.class);

        proxy = constructor.newInstance(h);
		
		}catch(Exception e){
			e.printStackTrace();
		}
		return proxy;
	}

}

  1. 测试类
package com.lic.test;

import com.lic.dao.UserDao;
import com.lic.dao.impl.UserDaoImpl;
import com.lic.util.MyInvocationHandler;
import com.lic.util.ProxyUtil;
import com.lic.util.ProxyUtil_V2;

public class Test {
	public static void main(String[] args) {
		UserDao proxy = (UserDao) ProxyUtil_V2.newInstance(new UserDaoImpl(),new MyInvocationHandler(new UserDaoImpl()));
		proxy.query();
		System.out.println("------------");
		System.out.println(proxy.query("测试"));
	}
}

注意:
在JDK动态代理中, 是直接生成字节码文件, 然后交由JVM去动态加载的;


JDK与Cglib动态代理对比?

1、JDK动态代理只能代理实现了接口的类,没有实现接口的类不能实现JDK的动态代理;

2、Cglib动态代理是针对类实现代理的,运行时动态生成被代理类的子类拦截父类方法调用,因此不能代理声明为final类型的类和方法;

Spring如何选择两种代理模式的?

1、如果目标对象实现了接口,则默认采用JDK动态代理;

2、如果目标对象没有实现接口,则使用Cglib代理;

3、如果目标对象实现了接口,但强制使用了Cglib,则使用Cglib进行代理

下一篇: CGLIB动态代理实现原理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值