๋คํ์ฑ์ ๊ณ ๋ คํด์ ์ธํฐํ์ด์ค์ ๊ฐ์ฒด๋ฅผ ๊ตฌ๋ถํ์ฌ ์ฝ๋๋ฅผ ๊ตฌํํ์ง๋ง ๊ฐ์ฒด๋ฅผ ๊ฐ์๋ผ์ฐ๋ ๊ณผ์ ์์ DIP์ OCP๊ฐ ์ง์ผ์ง์ง์๋ ๋ฌธ์ ๊ฐ ์๊ธด๋ค. ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํ ๊ณผ์ ์ ์ฝ๋๋ฅผ ํตํด ์ดํดํ๋ ์ฑํฐ
โ ๊ฐ๋ฐฉ ํ์ ์์น์ธ OCP(ํ์ฅ์๋ ์ด๋ ค์์ด์ผ ํ์ง๋ง ๋ณ๊ฒฝ์๋ ๋ซํ ์์ด์ผ ํ๋ค.)์ ์์กด ์ญ์ ์์น์ธ DIP(๊ฐ์ฒด๋ ๊ณ ์ฐจ์์ ๋ชจ๋์๋ง ์์กดํ ๋ฟ ์ ์ฐจ์์ ๋ชจ๋์ ๋ํด์ ์์กดํด์๋ ์๋๋ค)๋ฅผ ์งํค๊ธฐ ์ํด์ AppConfig๋ฅผ ์ฌ์ฉํด ์์กด ๊ด๊ณ ์ฃผ์ ์ ํด์ค๋ค.
@Configuration Annotation์ ๋ถ์ด๊ณ ๊ฐ ๋ฉ์๋์ @Bean Annotation์ ๋ถ์ธ๋ค.//AppConfig appConfig = new AppConfig();
//MemberService memberService = appConfig.memberService();
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
MemberService memberService = applicationContext.getBean("memberService", MemberService.class);
package hello.core.beanfind;
import hello.core.discount.DiscountPolicy;
import hello.core.discount.FixDiscountPolicy;
import hello.core.discount.RateDiscountPolicy;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Map;
public class ApplicationContextExtendsFindTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
@Test
@DisplayName("๋ถ๋ชจ ํ์
์ผ๋ก ์กฐํ์, ์์์ด ๋ ์ด์ ์์ผ๋ฉด, ์ค๋ณต ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ค.")
void findBeanByParentTypeDuplicate(){
Assertions.assertThrows(NoUniqueBeanDefinitionException.class,
() -> ac.getBean(DiscountPolicy.class));
}
@Test
@DisplayName("๋ถ๋ชจ ํ์
์ผ๋ก ์กฐํ์, ์์์ด ๋ ์ด์ ์์ผ๋ฉด, ๋น ์ด๋ฆ์ ์ง์ ํ๋ฉด ๋๋ค.")
void findByParentTypeBeanName() {
DiscountPolicy bean = ac.getBean("RateDiscountPolicy",DiscountPolicy.class);
org.assertj.core.api.Assertions.assertThat(bean).isInstanceOf(RateDiscountPolicy.class);
}
@Test
@DisplayName("ํน์ ํ์ ํ์
์ผ๋ก ์กฐํ")
void findBeanBySubType(){
RateDiscountPolicy bean = ac.getBean(RateDiscountPolicy.class);
org.assertj.core.api.Assertions.assertThat(bean).isInstanceOf(RateDiscountPolicy.class);
}
@Test
@DisplayName("๋ถ๋ชจ ํ์
์ผ๋ก ๋ชจ๋ ์กฐํํ๊ธฐ")
void findBeanByParentType() {
Map<String, DiscountPolicy> beansOfType = ac.getBeansOfType(DiscountPolicy.class);
org.assertj.core.api.Assertions.assertThat(beansOfType.size()).isEqualTo(2);
for (String key : beansOfType.keySet()) {
System.out.println("key = " + key + " value = " + beansOfType.get(key));
}
}
@Test
@DisplayName("๋ถ๋ชจ ํ์
์ผ๋ก ๋ชจ๋ ์กฐํํ๊ธฐ - Object")
void findAllBeanByObjectType() {
Map<String, Object> beansOfType = ac.getBeansOfType(Object.class);
for (String key : beansOfType.keySet()) {
System.out.println("key = " + key + " value = " + beansOfType.get(key));
}
}
@Configuration
static class TestConfig{
@Bean
public DiscountPolicy RateDiscountPolicy(){
return new RateDiscountPolicy();
}
@Bean
public DiscountPolicy FixDiscountPolicy(){
return new FixDiscountPolicy();
}
}
}โป static ์ ๋ถ์ด์ง ์์ inner class๋ outer class์ ์ข ์๋๋ค. ์ด๋ฐ ๊ฒฝ์ฐ outer class์ ๊ฐ์ฒด๋ฅผ ์ ์ธํ ํ inner class๋ฅผ ์ฌ์ฉํ ์ ์๋ค. ํ์ง๋ง static์ ๋ถ์ผ ๊ฒฝ์ฐ inner class์์๋ ๋ถ๊ตฌํ๊ณ ๋ ๋ฆฝ์ ์ธ ๊ฐ์ฒด๋ก ์ ์ธ์ด ๊ฐ๋ฅํ๋ค. โ Static inner class๋ ํด๋น outer class ๋ด์์ ์ ์ธํ์ฌ ์ด์ฉํ๋ ค๋ ๊ฒฝ์ฐ ์ฌ์ฉ
package hello.core.singleton;
public class SingletonService {
//1. static ์์ญ์ ๊ฐ์ฒด๋ฅผ ๋ฑ 1๊ฐ๋ง ์์ฑํด๋๋ค.
private static final SingletonService instance = new SingletonService();
//2. public์ผ๋ก ์ด์ด์ ๊ฐ์ฒด ์ธ์คํด์ค๊ฐ ํ์ํ๋ฉด ์ด static ๋ฉ์๋๋ฅผ ํตํด์๋ง ์กฐํํ๋๋ก
ํ์ฉํ๋ค.
public static SingletonService getInstance() {
return instance;
}
//3. ์์ฑ์๋ฅผ private์ผ๋ก ์ ์ธํด์ ์ธ๋ถ์์ new ํค์๋๋ฅผ ์ฌ์ฉํ ๊ฐ์ฒด ์์ฑ์ ๋ชปํ๊ฒ ๋ง๋๋ค.
private SingletonService() {
}
public void logic() {
System.out.println("์ฑ๊ธํค ๊ฐ์ฒด ๋ก์ง ํธ์ถ");
}
๊ณต์ ํ๋๋ฅผ ์กฐ์ฌํ๊ณ ํญ์ ๋ฌด์ํ๋ก ์ค๊ณํ์!
@Configuration ๋ฅผ ๋ถ์ด๋ฉด ๋ฐ์ดํธ ์ฝ๋๋ฅผ ์กฐ์ํ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ธ CGLIB์ ์ฌ์ฉํ์ฌ ๋ด๊ฐ ๋ง๋ AppConfig๋ฅผ ์์๋ฐ์ ์์์ ์์ ํด๋์ค๋ฅผ ๋ง๋ค๊ณ , ๊ทธ ํด๋์ค๋ฅผ ์คํ๋ง ๋น์ผ๋ก ๋ฑ๋กํ๋ค.@ComponentScan์ ๋ถ์ฌ์ค๋ค.@Component ๋ฅผ ๋ถ์ฌ์ค๋ค@Autowired ๋ฅผ ๋ถ์ฌ์ค๋ค.โป @Configuration ์์ค์ฝ๋๋ฅผ ์ด์ด๋ณด๋ฉด @Component Annotation์ด ๋ถ์ด์๊ธฐ ๋๋ฌธ์ @Configuration ๋ํ ์ปดํฌ๋ํธ ์ค์บ์ ๋์์ด ๋๋ค. ์ด๋ฅผ ๋ง๊ธฐ ์ํด์๋ ๋ค์๊ณผ ๊ฐ์ด ์ค์ ํด์ฃผ๋ฉด ๋๋ค.
@ComponentScan(
excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
)
public class AutoAppConfig {
}
@ComponentScan
@Component("memberService2")@ComponentScan(
basePackages = "hello.core", // {"hello.core", "hello.service"} ์ฌ๋ฌ ์์น ์ง์
)basePackageClasses : ์ง์ ํ ํด๋์ค์ ํจํค์ง๋ฅผ ํ์ ์์ ์์น๋ก ์ง์ @Autowired
Component Annotation์ด ์๋ Annotation๋ค์ ๊ธฐ๋ฅ
ConflictingBeanDefinitionException ์์ธ ๋ฐ์spring.main.allow-bean-definition-overriding=true ๋ฅผ ์ถ๊ฐํ๋ฉด ์ค๋ฅ๊ฐ ๋์ง์๊ณ ์ค๋ฒ๋ผ์ด๋ฉ์ด ๋๋๋ก ์ค์ ํ ์ ์๋ค.@Configuration
@ComponentScan(
includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = MyIncludeComponent.class),
excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = MyExcludeComponent.class)
)
static class ComponentFilterAppConfig{
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyIncludeComponent {
}์์ ์(Setter) ๋ฉ์๋๋ฅผ ํตํด ์์กด๊ด๊ณ๋ฅผ ์ฃผ์ ํ๋ ๋ฐฉ๋ฒ
์ ํ, ๋ณ๊ฒฝ ๊ฐ๋ฅ์ฑ์ด ์๋ ์์กด๊ด๊ณ์ ์ฌ์ฉ
@Autowired์ ๊ธฐ๋ณธ๋์์ ์ฃผ์
ํ ๋์์ด ์์ผ๋ฉด ์ค๋ฅ๋ฅผ ๋ฐ์์ํจ๋ค. ๋์์ด ์์ด๋ ๋์ํ๊ฒ ํ๊ธฐ ์ํด์๋ @Autowired(required=false)
private MemberRepository memberRepository;
@Autowired
public void setMemberRepository(MemberRepository memberRepository){
this.memberRepository = memberRepository;
}
@Autowired(required=false) : ์๋ ์ฃผ์
ํ ๋์์ด ์์ผ๋ฉด ์์ ์ ๋ฉ์๋๊ฐ ํธ์ถ๋์ง ์๋๋ค.@Nullable : ์๋ ์ฃผ์
ํ ๋์์ด ์์ผ๋ฉด null์ด ์
๋ ฅ๋๋ค.Optional<> : ์๋ ์ฃผ์
ํ ๋์์ด ์์ผ๋ฉด Optional.empty๊ฐ ์
๋ ฅ๋๋ค.
plugins {
id 'org.springframework.boot' version '2.3.2.RELEASE'
id 'io.spring.dependency-management' version '1.0.9.RELEASE'
id 'java'
}
group = 'hello'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
//lombok ์ค์ ์ถ๊ฐ ์์
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
//lombok ์ค์ ์ถ๊ฐ ๋
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter'
//lombok ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ถ๊ฐ ์์
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testCompileOnly 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'
//lombok ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ถ๊ฐ ๋
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
}
test {
useJUnitPlatform()
}ac.getBean(DiscountPolicy.class) ์ ์ ์ฌํ๊ฒ ๋์NoUniqueBeanDefinitionException ์ค๋ฅ ๋ฐ์๋น ๋ฑ๋ก์ @Qualifier๋ฅผ ๋ถ์ฌ์ค๋ค.
@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy {}
์ฃผ์ ์์ @Qualifier๋ฅผ ๋ถ์ฌ์ฃผ๊ณ ๋ฑ๋กํ ์ด๋ฆ์ ์ ์ด์ค๋ค.
@Autowired
public OrderServiceImpl(MemberRepository memberRepository,
@Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Qualifier("mainDiscountPolicy")
public @interface MainDiscountPolicy {
}
์) ํ ์ธ ์๋น์ค๋ฅผ ์ ๊ณตํ๋๋ฐ, ํด๋ผ์ด์ธํธ๊ฐ ํ ์ธ์ ์ข ๋ฅ๋ฅผ ์ ํํ ์ ์๋๋ก ํ๋ ๊ฒฝ์ฐ
public class AllBeanTest {
@Test
void findAllBean() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class, DiscountService.class);
DiscountService discountService = ac.getBean(DiscountService.class);
Member member = new Member(1L, "name", Grade.VIP);
int discountPrice = discountService.discount(member, 10000, "fixDiscountPolicy");
Assertions.assertThat(discountPrice).isEqualTo(1000);
int rateDiscountPrice = discountService.discount(member, 20000, "rateDiscountPolicy");
Assertions.assertThat(rateDiscountPrice).isEqualTo(2000);
}
static class DiscountService {
private final Map<String, DiscountPolicy> policyMap;
private final List<DiscountPolicy> policies;
@Autowired
public DiscountService(Map<String, DiscountPolicy> policyMap, List<DiscountPolicy> policies) {
this.policyMap = policyMap;
this.policies = policies;
System.out.println("policyMap = " + policyMap);
System.out.println("policies = " + policies);
}
public int discount(Member member, int price, String discountCode) {
DiscountPolicy discountPolicy = policyMap.get(discountCode);
return discountPolicy.discount(member, price);
}
}
}๊ฐ์ฒด์ ์์ฑ๊ณผ ์ด๊ธฐํ๋ฅผ ๋ถ๋ฆฌํ์
์์ฑ์์ ์ด๊ธฐํ ์ฝ๋๋ฅผ ๋ฃ์ผ๋ฉด ๋์ง ์์๊น๋ผ๋ ์๊ฐ์ ํ ์ ์๋ค. ํ์ง๋ง ์์ฑ์๋ ํ์์ ๋ณด(ํ๋ผ๋ฏธํฐ)๋ฅผ ๋ฐ๊ณ ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ ์ฑ ์์ ๊ฐ์ง๋ฉฐ, ์ด๊ธฐํ๋ ์ด๋ ๊ฒ ์์ฑ๋ ๊ฐ๋ค์ ํ์ฉํ์ฌ ์ธ๋ถ ์ปค๋ฅ์ ์ ์ฐ๊ฒฐํ๋ ๋ฑ์ ๋ฌด๊ฑฐ์ด ์์ ์ ์ํํ๋ค.
๋ฐ๋ผ์ ์์ฑ์ ์์์ ๋ฌด๊ฑฐ์ด ์ด๊ธฐํ ์์ ์ ํจ๊ปํ๋ ๊ฒ๋ณด๋ค ๋๋๋ ๊ฒ์ด ์ ์ง๋ณด์ ๊ด์ ์์ ๋ ๋์ ์ ์๋ค.
Bean(initMethod= "init", destroyMethod= "close") ์ฒ๋ผ ์ด๊ธฐํ, ์๋ฉธ ๋ฉ์๋๋ฅผ ์ง์ ํ ์ ์๋ค.close , shutdown ์ด๋ผ๋ ์ด๋ฆ์ ์ข
๋ฃ ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋ค.(inferred) ๋ก ๋ฑ๋ก๋์ด ์๋๋ฐ ์ด๋ close, shutdown ์ด๋ผ๋ ์ด๋ฆ์ ๋ฉ์๋๋ฅผ ์๋์ผ๋ก ํธ์ถ ํด์ค๋ค.@Scope("singleton") , @Scope("prototype") ๊ณผ ๊ฐ์ด Annotation์ ๋ถ์ฌ์ฃผ๋ฉด ๋๋ค. ์ปดํฌ๋ํธ ์ค์บ ์ฌ์ฉ์ ํด๋น ํด๋์ค์, Bean Annotation์ ์ฌ์ฉํ ์๋์ฃผ์
์ Bean Annotation์.
์คํ๋ง ์ ํ๋ฆฌ์ผ์ด์ ์ปจํ ์คํธ ์ ์ฒด ์ฃผ์
๋จ์ํ๊ฒ ApplicationContext(์ปจํ ์ด๋) ์์ฒด๋ฅผ ์ฑ๊ธํค ๋น์ ์ฃผ์ ํด์ ๋ก์ง์ ์คํํ ๋๋ง๋ค getBean์ ํตํด ๋น์ ๋ถ๋ฌ์ค๊ณ ์๋ก์ด ํ๋กํ ํ์ ๋น์ ์์ฑํด์ค๋ค.
ObjectFactory, ObjectProvider โ DL์ด ํ์ํ ๊ฒฝ์ฐ ์ฌ์ฉ
Provider
์น ํ๊ฒฝ์์๋ง ๋์ํ๋ค.
์คํ๋ง์ด ํด๋น ์ค์ฝํ์ ์ข ๋ฃ์์ ๊น์ง ๊ด๋ฆฌํ๋ค. ๋ฐ๋ผ์ ์ข ๋ฃ ๋ฉ์๋๊ฐ ํธ์ถ๋๋ค.
request: http ์์ฒญ ํ๋๊ฐ ๋ค์ด์ค๊ณ ๋๊น ๋๊น์ง ์ ์ง๋๋ ์ค์ฝํ, ๊ฐ๊ฐ์ http ์์ฒญ๋ง๋ค ๋ณ๋์ ๋น ์ธ์คํด์ค๊ฐ ์์ฑ๋๊ณ ๊ด๋ฆฌ๋๋ค.
session
application
websocket
@Component
@Scope("request")
public class MyLogger {
private String uuid;
private String requestURL;
//RequestURL ์ ๋น์ด ์์ฑ๋๋ ์์ ์๋ ์ ์ ์๊ธฐ ๋๋ฌธ์, ์ธ๋ถ์์ setter ๋ก ์
๋ ฅ๋ฐ๋๋ค.
public void setRequestURL(String requestURL) {
this.requestURL = requestURL;
}
public void log(String message){
System.out.println("[" + uuid + "]" + "[" + requestURL + "]" + message);
}
@PostConstruct
public void init(){
uuid = UUID.randomUUID().toString(); // ์ด ๋น์ Http ์์ฒญ ๋น ํ๋ ์ฉ ์์ฑ๋๋ฏ๋ก UUID๋ฅผ ์ ์ฅํด๋๋ฉด ๋ค๋ฅธ Http ์์ฒญ๊ณผ ๊ตฌ๋ถํ ์ ์๋ค.
System.out.println("[" + uuid + "] request scope bean create:" + this);
}
@PreDestroy
public void close(){
System.out.println("[" + uuid + "] request scope bean close:" + this);
}
}@Scope(value="request" proxyMode= ScopedProxyMode.TARGET_CLASS)โ ์ด๋ ๊ฒ ํ๋ฉด MyLogger์ ๊ฐ์ง ํ๋ก์ ํด๋์ค๋ฅผ ๋ง๋ค์ด๋๊ณ Http ์์ฒญ๊ณผ ์๊ด์์ด ๊ฐ์ง ํ๋ก์ ํด๋์ค๋ฅผ ๋ฏธ๋ฆฌ ์ฃผ์ ํด ๋ ์ ์๋ค. ์ด ๊ฐ์ง ํ๋ก์ ๊ฐ์ฒด๋ ์์ฒญ์ด ์ค๋ฉด ๊ทธ๋ ๋ด๋ถ์์ ์ง์ง ๋น์ ์์ฒญํ๋ ์์ ๋ก์ง์ด ๋ค์ด์๋ค.
๐ฅ Provider๋ฅผ ์ฌ์ฉํ๋ , ํ๋ก์๋ฅผ ์ฌ์ฉํ๋ ์ค์ํ ๊ฒ์ ์ง์ง ๊ฐ์ฒด ์กฐํ๋ฅผ ๊ผญ ํ์ํ ์์ ๊น์ง ์ง์ฐ์ฒ๋ฆฌ ํ๋ค๋ ์ ์ด๋ค. ์ด๋ฐ ํน๋ณํ scope๋ ๊ผญ ํ์ํ ๊ณณ์๋ง ์ต์ํํด์ ์ฌ์ฉ