How to Hot‑Deploy User‑Provided JARs with Spring and Reflection

This guide explains how to let users upload a JAR that implements a predefined interface, then hot‑deploy the new implementation at runtime using either Spring annotation‑based bean registration or pure Java reflection, complete with utility methods for loading, registering, and cleaning up beans.

Su San Talks Tech
Su San Talks Tech
Su San Talks Tech
How to Hot‑Deploy User‑Provided JARs with Spring and Reflection

During a recent project a requirement arose to allow users to provide their own implementation of a given interface, package it as a JAR, upload it to the system, and have the system hot‑deploy and switch to the new implementation.

Define a simple interface

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

A simple implementation of the interface

The implementation can be provided in two ways: using Spring’s annotation‑based management (annotation mode) or without Spring (reflection mode).

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

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

    /** reflection mode */
    @Override
    public int add(int a, int b) {
        return new CalculatorCore().add(a, b);
    }
}

The CalculatorCore bean is used to verify that, in annotation mode, the full bean dependency graph can be constructed and registered in the current Spring container.

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

Reflection method hot deployment

Users upload the JAR to a predefined directory. The path variables are defined as follows:

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

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

/** Hot‑load Calculator implementation using reflection */
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);
}

Annotation method hot deployment

If the uploaded JAR contains Spring components, the system scans all classes, registers those annotated with Spring stereotypes into the current Spring container, effectively performing dynamic bean registration.

/** Dynamically register beans from the uploaded JAR */
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());
        }
    }
}

The DeployUtils class provides helper methods for reading JAR entries, detecting Spring‑annotated classes, and generating bean names.

/** Read all class files from a JAR */
public static Set<String> readJarFile(String jarAddress) throws IOException {
    Set<String> classNameSet = new HashSet<>();
    JarFile jarFile = new JarFile(jarAddress);
    Enumeration<JarEntry> entries = jarFile.entries();
    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;
}

/** Determine whether a class has a Spring stereotype */
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;
}

/** Convert class name to Spring bean name (lower‑case first letter) */
public static String transformName(String className) {
    String tmp = className.substring(className.lastIndexOf('.') + 1);
    return tmp.substring(0, 1).toLowerCase() + tmp.substring(1);
}

Delete JAR and remove beans from Spring container

When a JAR is removed or switched, the previously registered beans must also be removed from the Spring context.

/** Delete beans registered from a JAR */
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));
        }
    }
}

Test

A test class simulates the user uploading a JAR. It repeatedly attempts hot deployment and sleeps when the JAR is not yet present.

public static void main(String[] args) throws Exception {
    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.

JavaReflectionspringDynamic LoadingHot DeploymentJAR
Su San Talks Tech
Written by

Su San Talks Tech

Su San, former staff at several leading tech companies, is a top creator on Juejin and a premium creator on CSDN, and runs the free coding practice site www.susan.net.cn.

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.