首页 > 代码库 > Javassist简单应用小结

Javassist简单应用小结

概述

  Javassist是一款字节码编辑工具,可以直接编辑和生成Java生成的字节码,以达到对.class文件进行动态修改的效果。熟练使用这套工具,可以让Java编程更接近与动态语言编程。
  下面一个方法的目的是获取一个类加载器(ClassLoader),以加载指定的.jar或.class文件,在之后的代码中会使用到。
private static ClassLoader getLocaleClassLoader() throws Exception {
	List<URL> classPathURLs = new ArrayList<>();
	// 加载.class文件路径
	classPathURLs.add(classesPath.toURI().toURL());

	// 获取所有的jar文件
	File[] jarFiles = libPath.listFiles(new FilenameFilter() {
		@Override
		public boolean accept(File dir, String name) {
			return name.endsWith(".jar");
		}
	});
	Assert.assertFalse(ObjectHelper.isArrayNullOrEmpty(jarFiles));

	// 将jar文件路径写入集合
	for (File jarFile : jarFiles) {
		classPathURLs.add(jarFile.toURI().toURL());
	}

	// 实例化类加载器
	return new URLClassLoader(classPathURLs.toArray(new URL[classPathURLs.size()]));
}

获取类型信息

@Test
public void test() throws NotFoundException {
	// 获取默认类型池对象
	ClassPool classPool = ClassPool.getDefault();

	// 获取指定的类型
	CtClass ctClass = classPool.get("java.lang.String");

	System.out.println(ctClass.getName());	// 获取类名
	System.out.println("\tpackage " + ctClass.getPackageName());	// 获取包名
	System.out.print("\t" + Modifier.toString(ctClass.getModifiers()) + " class " + ctClass.getSimpleName());	// 获取限定符和简要类名
	System.out.print(" extends " + ctClass.getSuperclass().getName());	// 获取超类
	// 获取接口
	if (ctClass.getInterfaces() != null) {
		System.out.print(" implements ");	
		boolean first = true;
		for (CtClass c : ctClass.getInterfaces()) {
			if (first) {
				first = false;
			} else {
				System.out.print(", ");
			}
			System.out.print(c.getName());
		}
	}
	System.out.println();
}

修改类方法

@Test
public void test() throws Exception {
	// 获取本地类加载器
	ClassLoader classLoader = getLocaleClassLoader();
	// 获取要修改的类
	Class<?> clazz = classLoader.loadClass("edu.alvin.reflect.TestLib");

	// 实例化类型池对象
	ClassPool classPool = ClassPool.getDefault();
	// 设置类搜索路径
	classPool.appendClassPath(new ClassClassPath(clazz));
	// 从类型池中读取指定类型
	CtClass ctClass = classPool.get(clazz.getName());

	// 获取String类型参数集合
	CtClass[] paramTypes = {classPool.get(String.class.getName())};
	// 获取指定方法名称
	CtMethod method = ctClass.getDeclaredMethod("show", paramTypes);
	// 赋值方法到新方法中
	CtMethod newMethod = CtNewMethod.copy(method, ctClass, null);
	// 修改源方法名称
	String oldName = method.getName() + "$Impl";
	method.setName(oldName);

	// 修改原方法
	newMethod.setBody("{System.out.println(\"执行前\");" + oldName + "($$);System.out.println(\"执行后\");}");
	// 将新方法添加到类中
	ctClass.addMethod(newMethod);

	// 加载重新编译的类
	clazz = ctClass.toClass();		// 注意,这一行会将类冻结,无法在对字节码进行编辑
	// 执行方法
	clazz.getMethod("show", String.class).invoke(clazz.newInstance(), "hello");
	ctClass.defrost();	// 解冻一个类,对应freeze方法
}

动态创建类

@Test
public void test() throws Exception {
	ClassPool classPool = ClassPool.getDefault();

	// 创建一个类
	CtClass ctClass = classPool.makeClass("edu.alvin.reflect.DynamiClass");
	// 为类型设置接口
	//ctClass.setInterfaces(new CtClass[] {classPool.get(Runnable.class.getName())});

	// 为类型设置字段
	CtField field = new CtField(classPool.get(String.class.getName()), "value", ctClass);
	field.setModifiers(Modifier.PRIVATE);
	// 添加getter和setter方法
	ctClass.addMethod(CtNewMethod.setter("setValue", field));
	ctClass.addMethod(CtNewMethod.getter("getValue", field));
	ctClass.addField(field);

	// 为类设置构造器
	// 无参构造器
	CtConstructor constructor = new CtConstructor(null, ctClass);
	constructor.setModifiers(Modifier.PUBLIC);
	constructor.setBody("{}");
	ctClass.addConstructor(constructor);
	// 参数构造器
	constructor = new CtConstructor(new CtClass[] {classPool.get(String.class.getName())}, ctClass);
	constructor.setModifiers(Modifier.PUBLIC);
	constructor.setBody("{this.value=http://www.mamicode.com/$1;}");>

创建代理类

@Test
public void test() throws Exception {
	// 实例化代理类工厂
	ProxyFactory factory = new ProxyFactory();  

	//设置父类,ProxyFactory将会动态生成一个类,继承该父类  
	factory.setSuperclass(TestProxy.class);

	//设置过滤器,判断哪些方法调用需要被拦截
	factory.setFilter(new MethodFilter() {  
		@Override  
		public boolean isHandled(Method m) {  
			return m.getName().startsWith("get");
		}  
	});

	Class<?> clazz = factory.createClass();
	TestProxy proxy = (TestProxy) clazz.newInstance();
	((ProxyObject)proxy).setHandler(new MethodHandler() {
		@Override
		public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable {
			//拦截后前置处理,改写name属性的内容  
			//实际情况可根据需求修改  
			System.out.println(thisMethod.getName() + "被调用");
			try {
				Object ret = proceed.invoke(self, args);  
				System.out.println("返回值: " + ret);
				return ret;
			} finally {
				System.out.println(thisMethod.getName() + "调用完毕");
			}
		}
	});

	proxy.setName("Alvin");
	proxy.setValue("1000");
	proxy.getName();
	proxy.getValue();
}
  其中,TestProxy类内容如下:
public class TestProxy {
	private String name;
	private String value;
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getValue() {
		return value;
	}
	public void setValue(String value) {
		this.value = http://www.mamicode.com/value;>

获取方法名称

@Test
public void test() throws Exception {
	// 获取本地类加载器
	ClassLoader classLoader = getLocaleClassLoader();
	// 获取要修改的类
	Class<?> clazz = classLoader.loadClass("edu.alvin.reflect.TestLib");
	
	// 实例化类型池
	ClassPool classPool = ClassPool.getDefault();
	classPool.appendClassPath(new ClassClassPath(clazz));
	CtClass ctClass = classPool.get(clazz.getName());
	
	// 获取方法
	CtMethod method = ctClass.getDeclaredMethod("show", ObjectHelper.argumentsToArray(CtClass.class, classPool.get("java.lang.String")));
	// 判断是否为静态方法
	int staticIndex = Modifier.isStatic(method.getModifiers()) ? 0 : 1; 
	
	// 获取方法的参数
	MethodInfo methodInfo = method.getMethodInfo();
	CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
	LocalVariableAttribute localVariableAttribute = (LocalVariableAttribute)codeAttribute.getAttribute(LocalVariableAttribute.tag);
	
	for (int i = 0; i < method.getParameterTypes().length; i++) {
		System.out.println("第" + (i + 1) + "个参数名称为: " + localVariableAttribute.variableName(staticIndex + i));
	}
}
  关于“获取方法名称”,其主要作用是:当Java虚拟机加载.class文件后,会将类方法“去名称化”,即丢弃掉方法形参的参数名,而是用形参的序列号来传递参数。如果要通过Java反射获取参数的参数名,则必须在编辑是指定“保留参数名称”。Javassist则不存在这个问题,对于任意方法,都能正确的获取其参数的参数名。
  Spring MVC就是通过方法参数将请求参数进行注入的,这一点比struts2 MVC要方便很多,Spring也是借助了Javassist来实现这一点的。

附录

  代码中使用了一个ObjectHelper类,这是我自用的一个小工具类,该类的代码可点击查看。