当使用打包时,会下载org-springframework-boot-loader的jar,并且不会放在lib存放的第三方jar包文件中,该jar包中有个JarLauncher.class文件中设置了jar包运行时的入口和打包后文件的结构(定义了BOOT-INF,META-INF中放什么数据)

使用java -jar 启动项目时,因为META-INF中文件记载了启动的顺序

Manifest-Version: 1.0   #版本
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Start-Class: com.zyy.gradletest1.GradleTest1Application #项目的启动器
Spring-Boot-Classes: BOOT-INF/classes/ #记录编译后文件存放地址
Spring-Boot-Lib: BOOT-INF/lib/ #记录第三方jar包存放地址
Spring-Boot-Version: 2.3.0.RELEASE #SpringBoot版本
Main-Class: org.springframework.boot.loader.JarLauncher #标记了程序的入口,程序的入口定义类加载器去加载项目的启动器

所以程序会直接进入JarLauncher.class中执行main方法

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);
}

@Override
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);
}

}

JarLancher继承了ExecutableArchiveLauncher,ExecutableArchiveLauncher抽象类是归档启动器的父类,它有两个子类

Lancher:是整个归档的基类(抽象类)

|

ExecutableArchiveLauncher

| |

JarLancher WarLancher

顾名思义jarLancher就是打包成jar的归档启动类,WarLancher是War打包方式的归档启动类。

当执行JarLancher的main方法时,会调用Lancher基类的launch方法,该方法注释为:启动应用程序。 此方法是初始入口点应该由一个子类被称为public static void main(String[] args)方法。

使用SpringBoot自定义类加载器(LaunchedURLClassLoader)来加载打包好的Jar文件中BOOT-INF里面的类和lib

ClassLoader classLoader = createClassLoader(getClassPathArchives());
getClassPathArchives()

方法:返回嵌套Archive S表示匹配指定过滤器条目。

意思就是:获得需要自定义类加载器需要加载的类的路径并返回(归档文件)

public abstract class ExecutableArchiveLauncher extends Launcher {
@Override
protected List<Archive> getClassPathArchives() throws Exception {
List<Archive> archives = new ArrayList<>(
/*this::isNestedArchive:确定指定JarEntry是应添加到类路径嵌套项。 该方法被调用一次为每个条目。
说人话就是查看当前需要加载的路径是否符合,如果符合返回true,这个方法调用的就是JarLancher类 中的isNestedArchive方法*/
this.archive.getNestedArchives(this::isNestedArchive));
postProcessClassPathArchives(archives);
//返回Archive类型的集合,Archive是可以由{@link Launcher}启动的档案类,里面记录的需要加载类的路径
return archives;
}
}

补充知识:

正常的jar包加载的方式就是直接找到classpath下的顶层路径加载(例如:org.springframework)

传统的打包方式是将第三方包和自己写的代码打包在一块,这就有可能出现,包名冲突之类的。为了解决这种问题,Spring的打包方式就是把第三方包和自己的代码分开。但是此时就出现了一个问题,不能正常找到顶层的加载文件,那么spring就出现了org-springframework-boot-loader包,spring默认规定该包在打包时将该包放到classpath下(顶层路径),首先加载该类中对应的Lancher类,然后通过该类创建的自定义类加载器去加载项目中其他的类。

准备去加载项目的主运行程序

launch(args, getMainClass(), classLoader);

getMainClass():获得ExecutableArchiveLauncher.Archive类中的清单,Archive在创建自定义类加载器时,被构造方法初始化,初始化的路径就是classpath路径下的META-INF文件中的信息然后读取对应的主程序入口的路径(也就是META-INF文件中的信息)key为Start-Class的值(就是项目主程序的启动器)

public abstract class ExecutableArchiveLauncher extends Launcher {

private final Archive archive;

public ExecutableArchiveLauncher() {
try {
this.archive = createArchive();
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
@Override
protected String getMainClass() throws Exception {
Manifest manifest = this.archive.getManifest();
String mainClass = null;
if (manifest != null) {
mainClass = manifest.getMainAttributes().getValue("Start-Class");
}
if (mainClass == null) {
throw new IllegalStateException(
"No 'Start-Class' manifest entry specified in " + this);
}
return mainClass;
}
}

当所有参数准备完毕之后,进入launch方法中

public abstract class Launcher {
/**
* Launch the application given the archive file and a fully configured classloader.
* @param args the incoming arguments
* @param mainClass the main class to run
* @param classLoader the classloader
* @throws Exception if the launch fails
*/
protected void launch(String[] args, String mainClass, ClassLoader classLoader)
throws Exception {
//将当前线程的上下文设置为自定义类加载器
Thread.currentThread().setContextClassLoader(classLoader);
createMainMethodRunner(mainClass, args, classLoader).run();
}
}

创建一个运行Main的县城,将运行产参数和类加载器传入进去

createMainMethodRunner(mainClass, args, classLoader)

/**
* Create the {@code MainMethodRunner} used to launch the application.
* @param mainClass the main class main方法运行的路径
* @param args the incoming arguments 方法的参数
* @param classLoader the classloader 自定义类加载器
* @return the main method runner 返回线程
*/
protected MainMethodRunner createMainMethodRunner(String mainClass, String[] args,
ClassLoader classLoader) {
return new MainMethodRunner(mainClass, args);
}

当线程创建好之后!!!重点来了!!!!运行run方法

createMainMethodRunner(mainClass, args, classLoader).run();

public void run() throws Exception {
//获得线程上下文取出类加载器,然后获得要运行的main方法的路径
Class<?> mainClass = Thread.currentThread().getContextClassLoader()
.loadClass(this.mainClassName);
//通过反射定位到main方法的位置
Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
//调用对应的main方法(此时调用的就是SpringBoot项目的启动器)
mainMethod.invoke(null, new Object[] { this.args });
}

注意:mainMethod.invoke(null, new Object[] { this.args });

为什么invoke第一个参数是空,因为main方法是static的,static不归存与类中,它只是把类当做寄存的场所而已,原因是所有的方法必须存在与类中

什么是jdwep?

JDWP 是 Java Debug Wire Protocol 的缩写,它定义了调试器(debugger)和被调试的 Java 虚拟机(target vm)之间的通信协议,可以项目在线运行时,远程debug查看运行的流程,如果项目是部署在tomcat中运行,需要在tomcat中配置相关的启动,才能使用远程debug

启动jar包时,使用命令

然后在idea中配置

注意:这种调试需要在配置之前在idea中有相同的代码,然后打上断点

java.lang.annotation 提供了四种元注解,专门注解其他的注解(在自定义注解的时候,需要使用到元注解): @Documented – 注解是否将包含在JavaDoc中 @Retention – 什么时候使用该注解 ​​@Target​​ – 注解用于什么地方 @Inherited – 是否允许子类继承该注解

1.)@Retention – 定义该注解的生命周期 RetentionPolicy.SOURCE : 在编译阶段丢弃。这些注解在编译结束之后就不再有任何意义,所以它们不会写入字节码。@Override, @SuppressWarnings都属于这类注解。 RetentionPolicy.CLASS : 在类加载的时候丢弃。在字节码文件的处理中有用。注解默认使用这种方式 RetentionPolicy.RUNTIME : 始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解的信息。我们自定义的注解通常使用这种方式。

2.)Target – 表示该注解用于什么地方。默认值为任何元素,表示该注解用于什么地方。可用的ElementType 参数包括 ElementType.CONSTRUCTOR: 用于描述构造器 ElementType.FIELD: 成员变量、对象、属性(包括enum实例) ElementType.LOCAL_VARIABLE: 用于描述局部变量 ElementType.METHOD: 用于描述方法 ElementType.PACKAGE: 用于描述包 ElementType.PARAMETER: 用于描述参数 ElementType.TYPE: 用于描述类、接口(包括注解类型) 或enum声明

3.)@Documented – 一个简单的Annotations 标记注解,表示是否将注解信息添加在java 文档中。

4.)@Inherited – 定义该注释和子类的关系 @Inherited 元注解是一个标记注解,@Inherited 阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited 修饰的annotation 类型被用于一个class,则这个annotation 将被用于该class 的子类。

修饰一个类为配置类,将Bean注入到Spring容器中,在运行期间生成bean的定义

@Configuration
public class AppConfig {

@Bean
public MyBean myBean() {
// instantiate, configure and return bean ...
}
}

@Configuration类通常使用AnnotationConfigApplicationContext或者支持web的AnnotationConfigWebApplicationContext的引导,来使用@Bean

//在test类中可以使用,或者直接使用@Autowired自动注入
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(AppConfig.class);
ctx.refresh();
MyBean myBean = ctx.getBean(MyBean.class);
// use myBean ...

PropertySource @Configuration类可以使用@PropertySource注释将属性源贡献给环境对象:

@Configuration
@PropertySource("classpath:/com/acme/app.properties")
public class AppConfig {

@Inject Environment env;

@Bean
public MyBean myBean() {
return new MyBean(env.getProperty("bean.name"));
}
}

@Configuration类不仅可以使用组件扫描引导,还可以自己使用@ComponentScan注释配置组件扫描:

@Configuration @ComponentScan("com.acme.app.services") public class AppConfig { // various @Bean definitions ... } @Configuration类可以使用@Import注释组合,因为@Configuration对象在容器中被管理为Spring bean,所以导入的配置可能会被注入——例如,通过构造函数注入:

@Configuration

public class DatabaseConfig {

   @Bean
public DataSource dataSource() {
// instantiate, configure and return DataSource
}

}

@Configuration @Import(DatabaseConfig.class) public class AppConfig {

   private final DatabaseConfig dataConfig;

public AppConfig(DatabaseConfig dataConfig) {
this.dataConfig = dataConfig;
}

@Bean
public MyBean myBean() {
// reference the dataSource() bean method
return new MyBean(dataConfig.dataSource());
}

} 组合多个@Configuration配置组件

@RunWith(SpringRunner.class) @ContextConfiguration(classes = {AppConfig.class, DatabaseConfig.class}) public class MyTests {

   @Autowired MyBean myBean;

@Autowired DataSource dataSource;

@Test
public void test() {
// assertions against myBean ...
}

} 修饰一个类,表明一个注解的类是“部件”。@Configuration就是基于该注解。

自动注入,按照类型来匹配。

分别开发环境和生产环境的配置

application.properties 配置文件

spring.profiles.active=development 测试环境

@Profile("development") @Configuration public class EmbeddedDatabaseConfig { @Bean public DataSource dataSource() { // instantiate, configure and return embedded DataSource } } 生产环境

@Profile("production") @Configuration public class ProductionDatabaseConfig { @Bean public DataSource dataSource() { // instantiate, configure and return production DataSource } } 第二种方法

@Configuration public class ProfileDatabaseConfig { //测试环境 @Bean("dataSource") @Profile("development") public DataSource embeddedDatabase() { ... } //生产环境 @Bean("dataSource") @Profile("production") public DataSource productionDatabase() { ... } } 导入Spring的配置文件,让配置文件里面的内容生效;Spring Boot里面没有Spring的配置文件,我们自己编写的配置文件

例:@ImportResource(locations = {"classpath:beans.xml"})
但是SpringBoott推荐给容器中添加组件的方式;推荐使用全注解的方式

启用Spring应用程序上下文的自动配置(开启自动配置spring),当你不需要加载某个自定义类时可以在yml文件中:spring.autoconfigure.exclude: XXXX 来排除某个类。

配置使用组件扫描指令与@ Configuration类。 提供与Spring XML的支持并行context:component-scan元件。它包含了annotation-config这个属性。

@SpringBootApplication public class GradleTest1Application {

public static void main(String[] args) {
SpringApplication.run(GradleTest1Application.class, args);
}

} public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) { return run(new Class<?>[] { primarySource }, args); } public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { return new SpringApplication(primarySources).run(args); } public SpringApplication(Class<?>... primarySources) { this(null, primarySources); } 从这里开始,有SpringApplication构造方法中嵌套的源码

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { this.resourceLoader = resourceLoader; //资源加载器,因为上一次调用赋值为null,所以之这里时null Assert.notNull(primarySources, "PrimarySources must not be null"); //断言这里是否传入的SpringBoot的启动器是否为空 this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); //将SpringBoot启动器放入一个Set<Class<?>>中 /*WebApplicationType:Web应用程序可能类型的枚举,里面有三个枚举参数(查看Web是那种类型),deduceFromClasspath()方法中使用CLassUtils的class工具查看对应 的classpath路径下是否有对应的配置,如果有返回对应的Web类型 注意:源码在本代码块后第一个代码块中 */ this.webApplicationType = WebApplicationType.deduceFromClasspath(); /加载默认配置类(ApplicationContextInitializer是初始化所有自动配置类的类),扫描所有自动配置jar包中“META-INF/spring.factories”的配置类jar包( 主要有org.springframework.boot.autoconfigure、org.springframework.boot.context、org.springframework.boot.beans) 找到实现ApplicationContextInitializer接口的类,并实例化他们 提示:源码在本代码块后第二个代码块中 / setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); //和上一行代码操作一致,只不过是在三个jar包中找到实现ApplicationListener.class类并实例化它们 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); //找到主要的应用程序类 this.mainApplicationClass = deduceMainApplicationClass(); / deduceMainApplicationClass();源码说明: private Class<?> deduceMainApplicationClass() { try { //直接new出一个运行异常,然后获得堆信息 StackTraceElement[] stackTrace = new RuntimeException().getStackTrace(); //在堆信息中找到main方法 for (StackTraceElement stackTraceElement : stackTrace) { if ("main".equals(stackTraceElement.getMethodName())) { //加载main方法,并返回class对象 return Class.forName(stackTraceElement.getClassName()); } } } catch (ClassNotFoundException ex) { // Swallow and continue } return null; } */ } this.webApplicationType = WebApplicationType.deduceFromClasspath();源码:

WebApplicationType:Web应用程序可能类型的枚举,里面有三个枚举参数(查看Web是那种类型),deduceFromClasspath()方法中使用CLassUtils的class工具查看对应 的classpath路径下是否有对应的配置,如果有返回对应的Web类型

/**

  • An enumeration of possible types of web application.

  • @author Andy Wilkinson
  • @author Brian Clozel
  • @since 2.0.0 */ public enum WebApplicationType {
    /**
  • The application should not run as a web application and should not start an
  • embedded web server. 应用程序不应作为web应用程序运行,也不应该启动嵌入式web服务器。 */ NONE,

/**

  • The application should run as a servlet-based web application and should start an
  • embedded servlet web server. 应用程序应作为基于servlet的web应用程序运行,并应启动嵌入式servlet web服务器。 */ SERVLET,

/**

  • The application should run as a reactive web application and should start an
  • embedded reactive web server. 应用程序应该作为一个反应性web应用程序运行,并启动一个嵌入式反应性web服务器。当前反应性web服务器在Spring5引进,它可以支持servlet容器,也可以支持servlet职位的容器 */ REACTIVE;

static WebApplicationType deduceFromClasspath() { //判断类路径是否存在,存在:true 不存在:false if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null) && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) { return WebApplicationType.REACTIVE; } for (String className : SERVLET_INDICATOR_CLASSES) { if (!ClassUtils.isPresent(className, null)) { return WebApplicationType.NONE; } } return WebApplicationType.SERVLET; } } setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); 源码:

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) { return getSpringFactoriesInstances(type, new Class<?>[] {}); }

/** 重点!!!!!!!!! 这个类是返回在org.springframework.boot.autoconfigure、org.springframework.boot.context、org.springframework.boot.beans这三个包中继承type类型的所有类, 这个类在SpringApplication中经常使用!!!!!!! */ private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) { //获得类加载器 ClassLoader classLoader = getClassLoader();

//获得所有的自动配置类的路径信息     

Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader)); /* SpringFactoriesLoader.loadFactoryNames(type, classLoader)说明和源码 | | | 使用set集合为了确保惟一以防止重复,因为这里存放的是所有自动配置类(AutoConfiguration) loadFactoryNames(type, classLoader));
参数说明: (type = ApplicationContextInitializer.class(记录被加载类的配置信息,并初始化) classLoader = 系统加载器)

   方法调用了loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader)的同名方法,
loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader)源码:

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
//loadSpringFactories(classLoader):找到所有“META-INF/spring.factories”配置文件中的类
//getOrDefault(factoryTypeName, Collections.emptyList()); 过滤所有的类找出实现factoryTypeName的类并放入集合中 (factoryTypeName=ApplicationContextInitializer.class
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
在return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());中获得所有的自动配置类的路径信息
*/


//在获得所有需要初始化的自动配置类后,初始化他们

List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); //对类排序 AnnotationAwareOrderComparator.sort(instances); //返回所有排序后的自动配置类集合 return instances; } 到这里结束

/**

  • Run the Spring application, creating and refreshing a new 运行Spring应用程序,创建并刷新一个新应用程序
  • {@link ApplicationContext}.
  • @param args the application arguments (usually passed from a Java main method)
  • @return a running {@link ApplicationContext}/ public ConfigurableApplicationContext run(String... args) { //计时器(Spring创始人2001年写的) StopWatch stopWatch = new StopWatch(); //启动计时器 stopWatch.start(); //ApplicationContext子类,封装了配置和生命周期,防止ApplicationContext客户端代码看到它们 ConfigurableApplicationContext context = null; //存放SpringApplication启动运行时的异常 Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); //以服务器模式运行,在系统可能缺少显示设备、键盘或鼠标这些外设的情况下可以使用该模式,激活该模式:System.setProperty("java.awt.headless", "true"); configureHeadlessProperty(); /SpringApplication运行时的监视器,每运行一次都会创建一个SpringApplicationRunListener的子类放入SpringApplicationRunListeners类中的List<SpringApplicationRunListener> 的集合中/ SpringApplicationRunListeners listeners = getRunListeners(args); /* | | | 源码: private SpringApplicationRunListeners getRunListeners(String[] args) { Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class }; return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args)); //找到继承SpringApplicationRunListener类的所有类 } */
    //开始启动所有继承SpringApplicationRunListener的监视器 listeners.starting(); try { //加载默认参数,只是把args赋值给DefaultApplicationArguments类中的成员变量 ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); //配置环境,传入所有监听器和应用程序的参数 ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); //配置忽略一些Bean信息 configureIgnoreBeanInfo(environment); //向控制台打印Banner(就是那个大的Spring字体,我们可以在classpath目录下创建一个banner.txt文件,把想要打印的东西放入进去就可以在程序启动时打印出来) Banner printedBanner = printBanner(environment); //创建应用上下文 context = createApplicationContext(); //获取Spring工厂的实例 exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); //准备上下文,并输出某些日志 prepareContext(context, environment, listeners, applicationArguments, printedBanner); //刷新上下文,并输出某个日志 refreshContext(context); //刷新上下问后置处理 afterRefresh(context, applicationArguments); //计时停止了 stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch); } listeners.started(context); callRunners(context, applicationArguments); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, listeners); throw new IllegalStateException(ex); }
    try { listeners.running(context); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, null); throw new IllegalStateException(ex); } return context; }