서문.
SpringBoot의 자동 어셈블리는 개발자의 구성 부담을 많이 덜어줍니다.
그렇다면 자동화된 조립이란 무엇일까요?
자동 어셈블리는 개발자가 직접 구성하지 않고도 필요한 빈을 로드하여 호출 시간을 단축할 수 있도록 하는 Spring 시작 프로세스입니다.
다음은 Spring 스타트업 클래스입니다:
@SpringBootApplication
public class LasSystemApplication {
public static void main(String[] args) {
SpringApplication.run(LasSystemApplication.class, args);
}
}
자동 어셈블리는 주로 @SpringBootApplication 통해 , 방법을 알아보려면 계속 읽어보세요.
&&
주석 분석
Spring BootApplication 어노테이션은 다음과 같은 여러 어노테이션으로 나뉘며, EnableAutoConfiguration 대한 핵심 어노테이션을 자동으로 어셈블한 다음 이 어노테이션의 분석에 중점을 둡니다.
@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 {
//
}
EnableAutoConfiguration
주석 분석:
메타 주석 외에도 두 가지 주요 주석이 있는데, 하나는 @AutoConfigurationPackage 다른 하나는 @Import(AutoConfigurationImportSelector.class), 아래에서 하나씩 분석합니다.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
/**
* Environment property that can be used to override when auto-configuration is
* enabled.
*/
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 {};
}
AutoConfigurationPackage
마찬가지로, 메타 노트 외에 노트 @Import(AutoConfigurationPackages.Registrar.class) 남습니다.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
/**
* Base packages that should be registered with {@link AutoConfigurationPackages}.
* <p>
* Use {@link #basePackageClasses} for a type-safe alternative to String-based package
* names.
* @return the back package names
* @since 2.3.0
*/
String[] basePackages() default {};
/**
* Type-safe alternative to {@link #basePackages} for specifying the packages to be
* registered with {@link AutoConfigurationPackages}.
* <p>
* Consider creating a special no-op marker class or interface in each package that
* serves no purpose other than being referenced by this attribute.
* @return the base package classes
* @since 2.3.0
*/
Class<?>[] basePackageClasses() default {};
}
분석을 계속 진행하면 핵심 메서드인 AutoConfigurationPackages.Registrar.class 가진 registerBeanDefinitions 클래스가 임포트된 것을 볼 수 있으며, 이는 사용자 자신의 패키지에서 빈을 로드하는 데 중점을 둡니다.
자동 조립된 원두는 두 부분으로 나뉩니다:
하나는 사용자가 정의한 빈이고 다른 하나는 사용자가 pom 파일에 도입한 종속 빈입니다.
/**
* {@link ImportBeanDefinitionRegistrar} to store the base package from the importing
* configuration.
*/
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
//여기 스타트업의 메타데이터는 메인 스타트업 클래스의 전체 클래스 이름이며, 메인 스타트업 클래스와 동일한 수준의 패키지를 검색합니다,
//이 패키지의 원두를 원두 용기에 등록하기
register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImports(metadata));
}
}
AutoConfigurationImportSelector
이 클래스에는 종속 빈을 빈 컨테이너에 로드하는 핵심 메서드인 getAutoConfigurationEntry 있습니다.
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = getConfigurationClassFilter().filter(configurations);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
이 메서드 내부의 주요 로직입니다:
이 메서드는 모든 후보 구성을 가져오는 데 사용되며, 이 메서드는 스프링팩토리로더를 통해 META-INF/spring.factories 내부의 모든 구성을 로드하는 데 사용됩니다.
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
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;
}
내부에 메서드가 있고 그 아래에 메인 메서드가 있는 SpringFactoriesLoader.loadFactoryNames 메서드에 대한 주요 분석은 loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList()); 메서드가 내부의 모든 구성을 classLoader.getResources(FACTORIES_RESOURCE_LOCATION); 이유를 설명합니다.
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
Map<String, List<String>> result = cache.get(classLoader);
if (result != null) {
return result;
}
result = new HashMap<>();
try {
Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
String[] factoryImplementationNames =
StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
for (String factoryImplementationName : factoryImplementationNames) {
result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
.add(factoryImplementationName.trim());
}
}
}
// Replace all lists with unmodifiable lists containing unique elements
result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
cache.put(classLoader, result);
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
return result;
}
계속해서 더 자세히 살펴보면 이 메서드는 주어진 이름의 모든 리소스를 찾고, 여기서 주어진 이름은 META-INF/spring.factories, 이 메서드가 하는 일은 프로젝트 종속성 내의 모든 리소스 파일을 체크 아웃하는 것임을 알 수 있습니다.
public Enumeration<URL> getResources(String name) throws IOException {
Objects.requireNonNull(name);
@SuppressWarnings("unchecked")
Enumeration<URL>[] tmp = (Enumeration<URL>[]) new Enumeration<?>[2];
if (parent != null) {
tmp[0] = parent.getResources(name);
} else {
tmp[0] = BootLoader.findResources(name);
}
tmp[1] = findResources(name);
return new CompoundEnumeration<>(tmp);
}
spring-boot-autoconfigure
이 종속성을 강조한 이유는 일반적으로 사용하는 대부분의 종속성이 이 자동 구성 종속성을 사용하기 때문입니다.
소스 구조를 열면 자동 구성에 원하는 파일인 spring.factories가 있는 것을 확인할 수 있습니다.
이 파일을 열어보면 초기화 클래스, 리스너, 다음 주인공인 EnableAutoConfiguration 등을 포함할 수 있는 여러 자동 구성 클래스가 있는 것을 볼 수 있습니다. 그렇다면 왜 이렇게 많은 클래스가 로드되지 않는지, 그 이유에 대해서는 계속 읽어보시기 바랍니다.
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.autoconfigure.integration.IntegrationPropertiesEnvironmentPostProcessor
# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener
# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
......생략하기에는 너무 많은 내용
EnableAutoConfiguration 로드 중
위에서 언급한 메서드에 getCandidateConfigurations 함수가 있는데, getSpringFactoriesLoaderFactoryClass() 이 메서드는 간단하며 EnableAutoConfiguration.class; 클래스를 반환합니다.
/**
* Return the auto-configuration class names that should be considered. By default
* this method will load candidates using {@link SpringFactoriesLoader} with
* {@link #getSpringFactoriesLoaderFactoryClass()}.
* @param metadata the source metadata
* @param attributes the {@link #getAttributes(AnnotationMetadata) annotation
* attributes}
* @return a list of candidate configurations
*/
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
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;
}
/**
* Return the class used by {@link SpringFactoriesLoader} to load configuration
* candidates.
* @return the factory class
*/
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
그런 다음 loadFactoryNames 메서드를 계속 살펴보십시오. 이해를 돕기 위해 디버그를 수행하면 기본값이 EnableAutoConfiguration 구성 클래스의 키를 가져 오는 것을 볼 수 있으며 여기에는 133 개가 있으며이 133 개는 org 내부의 spring.factories입니다. 구성 클래스 아래에 있는 springframework.boot.autoconfigure.EnableAutoConfiguratio입니다. 여기까지입니다. 133개의 클래스가 모두 주입되지 않았다면 어디를 찾아서 어떤 클래스를 찾아야 할까요? 정답은 '아니요, 온디맨드 인젝션'입니다. 그렇다면 온디맨드 인젝션을 호출하는 방법은 무엇일까요? 다음 분석입니다.
온디맨드 인젝션
여기서 주입할 133개의 콩 중 몇 개를 무작위로 분석하고 나머지는 같은 방식으로 분석합니다.
1.org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration
여기에서 이 구성 클래스를 분석하면 이 클래스가 RestTemplate의 자동 구성 클래스임을 알 수 있으며, 먼저 이 클래스의 어노테이션에 집중하세요.
@Configuration(proxyBeanMethods = false)//Spring에 이것이 구성 클래스라고 알리기
@AutoConfigureAfter(HttpMessageConvertersAutoConfiguration.class)//HttpMessageConvertersAutoConfiguration 이후의 어셈블리
@ConditionalOnClass(RestTemplate.class)//프로젝트 레드에 Rest 템플릿이 있나요?.class이 클래스는 어셈블리만 있는 경우
@Conditional(NotReactiveWebApplicationCondition.class)//반응형 웹 앱의 예와 아니오
public class RestTemplateAutoConfiguration {
//.....
}
이 어노테이션을 통해 이러한 조건이 충족되는지 확인한 다음, 이 구성 클래스가 주입되는 컨테이너, 우선 restTemplateBuilderConfigurer, restTemplateBuilder, 주석 뒤에 코드에 작성한 어노테이션의 의미, 이 클래스가 컨테이너에 주입되어야 한다는 것을 확인할 수 있습니다.
public class RestTemplateAutoConfiguration {
@Bean
@Lazy//
@ConditionalOnMissingBean//주입하기 전에 그러한 빈이 없음을 나타냅니다.
public RestTemplateBuilderConfigurer restTemplateBuilderConfigurer(
ObjectProvider<HttpMessageConverters> messageConverters,
ObjectProvider<RestTemplateCustomizer> restTemplateCustomizers,
ObjectProvider<RestTemplateRequestCustomizer<?>> restTemplateRequestCustomizers) {
RestTemplateBuilderConfigurer configurer = new RestTemplateBuilderConfigurer();
configurer.setHttpMessageConverters(messageConverters.getIfUnique());
configurer.setRestTemplateCustomizers(restTemplateCustomizers.orderedStream().collect(Collectors.toList()));
configurer.setRestTemplateRequestCustomizers(
restTemplateRequestCustomizers.orderedStream().collect(Collectors.toList()));
return configurer;
}
@Bean
@Lazy
@ConditionalOnMissingBean
public RestTemplateBuilder restTemplateBuilder(RestTemplateBuilderConfigurer restTemplateBuilderConfigurer) {
RestTemplateBuilder builder = new RestTemplateBuilder();
return restTemplateBuilderConfigurer.configure(builder);
}
static class NotReactiveWebApplicationCondition extends NoneNestedConditions {
NotReactiveWebApplicationCondition() {
super(ConfigurationPhase.PARSE_CONFIGURATION);
}
@ConditionalOnWebApplication(type = Type.REACTIVE)
private static class ReactiveWebApplication {
}
}
}
원두 컨테이너에 두 원두를 모두 넣으면 두 원두가 모두 존재한다는 것을 보여줍니다.
2.org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration
자동 구성 클래스의 분석에서 이것이 Redis 자동 구성 클래스임을 알 수 있으며, 조건이 충족되면이 클래스는 컨테이너에 redisTemplate 및 stringRedisTemplate이 될 것이며, 현재 프로젝트에는이 클래스에 RedisOperations가 없기 때문에 분명히이 조건을 충족하지 않으므로 다음을 확인합니다. 두 개의 빈을 살펴볼 필요가 없으며 주입되지 않으므로 다음을 확인합니다.
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
return new StringRedisTemplate(redisConnectionFactory);
}
}
유효성 검사 프로세스:
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(InitProjectTemplateApplication.class, args);
boolean beanDefinition = run.containsBeanDefinition("redisTemplate");
System.out.println("redisTemplate존재하나요?>"+beanDefinition);
boolean builder = run.containsBeanDefinition("stringRedisTemplate");
System.out.println("stringRedisTemplate존재하나요?>"+builder);
}
redisTemplate존재하나요?>false
stringRedisTemplate존재하나요?>false
이 글은 여기까지이며, 궁금한 점이 있으시면 언제든지 공유해 주세요.
더 많은 기사를 계속 게시하세요!



