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;
}
}
不愧是 G 大。
不愧是 G 大。
不愧是大G