起源
执行 mvn install -Dmaven.test.skip=true
报错 org.apache.maven.plugin.MojoExecutionException: GB23121
疑问
1、我没有显示的指定字符编码集
2、我指定为utf-8还是报这个错
3、我的window系统的编码集输出是GBK(936)
入口
maven-embedder包下面的 MavenCli的main方法
核心方法
doMain(CliRequest cliRequest){
1、cli( cliRequest );
2、initialize( cliRequest );
3、logging( cliRequest );
4、version( cliRequest );
5、properties( cliRequest );
6、localContainer = container( cliRequest );
7、commands( cliRequest );
8、settings( cliRequest );
9、populateRequest( cliRequest );
10、encryption( cliRequest );
11、repository( cliRequest );
return execute( cliRequest );
}
可以看到,cliRequest 对象是贯穿整个方法体的,其实在execute之前都是对此对象的构建,在入参刚进来的时候此对象的属性均是默认值,接下来我们一个一个去剖析。
1、cli Method
cliRequest.commandLine = cliManager.parse( cliRequest.args );
此方法将main方法的入参进行解析,即我们开发者在mvn命令之后打印的参数进行解析和封装,最终封装在commandLime属性中。可以看到在CLIManager的构造方法中对mvn支持的命令以及description和缩写都做了预置容器的存储(底层是List和Map实现),以下截取部分代码片段。
options = new Options();
options.addOption( OptionBuilder.withLongOpt( "help" ).withDescription( "Display help information" ).create( HELP ) );
options.addOption( OptionBuilder.withLongOpt( "file" ).hasArg().withDescription( "Force the use of an alternate POM file (or directory with pom.xml)." ).create( ALTERNATE_POM_FILE ) );
options.addOption( OptionBuilder.withLongOpt( "define" ).hasArg().withDescription( "Define a system property" ).create( SET_SYSTEM_PROPERTY ) );
options.addOption( OptionBuilder.withLongOpt( "offline" ).withDescription( "Work offline" ).create( OFFLINE ) );
options.addOption( OptionBuilder.withLongOpt( "version" ).withDescription( "Display version information" ).create( VERSION ) );
options.addOption( OptionBuilder.withLongOpt( "quiet" ).withDescription( "Quiet output - only show errors" ).create( QUIET ) );
options.addOption( OptionBuilder.withLongOpt( "debug" ).withDescription( "Produce execution debug output" ).create( DEBUG ) );
这也是我们为什么在输入mvn -X的时候代表的是打印debug日志的原因(Produce execution debug output)。
当然,如果解析命令出现错误的时候,maven也会捕获这个异常并且打印固定的日志,并且最终抛出这个异常。
2、initialize Method
从方法的名字就可以看出来这是初始化的方法(所以,见名知意很重要)。
cliRequest.workingDirectory=如果没传值,就当前所在位置
如果maven.home不为空,创建一个文件,,并且将maven.home设置到属性中去
这个方法就做了这么一件事,和我理解的初始化方法有点差异。
3、logging Method
初始化log相关的属性,比如日志等级、日志文件地址、日志对象的创建等,关于日志的输出也是根据方法1cli中解析的命令参数,并且对cliRequest的其他日志相关的属性赋值,均为boolean类型,比如。
//CLIManager.DEBUG,CLIManager.QUIET,CLIManager.ERRORS等c常量的值对应
//的是方法1中提到命令缩写,比如-e代表打印错误日志,-x代表打印debug日志。
cliRequest.debug = cliRequest.commandLine.hasOption( CLIManager.DEBUG );
cliRequest.quiet = !cliRequest.debug && cliRequest.commandLine.hasOption( CLIManager.QUIET );
cliRequest.showErrors = cliRequest.debug || cliRequest.commandLine.hasOption( CLIManager.ERRORS );
4、version Method
若命令中包含-V参数,代表输出版本相关的信息包含,java home,java version,maven home,file encoding等信息。
final String LS = System.getProperty( "line.separator" );
Properties properties = getBuildProperties();
StringBuffer version = new StringBuffer();
version.append( createMavenVersionString( properties ) ).append( LS );
version.append( reduce( properties.getProperty( "distributionShortName" ) + " home: "
+ System.getProperty( "maven.home", "<unknown maven home>" ) ) ).append( LS );
version.append( "Java version: " + System.getProperty( "java.version", "<unknown java version>" )
+ ", vendor: " + System.getProperty( "java.vendor", "<unknown vendor>" ) ).append( LS );
version.append( "Java home: " + System.getProperty( "java.home", "<unknown java home>" ) ).append( LS );
version.append( "Default locale: " + Locale.getDefault() + ", platform encoding: "
+ System.getProperty( "file.encoding", "<unknown encoding>" ) ).append( LS );
version.append( "OS name: \"" + Os.OS_NAME + "\", version: \"" + Os.OS_VERSION + "\", arch: \"" + Os.OS_ARCH
+ "\", family: \"" + Os.OS_FAMILY + "\"" );
return version.toString();
5、properties Method
populateProperties方法中入参,此时的systemProperties和userProperties属性值均为默认值。
EnvironmentUtils.addEnvVars( systemProperties )
(1)此方法将所有的系统环境变量即,System.getEnv放置在cliRequest中的systemProperties中,并且将EnvironmentUtils中的静态变量赋一样的值。
注意:如果已经初始化了,就不会再初始化,这里会有线程安全的问题。
if ( props != null )
{
if ( envVars == null )
{
Properties tmp = new Properties();
boolean caseSensitive = !Os.isFamily( Os.FAMILY_WINDOWS );
for ( Map.Entry<String, String> entry : System.getenv().entrySet() )
{
String key =
"env." + ( caseSensitive ? entry.getKey() : entry.getKey().toUpperCase( Locale.ENGLISH ) );
tmp.setProperty( key, entry.getValue() );
}
envVars = tmp;
}
props.putAll( envVars );
}
(2)如果在cliRequest中commandLine属性包含D参数,则代表是用户自定义的属性值,以下代码可知,maven会去解析D后面的字符串,以--开头,以-开头或者不加-均可解析。
if (str == null)
{
return null;
}
if (str.startsWith("--"))
{
return str.substring(2, str.length());
}
else if (str.startsWith("-"))
{
return str.substring(1, str.length());
}
}
解析出来后会以=分割,将key和value放入上述userProperties属性中,并且放入System.setProperty(name,value)中,如果参数值中没有等号,那key就是参数值,value为true。
(3)将userProperties装配完成以后,也会将用户设置的属性装配至systemProperties属性中去。
//因为这段代码在上述System.setProperty(name,value)之后执行。
systemProperties.putAll( System.getProperties() );
(4)在maven-core项目中会内置maven的properties,执行完1/2/3步骤后,会读取bulid.properties文件中的数据,存入临时变量bulidProperties,这个变量仅下面第5步骤会用到。
我的版本是maven3.1,bulid.properties的文件内容如下
buildNumber=${buildNumber}
timestamp=${timestamp}
version=${project.version}
distributionId=${distributionId}
distributionShortName=${distributionShortName}
distributionName=${distributionName}
(5)将第4步骤读取出来的maven的version装配至1中的systemProperties中,key为maven.version,将第4步骤读取出来的以字符串形式解析装配至1中的systemProperties中,key为maven.bulid.version。比如输入maven -v
container Method
Realm是一个自定义classloader,每个不同的Realm负责加载不同路径下的jar包。
(1)创建classworld对象,设置顶级的Realm(plexus.core),后续所有的Realm都会以这个为parent,将classworld放入cliRequest中。
(2)创建默认的容器配置,会将(1)中的classworld和Realm和classworld对象放置在DefaultContainerConfiguration中,此对象中的值在创建下面(3)中的DefaultPlexusContainer会用到。
(3)创建默认的Plexus容器,DefaultPlexusContainer,初始化此容器包括以下内容:
- ThreadLocal类型、集合类型、基础类型赋默认值(new)
- loggerManager默认是ConsoleLoggerManager
- 设置lookupRealm为父Realm
- 设置containerRealm为父Realm
- realmIds集合set父Realm的Id,plexus.core
- bean scanning设置为global_index
- 设置PlexusBeanModule类型的beanModules集合元素,2种实现,一种XML,一种注解
- 其他
在(3)中设置的属性,根据名称大概能猜出来是有什么作用,但具体用在哪里,怎么用,还需要后续的源码分析,这里先暂时记录设置了这些属性,属性值是什么。
(4)在3中将lookupRealm为设置父Realm,但是紧接着又设置为null,觉得这里多此一举,作者备注:
NOTE: To avoid inconsistencies, we'll use the TCCL exclusively for lookups
没太懂是什么用意
(5)设置日志等级
(6)使用Realm加载EventSpyDispatcher类
(7)将上述的3个properties属性和container注入进去EventSpyContext中的map中去。
(8)初始化EventSpyDispatcher,循环eventSpies属性,将每个eventSpy对象初始化,对于EventSpy官方的解释是
A core extension to monitor Maven's execution. Typically, such an extension gets loaded into Maven by specifying the
* system property {@code maven.ext.class.path} on the command line. As soon as dependency injection is setup, Maven
* looks up all implementators of this interface and calls their {@link #init(Context)} method. <em>Note:</em>
* Implementors are strongly advised to inherit from {@link AbstractEventSpy} instead of directly implementing this
* interface.
监控maven事件执行的核心扩展,,通过系统属性maven.ext.class.path加载到maven中
一旦依赖注入后,maven会自动寻找此类所有的实现者并且调用他们的init方法。
后续具体使用到会详细解释。
(9)使用Realm加载其他的类
maven = container.lookup( Maven.class );
executionRequestPopulator = container.lookup( MavenExecutionRequestPopulator.class );
modelProcessor = createModelProcessor( container );
settingsBuilder = container.lookup( SettingsBuilder.class );
dispatcher = (DefaultSecDispatcher) container.lookup( SecDispatcher.class, "maven" );
commands Mehtod
如果开启了错误打印和全局检查构建文件校验,则会打印相应开启此功能的日志。
setting Method
(1)当用户输入-s时,可以自定义自己的setting.xml文件的路径地址,支持绝对路径和相对路径,相对路径默认是${user.home}下。如果没有输入,就采用默认的地址,${user.home}/.m2/setting.xml。
(2)当用户输入-gs时,可以自定义自己的globle setting.xml文件的路径地址,支持绝对路径和相对路径,相对路径默认是${user.dir}下。如果没有输入,就采用默认的地址,${maven.home}/${user.dir}/conf/setting.xml。
(3)解析完成之后将(1)(2)文件配置cliRequest的request中的globalSettingsFile和userSettingsFile属性中
(4)maven采用事件监听和发布机制,将setting文件和properties属性注册到事件中,读取setting文件的配置到内存中。
(5)先读取global setting,然后读取user setting,分别读取2个文件中的activeProfile属性,对setting内容进行读取合并,list的值进行合并,单个值的取user setting中设置的。
(6)每一步读取内容都会进行一个Problem集合对象的跟踪,最终将这个Problem对象封装在settingsResult中,同样的将此对象发布一个event,并且后续代码判断此对象中Problem的情况进行日志的打印。就像这样:
populateRequest Method
此方法主要是装配cliRequest中request属性的,即maven运行时对象。
(1)打印弃用的命令"up", "npu", "cpu", "npr"
(2)设置是否支持批处理模式,-B,interactiveMode属性
(3)设置是否强制更新snapshot,-nsu,noSnapshotUpdates
(4)其他的各个类似参数很多,在这不一一列举,可在网上搜索maven参数列表
request.setBaseDirectory( baseDirectory ).setGoals( goals )
.setSystemProperties( cliRequest.systemProperties )
.setUserProperties( cliRequest.userProperties )
.setReactorFailureBehavior( reactorFailureBehaviour ) // default: fail fast
.setRecursive( recursive ) // default: true
.setShowErrors( showErrors ) // default: false
.addActiveProfiles( activeProfiles ) // optional
.addInactiveProfiles( inactiveProfiles ) // optional
.setExecutionListener( executionListener )
.setTransferListener( transferListener ) // default: batch mode which goes along with interactive
.setUpdateSnapshots( updateSnapshots ) // default: false
.setNoSnapshotUpdates( noSnapshotUpdates ) // default: false
.setGlobalChecksumPolicy( globalChecksumPolicy ) // default: warn
.setUserToolchainsFile( userToolchainsFile );
(5)激活指定的profile文件列表,参数为-P,用逗号分割,以-或者!开头的放入inactiveProfiles中,是无效的,其他的为有效的,放入activeProfiles集合中。
(6)指定资源传输的日志格式和日志打印方式,当quiet 模式下-q参数,不输出传输日志,当批处理模式下并且没有指定对应的日志文件时以控制台方式打印,主要还是在打印日志的格式上有区别。
(7)创建一个ExecutionEventLogger,将这个listener放入chainListener链中,具体怎么执行,什么时候执行,还没读到。
(8)解析用户自定义的toolchain.xml,如果没指定,则默认${user.home}/.m2/toolchain.xml,可以指定不同的jdk版本。
encryption Method
加密相关的配置,不做分析
repository Method
使用参数-llr或者-Dmavenmaven.legacyLocalRepo" 均可开启
execute
待续。。。