How to get bytecode of cglib proxy class instance? - java

I'm trying to get bytecode of cglib enhanced object this way using BCEL:
package app;
import cglib.MyInterceptor;
import net.sf.cglib.proxy.Enhancer;
import org.apache.bcel.Repository;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;
import service.Tool;
public class CgLibApp {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
// target object
Tool tool = new Tool();
// proxying
Enhancer e = new Enhancer();
e.setSuperclass(tool.getClass());
e.setCallback(new MyInterceptor(tool));
Tool proxifiedTool = (Tool) e.create();
// trying to get proxy byte code
JavaClass clazz = Repository.lookupClass(proxifiedTool.getClass());
Method method = clazz.getMethod(Tool.class.getMethod("meth"));
System.out.println(method.getCode().toString());
}
}
But I'm getting:
Exception in thread "main" java.lang.ClassNotFoundException: SyntheticRepository could not load service.Tool$$EnhancerByCGLIB$$22a3afcc
at org.apache.bcel.util.SyntheticRepository.loadClass(SyntheticRepository.java:174)
at org.apache.bcel.util.SyntheticRepository.loadClass(SyntheticRepository.java:158)
at org.apache.bcel.Repository.lookupClass(Repository.java:74)
at app.CgLibApp.main(CgLibApp.java:21)
What should I do to get bytecode from Enhanced object?

BCEL queries a class loader for a .class file in order to get hold of the byte array that represents it. Such a class file does not exist for a dynamically generated class.
In order to get hold of the class file, you have to collect the byte code during the class file's creation. Cglib is built on top of ASM and it allows you to register your own ClassVisitors to collect a class file.
With the Enhancer, use the generateClass(ClassVisitor) method and hand the latter method a ClassWriter. After calling the method, you can get the byte code from the class writer object that you passed.

here is the sample code to print pseudo code of generated CGLIB class.
visitEnd method prints the generated class in text format.
package naga.cglib.demo;
import static org.objectweb.asm.Opcodes.ASM7;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.util.TraceClassVisitor;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.FixedValue;
public class App {
public static void main(String[] args) throws Exception, IllegalArgumentException, InvocationTargetException {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(SampleClass.class);
enhancer.setCallback(new FixedValueImpl());
SampleClass proxy = (SampleClass) enhancer.create();
enhancer.generateClass(new CustomClassWriter());
System.out.println("Hello cglib!" + proxy.test(null));
}
}
class SampleClass {
public String test(String input) {
return "Hello world!";
}
}
class FixedValueImpl implements FixedValue {
#Override
public Object loadObject() throws Exception {
// TODO Auto-generated method stub
return "Hello cglib! from loadObject()";
}
}
class CustomClassWriter extends ClassVisitor {
TraceClassVisitor tracer;
PrintWriter pw = new PrintWriter(System.out);
public CustomClassWriter() {
super(ASM7);
tracer = new TraceClassVisitor(pw);
}
#Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
System.out.println("method name is :" + name);
return tracer.visitMethod(access, name, desc, signature, exceptions);
}
#Override
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
System.out.println("field name is :" + name);
return tracer.visitField(access, name, desc, signature, value);
}
public void visitEnd() {
tracer.visitEnd();
System.out.println(tracer.p.getText());
}
}

I've found this question while researching how to save the CGLIB-generated class in spring-boot 3.0 application (e.g. handling #Transactional or #Configuration-annotated classes). This simple approach may help:
import org.springframework.cglib.core.ReflectUtils;
...
public class SpringCglibUtils {
public static void initGeneratedClassHandler(String targetPath) {
File dir = new File(targetPath);
dir.mkdirs();
ReflectUtils.setGeneratedClassHandler((String className, byte[] classContent) -> {
try (FileOutputStream out = new FileOutputStream(new File(dir, className + ".class"))) {
out.write(classContent);
} catch (IOException e) {
throw new UncheckedIOException("Error while storing " + className, e);
}
});
}
}
and then define in your main class before creating context:
SpringCglibUtils.initGeneratedClassHandler("cglib");
Spring will store to the targetPath directory all generated class files.
Note: unfortunately it's not available before spring-boot 3

Related

How to extract pipeline dsl in the pipeline plugin with the Java?

I am developing a Jenkins pipeline plugin for CNB(buildpacks). I need to get the variables ​​in the pipeline script with Java but I still can't succeed.
This is the my pipeline script.
buildpacks {
builder = "some/builder"
}
And I can access these variables(like builder variable) ​​with Groovy language in the buildpacks.groovy
package dsl
// The call(body) method in any file in workflowLibs.git/vars is exposed as a
// method with the same name as the file.
def call(body) {
def config = [:]
body.resolveStrategy = Closure.DELEGATE_FIRST
body.delegate = config
body()
try {
echo "${config.builder}"
} catch (Exception rethrow) {
throw rethrow
}
}
But as i said i need to get these variables in Java.
Below is my class that I inherited from the GlobalVariable class.
public abstract class PipelineDSLGlobal extends GlobalVariable {
public abstract String getFunctionName();
#Override
public String getName() {
return getFunctionName();
}
#Override
public Object getValue(CpsScript script) throws Exception {
Binding binding = script.getBinding();
CpsThread c = CpsThread.current();
if (c == null)
throw new IllegalStateException("Expected to be called from CpsThread");
ClassLoader cl = getClass().getClassLoader();
String scriptPath = "dsl/" + getFunctionName() + ".groovy";
Reader r = new InputStreamReader(cl.getResourceAsStream(scriptPath), "UTF-8");
GroovyCodeSource gsc = new GroovyCodeSource(r, getFunctionName() + ".groovy", cl.getResource(scriptPath).getFile());
gsc.setCachable(true);
System.out.println(gsc.toString());
Object pipelineDSL = c.getExecution()
.getShell()
.getClassLoader()
.parseClass(gsc)
.getDeclaredConstructor()
.newInstance();
binding.setVariable(getName(), pipelineDSL);
r.close();
System.out.println("test");
return pipelineDSL;
}
}
And below is my class that i created for my buildpacksdsl.
package io.jenkins.plugins.buildpacks;
import hudson.Extension;
import io.jenkins.plugins.pipelinedsl.PipelineDSLGlobal;
import org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists.ProxyWhitelist;
import org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists.StaticWhitelist;
import java.io.IOException;
#Extension
public class BuildpacksDSL extends PipelineDSLGlobal {
#Override
public String getFunctionName() {
return "buildpacks";
}
#Extension
public static class MiscWhitelist extends ProxyWhitelist {
public MiscWhitelist() throws IOException {
super(new StaticWhitelist(
"method java.util.Map$Entry getKey",
"method java.util.Map$Entry getValue"
));
}
}
}
If you want to see the structure in more detail, you can take a look at the repository.
Can someone help me ? Thanks.
We found a little solution.
We created an instance of a class using compatibility between Groovy and Java.
And since we can already get values ​​with Groovy, we can pass parameters directly in the constructor method.
There is probably a more efficient method. But now it's working.
// Buildpacks.groovy
...
import io.jenkins.plugins.buildpacks.pipeline.BuildpacksDSL.BuildpacksPipelineDSL
class Buildpacks implements Serializable {
// first executed method is similar to main method in java
public void call(final Closure body) {
// the config array is the array that holds the variables.
def config = [:]
body.resolveStrategy = Closure.DELEGATE_FIRST
body.delegate = config
body()
// creating a new instance, when we give the 'config' array in the constructor, the variables is transferred.
BuildpacksPipelineDSL pipeline = new BuildpacksPipelineDSL(config)
pipeline.build()
}
}
...
// BuildpacksDSL.java
...
public static class BuildpacksPipelineDSL {
public BuildpacksPipelineDSL() {
}
/**
* This constructor takes dsl parameters, logger and envs from Jenkins and
* extracts them to local variables.
*
* #param c
* #throws Exception
*/
public BuildpacksPipelineDSL(LinkedHashMap<String, Object> c)
throws Exception {
// codes...
}
}
...

yaml serialization using yamlbeans and fxml properties

I'm trying to use YamlBeans to serialize fxml properties. Specificaly a Property. The class has a private property field and the fxml standard getter and setter methods but the information is not saved to the file when serialization occurs.
Entry point:
import java.io.IOException;
public class Main {
public static void main(String[] args) throws IOException{
Person person = new Person(5);
YamlSerializer.serialize(person, System.getProperty("user.dir") + "/person.yml");
}
}
Person.java
import javafx.beans.property.Property;
import javafx.beans.property.SimpleDoubleProperty;
public class Person{
private Property<Number> age;
public Person(){
age = new SimpleDoubleProperty();
age.setValue(3);
}
public Person(Number age){
this.age = new SimpleDoubleProperty(age.doubleValue());
}
public Property<Number> ageProperty() {
return this.age;
}
public Number getAge() {
return this.ageProperty().getValue();
}
public void setAge(final Number age) {
this.ageProperty().setValue(age);
}
}
YamlSerializer.java
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import com.esotericsoftware.yamlbeans.YamlReader;
import com.esotericsoftware.yamlbeans.YamlWriter;
public class YamlSerializer {
public static void serialize(Object object, String path) throws IOException{
File file = new File(path);
if(!file.exists())
file.getParentFile().mkdirs();
YamlWriter writer = new YamlWriter(new FileWriter(path));
writer.write(object);
writer.close();
}
public static Object deserialize(String path) throws IOException{
File file = new File(path);
if(!file.exists()){
if(!file.getParentFile().exists())
if (!file.getParentFile().mkdirs()){
System.out.println("Error creating files");
}
}
YamlReader reader = new YamlReader(new FileReader(path));
return reader.read();
}
}
Output file person.yml:
!Person {}
Your code looks OK. What does Beans getProperties return for your class? This is what YamlWriter uses.
Turns out Beans is finding all the properties, then looking for get/set methods. It looks like it should find your number field and then your setNumber and getNumber methods, but you didn't provide the full class code.
I suggest providing an SSCCE. Trying to help someone without that is a shot in the dark and a time waste.
Edit: The way Beans looks for fields, then a matching setter/getter, Property<Number> age doesn't work. Beans looks for a setter/getter of type Property<Number> and doesn't find them. IIRC it used to use Introspector, but that had to be ripped out because it was missing from Android.
To fix this you would need to patch Beans to be smarter about finding setter/getters without a corresponding field. A PR that does that would be merged.

Loading Class from Jar File : java.lang.InstantiationException

I got this code to instantiate a Life class from jar file:
import com.life.Life;
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
public class Main {
public static void main(String[] args) {
try{
File file = new File("Life.jar");
URL url = file.toURI().toURL();
URL[] urls = new URL[]{url};
ClassLoader cl = new URLClassLoader(urls);
Class cls = cl.loadClass("com.life.Life");
Life life = (Life) cls.newInstance();
System.out.println("Message: "+life.getMessage());
}catch(Exception e){
e.printStackTrace();
}
}
}
Here's the content of the Life.jar
public class Life {
public String getMessage(){
return "Life is Beautiful!";
}
}
Here's my interface name Life
package com.life;
public interface Life {
public String getMessage();
}
The code above will throw an error:
java.lang.InstantiationException: com.life.Life
at java.lang.Class.newInstance0(Class.java:340)
at java.lang.Class.newInstance(Class.java:308)
at com.Main.main(Main.java:20)
BUILD SUCCESSFUL (total time: 0 seconds)
What's wrong with the code? How to resolve this?
This happened because your interface is also named Life ( java tried to instantiate an interface). Change public interface Life to public interface LifeInterface and then have your class Life implement that like :
public class Life implements LifeInterface
{
#Override
public String getMessage()
{
return "Life is Beautiful!";
}
}

I am trying to learn java asm framework for bytecode instrumentation but not able to find sufficient docs or tutorials on it

I am trying to learn java asm framework for bytecode instrumentation but not able to find sufficient docs or tutorials on it.
I have studied about ClassReader, ClassWriter and ClassVisitor and some more alike APIs but not very clear about how to implement those and how to write corresponding adapters.
Lets say I have a HelloWorld java class.
public class HelloWorld {
public static void main(String[] args) {
//some code.....
}
}
Now I want to insert a variable "int i =10;" in the bytecode. Please give me idea about what Adapter/program should I write.
Thanks in advance!
Following is a way to add additional fields to a class such as "int i = 10;".
Assuming that you are using javaagent to perform instrumentation:
1) Use the following as the premain class of the java agent
import java.lang.instrument.Instrumentation;
public class SimpleAgent {
public static void premain(String agentArgs, Instrumentation inst) {
ClassTransformer transformer = new ClassTransformer();
inst.addTransformer(transformer);
}
}
2) addTransformer calls the transform method of ClassTransformer class which is defined as follows
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
public class ClassTransformer implements ClassFileTransformer{
public byte[] transform(ClassLoader loader,
String className,
Class classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] b)
throws IllegalClassFormatException {
try
{
ClassReader cr=new ClassReader(b);
ClassWriter cw = new ClassWriter(cr,ClassWriter.COMPUTE_MAXS);
AddField cp = new AddField(cw);
cr.accept(cp,0);
return cw.toByteArray();
}
catch(Exception e)
{
System.out.println(e);
}
return b;
}
}
3) finally AddField which is as follows is the ClassVisitor that is responsible to add a new field to the class
import static org.objectweb.asm.Opcodes.ASM4;
import org.objectweb.asm.ClassVisitor;
class AddField extends ClassVisitor{
static String className;
static String methName, descrip;
public AddField(ClassVisitor cv) {
super(ASM4, cv);
}
#Override
public void visit(int version, int access, String name,
String signature, String superName, String[] interfaces) {
className = name;
cv.visit(version, access, name, signature, superName, interfaces);
}
public void visitEnd() {
cv.visitField(0, "i", "I", null , new Integer(10));
cv.visitEnd();
}
}
4. ** NEW EDIT ** for adding a variable into a method. The variable has to be stored into a temporary variable and can then be used later. Following adapter can be used for the purpose (look at onMethodEnter):
import static org.objectweb.asm.Opcodes.ASM4;
import static org.objectweb.asm.Opcodes.*;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.commons.AdviceAdapter;
public class MethodAdapter extends ClassVisitor {
public MethodAdapter(ClassVisitor cv) {
super(ASM4, cv);
}
#Override
public void visit(int version, int access, String name,
String signature, String superName, String[] interfaces) {
cv.visit(version, access, name, signature, superName, interfaces);
}
public MethodVisitor visitMethod(int access, String name,
String desc, String signature, String[] exceptions) {
MethodVisitor mv;
mv = cv.visitMethod(access, name, desc, signature, exceptions);
mv = new AddVariableAdapter(access, name, desc, mv);
return mv;
}
public void visitEnd() {
cv.visitEnd();
}
public class AddVariableAdapter extends AdviceAdapter{
public AddCallAdapter(int access, String name, String desc,
MethodVisitor mv) {
super(ASM4, mv, access, name, desc);
}
protected void onMethodEnter() {
mv.visitIntInsn(BIPUSH, 10); // pushes the number 10 on to the stack
mv.visitVarInsn(ISTORE, 1); // pops the top of the stack into a local variable indexed by 1
/* code to print the local variable
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitVarInsn(ILOAD, 1);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(I)V");*/
}
}
}
A good way to find out how to use ASM is by running the ASMifier tool.
If you just want to know how certain language constructors such as variable initializers are converted to bytecode, it might be helpful to create a simple Java class, compile it, locate its .class file and run javap on it or open it with an IDE.

Parameterized test case classes in JUnit 3.x

I have a JUnit 3.x TestCase which I would like to be able to parameterize. I'd like to parametrize the entire TestCase (including the fixture). However, the TestSuite.addTestSuite() method does not allow be to pass a TestCase object, just a class:
TestSuite suite = new TestSuite("suite");
suite.addTestSuite(MyTestCase.class);
I would like to be able to pass a parameter (a string) to the MyTestCase instance which is created when the test runs. As it is now, I have to have a separate class for each parameter value.
I tried passing it an anynomous subclass:
MyTestCase testCase = new MyTestCase() {
String getOption() {
return "some value";
}
}
suite.addTestSuite(testCase.getClass());
However, this fails with the assertion:
... MyTestSuite$1 has no public constructor TestCase(String name) or TestCase()`
Any ideas? Am I attacking the problem the wrong way?
If this is Java 5 or higher, you might want to consider switching to JUnit 4, which has support for parameterized test cases built in.
Rather than create a parameterized test case for the multiple/different backends you want to test against, I would look into making my test cases abstract. Each new implementation of your API would need to supply an implementing TestCase class.
If you currently have a test method that looks something like
public void testSomething() {
API myAPI = new BlahAPI();
assertNotNull(myAPI.something());
}
just add an abstract method to the TestCase that returns the specific API object to use.
public abstract class AbstractTestCase extends TestCase {
public abstract API getAPIToTest();
public void testSomething() {
API myAPI = getAPIToTest();
assertNotNull(myAPI.something());
}
public void testSomethingElse() {
API myAPI = getAPIToTest();
assertNotNull(myAPI.somethingElse());
}
}
Then the TestCase for the new implementation you want to test only has to implement your AbstractTestCase and supply the concrete implementation of the API class:
public class ImplementationXTestCase extends AbstractTestCase{
public API getAPIToTest() {
return new ImplementationX();
}
}
Then all of the test methods that test the API in the abstract class are run automatically.
Ok, here is a quick mock-up of how JUnit 4 runs parameterized tests, but done in JUnit 3.8.2.
Basically I'm subclassing and badly hijacking the TestSuite class to populate the list of tests according to the cross-product of testMethods and parameters.
Unfortunately I've had to copy a couple of helper methods from TestSuite itself, and a few details are not perfect, such as the names of the tests in the IDE being the same across parameter sets (JUnit 4.x appends [0], [1], ...).
Nevertheless, this seems to run fine in the text and AWT TestRunners that ship with JUnit as well as in Eclipse.
Here is the ParameterizedTestSuite, and further down a (silly) example of a parameterized test using it.
(final note : I've written this with Java 5 in mind, it should be trivial to adapt to 1.4 if needed)
ParameterizedTestSuite.java:
package junit.parameterized;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
public class ParameterizedTestSuite extends TestSuite {
public ParameterizedTestSuite(
final Class<? extends TestCase> testCaseClass,
final Collection<Object[]> parameters) {
setName(testCaseClass.getName());
final Constructor<?>[] constructors = testCaseClass.getConstructors();
if (constructors.length != 1) {
addTest(warning(testCaseClass.getName()
+ " must have a single public constructor."));
return;
}
final Collection<String> names = getTestMethods(testCaseClass);
final Constructor<?> constructor = constructors[0];
final Collection<TestCase> testCaseInstances = new ArrayList<TestCase>();
try {
for (final Object[] objects : parameters) {
for (final String name : names) {
TestCase testCase = (TestCase) constructor.newInstance(objects);
testCase.setName(name);
testCaseInstances.add(testCase);
}
}
} catch (IllegalArgumentException e) {
addConstructionException(e);
return;
} catch (InstantiationException e) {
addConstructionException(e);
return;
} catch (IllegalAccessException e) {
addConstructionException(e);
return;
} catch (InvocationTargetException e) {
addConstructionException(e);
return;
}
for (final TestCase testCase : testCaseInstances) {
addTest(testCase);
}
}
private Collection<String> getTestMethods(
final Class<? extends TestCase> testCaseClass) {
Class<?> superClass= testCaseClass;
final Collection<String> names= new ArrayList<String>();
while (Test.class.isAssignableFrom(superClass)) {
Method[] methods= superClass.getDeclaredMethods();
for (int i= 0; i < methods.length; i++) {
addTestMethod(methods[i], names, testCaseClass);
}
superClass = superClass.getSuperclass();
}
return names;
}
private void addTestMethod(Method m, Collection<String> names, Class<?> theClass) {
String name= m.getName();
if (names.contains(name))
return;
if (! isPublicTestMethod(m)) {
if (isTestMethod(m))
addTest(warning("Test method isn't public: "+m.getName()));
return;
}
names.add(name);
}
private boolean isPublicTestMethod(Method m) {
return isTestMethod(m) && Modifier.isPublic(m.getModifiers());
}
private boolean isTestMethod(Method m) {
String name= m.getName();
Class<?>[] parameters= m.getParameterTypes();
Class<?> returnType= m.getReturnType();
return parameters.length == 0 && name.startsWith("test") && returnType.equals(Void.TYPE);
}
private void addConstructionException(Exception e) {
addTest(warning("Instantiation of a testCase failed "
+ e.getClass().getName() + " " + e.getMessage()));
}
}
ParameterizedTest.java:
package junit.parameterized;
import java.util.Arrays;
import java.util.Collection;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.parameterized.ParameterizedTestSuite;
public class ParameterizedTest extends TestCase {
private final int value;
private int evilState;
public static Collection<Object[]> parameters() {
return Arrays.asList(
new Object[] { 1 },
new Object[] { 2 },
new Object[] { -2 }
);
}
public ParameterizedTest(final int value) {
this.value = value;
}
public void testMathPow() {
final int square = value * value;
final int powSquare = (int) Math.pow(value, 2) + evilState;
assertEquals(square, powSquare);
evilState++;
}
public void testIntDiv() {
final int div = value / value;
assertEquals(1, div);
}
public static Test suite() {
return new ParameterizedTestSuite(ParameterizedTest.class, parameters());
}
}
Note: the evilState variable is just here to show that all test instances are different as they should be, and that there is no shared state between them.
a few details are not perfect, such as the names of the tests in the IDE being the same across parameter sets (JUnit 4.x appends [0], [1], ...).
To solve this you just need to overwrite getName() and change the constructor in your test case class:
private String displayName;
public ParameterizedTest(final int value) {
this.value = value;
this.displayName = Integer.toString(value);
}
#Override
public String getName() {
return super.getName() + "[" + displayName + "]";
}
For Android projects, we wrote a library called Burst for test parameterization. For example
public class ParameterizedTest extends TestCase {
enum Drink { COKE, PEPSI, RC_COLA }
private final Drink drink;
// Nullary constructor required by Android test framework
public ConstructorTest() {
this(null);
}
public ConstructorTest(Drink drink) {
this.drink = drink;
}
public void testSomething() {
assertNotNull(drink);
}
}
Not really an answer to your question since you're not using Android, but a lot of projects which still use JUnit 3 do so because Android's test framework requires it, so I hope some other readers will find this helpful.

Categories