How to Hot‑Deploy User‑Provided JAR Implementations with Spring and Reflection
This article walks through building a Java interface, providing two implementation styles (Spring‑managed and pure‑reflection), loading user‑uploaded JARs at runtime, dynamically registering or removing beans in the Spring container, and testing the whole hot‑deployment cycle with concrete code examples.
Define a Simple Interface
We start with a minimal calculator interface that declares two methods, int calculate(int a, int b) and int add(int a, int b):
public interface Calculator {
int calculate(int a, int b);
int add(int a, int b);
}Two Implementation Strategies
The author distinguishes two ways a user may implement the interface:
Annotation mode – the implementation is a Spring bean and will be injected by the container.
Reflection mode – the implementation is instantiated manually via reflection.
Both implementations are shown below.
@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 auxiliary CalculatorCore class contains the actual arithmetic logic:
@Service
public class CalculatorCore {
public int add(int a, int b) {
return a + b;
}
}Reflection‑Based Hot Deployment
When a user uploads a JAR, the system records the JAR's absolute path ( jarAddress) and builds a file:/ URL ( jarPath). The fully‑qualified class name of the implementation is supplied by the user. The following method loads the JAR with a URLClassLoader, obtains the Class object, creates an instance, and invokes add:
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‑Based Hot Deployment (Spring)
If the uploaded JAR contains Spring‑managed beans, the system must scan every class in the JAR, detect Spring annotations, and register the bean definitions into the current ApplicationContext. The loading of the JAR is identical to the reflection case; the difference lies in the post‑processing loop:
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 three key methods: readJarFile(String jarAddress) – iterates over the JAR entries, collects fully‑qualified class names, and returns a Set<String> of them. isSpringBeanClass(Class<?> clazz) – returns true if the class is not an interface or abstract class and carries a Spring stereotype annotation ( @Component, @Repository, or @Service). transformName(String className) – converts the class name's simple name to a bean name by lower‑casing the first character (e.g., CalculatorImpl → calculatorImpl).
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;
}
public static boolean isSpringBeanClass(Class<?> cla) {
if (cla == null || cla.isInterface() || 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 tmp = className.substring(className.lastIndexOf(".") + 1);
return tmp.substring(0,1).toLowerCase() + tmp.substring(1);
}Removing Beans When a JAR Is Deleted
When a JAR is replaced or removed, the previously registered beans must be deregistered from the same Spring context. The removal mirrors the registration logic:
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 Harness
A simple test program simulates the user uploading the JAR. It creates a Spring ApplicationContext, obtains the bean factory, and repeatedly attempts hot deployment until the JAR appears in the designated directory. If the JAR is missing, an exception is caught, printed, and the thread sleeps for ten seconds 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);
}
}Overall, the article demonstrates a complete workflow for dynamic, hot‑replaceable plugin development in a Java/Spring backend, covering interface definition, dual implementation paths, class‑loader manipulation, Spring bean registration/removal, and a practical test loop.
Java Web Project
Focused on Java backend technologies, trending internet tech, and the latest industry developments. The platform serves over 200,000 Java developers, inviting you to learn and exchange ideas together. Check the menu for Java learning resources.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
