Javassist - Java 字节码处理工具

关于java字节码的处理,有很多工具,如bcel,asm。不过这些都需要直接跟虚拟机指令打交道。如果你不想了解虚拟机指令,可以采用javassist。javassist是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;
    }
}