JBPM 异常分析.

JBPM 4.0使用hibernate 3.6.0作为数据持久框架,该版本依赖的javassist版本为3.12.0.GA

Hibernate中的每个Entity其实都是代理过的类,由javassit生成一个代理类去继承Entity类。

举个例子

public class Phone {
}

@Entity
public class PhoneImpl extends Phone {
}

public class User {

@OneToOne
private Phone phone;

public Phone getPhone() {
return phone;
}

}

@Entity
public class UserImpl extends User {


@OneToOne
private PhoneImpl phone;

@Override
public PhoneImpl getPhone() {
return phone;
}

}

//javassit生成的代理类如下
public class UserImplProxy extends UserImpl {

private PhoneImpl phone;

private <T> T fetchDataFromDB() {
return (T) new Object();
}


@Override
public PhoneImpl getPhone() {
//hibernate 代理 查询数据库
phone = fetchDataFromDB();
return phone;
}

}
public class Test {

public static void main(String[] args) {
Method[] declaredMethods = UserImplProxy.class.getDeclaredMethods();

/**
*
* /**
* <init>()V 0x0001 [public]
* fetchDataFromDB()Ljava/lang/Object; 0x0002 [private]
* getPhone()Lcom/project/pangu/cache/PhoneImpl;0x0001 [public]
* getPhone()Lcom/project/pangu/cache/Phone; 0x1041 [public synthetic bridge]
*/
//此处模拟jbpm的调用
UserImplProxy proxy = new UserImplProxy();
PhoneImpl phoneImpl = proxy.getPhone();
System.out.println(1);
}
}

你会发现

执行UserImplProxy.class.getDeclaredMethods()

你会发现返回结果“多出”一个synthetic bridge方法
getPhone()Lcom/project/pangu/cache/Phone; 0x1041 [public synthetic bridge]Java几种生成synthetic方法的情况,)

javassit在代理目标类时,会把目标类中定义的所有方法都查询出来,形成Map<String,Method>

javassist 3.12.0.GA

private static void getMethods(HashMap hash, Class clazz) {
Class[] ifs = clazz.getInterfaces();
for (int i = 0; i < ifs.length; i++)
getMethods(hash, ifs[i]);

Class parent = clazz.getSuperclass();
if (parent != null)
getMethods(hash, parent);

Method[] methods = SecurityActions.getDeclaredMethods(clazz);
for (int i = 0; i < methods.length; i++)
if (!Modifier.isPrivate(methods[i].getModifiers())) {
Method m = methods[i];
//重点,RuntimeSupport.makeDescriptor(m),这里返回格式为:方法参数表+方法返回类型;
String key = m.getName() + ':' + RuntimeSupport.makeDescriptor(m);
// JIRA JASSIST-85
// put the method to the cache, retrieve previous definition (if any)
Method oldMethod = (Method)hash.put(key, methods[i]);

// check if visibility has been reduced
if (null != oldMethod && Modifier.isPublic(oldMethod.getModifiers())
&& !Modifier.isPublic(methods[i].getModifiers()) ) {
// we tried to overwrite a public definition with a non-public definition,
// use the old definition instead.
hash.put(key, oldMethod);
}
}
}

Map的key最终格式为:methodName:paramsTypes+returnType

javassit 3.16.0.GA

private static void getMethods(HashMap hash, Class clazz, Set visitedClasses) {
if (visitedClasses.add(clazz)) {
Class[] ifs = clazz.getInterfaces();

for(int i = 0; i < ifs.length; ++i) {
getMethods(hash, ifs[i], visitedClasses);
}

Class parent = clazz.getSuperclass();
if (parent != null) {
getMethods(hash, parent, visitedClasses);
}

Method[] methods = SecurityActions.getDeclaredMethods(clazz);

for(int i = 0; i < methods.length; ++i) {
if (!Modifier.isPrivate(methods[i].getModifiers())) {
Method m = methods[i];
// //重点,RuntimeSupport.makeDescriptor(m),这里返回格式为:方法参数表;
String key = m.getName() + ':' + RuntimeSupport.makeDescriptor(m.getParameterTypes(), (Class)null);
Method oldMethod = (Method)hash.put(key, methods[i]);
if (null != oldMethod && Modifier.isPublic(oldMethod.getModifiers()) && !Modifier.isPublic(methods[i].getModifiers())) {
hash.put(key, oldMethod);
}
}
}

}
}

Map的key最终格式为:methodName:paramsTypes

当使用javaassist 3.12.0.GA时,由于遍历目标类的方法时,Map使用的key为methodName:paramsTypes+returnType,可以保证每个方法都能被代理到

由于项目中实际运行时使用的javassist版本是3.16.0.GA,(某个小伙伴引入的第三方库中使用javassist-3.16.0.GA,覆盖了jbpm所依赖的javassist-3.12.0.GA)

当遍历目标类的方法时,Map使用的key为methodName:paramsTypes,当出现同名同形参的方法时,这个时候能被代理到的方法和Class#getDeclaredMethods() 返回的顺序有关,该方法注释如下

Returns an array containing Method objects reflecting all the declared methods of the class or interface represented by this Class object, including public, protected, default (package) access, and private methods, but excluding inherited methods.
If this Class object represents a type that has multiple declared methods with the same name and parameter types, but different return types, then the returned array has a Method object for each such method.
If this Class object represents a type that has a class initialization method , then the returned array does not have a corresponding Method object.
If this Class object represents a class or interface with no declared methods, then the returned array has length 0.
If this Class object represents an array type, a primitive type, or void, then the returned array has length 0.
The elements in the returned array are not sorted and are not in any particular order (重点,返回数组中的元素没有排序,也没有特定顺序)

所以当执行下面代码时,有一定几率执行的方法不是代理过方法

UserImplProxy proxy = JavassistProxy(new UserImplProxy());
PhoneImpl phoneImpl = proxy.getPhone();

所以导致jbmp执行相关操作时,查询对象实体时,有一定几率导致返回为空(没有被代理,那么就不会触发查询)