feat(plugin): support annotation service

This commit is contained in:
qianmoQ 2024-11-22 12:37:02 +08:00
parent fd8f3112ee
commit a9ce7e989c
6 changed files with 189 additions and 4 deletions

View File

@ -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 <T extends Service> Set<T> getAllServices(Class<T> serviceClass)
{
validateInjector();
@ -316,8 +333,11 @@ public abstract class Plugin
if (pluginClassLoader != null) {
return PluginContextManager.runWithClassLoader(pluginClassLoader, () -> {
Set<T> 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<T> 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));

View File

@ -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 {};
}

View File

@ -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<Class<?>> scanServices(String basePackage, ClassLoader classLoader)
{
Set<Class<?>> 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;
}
}

View File

@ -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<? extends Service> serviceType, String basePackage, ClassLoader classLoader)
{
ServiceBindings bindings = loadServices(serviceType, classLoader);
// 扫描注解
// Scan annotations
Set<Class<?>> 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<? extends Service>) serviceImpl, serviceType);
}
else {
// 添加指定的接口绑定
// Add specified interface bindings
for (Class<?> iface : serviceInterfaces) {
if (Service.class.isAssignableFrom(iface) && serviceType.isAssignableFrom(iface)) {
bindings.addBinding((Class<? extends Service>) iface, (Class<? extends Service>) serviceImpl);
log.debug("Added annotated binding: {} -> {}", iface.getName(), serviceImpl.getName());
}
}
}
}
}
return bindings;
}
private static void addServiceBindings(ServiceBindings bindings, Class<? extends Service> serviceImpl, Class<? extends Service> 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<? extends Service> serviceInterface = (Class<? extends Service>) iface;
bindings.addBinding(serviceInterface, serviceImpl);
log.debug("Added interface binding: {} -> {}", serviceInterface.getName(), serviceImpl.getName());
}
}
}
/**
* 加载服务实现
* Load service implementations

View File

@ -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");
}
}

View File

@ -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<DataService> services = value.getAllServices(DataService.class);
services.forEach(DataService::print);