diff --git a/core/datacap-plugin/src/main/java/io/edurt/datacap/plugin/Plugin.java b/core/datacap-plugin/src/main/java/io/edurt/datacap/plugin/Plugin.java index a2a6ffcc..e6211448 100644 --- a/core/datacap-plugin/src/main/java/io/edurt/datacap/plugin/Plugin.java +++ b/core/datacap-plugin/src/main/java/io/edurt/datacap/plugin/Plugin.java @@ -160,6 +160,10 @@ public abstract class Plugin } } + /** + * 配置服务 + * Configure services + */ /** * 配置服务 * Configure services @@ -167,7 +171,20 @@ public abstract class Plugin private void configureServices() { getServiceTypes().forEach(serviceType -> { - ServiceBindings bindings = ServiceSpiLoader.loadServices(serviceType, Thread.currentThread().getContextClassLoader()); + // 获取包路径,默认使用插件类所在包 + // Get package path, default to the plugin's package + String basePackage = this.getClass().getPackage().getName(); + + // 同时使用SPI和注解两种方式加载服务 + // Load services using both SPI and annotation methods + ServiceBindings bindings = null; + ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); + if (pluginClassLoader != null) { + bindings = ServiceSpiLoader.loadServices(serviceType, basePackage, pluginClassLoader); + } + else { + bindings = ServiceSpiLoader.loadServices(serviceType, basePackage, contextClassLoader); + } // 支持多个实现 // Support multiple implementations @@ -305,7 +322,7 @@ public abstract class Plugin } // 获取所有服务 - // Get all services +// Get all services public Set getAllServices(Class serviceClass) { validateInjector(); @@ -316,8 +333,11 @@ public abstract class Plugin if (pluginClassLoader != null) { return PluginContextManager.runWithClassLoader(pluginClassLoader, () -> { Set services = Sets.newHashSet(); + // 获取包路径,默认使用插件类所在包 + // Get package path, default to the plugin's package + String basePackage = this.getClass().getPackage().getName(); ServiceBindings bindings = ServiceSpiLoader.loadServices( - serviceClass, pluginClassLoader); + serviceClass, basePackage, pluginClassLoader); bindings.getBindings().get(serviceClass).forEach(impl -> { String name = impl.getSimpleName(); services.add(getService(serviceClass, name)); @@ -327,8 +347,11 @@ public abstract class Plugin } else { Set services = Sets.newHashSet(); + // 获取包路径,默认使用插件类所在包 + // Get package path, default to the plugin's package + String basePackage = this.getClass().getPackage().getName(); ServiceBindings bindings = ServiceSpiLoader.loadServices( - serviceClass, Thread.currentThread().getContextClassLoader()); + serviceClass, basePackage, Thread.currentThread().getContextClassLoader()); bindings.getBindings().get(serviceClass).forEach(impl -> { String name = impl.getSimpleName(); services.add(getService(serviceClass, name)); diff --git a/core/datacap-plugin/src/main/java/io/edurt/datacap/plugin/service/InjectService.java b/core/datacap-plugin/src/main/java/io/edurt/datacap/plugin/service/InjectService.java new file mode 100644 index 00000000..85781286 --- /dev/null +++ b/core/datacap-plugin/src/main/java/io/edurt/datacap/plugin/service/InjectService.java @@ -0,0 +1,23 @@ +package io.edurt.datacap.plugin.service; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 用于标注Service实现类的注解 + * Annotation for marking Service implementation classes + */ +@Documented +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface InjectService +{ + /** + * 指定要实现的服务接口 + * Specify the service interface to implement + */ + Class[] value() default {}; +} diff --git a/core/datacap-plugin/src/main/java/io/edurt/datacap/plugin/service/ServiceAnnotationScanner.java b/core/datacap-plugin/src/main/java/io/edurt/datacap/plugin/service/ServiceAnnotationScanner.java new file mode 100644 index 00000000..e6788768 --- /dev/null +++ b/core/datacap-plugin/src/main/java/io/edurt/datacap/plugin/service/ServiceAnnotationScanner.java @@ -0,0 +1,48 @@ +package io.edurt.datacap.plugin.service; + +import com.google.common.collect.Sets; +import com.google.common.reflect.ClassPath; +import io.edurt.datacap.plugin.Service; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.util.Set; + +@Slf4j +public class ServiceAnnotationScanner +{ + /** + * 扫描指定包下带有 @InjectService 注解的类 + * Scan classes with @InjectService annotation in the specified package + * + * @param basePackage 基础包路径 + * @param basePackage base package path + * @param classLoader 类加载器 + * @param classLoader class loader + * @return 扫描到的服务类集合 + * @return scanned service class collection + */ + public static Set> scanServices(String basePackage, ClassLoader classLoader) + { + Set> services = Sets.newHashSet(); + try { + ClassPath classPath = ClassPath.from(classLoader); + for (ClassPath.ClassInfo classInfo : classPath.getTopLevelClassesRecursive(basePackage)) { + try { + Class clazz = classInfo.load(); + if (clazz.isAnnotationPresent(InjectService.class) && Service.class.isAssignableFrom(clazz)) { + services.add(clazz); + log.debug("Found service implementation class: {}", clazz.getName()); + } + } + catch (Throwable e) { + log.warn("Failed to load class: {}", classInfo.getName(), e); + } + } + } + catch (IOException e) { + log.error("Error scanning for services in package: {}", basePackage, e); + } + return services; + } +} diff --git a/core/datacap-plugin/src/main/java/io/edurt/datacap/plugin/service/ServiceSpiLoader.java b/core/datacap-plugin/src/main/java/io/edurt/datacap/plugin/service/ServiceSpiLoader.java index 0d89ae48..308c7121 100644 --- a/core/datacap-plugin/src/main/java/io/edurt/datacap/plugin/service/ServiceSpiLoader.java +++ b/core/datacap-plugin/src/main/java/io/edurt/datacap/plugin/service/ServiceSpiLoader.java @@ -15,6 +15,75 @@ import java.util.Set; @Slf4j public class ServiceSpiLoader { + /** + * 加载服务实现,同时支持SPI和注解方式 + * Load service implementations, supporting both SPI and annotation methods + * + * @param serviceType 服务类型(必须继承自Service) + * @param serviceType service type (must extend Service) + * @param basePackage 扫描注解的基础包路径 + * @param basePackage base package path for annotation scanning + * @param classLoader 类加载器 + * @param classLoader class loader + * @return 服务绑定 + * @return service bindings + */ + public static ServiceBindings loadServices(Class serviceType, String basePackage, ClassLoader classLoader) + { + ServiceBindings bindings = loadServices(serviceType, classLoader); + + // 扫描注解 + // Scan annotations + Set> annotatedServices = ServiceAnnotationScanner.scanServices(basePackage, classLoader); + log.info("Found {} annotated services", annotatedServices.size()); + log.info("Scanned annotated services from package: {}", basePackage); + log.debug("Annotated services: {}", annotatedServices); + for (Class serviceImpl : annotatedServices) { + InjectService annotation = serviceImpl.getAnnotation(InjectService.class); + if (annotation != null) { + Class[] serviceInterfaces = annotation.value(); + if (serviceInterfaces.length == 0) { + // 如果没有指定接口,使用类实现的所有接口 + // If no interface is specified, use all interfaces implemented by the class + addServiceBindings(bindings, (Class) serviceImpl, serviceType); + } + else { + // 添加指定的接口绑定 + // Add specified interface bindings + for (Class iface : serviceInterfaces) { + if (Service.class.isAssignableFrom(iface) && serviceType.isAssignableFrom(iface)) { + bindings.addBinding((Class) iface, (Class) serviceImpl); + log.debug("Added annotated binding: {} -> {}", iface.getName(), serviceImpl.getName()); + } + } + } + } + } + + return bindings; + } + + private static void addServiceBindings(ServiceBindings bindings, Class serviceImpl, Class serviceType) + { + // 添加直接绑定 + // Add direct binding + if (serviceType.isAssignableFrom(serviceImpl)) { + bindings.addBinding(serviceType, serviceImpl); + log.debug("Added direct binding: {} -> {}", serviceType.getName(), serviceImpl.getName()); + } + + // 检查并添加接口绑定 + // Check and add interface bindings + for (Class iface : getAllInterfaces(serviceImpl)) { + if (serviceType.isAssignableFrom(iface)) { + @SuppressWarnings("unchecked") + Class serviceInterface = (Class) iface; + bindings.addBinding(serviceInterface, serviceImpl); + log.debug("Added interface binding: {} -> {}", serviceInterface.getName(), serviceImpl.getName()); + } + } + } + /** * 加载服务实现 * Load service implementations diff --git a/test/datacap-test-plugin/src/main/java/io/edurt/datacap/test/AnnotationService.java b/test/datacap-test-plugin/src/main/java/io/edurt/datacap/test/AnnotationService.java new file mode 100644 index 00000000..58e00a65 --- /dev/null +++ b/test/datacap-test-plugin/src/main/java/io/edurt/datacap/test/AnnotationService.java @@ -0,0 +1,16 @@ +package io.edurt.datacap.test; + +import io.edurt.datacap.plugin.service.InjectService; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@InjectService +public class AnnotationService + implements DataService +{ + @Override + public void print() + { + log.info("Annotation Service"); + } +} diff --git a/test/datacap-test-plugin/src/test/java/io/edurt/datacap/test/DataServiceTest.java b/test/datacap-test-plugin/src/test/java/io/edurt/datacap/test/DataServiceTest.java index dfa203aa..9f79c893 100644 --- a/test/datacap-test-plugin/src/test/java/io/edurt/datacap/test/DataServiceTest.java +++ b/test/datacap-test-plugin/src/test/java/io/edurt/datacap/test/DataServiceTest.java @@ -48,6 +48,12 @@ public class DataServiceTest DataService logService = value.getService(DataService.class, "LogService"); logService.print(); + log.info("Found annotation Service"); + AnnotationService annotationService = value.getService(AnnotationService.class); + annotationService.print(); + DataService annotationService2 = value.getService(DataService.class, "AnnotationService"); + annotationService2.print(); + log.info("Get all Service"); Set services = value.getAllServices(DataService.class); services.forEach(DataService::print);