Hot Deployment of Custom Java Interface Implementations Using Reflection and Spring
This article demonstrates how to design a simple Calculator interface, provide two implementation strategies (annotation‑based Spring beans and reflection‑based classes), and dynamically load, register, and unload user‑supplied JAR files at runtime through hot‑deployment mechanisms in a Java backend system.
During development a requirement arose to allow users to upload a JAR that contains their own implementation of a predefined interface, and for the system to hot‑deploy and switch to that implementation without restarting.
Define a Simple Interface
The example uses a basic Calculator interface with two methods:
public interface Calculator {
int calculate(int a, int b);
int add(int a, int b);
}Simple Implementation
Two implementation styles are considered: an annotation‑based approach managed by Spring and a reflection‑based approach that does not depend on Spring. The calculate method uses the annotation style, while add uses reflection.
@Service
public class CalculatorImpl implements Calculator {
@Autowired
CalculatorCore calculatorCore;
/** annotation style */
@Override
public int calculate(int a, int b) {
int c = calculatorCore.add(a, b);
return c;
}
/** reflection style */
@Override
public int add(int a, int b) {
return new CalculatorCore().add(a, b);
}
}The CalculatorCore bean provides the actual addition logic:
@Service
public class CalculatorCore {
public int add(int a, int b) {
return a + b;
}
}Reflection‑Based Hot Deployment
Users place their JAR at a known directory. The system loads the JAR via a URLClassLoader, obtains the implementation class by its fully‑qualified name, creates an instance via reflection, and invokes the methods.
private static String jarAddress = "E:/zzq/IDEA_WS/CalculatorTest/lib/Calculator.jar";
private static String jarPath = "file:/" + jarAddress;
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
If the uploaded JAR contains Spring‑managed beans, the system scans all classes in the JAR, identifies those annotated with @Component, @Service, or @Repository, and registers them dynamically into the existing 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());
}
}
}The utility class DeployUtils provides methods to read JAR entries, determine if a class is a Spring bean, and transform class names to bean IDs.
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;
return cla.getAnnotation(Component.class) != null ||
cla.getAnnotation(Repository.class) != null ||
cla.getAnnotation(Service.class) != null;
}
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 Deleting a JAR
When a JAR is removed or replaced, the previously registered beans must be deregistered from the Spring container using the same bean IDs.
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));
}
}
}Testing
A test class simulates the upload process by repeatedly attempting hot deployment; if the JAR is not yet present, an exception is caught and the thread sleeps 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);
}
}The article concludes with source attribution and promotional links.
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.
Selected Java Interview Questions
A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!
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.
