Hot‑Deploy Custom Java Interfaces with JARs Using Spring and Reflection

This tutorial demonstrates how to let users upload a JAR that implements a predefined Java interface, then hot‑replace the implementation at runtime via reflection or Spring bean registration, covering interface definition, concrete classes, dynamic class loading, bean management, and testing.

Code Ape Tech Column
Code Ape Tech Column
Code Ape Tech Column
Hot‑Deploy Custom Java Interfaces with JARs Using Spring and Reflection

During development 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.

1. Define a simple interface

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

2. A basic implementation

The implementation supports two invocation modes: an annotation‑based mode managed by Spring and a reflection‑based mode that does not depend on Spring. The calculate method is used for the annotation mode, while add is used for the 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 class provides the actual addition logic and is injected to verify that the Spring container can fully construct the bean dependency graph.

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

3. Hot deployment using reflection

Users place the JAR in a predefined directory. The system loads the JAR with a URLClassLoader, obtains the fully‑qualified class name of the implementation, creates an instance via reflection, and invokes the desired 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. Hot deployment using Spring annotations

If the uploaded JAR contains Spring components, the system scans all classes, detects those annotated with Spring stereotypes, and registers them dynamically into the current Spring container, effectively performing class hot‑loading plus dynamic bean registration.

/**
 * After adding a JAR, dynamically register Spring beans, including their 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());
        }
    }
}

The utility class DeployUtils provides methods to read all class files from a JAR, determine whether a class carries Spring annotations, and generate 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 if a class is a Spring bean */
public static boolean isSpringBeanClass(Class<?> cla) {
    if (cla == null || cla.isInterface() || Modifier.isAbstract(cla.getModifiers())) {
        return false;
    }
    return cla.getAnnotation(Component.class) != null ||
           cla.getAnnotation(Repository.class) != null ||
           cla.getAnnotation(Service.class) != null;
}

/** Convert class name to 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);
}

5. Removing a JAR

When a JAR is replaced or deleted, the beans that were registered from that JAR must be removed from the Spring container. The removal process mirrors the registration process, using the same application context.

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

6. Test harness

A simple test class simulates the user uploading a JAR. It repeatedly attempts hot deployment, catching exceptions until the JAR appears in the designated directory.

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);
    }
}

The article ends with a brief personal note, which is omitted here as it is unrelated to the technical content.

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.

ReflectionspringDynamic LoadingHot DeploymentJar
Code Ape Tech Column
Written by

Code Ape Tech Column

Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.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.