现象和问题: 写了个 xx-service 的基于maven项目,借助spring配置文件可执行,现在有需求是希望将该项目以SDk方式打包到maven仓库以便使用其中某些service类(暂不论合理与否),如何通过maven profile实现在编辑器里不同开发人员默认用不同的 active profile? 实现不同的 active profile是希望双方在开发的时候,一个可以直接package出来 不含配置文件的纯 SDK 包,而我这边使用eclipse时可以默认生成需要的配置文件(local profile)到classpath下,免于命令行切换,提高开发/测试效率。
源项目通过Main函数启动,所以一种方案是定义spring.active.profile的方式,在eclipse配置参数方式启动,但是我不希望引入这种不纯粹的方式,过多依赖在一些零散的地方设置,就像我觉得使用lomb组件的不配谈代码优雅。 大部分了解maven的会想到active profile实现,但我也不想通过maven安装包下的 settings.xml中使用activeProfiles指定的方式,更不想通过两人部分代码不同的方式实现,即希望两人看到的代码一样。最好能都在项目代码里如pom.xml/xx.properties(配置即是代码!)里设置搞定。 那么有什么好办法呢? 因为classpath生成是在编译之后,即maven编译之后的,所以不要妄想通过eclipse的配置 jvm run 参数,必定是在maven编译前或时(eclipse通过m2e和maven交互即时编译),而maven pom.xml没有支持 if-else这类表达式语言的,实际上maevn基于配置的理念并不能支持其如DSL一般灵活,gradle可以。m2eclipse只是扩展了maven集成到eclipse的能力,并未扩展功能。 如果pom指定了A profile为active的,没有理由在其他人机器上A profile不是active的,除非maven支持基于机器等设置profile。好在maven 对profile本身支持力度是勉强可以达到上述功能。
让我们先看看官方支持激活profile的几种方式: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <activeByDefault > true</activeByDefault > <jdk > 1.5</jdk > <jdk > [1.5,)</jdk > <os > <name > Windows XP</name > <family > Windows</family > <arch > x86</arch > <version > 5.1.2600</version > </os > <property > <name > env</name > <value > test</value > </property > <file > <missing > target/generated-sources/axistools/wsdl2java/wdl</missing > <exists /> </file >
所以解决办法就是环境变量或者文件是否存在的方式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 <profile > <id > local</id > <activation > <property > <name > user.name</name > <value > thomas</value > </property > </activation > <properties > <log.path > ./logs</log.path > </properties > <build > <resources > <resource > <directory > ${basedir}/src/main/resources</directory > <filtering > true</filtering > </resource > <resource > <directory > ${basedir}/profiles/common</directory > <filtering > true</filtering > </resource > </resources > </build > </profile > <profile > <id > sdk</id > <activation > <activeByDefault > true</activeByDefault > </activation > <build > <resources > <resource > <directory > src/main/resources</directory > <excludes > <exclude > *</exclude > </excludes > </resource > </resources > </build > </profile >
该方式虽然也依赖一些配置,但好处是一目了然,看到代码即可明白。 通过上述配置,在我的eclipse上是classpath下包含配置的local模式可运行项目,而在对方机器就是sdk可打包项目,非常轻便了。 对方可通过 mvn deploy 正常的发布包,但是,当我使用 “mvn package deploy -Psdk” 时,却发现打出来的包中包含了配置文件!
为什么,指定了 sdk 的profile却发现配置文件被包含进来 怎么定位问题呢?使用 help:active-profiles 命令试试:
1 2 3 4 5 6 7 8 9 10 11 12 -> mvn -P sdk help :active-profiles ... Active Profiles for Project 'org.thomas:justitia-service:jar:0.2.8-SNAPSHOT' : The following profiles are active: - sonar (source : external) - gdev (source : external) - local (source : org.thomas:justitia-service:0.2.8-SNAPSHOT) - sdk (source : org.thomas:justitia-service:0.2.8-SNAPSHOT) - dev (source : org.thomas:justitia-parent:0.2.8-SNAPSHOT) ...
即,”mvn -P sdk”并不表示 仅使用 sdk 或parent/setting.xml里指定的active的profile打包,还包括任何能够匹配的profile! 即上面输出里看到的,默认生效的profile有 settings.xml定义的 sonar/gdev,还有本pom.xml里的 local/sdk 的profile,以及父类的默认 dev ,这是一个注意事项
怎么解决或者原因在哪呢? 其实 -P 参数支持简单的包含/排除运算,即 mvn package deploy -Plocal 就表示排除 local 这个active profile, !local 也表示排除, +local表示添加 local,这是一个技巧 ,代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 List<String> activeProfiles = new ArrayList <>(); List<String> inactiveProfiles = new ArrayList <>(); if ( commandLine.hasOption( CLIManager.ACTIVATE_PROFILES ) ){ String[] profileOptionValues = commandLine.getOptionValues( CLIManager.ACTIVATE_PROFILES ); if ( profileOptionValues != null ) { for ( String profileOptionValue : profileOptionValues ) { StringTokenizer profileTokens = new StringTokenizer ( profileOptionValue, "," ); while ( profileTokens.hasMoreTokens() ) { String profileAction = profileTokens.nextToken().trim(); if ( profileAction.startsWith( "-" ) || profileAction.startsWith( "!" ) ) { inactiveProfiles.add( profileAction.substring( 1 ) ); } else if ( profileAction.startsWith( "+" ) ) { activeProfiles.add( profileAction.substring( 1 ) ); } else { activeProfiles.add( profileAction ); } } } } }
这是第二个技巧
但为什么 sonar/gdev/local/sdk/dev 这些profile都生效了? 在 org.apache.maven.cli.configuration.SettingsXmlConfigurationProcessor.populateFromSettings(MavenExecutionRequest, Settings) 这个方法有涉及,不过这里我们倒序追溯一下:
mvn 命令的入口都在 org.apache.maven.cli.MavenCli 类里面,MavenCli 封装配置build好一个MavenExecutionRequest 之后,最终调用DefaultMaven.doExecute(MavenExecutionRequest request)实现,也是所有maven项目的MavenExecutionRequest,Project/Module/Artifact等都是在此实现的,该方法会调用这个方法进行 Project 的初始化: org.apache.maven.project.DefaultProjectBuilder.initProject(MavenProject, ModelBuildingResult, Map<File, Boolean>)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 private void initProject ( MavenProject project, ModelBuildingResult result, Map<File, Boolean> profilesXmls ) { Model model = result.getEffectiveModel(); project.setModel( model ); project.setOriginalModel( result.getRawModel() ); project.setFile( model.getPomFile() ); File parentPomFile = result.getRawModel( result.getModelIds().get( 1 ) ).getPomFile(); project.setParentFile( parentPomFile ); Artifact projectArtifact = repositorySystem.createArtifact( project.getGroupId(), project.getArtifactId(), project.getVersion(), null , project.getPackaging() ); project.setArtifact( projectArtifact ); if ( project.getFile() != null ) { Build build = project.getBuild(); project.addScriptSourceRoot( build.getScriptSourceDirectory() ); project.addCompileSourceRoot( build.getSourceDirectory() ); project.addTestCompileSourceRoot( build.getTestSourceDirectory() ); } List<Profile> activeProfiles = new ArrayList <Profile>(); activeProfiles.addAll( result.getActivePomProfiles( result.getModelIds().get( 0 ) ) ); activeProfiles.addAll( result.getActiveExternalProfiles() ); project.setActiveProfiles( activeProfiles ); project.setInjectedProfileIds( "external" , getProfileIds( result.getActiveExternalProfiles() ) ); for ( String modelId : result.getModelIds() ) { project.setInjectedProfileIds( modelId, getProfileIds( result.getActivePomProfiles( modelId ) ) ); } String modelId = findProfilesXml( result, profilesXmls ); if ( modelId != null ) { ModelProblem problem = new DefaultModelProblem ( "Detected profiles.xml alongside " + modelId + ", this file is no longer supported and was ignored" + ", please use the settings.xml instead" , ModelProblem.Severity.WARNING, model, -1 , -1 , null ); result.getProblems().add( problem ); } }
这几个方法就是分别从 pom/父项目/maven settings中获取active profile的,而profile 判定isActive是支持系统变量方式的:
1 2 3 4 activeProfiles.addAll( result.getActivePomProfiles( result.getModelIds().get( 0 ) ) ); activeProfiles.addAll( result.getActiveExternalProfiles() ); project.setActiveProfiles( activeProfiles ); project.setInjectedProfileIds( "external" , getProfileIds( result.getActiveExternalProfiles() ) );
三类profile都会作为maven project的Active Profiles,这也是 help:active-profiles 实现原理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 private void getActiveProfileStatement ( MavenProject project, StringBuilder message ) { Map<String, List<String>> activeProfileIds = project.getInjectedProfileIds(); message.append( LS ); message.append( "Active Profiles for Project \'" ).append( project.getId() ).append( "\':" ); message.append( LS ).append( LS ); if ( activeProfileIds.isEmpty() ) { message.append( "There are no active profiles." ); } else { message.append( "The following profiles are active:" ).append( LS ); for ( Map.Entry<String, List<String>> entry : activeProfileIds.entrySet() ) { for ( String profileId : entry.getValue() ) { message.append( LS ).append( " - " ).append( profileId ); message.append( " (source: " ).append( entry.getKey() ).append( ")" ); } } } message.append( LS ); }
其他 说两个不相关的问题: 1. Mac 的sed命令不支持一些linux sed写法 众所周知,有时甚至导致一些莫名其妙运行结果,故建议避免使用,改为linux sed 即安装gsed:brew install gnu-sed 不过不建议把gsed替换或直接别名为sed, 有些外部进程会调用sed。 2. 最近在写一个Chrome extension,发现下面写法返回空,要是把 sendResponse 放到chrome.storage.local.get的回调函数里就会报一个 类似: “unchecked runtime lasterror the message port closed before a response”的错误,原因就是因为异步且没有作为回调函数的方式,但即便用Promise也是该情况, 解决办法是给函数加一个简单的 return true;即把下面else里已有的注释掉,现有注释部分打开,就解决该问题了。https://github.com/mozilla/webextension-polyfill/issues/130 虽然写过一些js代码,但觉得js的这个功能很神奇。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 chrome.runtime .onMessage .addListener (main.onMessageListener ); ... onMessageListener : function (message, sender, sendResponse ){ if (message == 'tabs' ){ sendResponse (main.queue .slice ()); }else { var msg; chrome.storage .local .get (message, function (result ) { msg=result[message]; alert (result) }); sendResponse (msg); } }