SpringBootCLI使用相关
介绍
Spring Boot CLI(Command Line Interface)是一个命令行工具,我们可以用它来快速构建Spring原型应用。通过Spring Boot CLI,我们可以通过编写Java脚本来快速的构建出Spring Boot应用,并通过命令行的方式将其运行起来。
下载工具包
这里我们使用 2.0.1.RELEASE
版本,更高的版本可能需要更高的JDK版本来配合。我们也需要提前配置JAVA路径的环境变量,这里使用JDK11。
运行Java脚本
脚本如下
@RestController
public class HelloController {
@RequestMapping("/")
public String home() {
return "Hello World!";
}
}
运行命令
.\spring.bat run HelloController.java
运行结果
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by org.codehaus.groovy.vmplugin.v7.Java7$1 (jar:file:/C:/Users/xxx/Downloads/spring-boot-cli-2.0.1.RELEASE-bin/spring-2.0.1.RELEASE/lib/spring-boot-cli-2.0.1.RELEASE.jar!/BOOT-INF/lib/groovy-2.4.15.jar!/) to constructor java.lang.invoke.MethodHandles$Lookup(java.lang.Class,int)
WARNING: Please consider reporting this to the maintainers of org.codehaus.groovy.vmplugin.v7.Java7$1
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
Resolving dependencies..............................................................................
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.0.1.RELEASE)
2023-01-30 14:35:29.867 INFO 14080 --- [ runner-0] o.s.boot.SpringApplication : Starting application on SH-FM-NB-320 with PID 14080 (started by xxx in C:\Users\xxx\Downloads\spring-boot-cli-2.0.1.RELEASE-bin\spring-2.0.1.RELEASE\bin)
2023-01-30 14:35:29.881 INFO 14080 --- [ runner-0] o.s.boot.SpringApplication : No active profile set, falling back to default profiles: default
2023-01-30 14:35:30.096 INFO 14080 --- [ runner-0] ConfigServletWebServerApplicationContext : Refreshing org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@1650a964: startup date [Mon Jan 30 14:35:30 CST 2023]; root of context hierarchy
2023-01-30 14:35:31.494 INFO 14080 --- [ runner-0] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2023-01-30 14:35:31.527 INFO 14080 --- [ runner-0] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2023-01-30 14:35:31.527 INFO 14080 --- [ runner-0] org.apache.catalina.core.StandardEngine : Starting Servlet Engine: Apache Tomcat/8.5.29
2023-01-30 14:35:31.540 INFO 14080 --- [ost-startStop-1] o.a.catalina.core.AprLifecycleListener : The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: [C:\D-myfiles\java\jdk\jdk-11\bin;C:\Windows\Sun\Java\bin;C:\Windows\system32;C:\Windows;C:\Python27\;C:\Python27\Scripts;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Windows\System32\OpenSSH\;C:\Program Files\PuTTY\;C:\Program Files (x86)\Tencent\微信web开发者工具\dll;C:\Users\xxx\AppData\Local\Microsoft\WindowsApps;C:\Users\xxx\AppData\Local\Programs\Git\cmd;C:\D-myfiles\java\jdk\jdk-11\bin;C:\D-myfiles\java\apache-maven-3.6.1\bin;C:\Users\xxx\AppData\Local\Programs\Microsoft VS Code\bin;.]
2023-01-30 14:35:31.599 INFO 14080 --- [ost-startStop-1] org.apache.catalina.loader.WebappLoader : Unknown loader org.springframework.boot.cli.compiler.ExtendedGroovyClassLoader$DefaultScopeParentClassLoader@22eeefeb class org.springframework.boot.cli.compiler.ExtendedGroovyClassLoader$DefaultScopeParentClassLoader
2023-01-30 14:35:31.691 INFO 14080 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2023-01-30 14:35:31.691 INFO 14080 --- [ost-startStop-1] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 1596 ms
2023-01-30 14:35:31.910 INFO 14080 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean : Servlet dispatcherServlet mapped to [/]
2023-01-30 14:35:31.921 INFO 14080 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'characterEncodingFilter' to: [/*]
2023-01-30 14:35:31.921 INFO 14080 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2023-01-30 14:35:31.922 INFO 14080 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2023-01-30 14:35:31.922 INFO 14080 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'requestContextFilter' to: [/*]
2023-01-30 14:35:32.150 INFO 14080 --- [ runner-0] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2023-01-30 14:35:32.460 INFO 14080 --- [ runner-0] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@1650a964: startup date [Mon Jan 30 14:35:30 CST 2023]; root of context hierarchy
2023-01-30 14:35:32.564 INFO 14080 --- [ runner-0] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/]}" onto public java.lang.String HelloController.home()
2023-01-30 14:35:32.569 INFO 14080 --- [ runner-0] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2023-01-30 14:35:32.570 INFO 14080 --- [ runner-0] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2023-01-30 14:35:32.607 INFO 14080 --- [ runner-0] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2023-01-30 14:35:32.608 INFO 14080 --- [ runner-0] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2023-01-30 14:35:33.054 INFO 14080 --- [ runner-0] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup
2023-01-30 14:35:33.101 INFO 14080 --- [ runner-0] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2023-01-30 14:35:33.107 INFO 14080 --- [ runner-0] o.s.boot.SpringApplication : Started application in 3.938 seconds (JVM running for 102.914)
2023-01-30 14:36:38.790 INFO 14080 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring FrameworkServlet 'dispatcherServlet'
2023-01-30 14:36:38.790 INFO 14080 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization started
2023-01-30 14:36:38.831 INFO 14080 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization completed in 41 ms
会先去maven中央仓库下载依赖,然后运行。
原理分析
在任意一个maven项目中添加此依赖,最好不要是SpringBoot项目,可能有未知的问题。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-cli</artifactId>
<version>2.0.1.RELEASE</version>
</dependency>
启动类为 org.springframework.boot.cli.SpringCli,这里我们以 spring run xxx.java
命令为例来分析原理。
- RunCommand的run()方法
- RunOptionHandler的run()方法
- SpringApplicationRunner的compileAndRun()方法,开始编译并运行
- GroovyCompiler的compile()方法,将java文件编译为class
- 开启一个守护线程RunThread
- 创建一个SpringApplicationLauncher来启动应用程序,简单来说就是通过反射来创建 org.springframework.boot.SpringApplication 对象并调用run()方法,和我们自己启动SpringBoot项目类似。
- 编译后得到的HelloController类通过构造器参数传入SpringApplication。
在创建GroovyCompiler对象的过程中也会处理maven依赖相关,这块逻辑比较复杂,核心为AetherGrapeEngine类。
顺便一提,.\spring.bat init --dependencies=web myproject
内部就是直接调用 https://start.spring.io/starter.zip 生成项目代码,接口返回为zip文件,再解压为文件夹,起始类为InitCommand。
源码运行遇到的问题
Unable to find groovy JAR
缩短命令行的方式,不能选择第三或第四种,可以选择第一或第二种。
maven依赖的处理
点击查看代码
import lombok.Data;
import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
import org.eclipse.aether.DefaultRepositorySystemSession;
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.collection.CollectRequest;
import org.eclipse.aether.collection.DependencyCollectionException;
import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory;
import org.eclipse.aether.graph.Dependency;
import org.eclipse.aether.graph.DependencyNode;
import org.eclipse.aether.impl.DefaultServiceLocator;
import org.eclipse.aether.repository.Authentication;
import org.eclipse.aether.repository.LocalRepository;
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.eclipse.aether.resolution.DependencyRequest;
import org.eclipse.aether.resolution.DependencyResolutionException;
import org.eclipse.aether.resolution.DependencyResult;
import org.eclipse.aether.spi.connector.RepositoryConnectorFactory;
import org.eclipse.aether.spi.connector.transport.TransporterFactory;
import org.eclipse.aether.transport.file.FileTransporterFactory;
import org.eclipse.aether.transport.http.HttpTransporterFactory;
import org.eclipse.aether.util.repository.AuthenticationBuilder;
import org.springframework.util.StringUtils;
import java.io.File;
/**
* 下载maven依赖,使用Aether框架,逻辑简化版
* 参考 https://www.cnblogs.com/xiaosiyuan/articles/5887642.html
* https://wusandi.github.io/2018/10/29/equinox-aether-introduction/
* 核心类为 RepositorySystem,RepositorySystemSession,使用这两个类从指定的远程仓库下载指定的jar包
*/
public class TestMavenDependencyDownload2 {
public static void main(String[] args) throws Exception {
Params params = new Params();
params.setGroupId("org.springframework.boot");
params.setArtifactId("spring-boot-starter");
params.setVersion("2.0.1.RELEASE");
downLoadDependency(params);
}
private static RepositorySystem newRepositorySystem() {
DefaultServiceLocator locator = MavenRepositorySystemUtils.newServiceLocator();
locator.addService(RepositoryConnectorFactory.class, BasicRepositoryConnectorFactory.class);
locator.addService(TransporterFactory.class, FileTransporterFactory.class);
locator.addService(TransporterFactory.class, HttpTransporterFactory.class);
return locator.getService(RepositorySystem.class);
}
private static RepositorySystemSession newSession(RepositorySystem system, String target) {
DefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession();
LocalRepository localRepo = new LocalRepository( /*"target/local-repo" */target);
session.setLocalRepositoryManager(system.newLocalRepositoryManager(session, localRepo));
return session;
}
public static void downLoadArtifact(Params params) throws ArtifactResolutionException {
String groupId = params.getGroupId();
String artifactId = params.getArtifactId();
String version = params.getVersion();
String repositoryUrl = params.getRepository();
String target = params.getTarget();
String username = params.getUsername();
String password = params.getPassword();
RepositorySystem repoSystem = newRepositorySystem();
RepositorySystemSession session = newSession(repoSystem, target);
RemoteRepository central = null;
if (username == null && password == null) {
central = new RemoteRepository.Builder("central", "default", repositoryUrl).build();
} else {
Authentication authentication = new AuthenticationBuilder().addUsername(username).addPassword(password).build();
central = new RemoteRepository.Builder("central", "default", repositoryUrl).setAuthentication(authentication).build();
}
/**
* 下载一个jar包
*/
Artifact artifact = new DefaultArtifact(groupId, artifactId, "jar", version);
ArtifactRequest artifactRequest = new ArtifactRequest();
artifactRequest.addRepository(central);
artifactRequest.setArtifact(artifact);
ArtifactResult artifactResult = repoSystem.resolveArtifact(session, artifactRequest);
System.out.println("artifactResult: " + artifactResult.getArtifact().getFile());
}
public static void downLoadDependency(Params params) throws ArtifactResolutionException, DependencyCollectionException, DependencyResolutionException {
String groupId = params.getGroupId();
String artifactId = params.getArtifactId();
String version = params.getVersion();
String repositoryUrl = params.getRepository();
String target = params.getTarget();
String username = params.getUsername();
String password = params.getPassword();
RepositorySystem repoSystem = newRepositorySystem();
RepositorySystemSession session = newSession(repoSystem, target);
RemoteRepository central = null;
if (username == null && password == null) {
central = new RemoteRepository.Builder("central", "default", repositoryUrl).build();
} else {
Authentication authentication = new AuthenticationBuilder().addUsername(username).addPassword(password).build();
central = new RemoteRepository.Builder("central", "default", repositoryUrl).setAuthentication(authentication).build();
}
Artifact artifact = new DefaultArtifact(groupId, artifactId, "jar", version);
ArtifactRequest artifactRequest = new ArtifactRequest();
artifactRequest.addRepository(central);
artifactRequest.setArtifact(artifact);
/**
* 下载该jar包及其所有依赖jar包
*/
Dependency dependency = new Dependency(artifact, null);
CollectRequest collectRequest = new CollectRequest();
collectRequest.setRoot(dependency);
collectRequest.addRepository(central);
DependencyNode node = repoSystem.collectDependencies(session, collectRequest).getRoot();
DependencyRequest dependencyRequest = new DependencyRequest();
dependencyRequest.setRoot(node);
DependencyResult dependencyResult = repoSystem.resolveDependencies(session, dependencyRequest);
for (ArtifactResult artifactResult : dependencyResult.getArtifactResults()) {
System.out.println(artifactResult.getArtifact().getFile());
}
}
private static File getM2RepoDirectory() {
return new File(getDefaultM2HomeDirectory(), "repository");
}
private static File getDefaultM2HomeDirectory() {
String mavenRoot = System.getProperty("maven.home");
if (StringUtils.hasLength(mavenRoot)) {
return new File(mavenRoot);
}
return new File(System.getProperty("user.home"), ".m2");
}
@Data
static class Params {
/**
* jar包在maven仓库中的groupId
*/
private String groupId;
/**
* jar包在maven仓库中的artifactId
*/
private String artifactId;
/**
* jar包在maven仓库中的version
*/
private String version;
/**
* 远程maven仓库的URL地址
*/
private String repository = "https://repo.maven.apache.org/maven2/";
/**
* 下载的jar包存放的目标地址,默认为 ${user.home}/.m2/repository
*/
private String target = getM2RepoDirectory().getAbsolutePath();
/**
* 登录远程maven仓库的用户名,若远程仓库不需要权限,设为null,默认为null
*/
private String username = null;
/**
* 登录远程maven仓库的密码,若远程仓库不需要权限,设为null,默认为null
*/
private String password = null;
}
}
主要使用了aether框架,spring-boot-cli 也是在此基础上封装的。更多用法可以参考官网。
参考
Spring Boot快速开发利器:Spring Boot CLI
Spring Cloud CLI-官方
利用aether api实现从指定maven仓库下载jar包
eclipse aether入门