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.
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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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
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.
