Skip to content

文档

https://pf4j.org/

简介

将java单体应用转换为模块化应用

DEMO

core

核心在于applicationContext的父子关系处理,

java
public class ApiSpringPlugin extends SpringPlugin {

    public ApiSpringPlugin(PluginWrapper wrapper) {
        super(wrapper);
    }

    @Override
    protected ApplicationContext createApplicationContext() {
        ApplicationContext applicationContextRoot = ((SpringPluginManager) getWrapper().getPluginManager()).getApplicationContext();
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        applicationContext.setClassLoader(getWrapper().getPluginClassLoader());
        applicationContext.setParent(applicationContextRoot);
        // 包扫描取插件包中指定注解的bean 注册到spring中 
        Set<Class> clazzFromAnnotation = ReadAnnotationUtils.getClazzFromAnnotation(getWrapper().getPluginClassLoader(), "com.demo",
                Configuration.class, Component.class);
        applicationContext.register(clazzFromAnnotation.toArray(new Class[0]));
        applicationContext.refresh();
        return applicationContext;
    }
}
java
public class ReadAnnotationUtils {
    private static final StandardEnvironment environment = new StandardEnvironment();

    private static final SimpleMetadataReaderFactory register = new SimpleMetadataReaderFactory();

    public static String getResourcePath(String packagePath) {
        if (StringUtils.isEmpty(packagePath)) return "";
        String resourcePath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
                + ClassUtils.convertClassNameToResourcePath(environment.resolveRequiredPlaceholders(packagePath))
                + '/' + "**/*.class";
        return resourcePath;
    }

    public static Set<Class> getClazzFromAnnotation(ClassLoader classLoader, String pkgPath, Class<? extends Annotation>... annoClazz) {
        //获取spring的包路径
        String pathPackage = getResourcePath(pkgPath);
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(classLoader);
        Set<Class> paths = new HashSet<>();
        Resource[] resources;
        try {
            //加载路径
            resources = resolver.getResources(pathPackage);
        } catch (IOException e) {
            //异常处理
            return new HashSet<>();
        }
        for (int i = 0; i < resources.length; i++) {
            Resource resource = resources[i];
            if (resource.isFile()) {
                continue;
            }
            MetadataReader metadataReader = null;
            try {
                //读取资源
                metadataReader = register.getMetadataReader(resource);
            } catch (IOException e) {
                continue;
            }
            //读取资源的注解配置
            AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
            //判断是否包含注解
            boolean has = false;
            for (Class<? extends Annotation> clazz : annoClazz) {
                if (annotationMetadata.hasAnnotation(clazz.getName())) {
                    has = true;
                    break;
                }
            }
            if (!has) {
                continue;
            }
            //类信息
            ClassMetadata classMetadata = metadataReader.getClassMetadata();
            //类全名
            String className = classMetadata.getClassName();
            try {
                //加载类
                Class<?> clazz = classLoader.loadClass(className);
                paths.add(clazz);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
        return paths;
    }
}

插件包中的请求体需要实现该接口.

java
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property = "type")
public interface BaseRequest {
}

声明注解标记所有接口处理类

java
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Extension
public @interface RestPlugin {
    String[] handlerType();
}

handler 注册

java

@Slf4j
public class HandlerUtil {

    private static ConcurrentHashMap<Class<? extends BaseRequest>, IHandler> handlerConcurrentHashMap = new ConcurrentHashMap<>();

    private static DefaultHandler defaultHandler = new DefaultHandler();

    /**
     * handler 执行
     */
    public static ResponseEntity<Object> execute(HandlerContext context, BaseRequest baseRequest) throws HandlerException {
        return HandlerUtil.handler(baseRequest).execute(context, baseRequest);
    }

    /**
     * handler注册
     */
    public static void register(List<IHandler> list, ObjectMapper objectMapper) {
        for (IHandler iHandler : list) {
            // handler 注册
            HandlerUtil.register(iHandler);
            Class clz = iHandler.clz();
            String[] s = iHandler.getClass().getAnnotation(RestPlugin.class).handlerType();
            // jackson sub handlerType 注册
            for (String s1 : s) {
                objectMapper.registerSubtypes(new NamedType(clz, s1));
            }
            log.info("register handler success  class:{},name:{}", clz, s);
        }
    }

    /**
     * handler获取
     */
    protected static IHandler handler(BaseRequest baseRequest) {
        IHandler handler = handlerConcurrentHashMap.get(baseRequest.getClass());
        if (Objects.isNull(handler)) {
            log.info("this handlerType is not exists");
            return defaultHandler;
        }
        return handler;
    }

    /**
     * handler注册
     */
    protected static void register(IHandler handler) {
        if (handler.handlerEnable()) {
            Class clz = handler.clz();
            log.info("register handler success :{}", clz.getSimpleName());
            handlerConcurrentHashMap.put(clz, handler);
        } else {
            log.info("this handler is disabled");
        }
    }


}

plugins-a

pom

需要注意的是,插件模块的依赖都应该是 provided

java
    <properties>
        <plugin.id>PluginA</plugin.id>
        <plugin.class>com.demo.rest.core.ApiSpringPlugin</plugin.class>
        <plugin.version>0.0.1</plugin.version>
        <plugin.provider>demo</plugin.provider>
        <plugin.dependencies/>
    </properties>
    
            
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <configuration>
                    <outputDirectory>../../plugins-a</outputDirectory>
                    <archive>
                        <manifestEntries>
                            <Plugin-Id>${plugin.id}</Plugin-Id>
                            <Plugin-Class>${plugin.class}</Plugin-Class>
                            <Plugin-Version>${plugin.version}</Plugin-Version>
                            <Plugin-Provider>${plugin.provider}</Plugin-Provider>
                            <Plugin-Dependencies>${plugin.dependencies}</Plugin-Dependencies>
                        </manifestEntries>
                    </archive>
                </configuration>
            </plugin>

插件包的yml

放在resource下, 不过这里存在一个问题,没办法使用 前缀,暂时的解决方案仍然使用 @Value 处理

java
@Data
@Configuration
@PropertySource(value = "classpath:test.yml")
public class TestProperty {
    @Value("${tokenKey}")
    private String tokenKey;
}

default handler

java
@RestPlugin(handlerType = "default")
public class DefaultHandler implements IHandler<DefaultHandler.DefaultRequest, String> {
    @Override
    public ResponseEntity<String> handler(HandlerContext context, DefaultRequest defaultRequest) throws HandlerException {
        return new ResponseEntity<>(200, "this is default handler");
    }

    public static class DefaultRequest implements BaseRequest {
    }
}