java类加载及动态代理之JAVASSIT

之前和大家说过字节码操作框架的ASM,它使用起来比较困难,今天再学习 一个javassit,它使用起来比较简单,把很多的底层细节都屏蔽了..
javassit是什么呢?

Javassist是一款字节码编辑工具,可以直接编辑和生成Java生成的字节码,以达到对.class文件进行动态修改的效果。熟练使用这套工具,可以让Java编程更接近与动态语言编程。

它的作用很广泛,我们接下来主要从以下几个方面进行代码的讲解,

& 获取类型的基本信息:包名,类名,父类,接口等

// 获取默认类型池对象
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();
基本可以获取类的全部基础信息...

& 修改类的方法:动态修改指定类的方法

下面的代码,拦截到买票的方法,并在该方法执行前插入了两个控制台打印信息:

/**
 * 修改指定类的动态方法
 * @throws Exception
 */
public void modifyClassMethod() throws Exception {
    // 获取要修改的类
    Class<?> clazz = Class.forName("com.suning.dynamic_proxy.javassit.Station");
    // 实例化类型池对象
    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("sellTicket", 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("sellTicket", String.class).invoke(clazz.newInstance(), null);
    // 解冻一个类
    ctClass.defrost();
  
}

& 动态创建类:

比如下面的代码创建了一个Person类,该类里面有一个字段叫userName,然后有对应的setter和getter方法,最后还有一个show方法显示用户的名称:

/**
 * 动态创建类
 * @throws Exception
 */
public  void dynamicGeneratorClass() throws Exception {
    ClassPool classPool = ClassPool.getDefault();

    // 创建一个类
    CtClass ctClass = classPool.makeClass("com.test.Person");

    // 为类型设置字段
    CtField field = new CtField(classPool.get(String.class.getName()), "userName", ctClass);
    field.setModifiers(Modifier.PRIVATE);
    // 添加gettersetter方法
    ctClass.addMethod(CtNewMethod.setter("setUserName", field));
    ctClass.addMethod(CtNewMethod.getter("getUserName", 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.userName=$1;}");
    ctClass.addConstructor(constructor);

    // 为类设置方法
    CtMethod method = new CtMethod(CtClass.voidType, "show", null, ctClass);
    method.setModifiers(Modifier.PUBLIC);
    method.setBody("{System.out.println(\"执行结果\" + this.userName);}");
    ctClass.addMethod(method);

    // 加载和执行生成的类
    Class<?> clazz = ctClass.toClass();
    Object obj = clazz.newInstance();
    clazz.getMethod("setUserName", String.class).invoke(obj, "jhp");
    clazz.getMethod("show").invoke(obj);
    //执行构造器传递进来的参数
    obj = clazz.getConstructor(String.class).newInstance("lyn");
    clazz.getMethod("show").invoke(obj);

}

然后显示的结果如下:


& 创建代理类:

下面的代码创建了一个动态的代理类,拦截到set方法并为其重新赋值,然后再通过拦截到get方法进行执行

/**
 * 创建代理类
 * @throws Exception
 */
public void createProxyClass()throws Exception{
    // 实例化代理类工厂
    ProxyFactory factory = new ProxyFactory();

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

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

    Class<?> clazz = factory.createClass();
    PersonProxy proxy = (PersonProxy) clazz.newInstance();
    ((ProxyObject)proxy).setHandler(new MethodHandler() {
        @Override
        public Object invoke(Object self, Method method, Method proxyMethod, Object[] args) throws Throwable {
            //拦截后前置处理
            System.out.println("方法:"+method.getName() + ",被调用");
            try {
                Object ret = proxyMethod.invoke(self, args);
                System.out.println("返回值: " + ret);
                return ret;
            } finally {
                System.out.println("方法:"+method.getName() + "调用完毕");
            }
        }
    });

    proxy.setName("烧烤小分队");
    proxy.getName();
}

最后运行完的效果如下:


& 获取运行时候的指定方法对应的参数:

其实springmvc框架的参数注入使用的就是这个机制

比如下面的代码就可以实现获取person的register方法里面的所有参数

/**
 * 获取类运行时候的方法参数名称
 * @throws Exception
 */
public void getRuntimeMethodParamterName()throws Exception{

    // 获取要修改的类
    Class<?> clazz = Class.forName("com.suning.dynamic_proxy.classloader.Person");

    // 实例化类型池
    ClassPool classPool = ClassPool.getDefault();
    classPool.appendClassPath(new ClassClassPath(clazz));
    CtClass ctClass = classPool.get(clazz.getName());

    // 获取方法
    CtMethod method = ctClass.getDeclaredMethod("register");
    // 判断是否为静态方法
    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));
    }
}

运行代码的效果如下:


至此javassit的常用功能就讲完了,功能很强大,但使用还是比较简单的,一般就是实例化classpool,之后进行其他对象的获取

CtClass,CtField,CtMethod常用的也就是这几个.

猜你喜欢

转载自blog.csdn.net/qq_18603599/article/details/80747986
今日推荐