Javassist - Java 字节码处理工具

关于java字节码的处理,有很多工具,如bcel,asm。不过这些都需要直接跟

https://baike.baidu.com/item/%E8%99%9A%E6%8B%9F%E6%9C%BA

指令打交道。如果你不想了解虚拟机指令,可以采用javassist。javassist是

https://baike.baidu.com/item/jboss

的一个子项目,其主要的优点,在于简单,而且快速。直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。

生成类

ClassPool pool = ClassPool.getDefault(); CtClass fangcongC = pool.makeClass("fangcong"); fangcongC.addField(CtField.make("public String name;",fangcongC)); fangcongC.addMethod(CtMethod.make("public String getName(){return this.name;}", fangcongC)); fangcongC.addMethod(CtMethod.make("public void setName(String name){this.name = name;}", fangcongC)); fangcongC.writeFile("c://"); Class clazz = fangcongC.toClass(); Object o = clazz.newInstance(); Method setName = clazz.getMethod("setName", String.class); setName.invoke(o, "方聪"); Method getName = clazz.getMethod("getName"); System.out.println(getName.invoke(o));

读取和输出字节码

ClassPool pool = ClassPool.getDefault(); //会从classpath中查询该类 CtClass cc = pool.get("test.Rectangle"); //设置.Rectangle的父类 cc.setSuperclass(pool.get("test.Point")); //输出成二进制格式 //byte[] b=cc.toBytecode(); //输出并加载class 类,默认加载到当前线程的ClassLoader中,也可以选择输出的ClassLoader。

类冻结

若是一个 CtClass 对象经过 writeFile(), toClass(), toBytecode() 被转换成一个类文件,此 CtClass 对象会被冻结起来,不容许再修改。由于一个类只能被 JVM 加载一次。

一个冻结的 CtClass 也能够被解冻。

CtClasss cc = ...; : cc.writeFile(); cc.defrost(); cc.setSuperclass(...); // 由于类已经被解冻,因此这里能够调用成功

调用 defrost() 以后,此 CtClass 对象又能够被修改了。

若是 ClassPool.doPruning 被设置为 true,Javassist 在冻结 CtClass 时,会修剪 CtClass 的数据结构。为了减小内存的消耗,修剪操做会丢弃 CtClass 对象中没必要要的属性。例如,Code_attribute 结构会被丢弃。一个 CtClass 对象被修改以后,方法的字节码是不可访问的,可是方法名称、方法签名、注解信息能够被访问。修剪过的 CtClass 对象不能再次被解冻。ClassPool.doPruning 的默认值为 false。

CtClasss cc = ...; cc.stopPruning(true); : cc.writeFile(); // 转换成一个 class 文件 // cc is not pruned.

实现动态代理

能动态操作字节码当然可以实现动态代理了,在链滴和摸鱼派的后端框架latke的源码中实现ioc使用了动态代理用来包装了bean来完成一些类如事务的需求。具体代码如下

final class JavassistMethodHandler implements MethodHandler { /** * Logger. */ private static final Logger LOGGER = LogManager.getLogger(JavassistMethodHandler.class); /** * Bean manager. */ private final BeanManager beanManager; /** * Call count in the current thread. */ private static final ThreadLocal<AtomicInteger> CALLS = new ThreadLocal<>(); /** * Method filter. 被过滤的方法不进行代理处理 */ private final MethodFilter methodFilter = method -> { final String pkg = method.getDeclaringClass().getPackage().getName(); if (StringUtils.startsWithAny(pkg, new String[]{"org.b3log.latke", "java.", "javax."})) { return false; } final String name = method.getName(); return !"invoke".equals(name) && !"beginTransaction".equals(name) && !"hasTransactionBegun".equals(name); }; /** * Constructs a method handler with the specified bean manager. * * @param beanManager the specified bean manager */ JavassistMethodHandler(final BeanManager beanManager) { this.beanManager = beanManager; } @Override public Object invoke(final Object proxy, final Method method, final Method proceed, final Object[] params) throws Throwable { LOGGER.trace("Processing invocation [" + method.toString() + "]"); //记录方法调用次数 AtomicInteger calls = CALLS.get(); if (null == calls) { calls = new AtomicInteger(0); CALLS.set(calls); } calls.incrementAndGet(); // 事务的处理也是通过代理类来实现 final boolean withTransactionalAnno = method.isAnnotationPresent(Transactional.class); JdbcTransaction transaction = JdbcRepository.TX.get(); final boolean alreadyInTransaction = null != transaction; final boolean needHandleTrans = withTransactionalAnno && !alreadyInTransaction; // Transaction Propagation: REQUIRED (Support a current transaction, create a new one if none exists) if (needHandleTrans) { try { transaction = new JdbcTransaction(); } catch (final SQLException e) { LOGGER.log(Level.ERROR, "Failed to initialize JDBC transaction", e); throw new IllegalStateException("Begin a transaction failed"); } JdbcRepository.TX.set(transaction); } Object ret; try { ret = proceed.invoke(proxy, params); if (needHandleTrans) { transaction.commit(); } } catch (final InvocationTargetException e) { transaction = JdbcRepository.TX.get(); if (null != transaction && transaction.isActive()) { transaction.rollback(); } throw e.getTargetException(); } finally { if (0 == calls.decrementAndGet()) { CALLS.remove(); final Connection connection = JdbcRepository.CONN.get(); if (null != connection) { connection.close(); JdbcRepository.CONN.remove(); } } } return ret; } /** * Gets the method filter. * * @return method filter */ public MethodFilter getMethodFilter() { return methodFilter; }
public class Bean<T> { ..... /** * Bean class. */ private final Class<T> beanClass; /** * Proxy class. */ private final Class<T> proxyClass; /** * Javassist method handler. */ private final JavassistMethodHandler javassistMethodHandler; ..... /** * Constructs a Latke bean. * * @param beanManager the specified bean manager * @param name the specified bean name * @param beanClass the specified bean class * @param types the specified bean types * @param stereotypes the specified stereo types */ public Bean(final BeanManager beanManager, final String name, final Class<T> beanClass, final Set<Type> types, final Set<Class<? extends Annotation>> stereotypes) { this.beanManager = beanManager; this.name = name; this.beanClass = beanClass; this.types = types; this.stereotypes = stereotypes; this.configurator = beanManager.getConfigurator(); javassistMethodHandler = new JavassistMethodHandler(beanManager); final ProxyFactory proxyFactory = new ProxyFactory(); //Javassist代理工厂 proxyFactory.setSuperclass(beanClass); //代理类将继承成被包装的类 proxyFactory.setFilter(javassistMethodHandler.getMethodFilter()); //方法过滤 proxyClass = (Class<T>) proxyFactory.createClass(); //生成代理类对象 annotatedType = new AnnotatedTypeImpl<>(beanClass); fieldInjectionPoints = new HashSet<>(); initFieldInjectionPoints(); } public T create() { T ret = null; try { ret = instantiateReference(); resolveDependencies(ret); } catch (final Exception e) { LOGGER.log(Level.ERROR, "Creates bean [name=" + name + "] failed", e); } return ret; //最终返回的是代理对象 } private T instantiateReference() throws Exception { final T ret = proxyClass.newInstance(); ((ProxyObject) ret).setHandler(javassistMethodHandler); LOGGER.log(Level.TRACE, "Uses Javassist method handler for bean [class={}]", beanClass.getName()); return ret; } }