起源

执行 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

Maven 替换源 maven源代码剖析_java

 

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的情况进行日志的打印。就像这样:

Maven 替换源 maven源代码剖析_maven_02

 

 

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

待续。。。