最近的工作中设计了一个maven插件,需要在插件执行时的增加新的依赖库,本文作为总结,说明如何在插件执行时自主注入新的依赖库。
动态依赖注入实现
示例解析
通过ExampleMojo
插件,我们可以在编译阶段动态注入指定的依赖:
public void execute() throws MojoExecutionException {
ArtifactSupport.injectCacheEngine(
project, repositorySystem,
repoSession, remoteRepos, getLog()
);
// 执行核心业务逻辑
processAspectWeaving();
}
核心注入逻辑通过Aether实现依赖解析:
// 构建Aether构件坐标
Artifact aetherArtifact = new DefaultArtifact(
"com.example",
"cache-engine",
"jar",
"2.1.0"
);
// 解析远程仓库中的构件
ArtifactResult result = repoSystem.resolveArtifact(
repoSession,
new ArtifactRequest(aetherArtifact, remoteRepos)
);
// 转换为Maven原生Artifact
DefaultArtifact mavenArtifact = new DefaultArtifact(
result.getArtifact().getGroupId(),
result.getArtifact().getArtifactId(),
result.getArtifact().getVersion(),
"provided",
result.getArtifact().getExtension(),
result.getArtifact().getClassifier(),
new DefaultArtifactHandler("jar")
);
// 绑定物理文件路径
mavenArtifact.setFile(result.getArtifact().getFile());
// 注入项目构建流程
project.getArtifacts().add(mavenArtifact);
为什么直接修改dependencies无效?
开始看到MaveProject有dependencies字段,就想当然的认为只要将依赖添加到dependencies列表中就可以了,事实上没有效果,原因如下:
- 生命周期限制:依赖解析在initialize阶段完成,后续修改不会触发重新解析
- 作用域隔离:
project.dependencies
管理声明式依赖,project.artifacts
存储已解析结果 - 缓存机制:Maven会缓存依赖树,运行时修改无法影响已缓存的元数据
架构演进背后的思考
做这个插件时,开始参考了很多网上的示例,多数有些过时,使用了一些废弃的注解和对象,导致我走了弯路,在此一并总结。
从@Component到JSR 330
@Component
注解废弃了,旧版注入方式:
@Component
private ArtifactFactory factory;
现代注入规范:
@Inject
public ExampleMojo(RepositorySystem system) {
this.repositorySystem = system;
}
/** 也可以直接将注解写在字段上,但字段不可定义为final */
@Inject
private RepositorySystem repositorySystem;
演进原因:
- 标准化:遵循Java依赖注入标准(JSR 330)
- 兼容性:支持多种DI容器(Guice、Spring等)
- 可测试性:构造函数注入更易于单元测试
- 生命周期:明确组件初始化顺序
演进优势对比:
特性 | 旧方案(@Component) | 新方案(@Inject) |
---|---|---|
标准化 | Maven专属 | Java EE标准 |
可测试性 | 需要模拟Plexus容器 | 支持任意DI容器 |
生命周期管理 | 隐式初始化 | 显式构造函数控制 |
兼容性 | 仅限Maven 2.x | 支持Maven 3.x+ |
Aether的崛起
ArtifactFactory
也废弃了,这又是一个坑,根据 Maven 3.0+ API 规范,使用 纯 Aether API 实现 Artifact 管理,完全替代废弃的 ArtifactFactory
Maven依赖管理演进路线:
Maven 2.x (原生) → Maven 3.x (Aether) → Maven Resolver (最新)
优势对比:
特性 | 原生实现 | Aether |
---|---|---|
解析速度 | 100ms/req | 50ms/req |
并发支持 | 无 | 有 |
扩展性 | 有限 | 插件化架构 |
依赖树算法 | 简单DFS | 高级冲突解决 |
远程仓库协议 | HTTP/FTP | 支持S3等 |
完整实现代码
ArtifactSupport.java(核心工具类)
import java.util.List;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.resolution.ArtifactRequest;
import org.eclipse.aether.resolution.ArtifactResolutionException;
import org.eclipse.aether.resolution.ArtifactResult;
import org.apache.maven.artifact.ArtifactUtils;
import org.apache.maven.model.Dependency;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.project.MavenProject;
public class ArtifactSupport{
/** 切面库模块名称 */
private static final String AOCACHE_AJ = "aocache-aj";
/** aocache 运行时依赖名称 */
private static final String AOCACHE = "aocache";
/** aocache 组名 */
private static final String AOCACHE_GROUP_ID = "com.gitee.l0km";
static void addAocacheAjArtifact(
MavenProject project,
RepositorySystem repoSystem,
RepositorySystemSession repoSession,
List<RemoteRepository> remoteRepos,
Log log
) throws MojoExecutionException {
// 1. 检查运行时依赖是否存在
Dependency runtimeDep = project.getDependencies().stream()
.filter(d -> AOCACHE_GROUP_ID.equals(d.getGroupId())
&& AOCACHE.equals(d.getArtifactId()))
.findFirst()
.orElseThrow(() -> new MojoExecutionException("缺少aocache运行时依赖"));
// 2. 构建aocache-aj构件坐标
String ajVersion = runtimeDep.getVersion();
Artifact ajArtifact = new DefaultArtifact(
AOCACHE_GROUP_ID,
AOCACHE_AJ,
"jar",
ajVersion
);
// 3. 检查是否已存在该构件
boolean exists = project.getArtifacts().stream()
.anyMatch(a ->
AOCACHE_GROUP_ID.equals(a.getGroupId()) &&
AOCACHE_AJ.equals(a.getArtifactId()) &&
ajVersion.equals(a.getVersion()) &&
"jar".equals(a.getType()) &&
(a.getClassifier() == null || a.getClassifier().isEmpty())
);
if (exists) {
log.debug("aocache-aj已存在于项目artifacts");
return;
}
// 4. 解析构件
try {
ArtifactResult result = repoSystem.resolveArtifact(
repoSession,
new ArtifactRequest(ajArtifact, remoteRepos, null)
);
// 5. 转换为Maven原生Artifact并注入
org.apache.maven.artifact.Artifact mavenArtifact =
new org.apache.maven.artifact.DefaultArtifact( // 使用maven-core内置实现
result.getArtifact().getGroupId(),
result.getArtifact().getArtifactId(),
result.getArtifact().getVersion(),
"provided",
result.getArtifact().getExtension(),
result.getArtifact().getClassifier(),
null
);
mavenArtifact.setFile(result.getArtifact().getFile());
project.getArtifacts().add(mavenArtifact);
// project.addAttachedArtifact(mavenArtifact);
log.info("成功注入aocache-aj构件: " + ArtifactUtils.key(mavenArtifact));
} catch (ArtifactResolutionException e) {
throw new MojoExecutionException("解析aocache-aj失败: " + ajArtifact, e);
}
}
}
ExampleMojo.java(插件入口)
// 导入包略
@Mojo( name="example", defaultPhase = LifecyclePhase.COMPILE, requiresDependencyResolution = ResolutionScope.COMPILE, threadSafe = true )
public class ExampleMojo extends AbstractMojo {
@Inject
private RepositorySystem repositorySystem;
@Parameter(defaultValue = "${repositorySystemSession}")
private RepositorySystemSession repoSession;
@Parameter(defaultValue = "${project.remoteProjectRepositories}")
private List<RemoteRepository> remoteRepos;
@Override
public void execute() {
// 依赖注入与业务逻辑分离
ArtifactSupport.injectCacheEngine(project, repoSystem, ...);
processBusinessLogic();
}
private void processBusinessLogic() {
// 核心业务实现...
}
}