springboot 启动流程(二) Main函数启动

上一章介绍了通过外部web容器启动springboot项目,本章介绍通过Springboot的Main函数启动。

一、springboot Main 方式启动

@EnableSwagger2
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

二、Springboot 的启动入口

很多人都自然想到Main函数时程序启动入口,解开springboot的jar包,可以看到里面的文件目录

可以看到多了loader的目录结构,主要原因是springboot项目以来的方式是jar in jar,与普通的在同目录下的lib不一样,当加载lib包时,springboot需要自定义加载方式,将依赖加载到classpath下。

1、函数的入口

 

Mainclass是JarLauncher,查看具体的内容有

 

package org.springframework.boot.loader;

import org.springframework.boot.loader.archive.Archive;
import org.springframework.boot.loader.archive.Archive.Entry;

public class JarLauncher extends ExecutableArchiveLauncher
{
  static final String BOOT_INF_CLASSES = "BOOT-INF/classes/";
  static final String BOOT_INF_LIB = "BOOT-INF/lib/";
  
  public JarLauncher() {}
  
  protected JarLauncher(Archive archive){
    super(archive);
  }
  
  protected boolean isNestedArchive(Archive.Entry entry){
    if (entry.isDirectory()) {
      return entry.getName().equals("BOOT-INF/classes/");
    }
    return entry.getName().startsWith("BOOT-INF/lib/");
  }
  
  public static void main(String[] args) throws Exception {
    new JarLauncher().launch(args);
  }
}

接着看Launcher的实现

public abstract class Launcher{
   // 加载函数
  protected void launch(String[] args) throws Exception{
    JarFile.registerUrlProtocolHandler();
    ClassLoader classLoader = createClassLoader(getClassPathArchives());
    launch(args, getMainClass(), classLoader);
  }
  
  protected ClassLoader createClassLoader(List<Archive> archives)throws Exception {
    List<URL> urls = new ArrayList(archives.size());
    for (Archive archive : archives) {
      urls.add(archive.getUrl());
    }
    return createClassLoader((URL[])urls.toArray(new URL[0]));
  }
  // 自定义类加载器
  protected ClassLoader createClassLoader(URL[] urls) throws Exception{
    return new LaunchedURLClassLoader(urls, getClass().getClassLoader());
  }
  
  // 加载函数
  protected void launch(String[] args, String mainClass, ClassLoader classLoader) throws Exception{
    Thread.currentThread().setContextClassLoader(classLoader);
    // 真正的开始执行方法
    createMainMethodRunner(mainClass, args, classLoader).run();
  }
  
  protected MainMethodRunner createMainMethodRunner(String mainClass, String[] args, ClassLoader classLoader){
    return new MainMethodRunner(mainClass, args);
  }
  
  protected abstract String getMainClass()throws Exception;
  
  protected abstract List<Archive> getClassPathArchives() throws Exception;
  
  protected final Archive createArchive()throws Exception{
    ProtectionDomain protectionDomain = getClass().getProtectionDomain();
    CodeSource codeSource = protectionDomain.getCodeSource();
    URI location = codeSource != null ? codeSource.getLocation().toURI() : null;
    String path = location != null ? location.getSchemeSpecificPart() : null;
    if (path == null) {
      throw new IllegalStateException("Unable to determine code source archive");
    }
    File root = new File(path);
    if (!root.exists()) {
      throw new IllegalStateException("Unable to determine code source archive from " + root);
    }
    return root.isDirectory() ? new ExplodedArchive(root) : new JarFileArchive(root);
  }
}
package org.springframework.boot.loader;

import java.lang.reflect.Method;

public class MainMethodRunner{
  private final String mainClassName;
  private final String[] args;
  
  public MainMethodRunner(String mainClass, String[] args){
    this.mainClassName = mainClass;
    this.args = (args != null ? (String[])args.clone() : null);
  }
  
  // 通过反射获取main方法,然后调用main方法执行
  public void run() throws Exception{
    Class<?> mainClass = Thread.currentThread().getContextClassLoader().loadClass(this.mainClassName);
    Method mainMethod = mainClass.getDeclaredMethod("main", new Class[] { String[].class });
    mainMethod.invoke(null, new Object[] { this.args });
  }
}

所以,springboot的启动函数入口是Main函数,但不是程序中定义的Main函数,而是JarLauncher中的launch,然后反射出app 的Main函数执行。

三、springboot的自动加载

1、@SpringBootApplication注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
		@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {}

包含了ComponentScan,EnableAutoConfiguration和SpringBootConfiguration,@ComponentScan是包扫描,对特定注解的类提交给IOC管理。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
    // 本质上是Configuration,本身是Bean,也提供对其他Bean的创建
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}

重点关注AutoConfigurationImportSelector类,里面实现了selectImports方法

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, 
		ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
				.loadMetadata(this.beanClassLoader);
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(
				autoConfigurationMetadata, annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}
	
	protected AutoConfigurationEntry getAutoConfigurationEntry(
			AutoConfigurationMetadata autoConfigurationMetadata,
			AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
        // 获取候选Bean的名字
		List<String> configurations = getCandidateConfigurations(annotationMetadata,
				attributes);
        // 去除重复的Bean名字
		configurations = removeDuplicates(configurations);
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		checkExcludedClasses(configurations, exclusions);
		configurations.removeAll(exclusions);
		configurations = filter(configurations, autoConfigurationMetadata);
        // 发送自动配置依赖的listener
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}
	
	// 这个方法会扫描classpath下META-INF/spring.factories中的类,改文件很多spring包中都有
	protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
			AnnotationAttributes attributes) {
		// loadFactoryNames 方法会扫描META-INF/spring.factories,找到实现类,以URL方式读取配置文件
		List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
				getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
		Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
						+ "are using a custom packaging, make sure that file is correct.");
		return configurations;
	}
}

 

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值