From 5ec09209aa5f035400bb6963c7e728c4d2d5202e Mon Sep 17 00:00:00 2001
From: phanes <5411232+phanes@user.noreply.gitee.com>
Date: Sat, 5 Jul 2025 16:18:58 +0800
Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20Spring=20Boot=20=E5=8A=A8?=
=?UTF-8?q?=E6=80=81=E7=BC=96=E8=AF=91=E5=92=8C=20Bean=20=E7=AE=A1?=
=?UTF-8?q?=E7=90=86=E6=BC=94=E7=A4=BA?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
demo_springboot_function/pom.xml | 83 ++++
.../main/java/demo/ControllerRegister.java | 60 +++
.../src/main/java/demo/DemoApp.java | 18 +
.../src/main/java/demo/DemoController.java | 24 ++
.../src/main/java/demo/DemoService.java | 26 ++
.../main/java/demo/DynamicBeanManager.java | 290 ++++++++++++++
.../src/main/resources/application.yml | 9 +
.../main/resources/codefile/HelloLiquor.txt | 45 +++
.../src/test/java/demo/DemoAppTest.java | 365 ++++++++++++++++++
pom.xml | 1 +
10 files changed, 921 insertions(+)
create mode 100644 demo_springboot_function/pom.xml
create mode 100644 demo_springboot_function/src/main/java/demo/ControllerRegister.java
create mode 100644 demo_springboot_function/src/main/java/demo/DemoApp.java
create mode 100644 demo_springboot_function/src/main/java/demo/DemoController.java
create mode 100644 demo_springboot_function/src/main/java/demo/DemoService.java
create mode 100644 demo_springboot_function/src/main/java/demo/DynamicBeanManager.java
create mode 100644 demo_springboot_function/src/main/resources/application.yml
create mode 100644 demo_springboot_function/src/main/resources/codefile/HelloLiquor.txt
create mode 100644 demo_springboot_function/src/test/java/demo/DemoAppTest.java
diff --git a/demo_springboot_function/pom.xml b/demo_springboot_function/pom.xml
new file mode 100644
index 0000000..b85e087
--- /dev/null
+++ b/demo_springboot_function/pom.xml
@@ -0,0 +1,83 @@
+
+
+ 4.0.0
+
+
+ org.springframework.boot
+ spring-boot-parent
+ 2.7.18
+
+
+
+ org.noear
+ demo_springboot_function
+ demo
+
+ ${project.artifactId}
+
+
+ UTF-8
+ UTF-8
+ 1.8
+
+
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+
+ org.noear
+ liquor
+ 1.5.7
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+ org.junit.platform
+ junit-platform-launcher
+ test
+
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+ 2.7.18
+
+ demo.DemoApp
+
+
+
+
+ repackage
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/demo_springboot_function/src/main/java/demo/ControllerRegister.java b/demo_springboot_function/src/main/java/demo/ControllerRegister.java
new file mode 100644
index 0000000..7a5aed3
--- /dev/null
+++ b/demo_springboot_function/src/main/java/demo/ControllerRegister.java
@@ -0,0 +1,60 @@
+package demo;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.ResourceLoader;
+import org.springframework.stereotype.Component;
+import org.springframework.util.FileCopyUtils;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * 动态Controller注册器
+ * 使用Spring原生Bean注册机制,完全集成到Spring生命周期中
+ *
+ * @author phanes
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class ControllerRegister implements ApplicationRunner {
+
+ private final DynamicBeanManager dynamicBeanManager;
+ private final ResourceLoader resourceLoader;
+
+ @Override
+ public void run(ApplicationArguments args) throws Exception {
+ this.registerHelloLiquorController();
+ }
+
+ /**
+ * 注册HelloLiquor控制器
+ */
+ public void registerHelloLiquorController() {
+ try {
+ // 读取资源文件
+ Resource resource = resourceLoader.getResource("classpath:codefile/HelloLiquor.txt");
+ String classCode;
+ try (InputStreamReader reader = new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8)) {
+ classCode = FileCopyUtils.copyToString(reader);
+ }
+
+ // 编译并注册动态Controller
+ dynamicBeanManager.registerDynamicController("helloLiquorController", "demo.HelloLiquor", classCode);
+
+ log.info("动态Controller注册成功: HelloLiquor");
+ } catch (IOException e) {
+ log.error("读取HelloLiquor.txt文件失败", e);
+ throw new RuntimeException("动态Controller注册失败", e);
+ } catch (Exception e) {
+ log.error("动态Controller注册失败", e);
+ throw new RuntimeException("动态Controller注册失败", e);
+ }
+ }
+
+}
diff --git a/demo_springboot_function/src/main/java/demo/DemoApp.java b/demo_springboot_function/src/main/java/demo/DemoApp.java
new file mode 100644
index 0000000..9f0fd23
--- /dev/null
+++ b/demo_springboot_function/src/main/java/demo/DemoApp.java
@@ -0,0 +1,18 @@
+package demo;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ * Spring Boot 启动类
+ *
+ * @author phanes
+ */
+@SpringBootApplication
+public class DemoApp {
+
+ public static void main(String[] args) {
+ SpringApplication.run(DemoApp.class, args);
+ }
+
+}
diff --git a/demo_springboot_function/src/main/java/demo/DemoController.java b/demo_springboot_function/src/main/java/demo/DemoController.java
new file mode 100644
index 0000000..3ef2bf5
--- /dev/null
+++ b/demo_springboot_function/src/main/java/demo/DemoController.java
@@ -0,0 +1,24 @@
+package demo;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @author phanes
+ */
+@RestController
+@RequestMapping("")
+@RequiredArgsConstructor
+public class DemoController {
+
+ private final DemoService demoService;
+
+ @GetMapping(path = {"", "/hello"})
+ public String hello(@RequestParam(value = "name", defaultValue = "World") String name) {
+ return demoService.sayHello(name);
+ }
+
+}
\ No newline at end of file
diff --git a/demo_springboot_function/src/main/java/demo/DemoService.java b/demo_springboot_function/src/main/java/demo/DemoService.java
new file mode 100644
index 0000000..2193b28
--- /dev/null
+++ b/demo_springboot_function/src/main/java/demo/DemoService.java
@@ -0,0 +1,26 @@
+package demo;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+/**
+ * 演示服务类
+ * 用于测试动态编译类的构造函数依赖注入功能
+ *
+ * @author phanes
+ */
+@Slf4j
+@Service
+public class DemoService {
+
+ public String sayHello(String name) {
+ String message = String.format("Hello %s!", name);
+ log.info("DemoService.sayHello 调用: {}", message);
+ return message;
+ }
+
+ public String getServiceInfo() {
+ return "DemoService - 动态Bean依赖注入测试服务";
+ }
+
+}
diff --git a/demo_springboot_function/src/main/java/demo/DynamicBeanManager.java b/demo_springboot_function/src/main/java/demo/DynamicBeanManager.java
new file mode 100644
index 0000000..263b1b5
--- /dev/null
+++ b/demo_springboot_function/src/main/java/demo/DynamicBeanManager.java
@@ -0,0 +1,290 @@
+package demo;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.noear.liquor.DynamicCompiler;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.support.AbstractBeanDefinition;
+import org.springframework.beans.factory.support.DefaultListableBeanFactory;
+import org.springframework.beans.factory.support.GenericBeanDefinition;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.support.GenericApplicationContext;
+import org.springframework.core.annotation.AnnotatedElementUtils;
+import org.springframework.stereotype.Component;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
+import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
+
+import java.lang.reflect.Method;
+import java.time.LocalDateTime;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * 动态Bean管理器
+ * 提供动态编译类的Spring Bean注册、生命周期管理和RequestMapping自动刷新功能
+ *
+ * @author phanes
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class DynamicBeanManager {
+
+ private final ConfigurableApplicationContext applicationContext;
+ private final RequestMappingHandlerMapping requestMappingHandlerMapping;
+
+ /**
+ * 已注册的动态Bean缓存
+ */
+ @Getter
+ private final Map registeredBeans = new ConcurrentHashMap<>();
+
+ /**
+ * 注册动态编译的Controller类到Spring容器
+ *
+ * @param beanName Bean名称
+ * @param className 类名
+ * @param sourceCode 源代码
+ * @throws Exception 注册过程中的异常
+ */
+ public void registerDynamicController(String beanName, String className, String sourceCode) throws Exception {
+ try {
+ log.info("开始注册动态Controller: {} -> {}", beanName, className);
+
+ // 检查是否已存在,如果存在则先注销
+ if (registeredBeans.containsKey(beanName)) {
+ unregisterDynamicController(beanName);
+ }
+
+ // 编译动态类
+ Class> clazz = compileDynamicClass(className, sourceCode);
+
+ // 注册为Spring Bean
+ registerSpringBean(beanName, clazz);
+
+ // 刷新RequestMapping映射
+ refreshRequestMappings(beanName);
+
+ // 记录注册信息
+ DynamicBeanInfo beanInfo = new DynamicBeanInfo(beanName, className, clazz);
+ registeredBeans.put(beanName, beanInfo);
+
+ log.info("动态Controller注册成功: {}", beanName);
+
+ } catch (Exception e) {
+ log.error("注册动态Controller失败: {} -> {}", beanName, className, e);
+ // 清理可能的部分注册状态
+ cleanupFailedRegistration(beanName);
+ throw e;
+ }
+ }
+
+ /**
+ * 注销动态Controller
+ *
+ * @param beanName Bean名称
+ */
+ public void unregisterDynamicController(String beanName) {
+ DynamicBeanInfo beanInfo = registeredBeans.remove(beanName);
+ if (beanInfo != null) {
+ log.info("开始注销动态Controller: {} (类名: {})", beanName, beanInfo.getClassName());
+ try {
+ // 注销RequestMapping映射
+ unregisterRequestMappings(beanName);
+
+ // 使用DefaultListableBeanFactory进行完整的Bean清理
+ DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
+
+ // 完整清理Bean实例和定义
+ if (beanFactory.containsBeanDefinition(beanName)) {
+ // 如果Bean实例已创建,先销毁实例
+ if (beanFactory.containsSingleton(beanName)) {
+ beanFactory.destroySingleton(beanName);
+ log.info("Bean实例 {} 已被销毁", beanName);
+ }
+
+ // 移除Bean定义
+ beanFactory.removeBeanDefinition(beanName);
+ log.info("Bean定义 {} 已从Spring容器中移除", beanName);
+ } else {
+ log.warn("Bean定义 {} 不存在,跳过移除", beanName);
+ }
+
+ // 验证清理状态
+ validateCleanupStatus(beanName, beanFactory);
+
+ log.info("动态Controller注销成功: {}", beanName);
+ } catch (ClassCastException e) {
+ log.error("ApplicationContext类型不支持动态Bean管理: {}", beanName, e);
+ } catch (IllegalStateException e) {
+ log.error("Spring容器状态异常,无法注销Bean: {}", beanName, e);
+ } catch (Exception e) {
+ log.error("注销动态Controller失败: {}", beanName, e);
+ }
+ } else {
+ log.warn("尝试注销不存在的动态Controller: {}", beanName);
+ }
+ }
+
+ /**
+ * 编译动态类
+ */
+ private Class> compileDynamicClass(String className, String sourceCode) throws Exception {
+ DynamicCompiler compiler = new DynamicCompiler();
+ compiler.addSource(className, sourceCode).build();
+ return compiler.getClassLoader().loadClass(className);
+ }
+
+ /**
+ * 使用BeanDefinition方式注册Bean(支持构造函数注入)
+ */
+ private void registerSpringBean(String beanName, Class> clazz) {
+ if (!(applicationContext instanceof GenericApplicationContext)) {
+ throw new IllegalStateException("ApplicationContext必须是GenericApplicationContext类型才能动态注册Bean");
+ }
+
+ GenericApplicationContext context = (GenericApplicationContext) applicationContext;
+
+ // 创建BeanDefinition
+ GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
+ beanDefinition.setBeanClass(clazz);
+ beanDefinition.setScope(BeanDefinition.SCOPE_SINGLETON);
+ beanDefinition.setRole(BeanDefinition.ROLE_APPLICATION);
+ beanDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
+
+ // 注册BeanDefinition
+ context.registerBeanDefinition(beanName, beanDefinition);
+
+ log.debug("Spring Bean注册成功: {} -> {}", beanName, clazz.getName());
+ }
+
+ /**
+ * 刷新RequestMapping映射
+ */
+ private void refreshRequestMappings(String beanName) {
+ try {
+ Object bean = applicationContext.getBean(beanName);
+ Class> beanClass = bean.getClass();
+
+ // 遍历所有方法,为标注了@RequestMapping的方法创建映射
+ Method[] methods = beanClass.getDeclaredMethods();
+ int registeredCount = 0;
+
+ for (Method method : methods) {
+ RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(method, RequestMapping.class);
+ if (requestMapping != null) {
+ // 创建RequestMappingInfo
+ RequestMappingInfo mappingInfo = createRequestMappingInfo(requestMapping);
+
+ // 注册映射
+ requestMappingHandlerMapping.registerMapping(mappingInfo, bean, method);
+
+ registeredCount++;
+ log.debug("注册RequestMapping: {} -> {}", mappingInfo.getPathPatternsCondition(), method.getName());
+ }
+ }
+
+ log.info("RequestMapping映射刷新成功: {} ({}个方法)", beanName, registeredCount);
+ } catch (Exception e) {
+ log.error("刷新RequestMapping映射失败: {}", beanName, e);
+ }
+ }
+
+ /**
+ * 创建RequestMappingInfo
+ */
+ private RequestMappingInfo createRequestMappingInfo(RequestMapping requestMapping) {
+ // 获取配置信息
+ RequestMappingInfo.BuilderConfiguration config = requestMappingHandlerMapping.getBuilderConfiguration();
+
+ // 创建RequestMappingInfo
+ RequestMappingInfo.Builder builder = RequestMappingInfo.paths(requestMapping.value())
+ .methods(requestMapping.method())
+ .params(requestMapping.params())
+ .headers(requestMapping.headers())
+ .consumes(requestMapping.consumes())
+ .produces(requestMapping.produces())
+ .options(config);
+
+ return builder.build();
+ }
+
+ /**
+ * 注销RequestMapping映射
+ */
+ private void unregisterRequestMappings(String beanName) {
+ try {
+ Object bean = applicationContext.getBean(beanName);
+ Class> beanClass = bean.getClass();
+
+ // 遍历所有方法,注销RequestMapping映射
+ Method[] methods = beanClass.getDeclaredMethods();
+ int unregisteredCount = 0;
+
+ for (Method method : methods) {
+ RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(method, RequestMapping.class);
+ if (requestMapping != null) {
+ // 创建RequestMappingInfo(用于注销)
+ RequestMappingInfo mappingInfo = createRequestMappingInfo(requestMapping);
+
+ // 注销映射
+ requestMappingHandlerMapping.unregisterMapping(mappingInfo);
+
+ unregisteredCount++;
+ log.debug("注销RequestMapping: {} -> {}", mappingInfo.getPathPatternsCondition(), method.getName());
+ }
+ }
+
+ log.info("RequestMapping映射注销成功: {} ({}个方法)", beanName, unregisteredCount);
+ } catch (Exception e) {
+ log.debug("注销RequestMapping映射时出现异常: {}", beanName, e);
+ }
+ }
+
+ /**
+ * 验证Bean清理状态
+ */
+ private void validateCleanupStatus(String beanName, DefaultListableBeanFactory beanFactory) {
+ boolean definitionExists = beanFactory.containsBeanDefinition(beanName);
+ boolean singletonExists = beanFactory.containsSingleton(beanName);
+
+ if (definitionExists || singletonExists) {
+ log.warn("Bean清理验证失败: {} (定义存在: {}, 实例存在: {})",
+ beanName, definitionExists, singletonExists);
+ } else {
+ log.debug("Bean清理验证成功: {} 已完全清理", beanName);
+ }
+ }
+
+ /**
+ * 清理失败注册的残留状态
+ */
+ private void cleanupFailedRegistration(String beanName) {
+ try {
+ registeredBeans.remove(beanName);
+ } catch (Exception e) {
+ log.debug("清理失败注册状态时出现异常: {}", beanName, e);
+ }
+ }
+
+ /**
+ * 动态Bean信息
+ */
+ @Getter
+ public static class DynamicBeanInfo {
+ private final String beanName;
+ private final String className;
+ private final Class> clazz;
+ private final LocalDateTime registrationTime;
+
+ public DynamicBeanInfo(String beanName, String className, Class> clazz) {
+ this.beanName = beanName;
+ this.className = className;
+ this.clazz = clazz;
+ this.registrationTime = LocalDateTime.now();
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/demo_springboot_function/src/main/resources/application.yml b/demo_springboot_function/src/main/resources/application.yml
new file mode 100644
index 0000000..11e7727
--- /dev/null
+++ b/demo_springboot_function/src/main/resources/application.yml
@@ -0,0 +1,9 @@
+server:
+ port: 8080
+ servlet:
+ context-path: /
+
+logging:
+ level:
+ demo: debug
+ web: debug
diff --git a/demo_springboot_function/src/main/resources/codefile/HelloLiquor.txt b/demo_springboot_function/src/main/resources/codefile/HelloLiquor.txt
new file mode 100644
index 0000000..294b25c
--- /dev/null
+++ b/demo_springboot_function/src/main/resources/codefile/HelloLiquor.txt
@@ -0,0 +1,45 @@
+package demo;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+import java.time.LocalDateTime;
+
+public class HelloLiquor {
+
+ private final DemoService demoService;
+
+ @Value("${server.port}")
+ private String serverPort;
+
+ @Value("${server.servlet.context-path:/}")
+ private String contextPath;
+
+ @Value("${logging.level.demo:info}")
+ private String logLevel;
+
+ public HelloLiquor(DemoService demoService) {
+ this.demoService = demoService;
+ }
+
+ @ResponseBody
+ @GetMapping(path = {"/liquor", "/liquor/hello"})
+ public String hello(@RequestParam(value = "name", defaultValue = "liquor") String name) {
+ return demoService.sayHello(name);
+ }
+
+ @ResponseBody
+ @GetMapping(path = {"/liquor/getDate"})
+ public String getDate() {
+ return LocalDateTime.now().toString();
+ }
+
+ @ResponseBody
+ @GetMapping(path = {"/liquor/config"})
+ public String getConfig() {
+ return String.format("服务配置信息 - 端口: %s, 上下文路径: %s, 日志级别: %s",
+ serverPort, contextPath, logLevel);
+ }
+
+}
diff --git a/demo_springboot_function/src/test/java/demo/DemoAppTest.java b/demo_springboot_function/src/test/java/demo/DemoAppTest.java
new file mode 100644
index 0000000..ca8bfb4
--- /dev/null
+++ b/demo_springboot_function/src/test/java/demo/DemoAppTest.java
@@ -0,0 +1,365 @@
+package demo;
+
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.web.client.TestRestTemplate;
+import org.springframework.context.ApplicationContext;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeParseException;
+import java.util.Map;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Spring Boot API集成测试类
+ * 测试静态控制器、动态控制器和动态Bean管理功能
+ *
+ * @author phanes
+ */
+@Slf4j
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
+public class DemoAppTest {
+
+ @Value("${local.server.port}")
+ private int port;
+
+ @Autowired
+ private TestRestTemplate restTemplate;
+
+ @Autowired
+ private ApplicationContext applicationContext;
+
+ @Autowired
+ private DynamicBeanManager dynamicBeanManager;
+
+ /**
+ * 测试应用启动和动态Bean注册
+ */
+ @Test
+ @Order(1)
+ public void testApplicationStartupAndDynamicBeanRegistration() {
+ log.info("开始测试应用启动和动态Bean注册...");
+
+ // 验证应用上下文正常启动
+ assertThat(applicationContext).isNotNull();
+
+ // 验证动态Bean管理器正常工作
+ assertThat(dynamicBeanManager).isNotNull();
+
+ // 验证动态Bean已注册
+ assertThat(dynamicBeanManager.getRegisteredBeans()).isNotEmpty();
+ assertThat(dynamicBeanManager.getRegisteredBeans()).containsKey("helloLiquorController");
+
+ log.info("应用启动和动态Bean注册测试通过");
+ }
+
+ /**
+ * 测试静态控制器 - 根路径默认参数
+ */
+ @Test
+ @Order(2)
+ public void testIndexControllerRootWithDefaultParam() {
+ log.info("测试静态控制器根路径默认参数...");
+
+ ResponseEntity response = restTemplate.getForEntity(
+ "http://localhost:" + port + "/", String.class);
+
+ assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
+ assertThat(response.getBody()).isEqualTo("Hello World!");
+
+ log.info("静态控制器根路径默认参数测试通过");
+ }
+
+ /**
+ * 测试静态控制器 - hello路径默认参数
+ */
+ @Test
+ @Order(3)
+ public void testIndexControllerHelloWithDefaultParam() {
+ log.info("测试静态控制器hello路径默认参数...");
+
+ ResponseEntity response = restTemplate.getForEntity(
+ "http://localhost:" + port + "/hello", String.class);
+
+ assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
+ assertThat(response.getBody()).isEqualTo("Hello World!");
+
+ log.info("静态控制器hello路径默认参数测试通过");
+ }
+
+ /**
+ * 测试静态控制器 - 自定义参数
+ */
+ @Test
+ @Order(4)
+ public void testIndexControllerWithCustomParam() {
+ log.info("测试静态控制器自定义参数...");
+
+ ResponseEntity response = restTemplate.getForEntity(
+ "http://localhost:" + port + "/?name=SpringBoot", String.class);
+
+ assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
+ assertThat(response.getBody()).isEqualTo("Hello SpringBoot!");
+
+ // 测试hello路径的自定义参数
+ response = restTemplate.getForEntity(
+ "http://localhost:" + port + "/hello?name=Test", String.class);
+
+ assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
+ assertThat(response.getBody()).isEqualTo("Hello Test!");
+
+ log.info("静态控制器自定义参数测试通过");
+ }
+
+ /**
+ * 测试动态控制器 - liquor路径默认参数
+ */
+ @Test
+ @Order(5)
+ public void testDynamicControllerLiquorWithDefaultParam() {
+ log.info("测试动态控制器liquor路径默认参数...");
+
+ ResponseEntity response = restTemplate.getForEntity(
+ "http://localhost:" + port + "/liquor", String.class);
+
+ assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
+ assertThat(response.getBody()).isEqualTo("Hello liquor!");
+
+ log.info("动态控制器liquor路径默认参数测试通过");
+ }
+
+ /**
+ * 测试动态控制器 - liquor/hello路径默认参数
+ */
+ @Test
+ @Order(6)
+ public void testDynamicControllerLiquorHelloWithDefaultParam() {
+ log.info("测试动态控制器liquor/hello路径默认参数...");
+
+ ResponseEntity response = restTemplate.getForEntity(
+ "http://localhost:" + port + "/liquor/hello", String.class);
+
+ assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
+ assertThat(response.getBody()).isEqualTo("Hello liquor!");
+
+ log.info("动态控制器liquor/hello路径默认参数测试通过");
+ }
+
+ /**
+ * 测试动态控制器 - 自定义参数
+ */
+ @Test
+ @Order(7)
+ public void testDynamicControllerWithCustomParam() {
+ log.info("测试动态控制器自定义参数...");
+
+ ResponseEntity response = restTemplate.getForEntity(
+ "http://localhost:" + port + "/liquor?name=DynamicTest", String.class);
+
+ assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
+ assertThat(response.getBody()).isEqualTo("Hello DynamicTest!");
+
+ // 测试liquor/hello路径的自定义参数
+ response = restTemplate.getForEntity(
+ "http://localhost:" + port + "/liquor/hello?name=Compiler", String.class);
+
+ assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
+ assertThat(response.getBody()).isEqualTo("Hello Compiler!");
+
+ log.info("动态控制器自定义参数测试通过");
+ }
+
+ /**
+ * 测试动态控制器 - 时间获取接口
+ */
+ @Test
+ @Order(8)
+ public void testDynamicControllerGetDate() {
+ log.info("测试动态控制器时间获取接口...");
+
+ ResponseEntity response = restTemplate.getForEntity(
+ "http://localhost:" + port + "/liquor/getDate", String.class);
+
+ assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
+ assertThat(response.getBody()).isNotNull();
+ assertThat(response.getBody()).isNotEmpty();
+
+ // 验证返回的时间格式是否为有效的LocalDateTime字符串
+ try {
+ LocalDateTime.parse(response.getBody());
+ log.info("时间格式验证成功: {}", response.getBody());
+ } catch (DateTimeParseException e) {
+ throw new AssertionError("返回的时间格式无效: " + response.getBody(), e);
+ }
+
+ log.info("动态控制器时间获取接口测试通过");
+ }
+
+ /**
+ * 测试动态控制器 - 配置信息获取接口
+ */
+ @Test
+ @Order(9)
+ public void testDynamicControllerGetConfig() {
+ log.info("测试动态控制器配置信息获取接口...");
+
+ ResponseEntity response = restTemplate.getForEntity(
+ "http://localhost:" + port + "/liquor/config", String.class);
+
+ assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
+ assertThat(response.getBody()).isNotNull();
+ assertThat(response.getBody()).isNotEmpty();
+
+ // 验证返回内容包含配置信息
+ assertThat(response.getBody()).contains("服务配置信息");
+ assertThat(response.getBody()).contains("端口: 0");
+ assertThat(response.getBody()).contains("上下文路径: /");
+ assertThat(response.getBody()).contains("日志级别: debug");
+
+ log.info("动态控制器配置信息获取接口测试通过: {}", response.getBody());
+ }
+
+ /**
+ * 测试动态Bean管理功能
+ */
+ @Test
+ @Order(10)
+ public void testDynamicBeanManagement() {
+ log.info("测试动态Bean管理功能...");
+
+ // 验证动态Bean注册信息
+ Map registeredBeans = dynamicBeanManager.getRegisteredBeans();
+ assertThat(registeredBeans).hasSize(1);
+
+ DynamicBeanManager.DynamicBeanInfo beanInfo = registeredBeans.get("helloLiquorController");
+ assertThat(beanInfo).isNotNull();
+ assertThat(beanInfo.getBeanName()).isEqualTo("helloLiquorController");
+ assertThat(beanInfo.getClassName()).isEqualTo("demo.HelloLiquor");
+ assertThat(beanInfo.getClazz()).isNotNull();
+ assertThat(beanInfo.getRegistrationTime()).isNotNull();
+
+ // 验证动态Bean在Spring容器中存在
+ assertThat(applicationContext.containsBean("helloLiquorController")).isTrue();
+
+ // 验证动态编译的类可以被加载
+ Object dynamicBean = applicationContext.getBean("helloLiquorController");
+ assertThat(dynamicBean).isNotNull();
+ assertThat(dynamicBean.getClass().getName()).isEqualTo("demo.HelloLiquor");
+
+ log.info("动态Bean管理功能测试通过 - Bean: {}, 类: {}, 注册时间: {}",
+ beanInfo.getBeanName(),
+ beanInfo.getClassName(),
+ beanInfo.getRegistrationTime());
+ }
+
+ /**
+ * 测试动态控制器重新注册功能
+ */
+ @Test
+ @Order(11)
+ public void testDynamicControllerReRegistration() throws Exception {
+ log.info("测试动态控制器重新注册功能...");
+
+ dynamicBeanManager.unregisterDynamicController("helloLiquorController");
+
+ // 读取修改后的HelloLiquor.txt内容
+ String updatedClassCode = " package demo;\n" +
+ "\n" +
+ " import org.springframework.beans.factory.annotation.Autowired;\n" +
+ " import org.springframework.web.bind.annotation.GetMapping;\n" +
+ " import org.springframework.web.bind.annotation.RequestParam;\n" +
+ " import org.springframework.web.bind.annotation.ResponseBody;\n" +
+ "\n" +
+ " public class HelloLiquor {\n" +
+ "\n" +
+ " @Autowired\n" +
+ " private DemoService demoService;\n" +
+ "\n" +
+ " @ResponseBody\n" +
+ " @GetMapping(\"/liquor/profile\")\n" +
+ " public String getUserProfile(\n" +
+ " @RequestParam(value = \"name\", defaultValue = \"访客\") String name,\n" +
+ " @RequestParam(value = \"age\", defaultValue = \"18\") Integer age\n" +
+ " ) {\n" +
+ " String greeting = demoService.sayHello(name);\n" +
+ " return String.format(\"%s 年龄: %d岁, 服务信息: %s\",\n" +
+ " greeting, age, demoService.getServiceInfo());\n" +
+ " }\n" +
+ "\n" +
+ " }";
+
+ // 重新注册动态控制器
+ dynamicBeanManager.registerDynamicController("helloLiquorController", "demo.HelloLiquor", updatedClassCode);
+
+ // 验证重新注册成功
+ Map registeredBeans = dynamicBeanManager.getRegisteredBeans();
+ assertThat(registeredBeans).hasSize(1);
+ assertThat(registeredBeans).containsKey("helloLiquorController");
+
+ // 验证新的Bean可以正常工作
+ Object dynamicBean = applicationContext.getBean("helloLiquorController");
+ assertThat(dynamicBean).isNotNull();
+ assertThat(dynamicBean.getClass().getName()).isEqualTo("demo.HelloLiquor");
+
+ log.info("动态控制器重新注册功能测试通过");
+ }
+
+ /**
+ * 测试新增的profile接口 - 默认参数
+ */
+ @Test
+ @Order(12)
+ public void testNewProfileInterfaceWithDefaultParams() {
+ log.info("测试新增的profile接口默认参数...");
+
+ ResponseEntity response = restTemplate.getForEntity(
+ "http://localhost:" + port + "/liquor/profile", String.class);
+
+ assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
+ assertThat(response.getBody()).isNotNull();
+ assertThat(response.getBody()).contains("Hello 访客!");
+ assertThat(response.getBody()).contains("年龄: 18岁");
+ assertThat(response.getBody()).contains("服务信息: DemoService - 动态Bean依赖注入测试服务");
+
+ log.info("新增profile接口默认参数测试通过: {}", response.getBody());
+ }
+
+ /**
+ * 测试新增的profile接口 - 自定义参数
+ */
+ @Test
+ @Order(13)
+ public void testNewProfileInterfaceWithCustomParams() {
+ log.info("测试新增的profile接口自定义参数...");
+
+ ResponseEntity response = restTemplate.getForEntity(
+ "http://localhost:" + port + "/liquor/profile?name=张三&age=25", String.class);
+
+ assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
+ assertThat(response.getBody()).isNotNull();
+ assertThat(response.getBody()).contains("Hello 张三!");
+ assertThat(response.getBody()).contains("年龄: 25岁");
+ assertThat(response.getBody()).contains("服务信息: DemoService - 动态Bean依赖注入测试服务");
+
+ // 测试部分参数自定义
+ response = restTemplate.getForEntity(
+ "http://localhost:" + port + "/liquor/profile?name=李四", String.class);
+
+ assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
+ assertThat(response.getBody()).contains("Hello 李四!");
+ assertThat(response.getBody()).contains("年龄: 18岁"); // 使用默认年龄
+
+ log.info("新增profile接口自定义参数测试通过");
+ }
+
+}
diff --git a/pom.xml b/pom.xml
index 58c7d08..93389ac 100755
--- a/pom.xml
+++ b/pom.xml
@@ -22,6 +22,7 @@
liquor_benchmark
demo_solon_jarinjar
demo_springboot_jarinjar
+ demo_springboot_function
demo_dynamic_compiling_and_debugging_solon
--
Gitee