์ผ | ์ | ํ | ์ | ๋ชฉ | ๊ธ | ํ |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 |
- java
- ์ฐ์ ์์ ํ
- dynamic debugging
- OS
- ์๋ฎฌ๋ ์ด์
- Spring
- ์์ ์ ๋ ฌ
- ๋งต
- c++
- ์คํ
- ๋ฌธ์์ด
- error
- ๋ฐ์ดํฌ์คํธ๋ผ
- thymeleaf
- web
- ๋์ ํฉ
- JPA
- Reversing
- ์ต๋จ ๊ฒฝ๋ก
- CVE
- ๋ฐฑํธ๋ํน
- ๊ทธ๋ฆฌ๋
- ๊ตฌํ
- DP
- GCP
- BFS
- dfs
- ๋ถํ ์ ๋ณต
- ์ด๋ถ ํ์
- ์ฌ๊ท
- Today
- Total
hades
[Spring] ์คํ๋ง ๊ธฐ์ด (2) ๋ณธ๋ฌธ
์คํ๋ง ์ปจํ ์ด๋์ ์คํ๋ง ๋น
์คํ๋ง ์ปจํ ์ด๋ ์์ฑ
ApplicationContext๋ ์คํ๋ง ์ปจํ ์ด๋์ด์ ์ธํฐํ์ด์ค์ด๋ค. ๋ฐ๋ผ์ ๋คํ์ฑ์ด ์ ์ฉ๋๋ค. AnnotationConfigApplicationContext๋ ์๊ณ , ๋ค๋ฅธ ApplicationContext๋ค๋ ์๋ค.
์ค์ ์์ ๋ํ๋ด๋ @Configuration์ด ๋ถ์ AppConfig.class๋ฅผ ๋งค๊ฐ๋ณ์๋ก ํ๋ AnnotationConfigApplicationContext์ ์์ฑํ๋ฉด, @Bean์ด ๋ถ์ ๊ฒ๋ค์ด ์คํ๋ง ์ ์ฅ์์ ์ ์ฅ๋๋ค. ๋น ์ด๋ฆ์ ๋ฉ์๋ ์ด๋ฆ, ๋น ๊ฐ์ฒด๋ return ๊ฐ์ด๋ค.
๋น์ ์ด๋ฆ์ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ฉ์๋ ์ด๋ฆ์ด๋ฉฐ, ํญ์ ๋ค๋ฅธ ์ด๋ฆ์ ๋ถ์ฌํด์ผ ํ๋ค.
3. ์คํ๋ง ๋น ์์กด ๊ด๊ณ ์ค์
์คํ๋ง ์ปจํ
์ด๋๋ ์ค์ ์ ๋ณด๋ฅผ ์ฐธ๊ณ ํด์ ์์กด๊ด๊ณ๋ฅผ ์ฃผ์
ํ๋ค. (์๋ก ๋ค๋ฅธ memberRepository๋ฅผ ๊ฐ๋ฅดํค๋ ๊ฑฐ ์๋๊ฐ..?)->@Configuration๊ณผ ์ฑ๊ธํค ์ฐธ์กฐ
์ปจํ ์ด๋์ ๋ฑ๋ก๋ ๋ชจ๋ ๋น ์กฐํ
package hello.core.beanfind;
import hello.core.AppConfig;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class ApplicationContextInfoTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
@Test
@DisplayName("๋ชจ๋ ๋น ์ถ๋ ฅ")
void findAllBean() {
String[] beanDefinitionNames = ac.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) { // iter ์น๊ณ , Tab
Object bean = ac.getBean(beanDefinitionName);
System.out.println("name = " + beanDefinitionName + " object = " + bean);
}
}
@Test
@DisplayName("์ ํ๋ฆฌ์ผ์ด์
๋น ์ถ๋ ฅ")
void findApplicationAllBean() {
String[] beanDefinitionNames = ac.getBeanDefinitionNames(); // Bean์ ์ด๋ฆ๋ค์ ๋ค ๊ฐ์ ธ์ด
for (String beanDefinitionName : beanDefinitionNames) { // iter ์น๊ณ , Tab
BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName); // Bean์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ด
if (beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION){ // <-> ROLE_INFRASTRUCTURE ์คํ๋ง ๋ด๋ถ์์ ์ฌ์ฉํ๋ ๋น
Object bean = ac.getBean(beanDefinitionName);
System.out.println("name = " + beanDefinitionName + " object = " + bean);
}
}
}
}
BeanDefinition.ROLE_APPLICATION์ ํ๋ก๊ทธ๋๋จธ๊ฐ ๋ฑ๋กํ ๋น์ด๊ณ , ROLE_INFRASTRUCTURE ์คํ๋ง ๋ด๋ถ์์ ์ฌ์ฉํ๋ ๋น์ด๋ค.
์คํ๋ง ๋น ์กฐํ - ๊ธฐ๋ณธ
ac.getBean(์ด๋ฆ, ํ์
)
ac.getBean(ํ์
)
@Test
@DisplayName("๋น ์ด๋ฆ์ผ๋ก ์ฐพ๊ธฐ")
void findBeanByName(){
MemberService memberService = ac.getBean("memberService", MemberService.class);
// System.out.println("memberService = " + memberService);
// System.out.println("memberService.getClass() = " + memberService.getClass());
Assertions.assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
memberService๋ AppConfig์์ memberService ๋ฉ์๋์ return ๊ฒฐ๊ณผ์ธ memberServiceImpl์ด๋ค.
@Test
@DisplayName("ํ์
์ผ๋ก ์ฐพ๊ธฐ")
void findBeanByType(){
MemberService memberService = ac.getBean(MemberService.class);
Assertions.assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
ํ์ ๋ง์ผ๋ก๋ ์ฐพ์ ์ ์๋ค.
@Test
@DisplayName("๊ตฌ์ฒด ํ์
์ผ๋ก ์ฐพ๊ธฐ")
void findBeanBySpecificType(){
MemberServiceImpl memberService = ac.getBean("memberService", MemberServiceImpl.class);
Assertions.assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
๊ตฌ์ฒด ํ์ ์ผ๋ก ์ฐพ์ ์๋ ์์ง๋ง, ์ญํ ๊ณผ ๊ตฌํ์ ๋ถ๋ฆฌํด์ผ ํ๋ค๋ ์์น์ ๋ฐ๋ผ ์ข์ ๋ฐฉ๋ฒ์ ์๋๋ค.
@Test
@DisplayName("๋น ์ด๋ฆ์ผ๋ก ์ฐพ๊ธฐ ์คํจ")
void findBeanByNameX(){
MemberService memberService = ac.getBean("fail", MemberService.class);
}
์คํ๋ง ๋น์ ๋ฑ๋ก๋์ง ์์ ์ด๋ฆ์ผ๋ก getBean์ ํ ๊ฒฝ์ฐ, ์๋ฌ๊ฐ ๋ฐ์ํ๋ค.
์คํ๋ง ๋น ์กฐํ - ๋์ผํ ํ์ ์ด ๋ ์ด์
@Test
@DisplayName("๋์ผ ํ์
์กด์ฌ ์ ์ค๋ฅ ๋ฐ์")
void findBeanByTypeDuplicate(){
MemberRepository memberRepository = ac.getBean(MemberRepository.class);
}
@Configuration
static class TestConfig {
@Bean
public MemberRepository memberRepository1(){
return new MemoryMemberRepository();
}
@Bean
public MemberRepository memberRepository2(){
return new MemoryMemberRepository();
}
}
๋์ผํ ํ์ ์ ๊ฐ๋ ๋น์ด ์ฌ๋ฌ ๊ฐ์ผ ๋, getBean์ ํ๋ฉด ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ค.
@Test
@DisplayName("๋์ผ ํ์
์กด์ฌ ์ ์ด๋ฆ์ ์ง์ ํ๋ฉด ์ค๋ฅ ๋ฐ์ํ์ง ์์")
void findBeanByTypeDuplicate2(){
MemberRepository memberRepository = ac.getBean("memberRepository1", MemberRepository.class);
}
์ด๋ฆ์ ์ง์ ํ๋ฉด ์ค๋ฅ๊ฐ ๋ฐ์ํ์ง ์๋๋ค. ์ด๋ก์จ ์ ์ ์๋ ๊ฒ์ ํด๋์ค๊ฐ ๊ฐ์๋ ์ด๋ฆ์ด ๋ค๋ฅด๋ฉด, ์คํ๋ง ๋น์ผ๋ก ๋ฑ๋กํ๋ ๊ฒ์ผ๋ก ๋ณด์ธ๋ค.
@Test
@DisplayName("ํน์ ํ์
์ ๊ฐ์ง๋ ๋น์ ๋ชจ๋ ์กฐํํ๊ธฐ")
void findAllBeanByType(){
Map<String, MemberRepository> beansOfType = ac.getBeansOfType(MemberRepository.class);
for (String key : beansOfType.keySet()) {
System.out.println("key = " + key + " value = " + beansOfType.get(key));;
}
System.out.println("beansOfType = " + beansOfType);
Assertions.assertEquals(beansOfType.size(), 2);
}
beansOfType return ๊ฐ์ key๋ bean์ ์ด๋ฆ, value๋ bean์ด๋ค.
์คํ๋ง ๋น ์กฐํ - ์์ ๊ด๊ณ๊ฐ ์๋ ๊ฒฝ์ฐ
๋ถ๋ชจ ํ์ ์ ์กฐํํ๋ฉด, ์์ ํ์ ๋ ์กฐํ๋๋ค.
๋ชจ๋ ์๋ฐ ๊ฐ์ฒด์ ์ต๊ณ ๋ถ๋ชจ๋ Object์ธ๋ฐ, Object๋ฅผ ์กฐํํ๋ฉด ๋ชจ๋ ์คํ๋ง ๋น์ด ์กฐํ๋๋ค.
@Test
@DisplayName("๋ถ๋ชจ ํ์
์ผ๋ก getBeanํ ์ ์์์ด ๋ ์ด์ ์์ผ๋ฉด, ์ค๋ณต ์ค๋ฅ ๋ฐ์")
void findBeanByParentTypeDuplicate() {
//DiscountPolicy bean = ac.getBean(DiscountPolicy.class);
assertThrows(NoUniqueBeanDefinitionException.class, () ->
ac.getBean(DiscountPolicy.class));
}
์ธํฐํ์ด์ค์ธ DiscountPolicy์ ์์์ด ๋ ์ด์์ผ ๋, getBean์ ํ๊ฒ ๋๋ฉด, ์ด๋ ์์์ ์ ํํด์ผ ํ ์ง ๋ชจ๋ฅด๋ฏ๋ก ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ค.
@Test
@DisplayName("๋ถ๋ชจ ํ์
์ผ๋ก ์กฐํ์, ์์์ด ๋ ์ด์ ์์ผ๋ฉด, ๋น ์ด๋ฆ์ ์ง์ ํ๋ฉด ๋๋ค")
void findBeanByParentTypeBeanName() {
DiscountPolicy rateDiscountPolicy = ac.getBean("rateDiscountPolicy",
DiscountPolicy.class);
assertThat(rateDiscountPolicy).isInstanceOf(RateDiscountPolicy.class);
}
์์์ ๋น ์ด๋ฆ์ ์ง์ ํ๋ ๊ฒ์ผ๋ก ํด๊ฒฐ ๊ฐ๋ฅํ๋ค.
@Test
@DisplayName("ํน์ ํ์ ํ์
์ผ๋ก ์กฐํ")
void findBeanBySubType() {
RateDiscountPolicy bean = ac.getBean(RateDiscountPolicy.class);
assertThat(bean).isInstanceOf(RateDiscountPolicy.class);
}
์์๋ค์ ํ์ ์ด ๋ชจ๋ ๋ค๋ฅด๋ค๋ฉด, ํน์ ํ์ ํ์ ์ผ๋ก ์กฐํํ ์ ์๋ค.
@Test
@DisplayName("๋ถ๋ชจ ํ์
์ผ๋ก ๋ชจ๋ ์กฐํํ๊ธฐ")
void findAllBeanByParentType() {
Map<String, DiscountPolicy> beansOfType = ac.getBeansOfType(DiscountPolicy.class);
assertThat(beansOfType.size()).isEqualTo(2);
for (String key : beansOfType.keySet()) {
System.out.println("key = " + key + " value =" +
beansOfType.get(key));
}
}
getBean์ด ์๋๊ธฐ ๋๋ฌธ์ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ง ์๊ณ , ๋ชจ๋ ๋น์ด ๋์จ๋ค.
BeanFactory์ ApplicationContext
BeanFactory
- ์คํ๋ง ์ปจํ ์ด๋์ ์ต์์ ์ธํฐํ์ด์ค๋ค.
- ์คํ๋ง ๋น์ ๊ด๋ฆฌํ๊ณ ์กฐํํ๋ ์ญํ ์ ๋ด๋นํ๋ค.
- getBean() ์ ์ ๊ณตํ๋ค.
- ์ง๊ธ๊น์ง ์ฐ๋ฆฌ๊ฐ ์ฌ์ฉํ๋ ๋๋ถ๋ถ์ ๊ธฐ๋ฅ์ BeanFactory๊ฐ ์ ๊ณตํ๋ ๊ธฐ๋ฅ์ด๋ค.
ApplicationContext
BeanFactory ๊ธฐ๋ฅ์ ๋ชจ๋ ์์๋ฐ์์ ์ ๊ณตํ๋ค.
๋น์ ๊ด๋ฆฌํ๊ณ ๊ฒ์ํ๋ ๊ธฐ๋ฅ์ BeanFactory๊ฐ ์ ๊ณตํด์ฃผ๋๋ฐ, ์ ํ์ํ์ง?
์ ํ๋ฆฌ์ผ์ด์
์ ๊ฐ๋ฐํ ๋๋ ๋น์ ๊ด๋ฆฌํ๊ณ ์กฐํํ๋ ๊ธฐ๋ฅ์ ๋ฌผ๋ก ์ด๊ณ , ์ ๋ง์ ๋ถ๊ฐ๊ธฐ๋ฅ์ด ํ์ํ๋ค.
๋ฉ์์ง์์ค๋ฅผ ํ์ฉํ ๊ตญ์ ํ ๊ธฐ๋ฅ
์๋ฅผ ๋ค์ด์ ํ๊ตญ์์ ๋ค์ด์ค๋ฉด ํ๊ตญ์ด๋ก, ์์ด๊ถ์์ ๋ค์ด์ค๋ฉด ์์ด๋ก ์ถ๋ ฅ
ํ๊ฒฝ๋ณ์
๋ก์ปฌ, ๊ฐ๋ฐ, ์ด์๋ฑ์ ๊ตฌ๋ถํด์ ์ฒ๋ฆฌ
์ ํ๋ฆฌ์ผ์ด์
์ด๋ฒคํธ
์ด๋ฒคํธ๋ฅผ ๋ฐํํ๊ณ ๊ตฌ๋
ํ๋ ๋ชจ๋ธ์ ํธ๋ฆฌํ๊ฒ ์ง์
ํธ๋ฆฌํ ๋ฆฌ์์ค ์กฐํ
ํ์ผ, ํด๋์คํจ์ค, ์ธ๋ถ ๋ฑ์์ ๋ฆฌ์์ค๋ฅผ ํธ๋ฆฌํ๊ฒ ์กฐํ
๊ฒฐ๋ก ์ ์ผ๋ก, BeanFactory์ ๋ค๋ฅธ ์ธํฐํ์ด์ค๋ฅผ ์์๋ฐ๋ ApplicationContext๋ฅผ ์ฌ์ฉํ๋ค.
๋ค์ํ ์ค์ ํ์ ์ง์ - ์๋ฐ ์ฝ๋, XML
์คํ๋ง ์ปจํ ์ด๋๋ ๋ค์ํ ํ์์ ์ค์ ์ ๋ณด๋ฅผ ๋ฐ์ ์ ์๋ค.
์์ฆ์ AnnotationConfigApplicationContext๋ฅผ ๋ง์ด ์ฌ์ฉํ๋ค.
xml๋ก ์ค์ ํ๊ธฐ
package hello.core.xml;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;
public class XmlAppContext {
@Test
void xmlAppContext(){
ApplicationContext ac = new GenericXmlApplicationContext("appConfig.xml");
MemberService memberService = ac.getBean("memberService", MemberService.class);
Assertions.assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
}
์ค์ ์ ๋ณด๋ฅผ Xml๋ก๋ถํฐ ์ป๋ ApplicationContext
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="memberService" class="hello.core.member.MemberServiceImpl">
<constructor-arg name="memberRepository" ref="memberRepository" />
</bean>
<bean id="memberRepository"
class="hello.core.member.MemoryMemberRepository" />
<bean id="orderService" class="hello.core.order.OrderServiceImpl">
<constructor-arg name="memberRepository" ref="memberRepository" />
<constructor-arg name="discountPolicy" ref="discountPolicy" />
</bean>
<bean id="discountPolicy" class="hello.core.discount.RateDiscountPolicy" />
</beans>
orderService๋ผ๋ ๋น์ OrderServiceImpl ์์ฑ์๋ฅผ ๋ฐํํ๊ณ , memberRepository์ discountPolicy๋ฅผ ์ ๋ฌ๋ฐ๋๋ค.
- id : ์คํ๋ง ๋น ์ด๋ฆ
- class : ์คํ๋ง ๋น value
- contructor-arg : name์ ๋งค๊ฐ๋ณ์ ์ด๋ฆ, ref๋ ์ฐธ์กฐํ id
์คํ๋ง ๋น ์ค์ ๋ฉํ ์ ๋ณด - BeanDefinition
์คํ๋ง์ด ๋ค์ํ ํ์์ผ๋ก ์ค์ ํ ์ ์๋ ์ด์ ๋ BeanDefinition์ด๋ผ๋ ์ถ์ํ๊ฐ ์๊ธฐ ๋๋ฌธ์ด๋ค. ์คํ๋ง ์ปจํ ์ด๋๋ ๋น ์ค์ ๋ฉํ์ ๋ณด์ธ BeanDefinition๋ง ์๋ฉด ๋๋ค.
AnnotationConfigApplicationContext ๋ AnnotatedBeanDefinitionReader ๋ฅผ ์ด์ฉํ์ฌ AppConfig.class ๋ฅผ ์ฝ๊ณ BeanDefinition ์ ์์ฑํ๋ค.
GenericXmlApplicationContext ๋ XmlBeanDefinitionReader ๋ฅผ ์ฌ์ฉํด์ appConfig.xml ์ค์ ์ ๋ณด๋ฅผ ์ฝ๊ณ BeanDefinition ์ ์์ฑํ๋ค.
์ฑ๊ธํค ์ปจํ ์ด๋
์น ์ ํ๋ฆฌ์ผ์ด์ ๊ณผ ์ฑ๊ธํค
์น ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ณดํต ์ฌ๋ฌ ๊ณ ๊ฐ์ด ๋์์ ์์ฒญ์ ํ๋ค.
package hello.core.singleton;
import hello.core.AppConfig;
import hello.core.member.MemberService;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
public class SingletonTest {
@Test
@DisplayName("์คํ๋ง์ ์ฌ์ฉํ์ง ์์ DI ์ปจํ
์ด๋")
void pureContainer(){
AppConfig appConfig = new AppConfig();
MemberService memberService1 = appConfig.memberService();
MemberService memberService2 = appConfig.memberService();
System.out.println("memberService1 = " + memberService1);
System.out.println("memberService2 = " + memberService2);
Assertions.assertThat(memberService1).isNotSameAs(memberService2);
}
}
์คํ๋ง์ ์ฌ์ฉํ์ง ์์ผ๋ฉด(= @Bean์ผ๋ก ๋ฑ๋กํ๊ณ , getBean์ผ๋ก ๊ฐ์ ธ์ค์ง ์์ผ๋ฉด), memberService๋ฅผ ๋ถ๋ฌ์ฌ ๋๋ง๋ค ์๋ก์ด ๊ฐ์ฒด๊ฐ ์์ฑ๋๋ค.
ํด๊ฒฐํ๋ ๋ฐฉ๋ฒ์ ํ๋๋ง ์์ฑํ๊ณ , ๊ทธ๊ฒ์ ๊ณต์ ํ๋๋ก ์ค๊ณํ๋ ๊ฒ์ด๋ค. ์ด๊ฒ์ด ์ฑ๊ธํค์ด๋ค.
์ฑ๊ธํค ํจํด
ํด๋์ค์ ์ธ์คํด์ค๊ฐ ๋ฑ 1๊ฐ๋ง ์์ฑ๋๋ ๊ฒ์ ๋ณด์ฅํ๋ ๋์์ธ ํจํด์ด๋ค.
private ์์ฑ์๋ฅผ ์ฌ์ฉํด์ ์ธ๋ถ์์ ์์๋ก new ํค์๋๋ฅผ ์ฌ์ฉํ์ฌ ์์ฑํ์ง ๋ชปํ๋๋ก ๋ง์์ผ ํ๋ค.
package hello.core.singleton;
public class SingletonService {
private static final SingletonService instance = new SingletonService();
public static SingletonService getInstance(){
return instance;
}
private SingletonService(){
}
public void logic(){
System.out.println("์ฑ๊ธํค ๊ฐ์ฒด ๋ก์ง ํธ์ถ");
}
}
๊ฐ์ฒด๋ฅผ static ์์ญ์ ๋ฏธ๋ฆฌ ์์ฑํด๋๋๋ค.
๊ฐ์ฒด๋ฅผ ์ ๊ทผํ ์ ์๋ ๋ฐฉ๋ฒ์ getInstance()๋ฅผ ํตํด์๋ง ๊ฐ๋ฅํ๋ค.
ํจ๋ถ๋ก ์ ๊ทผํ์ง ๋ชปํ๋๋ก private๋ก ๋ง์๋์๋ค.
์ ๋ฐฉ๋ฒ์ ์ฑ๊ธํค ํจํด์ ๊ตฌํํ๋ ์ฌ๋ฌ๊ฐ์ง ๋ฐฉ๋ฒ ์ค ๊ฐ์ฅ ๋จ์ํ๊ณ ์์ ํ ๋ฐฉ๋ฒ์ด๋ค.
@Test
@DisplayName("์ฑ๊ธํค ํจํค์ ์ ์ฉํ ๊ฐ์ฒด ์ฌ์ฉ")
void singletonServiceTest(){
SingletonService singletonService1 = SingletonService.getInstance();
SingletonService singletonService2 = SingletonService.getInstance();
System.out.println("singletonService1 = " + singletonService1);
System.out.println("singletonService2 = " + singletonService2);
Assertions.assertThat(singletonService1).isSameAs(singletonService2);
}
ํ๋์ ๊ฐ์ฒด๋ง์ ๊ฐ๋ฅดํค๋ฏ๋ก, singletonService1๊ณผ singletonService2๋ ๊ฐ๋ค.
equalTo๋ ๊ฐ์ด ๊ฐ์์ง, sameAs๋ ์ฐธ์กฐ๊ฐ ๊ฐ์์ง ๋น๊ตํ๋ค.
์ฑ๊ธํค ํจํด์ ๋ฌธ์ ์
- ์ฑ๊ธํค ํจํด์ ๊ตฌํํ๋ ์ฝ๋๊ฐ ๋ง์ด ๋ค์ด๊ฐ๋ค.
- ์ฑ๊ธํค ํจํด์ ์ฌ์ฉํ ๋ ๊ตฌ์ฒด ํด๋์ค์ ์์กดํด์ DIP(์์กด ์ญ์ ์์น)์ ์๋ฐํ๋ค. (AppConfig์ ๊ตฌ์ฒดํ๊ฐ ๋ค์ด๊ฐ)
- OCP๋ฅผ ์๋ฐํ ๊ฐ๋ฅ์ฑ๋ ๋์์ง๊ฒ ๋๋ค.
- ํ ์คํธํ๊ธฐ ์ด๋ ต๋ค.
- private ์์ฑ์๋ก ์์ ํด๋์ค๋ฅผ ๋ง๋ค๊ธฐ ์ด๋ ต๋ค.
- ์ ์ฐ์ฑ์ด ๋จ์ด์ง๋ค.
- ์ํฐํจํด์ผ๋ก ๋ถ๋ฆฌ๊ธฐ๋ ํ๋ค.
์ฐธ๊ณ ) DIP : ์ถ์ํ์ ์์กดํด์ผ์ง, ๊ตฌ์ฒดํ์ ์์กดํ๋ฉด ์ ๋๋ค
์ฑ๊ธํค ์ปจํ ์ด๋
์คํ๋ง ์ปจํ ์ด๋๋ ์ฑ๊ธํค ํจํด์ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ฉด์, ๊ฐ์ฒด ์ธ์คํด์ค๋ฅผ ์ฑ๊ธํค์ผ๋ก ๊ด๋ฆฌํ๋ค. ์คํ๋ง ๋น์ ์ฑ๊ธํค์ผ๋ก ๊ด๋ฆฌ๋๋ ๋น์ด๋ค.
@Test
@DisplayName("์คํ๋ง ์ปจํ
์ด๋์ ์ฑ๊ธํค")
void springContainer(){
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
MemberService memberService1 = ac.getBean("memberService", MemberService.class);
MemberService memberService2 = ac.getBean("memberService", MemberService.class);
System.out.println("memberService1 = " + memberService1);
System.out.println("memberService2 = " + memberService2);
Assertions.assertThat(memberService1).isSameAs(memberService2);
}
์คํ๋ง ๋น์ ์ฑ๊ธํค์ผ๋ก ๊ด๋ฆฌ๋๊ธฐ ๋๋ฌธ์ singletonService1๊ณผ singletonService2๋ ๊ฐ๋ค.
์ฑ๊ธํค ๋ฐฉ์์ ์ฃผ์์
์ฑ๊ธํค ํจํด์ด๋ , ์คํ๋ง์ ์ฑ๊ธํค ์ปจํ ์ด๋๋ ์ฌ๋ฌ ํด๋ผ์ด์ธํธ๊ฐ ํ๋์ ๊ฐ์ฒด ์ธ์คํด์ค๋ฅผ ๊ณต์ ํ๊ธฐ ๋๋ฌธ์ ์ํ๋ฅผ ์ ์งํ๊ฒ ์ค๊ณํ๋ฉด ์๋๋ค. ์ฆ, ๊ฐ์ ์ ์ฅํ๊ฑฐ๋ ์์ ํ ์ ์๊ฒ ํด์ผ ํ๋ค. ํ๋ ๋์ ์ ์๋ฐ์์ ๊ณต์ ๋์ง ์๋ ์ง์ญ๋ณ์, ํ๋ผ๋ฏธํฐ, ThreadLocal ๋ฑ์ ์ฌ์ฉํด์ผ ํ๋ค.
package hello.core.singleton;
public class StatefulService {
private int price; // ์ํ ์ ์ฅ
public void order(String name, int price) {
System.out.println("name = " + name + " price = " + price);
this.price = price; // ์ํ ๋ณ๊ฒฝ
}
public int getPrice() {
return price;
}
}
package hello.core.singleton;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import static org.junit.jupiter.api.Assertions.*;
class StatefulServiceTest {
@Test
void statefulServiceSingleton(){
ApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
StatefulService statefulService1 = ac.getBean(StatefulService.class);
StatefulService statefulService2 = ac.getBean(StatefulService.class);
statefulService1.order("userA", 10000);
statefulService2.order("userB", 20000);
int price = statefulService1.getPrice();
Assertions.assertThat(statefulService1.getPrice()).isEqualTo(20000);
}
static class TestConfig {
@Bean
public StatefulService statefulService(){
return new StatefulService();
}
}
}
์คํ๋ง์ ์ํด statefulService๋ผ๋ ์ฑ๊ธํค ์ปจํ ์ด๋๋ฅผ ์ฌ์ฉํ๋ค. A๊ฐ 10000์ ์ง๋ฆฌ ์ฃผ๋ฌธ์ ํ๊ณ , B๊ฐ 20000์ ์ง๋ฆฌ ์ฃผ๋ฌธ์ ํ ํ์ A๊ฐ ์ฃผ๋ฌธํ ๊ฐ๊ฒฉ์ ์กฐํํ๋ฉด, ์๋์ ๋ค๋ฅด๊ฒ 20000์์ด ๋์ถ๋๋ค. ์ฑ๊ธํค ์ปจํ ์ด๋๋ฅผ ์ฌ์ฉํ๊ณ , ์ค๊ฐ์ ๊ฐ์ ์์ ํ๋ ๊ณผ์ ์ด ์๊ธฐ ๋๋ฌธ์ ๋ฐ์๋๋ ๋ฌธ์ ์ด๋ค.
โ โ โ ์คํ๋ง์ ๋น์ ๋ฌด์ํ๋ก ์ค๊ณํ์! โ โ โ
package hello.core.singleton;
public class StatelessService {
public void order(String name, int price){
System.out.println("name = " + name + " price " + price);
return price;
}
}
@Test
void statefulServiceSingleton(){
ApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
StatefulService statefulService1 = ac.getBean(StatefulService.class);
StatefulService statefulService2 = ac.getBean(StatefulService.class);
int userAPrice = statefulService1.order("userA", 10000);
int userBPrice = statefulService2.order("userB", 20000);
Assertions.assertThat(userAPrice).isEqualTo(10000);
}
๊ฐ์ ์์ ํ ์ ์๊ฒ ํ๊ณ , ํ๋ ๋์ ์ ์๋ฐ์์ ๊ณต์ ๋์ง ์๋ ์ง์ญ๋ณ์๋ฅผ ์ด์ฉํ ๊ฒฐ๊ณผ์ด๋ค.
@Configuration๊ณผ ์ฑ๊ธํค
AppConfig์ memberService์ orderService๋ฅผ ํธ์ถํ๋ฉด, ์๋ก ๋ค๋ฅธ MemoryMemberRepository๊ฐ ์์ฑ๋๋ ๊ฒ์ฒ๋ผ ๋ณด์ธ๋ค.
public class ConfigurationSingletonTest {
@Test
@DisplayName("๋ ๊ฐ๊ฐ ๊ฐ์์ผ ํจ")
void configurationTest(){
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
MemberServiceImpl memberService = ac.getBean("memberService", MemberServiceImpl.class);
OrderServiceImpl orderService = ac.getBean("orderService", OrderServiceImpl.class);
MemberRepository memberRepository1 = memberService.getMemberRepository();
MemberRepository memberRepository2 = orderService.getMemberRepository();
System.out.println("memberRepository1 = " + memberRepository1);
System.out.println("memberRepository2 = " + memberRepository2);
Assertions.assertThat(memberRepository1).isSameAs(memberRepository2);
}
}
ํ์ธ ๊ฒฐ๊ณผ ๋์ ๊ฐ๋ค.
โ โ getMemberRepository๋ฅผ ์ธํฐํ์ด์ค์ธ MemberService์๋ ์ ์ธํ์ง ์์์ผ๋ฏ๋ก, ์ฌ์ฉํ์ง ๋ชปํ๋ค.
@Configuration๊ณผ ๋ฐ์ดํธ์ฝ๋ ์กฐ์์ ๋ง๋ฒ
@Test
void configurationDeep(){
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
AppConfig bean = ac.getBean(AppConfig.class);
System.out.println("bean.getClass() = " + bean.getClass());
}
AppConfig์์ ๋๋์ง ์๊ณ , $$SpringCGLIB~ ๋ผ๋ ๊ฒ์ด ๋ถ๋๋ค.
appConfig ๋น์๋ AppConfig๋ฅผ ์์๋ฐ์ ์กฐ์ํ AppConfig@CGLIB๊ฐ ๋ฑ๋ก๋์ด ์๋ค.
@Configuration์ด ์์ด๋ ์คํ๋ง ๋น์ผ๋ก ๋ฑ๋ก์ ๋์ง๋ง, ์ฑ๊ธํค์ด ๊นจ์ง๋ค. ๊ทธ๋์ ์ฐ๋ ๊ฒ์ด ์ข๋ค.
์ปดํฌ๋ํธ ์ค์บ
์ปดํฌ๋ํธ ์ค์บ๊ณผ ์์กด๊ด๊ณ ์๋ ์ฃผ์
์คํ๋ง ๋น์ ๋ฑ๋กํ ๋๋ @Bean์ด๋ XML์ <bean>์ ํปํด์ ๋ฑ๋กํ ์ ์๋ค.
ํ์ง๋ง, ๋ฑ๋กํ ์คํ๋ง ๋น์ด ๋ง์์ง๋ฉด, ์ผ์ผ์ด ๋ฑ๋กํ๊ธฐ๋ ๊ท์ฐฎ๊ณ , ์ค์ ์ ๋ณด๋ ์ปค์ง๊ณ , ๋๋ฝํ๋ ๋ฌธ์ ๋ ๋ฐ์ํ๋ค.
๊ทธ๋์ ์คํ๋ง์ ์ค์ ์ ๋ณด๊ฐ ์์ด๋ ์๋์ผ๋ก ์คํ๋ง ๋น์ผ๋ก ๋ฑ๋กํ๋ ์ปดํฌ๋ํธ ์ค์บ์ด๋ผ๋ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ค.
package hello.core;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
@Configuration
@ComponentScan(
excludeFilters = @ComponentScan.Filter(type= FilterType.ANNOTATION, classes = Configuration.class)
)
public class AutoAppConfig {
}
@Configuration -> ์ค์ ๊ด๋ จ ํด๋์ค๋ก ๋ง๋ ๋ค. @Configuration ๋ด๋ถ์ @Component๊ฐ ํฌํจ๋๋ฏ๋ก, ์ด๊ฒ์ด ๋ถ์ ํด๋์ค๋ ์คํ๋ง ๋น์ผ๋ก ๋ฑ๋ก๋๋ค.
@ComponentScan -> ํด๋์ค๊ฐ ComponentScan์ ๊ฐ๋ฅํ๊ฒ ํ๋๋ฐ, Filter๋ฅผ ์ด์ฉํ๋ฉด ๋ค๋ฅธ Configuration์ด ํฌํจ๋์ง ์๋๋ก ์ ์ธ์ํจ๋ค.
์คํ๋ง ๋น์ผ๋ก ๋ฑ๋กํ ํด๋์ค ์์ @Component๋ฅผ ๋ถ์ด๊ณ , ์์กด์ฑ ์ฃผ์ ์ด ํ์ํ ๊ณณ์ @Autowired๋ฅผ ๋ถ์ธ๋ค.
package hello.core.scan;
import hello.core.AutoAppConfig;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class AutoAppConfigTest {
@Test
void basicScan(){
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class);
MemberService memberService = ac.getBean(MemberService.class);
Assertions.assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
}
@ComponentScan ์ @Component ๊ฐ ๋ถ์ ๋ชจ๋ ํด๋์ค๋ฅผ ์คํ๋ง ๋น์ผ๋ก ๋ฑ๋กํ๋ค.
์ด๋ ์คํ๋ง ๋น์ ๊ธฐ๋ณธ ์ด๋ฆ์ ํด๋์ค๋ช
์ ์ฌ์ฉํ๋ ๋งจ ์๊ธ์๋ง ์๋ฌธ์๋ฅผ ์ฌ์ฉํ๋ค.
์๋ฅผ ๋ค์ด, ๊ธฐ๋ณธ์ ์ผ๋ก MemberServiceImpl ํด๋์ค๋ผ๋ฉด memberServiceImpl๋ก ์ ์ฅ๋๋ค.
๋ง์ฝ ์คํ๋ง ๋น์ ์ด๋ฆ์ ์ง์ ์ง์ ํ๊ณ ์ถ์ผ๋ฉด, @Component("memberService2")์ฒ๋ผ ์ด๋ฆ์ ๋ถ์ฌํ๋ฉด ๋๋ค.
์์ฑ์์ @Autowired๋ฅผ ์ง์ ํ๋ฉด, ์คํ๋ง ์ปจํ ์ด๋๊ฐ ๊ธฐ๋ณธ์ ์ผ๋ก ํ์ ์ด ๊ฐ์(์์ ํฌํจ) ์คํ๋ง ๋น์ ์ฐพ์์ ์ฃผ์ ํ๋ค.
ํ์ ์์น์ ๊ธฐ๋ณธ ์ค์บ ๋์
package hello.core;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
@Configuration
@ComponentScan(
basePackages = "hello.core.member",
excludeFilters = @ComponentScan.Filter(type= FilterType.ANNOTATION, classes = Configuration.class)
)
public class AutoAppConfig {
}
basePackages๋ ํ์ํ ํจํค์ง์ ์์ ์์น๋ฅผ ์ง์ ํ๋๋ฐ, ์ด ์์น๋ถํฐ ํ์ ํจํค์ง๋ฅผ ๋ชจ๋ ํ์ํ๋ค. ์์ ๊ฐ์ด ์ง์ ํด์ค๋ค๋ฉด, order์ discount ๊ด๋ จ ์คํ๋ง ๋น์ ์์ฑ๋์ง ์๋๋ค.
backPackages๋ฅผ ์ง์ ํ์ง ์์ผ๋ฉด, @ComponentScan์ด ๋ถ์ ์ค์ ์ ๋ณด ํด๋์ค๊ฐ ์๋ ํจํค์ง๊ฐ ์์ ์์น๊ฐ ๋๋ค.
โ ๊ด๋ก์ ์ผ๋ก, ์ค์ ์ ๋ณด ํด๋์ค๋ฅผ ํ๋ก์ ํธ ์ต์๋จ์ ๋๋ ๊ฒ์ด ์ข๋ค. @SpringBootApplication์ @ComponentScan์ ํฌํจํ๋ฏ๋ก, ์ด๋ฅผ ํฌํจํ๋ ํ์ผ์ ํ๋ก์ ํธ ์ต์๋จ์ ๋๋ค.
์ปดํฌ๋ํธ ์ค์บ ๊ธฐ๋ณธ ๋์
@Component : ์ปดํฌ๋ํธ ์ค์บ์ ๋์์ด๊ณ , ์คํ๋ง ๋น์ผ๋ก ๋ฑ๋ก๋จ.
@Controller : ์คํ๋ง MVC ์ปจํธ๋กค๋ฌ์์ ์ฌ์ฉ
@Service : ์คํ๋ง ๋น์ฆ๋์ค ๋ก์ง์์ ์ฌ์ฉ, ๋ฐ์ดํฐ ๊ณ์ธต์ ์์ธ๋ฅผ ์คํ๋ง ์์ธ๋ก ๋ณํํด์ค๋ค.(์์ง์ ๋ชจ๋ฅด๊ฒ ๋ค)
@Repository : ์คํ๋ง ๋ฐ์ดํฐ ์ ๊ทผ ๊ณ์ธต์์ ์ฌ์ฉ
@Configuration : ์คํ๋ง ์ค์ ์ ๋ณด์์ ์ฌ์ฉ, @Bean์ด ๋ถ์ด์์ด ๋ฑ๋ก๋๋ ์คํ๋ง ๋น์ด ์ฑ๊ธํค์ ์ ์งํ๋๋ก ์ถ๊ฐ ์ฒ๋ฆฌ
์ฐธ๊ณ ) ์ฌ์ค ์ ๋ ธํ ์ด์ ์๋ ์์๊ด๊ณ๋ผ๋ ๊ฒ์ด ์๋ค. ์ ๋ ธํ ์ด์ ์ด ํน์ ์ ๋ ธํ ์ด์ ์ ํฌํจํ๋ ๊ฒ์ ์ธ์ํ ์ ์๋ ๊ฒ์ ์๋ฐ ์ธ์ด๊ฐ ์ง์ํ๋ ๊ธฐ๋ฅ์ ์๋๊ณ , ์คํ๋ง์ด ์ง์ํ๋ ๊ธฐ๋ฅ์ด๋ค.
ํํฐ (์ค์๋ ๋ฎ์)
includeFilters : ์ปดํฌ๋ํธ ์ค์บ ๋์์ ์ถ๊ฐ๋ก ์ง์ , @Component๋ผ๊ณ ๋ถ์ ๊ฒ ์ด์ธ์ ์ถ๊ฐ๋ก ์ค์บํ ๊ฒ
excludeFilters : ์ปดํฌ๋ํธ ์ค์บ์์ ์ ์ธํ ๋์์ ์ง์
package hello.core.scan.filter;
import java.lang.annotation.*;
@Target({ElementType.TYPE}) // ํด๋์ค ๋ ๋ฒจ์ ๋ถ๋๋ค๋ ๋ป
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyIncludeComponent {
}
package hello.core.scan.filter;
import java.lang.annotation.*;
@Target({ElementType.TYPE}) // ํด๋์ค ๋ ๋ฒจ์ ๋ถ๋๋ค๋ ๋ป
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyExcludeComponent {
}
ํฌํจํ ์ ๋ ธํ ์ด์ ๊ณผ ํฌํจํ์ง ์์ ์ ๋ ธํ ์ด์ ์ ๊ฐ๊ฐ ํ๋์ฉ ๋ง๋ ๋ค.
package hello.core.scan.filter;
@MyIncludeComponent
public class beanA {
}
package hello.core.scan.filter;
@MyExcludeComponent
public class beanB {
}
package hello.core.scan.filter;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
public class ComponentFilterAppConfigTest {
@Test
void filterScan(){
ApplicationContext ac = new AnnotationConfigApplicationContext(ComponentFilterAppConfig.class);
BeanA beanA = ac.getBean(BeanA.class);
Assertions.assertThat(beanA).isNotNull();
org.junit.jupiter.api.Assertions.assertThrows(NoSuchBeanDefinitionException.class, ()->ac.getBean(BeanB.class));
}
@Configuration
@ComponentScan(
includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = MyIncludeComponent.class),
excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = MyExcludeComponent.class)
)
static class ComponentFilterAppConfig{
}
}
beanA๋ ์คํ๋ง ๋น์ผ๋ก ๋ฑ๋ก๋์ด getBeanํ ์ ์์ง๋ง, beanB๋ ๋ฑ๋ก๋ ์ ์์ผ๋ฏ๋ก getBeanํ ์ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ค.
FilterType Option
ANNOTATION : ๊ธฐ๋ณธ๊ฐ, ์ ๋
ธํ
์ด์
์ ์ธ์ํด์ ๋์ํ๋ค.
ex) org.example.SomeAnnotation
ASSIGNABLE_TYPE: ์ง์ ํ ํ์
๊ณผ ์์ ํ์
์ ์ธ์ํด์ ๋์ํ๋ค.
ex) org.example.SomeClass
ASPECTJ: AspectJ ํจํด ์ฌ์ฉ
ex) org.example..*Service+
REGEX: ์ ๊ท ํํ์
ex) org\.example\.Default.*
CUSTOM: `TypeFilter` ์ด๋ผ๋ ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํด์ ์ฒ๋ฆฌ
ex) org.example.MyTypeFilter
์ฐธ๊ณ ) @Component ๋ฉด ์ถฉ๋ถํ๊ธฐ ๋๋ฌธ์, includeFilters ๋ฅผ ์ฌ์ฉํ ์ผ์ ๊ฑฐ์ ์๋ค๊ณ ํ๋ค. excludeFilters๋ ์ฌ๋ฌ๊ฐ์ง ์ด์ ๋ก ๊ฐํน ์ฌ์ฉํ ๋๊ฐ ์์ง๋ง ๋ง์ง๋ ์๋ค.
ํนํ, ์ต๊ทผ ์คํ๋ง ๋ถํธ๋ ์ปดํฌ๋ํธ ์ค์บ์ ๊ธฐ๋ณธ์ผ๋ก ์ ๊ณตํ๋๋ฐ, ๊ฐ์ธ์ ์ผ๋ก๋ ์ต์
์ ๋ณ๊ฒฝํ๋ฉด์ ์ฌ์ฉํ๊ธฐ ๋ณด๋ค๋ ์คํ๋ง์ ๊ธฐ๋ณธ ์ค์ ์ ์ต๋ํ ๋ง์ถ์ด ์ฌ์ฉํ๋ ๊ฒ์ ๊ถ์ฅํ์ ๋ค๊ณ ํ๋ค.
์ค๋ณต ๋ฑ๋ก๊ณผ ์ถฉ๋
1. ์๋ ๋น ๋ฑ๋ก vs ์๋ ๋น ๋ฑ๋ก -> ์ถฉ๋ ์ค๋ฅ
2. ์๋ ๋น ๋ฑ๋ก vs ์๋ ๋น ๋ฑ๋ก -> ์๋ ๋น ์ฐ์ , ํ์ง๋ง ์ต๊ทผ ์คํ๋ง ๋ถํธ์์๋ ์๋ ๋น ๋ฑ๋ก๊ณผ ์๋ ๋น ๋ฑ๋ก์ด ์ถฉ๋๋๋ฉด ์ค๋ฅ๊ฐ ๋ฐ์ํ๋๋ก ๊ธฐ๋ณธ ๊ฐ์ ๋ฐ๊พธ์๋ค. (@SpringBootApplication์ผ๋ก ์คํํ ๋)
์์กด ๊ด๊ณ ์๋ ์ฃผ์
์์กด๊ด๊ณ ์ฃผ์ ๋ฐฉ๋ฒ
์์ฑ์ ์ฃผ์
์์ฑ์๋ฅผ ํตํด์ ์์กด ๊ด๊ณ๋ฅผ ์ฃผ์ ํ๋ ๋ฐฉ๋ฒ์ด๋ค.
โ ๋ถ๋ณโ ์ด ์ค์ํ๋ฐ, ์์ฑ์ ํธ์ถ์์ ์ ๋งค๊ฐ๋ณ์๋ค์ด ๋ฑ 1๋ฒ๋ง ํธ์ถ๋๋ ๊ฒ์ด ๋ณด์ฅ๋๋ค. ์ฆ, ์ฃผ์ ๋๋ ์์ ์ ์ ์ธํ๊ณ ๋ ๋ค๋ฅธ ๊ฒ์ผ๋ก ์ฃผ์ ํ ์ ์๋ค๋ ์๋ฏธ์ด๋ค.
final์ด ๋ถ์ผ๋ฉด ์์ฑ์ ์คํ์ด ์๋ฃ๋ ๋๊น์ง ๋ฌด์กฐ๊ฑด ์ง์ ๋์ด ์์ด์ผ ํ๊ณ , ์์ฑ์ ์คํ์ด ์๋ฃ๋๋ฉด ๋ณ๊ฒฝํ ์ ์๋ค. ์์์ ์ฃผ์ ์ ํตํด์ ์์ฑ์ ์คํ ๊ณผ์ ์์ ํ๋ผ๋ฏธํฐ ์์ฑ์๊ฐ ์ฃผ์ ๋์ด ์ง์ ๋๋ค. ๋ค๋ฅธ ์ฃผ์ ๋ฐฉ์๋ค์ ์์ฑ์ ์คํ ์ดํ์ ์ผ์ด๋๋ ๊ฒ์ด๋ฏ๋ก, final์ ์ด์ฉํ ์ ์๋ค. final๊ณผ ์์ฑ์ ์ฃผ์ ๋ฐฉ์์ ์ฌ์ฉํ์ฌ ๋๋ฝ์ ๋ฐฉ์งํ ์ ์๋ค๋ ์ฅ์ ์ด ์๋ค.
์์ฑ์๊ฐ ํ๋์ด๋ฉด, @Autowired๊ฐ ์๋ถ์ด๋ ์์กด ๊ด๊ณ ์ฃผ์ ์ด ์ผ์ด๋๋ค.
์ต์ ์ฒ๋ฆฌ
package hello.core.autowired;
import hello.core.member.Member;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.lang.Nullable;
import java.util.Optional;
public class AutowiredTest {
@Test
void AutowiredOption(){
ApplicationContext ac = new AnnotationConfigApplicationContext(AutowiredConfig.class);
}
@Configuration
static class AutowiredConfig {
@Autowired(required = false)
public void setNoBean1(Member member) {
System.out.println("setNoBean1 = " + member);
}
@Autowired
public void setNoBean2(@Nullable Member member){
System.out.println("setNoBean2 = " + member);
}
@Autowired
public void setNoBean3(Optional<Member> member){
System.out.println("setNoBean3 = " + member);
}
}
}
@Autowired์์ required ์ต์ ์ ๊ธฐ๋ณธ๊ฐ์ด true๋ก ๋์ด ์์ด์ ์ฃผ์ ํ ๊ฒ์ ๋ถ๋ฌ์ฌ ์ ์์ผ๋ฉด, ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ค. required ์ต์ ์ด false์ด๊ณ , ์ฃผ์ ํ ์ ์๋ ๊ฒ์ด ์์ผ๋ฉด ์์ ์ ๋ฉ์๋ ์์ฒด๊ฐ ํธ์ถ๋์ง ์๋๋ค.
@Nullable : ์๋ ์ฃผ์ ํ ๋์์ด ์์ผ๋ฉด, null์ด ์ ๋ ฅ๋๋ค.
Optional<> : ์๋ ์ฃผ์ ํ ๋์์ด ์์ผ๋ฉด Optional.empty ๊ฐ ์ ๋ ฅ๋๋ค.
๋กฌ๋ณต๊ณผ ์ต์ ํธ๋ ๋
๋กฌ๋ณต์ ์ฌ์ฉํ๊ธฐ ์ํด์ ํ์ํ ์ค์
//lombok ์ค์ ์ถ๊ฐ ์์
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
//lombok ์ค์ ์ถ๊ฐ ๋
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'
}
}
build.gradle์ ์ ์ฝ๋๋ฅผ ์ถ๊ฐํ๊ณ , plugin์์ lombok ์ค์นํ๊ณ , Annotation Processors์์ Enable annotation processing ์ฒดํฌํ๋ฉด ๋กฌ๋ณต์ ์ฌ์ฉํ ์ ์๋ค.
๋กฌ๋ณต์ ๊ฐ๋จํ ์ฌ์ฉ
package hello.core;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@ToString
public class HelloLombok {
private int age;
private String name;
public static void main(String[] args) {
HelloLombok helloLombok = new HelloLombok();
helloLombok.setName("์ ์งฑ๊ตฌ");
System.out.println("name = " + helloLombok.getName());
System.out.println(helloLombok);
}
}
@Getter, @Setter๋ฅผ ๋ถ์ด๋ ๊ฒ๋ง์ผ๋ก๋ Getter, Setter๊ฐ ๊ตฌํ๋๋ค.
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
@RequiredArgsConstructor๋ฅผ ํด๋์ค ์์ ๋ถ์ฌ์ฃผ๋ ๊ฒ๋ง์ผ๋ก ์ ์์ฑ์ ์ฝ๋๋ฅผ ๋์ฒดํ ์ ์๋ค. Ctrl + F12๋ฅผ ๋๋ฅด๋ฉด ์์ฑ์๋ฅผ ํ์ธํ ์ ์๋ค.
@Autowired ํ๋ ๋ช , @Qualifier, @Primary
์ฃผ์ ํ ์ ์๋ ๋น์ด 2๊ฐ ์ด์ -> ๋ฌธ์ ๋ฐ์!
FixedDiscountPolicy์ RateDiscountPolicy ๋ชจ๋ @Component๋ฅผ ์ถ๊ฐํ๋ฉด ์๋ฌ๊ฐ ๋ฐ์ํ๋ค.
@Autowired ๋งค์นญ
@Autowired๋ ํ์ ์ผ๋ก ๋งค์นญ์ ์๋ํ๊ณ , ์ด๋ ์ฌ๋ฌ ๋น์ด ์์ผ๋ฉด, ํ๋ ์ด๋ฆ, ํ๋ผ๋ฏธํฐ ์ด๋ฆ์ผ๋ก ๋น ์ด๋ฆ์ ์ถ๊ฐ ๋งค์นญ ์๋ํ๋ค.
@Autowired
private DiscountPolicy rateDiscountPolicy;
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy rateDiscountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = rateDiscountPolicy;
}
์๋ ํ๋(ํด๋์ค ๋ด๋ถ ๋ฉค๋ฒ) ์ด๋ฆ ๋งค์นญ, ์๋๋ ํ๋ผ๋ฏธํฐ ์ด๋ฆ ๋งค์นญ์ผ๋ก ๋ชจ๋ Test๋ฅผ ํต๊ณผํ๋ค. -> ์ด๋ฆ์ ๋ณด๊ณ ๋งค์นญํ๋ค.
@Qualifier
์ค์ ๋น ์ด๋ฆ์ ๋ณ๊ฒฝํ๋ ๊ฒ์ด ์๋๋ผ, ๋ณ๋ช ์ ์ถ๊ฐํ๋ค. @Qualifier ๋ผ๋ฆฌ ๋งค์นญํ๊ณ , ์์ผ๋ฉด ๋น์์ ์ฐพ๊ณ , ๊ทธ๊ฒ๋ ์๋๋ฉด ์์ธ๊ฐ ๋ฐ์ํ๋ค.
๊ฐ์ ๋ถ๋ชจ๋ฅผ ๊ฐ์ง๋ ํด๋์ค ์ค ํ๋์ @Qualifier("mainDiscountPolicy")๋ฅผ ๋ถ์ด๋ฉด, @Qualifier("mainDiscountPolicy")๊ฐ ๋ถ์ ์์ ํด๋์ค๋ฅผ ์ฌ์ฉํ๋ค.
package hello.core.discount;
import hello.core.member.Grade;
import hello.core.member.Member;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.stereotype.Component;
@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy{
double discountRate = 0.1;
@Override
public int discount(Member member, int price) {
if (member.getGrade() == Grade.VIP){
return (int)(price * discountRate);
}
else{
return 0;
}
}
}
@Component
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, @Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
...
}
@Qualifier๋ @Qualifier๋ฅผ ์ฐพ๋ ์ฉ๋๋ก๋ง ์ฌ์ฉํ์!!
@Primary
์ฐ์ ์์๋ฅผ ์ง์ ํ๋ค. @Component๋ฅผ ๋ค ๋ถ์ฌ๋๊ณ , @Primary๋ฅผ ๋ถ์ด๋ฉด ๋งค์ฐ ํธํ ๋ฏํ๋ค.
์ฐ์ ์์
@Primary๋ FixedDiscountPolicy์, @Qualifier๋ RateDiscountPolicy์ ๋ถ์ด์๊ณ , @Qualifier๋ฅผ ์ฌ์ฉํ๋ค๋ฉด, ์์ธํ ์ ๋ณด๋ฅผ ์ ๊ณตํ๋ @Qualifier๊ฐ ์ฐ์ ๋๋ค.
์ ๋ ธํ ์ด์ ์ง์ ๋ง๋ค๊ธฐ
@Qualifier("mainDiscountPolicy") ๋ด๋ถ์ ๋ฌธ์์ด์ ์ปดํ์ผ ์๊ฐ์ ๊ฒ์ฆ๋์ง ์๋๋ค. ๋ฐ๋ผ์, ๋ฌธ์์ด์ mainnDiscountPolicy๋ผ๊ณ ์๋ชป ์จ๋ ์ปดํ์ผ ์๊ฐ์ ์ ์ ์๋ค. ์ด๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด ์ ๋ ธํ ์ด์ ์ ์ง์ ๋ง๋ค์ด์ ๊ทธ ์์ @Qualifier๋ฅผ ๋ฃ์ด์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ด ์๋ค.
package hello.core.annotation;
import org.springframework.beans.factory.annotation.Qualifier;
import java.lang.annotation.*;
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Qualifier("mainDiscountPolicy")
public @interface MainDiscountPolicy {
}
์ ๋ ธํ ์ด์ ์ ๋ง๋ค๊ณ ,
package hello.core.order;
import hello.core.annotation.MainDiscountPolicy;
import hello.core.discount.DiscountPolicy;
import hello.core.discount.FixDiscountPolicy;
import hello.core.member.*;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, @MainDiscountPolicy DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
...
}
๋ง๋ ์ ๋ ธํ ์ด์ ์ ๋ถ์ธ๋ค.
์กฐํํ ๋น์ด ๋ชจ๋ ํ์ํ ๋, List, Map
ํด๋ผ์ด์ธํธ๊ฐ ํ ์ธ์ ์ข ๋ฅ๋ฅผ ์ ํํ ์ ์๋ ๊ฒฝ์ฐ์ DiscountService์์๋ Policy๋ค์ ๋ชจ๋ ๊ฐ์ ธ์์ผ ํ๋ค.
package hello.core.autowired;
import hello.core.AutoAppConfig;
import hello.core.discount.DiscountPolicy;
import hello.core.member.Grade;
import hello.core.member.Member;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import java.util.List;
import java.util.Map;
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, "userA", Grade.VIP);
int fixDiscountPrice = discountService.discount(member, 10000, "fixDiscountPolicy");
Assertions.assertThat(fixDiscountPrice).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);
}
}
}
AutoAppConfig์ DiscountService๊ฐ ์คํ๋ง ๋น์ ๋ฑ๋ก๋๋ค.
โ AutoAppConfig์ @ComponentScan์ด ์์ผ๋ฏ๋ก, DiscountPolicy๋ค์ ์คํ๋ง ๋น์ ๋ฑ๋กํ๋ค!
Map๊ณผ List์ ์๋์ผ๋ก ์ฃผ์ ์ด ์ผ์ด๋๊ณ , DiscountPolicy๋ค์ด ๋ด๊ฒจ ์๋ค.
discount์์ ์ ๋ฌ๋ฐ์ discountCode๋ฅผ key๋ก ์ฌ์ฉํ์ฌ ์ธ์คํด์ค๋ฅผ ์ป๋๋ค.
์๋, ์๋์ ์ฌ๋ฐ๋ฅธ ์ค๋ฌด ์ด์ ๊ธฐ์ค
ํธ๋ฆฌํ ์๋ ๊ธฐ๋ฅ(์ปดํฌ๋ํธ ์ค์บ๊ณผ ์๋ ์ฃผ์ )์ ๊ธฐ๋ณธ์ผ๋ก ์ฌ์ฉํ์!
ํธ๋ฆฌํ ์๋ ๊ธฐ๋ฅ์ ๊ธฐ๋ณธ์ผ๋ก ์ฌ์ฉํ์
์ง์ ๋ฑ๋กํ๋ ๊ธฐ์ ์ง์ ๊ฐ์ฒด(AOP, ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ..)๋ ์๋ ๋ฑ๋ก
๋คํ์ฑ์ ์ ๊ทน ํ์ฉํ๋ ๋น์ฆ๋์ค ๋ก์ง์ ์๋ ๋ฑ๋ก์ ๊ณ ๋ฏผํด๋ณด์
๋น ์๋ช ์ฃผ๊ธฐ ์ฝ๋ฐฑ
๋น ์๋ช ์ฃผ๊ธฐ ์ฝ๋ฐฑ ์์
๋ฐ์ดํฐ๋ฒ ์ด์ค ์ปค๋ฅ์
ํ์ด๋, ๋คํธ์ํฌ ์์ผ์ฒ๋ผ ์ ํ๋ฆฌ์ผ์ด์
์์ ์์ ์ ํ์ํ ์ฐ๊ฒฐ์ ๋ฏธ๋ฆฌ ํด๋๊ณ , ์ ํ๋ฆฌ์ผ์ด์
์ข
๋ฃ ์์ ์ ์ฐ๊ฒฐ์ ๋ชจ๋ ์ข
๋ฃํ๋ ์์
์ ์งํํ๋ ค๋ฉด, ๊ฐ์ฒด์ ์ด๊ธฐํ์ ์ข
๋ฃ ์์
์ด ํ์ํ๋ค.
package hello.core.lifecycle;
public class NetworkClient {
private String url;
public NetworkClient() {
System.out.println("์์ฑ์ ํธ์ถ, url = " + url);
connect();
call("์ด๊ธฐํ ์ฐ๊ฒฐ ๋ฉ์ธ์ง");
}
public void setUrl(String url) {
this.url = url;
}
public void connect(){
System.out.println("connect: " + url);
}
public void call(String message){
System.out.println("call: " + url + " message = " + message);
}
public void disconnect(){
System.out.println("close: " + url);
}
}
package hello.core.lifecycle;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
public class BeanLifeCycleTest {
@Test
public void lifeCycleTest(){
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(LifeCycleConfig.class);
NetworkClient client = ac.getBean(NetworkClient.class);
ac.close();
}
@Configuration
static class LifeCycleConfig {
@Bean
public NetworkClient networkClient(){
NetworkClient networkClient = new NetworkClient();
networkClient.setUrl("http://naver.com");
return networkClient;
}
}
}
์คํ๋ง ๋น์ผ๋ก ๋ฑ๋กํ๋ ๊ณผ์ ์์ ์์ฑ์๋ฅผ ํธ์ถํ ๋, url์ ์ ๋ฌํ์ง ์์์ผ๋ฏ๋ก, url์ ๋น์ฐํ null์ด ๋๋ค.
์คํ๋ง ๋น์ ์ด๋ฒคํธ ๋ผ์ดํ์ฌ์ดํด
์คํ๋ง ์ปจํ ์ด๋ ์์ฑ -> ์คํ๋ง ๋น ์์ฑ -> ์์กด๊ด๊ณ ์ฃผ์ (์์ฑ์ ์ฃผ์ ์ ์ด์ธ์ ์ฃผ์ )-> ์ด๊ธฐํ ์ฝ๋ฐฑ -> ์ฌ์ฉ -> ์๋ฉธ์ ์ฝ๋ฐฑ -> ์คํ๋ง ์ข ๋ฃ
์ด๊ธฐํ ์ฝ๋ฐฑ: ๋น์ด ์์ฑ๋๊ณ , ๋น์ ์์กด๊ด๊ณ ์ฃผ์
์ด ์๋ฃ๋ ํ ํธ์ถ
์๋ฉธ์ ์ฝ๋ฐฑ: ๋น์ด ์๋ฉธ๋๊ธฐ ์ง์ ์ ํธ์ถ
์ธํฐํ์ด์ค InitializingBean, DisposableBean (์ง๊ธ์ ๊ฑฐ์ ์ฌ์ฉํ์ง ์์)
package hello.core.lifecycle;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
public class NetworkClient implements InitializingBean, DisposableBean {
private String url;
public NetworkClient() {
System.out.println("์์ฑ์ ํธ์ถ, url = " + url);
}
public void setUrl(String url) {
this.url = url;
}
public void connect(){
System.out.println("connect: " + url);
}
public void call(String message){
System.out.println("call: " + url + " message = " + message);
}
public void disconnect(){
System.out.println("close: " + url);
}
@Override
public void afterPropertiesSet() throws Exception {
// ์์กด๊ด๊ณ ์ฃผ์
์ด ๋๋๋ฉด
connect();
call("์ด๊ธฐํ ์ฐ๊ฒฐ ๋ฉ์ธ์ง");
}
@Override
public void destroy() throws Exception {
disconnect();
}
}
afterPropertiesSet์ setter๊ฐ ๋๋ ํ์ ์คํ๋๋ค. destroy๋ ์คํ๋ง ์ปจํ ์ด๋์ ์ข ๋ฃ๊ฐ ํธ์ถ๋ ๋ ์คํ๋๋ค.
์ด๊ธฐํ, ์๋ฉธ ์ธํฐํ์ด์ค ๋จ์
์ด ์ธํฐํ์ด์ค๋ ์คํ๋ง ์ ์ฉ ์ธํฐํ์ด์ค๋ค. ํด๋น ์ฝ๋๊ฐ ์คํ๋ง ์ ์ฉ ์ธํฐํ์ด์ค์ ์์กดํ๋ค.
์ด๊ธฐํ, ์๋ฉธ ๋ฉ์๋์ ์ด๋ฆ์ ๋ณ๊ฒฝํ ์ ์๋ค.
๋ด๊ฐ ์ฝ๋๋ฅผ ๊ณ ์น ์ ์๋ ์ธ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์ ์ฉํ ์ ์๋ค.
package hello.core.lifecycle;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
public class NetworkClient{
private String url;
public NetworkClient() {
System.out.println("์์ฑ์ ํธ์ถ, url = " + url);
}
public void setUrl(String url) {
this.url = url;
}
public void connect(){
System.out.println("connect: " + url);
}
public void call(String message){
System.out.println("call: " + url + " message = " + message);
}
public void disconnect(){
System.out.println("close: " + url);
}
public void init(){
System.out.println("NetworkClient.init");
connect();
call("์ด๊ธฐํ ์ฐ๊ฒฐ ๋ฉ์ธ์ง");
}
public void close(){
System.out.println("NetworkClient.close");
disconnect();
}
}
@Configuration
static class LifeCycleConfig {
@Bean(initMethod = "init", destroyMethod = "close")
public NetworkClient networkClient(){
NetworkClient networkClient = new NetworkClient();
networkClient.setUrl("http://naver.com");
return networkClient;
}
}
์ค์ ์ ๋ณด ์ฌ์ฉ ํน์ง
- ๋ฉ์๋ ์ด๋ฆ์ ์์ ๋กญ๊ฒ ์ค ์ ์๋ค.
- ์คํ๋ง ๋น์ผ๋ก ๋ฑ๋ก๋๋ ํด๋์ค๊ฐ ์คํ๋ง ์ฝ๋์ ์์กดํ์ง ์๋๋ค.
- ์ฝ๋๊ฐ ์๋๋ผ ์ค์ ์ ๋ณด๋ฅผ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ ์ฝ๋๋ฅผ ๊ณ ์น ์ ์๋ ์ธ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์๋ ์ด๊ธฐํ, ์ข
๋ฃ ๋ฉ์๋๋ฅผ ์
์ฉํ ์ ์๋ค. - @Bean์ผ๋ก ๋ฑ๋กํ ๋๋ง ๋ฐ์ํ๋ค.
์ข ๋ฃ ๋ฉ์๋ ์ถ๋ก
@Bean์ destroyMethod ์์ฑ์๋ ์์ฃผ ํน๋ณํ ๊ธฐ๋ฅ์ด ์๋ค.
- ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ๋๋ถ๋ถ close , shutdown ์ด๋ผ๋ ์ด๋ฆ์ ์ข ๋ฃ ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋ค.
- @Bean์ destroyMethod ๋ ๊ธฐ๋ณธ๊ฐ์ด (inferred) (์ถ๋ก )์ผ๋ก ๋ฑ๋ก๋์ด ์๋ค.
- ์ด ์ถ๋ก ๊ธฐ๋ฅ์ close, shutdown ๋ผ๋ ์ด๋ฆ์ ๋ฉ์๋๋ฅผ ์๋์ผ๋ก ํธ์ถํด์ค๋ค. ์ด๋ฆ ๊ทธ๋๋ก ์ข ๋ฃ ๋ฉ์๋๋ฅผ ์ถ๋ก ํด์ ํธ์ถํด์ค๋ค. ๋ฐ๋ผ์ ์ง์ ์คํ๋ง ๋น์ผ๋ก ๋ฑ๋กํ๋ฉด ์ข ๋ฃ ๋ฉ์๋๋ ๋ฐ๋ก ์ ์ด์ฃผ์ง ์์๋ ์ ๋์ํ๋ค.
- ์ถ๋ก ๊ธฐ๋ฅ์ ์ฌ์ฉํ๊ธฐ ์ซ์ผ๋ฉด destroyMethod="" ์ฒ๋ผ ๋น ๊ณต๋ฐฑ์ ์ง์ ํ๋ฉด ๋๋ค.
์ ๋ ธํ ์ด์ @PostConstruct, @PreDestroy โ โ โ
@PostConstruct
public void init(){
System.out.println("NetworkClient.init");
connect();
call("์ด๊ธฐํ ์ฐ๊ฒฐ ๋ฉ์ธ์ง");
}
@PreDestroy
public void close(){
System.out.println("NetworkClient.close");
disconnect();
}
@PostConstruct, @PreDestroy ์ ๋ ธํ ์ด์ ํน์ง
- ์ต์ ์คํ๋ง์์ ๊ฐ์ฅ ๊ถ์ฅํ๋ ๋ฐฉ๋ฒ์ด๋ค.
- ์ ๋ ธํ ์ด์ ํ๋๋ง ๋ถ์ด๋ฉด ๋๋ฏ๋ก ๋งค์ฐ ํธ๋ฆฌํ๋ค.
- ์คํ๋ง์ ์ข ์์ ์ธ ๊ธฐ์ ์ด ์๋๋ผ ์๋ฐ ํ์ค์ด๋ค. ๋ฐ๋ผ์, ์คํ๋ง์ด ์๋ ๋ค๋ฅธ ์ปจํ ์ด๋์์๋ ์ฌ์ฉํ ์ ์๋ค.
- ์ปดํฌ๋ํธ ์ค์บ๊ณผ ์ ์ด์ธ๋ฆฐ๋ค.
- ์ ์ผํ ๋จ์ ์ ์ธ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์๋ ์ ์ฉํ์ง ๋ชปํ๋ค๋ ๊ฒ์ด๋ค. ์ธ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ด๊ธฐํ, ์ข
๋ฃ ํด์ผ ํ๋ฉด
@Bean์ ๊ธฐ๋ฅ์ ์ฌ์ฉํ์.
๋น ์ค์ฝํ
์คํ๋ง ๋น์ ๊ธฐ๋ณธ์ ์ผ๋ก ์คํ๋ง ์ปจํ ์ด๋์ ์์๊ณผ ํจ๊ป ์์ฑ๋์ด์ ์คํ๋ง ์ปจํ ์ด๋๊ฐ ์ข ๋ฃ๋ ๋๊น์ง ์ ์ง๋๋ค. ์ด๋ ์คํ๋ง ๋น์ด ๊ธฐ๋ณธ์ ์ผ๋ก ์ฑ๊ธํค ์ค์ฝํ๋ก ์์ฑ๋๊ธฐ ๋๋ฌธ์ด๋ค. ์ค์ฝํ๋ ๋น์ด ์คํ๋ง ์ปจํ ์ด๋์ ์กด์ฌํ ์ ์๋ ์ง์ ์๊ฐ์ด๋ค.
์ค์ฝํ ์ข ๋ฅ
- ์ฑ๊ธํค: ๊ธฐ๋ณธ ์ค์ฝํ, ์คํ๋ง ์ปจํ ์ด๋์ ์์๊ณผ ์ข ๋ฃ๊น์ง ์ ์ง๋๋ ๊ฐ์ฅ ๋์ ๋ฒ์์ ์ค์ฝํ์ด๋ค.
- ํ๋กํ ํ์ : ์คํ๋ง ์ปจํ ์ด๋๋ ํ๋กํ ํ์ ๋น์ ์์ฑ๊ณผ ์์กด๊ด๊ณ ์ฃผ์ ๊น์ง๋ง ๊ด์ฌํ๊ณ ๋๋ ๊ด๋ฆฌํ์ง ์๋ ๋งค์ฐ ์งง์ ๋ฒ์์ ์ค์ฝํ์ด๋ค.
- ์น ๊ด๋ จ ์ค์ฝํ
- request: ์น ์์ฒญ์ด ๋ค์ด์ค๊ณ ๋๊ฐ๋ ๊น์ง ์ ์ง๋๋ ์ค์ฝํ์ด๋ค.
- session: ์น ์ธ์ ์ด ์์ฑ๋๊ณ ์ข ๋ฃ๋ ๋ ๊น์ง ์ ์ง๋๋ ์ค์ฝํ์ด๋ค.
- application: ์น์ ์๋ธ๋ฆฟ ์ปจํ ์คํธ์ ๊ฐ์ ๋ฒ์๋ก ์ ์ง๋๋ ์ค์ฝํ์ด๋ค.
๋๋ถ๋ถ์ ์ฑ๊ธํค ๋น์ ์ฌ์ฉํ๋ค.
ํ๋กํ ํ์ ์ค์ฝํ
์ฑ๊ธํค ์ค์ฝํ์ ๋น์ ์กฐํํ๋ฉด ์คํ๋ง ์ปจํ ์ด๋๋ ํญ์ ๊ฐ์ ์ธ์คํด์ค ์คํ๋ง ๋น์ ๋ฐํํ๋ค. ๋ฐ๋ฉด์ ํ๋กํ ํ์ ์ค์ฝํ๋ฅผ ์คํ๋ง ์ปจํ ์ด๋์ ์กฐํํ๋ฉด ์คํ๋ง ์ปจํ ์ด๋๋ ํญ์ ์๋ก์ด ์ธ์คํด์ค๋ฅผ ์์ฑํด์ ๋ฐํํ๋ค.
ํด๋ผ์ด์ธํธ A๊ฐ ํ๋กํ ํ์ ๋น์ ์์ฒญํ๋ฉด, ์คํ๋ง ์ปจํ ์ด๋๋ ํ๋กํ ํ์ ๋น์ ์์กด๊ด๊ณ ์ฃผ์ ํ์ฌ ์์ฑํ๊ณ , ํด๋ผ์ด์ธํธ A์๊ฒ ์ ๊ณตํ๊ณ ๋ฒ๋ ค๋ฒ๋ฆฐ๋ค. ์ฆ ์ปจํ ์ด๋๊ฐ ๋ ์ด์ ๊ด๋ฆฌํ์ง ์๋๋ค. ๊ทธ๋์ ์คํ๋ง ์ปจํ ์ด๋๊ฐ ์คํ๋ง ๋น์ด ์ฌ๋ผ์ง ๋ ํธ์ถํ๋ @PreDestroy๊ฐ ์ ์ฉ๋์ง ์๋๋ค.
package hello.core.scope;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Scope;
public class PrototypeTest {
@Test
void prototypeBeanFind() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(prototypeBean.class);
prototypeBean prototypeBean1 = ac.getBean(prototypeBean.class);
prototypeBean prototypeBean2 = ac.getBean(prototypeBean.class);
System.out.println("prototypeBean1 = " + prototypeBean1);
System.out.println("prototypeBean2 = " + prototypeBean2);
Assertions.assertThat(prototypeBean1).isNotSameAs(prototypeBean2);
ac.close();
}
@Scope("prototype")
static class prototypeBean {
@PostConstruct
public void init(){
System.out.println("prototypeBean.init");
}
@PreDestroy
public void destroy(){
System.out.println("prototypeBean.destroy");
}
}
}
@PreDestroy๋ก ์ง์ ํ detroy๊ฐ ์คํ๋์ง ์์ ๊ฒ์ ๋ณผ ์ ์๋ค.
์ฑ๊ธํค ๋น๊ณผ ํ๋กํ ํ์ ๋น์ ํจ๊ป ์ฌ์ฉ์ ๋ฌธ์ ์
์ฑ๊ธํค ๋น์ด ํ๋กํ ํ์ ๋น์ ์ฃผ์ ๋ฐ์์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ, ๊ฒฐ๊ณผ์ ์ผ๋ก ๊ฐ์ ํ๋กํ ํ์ ์ ์ด์ฉํ๊ธฐ ๋๋ฌธ์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ค.
์ด๋ฅผ ํด๊ฒฐํ๋ ๊ฐ์ฅ ๊ฐ๋จํ ํด๊ฒฐ๋ฐฉ๋ฒ์ ์ฑ๊ธํค ๋น ์์์ ํ๋กํ ํ์ ๋น์ ์๋ก ์์ฒญํ๊ณ ๋ฐ์์ ์ฌ์ฉํ๋ ๊ฒ์ด๋ค.
package hello.core.scope;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Scope;
public class PrototypeProviderTest {
@Test
void providerTest() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(ClientBean.class, PrototypeBean.class);
ClientBean clientBean1 = ac.getBean(ClientBean.class);
int count1 = clientBean1.logic();
Assertions.assertThat(count1).isEqualTo(1);
ClientBean clientBean2 = ac.getBean(ClientBean.class);
int count2 = clientBean2.logic();
Assertions.assertThat(count2).isEqualTo(1);
}
static class ClientBean {
private ApplicationContext ac; // ์คํ๋ง ์ปจํ
์ด๋ ์์ฑ
public ClientBean(ApplicationContext ac) { // ์คํ๋ง ์ปจํ
์ด๋ ์ฃผ์
this.ac = ac;
}
public int logic() {
PrototypeBean prototypeBean = ac.getBean(PrototypeBean.class);
prototypeBean.addCount();
int count = prototypeBean.getCount();
return count;
}
}
@Scope("prototype")
static class PrototypeBean {
private int count = 0;
public void addCount() {
count++;
}
public int getCount() {
return count;
}
@PostConstruct
public void init() {
System.out.println("PrototypeBean.init " + this);
}
@PreDestroy
public void destroy() {
System.out.println("PrototypeBean.destroy");
}
}
}
ClientBean์ PrototypeBean์ ์์กด์ฑ ์ฃผ์ (DI)ํ์ง ์๊ณ , ClientBean ๋ด๋ถ์์ PrototypeBean์ ์ฐพ์์ ์ฌ์ฉํ๋ ๊ฒ์ ์์กด์ฑ ํ์, DL(Dependency Lookup)์ด๋ผ๊ณ ํ๋ค. ํ์ง๋ง, ๋ณต์กํด์ง๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ค.
ObjectProvider, ObjectFactory
static class ClientBean {
private ObjectProvider<PrototypeBean> prototypeBeanProvider;
public ClientBean(ObjectProvider<PrototypeBean> prototypeBeanProvider) {
this.prototypeBeanProvider = prototypeBeanProvider;
}
public int logic() {
PrototypeBean prototypeBean = prototypeBeanProvider.getObject();
prototypeBean.addCount();
int count = prototypeBean.getCount();
return count;
}
}
ObjectProvider๋ ์คํ๋ง ์ปจํ ์ด๋์์ ์คํ๋ง ๋น์ ์์ฒญํด์ ๋ฐ์์ฃผ๋ ๋๋ฆฌ์ ์ญํ ์ ํ๋ค. ํ๋กํ ํ์ ์ ์ํด์๋ง ์ฌ์ฉํ๋ ๊ฒ์ ์๋๋ค.
ObjectProvider<PrototypeBean>์ด๋ฏ๋ก, getObject()๋ฅผ ํ๊ฒ ๋๋ฉด ํ๋กํ ํ์ ์ ์ฐพ๋๋ฐ, ์คํ๋ง ์ปจํ ์ด๋๋ ํ๋กํ ํ์ ์ ๋ํ ์์ฒญ์ด ๋ค์ด์์ ๋, ํ๋กํ ํ์ ์ ์๋ก ์์ฑํด์ ์ ๊ณตํ๋ค.
ObjectProvider๋ ์คํ๋ง์ด ๋ง๋ค์ด์ ์ ๊ณตํด์ค๋ค. ๊น๋ํ๊ฒ DL ๊ธฐ๋ฅ๋ง ์ ๊ณตํ๋ค.
JSR330 Provider
@Scope("singleton")
static class ClientBean {
private Provider<PrototypeBean> prototypeBeanProvider;
public ClientBean(Provider<PrototypeBean> prototypeBeanProvider) {
this.prototypeBeanProvider = prototypeBeanProvider;
}
public int logic() {
PrototypeBean prototypeBean = prototypeBeanProvider.get();
prototypeBean.addCount();
int count = prototypeBean.getCount();
return count;
}
}
ObjectProvider ๋์ ์๋ฐ์์ ์ ๊ณตํ๋ Provider๊ฐ ์๋ค.
ํ๋กํ ํ์ ๋น์ ๋งค๋ฒ ์ฌ์ฉํ ๋๋ง๋ค ์์กด๊ด๊ณ ์ฃผ์ ์ด ์๋ฃ๋ ์๋ก์ด ๊ฐ์ฒด๊ฐ ํ์ํ๋ฉด ์ฌ์ฉํ๋ฉด ๋๋ค. ํ์ง๋ง, ํ๋กํ ํ์ ์์ฒด๋ฅผ ์ค๋ฌด์์๋ ๋ณ๋ก ์ฌ์ฉํ๋ ์ผ์ด ๋๋ฌผ๋ค๊ณ ํ๋ค.
ObjectProvider์ JSR330 Provider๋ ํ๋กํ ํ์ ๋ฟ๋ง ์๋๋ผ ๋ค๋ฅธ ๊ฒ์์๋ ์ฌ์ฉํ ์ ์๋ค.
์น ์ค์ฝํ
์น ์ค์ฝํ๋ ์น ํ๊ฒฝ์์๋ง ๋์ํ๋ค.
์น ์ค์ฝํ์ ์ข ๋ฅ
- request : HTTP ์์ฒญ ํ๋๊ฐ ๋ค์ด์ค๊ณ ๋๊ฐ ๋ ๊น์ง ์ ์ง๋๋ ์ค์ฝํ, ๊ฐ๊ฐ์ HTTP ์์ฒญ๋ง๋ค ๋ณ๋์ ๋น ์ธ์คํด์ค๊ฐ ์์ฑ๋๊ณ , ๊ด๋ฆฌ๋๋ค.
- session : HTTP Session๊ณผ ๋์ผํ ์๋ช ์ฃผ๊ธฐ๋ฅผ ๊ฐ์ง๋ ์ค์ฝํ
- application : ์๋ธ๋ฆฟ ์ปจํ ์คํธ( `ServletContext` )์ ๋์ผํ ์๋ช ์ฃผ๊ธฐ๋ฅผ ๊ฐ์ง๋ ์ค์ฝํ
- websocket : ์น ์์ผ๊ณผ ๋์ผํ ์๋ช ์ฃผ๊ธฐ๋ฅผ ๊ฐ์ง๋ ์ค์ฝํ
HTTP ์์ฒญ์ด ๊ฐ์ผ๋ฉด, ๊ฐ์ ์คํ๋ง ๋น์ ์ด์ฉํ๋ค!
request ์ค์ฝํ ์์ ๊ฐ๋ฐ
๋์์ ์ฌ๋ฌ HTTP ์์ฒญ์ด ์ค๋ฉด ์ ํํ ์ด๋ค ์์ฒญ์ด ๋จ๊ธด ๋ก๊ทธ์ธ์ง ๊ตฌ๋ถํ๊ธฐ ์ด๋ ต๋ค. ์ด๋ด ๋ request ์ค์ฝํ๋ฅผ ์ฌ์ฉํ๋ค.
๊ธฐ๋ํ๋ ๊ณตํต ํฌ๋ฉง: [UUID][requestURL] {message} -> requestURL์ ํธ์ถํ๋ฉด message ์คํ
UUID๋ฅผ ์ฌ์ฉํด์ HTTP ์์ฒญ์ ๊ตฌ๋ถํ์.
requestURL ์ ๋ณด๋ ์ถ๊ฐ๋ก ๋ฃ์ด์ ์ด๋ค URL์ ์์ฒญํด์ ๋จ์ ๋ก๊ทธ์ธ์ง ํ์ธํ์.
์ฐธ๊ณ ) UUID = Universally Unique Identifier
MyLogger
package hello.core.common;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.Setter;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import java.util.UUID;
@Component
@Scope("request")
public class MyLogger {
private String uuid;
private String requestURL;
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();
System.out.println("[" + uuid + "] request scope bean create:" + this);
}
@PreDestroy
public void close(){
System.out.println("[" + uuid + "] request scope bean close:" + this);
}
}
Request ์์ฒญ์ด ์ค๋ฉด ์์ฑ๋๋ ์คํ๋ง ๋น์ด๋ค.
Controller
package hello.core.web;
import hello.core.common.MyLogger;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequiredArgsConstructor
public class LogDemoController {
private final LogDemoService logDemoService;
private final MyLogger mylogger;
@RequestMapping("log-demo")
@ResponseBody
public String logDemo(HttpServletRequest request){
String requestURL = request.getRequestURL().toString();
mylogger.setRequestURL(requestURL);
mylogger.log("controller test");
logDemoService.logic("testId");
return "OK";
}
}
log-demo๋ก Request๊ฐ ์๋ค๋ฉด, HttpServletRequest๋ก ์ ๋ฌ๋ฐ๊ณ , mylogger์ ์์ฒญ๋ฐ์ URL์ ์ค์ ํ๋ค.
์ฐธ๊ณ ) HttpServletRequest : HTTP ํ๋กํ ์ฝ์ Request ์ ๋ณด๋ฅผ ์๋ธ๋ฆฟ์๊ฒ ์ ๋ฌํ๊ธฐ ์ํด ์ฌ์ฉํ๋ค.
Servlet : ์น ํ์ด์ง๋ฅผ ๋ง๋ค ๋ ์ฌ์ฉ๋๋ ์๋ฐ ๊ธฐ๋ฐ์ ์น ์ ํ๋ฆฌ์ผ์ด์ ํ๋ก๊ทธ๋๋ฐ ๊ธฐ์
Service
package hello.core.web;
import hello.core.common.MyLogger;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class LogDemoService {
private final MyLogger myLogger;
public void logic(String id){
myLogger.log("server id = " + id);
}
}
ํ์ง๋ง, ํ์ฌ ๋ฌธ์ ๋ Request๊ฐ ์๋๋ฐ, MyLogger์ scope๋ request์ด๋ฏ๋ก, Controller์ Service์ ์ฃผ์ ํ ์ ์์ด ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ค!!
ํด๊ฒฐ
@Controller
@RequiredArgsConstructor
public class LogDemoController {
...
@RequestMapping("log-demo")
@ResponseBody
public String logDemo(HttpServletRequest request){
MyLogger myLogger = myLoggerProvider.getObject();
...
}
}
@Service
@RequiredArgsConstructor
public class LogDemoService {
private final ObjectProvider<MyLogger> myLoggerProvider;
public void logic(String id) {
MyLogger myLogger = myLoggerProvider.getObject();
myLogger.log("service id = " + id);
}
}
ObjectProvider๋ฅผ ์ด์ฉํ์ฌ ํ์ํ ๋, ๋ฐ์์ ํด๊ฒฐํ๋ค.
์์ฒญ๋ง๋ค ๋ค๋ฅธ ์คํ๋ง ๋น์ด ์ ์ฉ๋๊ณ , ์ ๋์ํ๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
์ค์ฝํ์ ํ๋ก์
ObjectProvider๋ฅผ ์ด์ฉํ์ฌ ํด๊ฒฐํ์ง๋ง, ๋ ๊ฐ๋จํ ํ๊ณ ์ถ๋ค...
@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
@Setter
public class MyLogger {
}
๊ฐ์ง ํ๋ก์ ๊ฐ์ฒด๋ฅผ ์ฃผ์ ํด์ค๋ค. ๊ทธ๋ฆฌ๊ณ ์ค์ ๋ก ์ฌ์ฉํ๋ ์์ ์ ์ง์ง ๊ฐ์ฒด๋ฅผ ์ฐพ์์ ๋ฐ๊ฟ์น๊ธฐ ํ ํ ๊ธฐ๋ฅ์ ์ํํ๋ค.
๊ฐ์ง ํ๋ก์ ๊ฐ์ฒด๋ ์๋ณธ ํด๋์ค๋ฅผ ์์ ๋ฐ์์ ๋ง๋ค์ด์ก๊ธฐ ๋๋ฌธ์ ์ง์ง ๊ฐ์ฒด์ ๋์ผํ๊ฒ ์ฌ์ฉํ ์ ์๋ค.
Provider์ ํ๋ก์์ ๊ณตํต์ ์ ์ง์ง ๊ฐ์ฒด ์กฐํ๋ฅผ ๊ผญ ํ์ํ ์์ ๊น์ง ์ง์ฐ์ฒ๋ฆฌํ๋ค๋ ์ ์ด๋ค.
์ฑ๊ธํค์ด ์๋ ํน๋ณํ scope๋ ํ์ํ ๊ณณ์์๋ง ์ฌ์ฉํ์.
'๐๐ปโโ๏ธ ๊ธฐ๋ณธํ๋ จ > Spring' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Thymeleaf] ํ ์คํธ ๋ฆฌํฐ๋ด (0) | 2024.08.05 |
---|---|
[Thymeleaf] ํ ํ๋ฆฟ ์์น (0) | 2024.07.29 |
[Spring] ์คํ๋ง ๊ธฐ์ด (3) (0) | 2024.07.27 |
[Spring] ๊ฐ์ ํด๋์ค์ ๋น์ด ๋ ๊ฐ ์ด์์ผ ๋ (0) | 2024.07.25 |
[Spring] ์คํ๋ง ๊ธฐ์ด (1) (0) | 2024.07.22 |