maven的插件
我們知道Maven具體構建動作都是由插件執行的,maven本身只是提供一個框架,這樣就提供了高度可定制化的功能,我們用maven命令執行比如mvn clean package這樣的命令時maven會將package這個階段(phase)綁定到相應的生命周期(lifecycle),再尋找項目(project)里配置的plugin,執行具體的plugin完成持續構建
maven綁定插件(plugin)
maven在讀取命令行之后會根據命令行參數是系統默認的phase還是其他的自定義插件(goal)來解析成task參數,繼而根據這些task參數來生成插件執行列表。
for ( Object task : tasks ) { if ( task instanceof GoalTask ) { String pluginGoal = ( (GoalTask) task ).pluginGoal; MojoDescriptor mojoDescriptor = mojoDescriptorCreator.getMojoDescriptor( pluginGoal, session, project ); MojoExecution mojoExecution = new MojoExecution( mojoDescriptor, "default-cli", MojoExecution.Source.CLI ); mojoExecutions.add( mojoExecution ); } else if ( task instanceof LifecycleTask ) { String lifecyclePhase = ( (LifecycleTask) task ).getLifecyclePhase(); Map<String, List<MojoExecution>> phaseToMojoMapping = calculateLifecycleMappings( session, project, lifecyclePhase ); for ( List<MojoExecution> mojoExecutionsFromLifecycle : phaseToMojoMapping.values() ) { mojoExecutions.addAll( mojoExecutionsFromLifecycle ); } } else { throw new IllegalStateException( "unexpected task " + task ); } }
代碼里先判斷task的類型,如果是GoalTask就說明參數是clean:clean這一種帶goal的格式,這種情況可能是maven內置插件也可能是開發者自己開發的插件,這種情況下maven就會先根據插件的goal去找到具體的插件,找的方法是先從項目定義的插件里找,找不到的話再去倉庫里找,這里把核心部分代碼貼出來:
PluginDescriptor pluginDescriptor = pluginManager.loadPlugin( plugin, request.getRepositories(), request.getRepositorySession() ); if ( request.getPrefix().equals( pluginDescriptor.getGoalPrefix() ) ) { return new DefaultPluginPrefixResult( plugin ); }
這個這部分比較簡單,就是遍歷項目里的插件一個個加載,然后比較這個插件的goal前綴是否和命令行的請求相同,如果相同的話直接封裝下該插件返回。其他用groupId:artifactId:version:goal格式的命令行解析也是差不多的方法,根據這些信息加載響應的plugin插件。接下來要重點說的是比較復雜的情況,即clean package這種比較內置的phase,下面代碼一一解讀: 先根據lifecyclephase來決定是哪個lifecycle,比如package這個phase就是在default生命周期里,maven內置定義了clean、default、site三個生命周期,maven采用plexus作為IOC容器,這個defaultLifeCycles的依賴是在maven-core的component.xml中定義的,與此同時定義了各個生命周期里的phase,這個讀者感興趣可以去看相應的代碼,此處略去不表。回到這里的代碼,maven根據phase去默認找生命周期,這里通過package找到了default生命周期。
/* * Determine the lifecycle that corresponds to the given phase. */ Lifecycle lifecycle = defaultLifeCycles.get( lifecyclePhase ); if ( lifecycle == null ) { throw new LifecyclePhaseNotFoundException( "Unknown lifecycle phase \"" + lifecyclePhase + "\". You must specify a valid lifecycle phase" + " or a goal in the format <plugin-prefix>:<goal> or" + " <plugin-group-id>:<plugin-artifact-id>[:<plugin-version>]:<goal>. Available lifecycle phases are: " + defaultLifeCycles.getLifecyclePhaseList() + ".", lifecyclePhase ); }
接下來遍歷default lifecycle下所有的phase直到package這個phase就退出遍歷循環,這里體現了maven的一個特性,就是如果你指定了某個生命周期中某個phase,那這個phase之前的phase都會被執行,這里主要是初始化mappings包含哪些phase,每個phase的插件列表之所以是TreeMap是因為后面要根據優先級,也就是Key來排序遍歷確定插件執行順序,每個phase具體要執行的插件到下一段代碼再寫入。
/* * Initialize mapping from lifecycle phase to bound mojos. The key set of this map denotes the phases the caller * is interested in, i.e. all phases up to and including the specified phase. */ Map<String, Map<Integer, List<MojoExecution>>> mappings = new LinkedHashMap<String, Map<Integer, List<MojoExecution>>>(); for ( String phase : lifecycle.getPhases() ) { Map<Integer, List<MojoExecution>> phaseBindings = new TreeMap<Integer, List<MojoExecution>>(); mappings.put( phase, phaseBindings ); if ( phase.equals( lifecyclePhase ) ) { break; } }
接下來遍歷本項目中所有插件,每個插件在遍歷所有執行配置,如果execution配置里已經指定了phase,則將這個execution下所有goal對應的Mojo加到對應phase的執行map里,如果execution配置里沒有指定phase的話,那就要去遍歷這個execution下所有goal,依次獲取該goal的Mojo描述信息,根據每個Mojo綁定的phase來將該Mojo加到對應phase的執行map里。
/* * Grab plugin executions that are bound to the selected lifecycle phases from project. The effective model of * the project already contains the plugin executions induced by the project's packaging type. Remember, all * phases of interest and only those are in the lifecyle mapping, if a phase has no value in the map, we are not * interested in any of the executions bound to it. */ for ( Plugin plugin : project.getBuild().getPlugins() ) { for ( PluginExecution execution : plugin.getExecutions() ) { // if the phase is specified then I don't have to go fetch the plugin yet and pull it down // to examine the phase it is associated to. if ( execution.getPhase() != null ) { Map<Integer, List<MojoExecution>> phaseBindings = mappings.get( execution.getPhase() ); if ( phaseBindings != null ) { for ( String goal : execution.getGoals() ) { MojoExecution mojoExecution = new MojoExecution( plugin, goal, execution.getId() ); mojoExecution.setLifecyclePhase( execution.getPhase() ); addMojoExecution( phaseBindings, mojoExecution, execution.getPriority() ); } } } // if not then i need to grab the mojo descriptor and look at the phase that is specified else { for ( String goal : execution.getGoals() ) { MojoDescriptor mojoDescriptor = pluginManager.getMojoDescriptor( plugin, goal, project.getRemotePluginRepositories(), session.getRepositorySession() ); Map<Integer, List<MojoExecution>> phaseBindings = mappings.get( mojoDescriptor.getPhase() ); if ( phaseBindings != null ) { MojoExecution mojoExecution = new MojoExecution( mojoDescriptor, execution.getId() ); mojoExecution.setLifecyclePhase( mojoDescriptor.getPhase() ); addMojoExecution( phaseBindings, mojoExecution, execution.getPriority() ); } } } } }
經過前面幾個步驟之后,已經拿到了所有phase對應的Mojo執行列表,接下來需要將所有phase的Mojo串起來到一個總的列表里,這里注意mappings是一個LinkedHashMap,所以遍歷的時候是有順序的,而每個phase的execution map是TreeMap,根據優先級排序,這樣最后總體的順序是先按照總體的phase順序,再按照phase內的優先級進行排序。
Map<String, List<MojoExecution>> lifecycleMappings = new LinkedHashMap<String, List<MojoExecution>>(); for ( Map.Entry<String, Map<Integer, List<MojoExecution>>> entry : mappings.entrySet() ) { List<MojoExecution> mojoExecutions = new ArrayList<MojoExecution>(); for ( List<MojoExecution> executions : entry.getValue().values() ) { mojoExecutions.addAll( executions ); } lifecycleMappings.put( entry.getKey(), mojoExecutions ); }