diff --git a/.gitignore b/.gitignore index c47c747..8051e01 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,4 @@ /.classpath /.idea /.DS_Store -*.iml -/bin/ \ No newline at end of file +*.iml \ No newline at end of file diff --git a/bin/create-project.bat b/bin/create-project.bat new file mode 100644 index 0000000..1ed1a13 --- /dev/null +++ b/bin/create-project.bat @@ -0,0 +1,54 @@ +@echo off +echo -------------------------------------------- +echo Create Titan Module Batch +for /f "tokens=2 delims==" %%a in ('wmic path win32_operatingsystem get LocalDateTime /value') do ( + set t=%%a +) +set Today=%t:~0,4%-%t:~4,2%-%t:~6,2% +echo @version 1.0.%Today% +echo @author Administrator +echo -------------------------------------------- +set /p systemName=Please insert your Titan Module English name +echo -------------------------------------------- +echo Titan Module creatingPlease wait... +echo -------------------------------------------- +md %systemName%\src\main\java\com\mybank\bkcommon\collector\%systemName%\collector +md %systemName%\src\main\java\com\mybank\bkcommon\collector\%systemName%\model +md %systemName%\src\main\resources\META-INF\spring +md %systemName%\src\main\resources\view +md %systemName%\src\test\java\com\mybank\bkcommon\collector\%systemName% +md %systemName%\src\test\resources\ +copy project-template\helloworld.vm %systemName%\src\main\resources\view\helloworld.vm +copy project-template\HelloWorld.java %systemName%\src\main\java\com\mybank\bkcommon\collector\%systemName%\model\HelloWorld.java +copy project-template\HelloWorldCollector.java %systemName%\src\main\java\com\mybank\bkcommon\collector\%systemName%\collector\HelloWorldCollector.java +copy project-template\pom-template.xml %systemName%\pom.xml +copy project-template\integration.xml %systemName%\src\main\resources\META-INF\spring\integration.xml +copy project-template\QuickStarter-template.java %systemName%\src\test\java\com\mybank\bkcommon\collector\%systemName%\QuickStarter.java +copy project-template\integration-test.xml %systemName%\src\test\resources\integration-test.xml +copy project-template\spring-template.xml %systemName%\src\main\resources\META-INF\spring\%systemName%.xml +copy project-template\log4j-template.xml %systemName%\src\test\resources\log4j.xml +copy project-template\log4j-main-template.xml %systemName%\src\main\resources\log4j.xml +setlocal Enabledelayedexpansion +call:changePath %systemName%\src\test\java\com\mybank\bkcommon\collector\%systemName%\QuickStarter.java +call:changePath %systemName%\pom.xml +call:changePath %systemName%\src\test\resources\integration-test.xml +call:changePath %systemName%\src\main\resources\META-INF\spring\%systemName%.xml +call:changePath %systemName%\src\main\java\com\mybank\bkcommon\collector\%systemName%\model\HelloWorld.java +call:changePath %systemName%\src\main\java\com\mybank\bkcommon\collector\%systemName%\collector\HelloWorldCollector.java +call:changePath %systemName%\src\main\resources\log4j.xml +endlocal +echo "Create Titan Module %systemName% Finish" +pause + +:changePath +set pacStr=${package_name} +set proStr=${project_name} +set targetStr=%systemName% +for /f "tokens=* delims=" %%i in (%1) do ( + set var=%%i + set var=!var:%pacStr%=%targetStr%! + set var=!var:%proStr%=%targetStr%! + echo !var!>>$ +) +move $ %1 +goto:eof \ No newline at end of file diff --git a/bin/create-project.sh b/bin/create-project.sh new file mode 100644 index 0000000..4b6964b --- /dev/null +++ b/bin/create-project.sh @@ -0,0 +1,57 @@ +name=$1 + +if [ -z "$name" ]; then + echo "need name" + exit 2 +fi + +URL="https://private-alipayobjects.alipay.com/alipay-rmsdeploy-image/rmsportal/xJXQkCxicEVnAvkHWXgI.gz" +installer_file="project-template.tar.gz" + +function download { + echo "Downloading Titan project template: $URL" + curl -# $URL > $installer_file || exit +} + +function unzip { + echo "unzip $installer_file" + tar -zxvf $installer_file +} + +function createProject +{ + mkdir -p $name/src/main + mkdir -p $name/src/main/java/com/mybank/bkcommon/collector/$name + mkdir -p $name/src/main/java/com/mybank/bkcommon/collector/$name/collector + mkdir -p $name/src/main/java/com/mybank/bkcommon/collector/$name/model + mkdir -p $name/src/main/resources/META-INF/spring + mkdir -p $name/src/main/resources/view + + mkdir -p $name/src/test + mkdir -p $name/src/test/java/com/mybank/bkcommon/collector/$name/util + mkdir -p $name/src/test/resources/ + + cp project-template/helloworld.vm $name/src/main/resources/view/helloworld.vm + cp project-template/HelloWorld.java $name/src/main/java/com/mybank/bkcommon/collector/$name/model/HelloWorld.java + cp project-template/HelloWorldCollector.java $name/src/main/java/com/mybank/bkcommon/collector/$name/collector/HelloWorldCollector.java + cp project-template/pom-template.xml $name/pom.xml + cp project-template/integration.xml $name/src/main/resources/META-INF/spring/integration.xml + cp project-template/QuickStarter-template.java $name/src/test/java/com/mybank/bkcommon/collector/$name/QuickStarter.java + cp project-template/integration-test.xml $name/src/test/resources/integration-test.xml + cp project-template/spring-template.xml $name/src/main/resources/META-INF/spring/$name.xml + cp project-template/log4j-template.xml $name/src/test/resources/log4j.xml + cp project-template/log4j-main-template.xml $name/src/main/resources/log4j.xml + + sed -i '' 's/${package_name}/'$name'/g' $name/src/test/java/com/mybank/bkcommon/collector/$name/QuickStarter.java + sed -i '' 's/${project_name}/'$name'/g' $name/pom.xml + sed -i '' 's/${project_name}/'$name'/g' $name/src/test/resources/integration-test.xml + sed -i '' 's/${project_name}/'$name'/g' $name/src/main/resources/META-INF/spring/$name.xml + sed -i '' 's/${package_name}/'$name'/g' $name/src/main/java/com/mybank/bkcommon/collector/$name/model/HelloWorld.java + sed -i '' 's/${package_name}/'$name'/g' $name/src/main/java/com/mybank/bkcommon/collector/$name/collector/HelloWorldCollector.java + sed -i '' 's/${project_name}/'$name'/g' $name/src/main/resources/log4j.xml + echo "Create Titan Module $name Finish" +} + +download +unzip +createProject \ No newline at end of file diff --git a/bin/template/project-template.tar b/bin/template/project-template.tar new file mode 100644 index 0000000..025e486 Binary files /dev/null and b/bin/template/project-template.tar differ diff --git a/jarslink-api/pom.xml b/jarslink-api/pom.xml index 080ed61..7b1b2cd 100644 --- a/jarslink-api/pom.xml +++ b/jarslink-api/pom.xml @@ -5,7 +5,7 @@ 4.0.0 com.alipay.jarslink jarslink-api - 1.6.1.20180301 + 1.7.0.20180401 Alipay JarsLink API https://github.com/alibaba/jarslink jar @@ -51,22 +51,12 @@ - - org.springframework - spring-aop - ${org.springframework.version} - org.springframework spring-context ${org.springframework.version} true - - commons-logging - commons-logging - 1.1.3 - org.apache.commons commons-lang3 @@ -105,37 +95,10 @@ test - - - - - 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 - 1.8 + ${java.version} + ${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} + ${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} + ${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} + ${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"; + } +}