Hot‑Deploying User‑Defined Interfaces in Spring Boot: Reflection vs Annotation
This article demonstrates how to design a simple Calculator interface, provide two implementation strategies (annotation‑managed and reflection‑based), and achieve hot deployment of user‑supplied JARs in a Spring Boot application, including loading, registering, and removing beans dynamically.
During system development a requirement arose to expose an interface that users can implement, package as a JAR, upload to the system, and have the system hot‑deploy the new implementation and switch to it at runtime.
1 Define a Simple Interface
We use a basic calculator example:
public interface Calculator {
int calculate(int a, int b);
int add(int a, int b);
}2 A Simple Implementation
Two implementation approaches are shown: one managed by Spring annotations and one using pure reflection.
@Service
public class CalculatorImpl implements Calculator {
@Autowired
CalculatorCore calculatorCore;
/** Annotation‑based */
@Override
public int calculate(int a, int b) {
int c = calculatorCore.add(a, b);
return c;
}
/** Reflection‑based */
@Override
public int add(int a, int b) {
return new CalculatorCore().add(a, b);
}
}The CalculatorCore bean is injected to verify that the annotation mode can fully construct the bean dependency graph and register it in the current Spring container.
@Service
public class CalculatorCore {
public int add(int a, int b) {
return a + b;
}
}3 Reflection‑Based Hot Deployment
Users upload a JAR to a predefined directory. The JAR path and address are defined as:
private static String jarAddress = "E:/zzq/IDEA_WS/CalculatorTest/lib/Calculator.jar";
private static String jarPath = "file:/" + jarAddress;The system loads the JAR with a thread‑context class loader, obtains the implementation class by its fully‑qualified name, creates an instance via reflection, and invokes the 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 Annotation‑Based Hot Deployment
If the uploaded JAR contains Spring‑managed beans, the system scans all classes, identifies those annotated with Spring stereotypes, and registers them dynamically in the current Spring container.
/**
* Dynamically register beans from the uploaded JAR, including 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());
}
}
}Utility methods used in the process:
public static Set<String> readJarFile(String jarAddress) throws IOException {
Set<String> classNameSet = new HashSet<>();
JarFile jarFile = new JarFile(jarAddress);
Enumeration<JarEntry> entries = jarFile.entries(); // iterate the JAR
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) return false;
if (cla.isInterface()) return false;
if (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);
}When a JAR is removed, the previously registered beans must also be deregistered to keep the Spring context consistent.
/**
* Remove beans registered from the JAR when it is deleted
*/
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));
}
}
}5 Test
A test class simulates the user uploading a JAR. It repeatedly attempts hot deployment, catching exceptions and waiting 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);
}
}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.
macrozheng
Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.
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.
