SpringBoot为啥不用配置启动类的实现
前言
在学习SparkJava、Vert.x等轻量级Web框架的时候,都遇到过打包问题,这两个框架打包的时候都需要添加额外的Maven配置,并指定启动类才能得到可执行的JAR包;
而springboot项目,似乎都不需要额外的配置,直接package就可以得到可执行的JAR包,这是怎么回事呢?
Vert.x要怎么配?
我们先来看看,Vert.x打包做哪些配置
1)引入maven-shade-plugin插件
2)在插件中指定在package完成时触发shade操作
3)指定启动类
maven-shade-plugin 3.1.0 package shade com.test.Starter
效果:
执行package操作后,将得到两个jar包
①origin-[yourproject].jar(Maven默认打包操作得到的jar包,该包仅包含此项目的类)
②[yourproject].jar(带有依赖包,且配置有启动类的可执行JAR包)
SpringBoot又是怎么做的
不用添加插件?=>初始化时默认就有
SpringBoot初始化得到的项目中,默认带有spring-boot-maven-plugin的Maven配置
SpringBoot打包的基本原理与前面的Vertx配置相同,都是使用maven-shade-plugin(spring-boot-maven-plugin底层使用maven-shade-plugin),在package完成之后,加入依赖的包,并指定启动类。
SpringBoot是在package时,触发repackage,将原打包结果重命名为[yourproject].jar.original,并得到带有依赖包和配置好启动类的[yourproject].jar
不用指定启动类?=>默认扫描得到启动类
spring-boot-maven-plugin会扫描项目,并以带有@SpringBootApplication注解和main方法的类作为启动类。
默认情况下,SpringBoot项目默认启动类写死JarLauncher,该类的main方法再调用扫描得到的实际启动类(XXXApplication)的main方法
源码查看
我们从mavenrepository下载一个spring-boot-maven-plugin的源码进行查看,查看RepackageMojo.java。
从@Mojo注解中,我们可以知道,该Mojo绑定在PACKAGE(注解中的defaultPhase=LifecyclePhase.PACKAGE),即在package完成后触发
/**
*RepackagesexistingJARandWARarchivessothattheycanbeexecutedfromthecommand
*lineusing{@literaljava-jar}.Withlayout=NONEcanalsobeusedsimply
*topackageaJARwithnesteddependencies(andnomainclass,sonotexecutable).
*
*@authorPhillipWebb
*@authorDaveSyer
*@authorStephaneNicoll
*@authorBjörnLindström
*@since1.0.0
*/
@Mojo(name="repackage",defaultPhase=LifecyclePhase.PACKAGE,requiresProject=true,threadSafe=true,
requiresDependencyResolution=ResolutionScope.COMPILE_PLUS_RUNTIME,
requiresDependencyCollection=ResolutionScope.COMPILE_PLUS_RUNTIME)
publicclassRepackageMojoextendsAbstractDependencyFilterMojo{
//...
}
我们可以看到,该Mojo中可以指定一个mainclass作为启动类,但是如果没有指定的时候,它是如何处理的呢?
/** *Thenameofthemainclass.Ifnotspecifiedthefirstcompiledclassfoundthat *containsa'main'methodwillbeused. *@since1.0.0 */ @Parameter privateStringmainClass;
我们跟踪这个mainClass,发现在此类中,没有对这个mainClass进行赋值的操作,只用来构造一个Repackager(也就是说在该Maven插件没有配置mainClass的时候,传给Repackager的就是一个null),我们观察到这个Repackager就是该Mojo执行
@Override
publicvoidexecute()throwsMojoExecutionException,MojoFailureException{
if(this.project.getPackaging().equals("pom")){
getLog().debug("repackagegoalcouldnotbeappliedtopomproject.");
return;
}
if(this.skip){
getLog().debug("skippingrepackagingasperconfiguration.");
return;
}
repackage();
}
privatevoidrepackage()throwsMojoExecutionException{
Artifactsource=getSourceArtifact();
Filetarget=getTargetFile();
Repackagerrepackager=getRepackager(source.getFile());
Setartifacts=filterDependencies(this.project.getArtifacts(),getFilters(getAdditionalFilters()));
Librarieslibraries=newArtifactsLibraries(artifacts,this.requiresUnpack,getLog());
try{
LaunchScriptlaunchScript=getLaunchScript();
repackager.repackage(target,libraries,launchScript);//执行repackage操作
}
catch(IOExceptionex){
thrownewMojoExecutionException(ex.getMessage(),ex);
}
updateArtifact(source,target,repackager.getBackupFile());
}
privateRepackagergetRepackager(Filesource){
Repackagerrepackager=newRepackager(source,this.layoutFactory);
repackager.addMainClassTimeoutWarningListener(newLoggingMainClassTimeoutWarningListener());
repackager.setMainClass(this.mainClass);//将插件配置的mainClass注入,默认就是null
if(this.layout!=null){
getLog().info("Layout:"+this.layout);
repackager.setLayout(this.layout.layout());
}
returnrepackager;
}
由上可知,mainClass的最终确定,应该在Repackager的中完成,我继续跟踪该代码(Repackager来自spring-boot-maven-plugin下引入的spring-boot-loader-tools),打开Repackager的代码。我们观察到Repackager的setMainClass并没有做额外的操作,只是将传入的参数set进来,但是从注释中可以得知,其在使用时如果为空,则会搜索合适的类作为MainClass
/**
*Utilityclassthatcanbeusedtorepackageanarchivesothatitcanbeexecutedusing
*'{@literaljava-jar}'.
*
*@authorPhillipWebb
*@authorAndyWilkinson
*@authorStephaneNicoll
*@since1.0.0
*/
publicclassRepackager{
//...
/**
*Setsthemainclassthatshouldberun.Ifnotspecifiedthevaluefromthe
*MANIFESTwillbeused,orifnomanifestentryisfoundthearchivewillbe
*searchedforasuitableclass.
*@parammainClassthemainclassname
*/
publicvoidsetMainClass(StringmainClass){
this.mainClass=mainClass;
}
//...
}
我们就从上面调用repackage方法开始看
/**
*Repackagetothegivendestinationsothatitcanbelaunchedusing'
*{@literaljava-jar}'.
*@paramdestinationthedestinationfile(maybethesameasthesource)
*@paramlibrariesthelibrariesrequiredtorunthearchive
*@paramlaunchScriptanoptionallaunchscriptprependedtothefrontofthejar
*@throwsIOExceptionifthefilecannotberepackaged
*@since1.3.0
*/
publicvoidrepackage(Filedestination,Librarieslibraries,LaunchScriptlaunchScript)throwsIOException{
if(destination==null||destination.isDirectory()){
thrownewIllegalArgumentException("Invaliddestination");
}
if(libraries==null){
thrownewIllegalArgumentException("Librariesmustnotbenull");
}
if(this.layout==null){
this.layout=getLayoutFactory().getLayout(this.source);
}
destination=destination.getAbsoluteFile();
FileworkingSource=this.source;
if(alreadyRepackaged()&&this.source.equals(destination)){
return;
}
if(this.source.equals(destination)){
workingSource=getBackupFile();
workingSource.delete();
renameFile(this.source,workingSource);
}
destination.delete();
try{
try(JarFilejarFileSource=newJarFile(workingSource)){
repackage(jarFileSource,destination,libraries,launchScript);//这里往下查看
}
}
finally{
if(!this.backupSource&&!this.source.equals(workingSource)){
deleteFile(workingSource);
}
}
}
privatevoidrepackage(JarFilesourceJar,Filedestination,Librarieslibraries,LaunchScriptlaunchScript)
throwsIOException{
WritableLibrarieswriteableLibraries=newWritableLibraries(libraries);
try(JarWriterwriter=newJarWriter(destination,launchScript)){
writer.writeManifest(buildManifest(sourceJar));//注意这里有一个buildManifest
writeLoaderClasses(writer);
if(this.layoutinstanceofRepackagingLayout){
writer.writeEntries(sourceJar,
newRenamingEntryTransformer(((RepackagingLayout)this.layout).getRepackagedClassesLocation()),
writeableLibraries);
}
else{
writer.writeEntries(sourceJar,writeableLibraries);
}
writeableLibraries.write(writer);
}
}
privateManifestbuildManifest(JarFilesource)throwsIOException{
Manifestmanifest=source.getManifest();
if(manifest==null){
manifest=newManifest();
manifest.getMainAttributes().putValue("Manifest-Version","1.0");
}
manifest=newManifest(manifest);
StringstartClass=this.mainClass;//mainClass
if(startClass==null){
startClass=manifest.getMainAttributes().getValue(MAIN_CLASS_ATTRIBUTE);//先尝试从mainfest中拿,这个暂时不清楚数据来源
}
if(startClass==null){
startClass=findMainMethodWithTimeoutWarning(source);//这里触发搜索mainClass
}
StringlauncherClassName=this.layout.getLauncherClassName();
if(launcherClassName!=null){
manifest.getMainAttributes().putValue(MAIN_CLASS_ATTRIBUTE,launcherClassName);
if(startClass==null){
thrownewIllegalStateException("Unabletofindmainclass");
}
manifest.getMainAttributes().putValue(START_CLASS_ATTRIBUTE,startClass);
}
elseif(startClass!=null){
manifest.getMainAttributes().putValue(MAIN_CLASS_ATTRIBUTE,startClass);
}
StringbootVersion=getClass().getPackage().getImplementationVersion();
manifest.getMainAttributes().putValue(BOOT_VERSION_ATTRIBUTE,bootVersion);
manifest.getMainAttributes().putValue(BOOT_CLASSES_ATTRIBUTE,(this.layoutinstanceofRepackagingLayout)
?((RepackagingLayout)this.layout).getRepackagedClassesLocation():this.layout.getClassesLocation());
Stringlib=this.layout.getLibraryDestination("",LibraryScope.COMPILE);
if(StringUtils.hasLength(lib)){
manifest.getMainAttributes().putValue(BOOT_LIB_ATTRIBUTE,lib);
}
returnmanifest;
}
privateStringfindMainMethodWithTimeoutWarning(JarFilesource)throwsIOException{
longstartTime=System.currentTimeMillis();
StringmainMethod=findMainMethod(source);//这里往下看
longduration=System.currentTimeMillis()-startTime;
if(duration>FIND_WARNING_TIMEOUT){
for(MainClassTimeoutWarningListenerlistener:this.mainClassTimeoutListeners){
listener.handleTimeoutWarning(duration,mainMethod);
}
}
returnmainMethod;
}
protectedStringfindMainMethod(JarFilesource)throwsIOException{
returnMainClassFinder.findSingleMainClass(source,this.layout.getClassesLocation(),
SPRING_BOOT_APPLICATION_CLASS_NAME);//在指定Jar文件中查找MainClass
}
privatestaticfinalStringSPRING_BOOT_APPLICATION_CLASS_NAME="org.springframework.boot.autoconfigure.SpringBootApplication";
/**
*Findasinglemainclassinagivenjarfile.Amainclassannotatedwithan
*annotationwiththegiven{@codeannotationName}willbepreferredoveramain
*classwithnosuchannotation.
*@paramjarFilethejarfiletosearch
*@paramclassesLocationthelocationwithinthejarcontainingclasses
*@paramannotationNamethenameoftheannotationthatmaybepresentonthemain
*class
*@returnthemainclassor{@codenull}
*@throwsIOExceptionifthejarfilecannotberead
*/
publicstaticStringfindSingleMainClass(JarFilejarFile,StringclassesLocation,StringannotationName)
throwsIOException{
SingleMainClassCallbackcallback=newSingleMainClassCallback(annotationName);
MainClassFinder.doWithMainClasses(jarFile,classesLocation,callback);
returncallback.getMainClassName();
}
从最后几步中,我们可以知道,查找的mainClass是一个带有@SpringBootApplication注解的类。不用说明,该类肯定是带有main方法,如果你想进一步确认,则可以继续查看MainClassFinder的代码(来自spring-boot-loader-tools)。
//...
privatestaticfinalTypeMAIN_METHOD_TYPE=Type.getMethodType(Type.VOID_TYPE,STRING_ARRAY_TYPE);
privatestaticfinalStringMAIN_METHOD_NAME="main";
privatestaticclassClassDescriptorextendsClassVisitor{
privatefinalSetannotationNames=newLinkedHashSet<>();
privatebooleanmainMethodFound;
ClassDescriptor(){
super(SpringAsmInfo.ASM_VERSION);
}
@Override
publicAnnotationVisitorvisitAnnotation(Stringdesc,booleanvisible){
this.annotationNames.add(Type.getType(desc).getClassName());
returnnull;
}
@Override
publicMethodVisitorvisitMethod(intaccess,Stringname,Stringdesc,Stringsignature,String[]exceptions){
//如果访问方式是publicstatic且方法名为main且返回值为void,则认定该类含有main方法
if(isAccess(access,Opcodes.ACC_PUBLIC,Opcodes.ACC_STATIC)&&MAIN_METHOD_NAME.equals(name)
&&MAIN_METHOD_TYPE.getDescriptor().equals(desc)){
this.mainMethodFound=true;
}
returnnull;
}
privatebooleanisAccess(intaccess,int...requiredOpsCodes){
for(intrequiredOpsCode:requiredOpsCodes){
if((access&requiredOpsCode)==0){
returnfalse;
}
}
returntrue;
}
booleanisMainMethodFound(){
returnthis.mainMethodFound;
}
SetgetAnnotationNames(){
returnthis.annotationNames;
}
}
//...
到此这篇关于SpringBoot为啥不用配置启动类的实现的文章就介绍到这了,更多相关SpringBoot启动类内容请搜索毛票票以前的文章或继续浏览下面的相关文章希望大家以后多多支持毛票票!
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。