Spring Boot 2.0之理解SpringApplication

  • Post author:
  • Post category:其他




SpringApplication的基本使用



SpringApplication 运行

SpringApplication.run(SpringApplicationBootstrap.class,args);



自定义 SpringApplication



通过 SpringApplication API 调整
SpringApplication springApplication = new SpringApplication(SpringApplicationBootstrap.class);
springApplication.setBannerMode(Banner.Mode.CONSOLE);
springApplication.setWebApplicationType(WebApplicationType.NONE);
springApplication.setAdditionalProfiles("prod");
springApplication.setHeadless(true);


通过 SpringApplicationBuilder API 调整

流式方法处理,更优雅

new SpringApplicationBuilder(SpringApplicationBootstrap.class)
.bannerMode(Banner.Mode.CONSOLE)
.web(WebApplicationType.NONE)
.profiles("prod")
.headless(true)
.run(args);



SpringApplication 准备阶段



配置 Spring Boot Bean 源

Java 配置 Class 或 XML 上下文配置文件集合,用于 Spring Boot

BeanDefinitionLoader

读取并且将配置源解析加载为Spring Bean 定义


Java 配置 Class


用于 Spring 注解驱动中 Java 配置类,大多数情况是 Spring 模式注解所标注的类,如

@Configuration

。在spring boot中是

@SpringBootApplication



@SpringBootApplication

—>

@SpringBootConfiguration

—>

@Configuration

/**
 * {@link SpringApplication}
 * Created by Yuk on 2019/3/9.
 */
@SpringBootApplication
public class SpringApplicationBootstrap {

    public static void main(String[] args) {

        SpringApplication.run(SpringApplicationBootstrap.class,args);
    }
}
@SpringBootApplication
public class SpringApplicationBootstrap {

    public static void main(String[] args) {

        //SpringApplication.run(SpringApplicationBootstrap.class,args);
        Set sources = new HashSet();
        sources.add(SpringApplicationBootstrap.class.getName());

        SpringApplication application = new SpringApplication();
        application.setSources(new HashSet<>(sources));
        application.run(args);
    }
}

这两种方式都可以正常启动,重点看

application.setSources(new HashSet<>(sources));

/**
	 * Set additional sources that will be used to create an ApplicationContext. A source
	 * can be: a class name, package name, or an XML resource location.
	 * <p>
	 * Sources set here will be used in addition to any primary sources set in the
	 * constructor.
	 * @param sources the application sources to set
	 * @see #SpringApplication(Class...)
	 * @see #getAllSources()
	 */
	public void setSources(Set<String> sources) {
		Assert.notNull(sources, "Sources must not be null");
		this.sources = new LinkedHashSet<>(sources);
	}

看英文注释可以知道


A source can be: a class name, package name, or an XML resource location.


这里Set集合的sources可以是className(但必须是加了

@Configuration

),可以是包名,也可以是spring的配置文件



推断 Web 应用类型

根据当前应用 ClassPath 中是否存在相关实现类来推断 Web 应用的类型,包括:


  • Web Reactive

    : WebApplicationType.REACTIVE

  • Web Servlet

    : WebApplicationType.SERVLET

  • 非 Web

    : WebApplicationType.NONE


参考方法:


spring boot 2.0.7版本

this.webApplicationType = WebApplicationType.deduceFromClasspath();
static WebApplicationType deduceFromClasspath() {
		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;
	}



推断引导类(Main Class)

这里可能会有个疑问,引导类不就是Main Class吗?

注意,这样写确实是的

@SpringBootApplication
public class SpringApplicationBootstrap {

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

但是这样写,引导类就不是当前运行main方法的class了

public class SpringApplicationBootstrap {

    public static void main(String[] args) {

        SpringApplication.run(ApplicationConfiguration.class,args);
    }

    @SpringBootApplication
    public static class ApplicationConfiguration{

    }
}


参考方法



org.springframework.boot.SpringApplication#deduceMainApplicationClass


抛出异常获取堆栈,然后判断堆栈的方法是否为main,从而获取类名,反射得到引导类

private Class<?> deduceMainApplicationClass() {
		try {
			StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
			for (StackTraceElement stackTraceElement : stackTrace) {
				if ("main".equals(stackTraceElement.getMethodName())) {
					return Class.forName(stackTraceElement.getClassName());
				}
			}
		}
		catch (ClassNotFoundException ex) {
			// Swallow and continue
		}
		return null;
	}

我们可以断点看一下

在这里插入图片描述



加载应用上下文初始器 ( ApplicationContextInitializer )

初始化时可以拿到上下文

ConfigurableApplicationContext

,做一些其他事情。

setInitializers((Collection) getSpringFactoriesInstances(
				ApplicationContextInitializer.class));
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
			Class<?>[] parameterTypes, Object... args) {
		ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
		// Use names and ensure unique to protect against duplicates
		Set<String> names = new LinkedHashSet<>(
				SpringFactoriesLoader.loadFactoryNames(type, classLoader));
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
				classLoader, args, names);
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}

跟踪这个方法,最后发现会去

META-INF/spring.factories

中查找。利用 Spring 工厂加载机制,实例化

ApplicationContextInitializer

的实现类,并排序对象集合。

可以看到spring boot默认有两个实现类实现了

ApplicationContextInitializer


在这里插入图片描述

并且,

AnnotationAwareOrderComparator.sort(instances);

对实现类进行排序,看截图可知。


ApplicationContextInitializer

的实现类定义排序有三种方式:

1.实现Ordered接口

2.注解Order

3.注解priority

在这里插入图片描述



自定义上下文初始器
  • 实现Ordered接口定义优先级
public class AfterApplicationContextInitiazer implements ApplicationContextInitializer,Ordered{
    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE; // 最低优先级
    }

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        System.out.println("After ApplicationContext.id="+applicationContext.getId());
    }
}
  • @Order注解定义优先级
@Order(Ordered.HIGHEST_PRECEDENCE)// 最高优先级
public class HelloWorldApplicationContextInitiazer implements ApplicationContextInitializer{

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        System.out.println("helloWorld ApplicationContext.id="+applicationContext.getId());
    }
}

在这里插入图片描述


spring.factories

# Initializers
org.springframework.context.ApplicationContextInitializer=\
com.imooc.diveinspringboot.context.AfterApplicationContextInitiazer,\
com.imooc.diveinspringboot.context.HelloWorldApplicationContextInitiazer


测试

控制台打印

helloWorld ApplicationContext.id=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@327b636c
After ApplicationContext.id=application



加载应用事件监听器( ApplicationListener )

利用 Spring 工厂加载机制,实例化

ApplicationListener

的实现类,并排序对象集合,与加载应用上下文初始器的机制一样。

setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));


自定义事件监听器

这里只是自定义监听器,监听的是spring默认的事件

在这里插入图片描述

public class AfterApplicationListener implements ApplicationListener<ContextRefreshedEvent>,Ordered {
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        System.out.println("After ApplicationListener :"+event.getApplicationContext().getId()
                +",timestamp:"+event.getTimestamp());
    }

    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;
    }
}
@Order(Ordered.HIGHEST_PRECEDENCE)
public class HelloWorldApplicationListener implements ApplicationListener<ContextRefreshedEvent> {

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        System.out.println("HelloWorld ApplicationListener :"+event.getApplicationContext().getId()
            +",timestamp:"+event.getTimestamp());
    }
}


spring.factories

org.springframework.context.ApplicationListener=\
com.imooc.diveinspringboot.listen.AfterApplicationListener,\
com.imooc.diveinspringboot.listen.HelloWorldApplicationListener


测试

控制台打印

HelloWorld ApplicationListener :application,timestamp:1552139922079
After ApplicationListener :application,timestamp:1552139922079

spring的事件监听特别好用,可以多个监听一个事件,是

观察者模式

的一种体现。这里只是自定义了监听器,其实还可以自定义事件,到时候在讲spring源码的时候具体会写一篇文章。



SpringApplication 运行阶段



加载 SpringApplication 运行监听器( SpringApplicationRunListeners )

利用 Spring 工厂加载机制,读取

SpringApplicationRunListener

对象集合,并且封装到组合类


SpringApplicationRunListeners




SpringApplication

最终的

run

方法中

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

其中

getSpringFactoriesInstances

方法,上面也讲到过,就是根据类型去

spring.factories

中获取相对应的类。这里可以看到获取的是实现了

SpringApplicationRunListener

接口的实现类



运行 SpringApplication 运行监听器( SpringApplicationRunListeners )


SpringApplicationRunListener

有7个方法,对应容器运行阶段的7种状态,可以自定义实现不同方法。比如容器开始时要做什么,启动时要做什么等等。。。

public interface SpringApplicationRunListener {

	void starting();
	
	void environmentPrepared(ConfigurableEnvironment environment);

	void contextPrepared(ConfigurableApplicationContext context);

	void contextLoaded(ConfigurableApplicationContext context);

	void started(ConfigurableApplicationContext context);

	void running(ConfigurableApplicationContext context);

	void failed(ConfigurableApplicationContext context, Throwable exception);

}
监听方法 阶段说明 Spring Boot 起始版本
starting() Spring 应用刚启动 1.0
environmentPrepared(ConfigurableEnvironment) ConfigurableEnvironment 准备妥当,允许将其调整 1.5
contextPrepared(ConfigurableApplicationContext) ConfigurableApplicationContext 准备妥当,允许将其调整 1.0
contextLoaded(ConfigurableApplicationContext context) ConfigurableApplicationContext 已装载,但仍未启动 1.0
started(ConfigurableApplicationContext context) ConfigurableApplicationContext 已启动,此时 Spring Bean 已初始化完成 2.0
running(ConfigurableApplicationContext) Spring 应用正在运行 2.0
failed(ConfigurableApplicationContext,Throwable) Spring 应用运行失败 1.0



监听 Spring Boot 事件 / Spring 事件

Spring Boot 通过

SpringApplicationRunListener

的实现类

EventPublishingRunListener

利用 Spring Framework 事件API ,广播 Spring Boot 事件。

在这里插入图片描述



listeners.starting();

时,发布了一个

ApplicationStartingEvent

事件,如果自定义监听器监听了该事件,则会在这里触发监听器。


EventPublishingRunListener.java

public void starting() {
		this.initialMulticaster.multicastEvent(
				new ApplicationStartingEvent(this.application, this.args));
	}


EventPublishingRunListener 监听方法与 Spring Boot 事件对应关系
监听方法 Spring Boot 事件 Spring Boot 起始版本
starting() ApplicationStartingEvent 1.5
environmentPrepared(ConfigurableEnvironment) ApplicationEnvironmentPreparedEvent 1.0
contextPrepared(ConfigurableApplicationContext)
contextLoaded(ConfigurableApplicationContext) ApplicationPreparedEvent 1.0
started(ConfigurableApplicationContext) ApplicationStartedEvent 2.0
running(ConfigurableApplicationContext) ApplicationReadyEvent 2.0
failed(ConfigurableApplicationContext,Throwable) ApplicationFailedEvent 1.0


Spring Framework 事件/监听器编程模型
  • Spring 应用事件

    • 普通应用事件: ApplicationEvent
    • 应用上下文事件: ApplicationContextEvent
  • Spring 应用监听器

  • Spring 应用事广播器

    • 接口: ApplicationEventMulticaster
    • 实现类: SimpleApplicationEventMulticaster
    • 执行模式:同步或异步

事件监听默认为同步,异步就是在

onApplicationEvent

方法上加

@Async

@Async
public void onApplicationEvent(ContextRefreshedEvent event) {
    System.out.println("After ApplicationListener :"+event.getApplicationContext().getId()
            +",timestamp:"+event.getTimestamp());
}



自定义 SpringApplication 运行监听器

public class HelloApplicationRunListener implements SpringApplicationRunListener{
    @Override
    public void starting() {
        System.out.println("Hello ApplicationRunListener is starting.");
    }

    @Override
    public void environmentPrepared(ConfigurableEnvironment environment) {

    }

    @Override
    public void contextPrepared(ConfigurableApplicationContext context) {

    }

    @Override
    public void contextLoaded(ConfigurableApplicationContext context) {

    }

    @Override
    public void started(ConfigurableApplicationContext context) {

    }

    @Override
    public void running(ConfigurableApplicationContext context) {

    }

    @Override
    public void failed(ConfigurableApplicationContext context, Throwable exception) {

    }
}

添加到spring.factories中

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
com.imooc.diveinspringboot.run.HelloApplicationRunListener

运行报错:


Caused by: java.lang.NoSuchMethodException: com.imooc.diveinspringboot.run.HelloApplicationRunListener.<init>(org.springframework.boot.SpringApplication, [Ljava.lang.String;) at java.lang.Class.getConstructor0(Class.java:3082) at java.lang.Class.getDeclaredConstructor(Class.java:2178) at org.springframework.boot.SpringApplication.createSpringFactoriesInstances(SpringApplication.java:434) ... 6 more


构造方法报错,我们看一下spring boot的运行运行监听器

EventPublishingRunListener

怎么实现的,它有一个带参数的构造函数

public EventPublishingRunListener(SpringApplication application, String[] args) {
		this.application = application;
		this.args = args;
		this.initialMulticaster = new SimpleApplicationEventMulticaster();
		for (ApplicationListener<?> listener : application.getListeners()) {
			this.initialMulticaster.addApplicationListener(listener);
		}
	}

我么仿照着写一个一样的构造函数,启动正常,打印出了

Hello ApplicationRunListener is starting.


为什么要加一个这样的构造函数,是因为创建运行监听器实例的时候,通过带参数的构造函数实例化的

private <T> List<T> createSpringFactoriesInstances(Class<T> type,
			Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,
			Set<String> names) {
		List<T> instances = new ArrayList<>(names.size());
		for (String name : names) {
			try {
				Class<?> instanceClass = ClassUtils.forName(name, classLoader);
				Assert.isAssignable(type, instanceClass);
				Constructor<?> constructor = instanceClass
						.getDeclaredConstructor(parameterTypes);
				T instance = (T) BeanUtils.instantiateClass(constructor, args);
				instances.add(instance);
			}
			catch (Throwable ex) {
				throw new IllegalArgumentException(
						"Cannot instantiate " + type + " : " + name, ex);
			}
		}
		return instances;
	}



创建 Spring 应用上下文( ConfigurableApplicationContext )

根据准备阶段的推断 Web 应用类型创建对应的

ConfigurableApplicationContext

实例:

  • Web Reactive: AnnotationConfigReactiveWebServerApplicationContext
  • Web Servlet: AnnotationConfigServletWebServerApplicationContext
  • 非 Web: AnnotationConfigApplicationContext
protected ConfigurableApplicationContext createApplicationContext() {
		Class<?> contextClass = this.applicationContextClass;
		if (contextClass == null) {
			try {
				switch (this.webApplicationType) {
				case SERVLET:
					contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
					break;
				case REACTIVE:
					contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
					break;
				default:
					contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
				}
			}
			catch (ClassNotFoundException ex) {
				throw new IllegalStateException(
						"Unable create a default ApplicationContext, "
								+ "please specify an ApplicationContextClass",
						ex);
			}
		}
		return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
	}



创建 Environment

根据准备阶段的推断 Web 应用类型创建对应的 ConfigurableEnvironment 实例:

  • Web Reactive: StandardReactiveWebEnvironment
  • Web Servlet: StandardServletEnvironment
  • 非 Web: StandardEnvironment
private ConfigurableEnvironment getOrCreateEnvironment() {
		if (this.environment != null) {
			return this.environment;
		}
		switch (this.webApplicationType) {
		case SERVLET:
			return new StandardServletEnvironment();
		case REACTIVE:
			return new StandardReactiveWebEnvironment();
		default:
			return new StandardEnvironment();
		}
	}



刷新上下文


run

方法中会执行

refreshContext(context);

,最后执行的就是

AbstractApplicationContext

中的

refresh()

方法


spring加载流程之refresh()



版权声明:本文为yu_kang原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。