Hot Deployment of Java Interface Implementations Using Jar Packages with Spring and Reflection
This article demonstrates how to design a simple Calculator interface, provide two implementation strategies (Spring annotation and reflection), and achieve hot deployment by uploading JAR files, dynamically loading classes, registering or removing beans in the Spring container, and testing the runtime behavior.
During recent development a requirement arose to allow users to upload a JAR containing an implementation of a predefined interface, which the system should hot‑deploy and switch to at runtime.
Define a Simple Interface
We start with a basic Calculator interface:
public interface Calculator {
int calculate(int a, int b);
int add(int a, int b);
}A Simple Implementation
The implementation supports two modes: an annotation‑based mode managed by Spring and a reflection‑based mode. The calculate method uses the annotation mode, while add uses reflection.
@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 supporting CalculatorCore class provides the actual addition logic:
@Service
public class CalculatorCore {
public int add(int a, int b) {
return a + b;
}
}Reflection‑Based Hot Deployment
Users upload a JAR to a predefined 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 its 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, identifies those annotated with @Component , @Service , or @Repository , and registers them into the current Spring context dynamically.
public static void hotDeployWithSpring() throws Exception {
Set
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());
}
}
}Utility Methods
DeployUtils.readJarFile reads all class entries from a JAR and returns their fully‑qualified names. isSpringBeanClass checks whether a class carries Spring stereotypes. transformName converts a class name to a bean name with a lowercase first letter.
public static Set
readJarFile(String jarAddress) throws IOException {
Set
classNameSet = new HashSet<>();
JarFile jarFile = new JarFile(jarAddress);
Enumeration
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 tmpstr = className.substring(className.lastIndexOf(".") + 1);
return tmpstr.substring(0, 1).toLowerCase() + tmpstr.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 names:
public static void delete() throws Exception {
Set
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. It repeatedly attempts hot deployment, sleeping for 10 seconds when the JAR is not yet present, allowing manual placement of the JAR for verification.
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 by inviting readers to discuss the approach, share questions, and join a community of architects for further exchange.
Top Architect
Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.
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.