maven是一个非常经典的和通用的项目管理工具,虽然现在热炒gradle将作为下一代 项目管理工具来取代maven,但是 由于maven强大和健全的功能,maven还有很强的生命力。
本文将介绍maven对于项目生命周期的设计以及原理。
一、 maven对项目生命周期的抽象--三大项目生命周期
maven从项目的三个不同的角度,定义了单套生命周期,三套生命周期是相互独立的,它们之间不会相互影响。
默认构建生命周期(Default Lifeclyle): 该生命周期表示这项目的构建过程,定义了一个项目的构建要经过的不同的阶段。
清理生命周期(Clean Lifecycle): 该生命周期负责清理项目中的多余信息,保持项目资源和代码的整洁性。一般拿来清空directory(即一般的target)目录下的文件。
站点管理生命周期(Site Lifecycle) :向我们创建一个项目时,我们有时候需要提供一个站点,来介绍这个项目的信息,如项目介绍,项目进度状态、项目组成成员,版本控制信息,项目javadoc索引信息等等。站点管理生命周期定义了站点管理过程的各个阶段。
本文只介绍maven项目默认的生命周期,其他两个生命周期将另起博文介绍。
二、 maven对项目默认生命周期的抽象
maven根据一个项目的生命周期的每个阶段,将一个项目的生命周期抽象成了如上图所示的23个阶段。而每一个阶段应该干什么事情由用户决定。换句话说,maven为每一个阶段设计了接口,你可以为每一阶段自己定义一个接口,进而实现对应阶段应该有的行为。关于如何为某个生命周期阶段绑定自定义的行为,我将在后面的章节介绍。
三、 maven指令与生命周期阶段的关系
四、maven生命周期各个阶段的行为与maven默认行为
使用过maven的读者会经常使用这些maven指令:
- mvn compile
- mvn package
- mvn install
- mvn deploy
在经历这些生命周期的阶段中,每个阶段会理论上会有相应的处理操作。但是,在实际的项目开发过程中, 并不是所有的生命周期阶段都是必须的。
然而,在实际的开发过程中,往往我们的项目的一些生命周期的阶段不需要相应的行为,我们只需要关心其中某些重要的生命周期阶段而已。下面,请看一下日常开发中,我们需要关注的生命周期阶段,即广大开发人员对项目周期阶段处理的约定:
1).应该将resource资源文件准备好,放到指定的target目录下----process-resources 阶段;
2).将java源文件编译成.class文件,然后将class 文件放置到对应的target目录下----compile阶段;
3).将test类型的resource移动到指定的 target目录下------process-test-resource阶段;
4).将test类型的java 源文件编译成class文件,然后放置到指定的target目录下------test-compile阶段;
5).运行test测试用例-------test阶段;
6).将compile阶段编译的class文件和resource资源打包成jar包或war包--------package阶段;
7).将生成的包安装到本地仓库中------install阶段
8).将生成的包部署到远程仓库中-----deploy阶段
由上面的约定可以看出,在大多数情况下,大家关心的项目生命周期阶段仅仅是上面的8个而已。跟上面maven对生命周期阶段23个的抽象相比,这就少的很多了。
基于类似的约定,maven默认地为一些不同类型的maven项目生命周期的阶段实现了默认的行为。
maven 在设计上将生命周期阶段的抽象和对应阶段应该执行的行为实现分离开,maven这些实现放到了插件中,这些插件本质上是实现了maven留在各个生命周期阶段的接口。关于插件的问题,我将另外写一篇博文介绍。
如下图所示,maven针对不同打包类型的maven项目的生命周期阶段绑定了对应的默认行为:
如上图所示,对于不同的打包格式的项目而言,maven为特定类型的包格式项目在不同的生命周期阶段的默认行为。
而对于我们经常使用的jar和war包格式的项目而言,maven总共为其规定了以下几个生命周期阶段的默认行为:
五、 maven项目的目录结构
well,每个项目工程,都有非常繁琐的目录结构,每个目录都有不同的作用。请记住这一点,目录的划分是根据需要来的,每个目录有其特定的功能。目录本质上就是一个文件或文件夹路径而已。那么,我们换一个思路考虑:一个项目的文件结构需要组织什么信息呢?让我们来看一下功能的划分:
如上图所示,你会看到maven项目里不同功能类型的目录定义以及maven默认的目录的路径。
请注意:对于maven管理项目工程的生命周期的操作上, 都发生在上述的几种目录中。换句话说,实质上,maven的项目管理的整个过程,就是围绕着对上述几种文件目录中内容的操作。
六、maven为生命周期阶段绑定特定行为动作的机制(即插件原理)
为maven生命周期的某些阶段绑定特定行为或动作,简单点就是调用一段代码而已,maven将需要执行的逻辑抽象成了一个接口,接口为 Mojo。Mojo是 Maven Old plain Java Object的简写,表示的意思是Mojo是maven的一个简单的对象。如下图所示:
maven通过为某一个项目的生命周期阶段绑定若干个Mojo,然后依次执行Mojo.execute()方法,从而实现特定生命周期应该执行的动作。
举例:比如,对于生命周期阶段process-resources,maven默认地为其绑定了一个Mojo: org.apache.maven.plugin.resources.ResourcesMojo。
当需要经历process-resources阶段时,maven将会创建一个ResourcesMojo 实例instance,然后调用instance.execute()方法。
- @Mojo( name = "resources", defaultPhase = LifecyclePhase.PROCESS_RESOURCES, threadSafe = true )
- public class ResourcesMojo
- extends AbstractMojo
- implements Contextualizable
- {
-
-
-
-
-
-
-
- @Parameter( property = "encoding", defaultValue = "${project.build.sourceEncoding}" )
- protected String encoding;
-
-
-
-
- @Parameter( defaultValue = "${project.build.outputDirectory}", required = true )
- private File outputDirectory;
-
-
-
-
- @Parameter( defaultValue = "${project.resources}", required = true, readonly = true )
- private List<Resource> resources;
-
-
-
-
- @Parameter( defaultValue = "${project}", required = true, readonly = true )
- protected MavenProject project;
-
-
-
-
-
-
-
-
-
- @Parameter( defaultValue = "${project.build.filters}", readonly = true )
- protected List<String> buildFilters;
-
-
-
-
-
-
-
-
-
-
-
-
-
- @Parameter
- protected List<String> filters;
-
-
-
-
-
-
-
-
-
- @Parameter( defaultValue = "true" )
- protected boolean useBuildFilters;
-
-
-
-
- @Component( role = MavenResourcesFiltering.class, hint = "default" )
- protected MavenResourcesFiltering mavenResourcesFiltering;
-
-
-
-
- @Parameter( defaultValue = "${session}", required = true, readonly = true )
- protected MavenSession session;
-
-
-
-
-
-
-
- @Parameter( property = "maven.resources.escapeString" )
- protected String escapeString;
-
-
-
-
-
-
- @Parameter( property = "maven.resources.overwrite", defaultValue = "false" )
- private boolean overwrite;
-
-
-
-
-
-
- @Parameter( property = "maven.resources.includeEmptyDirs", defaultValue = "false" )
- protected boolean includeEmptyDirs;
-
-
-
-
-
-
- @Parameter
- protected List<String> nonFilteredFileExtensions;
-
-
-
-
-
-
- @Parameter( property = "maven.resources.escapeWindowsPaths", defaultValue = "true" )
- protected boolean escapeWindowsPaths;
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- @Parameter
- protected List<String> delimiters;
-
-
-
-
- @Parameter( defaultValue = "true" )
- protected boolean useDefaultDelimiters;
-
-
-
-
-
-
-
-
-
- @Parameter
- private List<String> mavenFilteringHints;
-
-
-
-
- private PlexusContainer plexusContainer;
-
-
-
-
- private List<MavenResourcesFiltering> mavenFilteringComponents = new ArrayList<MavenResourcesFiltering>();
-
-
-
-
-
-
- @Parameter( property = "maven.resources.supportMultiLineFiltering", defaultValue = "false" )
- private boolean supportMultiLineFiltering;
-
- public void contextualize( Context context )
- throws ContextException
- {
- plexusContainer = (PlexusContainer) context.get( PlexusConstants.PLEXUS_KEY );
- }
-
- public void execute()
- throws MojoExecutionException
- {
- try
- {
-
- if ( StringUtils.isEmpty( encoding ) && isFilteringEnabled( getResources() ) )
- {
- getLog().warn( "File encoding has not been set, using platform encoding " + ReaderFactory.FILE_ENCODING
- + ", i.e. build is platform dependent!" );
- }
-
- List filters = getCombinedFiltersList();
-
- MavenResourcesExecution mavenResourcesExecution =
- new MavenResourcesExecution( getResources(), getOutputDirectory(), project, encoding, filters,
- Collections.<String>emptyList(), session );
-
- mavenResourcesExecution.setEscapeWindowsPaths( escapeWindowsPaths );
-
-
-
- mavenResourcesExecution.setInjectProjectBuildFilters( false );
-
- mavenResourcesExecution.setEscapeString( escapeString );
- mavenResourcesExecution.setOverwrite( overwrite );
- mavenResourcesExecution.setIncludeEmptyDirs( includeEmptyDirs );
- mavenResourcesExecution.setSupportMultiLineFiltering( supportMultiLineFiltering );
-
-
- if ( delimiters != null && !delimiters.isEmpty() )
- {
- LinkedHashSet<String> delims = new LinkedHashSet<String>();
- if ( useDefaultDelimiters )
- {
- delims.addAll( mavenResourcesExecution.getDelimiters() );
- }
-
- for ( String delim : delimiters )
- {
- if ( delim == null )
- {
-
- delims.add( "${*}" );
- }
- else
- {
- delims.add( delim );
- }
- }
-
- mavenResourcesExecution.setDelimiters( delims );
- }
-
- if ( nonFilteredFileExtensions != null )
- {
- mavenResourcesExecution.setNonFilteredFileExtensions( nonFilteredFileExtensions );
- }
- mavenResourcesFiltering.filterResources( mavenResourcesExecution );
-
- executeUserFilterComponents( mavenResourcesExecution );
- }
- catch ( MavenFilteringException e )
- {
- throw new MojoExecutionException( e.getMessage(), e );
- }
- }
-
-
-
-
- protected void executeUserFilterComponents( MavenResourcesExecution mavenResourcesExecution )
- throws MojoExecutionException, MavenFilteringException
- {
-
- if ( mavenFilteringHints != null )
- {
- for ( Iterator ite = mavenFilteringHints.iterator(); ite.hasNext(); )
- {
- String hint = (String) ite.next();
- try
- {
- mavenFilteringComponents.add(
- (MavenResourcesFiltering) plexusContainer.lookup( MavenResourcesFiltering.class.getName(),
- hint ) );
- }
- catch ( ComponentLookupException e )
- {
- throw new MojoExecutionException( e.getMessage(), e );
- }
- }
- }
- else
- {
- getLog().debug( "no use filter components" );
- }
-
- if ( mavenFilteringComponents != null && !mavenFilteringComponents.isEmpty() )
- {
- getLog().debug( "execute user filters" );
- for ( MavenResourcesFiltering filter : mavenFilteringComponents )
- {
- filter.filterResources( mavenResourcesExecution );
- }
- }
- }
-
- protected List<String> getCombinedFiltersList()
- {
- if ( filters == null || filters.isEmpty() )
- {
- return useBuildFilters ? buildFilters : null;
- }
- else
- {
- List<String> result = new ArrayList<String>();
-
- if ( useBuildFilters && buildFilters != null && !buildFilters.isEmpty() )
- {
- result.addAll( buildFilters );
- }
-
- result.addAll( filters );
-
- return result;
- }
- }
-
-
-
-
-
-
-
- private boolean isFilteringEnabled( Collection<Resource> resources )
- {
- if ( resources != null )
- {
- for ( Resource resource : resources )
- {
- if ( resource.isFiltering() )
- {
- return true;
- }
- }
- }
- return false;
- }
- }
上面只是介绍了Maven生命周期阶段绑定执行代码的基本模式,由于maven的生命周期众多,并且每个生命周期内有可能绑定多个Mojo,如果使用上述的模式简单关联的话,会显得结构组织很乱。
maven会根据Mojo功能的划分,将具有相似功能的Mojo放到一个插件中。并且某一个特定的Mojo能实现的功能称为 goal,即目标,表明该Mojo能实现什么目标。
例如,我们项目生命周期有两个阶段:compile 和 test-compile,这两阶段都是需要将Java源代码编译成class文件中,相对应地,compile和test-compiler分别被绑定到了org.apache.maven.plugin.compiler.CompilerMojo 和org.apache.maven.plugin.compiler.TestCompilerMojo上: