스프링캠프 2016 발표 - deep dive into spring boot autoconfiguration
TRANSCRIPT
KSUG�이수홍
Deep�dive�into�Spring�Boot��
Autoconfiguration
Why�Autoconfiguration?
스프링부트�이전의�설정
기존�MVC�설정
@Configuration @EnableWebMvc public class WebAppConfig extends WebMvcConfigurerAdapter { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(“/resource/") .addResourceLocations("/resource/**"); } @Bean public ViewResolver internalResourceViewResolver() { InternalResourceViewResolver viewResolver = new InternalResourceViewResolver(); viewResolver.setPrefix("/WEB-INF/views/"); viewResolver.setSuffix(".jsp"); return viewResolver; }…
DB�설정
@EnableTransactionManagement @Configuration public class DatabaseConfig { @Bean public DataSource dataSource( @Value("${db.driver}") Class<Driver> driverClass, @Value("${db.url}") String url, @Value("${db.user}") String user, @Value("${db.password}") String password) { return new SimpleDriverDataSource( BeanUtils.instantiateClass(driverClass), url, user, password); } }…
JPA�설정
@Configuration public class JpaConfig { @Autowired private DataSource dataSource; @Bean public LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBean() { LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean(); em.setDataSource(dataSource); em.setPackagesToScan("com.springcamp.test"); em.setPersistenceProvider(new HibernatePersistenceProvider()); em.setMappingResources("META-INF/orm.xml"); em.setJpaProperties(hibernateProperties()); return em; } @Bean public Properties hibernateProperties() { Properties properties = new Properties(); properties.put(DIALECT, MySQLDialect.class.getName()); properties.put(HBM2DDL_AUTO, ddlAutoProp); return properties; } ….
새로운�프로젝트�마다�반복적인�설정
스프링부트에서�어떻게�설정하는가?
기존�MVC�설정
@Configuration @EnableWebMvc public class WebAppConfig extends WebMvcConfigurerAdapter { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(“/resource/") .addResourceLocations("/resource/**"); } @Bean public ViewResolver internalResourceViewResolver() { InternalResourceViewResolver viewResolver = new InternalResourceViewResolver(); viewResolver.setPrefix("/WEB-INF/views/"); viewResolver.setSuffix(".jsp"); return viewResolver; }}
기존�MVC�설정
@Configuration @EnableWebMvc public class WebAppConfig extends WebMvcConfigurerAdapter { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(“/resource/") .addResourceLocations("/resource/**"); } @Bean public ViewResolver internalResourceViewResolver() { InternalResourceViewResolver viewResolver = new InternalResourceViewResolver(); viewResolver.setPrefix("/WEB-INF/views/"); viewResolver.setSuffix(".jsp"); return viewResolver; }}
기존�MVC�설정
@Configuration @EnableWebMvc public class WebAppConfig extends WebMvcConfigurerAdapter { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(“/resource/") .addResourceLocations("/resource/**"); } @Bean public ViewResolver internalResourceViewResolver() { InternalResourceViewResolver viewResolver = new InternalResourceViewResolver(); viewResolver.setPrefix("/WEB-INF/views/"); viewResolver.setSuffix(".jsp"); return viewResolver; }}
“/resource/""/resource/**"
ResourceHandler
기존�MVC�설정
@Configuration @EnableWebMvc public class WebAppConfig extends WebMvcConfigurerAdapter { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(“/resource/") .addResourceLocations("/resource/**"); } @Bean public ViewResolver internalResourceViewResolver() { InternalResourceViewResolver viewResolver = new InternalResourceViewResolver(); viewResolver.setPrefix("/WEB-INF/views/"); viewResolver.setSuffix(".jsp"); return viewResolver; }}
“/resource/""/resource/**"
ResourceHandler
Prefix("/WEB-INF/views/")Suffix(".jsp")
스프링�부트�MVC�설정
1. 의존성�추가 spring-boot-starter-web�
2. 환경변수�추가� spring.mvc.view.prefix:�/WEB-INF/viewsspring.mvc.view.suffix:�.jsp
기존�DB�설정
@EnableTransactionManagement @Configuration public class DatabaseConfig { @Bean public DataSource dataSource( @Value("${db.driver}") Class<Driver> driverClass, @Value("${db.url}") String url, @Value("${db.user}") String user, @Value("${db.password}") String password) { return new SimpleDriverDataSource( BeanUtils.instantiateClass(driverClass), url, user, password); } }}
기존�DB�설정
@EnableTransactionManagement @Configuration public class DatabaseConfig { @Bean public DataSource dataSource( @Value("${db.driver}") Class<Driver> driverClass, @Value("${db.url}") String url, @Value("${db.user}") String user, @Value("${db.password}") String password) { return new SimpleDriverDataSource( BeanUtils.instantiateClass(driverClass), url, user, password); } }}
기존�DB�설정
@EnableTransactionManagement @Configuration public class DatabaseConfig { @Bean public DataSource dataSource( @Value("${db.driver}") Class<Driver> driverClass, @Value("${db.url}") String url, @Value("${db.user}") String user, @Value("${db.password}") String password) { return new SimpleDriverDataSource( BeanUtils.instantiateClass(driverClass), url, user, password); } }}
"${db.driver}" "${db.url}" "${db.user}" "${db.password}"
기존�DB�설정
@EnableTransactionManagement @Configuration public class DatabaseConfig { @Bean public DataSource dataSource( @Value("${db.driver}") Class<Driver> driverClass, @Value("${db.url}") String url, @Value("${db.user}") String user, @Value("${db.password}") String password) { return new SimpleDriverDataSource( BeanUtils.instantiateClass(driverClass), url, user, password); } }}
"${db.driver}" "${db.url}" "${db.user}" "${db.password}"SimpleDriverDataSource
스프링�부트�DB�설정
1. 의존성�추가 spring-boot-starter-jdbc�
2. 환경변수�추가spring.datasource.url:�접속�url��� spring.datasource.driverClassName:�드라이브spring.datasource.username:�saspring.datasource.password:
JPA�설정
@Configuration public class JpaConfig { @Autowired private DataSource dataSource; @Bean public LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBean() { LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean(); em.setDataSource(dataSource); em.setPackagesToScan("com.springcamp.test"); em.setPersistenceProvider(new HibernatePersistenceProvider()); em.setMappingResources("META-INF/orm.xml"); em.setJpaProperties(hibernateProperties()); return em; } @Bean public Properties hibernateProperties() { Properties properties = new Properties(); properties.put(DIALECT, MySQLDialect.class.getName()); properties.put(HBM2DDL_AUTO, ddlAutoProp); return properties; } //..}
JPA�설정
@Configuration public class JpaConfig { @Autowired private DataSource dataSource; @Bean public LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBean() { LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean(); em.setDataSource(dataSource); em.setPackagesToScan("com.springcamp.test"); em.setPersistenceProvider(new HibernatePersistenceProvider()); em.setMappingResources("META-INF/orm.xml"); em.setJpaProperties(hibernateProperties()); return em; } @Bean public Properties hibernateProperties() { Properties properties = new Properties(); properties.put(DIALECT, MySQLDialect.class.getName()); properties.put(HBM2DDL_AUTO, ddlAutoProp); return properties; } //..}
JPA�설정
@Configuration public class JpaConfig { @Autowired private DataSource dataSource; @Bean public LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBean() { LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean(); em.setDataSource(dataSource); em.setPackagesToScan("com.springcamp.test"); em.setPersistenceProvider(new HibernatePersistenceProvider()); em.setMappingResources("META-INF/orm.xml"); em.setJpaProperties(hibernateProperties()); return em; } @Bean public Properties hibernateProperties() { Properties properties = new Properties(); properties.put(DIALECT, MySQLDialect.class.getName()); properties.put(HBM2DDL_AUTO, ddlAutoProp); return properties; } //..}
properties.put(DIALECT, MySQLDialect.class.getName()); properties.put(HBM2DDL_AUTO, ddlAutoProp);
스프링�부트�JPA�설정
1. 의존성�추가 spring-boot-starter-data-jpa�
2. 환경변수�추가spring.jpa.show-sql:�truespring.jpa.hibernate.ddl-auto:�create-drop
반복적인�설정�부분�제거��의존성과�환경변수�추가
그리고�소스
@SpringBootApplicationpublic class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
설정�부분�해결?!
설정�클래스는�다�어디�갔을까?
그�내부를�들여다�보자
Into�the�스프링�부트
@SpringBootApplicationpublic class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
Into�the�스프링�부트
@SpringBootApplicationpublic class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
Into�the�스프링�부트
@SpringBootApplicationpublic class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
스프링�컨테이너를�실행
Into�the�스프링�부트
@SpringBootApplicationpublic class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
스프링�컨테이너를�실행
Into�the�스프링�부트
@SpringBootApplicationpublic class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
스프링�컨테이너를�실행부트�단순함을�위한�어노테이션�
Into�the�@SpringBootApplication
@Configuration@EnableAutoConfiguration@ComponentScanpublic @interface SpringBootApplication { … }
Into�the�@SpringBootApplication
@Configuration@EnableAutoConfiguration@ComponentScanpublic @interface SpringBootApplication { … }
Into�the�@SpringBootApplication
@Configuration@EnableAutoConfiguration@ComponentScanpublic @interface SpringBootApplication { … } @EnableAutoConfiguration�
스프링부트�마법의�실마리
About�@EnableAutoConfiguration
•특정�기준의�설정클래스를�로드�
•@Enable*�모듈화된�설정의�일종
• JavaConfig에서�모듈화된�설정시�사용�(�스프링�3.1�부터�지원�)��
• 기존�XML�커스텀태그(<mvc:*/>,<context:*/>�…)와�대응�
• 쉽게�자신의�설정�모듈을�작성�가능 (�@Import�어노테이션�사용�)
@Enable*�어노테이션이란?
@EnableWebMvc @EnableAspectJAutoProxy … @Configuration public class ApplicationConfiguration { … }
Into�the�@EnableAutoConfiguration�
@AutoConfigurationPackage @Import(EnableAutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration { … }
Into�the�@EnableAutoConfiguration�
@AutoConfigurationPackage @Import(EnableAutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration { … }
Into�the�@EnableAutoConfiguration�
@AutoConfigurationPackage @Import(EnableAutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration { … }
스프링부트�자동�설정�기능담당
Into�the�EnableAutoConfigurationImportSelector�1
public class EnableAutoConfigurationImportSelector implements DeferredImportSelector, … { @Override public String[] selectImports(AnnotationMetadata metadata) { … }}
Into�the�EnableAutoConfigurationImportSelector�1
public class EnableAutoConfigurationImportSelector implements DeferredImportSelector, … { @Override public String[] selectImports(AnnotationMetadata metadata) { … }}
Into�the�EnableAutoConfigurationImportSelector�1
public class EnableAutoConfigurationImportSelector implements DeferredImportSelector, … { @Override public String[] selectImports(AnnotationMetadata metadata) { … }}
설정�클래스(@Configuration)�리스트�받아서�활성화
Into�the�EnableAutoConfigurationImportSelector�1
public class EnableAutoConfigurationImportSelector implements DeferredImportSelector, … { @Override public String[] selectImports(AnnotationMetadata metadata) { … }}
설정�클래스(@Configuration)�리스트�받아서�활성화
Into�the�EnableAutoConfigurationImportSelector�1
public class EnableAutoConfigurationImportSelector implements DeferredImportSelector, … { @Override public String[] selectImports(AnnotationMetadata metadata) { … }}
설정�클래스(@Configuration)�리스트�받아서�활성화
설정�클래스�리스트�(패키지명�포함�클래스명)
Into�the�EnableAutoConfigurationImportSelector�2
…protected List<String> getCandidateConfigurations(…) {
return SpringFactoriesLoader.loadFactoryNames( getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader()); }
…}
Into�the�EnableAutoConfigurationImportSelector�2
…protected List<String> getCandidateConfigurations(…) {
return SpringFactoriesLoader.loadFactoryNames( getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader()); }
…}
Into�the�EnableAutoConfigurationImportSelector�2
…protected List<String> getCandidateConfigurations(…) {
return SpringFactoriesLoader.loadFactoryNames( getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader()); }
…}Classpath의�모든�라이브러리의�“META-INF/spring.factories“�위치의�파일에서�설정파일�리스트�읽어온다.
Into�the�EnableAutoConfigurationImportSelector�2
Into�the�META-INF/spring.factories�
•Key=Value�형태로�설정클래스�리스트�기록�
•스프링부트의�기본�설정�정보는�아래의�라이브러리�안에�포함o.s.boot:spring-boot-autoconfigure:x.x.x.jar
# Auto Configureorg.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\ … org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\ org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\ … org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\ org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\ …
중간�정리
• 스프링부트의�설정의�시작@EnableAutoConfiguration�
• 설정리스트의�보관소�“META-INF/spring.factories”�
• 모든�설정클래스를�로딩!?
스프링부트는�지정된�모든�설정클래스가��
스프링컨테이너에�올라가는가?
당연히�NO!�
@Conditional
@Conditional은��어노테이션통한�조건적으로�Bean을�등록
@Conditional�
•스프링�4에서�도입된�어노테이션�
•조건부로�Bean을�(스프링컨테이너)에�등록
@Conditional�
@Bean @Conditional(PropertiesCondition.class) public CommandLineRunner propertiesConditional() { return (args) -> { System.out.println("test.hasValue 환경변수가 있으면 보입니다"); }; }
@Conditional�
@Bean @Conditional(PropertiesCondition.class) public CommandLineRunner propertiesConditional() { return (args) -> { System.out.println("test.hasValue 환경변수가 있으면 보입니다"); }; }
@Conditional�
@Bean @Conditional(PropertiesCondition.class) public CommandLineRunner propertiesConditional() { return (args) -> { System.out.println("test.hasValue 환경변수가 있으면 보입니다"); }; }
@Conditional�
@Bean @Conditional(PropertiesCondition.class) public CommandLineRunner propertiesConditional() { return (args) -> { System.out.println("test.hasValue 환경변수가 있으면 보입니다"); }; }
class PropertiesCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { return context.getEnvironment() .containsProperty("test.hasValue"); } }
@Conditional�
@Bean @Conditional(PropertiesCondition.class) public CommandLineRunner propertiesConditional() { return (args) -> { System.out.println("test.hasValue 환경변수가 있으면 보입니다"); }; }
class PropertiesCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { return context.getEnvironment() .containsProperty("test.hasValue"); } }
@Conditional�
@Bean @Conditional(PropertiesCondition.class) public CommandLineRunner propertiesConditional() { return (args) -> { System.out.println("test.hasValue 환경변수가 있으면 보입니다"); }; }
class PropertiesCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { return context.getEnvironment() .containsProperty("test.hasValue"); } }
true이면�Bean이�등록
@Profile�
@Profile
@Profile("dev") @Configuration class HelloProfileDevConfig { @Bean public HelloWorld helloDevWorld() { return new HelloWorld("dev"); } } @Profile("test") @Configuration class HelloProfileTestConfig { @Bean public HelloWorld helloTestWorld() { return new HelloWorld("test"); } }
@Profile
@Profile("dev") @Configuration class HelloProfileDevConfig { @Bean public HelloWorld helloDevWorld() { return new HelloWorld("dev"); } } @Profile("test") @Configuration class HelloProfileTestConfig { @Bean public HelloWorld helloTestWorld() { return new HelloWorld("test"); } }
@Profile
@Profile("dev") @Configuration class HelloProfileDevConfig { @Bean public HelloWorld helloDevWorld() { return new HelloWorld("dev"); } } @Profile("test") @Configuration class HelloProfileTestConfig { @Bean public HelloWorld helloTestWorld() { return new HelloWorld("test"); } }
@Profile("dev")
@Profile
@Profile("dev") @Configuration class HelloProfileDevConfig { @Bean public HelloWorld helloDevWorld() { return new HelloWorld("dev"); } } @Profile("test") @Configuration class HelloProfileTestConfig { @Bean public HelloWorld helloTestWorld() { return new HelloWorld("test"); } }
@Profile("test")
@Profile("dev")
Into�@Profile
Into�@Profile
@Conditional(ProfileCondition.class) public @interface Profile { String[] value();
}
Into�@Profile
@Conditional(ProfileCondition.class) public @interface Profile { String[] value();
}
Into�@Profile
@Conditional(ProfileCondition.class) public @interface Profile { String[] value();
}@Conditional�를�통해�Profile이�구현
@Conditional�예제
@Conditional�확장�1
@ConditionalOnClass
@ConditionalOnProperties
@ConditionalOnBean @ConditionalOnMissingBean
@ConditionalOnMissingClass
@ConditionalOnResources
@Conditional�확장�1
@ConditionalOnClass
@ConditionalOnProperties
@ConditionalOnBean @ConditionalOnMissingBean
@ConditionalOnMissingClass
@ConditionalOnResources
스프링�컨테이너의�Bean�존재유무
@Conditional�확장�1
@ConditionalOnClass
@ConditionalOnProperties
@ConditionalOnBean @ConditionalOnMissingBean
@ConditionalOnMissingClass
@ConditionalOnResources
스프링�컨테이너의�Bean�존재유무
classpath에서�클래스의�존재유무
@Conditional�확장�1
@ConditionalOnClass
@ConditionalOnProperties
@ConditionalOnBean @ConditionalOnMissingBean
@ConditionalOnMissingClass
@ConditionalOnResources
스프링�컨테이너의�Bean�존재유무
classpath에서�클래스의�존재유무
환경�변수의�유무
@Conditional�확장�1
@ConditionalOnClass
@ConditionalOnProperties
@ConditionalOnBean @ConditionalOnMissingBean
@ConditionalOnMissingClass
@ConditionalOnResources
스프링�컨테이너의�Bean�존재유무
classpath에서�클래스의�존재유무
환경�변수의�유무 Resources�존재�
@Conditional�확장�2
@ConditionalOnJava
@OnJndiCondition
@ConditionalOnWebApplication
@ConditionalOnExpression
@ConditionalOnNotWebApplication
@Conditional�확장�2
@ConditionalOnJava
@OnJndiCondition
@ConditionalOnWebApplication
@ConditionalOnExpression
현재�프로젝트가�웹애플리케이션인지�여부
@ConditionalOnNotWebApplication
@Conditional�확장�2
@ConditionalOnJava
@OnJndiCondition
@ConditionalOnWebApplication
@ConditionalOnExpression
현재�프로젝트가�웹애플리케이션인지�여부
JAVA�버전�종류�선택
@ConditionalOnNotWebApplication
@Conditional�확장�2
@ConditionalOnJava
@OnJndiCondition
@ConditionalOnWebApplication
@ConditionalOnExpression
현재�프로젝트가�웹애플리케이션인지�여부
JAVA�버전�종류�선택
@ConditionalOnNotWebApplication
SPEL의�true�false�여부
@Conditional�확장�2
@ConditionalOnJava
@OnJndiCondition
@ConditionalOnWebApplication
@ConditionalOnExpression
현재�프로젝트가�웹애플리케이션인지�여부
JAVA�버전�종류�선택
JNDI�lookup�가능�여부
@ConditionalOnNotWebApplication
SPEL의�true�false�여부
스프링부트는��@Conditional*을�통한�Autoconfiguration�
구현
스프링부트�설정
MVC�설정
Security�설정
JDBC�설정
JPA�설정
AOP�설정
스프링부트�설정
MVC�설정
Security�설정
JDBC�설정
JPA�설정
AOP�설정
서블릿�인터페이스�있는가?�이미�MVC�설정하는게�있는가?
스프링부트�설정
MVC�설정
Security�설정
JDBC�설정
JPA�설정
AOP�설정
서블릿�인터페이스�있는가?�이미�MVC�설정하는게�있는가?
스프링�시큐리티�라이브러리가�있는가?�등등
스프링부트�설정
MVC�설정
Security�설정
JDBC�설정
JPA�설정
AOP�설정
서블릿�인터페이스�있는가?�이미�MVC�설정하는게�있는가?
스프링�시큐리티�라이브러리가�있는가?�등등
DataSource�객체가�스프링�컨테이너에�존재하는가?�등
스프링부트�설정
MVC�설정
Security�설정
JDBC�설정
JPA�설정
AOP�설정
서블릿�인터페이스�있는가?�이미�MVC�설정하는게�있는가?
스프링�시큐리티�라이브러리가�있는가?�등등
DataSource�객체가�스프링�컨테이너에�존재하는가?�등
JPA,�하이버네이트�라이브리가�있는가?�DataSource�설정이�이미되었
는가?
스프링부트�설정
MVC�설정
Security�설정
JDBC�설정
JPA�설정
AOP�설정
서블릿�인터페이스�있는가?�이미�MVC�설정하는게�있는가?
스프링�시큐리티�라이브러리가�있는가?�등등
DataSource�객체가�스프링�컨테이너에�존재하는가?�등
JPA,�하이버네이트�라이브리가�있는가?�DataSource�설정이�이미되었
는가?
스프링�AOP,�AspectJ�라이브러리가�존재하는가?�
즉�spring.factories�의��설정�클래스는��
조건(@Conditional)에�따라�컨테이너에�등록
Autoconfiguration�지원�기술
• CoreSpring�(Security,�AOP,�Session,�Cache,�DevTools�…)�Atomikos,�…�
• WebSpring�(MVC,�Websocket,�Rest�Docs),�WS,�Jersey,�…�
• Template�Engines Freemarker,�Velocity,�Thymeleaf,�Mustache,�…�
• SQL�JPA,�JOOQ,�JDBC,�H2,�HSQLDB,�Derby,�MySQL,�PostreSQL�
• NoSQL,�Cloud,�Social,�I/O,�Ops,�…
커스텀�모듈�만들기�(소스)
감사합니다!