Springboot 초기화 프로세스 분석
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
이상은 가장 간단한 Springboot 프로그램(2.0.3버전)의 예이자 우리가 가장 통용하는 문법이다. 그러나 그 중에서 이 복잡한 기능 조작을 봉인하여 우리는 점차적으로 분석하기 시작했다.
우선 여기서 가장 중요한 것은 주해
@SpringBootApplication
입니다.@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 {
@AliasFor(annotation = EnableAutoConfiguration.class)
Class>[] exclude() default {};
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class>[] scanBasePackageClasses() default {};
}
@SpringBootApplication
주해는 몇 개의 주해가 복합적으로 구성되어 있는데 그 중에서 가장 중요한 것은 @SpringBootConfiguration
, @EnableAutoConfiguration
과 @ComponentScan
이 세 가지이다.@SpringBootConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}
그 중에서
@ComponentScan
은spring의 원생 주석이고 @SpringBootConfiguration
은springboot의 주석이지만 실질은 포장된 @Configuration
이다. 여전히spring의 주석으로 xml을 대체하는 방식으로 설정 bean을 관리하는 데 사용된다.@EnableAutoConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
/**
* Exclude specific auto-configuration classes such that they will never be applied.
* @return the classes to exclude
*/
Class>[] exclude() default {};
/**
* Exclude specific auto-configuration class names such that they will never be
* applied.
* @return the class names to exclude
* @since 1.3.0
*/
String[] excludeName() default {};
}
@EnableAutoConfiguration
의 정의는 다음과 같다. 여기서 가장 중요한 주석은 @Import
(@AutoConfigurationPackage
의 주석의 실현도 @Import
을 바탕으로 한다)이다. @Import
의 도움을 받아 자동 설정 조건에 부합되는 모든 bean 정의를 IoC 용기에 탑재한다.@EnableAutoConfiguration
에 대한 주석은 후속 언급은 그때 다시 상세하게 설명할 것이다.여기서 우리는 먼저 가동류의 run
방법으로 돌아가 초기화 절차를 처음부터 분석한다.run 방법
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);
}
'run'방법이 최종적으로 호출된 것은
new SpringApplication(primarySources).run(args)
이다. 여기서 먼저 SpringApplication
대상을 만들고 그 run
방법을 호출한다.public SpringApplication(Class>... primarySources) {
this(null, primarySources);
}
public SpringApplication(ResourceLoader resourceLoader, Class>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = deduceWebApplicationType();
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
여기에는 주로
SpringApplication
의 대상을 초기화하는데 여기서 특별히 언급할 것은 webApplicationType
과 getSpringFactoriesInstances
이다.webApplicationType
이것은 우리의 응용이 어떤 유형의 응용인지 표시하는 데 사용된다.
deduceWebApplicationType()
방법의 실현을 살펴보자. private WebApplicationType deduceWebApplicationType() {
if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
&& !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : WEB_ENVIRONMENT_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
그 반환값은
WebApplicationType
유형의 매거류로 그 값은 NONE
, SERVLET
, REACTIVE
세 가지가 있는데 각각 비WEB 응용, servlet 기반의 WEB 응용과reactive 기반의 WEB 응용이다.getSpringFactoriesInstances
private Collection getSpringFactoriesInstances(Class type) {
return getSpringFactoriesInstances(type, new Class>[] {});
}
private Collection getSpringFactoriesInstances(Class type,
Class>[] parameterTypes, Object... args) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// Use names and ensure unique to protect against duplicates
Set names = new LinkedHashSet<>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
여기서 핵심은
SpringFactoriesLoader.loadFactoryNames(type, classLoader)
방법입니다. public static List loadFactoryNames(Class> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
loadSpringFactories(classLoader)
이 뭘 했는지 주목해주세요. private static Map> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
Enumeration urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry, ?> entry : properties.entrySet()) {
List factoryClassNames = Arrays.asList(
StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
result.addAll((String) entry.getKey(), factoryClassNames);
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
이곳의
FACTORIES_RESOURCE_LOCATION
은 META-INF/spring.factories
으로 정의되어 있기 때문에 이 방법은 모든 가방에 있는 이 파일을 스캔하여 맵의 대상으로 해석하고 cache
에 캐시하여 중복 불러오는 것을 피한다.springboot 가방에 있는 이 파일의 일부 부분은 다음과 같다.
# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
이를 통해 알 수 있듯이
setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class))
과 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
이 각각 상기 유형에 대응한다.해석 완료 후
createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names)
처리 해석 결과를 호출하여 대응하는 실례를 생성한다. 원본 코드는 다음과 같다. @SuppressWarnings("unchecked")
private List createSpringFactoriesInstances(Class type,
Class>[] parameterTypes, ClassLoader classLoader, Object[] args,
Set names) {
List 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;
}
이곳의 핵심은
ClassUtils.forName(name, classLoader)
방법을 통해 반사적인 방식으로 유형의 실례를 생성하는 instanceClass
이다.이를 통해 알 수 있듯이 SpringFactoriesLoader.loadFactoryNames(type, classLoader)
의 역할은 META-INF/spring.factories
에 배치된 내용을 실례화한 공장 방법류로 매우 강한 확장성을 갖추고 SPI 메커니즘과 비슷한 효과가 있다.SpringApplication
의 초기화를 보고 run
으로 돌아가서 방법을 계속 분석합니다. public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);//
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);//
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();//
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;
}
여기서 그중에서 비교적 중요한 몇 가지 방법을 골라 분석하다
ConfigurableEnvironment
객체 private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();// environment
configureEnvironment(environment, applicationArguments.getSourceArgs());//
listeners.environmentPrepared(environment);// ,
bindToSpringApplication(environment);
if (this.webApplicationType == WebApplicationType.NONE) {
environment = new EnvironmentConverter(getClassLoader())
.convertToStandardEnvironmentIfNecessary(environment);
}
ConfigurationPropertySources.attach(environment);
return environment;
}
getOrCreateEnvironment()
방법으로 용기 환경 만들기 private ConfigurableEnvironment getOrCreateEnvironment() {
if (this.environment != null) {
return this.environment;
}
if (this.webApplicationType == WebApplicationType.SERVLET) {
return new StandardServletEnvironment();
}
return new StandardEnvironment();
}
environment
이 존재하면 중복 창설되지 않습니다. 응용 형식이 servlet일 때 창설된 대상은 StandardServletEnvironment
대상입니다. 그렇지 않으면 StandardEnvironment
대상을 창설합니다.이어서
configureEnvironment(environment, applicationArguments.getSourceArgs())
보겠습니다. protected void configureEnvironment(ConfigurableEnvironment environment,
String[] args) {
configurePropertySources(environment, args);//
configureProfiles(environment, args);// active
}
configurePropertySources(environment, args)
시작 명령줄의 설정 속성을 불러옵니다. 실현을 보십시오. protected void configurePropertySources(ConfigurableEnvironment environment,
String[] args) {
MutablePropertySources sources = environment.getPropertySources();
if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
sources.addLast(
new MapPropertySource("defaultProperties", this.defaultProperties));
}
//
if (this.addCommandLineProperties && args.length > 0) {
String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
if (sources.contains(name)) {
PropertySource> source = sources.get(name);
CompositePropertySource composite = new CompositePropertySource(name);
composite.addPropertySource(new SimpleCommandLinePropertySource(
"springApplicationCommandLineArgs", args));
composite.addPropertySource(source);
sources.replace(name, composite);
}
else {
sources.addFirst(new SimpleCommandLinePropertySource(args));
}
}
}
이곳의
MutablePropertySources
대상은 설정 집합을 저장하는 데 사용되며, 내부에는 CopyOnWriteArrayList
형식의list 대상이 유지되며, 기본 설정이 존재할 때 이list의 끝에 new MapPropertySource("defaultProperties", this.defaultProperties)
대상을 삽입합니다.이어서
configureProfiles(environment, args)
보겠습니다. protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
environment.getActiveProfiles(); // ensure they are initialized
// But these ones should go first (last wins in a property key clash)
Set profiles = new LinkedHashSet<>(this.additionalProfiles);
profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
environment.setActiveProfiles(StringUtils.toStringArray(profiles));
}
여기서 주로 하는 일은
environment.getActiveProfiles()
의 매개 변수를 environment
에 설정하는 것이다. 즉, spring.profiles.active
에 대응하는 환경 변수이다.마지막으로
listeners.environmentPrepared(environment)
보겠습니다. public void environmentPrepared(ConfigurableEnvironment environment) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.environmentPrepared(environment);
}
}
이곳의
listeners
은 이전에 META-INF/spring.factories
을 통해 등록된 모든listeners입니다. 그 중에서 가장 중요한 ConfigFileApplicationListener
을 예로 삼아 분석한 다음에 listener.environmentPrepared(environment)
을 살펴보겠습니다. @Override
public void environmentPrepared(ConfigurableEnvironment environment) {
this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
this.application, this.args, environment));
}
여기에서
ApplicationEnvironmentPreparedEvent
유형의 이벤트를 만들고 multicastEvent
방법을 호출했습니다. 이 방법을 통해 최종적으로listener의 onApplicationEvent
방법을 호출하여 이벤트 감청기의 실행을 촉발합니다.다음은
ConfigFileApplicationListener
의 onApplicationEvent
방법이 어떻게 되었는지 살펴보겠습니다. @Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent(
(ApplicationEnvironmentPreparedEvent) event);
}
if (event instanceof ApplicationPreparedEvent) {
onApplicationPreparedEvent(event);
}
}
ApplicationEnvironmentPreparedEvent
유형의 사건을 감시할 때 onApplicationEnvironmentPreparedEvent( (ApplicationEnvironmentPreparedEvent) event)
방법을 사용합니다 private void onApplicationEnvironmentPreparedEvent(
ApplicationEnvironmentPreparedEvent event) {
List postProcessors = loadPostProcessors();
postProcessors.add(this);
AnnotationAwareOrderComparator.sort(postProcessors);
for (EnvironmentPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessEnvironment(event.getEnvironment(),
event.getSpringApplication());
}
}
여기에는
loadPostProcessors()
방법으로 META-INF/spring.factories
의 모든 EnvironmentPostProcessor
류를 리스트에 싣고 ConfigFileApplicationListener
자신도 추가한 것을 볼 수 있다.다음으로list의 모든 대상을 훑어보고 postProcessEnvironment
방법을 실행합니다. 이어서 이 방법을 보십시오 @Override
public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application) {
addPropertySources(environment, application.getResourceLoader());
}
protected void addPropertySources(ConfigurableEnvironment environment,
ResourceLoader resourceLoader) {
RandomValuePropertySource.addToEnvironment(environment);
new Loader(environment, resourceLoader).load();
}
여기의 핵심은
new Loader(environment, resourceLoader).load()
입니다. 여기의 Loader
은 내부 클래스로 프로필의 마운트를 처리하는 데 사용됩니다. 우선 그 구조 방법을 살펴보겠습니다. Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
this.environment = environment;
this.resourceLoader = (resourceLoader != null ? resourceLoader
: new DefaultResourceLoader());
this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(
PropertySourceLoader.class, getClass().getClassLoader());
}
여기에는
resourceLoader
이 SpringFactoriesLoader
을 통해 로드되는 것을 볼 수 있습니다. 그러면 META-INF/spring.factories
에 정의된 resourceLoader
이 무엇인지 살펴보겠습니다.org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader
이름에서 알 수 있듯이
PropertiesPropertySourceLoader
과 YamlPropertySourceLoader
은 각각 처리에 쓰인다.properties 및.yml 형식의 프로필입니다.이어서
load()
방법이 어떻게 되었는지 살펴보겠습니다. public void load() {
this.profiles = new LinkedList<>();
this.processedProfiles = new LinkedList<>();
this.activatedProfiles = false;
this.loaded = new LinkedHashMap<>();
initializeProfiles();//
while (!this.profiles.isEmpty()) {//
Profile profile = this.profiles.poll();
if (profile != null && !profile.isDefaultProfile()) {
addProfileToEnvironment(profile.getName());
}
load(profile, this::getPositiveProfileFilter,
addToLoaded(MutablePropertySources::addLast, false));
this.processedProfiles.add(profile);
}
load(null, this::getNegativeProfileFilter,
addToLoaded(MutablePropertySources::addFirst, true));//
addLoadedPropertySources();
}
initializeProfiles()
은 profiles
의 초기화를 진행했고 기본적으로 null
과 default
에서 profiles
까지 null
에 대응하는 프로필 응용 프로그램을 추가했다.properties와 응용 프로그램.yml, default
대응 프로필 응용 프로그램-default.yml와 응용 프로그램-default.properties, 여기 있는 null
은 우선적으로 처리됩니다. 후처리는 선처리를 덮어쓰기 때문에 우선순위가 가장 낮습니다.이어서
load(profile, this::getPositiveProfileFilter, addToLoaded(MutablePropertySources::addLast, false))
방법을 살펴보겠습니다. private void load(Profile profile, DocumentFilterFactory filterFactory,
DocumentConsumer consumer) {
getSearchLocations().forEach((location) -> {
boolean isFolder = location.endsWith("/");
Set names = (isFolder ? getSearchNames() : NO_SEARCH_NAMES);
names.forEach(
(name) -> load(location, name, profile, filterFactory, consumer));
});
}
여기서 중점은
getSearchLocations()
을 통해 프로필을 가져오는 경로입니다. 기본적으로 4개의 경로를 가져옵니다.이어서 이 경로를 옮겨다니며 프로필 이름을 연결하고 적합한yml나properties 해상도를 선택하여 분석한 다음에 결과를
environment
의 propertySources
에 추가합니다.createApplicationContext()
을 통해 run
메서드의 반환값 대상 context
protected ConfigurableApplicationContext createApplicationContext() {
Class> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch (this.webApplicationType) {
case SERVLET:
contextClass = Class.forName(DEFAULT_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);
}
보시다시피 여기도
webApplicationType
의 수치에 따라 각각 다른 반환 유형을 만듭니다.prepareContext(context, environment, listeners, applicationArguments,printedBanner)
방법을 통해 listeners
, environment
, applicationArguments
, printedBanner
등 중요한 구성 요소와 상하문 대상 context
을 private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);// ,
postProcessApplicationContext(context);// ,
applyInitializers(context);
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
context.getBeanFactory().registerSingleton("springApplicationArguments",
applicationArguments);
if (printedBanner != null) {
context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
}
// Load the sources
Set
여기
sources
은 바로 우리의 시동류를 장착한 다음에 load(context, sources.toArray(new Object[0]))
방법을 통해 탑재한다 protected void load(ApplicationContext context, Object[] sources) {
if (logger.isDebugEnabled()) {
logger.debug(
"Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
}
BeanDefinitionLoader loader = createBeanDefinitionLoader(
getBeanDefinitionRegistry(context), sources);
if (this.beanNameGenerator != null) {
loader.setBeanNameGenerator(this.beanNameGenerator);
}
if (this.resourceLoader != null) {
loader.setResourceLoader(this.resourceLoader);
}
if (this.environment != null) {
loader.setEnvironment(this.environment);
}
loader.load();
}
loader
이 어떻게 탑재되었는지 살펴보겠습니다. public int load() {
int count = 0;
for (Object source : this.sources) {
count += load(source);
}
return count;
}
private int load(Object source) {
Assert.notNull(source, "Source must not be null");
if (source instanceof Class>) {
return load((Class>) source);
}
if (source instanceof Resource) {
return load((Resource) source);
}
if (source instanceof Package) {
return load((Package) source);
}
if (source instanceof CharSequence) {
return load((CharSequence) source);
}
throw new IllegalArgumentException("Invalid source type " + source.getClass());
}
private int load(Class> source) {
if (isGroovyPresent()
&& GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {
// Any GroovyLoaders added in beans{} DSL can contribute beans here
GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source,
GroovyBeanDefinitionSource.class);
load(loader);
}
if (isComponent(source)) {
this.annotatedReader.register(source);
return 1;
}
return 0;
}
일련의 호출을 거친 후에 최종적으로
load(Class> source)
방법으로 실행되었다. 여기서 재미있는 것은 Groovy가 존재할 때 Groovy를 우선적으로 호출하는 방식으로 불러오는 것이다. 그렇지 않으면 this.annotatedReader.register(source)
방법으로 시작 클래스를 beanDefinitionMap
에 등록하는 것이다.refreshContext(context)
리셋 용기 private void refreshContext(ConfigurableApplicationContext context) {
refresh(context);
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}
protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
((AbstractApplicationContext) applicationContext).refresh();
}
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
이
refresh()
방법은 상당히 중요하다. 특히 invokeBeanFactoryPostProcessors(beanFactory)
은spring-boot-starter-*(mybatis,redis 등) 자동화 설정을 실현하는 관건적인 부분이므로 후속으로 상세하게 설명한다.총결산
이로써 Springboot의 시작 절차는 대체적으로 분석되었고 설정 파일과 시작 클래스가 어떻게 불러오는지 파악했지만 두 가지 문제가 남아 있다. 첫째, Springboot의 핵심 사상은 설정보다 크다는 약속이다. 둘째, Springboot의 각종spring-boot-starter-*가 어떻게 역할을 발휘하는지 이 두 가지 문제는 후속 글에서 계속 분석해야 한다.
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
다양한 언어의 JSONJSON은 Javascript 표기법을 사용하여 데이터 구조를 레이아웃하는 데이터 형식입니다. 그러나 Javascript가 코드에서 이러한 구조를 나타낼 수 있는 유일한 언어는 아닙니다. 저는 일반적으로 '객체'{}...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.