Hot‑Deploying User‑Defined Interfaces in Spring Boot: Reflection vs Annotation

This article demonstrates how to design a simple Calculator interface, provide two implementation strategies (annotation‑managed and reflection‑based), and achieve hot deployment of user‑supplied JARs in a Spring Boot application, including loading, registering, and removing beans dynamically.

macrozheng
macrozheng
macrozheng
Hot‑Deploying User‑Defined Interfaces in Spring Boot: Reflection vs Annotation

During system development a requirement arose to expose an interface that users can implement, package as a JAR, upload to the system, and have the system hot‑deploy the new implementation and switch to it at runtime.

1 Define a Simple Interface

We use a basic calculator example:

public interface Calculator {
    int calculate(int a, int b);
    int add(int a, int b);
}

2 A Simple Implementation

Two implementation approaches are shown: one managed by Spring annotations and one using pure reflection.

@Service
public class CalculatorImpl implements Calculator {
    @Autowired
    CalculatorCore calculatorCore;

    /** Annotation‑based */
    @Override
    public int calculate(int a, int b) {
        int c = calculatorCore.add(a, b);
        return c;
    }

    /** Reflection‑based */
    @Override
    public int add(int a, int b) {
        return new CalculatorCore().add(a, b);
    }
}

The CalculatorCore bean is injected to verify that the annotation mode can fully construct the bean dependency graph and register it in the current Spring container.

@Service
public class CalculatorCore {
    public int add(int a, int b) {
        return a + b;
    }
}

3 Reflection‑Based Hot Deployment

Users upload a JAR to a predefined directory. The JAR path and address are defined as:

private static String jarAddress = "E:/zzq/IDEA_WS/CalculatorTest/lib/Calculator.jar";
private static String jarPath = "file:/" + jarAddress;

The system loads the JAR with a thread‑context class loader, obtains the implementation class by its fully‑qualified name, creates an instance via reflection, and invokes the method:

/**
 * Hot‑load Calculator implementation (reflection mode)
 */
public static void hotDeployWithReflect() throws Exception {
    URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL(jarPath)}, Thread.currentThread().getContextClassLoader());
    Class clazz = urlClassLoader.loadClass("com.nci.cetc15.calculator.impl.CalculatorImpl");
    Calculator calculator = (Calculator) clazz.newInstance();
    int result = calculator.add(1, 2);
    System.out.println(result);
}

4 Annotation‑Based Hot Deployment

If the uploaded JAR contains Spring‑managed beans, the system scans all classes, identifies those annotated with Spring stereotypes, and registers them dynamically in the current Spring container.

/**
 * Dynamically register beans from the uploaded JAR, including dependencies
 */
public static void hotDeployWithSpring() throws Exception {
    Set<String> classNameSet = DeployUtils.readJarFile(jarAddress);
    URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL(jarPath)}, Thread.currentThread().getContextClassLoader());
    for (String className : classNameSet) {
        Class clazz = urlClassLoader.loadClass(className);
        if (DeployUtils.isSpringBeanClass(clazz)) {
            BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
            defaultListableBeanFactory.registerBeanDefinition(DeployUtils.transformName(className), beanDefinitionBuilder.getBeanDefinition());
        }
    }
}

Utility methods used in the process:

public static Set<String> readJarFile(String jarAddress) throws IOException {
    Set<String> classNameSet = new HashSet<>();
    JarFile jarFile = new JarFile(jarAddress);
    Enumeration<JarEntry> entries = jarFile.entries(); // iterate the JAR
    while (entries.hasMoreElements()) {
        JarEntry jarEntry = entries.nextElement();
        String name = jarEntry.getName();
        if (name.endsWith(".class")) {
            String className = name.replace(".class", "").replaceAll("/", ".");
            classNameSet.add(className);
        }
    }
    return classNameSet;
}

public static boolean isSpringBeanClass(Class<?> cla) {
    if (cla == null) return false;
    if (cla.isInterface()) return false;
    if (Modifier.isAbstract(cla.getModifiers())) return false;
    if (cla.getAnnotation(Component.class) != null) return true;
    if (cla.getAnnotation(Repository.class) != null) return true;
    if (cla.getAnnotation(Service.class) != null) return true;
    return false;
}

public static String transformName(String className) {
    String tmpstr = className.substring(className.lastIndexOf('.') + 1);
    return tmpstr.substring(0,1).toLowerCase() + tmpstr.substring(1);
}

When a JAR is removed, the previously registered beans must also be deregistered to keep the Spring context consistent.

/**
 * Remove beans registered from the JAR when it is deleted
 */
public static void delete() throws Exception {
    Set<String> classNameSet = DeployUtils.readJarFile(jarAddress);
    URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL(jarPath)}, Thread.currentThread().getContextClassLoader());
    for (String className : classNameSet) {
        Class clazz = urlClassLoader.loadClass(className);
        if (DeployUtils.isSpringBeanClass(clazz)) {
            defaultListableBeanFactory.removeBeanDefinition(DeployUtils.transformName(className));
        }
    }
}

5 Test

A test class simulates the user uploading a JAR. It repeatedly attempts hot deployment, catching exceptions and waiting before retrying.

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
while (true) {
    try {
        hotDeployWithReflect();
        // hotDeployWithSpring();
        // delete();
    } catch (Exception e) {
        e.printStackTrace();
        Thread.sleep(1000 * 10);
    }
}
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

javaReflectionSpring Bootdependency-injectionHot Deployment
macrozheng
Written by

macrozheng

Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.

0 followers
Reader feedback

How this landed with the community

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.