blob: 02a1e9fd251a0ea357e58af40d17f659c3941e73 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.common.bytecode;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import javassist.ClassPool;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
interface Builder<T> {
T getName(Bean bean);
void setName(Bean bean, T name);
}
class BaseClass {
public BaseClass() {}
public BaseClass(StringBuilder sb) {
sb.append("constructor comes from BaseClass");
}
public String baseClassMethod() {
return "method comes from BaseClass";
}
}
interface BaseInterface {}
class ClassGeneratorTest {
@Test
void test() throws Exception {
ClassGenerator cg = ClassGenerator.newInstance();
// add className, interface, superClass
String className = BaseClass.class.getPackage().getName() + ".TestClass"
+ UUID.randomUUID().toString().replace("-", "");
cg.setClassName(className);
cg.addInterface(BaseInterface.class);
cg.setSuperClass(BaseClass.class);
// add constructor
cg.addDefaultConstructor();
cg.addConstructor(
Modifier.PUBLIC,
new Class[] {String.class, int.class},
new Class[] {Throwable.class, RuntimeException.class},
"this.strAttr = arg0;this.intAttr = arg1;");
cg.addConstructor(BaseClass.class.getConstructor(StringBuilder.class));
// add field
cg.addField(
"staticAttr", Modifier.PUBLIC | Modifier.STATIC | Modifier.VOLATILE, String.class, "\"defaultVal\"");
cg.addField("strAttr", Modifier.PROTECTED | Modifier.VOLATILE, String.class);
cg.addField("intAttr", Modifier.PRIVATE | Modifier.VOLATILE, int.class);
// add method
cg.addMethod(
"setStrAttr",
Modifier.PUBLIC,
void.class,
new Class[] {String.class, int.class},
new Class[] {Throwable.class, RuntimeException.class},
"this.strAttr = arg0;");
cg.addMethod(
"setIntAttr",
Modifier.PUBLIC,
void.class,
new Class[] {String.class, int.class},
new Class[] {Throwable.class, RuntimeException.class},
"this.intAttr = arg1;");
cg.addMethod(
"getStrAttr",
Modifier.PUBLIC,
String.class,
new Class[] {},
new Class[] {Throwable.class, RuntimeException.class},
"return this.strAttr;");
cg.addMethod(
"getIntAttr",
Modifier.PUBLIC,
int.class,
new Class[] {},
new Class[] {Throwable.class, RuntimeException.class},
"return this.intAttr;");
cg.addMethod(BaseClass.class.getMethod("baseClassMethod"));
// cg.toClass
Class<?> clz = cg.toClass(Bean.class);
// after cg.toClass, the TestClass.class generated by javassist is like the following file content
getClass().getResource("/org/apache/dubbo/common/bytecode/TestClass");
// verify class, superClass, interfaces
Assertions.assertTrue(ClassGenerator.isDynamicClass(clz));
Assertions.assertEquals(clz.getName(), className);
Assertions.assertEquals(clz.getSuperclass(), BaseClass.class);
Assertions.assertArrayEquals(clz.getInterfaces(), new Class[] {ClassGenerator.DC.class, BaseInterface.class});
// get constructors
Constructor<?>[] constructors = clz.getConstructors();
Assertions.assertEquals(constructors.length, 3);
Constructor<?> constructor0 = clz.getConstructor();
Constructor<?> constructor1 = clz.getConstructor(new Class[] {String.class, int.class});
Constructor<?> constructor2 = clz.getConstructor(new Class[] {StringBuilder.class});
Assertions.assertEquals(constructor1.getModifiers(), Modifier.PUBLIC);
Assertions.assertArrayEquals(
constructor1.getExceptionTypes(), new Class[] {Throwable.class, RuntimeException.class});
// get fields
Field staticAttrField = clz.getDeclaredField("staticAttr");
Field strAttrField = clz.getDeclaredField("strAttr");
Field intAttrField = clz.getDeclaredField("intAttr");
Assertions.assertNotNull(staticAttrField);
Assertions.assertNotNull(strAttrField);
Assertions.assertNotNull(intAttrField);
Assertions.assertEquals(staticAttrField.get(null), "defaultVal");
// get methods
Method setStrAttrMethod = clz.getMethod("setStrAttr", new Class[] {String.class, int.class});
Method setIntAttrMethod = clz.getMethod("setIntAttr", new Class[] {String.class, int.class});
Method getStrAttrMethod = clz.getMethod("getStrAttr");
Method getIntAttrMethod = clz.getMethod("getIntAttr");
Method baseClassMethod = clz.getMethod("baseClassMethod");
Assertions.assertNotNull(setStrAttrMethod);
Assertions.assertNotNull(setIntAttrMethod);
Assertions.assertNotNull(getStrAttrMethod);
Assertions.assertNotNull(getIntAttrMethod);
Assertions.assertNotNull(baseClassMethod);
// verify constructor0
Object objByConstructor0 = constructor0.newInstance();
Assertions.assertEquals(getStrAttrMethod.invoke(objByConstructor0), null);
Assertions.assertEquals(getIntAttrMethod.invoke(objByConstructor0), 0);
// verify constructor1
Object objByConstructor1 = constructor1.newInstance("v1", 1);
Assertions.assertEquals(getStrAttrMethod.invoke(objByConstructor1), "v1");
Assertions.assertEquals(getIntAttrMethod.invoke(objByConstructor1), 1);
// verify getter setter method
setStrAttrMethod.invoke(objByConstructor0, "v2", 2);
setIntAttrMethod.invoke(objByConstructor0, "v3", 3);
Assertions.assertEquals(getStrAttrMethod.invoke(objByConstructor0), "v2");
Assertions.assertEquals(getIntAttrMethod.invoke(objByConstructor0), 3);
// verify constructor and method witch comes from baseClass
StringBuilder sb = new StringBuilder();
Object objByConstructor2 = constructor2.newInstance(sb);
Assertions.assertEquals(sb.toString(), "constructor comes from BaseClass");
Object res = baseClassMethod.invoke(objByConstructor2);
Assertions.assertEquals(res, "method comes from BaseClass");
cg.release();
}
@SuppressWarnings("unchecked")
@Test
void testMain() throws Exception {
Bean b = new Bean();
Field fname = Bean.class.getDeclaredField("name");
fname.setAccessible(true);
ClassGenerator cg = ClassGenerator.newInstance();
cg.setClassName(Bean.class.getName() + "$Builder" + UUID.randomUUID().toString());
cg.addInterface(Builder.class);
cg.addField("public static java.lang.reflect.Field FNAME;");
cg.addMethod("public Object getName(" + Bean.class.getName()
+ " o){ boolean[][][] bs = new boolean[0][][]; return (String)FNAME.get($1); }");
cg.addMethod("public void setName(" + Bean.class.getName() + " o, Object name){ FNAME.set($1, $2); }");
cg.addDefaultConstructor();
Class<?> cl = cg.toClass(Bean.class);
cl.getField("FNAME").set(null, fname);
System.out.println(cl.getName());
Builder<String> builder = (Builder<String>) cl.getDeclaredConstructor().newInstance();
System.out.println(b.getName());
builder.setName(b, "ok");
System.out.println(b.getName());
}
@Test
void testMain0() throws Exception {
Bean b = new Bean();
Field fname = Bean.class.getDeclaredField("name");
fname.setAccessible(true);
ClassGenerator cg = ClassGenerator.newInstance();
cg.setClassName(Bean.class.getName() + "$Builder2" + UUID.randomUUID().toString());
cg.addInterface(Builder.class);
cg.addField("FNAME", Modifier.PUBLIC | Modifier.STATIC, java.lang.reflect.Field.class);
cg.addMethod("public Object getName(" + Bean.class.getName()
+ " o){ boolean[][][] bs = new boolean[0][][]; return (String)FNAME.get($1); }");
cg.addMethod("public void setName(" + Bean.class.getName() + " o, Object name){ FNAME.set($1, $2); }");
cg.addDefaultConstructor();
Class<?> cl = cg.toClass(Bean.class);
cl.getField("FNAME").set(null, fname);
System.out.println(cl.getName());
Builder<String> builder = (Builder<String>) cl.getDeclaredConstructor().newInstance();
System.out.println(b.getName());
builder.setName(b, "ok");
System.out.println(b.getName());
}
@Test
public void test_getClassPool() throws InterruptedException {
int threadCount = 5;
CountDownLatch LATCH = new CountDownLatch(threadCount);
ClassLoader loader = Thread.currentThread().getContextClassLoader();
List<Integer> hashCodeList = new ArrayList<>();
for (int i = 0; i < threadCount; i++) {
new Thread(new Runnable() {
@Override
public void run() {
ClassPool classPool = ClassGenerator.getClassPool(loader);
int currentHashCode = classPool.hashCode();
hashCodeList.add(currentHashCode);
System.out.println(currentHashCode);
LATCH.countDown();
}
})
.start();
}
LATCH.await();
Integer firstHashCode = null;
for (Integer currentHashCode : hashCodeList) {
if (firstHashCode == null) {
firstHashCode = currentHashCode;
continue;
}
Assertions.assertTrue(firstHashCode.intValue() == currentHashCode.intValue());
}
}
}
class Bean {
int age = 30;
private String name = "qianlei";
public int getAge() {
return age;
}
public String getName() {
return name;
}
public static volatile String abc = "df";
}