Backend Development 5 min read

Modifying Bytecode Before Class Loading in Spring Cloud Using Javassist

This article demonstrates how to use Spring's ApplicationContextInitializer together with Javassist to intercept class loading in a Spring Cloud environment, modify the bytecode of org.apache.commons.lang3.RandomStringUtils, and record method calls, handling parent‑child container initialization nuances.

Cognitive Technology Team
Cognitive Technology Team
Cognitive Technology Team
Modifying Bytecode Before Class Loading in Spring Cloud Using Javassist

In Spring Cloud projects many features are implemented via AOP or Java agents. When neither approach is feasible, developers can directly manipulate bytecode using Javassist to achieve the desired behavior before a class is loaded.

To perform bytecode modification early, Spring provides the ApplicationContextInitializer extension point. In a Spring Cloud setup with parent and child containers, this initializer runs twice, so the modification should be applied during the child container initialization.

The following abstract base class defines the initializer framework. Subclasses implement the doJavassist method to apply specific bytecode changes.

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;

import java.io.IOException;
import java.util.*;

/**
 * @author:
 * date: 2088/15/19
 */
public abstract class BaseApplicationContextInitializer implements ApplicationContextInitializer {
    protected Logger log = LoggerFactory.getLogger(getClass());
    private static volatile int initialized = 0;

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        Optional
optional = Arrays.stream(applicationContext.getEnvironment().getActiveProfiles())
            .filter(t -> "online".equals(t) || "unittest".equals(t))
            .findFirst();
        if (optional.isPresent()) {
            return;
        }
        if (initialized == 1) {
            log.info(getClass().getSimpleName() + " begin");
            try {
                doJavassist();
                log.info(getClass().getSimpleName() + " end");
            } catch (Throwable e) {
                log.error(getClass().getSimpleName(), e);
                throw new RuntimeException(e);
            }
            initialized += 1;
        }
        if (initialized == 0) {
            initialized += 1;
        }
    }

    protected abstract void doJavassist() throws Exception;
}

The concrete implementation below modifies the random method of Apache Commons RandomStringUtils . It creates a copy of the original method, renames it, and injects a new method body that records the invocation and result via a hypothetical RecordUtils.record utility.

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewMethod;

import java.util.Random;

/**
 * @author:
 * date: 2088/15/19
 */
public class RandomStringUtilsAop extends BaseApplicationContextInitializer {

    @Override
    protected void doJavassist() throws Exception {
        ClassPool classPool = ClassPool.getDefault();
        classPool.importPackage("java.util.Random");
        CtClass randomStringUtilsClass = classPool.get("org.apache.commons.lang3.RandomStringUtils");
        CtClass[] paramsClasses = new CtClass[]{
            classPool.get(int.class.getName()),
            classPool.get(int.class.getName()),
            classPool.get(int.class.getName()),
            classPool.get(boolean.class.getName()),
            classPool.get(boolean.class.getName()),
            classPool.get(char[].class.getName()),
            classPool.get(Random.class.getName())
        };
        CtMethod ctMethod = randomStringUtilsClass.getDeclaredMethod("random", paramsClasses);
        CtMethod srcInvoke = CtNewMethod.copy(ctMethod, randomStringUtilsClass, null);
        srcInvoke.setName("srcInvoke");
        randomStringUtilsClass.addMethod(srcInvoke);
        ctMethod.setBody("{\n"
            + " String className = \"org.apache.commons.lang3.RandomStringUtils\";\n"
            + " String methodName = \"random\";\n"
            + " String result = null;\n"
            + " Throwable ex = null;\n"
            + "\n"
            + " try {\n"
            + " result = srcInvoke($$);\n"
            + " } catch (RuntimeException e) {\n"
            + " ex = e;\n"
            + " }\n"
            + "\n"
            + " RecordUtils.record(className, methodName, result, ex);\n"
            + " if (ex != null) {\n"
            + " throw ex;\n"
            + " }\n"
            + " return result;\n"
            + " }");
        randomStringUtilsClass.toClass(); // load modified class, ensure it was not loaded before
        if (randomStringUtilsClass.isFrozen()) {
            randomStringUtilsClass.defrost();
        }
    }
}

By placing this initializer in the Spring Cloud application, the bytecode of RandomStringUtils.random is altered before the class is first used, enabling custom recording or monitoring logic without relying on AOP or external agents.

JavaAOPSpring CloudJavassistBytecode Manipulationapplicationcontextinitializer
Cognitive Technology Team
Written by

Cognitive Technology Team

Cognitive Technology Team regularly delivers the latest IT news, original content, programming tutorials and experience sharing, with daily perks awaiting you.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.