Java:使用javassist模拟实现cglib代理操作

Posted 你是小KS

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java:使用javassist模拟实现cglib代理操作相关的知识,希望对你有一定的参考价值。

当前版本:jdk1.8

1. 声明

当前内容主要为使用javassist实现cglib的代理功能,当前javassist内容参考官方文档官方文档

主要核心思想:

  1. 使用javassist动态创建需要操作类的子类
  2. 在子类中重写方法并构建需要转移的InvocationHandler和方法调用
  3. 这里直接使用java.lang.reflect.InvocationHandler来实现转移操作

2. 基本demo

/**
 * 
 * @author hy
 * @createTime 2022-03-06 16:16:28
 * @description 主要为了模拟cglib的功能
 *
 */
public class JavassistTest 
	public interface IShow
		void show();
	
	public static class Father implements IShow
		public void show() 
			System.out.println("father");
			//return "show called";
		
		
		public String getResult() 
			return "ok";
		
		/*
		 * @Override public String toString()  return "Father object"; 
		 */
	
	public static void main(String[] args) throws Exception 
		Object target = new Father();
		Father proxy = (Father)generatorProxy(Father.class,new InvocationHandler() 
			
			@Override
			public Object invoke(Object proxy, Method method, Object[] args) throws Throwable 
				System.out.println("before===>");
				Object invoke = method.invoke(target, args);
				System.out.println("after===>");
				return invoke;
			
		);
		((IShow)proxy).show();
		String result = proxy.getResult();
		System.out.println(result);
		System.out.println(proxy);
	
	
	// 生成代理(只处理实体类的情况)
	private static Object generatorProxy(Class<?> clazz,InvocationHandler invocationHandler) throws Exception 
		ClassPool pool = ClassPool.getDefault();
		CtClass fatherClass = pool.get(clazz.getName());
		CtClass invocationhandlerClass = pool.get(InvocationHandler.class.getName());
		//	直接创建一个子类
		CtClass childClass = pool.makeClass("com.hy.java.bytecode.generator.Proxy$1", fatherClass);
		
		// 为其添加所有的接口
		generatorInterfaces(childClass,clazz,pool);
		//	为该子类添加一个invocationHandler的字段
		generatorField(childClass);
		//	添加有参数的构造函数
		generatorConstructor(invocationhandlerClass,childClass);
		
		// 生成类的所有方法重写,并完成执行转发操作
		List<CtMethod> generatorMethods = generatorMethod(clazz,childClass);
		for (CtMethod ctMethod : generatorMethods) 
			childClass.addMethod(ctMethod);
		

		Class<?> generatorClass = childClass.toClass();
		Constructor<?> argConstructor = generatorClass.getConstructor(InvocationHandler.class);
		Object newInstance = argConstructor.newInstance(new Object[] invocationHandler);
		return newInstance;
	
	
	// 为生成的类添加接口
	private static void generatorInterfaces(CtClass childClass,Class<?> clazz,ClassPool pool) throws NotFoundException 
		Class<?>[] interfaces = clazz.getInterfaces();
		for (Class<?> in : interfaces) 
			CtClass ctClass = pool.get(in.getName());
			childClass.addInterface(ctClass);
		
	
	
	// 为生成的类添加有参的构造函数,并调用赋值
	private static void generatorConstructor(CtClass invocationhandlerClass,CtClass childClass) throws CannotCompileException 
		CtConstructor constructor = new CtConstructor(new CtClass[] invocationhandlerClass, childClass);
		// 执行this.h = 第一个参数,就是给创建的类进行赋值操作
		constructor.setBody("$0.h=$1;");
		childClass.addConstructor(constructor);
	
	
	// 为当前的类定义一个字段
	private static void generatorField(CtClass childClass) throws CannotCompileException 
		CtField hField = CtField.make("public java.lang.reflect.InvocationHandler h;", childClass);
		childClass.addField(hField);
		
	
	
	// 为实体类创建代理方法,为所有的方法都实现代理操作
	private static List<CtMethod> generatorMethod(Class<?> clazz,CtClass childClass) throws CannotCompileException 
		// 实现所有的clazz中的所有方法
		Method[] declaredMethods = clazz.getDeclaredMethods();
		List<CtMethod> ctMethods =new ArrayList<>(declaredMethods.length);
		for (Method method : declaredMethods) 
			String name = method.getName();
			Class<?> returnType = method.getReturnType();
			Class<?>[] parameterTypes = method.getParameterTypes();
			List<Class<?>> asList = Arrays.asList(parameterTypes);
			String parameterName = getParameterName(asList);
			CtMethod ctMethod = CtMethod.make("public "+returnType.getName()+" "+name+"("+parameterName+");", childClass) ;
			if(returnType==Void.class) 
				//clazz.getMethod("equals", parameterTypes)
				ctMethod.setBody("($r)$0.h.invoke($0,"+clazz.getName()+".class.getMethod(\\""+name+"\\",$sig),$args);");
			else 
				// return this.h.invoke(this,method,args);
				ctMethod.setBody("return ($r)$0.h.invoke($0,"+clazz.getName()+".class.getMethod(\\""+name+"\\",$sig),$args);");
			
			ctMethods.add(ctMethod);
			
		
		return ctMethods;
		
	
	
	// 生成参数列表
	private static String getParameterName(List<Class<?>> asList) 
		if(asList.isEmpty()) 
			return "";
		
		int size = asList.size();
		StringBuilder stringBuilder = new StringBuilder();
		for (int i = 0; i < size; i++) 
			String name = asList.get(i).getName();
			stringBuilder.append(name);
			if(i<size-1) 
				stringBuilder.append(",");
			
		
		return stringBuilder.toString();
	


这里我们构造一个类这个类继承传递的类,并实现传递类的接口,具有以下功能

  1. 具有有参的构造函数,参数为InvocationHandler,并在构造函数内为字段h赋值
  2. 具有一个字段InvocationHandler h
  3. 所有的方法都直接通过调用h的方式进行调用转发的操作

缺点:只能代理实体类

3. 测试和总结


实现成功!!!

1. 其中最主要的部分为($r)表示使用来将invoke的结果Object进行强制转换为返回值类型,保证结果(主要是为了解决有返回值的方法调用问题)

2. 对于方法的创建必须要手动进行访问修饰符以及方法参数的构建操作,这个比较麻烦

以上是关于Java:使用javassist模拟实现cglib代理操作的主要内容,如果未能解决你的问题,请参考以下文章

Java动态代理机制详解(JDK 和CGLIB,Javassist,ASM)

Java动态代理机制详解(JDK 和CGLIB,Javassist,ASM)

Spring框架学习06——AOP底层实现原理

Java逆向基础之动态生成类

性能优于JDK代理,CGLib如何实现动态代理

ASM学习记录