文档
简介
将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 {
}
}