SpringBoot深入理解

SpringBoot深入理解

项目打包SpringBoot启动过程

当使用打包时,会下载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)方法。

image-20200530094047755


使用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不归存与类中,它只是把类当做寄存的场所而已,原因是所有的方法必须存在与类中

jar包启动时,使用-agentlib:jdwp远程debug

什么是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 的子类。

@Configuration官方文档

修饰一个类为配置类,将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"));
       }
   }

@ComponentScan

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

@Configuration
   @ComponentScan("com.acme.app.services")
   public class AppConfig {
       // various @Bean definitions ...
   }

@Import

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

@ContextConfiguration

组合多个@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 ...
       }
   }

@Component

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

@Autowired

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

@Profile

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

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() { ... }
  }

@ImportResource

导入Spring的配置文件,让配置文件里面的内容生效;Spring Boot里面没有Spring的配置文件,我们自己编写的配置文件

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

@EnableAutoConfiguration

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

@ComponentScan

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

SpringBoot启动过程

从main中执行SpringApplication.run(GradleTest1Application.class, args);

@SpringBootApplication
public class GradleTest1Application {

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

}

进入到SpirngApplication类中,然后在run方法中间接调用new SpringApplication的构造方法

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

SpringApplication(primarySources):源码开始


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(args):源码开始


/**
 * 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;
}
原文地址:https://www.cnblogs.com/baroque/p/12990138.html