Stop Manually Deploying JARs: Embrace Dynamic Hot‑Deployment of User‑Provided Implementations
The article demonstrates how to let a system expose an interface, accept user‑written JARs that implement it, and hot‑replace the implementation at runtime using either Spring‑managed beans or pure reflection, including bean registration and cleanup.
Problem Statement
During development a requirement arose to expose a predefined interface, allow users to implement it in a JAR, upload the JAR, and have the system hot‑deploy the new implementation without restarting.
Define a Simple Interface
public interface Calculator {
int calculate(int a, int b);
int add(int a, int b);
}Implementation with Two Strategies
The implementation class supports both a Spring‑annotation approach (for the calculate method) and a pure‑reflection approach (for the add method).
@Service
public class CalculatorImpl implements Calculator {
@Autowired
CalculatorCore calculatorCore;
// Annotation‑based method
@Override
public int calculate(int a, int b) {
int c = calculatorCore.add(a, b);
return c;
}
// Reflection‑based method
@Override
public int add(int a, int b) {
return new CalculatorCore().add(a, b);
}
}Supporting Core Component
@Service
public class CalculatorCore {
public int add(int a, int b) {
return a + b;
}
}Reflection‑Based Hot Deployment
The uploaded JAR is placed in a known directory ( jarAddress) and its URL is built ( jarPath). The class is loaded with a URLClassLoader, instantiated via reflection, and the add method is invoked.
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);
}Spring‑Annotation Hot Deployment
If the uploaded JAR contains Spring components, the system scans all classes, identifies those annotated with @Component, @Repository or @Service, and registers them into the current 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());
}
}
}Utility Class DeployUtils
DeployUtilsprovides three helper methods: readJarFile(String jarAddress) – iterates over the JAR entries, collects fully‑qualified class names, and returns them as a Set<String>. isSpringBeanClass(Class<?> clazz) – returns true if the class is annotated with @Component, @Repository or @Service (and is not an interface or abstract class). transformName(String className) – converts the simple class name to a bean name with a lower‑case first letter.
public static Set<String> readJarFile(String jarAddress) throws IOException { /* … */ }
public static boolean isSpringBeanClass(Class<?> cla) { /* … */ }
public static String transformName(String className) { /* … */ }Removing Deployed Beans
When a JAR is replaced or deleted, the previously registered beans must be removed from the Spring container using the same bean name transformation.
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 test class simulates the upload process. It repeatedly attempts hot deployment, catches exceptions when the JAR is missing, sleeps for ten seconds, and then retries—allowing the developer to copy the JAR into the monitored 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);
}
}SpringMeng
Focused on software development, sharing source code and tutorials for various systems.
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.
