-
-
- org.sonatype.plugins
- nexus-staging-maven-plugin
- 1.6.7
- true
-
- ossrh
- https://oss.sonatype.org/
- true
-
-
-
-
- org.apache.maven.plugins
- maven-gpg-plugin
- 1.5
-
-
- sign-artifacts
- verify
-
- sign
-
-
-
-
+
+
org.codehaus.mojo
versions-maven-plugin
@@ -177,8 +140,8 @@
maven-compiler-plugin
2.0.2
-
- 1.8
+
+ ${java.version}
UTF-8
@@ -265,6 +228,7 @@
+
ossrh
@@ -276,7 +240,43 @@
https://oss.sonatype.org/service/local/staging/deploy/maven2/
+
+
+
+ deploy
+
+
+
+
+ org.sonatype.plugins
+ nexus-staging-maven-plugin
+ 1.6.7
+ true
+
+ ossrh
+ https://oss.sonatype.org/
+ true
+
+
+
+
+ org.apache.maven.plugins
+ maven-gpg-plugin
+ 1.5
+
+
+ sign-artifacts
+ verify
+
+ sign
+
+
+
+
+
+
+
diff --git a/jarslink-api/src/main/java/com/alipay/jarslink/api/ApplicationContextAware.java b/jarslink-api/src/main/java/com/alipay/jarslink/api/ApplicationContextAware.java
new file mode 100644
index 0000000..43875f5
--- /dev/null
+++ b/jarslink-api/src/main/java/com/alipay/jarslink/api/ApplicationContextAware.java
@@ -0,0 +1,19 @@
+package com.alipay.jarslink.api;
+
+import org.springframework.context.ConfigurableApplicationContext;
+
+/**
+ * 加载模块时在创建完模块的spring-context后刷新spring-context前调用,用于做一些拦截处理
+ *
+ * @author joe
+ * @version 2018.04.04 10:54
+ */
+public interface ApplicationContextAware extends JarslinkAware{
+ /**
+ * 加载模块时在创建完模块的spring-context后刷新spring-context前调用
+ *
+ * @param context 当前加载的模块对应的configurableApplicationContext
+ * @param moduleConfig 当前加载的模块对应的配置
+ */
+ void setConfigurableApplicationContext(ConfigurableApplicationContext context, ModuleConfig moduleConfig);
+}
diff --git a/jarslink-api/src/main/java/com/alipay/jarslink/api/ApplicationContextPostProcessor.java b/jarslink-api/src/main/java/com/alipay/jarslink/api/ApplicationContextPostProcessor.java
new file mode 100644
index 0000000..cb63bdb
--- /dev/null
+++ b/jarslink-api/src/main/java/com/alipay/jarslink/api/ApplicationContextPostProcessor.java
@@ -0,0 +1,19 @@
+package com.alipay.jarslink.api;
+
+import org.springframework.context.ConfigurableApplicationContext;
+
+/**
+ * 加载模块时在创建完模块的spring-context并且刷新spring-context后调用,用于做一些拦截处理
+ *
+ * @author joe
+ * @version 2018.04.04 10:53
+ */
+public interface ApplicationContextPostProcessor extends JarslinkPostProcessor {
+ /**
+ * 加载模块时在创建完模块的spring-context并且刷新spring-context后调用
+ *
+ * @param context 当前加载的模块对应的configurableApplicationContext
+ * @param moduleConfig 当前加载的模块对应的配置
+ */
+ void setConfigurableApplicationContext(ConfigurableApplicationContext context, ModuleConfig moduleConfig);
+}
diff --git a/jarslink-api/src/main/java/com/alipay/jarslink/api/JarslinkAware.java b/jarslink-api/src/main/java/com/alipay/jarslink/api/JarslinkAware.java
new file mode 100644
index 0000000..bbe69cb
--- /dev/null
+++ b/jarslink-api/src/main/java/com/alipay/jarslink/api/JarslinkAware.java
@@ -0,0 +1,10 @@
+package com.alipay.jarslink.api;
+
+/**
+ * 所有Aware的顶级接口
+ *
+ * @author joe
+ * @version 2018.04.04 11:05
+ */
+public interface JarslinkAware {
+}
diff --git a/jarslink-api/src/main/java/com/alipay/jarslink/api/JarslinkPostProcessor.java b/jarslink-api/src/main/java/com/alipay/jarslink/api/JarslinkPostProcessor.java
new file mode 100644
index 0000000..71794d2
--- /dev/null
+++ b/jarslink-api/src/main/java/com/alipay/jarslink/api/JarslinkPostProcessor.java
@@ -0,0 +1,10 @@
+package com.alipay.jarslink.api;
+
+/**
+ * 所有PostProcessor的顶级接口
+ *
+ * @author joe
+ * @version 2018.04.04 11:06
+ */
+public interface JarslinkPostProcessor {
+}
diff --git a/jarslink-api/src/main/java/com/alipay/jarslink/api/ModuleAware.java b/jarslink-api/src/main/java/com/alipay/jarslink/api/ModuleAware.java
new file mode 100644
index 0000000..555268b
--- /dev/null
+++ b/jarslink-api/src/main/java/com/alipay/jarslink/api/ModuleAware.java
@@ -0,0 +1,16 @@
+package com.alipay.jarslink.api;
+
+/**
+ * 在加载模块前调用
+ *
+ * @author joe
+ * @version 2018.04.04 10:59
+ */
+public interface ModuleAware extends JarslinkAware {
+ /**
+ * 加载模块前调用
+ *
+ * @param moduleConfig 模块配置
+ */
+ void setModuleConfig(ModuleConfig moduleConfig);
+}
diff --git a/jarslink-api/src/main/java/com/alipay/jarslink/api/ModuleConfig.java b/jarslink-api/src/main/java/com/alipay/jarslink/api/ModuleConfig.java
index 14ed128..aef7146 100644
--- a/jarslink-api/src/main/java/com/alipay/jarslink/api/ModuleConfig.java
+++ b/jarslink-api/src/main/java/com/alipay/jarslink/api/ModuleConfig.java
@@ -17,6 +17,7 @@
*/
package com.alipay.jarslink.api;
+import com.google.common.base.Function;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.commons.lang3.builder.EqualsBuilder;
@@ -28,7 +29,6 @@
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
-import java.util.stream.Collectors;
import static com.google.common.base.Preconditions.checkNotNull;
@@ -62,7 +62,7 @@ public class ModuleConfig extends ToStringObject {
*
* xml中的bean不能依赖注解bean,注解bean可以依赖xml定义的bean
*/
- private Set scanPackages = new CopyOnWriteArraySet<>();
+ private Set scanPackages = new CopyOnWriteArraySet();
/**
* 模块的版本,如1.0.0.20120609 版本变化会触发模块重新部署
@@ -102,7 +102,13 @@ public List getModuleUrl() {
public List getModuleUrlPath() {
List moduleUrls = Lists.newArrayList();
- moduleUrls.addAll(moduleUrl.stream().map(URL::toString).collect(Collectors.toList()));
+ List list = Lists.transform(moduleUrl, new Function() {
+ @Override
+ public String apply(URL input) {
+ return input.toString();
+ }
+ });
+ moduleUrls.addAll(list);
return moduleUrls;
}
diff --git a/jarslink-api/src/main/java/com/alipay/jarslink/api/ModuleLoader.java b/jarslink-api/src/main/java/com/alipay/jarslink/api/ModuleLoader.java
index 461c321..e0e6081 100644
--- a/jarslink-api/src/main/java/com/alipay/jarslink/api/ModuleLoader.java
+++ b/jarslink-api/src/main/java/com/alipay/jarslink/api/ModuleLoader.java
@@ -29,7 +29,6 @@ public interface ModuleLoader {
* 根据配置加载一个模块,创建一个新的ClassLoadr加载jar里的class,初始化Spring ApplicationContext等
*
* @param moduleConfig 模块配置信息
- *
* @return 加载成功的模块
*/
Module load(ModuleConfig moduleConfig);
@@ -41,4 +40,59 @@ public interface ModuleLoader {
*/
void unload(Module module);
+ /**
+ * 注册ApplicationContext前置处理器
+ *
+ * @param aware 要注册的前置处理器
+ */
+ void registerAware(ApplicationContextAware aware);
+
+ /**
+ * 注册Module前置处理器
+ *
+ * @param aware 要注册的前置处理器
+ */
+ void registerAware(ModuleAware aware);
+
+ /**
+ * 注册ApplicationContext后处理器
+ *
+ * @param postProcessor 要注册的后处理器
+ */
+ void registerPostProcessor(ApplicationContextPostProcessor postProcessor);
+
+ /**
+ * 注册Module后处理器
+ *
+ * @param postProcessor 要注册的后处理器
+ */
+ void registerPostProcessor(ModulePostProcessor postProcessor);
+
+ /**
+ * 移除ApplicationContext前置处理器
+ *
+ * @param aware 要移除的前置处理器
+ */
+ void unRegisterAware(ApplicationContextAware aware);
+
+ /**
+ * 移除Module前置处理器
+ *
+ * @param aware 要移除的前置处理器
+ */
+ void unRegisterAware(ModuleAware aware);
+
+ /**
+ * 移除ApplicationContext后处理器
+ *
+ * @param postProcessor 要移除的后处理器
+ */
+ void unRegisterPostProcessor(ApplicationContextPostProcessor postProcessor);
+
+ /**
+ * 移除Module后处理器
+ *
+ * @param postProcessor 要移除的后处理器
+ */
+ void unRegisterPostProcessor(ModulePostProcessor postProcessor);
}
diff --git a/jarslink-api/src/main/java/com/alipay/jarslink/api/ModuleManager.java b/jarslink-api/src/main/java/com/alipay/jarslink/api/ModuleManager.java
index 4d8aa24..249019f 100644
--- a/jarslink-api/src/main/java/com/alipay/jarslink/api/ModuleManager.java
+++ b/jarslink-api/src/main/java/com/alipay/jarslink/api/ModuleManager.java
@@ -38,7 +38,8 @@ public interface ModuleManager {
/**
* 根据模块名和版本查找Module
- * @param name 模块名称
+ *
+ * @param name 模块名称
* @param version 模块版本号
* @return
*/
@@ -68,10 +69,12 @@ public interface ModuleManager {
List getModules();
/**
- * 注册一个Module
+ * 注册一个Module,同时该module将会被设置为默认module
*
* @param module 模块
* @return 旧模块, 如果没有旧模块则返回null
+ *
+ * 如果当前系统中已经存在该模块那么将会抛出该异常
*/
Module register(Module module);
@@ -86,8 +89,8 @@ public interface ModuleManager {
/**
* 移除一个Module
*
- * @param name 模块名
- * @param version 版本号
+ * @param name 模块名
+ * @param version 版本号
* @return 被移除的模块
*/
Module remove(String name, String version);
diff --git a/jarslink-api/src/main/java/com/alipay/jarslink/api/ModulePostProcessor.java b/jarslink-api/src/main/java/com/alipay/jarslink/api/ModulePostProcessor.java
new file mode 100644
index 0000000..5190347
--- /dev/null
+++ b/jarslink-api/src/main/java/com/alipay/jarslink/api/ModulePostProcessor.java
@@ -0,0 +1,17 @@
+package com.alipay.jarslink.api;
+
+/**
+ * module创建完后会调用该回调
+ *
+ * @author joe
+ * @version 2018.04.04 11:01
+ */
+public interface ModulePostProcessor extends JarslinkPostProcessor {
+ /**
+ * module创建完后调用
+ *
+ * @param module 模块
+ * @param moduleConfig 模块对应的配置
+ */
+ void setModule(Module module, ModuleConfig moduleConfig);
+}
diff --git a/jarslink-api/src/main/java/com/alipay/jarslink/api/impl/AbstractModuleRefreshScheduler.java b/jarslink-api/src/main/java/com/alipay/jarslink/api/impl/AbstractModuleRefreshScheduler.java
index 27c7055..8fd6fc4 100644
--- a/jarslink-api/src/main/java/com/alipay/jarslink/api/impl/AbstractModuleRefreshScheduler.java
+++ b/jarslink-api/src/main/java/com/alipay/jarslink/api/impl/AbstractModuleRefreshScheduler.java
@@ -174,7 +174,7 @@ private void refreshModuleConfigs() {
private Collection filterEnabledModule() {
List moduleConfigs = queryModuleConfigs();
if (moduleConfigs == null || moduleConfigs.isEmpty()) {
- return new ArrayList<>();
+ return new ArrayList();
}
return Collections2.filter(moduleConfigs, new Predicate() {
@Override
diff --git a/jarslink-api/src/main/java/com/alipay/jarslink/api/impl/ModuleClassLoader.java b/jarslink-api/src/main/java/com/alipay/jarslink/api/impl/ModuleClassLoader.java
index 722234c..5494b04 100644
--- a/jarslink-api/src/main/java/com/alipay/jarslink/api/impl/ModuleClassLoader.java
+++ b/jarslink-api/src/main/java/com/alipay/jarslink/api/impl/ModuleClassLoader.java
@@ -49,7 +49,7 @@ public class ModuleClassLoader extends URLClassLoader {
/**
* 需要排除的包
*/
- private final Set excludedPackages = new HashSet<>();
+ private final Set excludedPackages = new HashSet();
/**
* 需要子加载器优先加载的包
diff --git a/jarslink-api/src/main/java/com/alipay/jarslink/api/impl/ModuleLoaderImpl.java b/jarslink-api/src/main/java/com/alipay/jarslink/api/impl/ModuleLoaderImpl.java
index 991998f..7c8cfc7 100644
--- a/jarslink-api/src/main/java/com/alipay/jarslink/api/impl/ModuleLoaderImpl.java
+++ b/jarslink-api/src/main/java/com/alipay/jarslink/api/impl/ModuleLoaderImpl.java
@@ -17,9 +17,7 @@
*/
package com.alipay.jarslink.api.impl;
-import com.alipay.jarslink.api.Module;
-import com.alipay.jarslink.api.ModuleConfig;
-import com.alipay.jarslink.api.ModuleLoader;
+import com.alipay.jarslink.api.*;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
@@ -41,6 +39,7 @@
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
@@ -60,7 +59,8 @@ public class ModuleLoaderImpl implements ModuleLoader, ApplicationContextAware {
/**
* Spring bean文件所在目录,不同的路径确保能取到资源
*/
- private static String[] SPRING_XML_PATTERN = {"classpath*:META-INF/spring/*.xml", "classpath*:*META-INF/spring/*.xml"};
+ private static String[] SPRING_XML_PATTERN = {"classpath*:META-INF/spring/*.xml", "classpath*:*META-INF/spring/*" +
+ ".xml"};
/**
* 模块版本属性
@@ -82,6 +82,13 @@ public class ModuleLoaderImpl implements ModuleLoader, ApplicationContextAware {
*/
private ApplicationContext applicationContext;
+ private final List applicationContextAwares = new
+ CopyOnWriteArrayList();
+ private final List moduleAwares = new CopyOnWriteArrayList();
+ private final List applicationContextPostProcessors = new
+ CopyOnWriteArrayList();
+ private final List modulePostProcessors = new CopyOnWriteArrayList();
+
@Override
public Module load(ModuleConfig moduleConfig) {
if (LOGGER.isInfoEnabled()) {
@@ -92,13 +99,17 @@ public Module load(ModuleConfig moduleConfig) {
LOGGER.info("Local jars: {}", tempFileJarURLs);
}
+ doModuleAware(moduleConfig);
+
ConfigurableApplicationContext moduleApplicationContext = loadModuleApplication(moduleConfig, tempFileJarURLs);
if (LOGGER.isInfoEnabled()) {
LOGGER.info("Loading module complete:{}", moduleConfig);
}
- return new SpringModule(moduleConfig, moduleConfig.getVersion(), moduleConfig.getName(),
+ Module module = new SpringModule(moduleConfig, moduleConfig.getVersion(), moduleConfig.getName(),
moduleApplicationContext);
+ doModulePostProcessor(module, moduleConfig);
+ return module;
}
@Override
@@ -108,6 +119,78 @@ public void unload(Module module) {
}
}
+ @Override
+ public void registerAware(com.alipay.jarslink.api.ApplicationContextAware aware) {
+ checkArgument(aware != null, "aware must be not null");
+ applicationContextAwares.add(aware);
+ }
+
+ @Override
+ public void registerAware(ModuleAware aware) {
+ checkArgument(aware != null, "aware must be not null");
+ moduleAwares.add(aware);
+ }
+
+ @Override
+ public void registerPostProcessor(ApplicationContextPostProcessor postProcessor) {
+ checkArgument(postProcessor != null, "PostProcessor must be not null");
+ applicationContextPostProcessors.add(postProcessor);
+ }
+
+ @Override
+ public void registerPostProcessor(ModulePostProcessor postProcessor) {
+ checkArgument(postProcessor != null, "PostProcessor must be not null");
+ modulePostProcessors.add(postProcessor);
+ }
+
+ @Override
+ public void unRegisterAware(com.alipay.jarslink.api.ApplicationContextAware aware) {
+ if (aware == null) return;
+ applicationContextAwares.remove(aware);
+ }
+
+ @Override
+ public void unRegisterAware(ModuleAware aware) {
+ if (aware == null) return;
+ moduleAwares.remove(aware);
+ }
+
+ @Override
+ public void unRegisterPostProcessor(ApplicationContextPostProcessor postProcessor) {
+ if (postProcessor == null) return;
+ applicationContextPostProcessors.remove(postProcessor);
+ }
+
+ @Override
+ public void unRegisterPostProcessor(ModulePostProcessor postProcessor) {
+ if (postProcessor == null) return;
+ modulePostProcessors.remove(postProcessor);
+ }
+
+ private void doApplicationContextAware(ConfigurableApplicationContext context, ModuleConfig moduleConfig) {
+ for (com.alipay.jarslink.api.ApplicationContextAware aware : applicationContextAwares) {
+ aware.setConfigurableApplicationContext(context, moduleConfig);
+ }
+ }
+
+ private void doModuleAware(ModuleConfig moduleConfig) {
+ for (ModuleAware aware : moduleAwares) {
+ aware.setModuleConfig(moduleConfig);
+ }
+ }
+
+ private void doApplicationContextPostProcessor(ConfigurableApplicationContext context, ModuleConfig moduleConfig) {
+ for (ApplicationContextPostProcessor postProcessor : applicationContextPostProcessors) {
+ postProcessor.setConfigurableApplicationContext(context, moduleConfig);
+ }
+ }
+
+ private void doModulePostProcessor(Module module, ModuleConfig moduleConfig) {
+ for (ModulePostProcessor postProcessor : modulePostProcessors) {
+ postProcessor.setModule(module, moduleConfig);
+ }
+ }
+
/**
* 根据本地临时文件Jar,初始化模块自己的ClassLoader,初始化Spring Application Context,同时要设置当前线程上下文的ClassLoader问模块的ClassLoader
*
@@ -149,7 +232,9 @@ private ConfigurableApplicationContext loadModuleApplication(ModuleConfig module
moduleConfig.getVersion());
}
((DefaultResourceLoader) context).setClassLoader(moduleClassLoader);
+ doApplicationContextAware(context, moduleConfig);
context.refresh();
+ doApplicationContextPostProcessor(context, moduleConfig);
return context;
} catch (Throwable e) {
CachedIntrospectionResults.clearClassLoader(moduleClassLoader);
@@ -203,7 +288,7 @@ private String[] findSpringConfigs(List tempFileJarURLs, ClassLoader mod
try {
PathMatchingResourcePatternResolver pmr = new PathMatchingResourcePatternResolver(moduleClassLoader);
Resource[] resources = ImmutableSet.builder().add(pmr.getResources(SPRING_XML_PATTERN[0])).add(pmr
- .getResources(SPRING_XML_PATTERN[1])).build().toArray(new Resource[] {});
+ .getResources(SPRING_XML_PATTERN[1])).build().toArray(new Resource[]{});
checkNotNull(resources, "resources is null");
checkArgument(resources.length > 0, "resources length is 0");
// 因为ClassLoader是树形结构,这里会找到ModuleClassLoader以及其父类中所有符合规范的spring配置文件,所以这里需要过滤,只需要Module Jar中的
diff --git a/jarslink-api/src/main/java/com/alipay/jarslink/api/impl/ModuleManagerImpl.java b/jarslink-api/src/main/java/com/alipay/jarslink/api/impl/ModuleManagerImpl.java
index 61f00f5..2f9db43 100644
--- a/jarslink-api/src/main/java/com/alipay/jarslink/api/impl/ModuleManagerImpl.java
+++ b/jarslink-api/src/main/java/com/alipay/jarslink/api/impl/ModuleManagerImpl.java
@@ -19,6 +19,7 @@
import com.alipay.jarslink.api.Module;
import com.alipay.jarslink.api.ModuleManager;
+import com.alipay.jarslink.api.ModuleRuntimeException;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
@@ -39,17 +40,15 @@
*
* @author tengfei.fangtf
* @version $Id: ModuleManagerImpl.java, v 0.1 Mar 20, 2017 4:04:32 PM tengfei.fangtf Exp $
-
*/
public class ModuleManagerImpl implements ModuleManager, DisposableBean {
- private static final Logger LOGGER = LoggerFactory
- .getLogger(ModuleManagerImpl.class);
+ private static final Logger LOGGER = LoggerFactory.getLogger(ModuleManagerImpl.class);
/**
* 已注册的所有模块,key:moduleName upperCase
*/
- private final ConcurrentHashMap allModules = new ConcurrentHashMap();
+ private final Map allModules = new ConcurrentHashMap();
private RuntimeModule getRuntimeModule(String name) {
RuntimeModule runtimeModule = allModules.get(name.toUpperCase());
@@ -58,41 +57,39 @@ private RuntimeModule getRuntimeModule(String name) {
@Override
public List getModules() {
- List modules = Lists.newArrayList();
-
- for (String name : allModules.keySet()) {
- RuntimeModule runtimeModule = getRuntimeModule((String) name);
- for (String version : runtimeModule.getModules().keySet()) {
- modules.add(runtimeModule.getModules().get(version));
- }
+ final List modules = Lists.newArrayList();
+ for (Map.Entry moduleEntry : allModules.entrySet()) {
+ RuntimeModule runtimeModule = moduleEntry.getValue();
+ modules.addAll(runtimeModule.getModules().values());
}
-
- return ImmutableList
- .copyOf(filter(modules, instanceOf(SpringModule.class)));
+ return ImmutableList.copyOf(filter(modules, instanceOf(SpringModule.class)));
}
@Override
public Module find(String name) {
checkNotNull(name, "module name is null");
- String defaultVersion = getDefaultVersion(name);
+ RuntimeModule runtimeModule = getRuntimeModule(name);
+ String defaultVersion = runtimeModule.getDefaultVersion();
checkNotNull(defaultVersion, "module default version is null");
- return find(name, defaultVersion);
+ return runtimeModule.getModule(defaultVersion);
}
- private String getDefaultVersion(String name) {return getRuntimeModule((String) name).getDefaultVersion();}
+ private String getDefaultVersion(String name) {
+ return getRuntimeModule(name).getDefaultVersion();
+ }
@Override
public Module find(String name, String version) {
checkNotNull(name, "module name is null");
checkNotNull(version, "module version is null");
- return getRuntimeModule((String) name).getModule(version);
+ return getRuntimeModule(name).getModule(version);
}
@Override
public void activeVersion(String name, String version) {
checkNotNull(name, "module name is null");
checkNotNull(version, "module version is null");
- getRuntimeModule((String) name).setDefaultVersion(version);
+ getRuntimeModule(name).setDefaultVersion(version);
}
@Override
@@ -102,19 +99,19 @@ public String getActiveVersion(String name) {
}
@Override
- public Module register(Module module) {
+ public synchronized Module register(Module module) {
checkNotNull(module, "module is null");
- String name = module.getName();
- String version = module.getVersion();
+ final String name = module.getName();
+ final String version = module.getVersion();
if (LOGGER.isInfoEnabled()) {
LOGGER.info("register Module: {}-{}", name, version);
}
- //same module and same version can not register
- Module registeredModule = getRuntimeModule(name).getModule(version);
- if (registeredModule != null) {
- return null;
- }
+ //此处如果不加读锁,那么多线程情况下有可能同时进入runtimeModule.getModules().isEmpty(),此时多个线程
+ //将同时调用allModules.put(name.toUpperCase(), runtimeModule),而由于key相同,此时只有一个线程会成功,而
+ //失败的线程也无法得知自己已经失败
+ //如果当前系统存在该模块那么应该抛出异常,否则外部调用无法得知是否注册成功
+ checkDuplicate(name, version);
RuntimeModule runtimeModule = getRuntimeModule(name);
Module oldModule = null;
@@ -127,28 +124,28 @@ public Module register(Module module) {
oldModule = runtimeModule.getDefaultModule();
runtimeModule.addModule(module).setDefaultVersion(version);
// remove module old version
- if (oldModule != null && module.getModuleConfig().isNeedUnloadOldVersion() && !runtimeModule.getModules().isEmpty()) {
+ if (oldModule != null && module.getModuleConfig().isNeedUnloadOldVersion()) {
runtimeModule.getModules().remove(oldModule.getVersion());
}
}
-
return oldModule;
}
+
@Override
public Module remove(String name) {
checkNotNull(name, "module name is null");
if (LOGGER.isInfoEnabled()) {
LOGGER.info("Remove Module: {}", name);
}
- return remove(name, getRuntimeModule((String) name).getDefaultVersion());
+ return remove(name, getRuntimeModule(name).getDefaultVersion());
}
@Override
public Module remove(String name, String version) {
checkNotNull(name, "module name is null");
checkNotNull(version, "module version is null");
- return getRuntimeModule((String) name).getModules().remove(version);
+ return getRuntimeModule(name).getModules().remove(version);
}
@Override
@@ -160,20 +157,33 @@ public void destroy() throws Exception {
LOGGER.error("Failed to destroy module: " + each.getName(), e);
}
}
+
allModules.clear();
}
@Override
public Map getErrorModuleContext() {
-
- Map result = Maps.newHashMap();
-
+ final Map result = Maps.newHashMap();
for (String name : allModules.keySet()) {
- RuntimeModule runtimeModule = getRuntimeModule((String) name);
+ RuntimeModule runtimeModule = getRuntimeModule(name);
result.put(name, runtimeModule.getErrorContext());
}
-
return result;
}
+ /**
+ * 检查当前是否有指定模块的指定版本存在系统
+ *
+ * @param name 模块名
+ * @param version 版本号
+ * @throws ModuleRuntimeException 如果当前系统存在该模块的指定版本将抛出异常
+ */
+ private void checkDuplicate(String name, String version) throws ModuleRuntimeException {
+ RuntimeModule runtimeModule = allModules.get(name.toUpperCase());
+ runtimeModule = runtimeModule != null ? runtimeModule : new RuntimeModule();
+ Module registeredModule = runtimeModule.getModule(version);
+ if (registeredModule != null) {
+ throw new ModuleRuntimeException("duplicate module :[" + name + ":" + version + "]");
+ }
+ }
}
\ No newline at end of file
diff --git a/jarslink-api/src/test/java/com/alipay/jarslink/api/impl/ApplicationContextAwareImpl.java b/jarslink-api/src/test/java/com/alipay/jarslink/api/impl/ApplicationContextAwareImpl.java
new file mode 100644
index 0000000..fd094c9
--- /dev/null
+++ b/jarslink-api/src/test/java/com/alipay/jarslink/api/impl/ApplicationContextAwareImpl.java
@@ -0,0 +1,21 @@
+package com.alipay.jarslink.api.impl;
+
+import com.alipay.jarslink.api.ApplicationContextAware;
+import com.alipay.jarslink.api.ModuleConfig;
+import org.junit.Assert;
+import org.springframework.context.ConfigurableApplicationContext;
+
+/**
+ * 测试用
+ *
+ * @author joe
+ * @version 2018.04.04 11:44
+ */
+public class ApplicationContextAwareImpl implements ApplicationContextAware {
+ @Override
+ public void setConfigurableApplicationContext(ConfigurableApplicationContext context, ModuleConfig moduleConfig) {
+ moduleConfig.withVersion("2.0");
+ Assert.assertNotNull(context);
+ Assert.assertNotNull(moduleConfig);
+ }
+}
diff --git a/jarslink-api/src/test/java/com/alipay/jarslink/api/impl/ApplicationContextPostProcessorImpl.java b/jarslink-api/src/test/java/com/alipay/jarslink/api/impl/ApplicationContextPostProcessorImpl.java
new file mode 100644
index 0000000..183f9f4
--- /dev/null
+++ b/jarslink-api/src/test/java/com/alipay/jarslink/api/impl/ApplicationContextPostProcessorImpl.java
@@ -0,0 +1,21 @@
+package com.alipay.jarslink.api.impl;
+
+import com.alipay.jarslink.api.ApplicationContextPostProcessor;
+import com.alipay.jarslink.api.ModuleConfig;
+import org.junit.Assert;
+import org.springframework.context.ConfigurableApplicationContext;
+
+/**
+ * 测试用
+ *
+ * @author joe
+ * @version 2018.04.04 11:44
+ */
+public class ApplicationContextPostProcessorImpl implements ApplicationContextPostProcessor {
+ @Override
+ public void setConfigurableApplicationContext(ConfigurableApplicationContext context, ModuleConfig moduleConfig) {
+ moduleConfig.withVersion("2.0");
+ Assert.assertNotNull(context);
+ Assert.assertNotNull(moduleConfig);
+ }
+}
diff --git a/jarslink-api/src/test/java/com/alipay/jarslink/api/impl/ConcurrentModuleManagerImpl.java b/jarslink-api/src/test/java/com/alipay/jarslink/api/impl/ConcurrentModuleManagerImpl.java
new file mode 100644
index 0000000..3a5b902
--- /dev/null
+++ b/jarslink-api/src/test/java/com/alipay/jarslink/api/impl/ConcurrentModuleManagerImpl.java
@@ -0,0 +1,179 @@
+package com.alipay.jarslink.api.impl;
+
+import com.alipay.jarslink.api.Module;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Predicates.instanceOf;
+import static com.google.common.collect.Iterables.filter;
+
+/**
+ * @author joe
+ * @version 2018.04.01 22:54
+ */
+@Component("testModuleManager")
+public class ConcurrentModuleManagerImpl extends ModuleManagerImpl {
+
+ private CountDownLatch countDownLatch = null;
+ private static final Logger LOGGER = LoggerFactory.getLogger(ModuleManagerImpl.class);
+
+ public void activeConcurrentTest() {
+ countDownLatch = new CountDownLatch(2);
+ }
+
+ public void disableConcurrentTest() {
+ countDownLatch = null;
+ }
+
+ /**
+ * 已注册的所有模块,key:moduleName upperCase
+ */
+ private final ConcurrentHashMap allModules = new ConcurrentHashMap();
+
+ private RuntimeModule getRuntimeModule(String name) {
+ RuntimeModule runtimeModule = allModules.get(name.toUpperCase());
+ return runtimeModule != null ? runtimeModule : new RuntimeModule();
+ }
+
+ @Override
+ public List getModules() {
+ List modules = Lists.newArrayList();
+
+ for (String name : allModules.keySet()) {
+ RuntimeModule runtimeModule = getRuntimeModule((String) name);
+ for (String version : runtimeModule.getModules().keySet()) {
+ modules.add(runtimeModule.getModules().get(version));
+ }
+ }
+
+ return ImmutableList.copyOf(filter(modules, instanceOf(SpringModule.class)));
+ }
+
+ @Override
+ public Module find(String name) {
+ checkNotNull(name, "module name is null");
+ String defaultVersion = getDefaultVersion(name);
+ checkNotNull(defaultVersion, "module default version is null");
+ return find(name, defaultVersion);
+ }
+
+ private String getDefaultVersion(String name) {
+ return getRuntimeModule((String) name).getDefaultVersion();
+ }
+
+ @Override
+ public Module find(String name, String version) {
+ checkNotNull(name, "module name is null");
+ checkNotNull(version, "module version is null");
+ return getRuntimeModule((String) name).getModule(version);
+ }
+
+ @Override
+ public void activeVersion(String name, String version) {
+ checkNotNull(name, "module name is null");
+ checkNotNull(version, "module version is null");
+ getRuntimeModule((String) name).setDefaultVersion(version);
+ }
+
+ @Override
+ public String getActiveVersion(String name) {
+ checkNotNull(name, "module name is null");
+ return getDefaultVersion(name);
+ }
+
+ @Override
+ public Module register(Module module) {
+ checkNotNull(module, "module is null");
+ String name = module.getName();
+ String version = module.getVersion();
+ if (LOGGER.isInfoEnabled()) {
+ LOGGER.info("register Module: {}-{}", name, version);
+ }
+
+ //same module and same version can not register
+ Module registeredModule = getRuntimeModule(name).getModule(version);
+ if (registeredModule != null) {
+ return null;
+ }
+
+ RuntimeModule runtimeModule = getRuntimeModule(name);
+ Module oldModule = null;
+ if (countDownLatch != null) {
+ countDownLatch.countDown();
+ try {
+ countDownLatch.await();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ //module frist register
+ if (runtimeModule.getModules().isEmpty()) {
+ runtimeModule = new RuntimeModule().withName(name).withDefaultVersion(version).addModule(module);
+ allModules.put(name.toUpperCase(), runtimeModule);
+ } else {
+ //the same module to register again
+ oldModule = runtimeModule.getDefaultModule();
+ runtimeModule.addModule(module).setDefaultVersion(version);
+ // remove module old version
+ if (oldModule != null && module.getModuleConfig().isNeedUnloadOldVersion() && !runtimeModule.getModules()
+ .isEmpty()) {
+ runtimeModule.getModules().remove(oldModule.getVersion());
+ }
+ }
+
+ return oldModule;
+ }
+
+ @Override
+ public Module remove(String name) {
+ checkNotNull(name, "module name is null");
+ if (LOGGER.isInfoEnabled()) {
+ LOGGER.info("Remove Module: {}", name);
+ }
+ return remove(name, getRuntimeModule((String) name).getDefaultVersion());
+ }
+
+ @Override
+ public Module remove(String name, String version) {
+ checkNotNull(name, "module name is null");
+ checkNotNull(version, "module version is null");
+ return getRuntimeModule((String) name).getModules().remove(version);
+ }
+
+ @Override
+ public void destroy() throws Exception {
+ for (Module each : getModules()) {
+ try {
+ each.destroy();
+ } catch (Exception e) {
+ LOGGER.error("Failed to destroy module: " + each.getName(), e);
+ }
+ }
+ allModules.clear();
+ }
+
+ @Override
+ public Map getErrorModuleContext() {
+
+ Map result = Maps.newHashMap();
+
+ for (String name : allModules.keySet()) {
+ RuntimeModule runtimeModule = getRuntimeModule((String) name);
+ result.put(name, runtimeModule.getErrorContext());
+ }
+
+ return result;
+ }
+
+
+}
diff --git a/jarslink-api/src/test/java/com/alipay/jarslink/api/impl/ConcurrentModuleManagerImplTest.java b/jarslink-api/src/test/java/com/alipay/jarslink/api/impl/ConcurrentModuleManagerImplTest.java
new file mode 100644
index 0000000..55a01f8
--- /dev/null
+++ b/jarslink-api/src/test/java/com/alipay/jarslink/api/impl/ConcurrentModuleManagerImplTest.java
@@ -0,0 +1,79 @@
+package com.alipay.jarslink.api.impl;
+
+import com.alipay.jarslink.api.Module;
+import com.alipay.jarslink.api.ModuleConfig;
+import com.alipay.jarslink.api.ModuleLoader;
+import com.google.common.collect.ImmutableList;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author joe
+ * @version 2018.04.01 22:58
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(locations = {"classpath*:META-INF/spring/jarslink.xml"})
+public class ConcurrentModuleManagerImplTest {
+ public static final String JARSLINK_MODULE_DEMO = "jarslink-module-demo-1.0.0.jar";
+ @Autowired
+ private ConcurrentModuleManagerImpl testModuleManager;
+ @Autowired
+ private ModuleLoader moduleLoader;
+
+
+ @Test
+ public void concurrentTest() {
+ testModuleManager.activeConcurrentTest();
+
+ Module m1 = moduleLoader.load(buildModuleConfig("1.0").withNeedUnloadOldVersion(false));
+ Module m2 = moduleLoader.load(buildModuleConfig("2.0").withNeedUnloadOldVersion(false));
+
+
+ Thread t1 = new Thread(new RegisterTask(testModuleManager, m1));
+ Thread t2 = new Thread(new RegisterTask(testModuleManager, m2));
+
+ t1.start();
+ t2.start();
+
+ try {
+ t1.join();
+ t2.join();
+ } catch (InterruptedException e) {
+ //此处不会抛出异常
+ Assert.assertNull(e);
+ }
+
+ m1 = testModuleManager.find("demo", "1.0");
+ m2 = testModuleManager.find("demo", "2.0");
+
+ //由于存在并发BUG,所以此处两个肯定有一个是null
+ Assert.assertTrue((m1 == null && m2 != null) || (m1 != null && m2 == null));
+
+ testModuleManager.disableConcurrentTest();
+ }
+
+ private ModuleConfig buildModuleConfig(String version) {
+ URL demoModule;
+ ModuleConfig moduleConfig = new ModuleConfig();
+ //通过该方法构建的配置都是使用注解形式扫描bean的
+ String scanBase = "com.alipay.jarslink.main";
+ moduleConfig.addScanPackage(scanBase);
+ moduleConfig.removeScanPackage(scanBase);
+ Map properties = new HashMap();
+ properties.put("url", "127.0.0.1");
+ moduleConfig.withEnabled(true).withVersion(version).withOverridePackages(ImmutableList.of("com.alipay" + "" +
+ ".jarslink.demo")).withName("demo").withProperties(properties);
+ demoModule = Thread.currentThread().getContextClassLoader().getResource(JARSLINK_MODULE_DEMO);
+ moduleConfig.setModuleUrl(ImmutableList.of(demoModule));
+ return moduleConfig;
+ }
+
+}
diff --git a/jarslink-api/src/test/java/com/alipay/jarslink/api/impl/ModuleAwareImpl.java b/jarslink-api/src/test/java/com/alipay/jarslink/api/impl/ModuleAwareImpl.java
new file mode 100644
index 0000000..2f8a6ca
--- /dev/null
+++ b/jarslink-api/src/test/java/com/alipay/jarslink/api/impl/ModuleAwareImpl.java
@@ -0,0 +1,19 @@
+package com.alipay.jarslink.api.impl;
+
+import com.alipay.jarslink.api.ModuleAware;
+import com.alipay.jarslink.api.ModuleConfig;
+import org.junit.Assert;
+
+/**
+ * 测试用
+ *
+ * @author joe
+ * @version 2018.04.04 11:47
+ */
+public class ModuleAwareImpl implements ModuleAware {
+ @Override
+ public void setModuleConfig(ModuleConfig moduleConfig) {
+ moduleConfig.withVersion("2.0");
+ Assert.assertNotNull(moduleConfig);
+ }
+}
diff --git a/jarslink-api/src/test/java/com/alipay/jarslink/api/impl/ModuleLoaderImplTest.java b/jarslink-api/src/test/java/com/alipay/jarslink/api/impl/ModuleLoaderImplTest.java
index 4f59472..a3e2f71 100644
--- a/jarslink-api/src/test/java/com/alipay/jarslink/api/impl/ModuleLoaderImplTest.java
+++ b/jarslink-api/src/test/java/com/alipay/jarslink/api/impl/ModuleLoaderImplTest.java
@@ -21,7 +21,6 @@
import java.util.Map;
/**
- *
* @author tengfei.fang
* @version $Id: ModuleLoaderImplTest.java, v 0.1 2018年04月01日 12:12 PM tengfei.fang Exp $
*/
@@ -37,9 +36,10 @@ public class ModuleLoaderImplTest {
@Test
public void shouldLoadModule() {
//1:加载模块
- Module module = loadModule();
+ String version = Double.toString(Math.random());
+ Module module = loadModuleWithVersion(version);
Assert.assertEquals("demo", module.getName());
- Assert.assertEquals("1.0.0.20170621", module.getVersion());
+ Assert.assertEquals(version, module.getVersion());
Assert.assertEquals(3, module.getActions().size());
Assert.assertNotNull(module);
Assert.assertNotNull(module.getCreation());
@@ -68,7 +68,7 @@ public void shouldLoadMultiModule() {
@Test
public void shouldDoAction() {
- Module module = loadModule();
+ Module module = loadModuleWithVersion(Double.toString(Math.random()));
String actionName = "helloworld";
//4.1:查找和执行Action
@@ -193,11 +193,11 @@ public static ModuleConfig buildModuleConfig() {
}
public static ModuleConfig buildModuleConfig(boolean enabled) {
- return buildModuleConfig("demo", "1.0.0.20170621", enabled);
+ return buildModuleConfig("demo", Double.toString(Math.random()), enabled);
}
public static ModuleConfig buildModuleConfig(String name, boolean enabled) {
- return buildModuleConfig(name, "1.0.0.20170621", enabled);
+ return buildModuleConfig(name, Double.toString(Math.random()), enabled);
}
public static ModuleConfig buildModuleConfig(String name, String version, boolean enabled) {
@@ -208,23 +208,23 @@ public static ModuleConfig buildModuleConfig(String name, String version, boolea
moduleConfig.addScanPackage(scanBase);
moduleConfig.removeScanPackage(scanBase);
Map properties = new HashMap();
- moduleConfig.withEnabled(enabled).withVersion("1.0.0.20170621").withOverridePackages(ImmutableList.of(
- "com.alipay.jarslink.demo")).withProperties(properties);
+ moduleConfig.withEnabled(enabled).withVersion(version).withOverridePackages(ImmutableList.of("com.alipay" + "" +
+ ".jarslink.demo")).withProperties(properties);
demoModule = Thread.currentThread().getContextClassLoader().getResource(JARSLINK_MODULE_DEMO);
moduleConfig.setOverridePackages(ImmutableList.of("com.alipay.jarslink.demo"));
moduleConfig.setName(name);
moduleConfig.setEnabled(enabled);
- moduleConfig.setVersion("1.0.0.20170621");
+ moduleConfig.setVersion(version);
properties.put("url", "127.0.0.1");
moduleConfig.setProperties(properties);
moduleConfig.setModuleUrl(ImmutableList.of(demoModule));
return moduleConfig;
}
- private Module loadModule() {
- return moduleLoader.load(buildModuleConfig(true));
+ private Module loadModuleWithVersion(String version) {
+ return moduleLoader.load(buildModuleConfig("demo", version, true));
}
private Module loadModule(String name) {
diff --git a/jarslink-api/src/test/java/com/alipay/jarslink/api/impl/ModuleLoaderInjectTest.java b/jarslink-api/src/test/java/com/alipay/jarslink/api/impl/ModuleLoaderInjectTest.java
new file mode 100644
index 0000000..e8e1c35
--- /dev/null
+++ b/jarslink-api/src/test/java/com/alipay/jarslink/api/impl/ModuleLoaderInjectTest.java
@@ -0,0 +1,131 @@
+package com.alipay.jarslink.api.impl;
+
+import com.alipay.jarslink.api.*;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+import java.util.concurrent.Semaphore;
+
+import static com.alipay.jarslink.api.impl.ModuleLoaderImplTest.buildModuleConfig;
+
+/**
+ * @author joe
+ * @version 2018.04.04 11:38
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(locations = {"classpath*:META-INF/spring/jarslink.xml"})
+public class ModuleLoaderInjectTest {
+ @Autowired
+ private ModuleLoader moduleLoader;
+ private Semaphore semaphore = new Semaphore(1);
+
+ @Test
+ public void shouldRegisterApplicationContextAware() throws InterruptedException {
+ semaphore.acquire();
+ ApplicationContextAware aware = new ApplicationContextAwareImpl();
+ moduleLoader.registerAware(aware);
+ ModuleConfig config = buildModuleConfig("demo", "1.0", true);
+ Module module = moduleLoader.load(config);
+ Assert.assertNotNull(module);
+ Assert.assertEquals("2.0", config.getVersion());
+ moduleLoader.unRegisterAware(aware);
+ semaphore.release();
+ }
+
+ @Test
+ public void shouldRegisterModuleAware() throws InterruptedException {
+ semaphore.acquire();
+ ModuleAware aware = new ModuleAwareImpl();
+ moduleLoader.registerAware(aware);
+ ModuleConfig config = buildModuleConfig("demo", "1.0", true);
+ Module module = moduleLoader.load(config);
+ Assert.assertNotNull(module);
+ Assert.assertEquals("2.0", module.getModuleConfig().getVersion());
+ moduleLoader.unRegisterAware(aware);
+ semaphore.release();
+ }
+
+ @Test
+ public void shouldRegisterApplicationContextPostProcessor() throws InterruptedException {
+ semaphore.acquire();
+ ApplicationContextPostProcessor postProcessor = new ApplicationContextPostProcessorImpl();
+ moduleLoader.registerPostProcessor(postProcessor);
+ ModuleConfig config = buildModuleConfig("demo", "1.0", true);
+ Module module = moduleLoader.load(config);
+ Assert.assertNotNull(module);
+ Assert.assertEquals("2.0", config.getVersion());
+ moduleLoader.unRegisterPostProcessor(postProcessor);
+ semaphore.release();
+ }
+
+ @Test
+ public void shouldRegisterModulePostProcessor() throws InterruptedException {
+ semaphore.acquire();
+ ModulePostProcessor postProcessor = new ModulePostProcessorImpl();
+ moduleLoader.registerPostProcessor(postProcessor);
+ ModuleConfig config = buildModuleConfig("demo", "1.0", true);
+ Module module = moduleLoader.load(config);
+ Assert.assertNotNull(module);
+ Assert.assertEquals("2.0", config.getVersion());
+ moduleLoader.unRegisterPostProcessor(postProcessor);
+ semaphore.release();
+ }
+
+ @Test
+ public void shouldUnRegisterApplicationContextAware() throws InterruptedException {
+ semaphore.acquire();
+ ApplicationContextAware aware = new ApplicationContextAwareImpl();
+ moduleLoader.registerAware(aware);
+ moduleLoader.unRegisterAware(aware);
+
+ ModuleConfig config = buildModuleConfig("demo", "1.0", true);
+ Module module = moduleLoader.load(config);
+ Assert.assertNotNull(module);
+ Assert.assertEquals("1.0", config.getVersion());
+ semaphore.release();
+ }
+
+ @Test
+ public void shouldUnRegisterModuleAware() throws InterruptedException {
+ semaphore.acquire();
+ ModuleAware aware = new ModuleAwareImpl();
+ moduleLoader.registerAware(aware);
+ moduleLoader.unRegisterAware(aware);
+
+ ModuleConfig config = buildModuleConfig("demo", "1.0", true);
+ Module module = moduleLoader.load(config);
+ Assert.assertNotNull(module);
+ Assert.assertEquals("1.0", module.getModuleConfig().getVersion());
+ semaphore.release();
+ }
+
+ @Test
+ public void shouldUnRegisterApplicationContextPostProcessor() throws InterruptedException {
+ semaphore.acquire();
+ ApplicationContextPostProcessor postProcessor = new ApplicationContextPostProcessorImpl();
+ moduleLoader.registerPostProcessor(postProcessor);
+ moduleLoader.unRegisterPostProcessor(postProcessor);
+ ModuleConfig config = buildModuleConfig("demo", "1.0", true);
+ Module module = moduleLoader.load(config);
+ Assert.assertNotNull(module);
+ Assert.assertEquals("1.0", config.getVersion());
+ semaphore.release();
+ }
+
+ @Test
+ public void shouldUnRegisterModulePostProcessor() throws InterruptedException {
+ semaphore.acquire();
+ ModulePostProcessor postProcessor = new ModulePostProcessorImpl();
+ moduleLoader.registerPostProcessor(postProcessor);
+ moduleLoader.unRegisterPostProcessor(postProcessor);
+ ModuleConfig config = buildModuleConfig("demo", "1.0", true);
+ Module module = moduleLoader.load(config);
+ Assert.assertNotNull(module);
+ Assert.assertEquals("1.0", config.getVersion());
+ semaphore.release();
+ }
+}
diff --git a/jarslink-api/src/test/java/com/alipay/jarslink/api/impl/ModuleManagerTest.java b/jarslink-api/src/test/java/com/alipay/jarslink/api/impl/ModuleManagerTest.java
index de4d7ea..b88d9ea 100644
--- a/jarslink-api/src/test/java/com/alipay/jarslink/api/impl/ModuleManagerTest.java
+++ b/jarslink-api/src/test/java/com/alipay/jarslink/api/impl/ModuleManagerTest.java
@@ -20,6 +20,7 @@
import com.alipay.jarslink.api.Module;
import com.alipay.jarslink.api.ModuleLoader;
import com.alipay.jarslink.api.ModuleManager;
+import com.alipay.jarslink.api.ModuleRuntimeException;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -84,8 +85,13 @@ public void shouldNotRegisterSameVersionModule() {
Assert.assertNull(removedModule);
Assert.assertEquals(1, moduleManager.getModules().size());
- //再注册同一个模块,不会注册成功,返回空
- Module register = moduleManager.register(module);
+ //再注册同一个模块,将会抛出异常
+ Module register = null;
+ try {
+ register = moduleManager.register(module);
+ } catch (ModuleRuntimeException e) {
+ Assert.assertNotNull(e);
+ }
Assert.assertNull(register);
Assert.assertEquals(1, moduleManager.getModules().size());
}
diff --git a/jarslink-api/src/test/java/com/alipay/jarslink/api/impl/ModulePostProcessorImpl.java b/jarslink-api/src/test/java/com/alipay/jarslink/api/impl/ModulePostProcessorImpl.java
new file mode 100644
index 0000000..faeaa16
--- /dev/null
+++ b/jarslink-api/src/test/java/com/alipay/jarslink/api/impl/ModulePostProcessorImpl.java
@@ -0,0 +1,21 @@
+package com.alipay.jarslink.api.impl;
+
+import com.alipay.jarslink.api.Module;
+import com.alipay.jarslink.api.ModuleConfig;
+import com.alipay.jarslink.api.ModulePostProcessor;
+import org.junit.Assert;
+
+/**
+ * 测试用
+ *
+ * @author joe
+ * @version 2018.04.04 11:44
+ */
+public class ModulePostProcessorImpl implements ModulePostProcessor {
+ @Override
+ public void setModule(Module module, ModuleConfig moduleConfig) {
+ moduleConfig.withVersion("2.0");
+ Assert.assertNotNull(module);
+ Assert.assertNotNull(moduleConfig);
+ }
+}
diff --git a/jarslink-api/src/test/java/com/alipay/jarslink/api/impl/RegisterTask.java b/jarslink-api/src/test/java/com/alipay/jarslink/api/impl/RegisterTask.java
new file mode 100644
index 0000000..4dc85f1
--- /dev/null
+++ b/jarslink-api/src/test/java/com/alipay/jarslink/api/impl/RegisterTask.java
@@ -0,0 +1,22 @@
+package com.alipay.jarslink.api.impl;
+
+import com.alipay.jarslink.api.Module;
+
+/**
+ * @author joe
+ * @version 2018.04.02 00:15
+ */
+public class RegisterTask implements Runnable {
+ private ConcurrentModuleManagerImpl concurrentModuleManager;
+ private Module m1;
+
+ public RegisterTask(ConcurrentModuleManagerImpl concurrentModuleManager, Module m1) {
+ this.concurrentModuleManager = concurrentModuleManager;
+ this.m1 = m1;
+ }
+
+ @Override
+ public void run() {
+ concurrentModuleManager.register(m1);
+ }
+}
diff --git a/jarslink-api/src/test/resources/META-INF/spring/jarslink.xml b/jarslink-api/src/test/resources/META-INF/spring/jarslink.xml
index dbe7c8e..fd759f8 100644
--- a/jarslink-api/src/test/resources/META-INF/spring/jarslink.xml
+++ b/jarslink-api/src/test/resources/META-INF/spring/jarslink.xml
@@ -1,7 +1,8 @@
+
+
diff --git a/jarslink-inject/pom.xml b/jarslink-inject/pom.xml
new file mode 100644
index 0000000..f3f90ae
--- /dev/null
+++ b/jarslink-inject/pom.xml
@@ -0,0 +1,66 @@
+
+
+
+
+ 4.0.0
+ com.alipay.jarslink.support
+ jarslink-inject
+ 1.0.0.20180403
+ Alipay JarsLink Support
+ https://github.com/alibaba/jarslink
+ jar
+ Alipay JarsLink Support
+
+
+ 4.3.10.RELEASE
+ 1.7.0.20180401
+ 1.6
+
+
+
+
+ org.springframework
+ spring-beans
+ ${org.springframework.version}
+
+
+ org.springframework
+ spring-context
+ ${org.springframework.version}
+
+
+ com.alipay.jarslink
+ jarslink-api
+ ${jarslink.version}
+
+
+ junit
+ junit
+ 4.12
+ test
+
+
+ org.springframework
+ spring-test
+ ${org.springframework.version}
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 2.0.2
+
+
+ ${java.version}
+ UTF-8
+
+
+
+
+
\ No newline at end of file
diff --git a/jarslink-inject/src/main/java/com/alipay/jarslink/support/ActionBean.java b/jarslink-inject/src/main/java/com/alipay/jarslink/support/ActionBean.java
new file mode 100644
index 0000000..f14e5fd
--- /dev/null
+++ b/jarslink-inject/src/main/java/com/alipay/jarslink/support/ActionBean.java
@@ -0,0 +1,64 @@
+package com.alipay.jarslink.support;
+
+import com.alipay.jarslink.api.Action;
+import com.alipay.jarslink.api.Module;
+import com.alipay.jarslink.api.ModuleManager;
+import com.alipay.jarslink.support.annotation.ActionReference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * action bean
+ *
+ * @author joe
+ * @version 2018.04.03 17:17
+ */
+public class ActionBean implements Action {
+ private static final Logger LOGGER = LoggerFactory.getLogger(ActionBean.class);
+ private final ModuleManager moduleManager;
+ private final ActionReference reference;
+ private String version;
+ private String moduleName;
+ private String actionName;
+
+ public ActionBean(ModuleManager moduleManager, ActionReference reference) {
+ this.moduleManager = moduleManager;
+ this.reference = reference;
+ init();
+ }
+
+ private void init() {
+ version = reference.version();
+ moduleName = reference.moduleName();
+ actionName = reference.name();
+ Utils.checkEmpty(version, "module version must not be null or empty");
+ Utils.checkEmpty(moduleName, "module version must not be null or empty");
+ Utils.checkEmpty(actionName, "module version must not be null or empty");
+ }
+
+ @Override
+ public Object execute(Object t) {
+ Module module = moduleManager.find(moduleName, version);
+ if (module == null) {
+ LOGGER.error("module[" + moduleName + ":" + version + "] not exist");
+ throw new IllegalStateException("module[" + moduleName + ":" + version + "] not exist");
+ }
+ Action action = null;
+ try {
+ action = module.getAction(actionName);
+ } catch (NullPointerException e) {
+ if (action == null) {
+ LOGGER.error("action[" + moduleName + ":" + version + ":" + actionName + "] not exist");
+ throw new IllegalStateException("action[" + moduleName + ":" + version + ":" + actionName + "] not " +
+ "exist");
+ }
+ }
+
+ return action.execute(t);
+ }
+
+ @Override
+ public String getActionName() {
+ return actionName;
+ }
+}
diff --git a/jarslink-inject/src/main/java/com/alipay/jarslink/support/ActionFactoryBean.java b/jarslink-inject/src/main/java/com/alipay/jarslink/support/ActionFactoryBean.java
new file mode 100644
index 0000000..4ff2235
--- /dev/null
+++ b/jarslink-inject/src/main/java/com/alipay/jarslink/support/ActionFactoryBean.java
@@ -0,0 +1,55 @@
+package com.alipay.jarslink.support;
+
+import com.alipay.jarslink.api.ModuleManager;
+import com.alipay.jarslink.support.annotation.ActionReference;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.FactoryBean;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * @author joe
+ * @version 2018.04.03 16:45
+ */
+public class ActionFactoryBean implements FactoryBean, ApplicationContextAware, DisposableBean {
+ private List actionBeans = new CopyOnWriteArrayList();
+ private ModuleManager moduleManager;
+ private ActionReference reference;
+ private ActionBean ref;
+
+ public ActionFactoryBean(ModuleManager moduleManager, ActionReference reference) {
+ this.moduleManager = moduleManager;
+ this.reference = reference;
+ this.ref = new ActionBean(moduleManager, reference);
+ }
+
+ @Override
+ public void destroy() throws Exception {
+ ref = null;
+ }
+
+ @Override
+ public Object getObject() throws Exception {
+ return ref;
+ }
+
+ @Override
+ public Class> getObjectType() {
+ return ActionBean.class;
+ }
+
+ @Override
+ public boolean isSingleton() {
+ return true;
+ }
+
+ @Override
+ public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+
+ }
+}
diff --git a/jarslink-inject/src/main/java/com/alipay/jarslink/support/ApplicationContextAwareImpl.java b/jarslink-inject/src/main/java/com/alipay/jarslink/support/ApplicationContextAwareImpl.java
new file mode 100644
index 0000000..10c2888
--- /dev/null
+++ b/jarslink-inject/src/main/java/com/alipay/jarslink/support/ApplicationContextAwareImpl.java
@@ -0,0 +1,33 @@
+package com.alipay.jarslink.support;
+
+import com.alipay.jarslink.api.ApplicationContextAware;
+import com.alipay.jarslink.api.ModuleConfig;
+import com.alipay.jarslink.api.ModuleManager;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.context.ConfigurableApplicationContext;
+
+/**
+ * 开启模块间调用的拦截
+ *
+ * @author joe
+ * @version 2018.04.04 14:54
+ */
+public class ApplicationContextAwareImpl implements ApplicationContextAware {
+ private final ReferenceAnnotationBeanPostProcessor postProcessor;
+
+ public ApplicationContextAwareImpl(ModuleManager moduleManager) {
+ this.postProcessor = new ReferenceAnnotationBeanPostProcessor(moduleManager);
+ }
+
+ @Override
+ public void setConfigurableApplicationContext(ConfigurableApplicationContext context, ModuleConfig moduleConfig) {
+ context.addBeanFactoryPostProcessor(new BeanFactoryPostProcessor() {
+ @Override
+ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
+ beanFactory.addBeanPostProcessor(postProcessor);
+ }
+ });
+ }
+}
diff --git a/jarslink-inject/src/main/java/com/alipay/jarslink/support/ReferenceAnnotationBeanPostProcessor.java b/jarslink-inject/src/main/java/com/alipay/jarslink/support/ReferenceAnnotationBeanPostProcessor.java
new file mode 100644
index 0000000..e9461c2
--- /dev/null
+++ b/jarslink-inject/src/main/java/com/alipay/jarslink/support/ReferenceAnnotationBeanPostProcessor.java
@@ -0,0 +1,291 @@
+package com.alipay.jarslink.support;
+
+import com.alipay.jarslink.api.ModuleManager;
+import com.alipay.jarslink.support.annotation.ActionReference;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.PropertyValues;
+import org.springframework.beans.factory.BeanCreationException;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.annotation.InjectionMetadata;
+import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;
+import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
+import org.springframework.beans.factory.support.RootBeanDefinition;
+import org.springframework.core.PriorityOrdered;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.ReflectionUtils;
+import org.springframework.util.StringUtils;
+
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import static org.springframework.core.BridgeMethodResolver.findBridgedMethod;
+import static org.springframework.core.BridgeMethodResolver.isVisibilityBridgeMethodPair;
+import static org.springframework.core.annotation.AnnotationUtils.findAnnotation;
+import static org.springframework.core.annotation.AnnotationUtils.getAnnotation;
+
+/**
+ * 处理使用注解上下文的action注入问题
+ *
+ * @author joe
+ * @version 2018.04.03 16:41
+ */
+public class ReferenceAnnotationBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter
+ implements MergedBeanDefinitionPostProcessor, PriorityOrdered, DisposableBean {
+ /**
+ * The bean name of {@link ReferenceAnnotationBeanPostProcessor}
+ */
+ public static final String BEAN_NAME = "referenceAnnotationBeanPostProcessor";
+
+ private final Log logger = LogFactory.getLog(getClass());
+
+ private ModuleManager moduleManager;
+
+ public ReferenceAnnotationBeanPostProcessor(ModuleManager moduleManager) {
+ this.moduleManager = moduleManager;
+ }
+
+
+ private final ConcurrentMap injectionMetadataCache =
+ new ConcurrentHashMap(256);
+
+ private final ConcurrentMap referenceBeansCache =
+ new ConcurrentHashMap();
+
+ @Override
+ public PropertyValues postProcessPropertyValues(
+ PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeanCreationException {
+
+ InjectionMetadata metadata = findReferenceMetadata(beanName, bean.getClass(), pvs);
+ try {
+ metadata.inject(bean, beanName, pvs);
+ } catch (BeanCreationException ex) {
+ throw ex;
+ } catch (Throwable ex) {
+ throw new BeanCreationException(beanName, "Injection of @ActionReference dependencies failed", ex);
+ }
+ return pvs;
+ }
+
+
+ /**
+ * Finds {@link InjectionMetadata.InjectedElement} Metadata from annotated
+ * {@link ActionReference @ActionReference} fields
+ *
+ * @param beanClass The {@link Class} of Bean
+ * @return non-null {@link List}
+ */
+ private List findFieldReferenceMetadata(final Class> beanClass) {
+
+ final List elements = new LinkedList();
+
+ ReflectionUtils.doWithFields(beanClass, new ReflectionUtils.FieldCallback() {
+ @Override
+ public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
+ ActionReference reference = getAnnotation(field, ActionReference.class);
+
+ if (reference != null) {
+
+ if (Modifier.isStatic(field.getModifiers())) {
+ if (logger.isWarnEnabled()) {
+ logger.warn("@ActionReference annotation is not supported on static fields: " + field);
+ }
+ return;
+ }
+
+ elements.add(new ReferenceFieldElement(field, reference));
+ }
+ }
+ });
+
+ return elements;
+
+ }
+
+ /**
+ * Finds {@link InjectionMetadata.InjectedElement} Metadata from annotated
+ * {@link ActionReference @ActionReference} methods
+ *
+ * @param beanClass The {@link Class} of Bean
+ * @return non-null {@link List}
+ */
+ private List findMethodReferenceMetadata(final Class> beanClass) {
+
+ final List elements = new LinkedList();
+
+ ReflectionUtils.doWithMethods(beanClass, new ReflectionUtils.MethodCallback() {
+ @Override
+ public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
+
+
+ Method bridgedMethod = findBridgedMethod(method);
+
+ if (!isVisibilityBridgeMethodPair(method, bridgedMethod)) {
+ return;
+ }
+
+ ActionReference reference = findAnnotation(bridgedMethod, ActionReference.class);
+
+ if (reference != null && method.equals(ClassUtils.getMostSpecificMethod(method, beanClass))) {
+ if (Modifier.isStatic(method.getModifiers())) {
+ if (logger.isWarnEnabled()) {
+ logger.warn("@ActionReference annotation is not supported on static methods: " + method);
+ }
+ return;
+ }
+ if (method.getParameterTypes().length == 0) {
+ if (logger.isWarnEnabled()) {
+ logger.warn("@ActionReference annotation should only be used on methods with parameters:" +
+ " " +
+ method);
+ }
+ }
+ PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, beanClass);
+ elements.add(new ReferenceMethodElement(method, pd, reference));
+ }
+ }
+ });
+
+ return elements;
+ }
+
+
+ /**
+ * @param beanClass
+ * @return
+ */
+ private InjectionMetadata buildReferenceMetadata(final Class> beanClass) {
+
+ final List elements = new LinkedList();
+
+ elements.addAll(findFieldReferenceMetadata(beanClass));
+
+ elements.addAll(findMethodReferenceMetadata(beanClass));
+
+ return new InjectionMetadata(beanClass, elements);
+
+ }
+
+ private InjectionMetadata findReferenceMetadata(String beanName, Class> clazz, PropertyValues pvs) {
+ // Fall back to class name as cache key, for backwards compatibility with custom callers.
+ String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
+ // Quick check on the concurrent map first, with minimal locking.
+ InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
+ if (InjectionMetadata.needsRefresh(metadata, clazz)) {
+ synchronized (this.injectionMetadataCache) {
+ metadata = this.injectionMetadataCache.get(cacheKey);
+ if (InjectionMetadata.needsRefresh(metadata, clazz)) {
+ if (metadata != null) {
+ metadata.clear(pvs);
+ }
+ try {
+ metadata = buildReferenceMetadata(clazz);
+ this.injectionMetadataCache.put(cacheKey, metadata);
+ } catch (NoClassDefFoundError err) {
+ throw new IllegalStateException("Failed to introspect bean class [" + clazz.getName() +
+ "] for reference metadata: could not find class that it depends on", err);
+ }
+ }
+ }
+ }
+ return metadata;
+ }
+
+
+ @Override
+ public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class> beanType, String beanName) {
+ if (beanType != null) {
+ InjectionMetadata metadata = findReferenceMetadata(beanName, beanType, null);
+ metadata.checkConfigMembers(beanDefinition);
+ }
+ }
+
+ @Override
+ public int getOrder() {
+ return LOWEST_PRECEDENCE;
+ }
+
+ @Override
+ public void destroy() throws Exception {
+
+ for (ActionFactoryBean actionFactoryBean : referenceBeansCache.values()) {
+ if (logger.isInfoEnabled()) {
+ logger.info(actionFactoryBean + " was destroying!");
+ }
+ actionFactoryBean.destroy();
+ }
+
+ injectionMetadataCache.clear();
+ referenceBeansCache.clear();
+
+ if (logger.isInfoEnabled()) {
+ logger.info(getClass() + " was destroying!");
+ }
+ }
+
+ /**
+ * {@link ActionReference} {@link Method} {@link InjectionMetadata.InjectedElement}
+ */
+ private class ReferenceMethodElement extends InjectionMetadata.InjectedElement {
+
+ private final Method method;
+
+ private final ActionReference reference;
+
+ protected ReferenceMethodElement(Method method, PropertyDescriptor pd, ActionReference reference) {
+ super(method, pd);
+ this.method = method;
+ this.reference = reference;
+ }
+
+ @Override
+ protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable {
+ Object referenceBean = buildReferenceBean(reference);
+ ReflectionUtils.makeAccessible(method);
+ method.invoke(bean, referenceBean);
+ }
+ }
+
+ /**
+ * {@link ActionReference} {@link Field} {@link InjectionMetadata.InjectedElement}
+ */
+ private class ReferenceFieldElement extends InjectionMetadata.InjectedElement {
+
+ private final Field field;
+
+ private final ActionReference reference;
+
+ protected ReferenceFieldElement(Field field, ActionReference reference) {
+ super(field, null);
+ this.field = field;
+ this.reference = reference;
+ }
+
+ @Override
+ protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable {
+ Object referenceBean = buildReferenceBean(reference);
+ ReflectionUtils.makeAccessible(field);
+ field.set(bean, referenceBean);
+ }
+
+ }
+
+ private Object buildReferenceBean(ActionReference reference) throws Exception {
+ String key = reference.moduleName() + ":" + reference.version() + "#" + reference.name();
+ ActionFactoryBean bean = referenceBeansCache.get(key);
+ ActionFactoryBean old = null;
+ if (bean == null) {
+ bean = new ActionFactoryBean(moduleManager, reference);
+ old = referenceBeansCache.putIfAbsent(key, bean);
+ }
+
+ return old == null ? bean.getObject() : old.getObject();
+ }
+}
diff --git a/jarslink-inject/src/main/java/com/alipay/jarslink/support/Utils.java b/jarslink-inject/src/main/java/com/alipay/jarslink/support/Utils.java
new file mode 100644
index 0000000..9630083
--- /dev/null
+++ b/jarslink-inject/src/main/java/com/alipay/jarslink/support/Utils.java
@@ -0,0 +1,21 @@
+package com.alipay.jarslink.support;
+
+/**
+ * 工具类
+ *
+ * @author joe
+ * @version 2018.04.03 17:27
+ */
+public class Utils {
+ /**
+ * 检查字符串非空,如果字符串是空则抛出异常
+ *
+ * @param str 字符串
+ * @param errorMsg 错误消息
+ */
+ public static void checkEmpty(String str, String errorMsg) {
+ if (str == null || str.trim().equals("")) {
+ throw new IllegalArgumentException(errorMsg);
+ }
+ }
+}
diff --git a/jarslink-inject/src/main/java/com/alipay/jarslink/support/annotation/ActionReference.java b/jarslink-inject/src/main/java/com/alipay/jarslink/support/annotation/ActionReference.java
new file mode 100644
index 0000000..58be531
--- /dev/null
+++ b/jarslink-inject/src/main/java/com/alipay/jarslink/support/annotation/ActionReference.java
@@ -0,0 +1,35 @@
+package com.alipay.jarslink.support.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 用于在模块中注入Action
+ *
+ * @author joe
+ * @version 2018.04.03 16:37
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE})
+public @interface ActionReference {
+ /**
+ * module name
+ *
+ * @return module name
+ */
+ String moduleName();
+
+ /**
+ * module version
+ *
+ * @return module version
+ */
+ String version();
+
+ /**
+ * action name
+ *
+ * @return action name
+ */
+ String name();
+}
diff --git a/jarslink-inject/src/test/java/com/alipay/jarslink/support/ReferenceAnnotationBeanPostProcessorTest.java b/jarslink-inject/src/test/java/com/alipay/jarslink/support/ReferenceAnnotationBeanPostProcessorTest.java
new file mode 100644
index 0000000..578b9e3
--- /dev/null
+++ b/jarslink-inject/src/test/java/com/alipay/jarslink/support/ReferenceAnnotationBeanPostProcessorTest.java
@@ -0,0 +1,71 @@
+package com.alipay.jarslink.support;
+
+import com.alipay.jarslink.api.*;
+import com.google.common.collect.ImmutableList;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+import java.net.URL;
+
+/**
+ * @author joe
+ * @version 2018.04.04 14:22
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(locations = {"classpath*:META-INF/spring/jarslink.xml"})
+public class ReferenceAnnotationBeanPostProcessorTest {
+ public static final String JARSLINK_MODULE_DEMO_A = "jarslink-module-a-1.0.0.jar";
+ public static final String JARSLINK_MODULE_DEMO_B = "jarslink-module-b-1.0.0.jar";
+ @Autowired
+ private ModuleManager moduleManager;
+ @Autowired
+ private ModuleLoader moduleLoader;
+
+ @Before
+ public void init() {
+ ApplicationContextAware aware = new ApplicationContextAwareImpl(moduleManager);
+ moduleLoader.registerAware(aware);
+ }
+
+ @Test
+ public void shouldInject() {
+ ModuleConfig configA = buildModuleConfig("demo-a", "1.0", true, JARSLINK_MODULE_DEMO_A);
+ ModuleConfig configB = buildModuleConfig("demo-b", "1.0", true, JARSLINK_MODULE_DEMO_B);
+ Module moduleA = moduleLoader.load(configA);
+ Module moduleB = moduleLoader.load(configB);
+ moduleManager.register(moduleA);
+ moduleManager.register(moduleB);
+ Assert.assertNotNull(moduleA);
+ Assert.assertNotNull(moduleB);
+ String result = moduleA.doAction("action-a", "request");
+ Assert.assertEquals("a-request", result);
+ result = moduleB.doAction("action-b", "request");
+ Assert.assertEquals("b-request", result);
+
+ result = moduleB.doAction("proxy-action-a", "request");
+ Assert.assertEquals("a-request", result);
+
+ result = moduleA.doAction("proxy-action-b", "request");
+ Assert.assertEquals("b-request", result);
+ }
+
+ public static ModuleConfig buildModuleConfig(String name, String version, boolean enabled, String path) {
+ ModuleConfig moduleConfig = new ModuleConfig();
+ //通过该方法构建的配置都是使用注解形式扫描bean的
+ String scanBase = "com.alipay.jarslink.demo.annotation";
+ moduleConfig.addScanPackage(scanBase);
+
+
+ moduleConfig.withEnabled(enabled).withName(name).withVersion(version).withOverridePackages(ImmutableList.of
+ ("com.alipay.jarslink.demo"));
+ URL demoModule = Thread.currentThread().getContextClassLoader().getResource(path);
+
+ moduleConfig.setModuleUrl(ImmutableList.of(demoModule));
+ return moduleConfig;
+ }
+}
diff --git a/jarslink-inject/src/test/resources/META-INF/spring/jarslink.xml b/jarslink-inject/src/test/resources/META-INF/spring/jarslink.xml
new file mode 100644
index 0000000..9ecea98
--- /dev/null
+++ b/jarslink-inject/src/test/resources/META-INF/spring/jarslink.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/jarslink-inject/src/test/resources/jarslink-module-a-1.0.0.jar b/jarslink-inject/src/test/resources/jarslink-module-a-1.0.0.jar
new file mode 100644
index 0000000..26ae4c2
Binary files /dev/null and b/jarslink-inject/src/test/resources/jarslink-module-a-1.0.0.jar differ
diff --git a/jarslink-inject/src/test/resources/jarslink-module-b-1.0.0.jar b/jarslink-inject/src/test/resources/jarslink-module-b-1.0.0.jar
new file mode 100644
index 0000000..78eba1e
Binary files /dev/null and b/jarslink-inject/src/test/resources/jarslink-module-b-1.0.0.jar differ
diff --git a/jarslink-module-a/pom.xml b/jarslink-module-a/pom.xml
new file mode 100644
index 0000000..bf977d7
--- /dev/null
+++ b/jarslink-module-a/pom.xml
@@ -0,0 +1,43 @@
+
+
+ 4.0.0
+
+ com.alipay.jarslink
+ jarslink-module-a
+ 1.0.0
+
+
+
+ 1.6
+
+
+
+
+ com.alipay.jarslink.support
+ jarslink-inject
+ 1.0.0.20180403
+
+
+ com.alipay.jarslink
+ jarslink-api
+ 1.7.0.20180401
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 2.0.2
+
+
+ ${java.version}
+ UTF-8
+
+
+
+
+
\ No newline at end of file
diff --git a/jarslink-module-a/src/main/java/com/alipay/jarslink/demo/annotation/AAction.java b/jarslink-module-a/src/main/java/com/alipay/jarslink/demo/annotation/AAction.java
new file mode 100644
index 0000000..74a14da
--- /dev/null
+++ b/jarslink-module-a/src/main/java/com/alipay/jarslink/demo/annotation/AAction.java
@@ -0,0 +1,21 @@
+package com.alipay.jarslink.demo.annotation;
+
+import com.alipay.jarslink.api.Action;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author joe
+ * @version 2018.04.04 14:44
+ */
+@Component
+public class AAction implements Action {
+ @Override
+ public String execute(String actionRequest) {
+ return "a-" + actionRequest;
+ }
+
+ @Override
+ public String getActionName() {
+ return "action-a";
+ }
+}
diff --git a/jarslink-module-a/src/main/java/com/alipay/jarslink/demo/annotation/ActionProxy.java b/jarslink-module-a/src/main/java/com/alipay/jarslink/demo/annotation/ActionProxy.java
new file mode 100644
index 0000000..343d27a
--- /dev/null
+++ b/jarslink-module-a/src/main/java/com/alipay/jarslink/demo/annotation/ActionProxy.java
@@ -0,0 +1,27 @@
+package com.alipay.jarslink.demo.annotation;
+
+import com.alipay.jarslink.api.Action;
+import com.alipay.jarslink.support.annotation.ActionReference;
+import org.springframework.stereotype.Component;
+
+/**
+ * action注入测试使用
+ *
+ * @author joe
+ * @version 2018.04.04 14:42
+ */
+@Component
+public class ActionProxy implements Action {
+ @ActionReference(moduleName = "demo-b", version = "1.0", name = "action-b")
+ private Action bAction;
+
+ @Override
+ public String execute(String actionRequest) {
+ return bAction.execute(actionRequest);
+ }
+
+ @Override
+ public String getActionName() {
+ return "proxy-" + bAction.getActionName();
+ }
+}
diff --git a/jarslink-module-b/pom.xml b/jarslink-module-b/pom.xml
new file mode 100644
index 0000000..420c468
--- /dev/null
+++ b/jarslink-module-b/pom.xml
@@ -0,0 +1,42 @@
+
+
+ 4.0.0
+
+ com.alipay.jarslink
+ jarslink-module-b
+ 1.0.0
+
+
+ 1.6
+
+
+
+
+ com.alipay.jarslink.support
+ jarslink-inject
+ 1.0.0.20180403
+
+
+ com.alipay.jarslink
+ jarslink-api
+ 1.7.0.20180401
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 2.0.2
+
+
+ ${java.version}
+ UTF-8
+
+
+
+
+
\ No newline at end of file
diff --git a/jarslink-module-b/src/main/java/com/alipay/jarslink/demo/annotation/ActionProxy.java b/jarslink-module-b/src/main/java/com/alipay/jarslink/demo/annotation/ActionProxy.java
new file mode 100644
index 0000000..80d89e3
--- /dev/null
+++ b/jarslink-module-b/src/main/java/com/alipay/jarslink/demo/annotation/ActionProxy.java
@@ -0,0 +1,27 @@
+package com.alipay.jarslink.demo.annotation;
+
+import com.alipay.jarslink.api.Action;
+import com.alipay.jarslink.support.annotation.ActionReference;
+import org.springframework.stereotype.Component;
+
+/**
+ * 注入其他模块的action
+ *
+ * @author joe
+ * @version 2018.04.04 14:40
+ */
+@Component
+public class ActionProxy implements Action{
+ @ActionReference(moduleName = "demo-a" , version = "1.0" , name = "action-a")
+ private Action aAction;
+
+ @Override
+ public String execute(String actionRequest) {
+ return aAction.execute(actionRequest);
+ }
+
+ @Override
+ public String getActionName() {
+ return "proxy-" + aAction.getActionName();
+ }
+}
diff --git a/jarslink-module-b/src/main/java/com/alipay/jarslink/demo/annotation/BAction.java b/jarslink-module-b/src/main/java/com/alipay/jarslink/demo/annotation/BAction.java
new file mode 100644
index 0000000..a02c419
--- /dev/null
+++ b/jarslink-module-b/src/main/java/com/alipay/jarslink/demo/annotation/BAction.java
@@ -0,0 +1,21 @@
+package com.alipay.jarslink.demo.annotation;
+
+import com.alipay.jarslink.api.Action;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author joe
+ * @version 2018.04.04 14:39
+ */
+@Component
+public class BAction implements Action{
+ @Override
+ public String execute(String actionRequest) {
+ return "b-" + actionRequest;
+ }
+
+ @Override
+ public String getActionName() {
+ return "action-b";
+ }
+}