Backend Development 8 min read

How to Hot‑Deploy Custom Java Interfaces with Spring Boot and Reflection

This guide demonstrates how to enable hot deployment of user‑provided Java interface implementations in a Spring Boot application, covering both annotation‑based and reflection‑based approaches, jar uploading, dynamic bean registration, class loading, and cleanup, with complete code examples.

macrozheng
macrozheng
macrozheng
How to Hot‑Deploy Custom Java Interfaces with Spring Boot and Reflection

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

1 Define Simple Interface

<code>public interface Calculator {
    int calculate(int a, int b);
    int add(int a, int b);
}</code>

2 Simple Implementation (Annotation and Reflection)

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

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

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

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

3 Reflection‑Based Hot Deployment

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

/** Hot load Calculator implementation via 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);
}</code>

4 Annotation‑Based Hot Deployment

<code>/** Register beans from JAR into Spring container */
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());
        }
    }
}

/** Read all class files from 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 if a class has a Spring annotation */
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 bean name (lowercase first letter) */
public static String transformName(String className) {
    String tmpstr = className.substring(className.lastIndexOf(".") + 1);
    return tmpstr.substring(0, 1).toLowerCase() + tmpstr.substring(1);
}

/** Delete beans when JAR is removed */
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));
        }
    }
}
</code>

5 Test

<code>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);
    }
}</code>
JavareflectionSpring BootHot DeploymentDynamic Bean Registration
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

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.