2022. 2. 6. 12:40ใBackend/๐ฟ Spring
๊น์ํ ๊ฐ์ฌ๋์ ์คํ๋ง ํต์ฌ ์๋ฆฌ - ๊ธฐ๋ณธํธ ์๊ฐ ํ ์ ๋ฆฌํ ํฌ์คํ ์ ๋๋ค.
1. ์น ์ดํ๋ฆฌ์ผ์ด์ ๊ณผ ์ฑ๊ธํค
๋ฌธ์ ์ : ๊ณ ๊ฐ ์์ฒญ์ด ์ฌ ๋๋ง๋ค ๊ฐ๋ณ ๊ฐ์ฒด๋ฅผ ๋ง๋ค์ด๋ธ๋ค. 1000๋ฒ์ ์์ฒญ์ด ์ค๋ฉด, 1000๊ฐ์ ์๋ก์ด ๊ฐ์ฒด๋ฅผ ์์ฑํ๋๋ฐ ์ด๋ ๋ฉ๋ชจ๋ฆฌ ๋ญ๋น๋ฅผ ์ด๋ํ๋ค.
ํด๊ฒฐ์ฑ : ํด๋น ๊ฐ์ฒด๊ฐ ๋ฑ 1๊ฐ๋ง ์์ฑ๋๊ณ , ๊ณต์ ํ๋๋ก ์ค๊ณํ๋ฉด ๋๋ค. => ์ฑ๊ธํค ํจํด
์ฑ๊ธํค ํจํด
JVM ๋ด์์, ํด๋์ค์ ์ธ์คํด์ค๊ฐ ๋ฑ 1๊ฐ๋ง ์์ฑ๋๋ ๊ฒ์ ๋ณด์ฅํ๋ ๋์์ธ ํจํด.
public class SingletonService {
// ์์ฑ์๋ฅผ private ์ผ๋ก ์ ํํ๊ธฐ์, ์ธ๋ถ ํด๋์ค์์ new ๋ฅผ ์ฌ์ฉํ์ฌ ์์ฑ์๋ฅผ ๋ง๋ค ์ ์๋ค.
private SingletonService() {
}
private static final SingletonService instance = new SingletonService();
// instance ๋ณ์์ ์ ๊ทผํ ์ ์๋ ๋ฐฉ๋ฒ์, getInstance() ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ด ์ ์ผํ๋ค. ์์ฑ์ ๋ง๋ค์ง ๋ชปํ๊ธฐ์.
public static SingletonService getInstance() {
return instance;
}
public void logic() {
System.out.println("์ฑ๊ธํค ๊ฐ์ฒด ๋ก์ง ํธ์ถ");
}
}
private ์ผ๋ก ์์ฑ์๋ฅผ ์ ํํ๊ธฐ์, ํน์๋ผ๋ ์ธ๋ถ์์ new ํค์๋๋ก ๊ฐ์ฒด ์ธ์คํด์ค๊ฐ ์์ฑ๋๋ ๊ฒ์ ๋ง๋๋ค. => ์ด๊ฑธ ์ปดํ์ผ ๋จ๊ณ์์ ์ก์ ์ค ์ ์๋ค. ( ์ปดํ์ผ ๋จ๊ณ์์ ์๋ฌ๋ฅผ ์ก์ ์ ์๋ค๋ ๊ฒ์, ์ ์ง์ฌ์ง ๊ฐ์ฒด์งํฅ ์์คํ ์ด๋ ๋ป )
// SingletonTest.java
public class SingletonTest {
@Test
@DisplayName("์ฑ๊ธํค ํจํด์ ์ ์ฉํ ๊ฐ์ฒด ์ฌ์ฉ") public void singletonServiceTest() {
SingletonService singletonService1 = SingletonService.getInstance();
SingletonService singletonService2 = SingletonService.getInstance();
System.out.println("singletonService1 = " + singletonService1);
System.out.println("singletonService2 = " + singletonService2);
assertThat(singletonService1).isSameAs(singletonService2);
}
}
์ฑ๊ธํค ํจํด ๋ฌธ์ ์
1. ์ฑ๊ธํค ํจํด์ ๊ตฌํํ๋๋ฐ ๋ง์ ์ฝ๋๋ฅผ ์์ฑํด์ผํ๋ค.
2. ์์กด๊ด๊ณ์ ํด๋ผ์ด์ธํธ๊ฐ ๊ตฌ์ฒด ํด๋์ค์ ์์กดํ๋ค. ( = DIP๋ฅผ ์๋ฐํ๋ค. ์ญํ ์๋ง ์์กดํด์ผํ๋๋ฐ ๊ตฌ์ฒด ํด๋์ค์ ์์กดํ์ผ๋ DIP ์๋ฐ)
ํด๋ผ์ด์ธํธ๊ฐ ๊ตฌ์ฒด ํด๋์ค์ ์์กดํด์ OCP ์์น์ ์๋ฐํ ๊ฐ๋ฅ์ฑ์ด ๋๋ค. ํ
์คํธํ๊ธฐ ์ด๋ ต๋ค.
3. ๋ด๋ถ ์์ฑ์ ๋ณ๊ฒฝํ๊ฑฐ๋ ์ด๊ธฐํ ํ๊ธฐ ์ด๋ ต๋ค.
4. private ์์ฑ์๋ก ์์ ํด๋์ค๋ฅผ ๋ง๋ค๊ธฐ ์ด๋ ต๋ค.
๊ฒฐ๋ก ์ ์ผ๋ก ์ ์ฐ์ฑ
์ด ๋จ์ด์ง๋ค.
์ํฐํจํด์ผ๋ก ๋ถ๋ฆฌ๊ธฐ๋ ํ๋ค.
ํด๊ฒฐ : ์คํ๋ง ์ปจํ ์ด๋๋ฅผ ์ฌ์ฉํ๋ฉด ์คํ๋ง ๋น ์์ฒด๊ฐ ์ฑ๊ธํค์ผ๋ก ๊ด๋ฆฌ๋๋ค.
2. ์ฑ๊ธํค ์ปจํ ์ด๋
์คํ๋ง ์ปจํ ์ด๋์์ ์ฑ๊ธํค ํจํด
์คํ๋ง ์ปจํ ์ด๋์ ๋น ๊ฐ์ฒด๋ฅผ ๋ฑ๋กํ๋ฉด, ์ ์ฝ๋์ฒ๋ผ ๋ณต์กํ๊ฒ ์ฑ๊ธํค ํจํด์ ์ ์ฉํ์ง ์์๋, ๊ฐ์ฒด ์ธ์คํด์ค๋ฅผ ์ฑ๊ธํค์ผ๋ก ๊ด๋ฆฌํ๋ค.
๋๋ถ์ ์ฑ๊ธํค์ ๋ชจ๋ ๋จ์ ์ ํด๊ฒฐํ๋ฉด์ ๊ฐ์ฒด๋ฅผ ์ฑ๊ธํค์ผ๋ก ์ ์งํ ์ ์๋ค.
// AppConfig.java
@Configuration
public class AppConfig {
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
...
}
// SingleTonTest.java
public class SingletonTest {
@Test
@DisplayName("์คํ๋ง ์ปจํ
์ด๋์ ์ฑ๊ธํค")
void springContainer() {
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
MemberService memberService1 = ac.getBean("memberService", MemberService.class);
MemberService memberService2 = ac.getBean("memberService", MemberService.class);
assertThat(memberService1).isSameAs(memberService2);
}
}
AnnotationConfigApplicationContext ๋ฅผ ํตํด ์คํ๋ง ์ปจํ ์ด๋ ๋ฐฉ์์ผ๋ก memberService ๋ผ๋ ์ด๋ฆ์ ๋น์ 2๋ฒ ์์ฑํ๊ณ , assertThat() ์ ๊ฒฐ๊ณผ ๋ ๊ฐ์ฒด๋ (memberService1, memberService2) ๊ฒฐ๊ตญ ๊ฐ๋ค๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
์ด์ ์ ์ฑ๊ธํค ํจํด์ ์ ์ฉํ๊ธฐ ์ํด์๋ private ์ ๊ทผ ์ ์ด์๋ฅผ ์ ์ธํ๋ ๋ฑ ๋ณ๋์ ์์ ์ด ๋ง์ด ํ์ํ์ง๋ง, ์คํ๋ง ์ปจํ ์ด๋๋ฅผ ์ฌ์ฉํ๋ฉด ๋ณ๋ ์์ ์์ด ์ฑ๊ธํค์ ์ ์งํ ์ ์๋ค.
์ฑ๊ธํค ํจํด์ ์ฃผ์์
์ฑ๊ธํค ๋ฐฉ์์ ์ฌ๋ฌ ํด๋ผ์ด์ธํธ๊ฐ ํ๋์ ๊ฐ์ ๊ฐ์ฒด ์ธ์คํด์ค๋ฅผ ๊ณต์ ํ๊ธฐ ๋๋ฌธ์, ์ํ๋ฅผ ์ ์ง(stateful) ํ๋๋ก ์ค๊ณํ๋ฉด ์๋๋ค. ์ฆ ์ฑ๊ธํค ๊ฐ์ฒด๋ stateless ํ๊ฒ ๊ด๋ฆฌํด์ผํ๋ค.
- ๋ฌด์ํ๋ก ์ค๊ณํ ๊ฒ
- ํน์ ํด๋ผ์ด์ธํธ๊ฐ ๊ฐ์ ๋ณ๊ฒฝํ ์ ์๋ ํ๋๊ฐ ์์ผ๋ฉด ์๋๋ค.
- ํ๋ ๋์ ์๋ฐ์์ ๊ณต์ ๋์ง ์๋ ์ง์ญ๋ณ์, ํ๋ผ๋ฏธํฐ, ThreadLocal ๋ฑ์ ์ฌ์ฉํด์ผํ๋ค.
- ์คํ๋ง ๋น์ํ๋์ ๊ณต์ ๊ฐ์ ์ค์ ํ๋ฉด ์ ๋ง ํฐ ์ฅ์ ๊ฐ ๋ฐ์ํ ์ ์๋ค.
์์๋ฅผ ํตํด ํ์ธํด๋ณด์.
์๋ StatefulService ์ order() ๋ฉ์๋ ๋ด๋ถ์์ price ๊ฐ์ ๋ณ๊ฒฝํ ์ ์๋
์ง์ ์ด ์๋ค.
order() ๋ฉ์๋์ ์ฑ ์์ ๊ทธ์ ์ด๋ค name ์ ์ํ์ ์ผ๋ง์ price ๋ก ์ฃผ๋ฌธํ์์ ์ถ๋ ฅํ๋ ๋ฐ์ ๋๋์ผํ๋ค.
ํ์ง๋ง this.price = price
์ฝ๋์์ ํด๋น ํ๋ ๊ฐ์ ๋ณ๊ฒฝํด๋ฒ๋ฆฌ๊ธฐ ๋๋ฌธ์ ๋ฌธ์ ์ ๋ฐ์ ๊ฐ๋ฅ์ฑ์ ์ง๋๊ฒ ๋๋ค.
// StatefulService
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;
}
}
๊ทธ๋ ๊ธฐ ๋๋ฌธ์ ํ ์คํธ์ฝ๋์์ ์ฑ๊ธํค ๊ฐ์ฒด๊ฐ ์๋ก ๋ค๋ฅธ price ๊ฐ์ ๋งค๊ฐ๋ณ์๋ก ๋ฐ๋๋ผ๋, ๋ ๊ฐ์ฒด๊ฐ ๋ฆฌํดํ๋ getPrice() ์ ๊ฐ์ ๊ฐ๊ฒ ๋๋ค.
// StatefulServiceTest
public class StatefulServiceTest {
@Test
void statefulServiceSingleton() {
ApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
StatefulService statefulService1 = ac.getBean(StatefulService.class);
StatefulService statefulService2 = ac.getBean(StatefulService.class);
// Thread A : A ์ฌ์ฉ์ 10000์ ์ฃผ๋ฌธ
statefulService1.order("userA", 10000);
// Thread B : B ์ฌ์ฉ์ 20000์ ์ฃผ๋ฌธ
statefulService2.order("userB", 20000);
int price = statefulService1.getPrice();
System.out.println("price = " + price); // 10000 ์ ๊ธฐ๋ํ์ง๋ง, 20000 ์ด ๋์จ๋ค.
assertThat(price).isNotEqualTo(10000);
assertThat(statefulService1).isSameAs(statefulService2);
}
}
๋ง์ง๋ง ์ค์ assertThat(statefulService1).isSameAs(statefulService2) ์ฝ๋๊ฐ ํต๊ณผ๋๋ฉด์ statefulService1 ๊ณผ statefulService2 ๋ ๊ฐ์ ๊ฐ์ฒด์์ ํ์ธํ ์ ์๋ค.
๋ฐ๋ผ์ StatefulService ์ฝ๋๋ ์๋์ฒ๋ผ stateless ํ๊ฒ ์ค๊ณํ๋ ๊ฒ์ด ์ณ๋ค.
public class StatefulService {
private int price;
public int order(String name, int price) {
System.out.println("name = " + name + " price = " + price);
// wrong
// this.price = price;
// right
return price; // ๊ฐ ํ ๋น์ด ์๋ ๊ณง๋ฐ๋ก ๋ฆฌํดํ๋๋ก ์์
}
public int getPrice() {
return price;
}
}
๊ฒฐ๋ก
๊ณต์ ํ๋
๋ ํญ์ ์กฐ์ฌํด์ผํ๋ฉฐ, ์คํ๋ง ๋น์ stateless
ํ๊ฒ ์ค๊ณํด์ผํ๋ค.
3. @Configuration ๊ณผ ์ฑ๊ธํค
@Configuration
public class AppConfig {
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
@Bean
public OrderService orderService() {
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
...
}
์ ์ฝ๋์์
memberService ๋น์ ๋ง๋๋ ์ฝ๋๋ memberRepository() ๋ฅผ ํธ์ถํ๋ค.
โก๏ธ ์ด ๋ฉ์๋๋ฅผ ํธ์ถํ๋ฉด new MemoryMemberRepository() ๋ฅผ ํธ์ถํ๋ค.
orderService ๋น์ ๋ง๋๋ ์ฝ๋๋ ๋์ผํ๊ฒ memberRepository() ๋ฅผ ํธ์ถํ๋ค.
โก๏ธ ์ด ๋ฉ์๋๋ฅผ ํธ์ถํ๋ฉด new MemoryMemberRepository() ๋ฅผ ํธ์ถํ๋ค.
๊ฒฐ๊ณผ์ ์ผ๋ก ๊ฐ๊ฐ ๋ค๋ฅธ 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 memberRepository = ac.getBean("memberRepository", MemberRepository.class);
// ์๋ก ๋ค๋ฅธ bean ๊ฐ์ฒด๋ก๋ถํฐ MemberRepository ๋ฅผ ํธ์ถ
MemberRepository memberRepository1 = memberService.getMemberRepository();
MemberRepository memberRepository2 = orderService.getMemberRepository();
System.out.println(memberRepository1 == memberRepository2 && memberRepository == memberRepository2);
// ๊ฒฐ๊ตญ ๋ชจ๋ ๊ฐ์ ์ฃผ์๊ฐ์ ๊ณต์ ํ๊ธฐ์, ๋์ผํ ๊ฐ์ฒด๋ก ๋ณผ ์ ์๋ค.
Assertions.assertThat(memberService.getMemberRepository()).isSameAs(memberRepository); // ๊ฐ๋ค
Assertions.assertThat(orderService.getMemberRepository()).isSameAs(memberRepository); // ๊ฐ๋ค
}
}
@Configuration
public class AppConfig {
@Bean
public MemberService memberService() {
System.out.println("call AppConfig.memberService");
return new MemberServiceImpl(memberRepository());
}
@Bean
public OrderService orderService() {
System.out.println("call AppConfig.orderService");
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
@Bean
public MemberRepository memberRepository() {
System.out.println("call AppConfig.memberRepository");
return new MemoryMemberRepository();
}
@Bean
public DiscountPolicy discountPolicy() {
return new RateDiscountPolicy();
}
}
์ถ๋ ฅ๋ฌธ์ ์ฝ์ ํ์ฌ, ๊ฐ๊ฐ ๊ฐ์ฒด๊ฐ ๋ช๋ฒ ์คํ๋๋์ง ํ์ธํด๋ณด์.
๊ฐ์
>>> memberService ์คํ๋ง ๋น ์์ฑํ ๋
"call AppConfig.memberService"
"call AppConfig.memberRepository"
>>> orderService ์คํ๋ง ๋น ์์ฑํ ๋
"call AppConfig.orderService"
"call AppConfig.memberRepository"
>>> memberRepository ์คํ๋ง ๋น ์์ฑํ ๋
"call AppConfig.memberRepository"
------------------------------------------
๋์ผํ memberRepository ๊ฐ ์ธ๋ฒ ์ถ๋ ฅ๋ ๊ฒ์ผ๋ก ์์
์ค์ ๊ฒ์ฆ
์์๊ณผ ๋ค๋ฅด๊ฒ memberRepository ๋ ํ๋ฒ๋ง
์ถ๋ ฅ๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
๊ทธ๋ ๋ค๋ฉด ์ ์ค๋ณต๋๋ ์ธ์คํด์ค
๋ ํ๋ฒ๋ง ํธ์ถ๋๋ ๊ฒ์ผ๊น?
์๋ํ๋ฉด @Configuration ์ด๋ ธํ ์ด์ ์ด ๋ถ์ผ๋ฉด CGLIB ์ด๋ผ๋ ๋ฐ์ดํธ์ฝ๋ ์กฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ๋์ํ๊ธฐ ๋๋ฌธ์ด๋ค.
์ฝ๋๋ก ๋ ์์ธํ ์์๋ณด์.
public class ConfigurationSingletonTest {
@Test
void configutationDeep() {
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
AppConfig bean = ac.getBean(AppConfig.class);
MemberServiceImpl memberService = ac.getBean("memberService", MemberServiceImpl.class);
System.out.println(" bean = " + memberService.getClass());
System.out.println(" bean = " + bean.getClass());
}
}
๋๋ฒ์งธ ์ถ๋ ฅ๋ฌธ์ ๊ฒฐ๊ณผ๋ฅผ ์์ํ๋ฉด bean = class hello.core.AppConfig ์ด ๋์์ผํ๋ค.
์๋ํ๋ฉด ์ฒซ๋ฒ์งธ ์ถ๋ ฅ๋ฌธ์ ๊ฒฐ๊ณผ๋ bean = class hello.core.member.MemberServiceImpl ์ด๊ธฐ ๋๋ฌธ์ด๋ค.
ํ์ง๋ง ์ค์ ์ฝ์์ ์ฐํ ๊ฒฐ๊ณผ๋ฅผ ํ์ธํ๋ฉด,
AppConfig ๋ค์ ๋ถ๊ฐ์ ์ธ ์ ๋ณด๊ฐ ๋ถ์ด ์ถ๋ ฅ๋๋ค. ๊ทธ๋ฆฌ๊ณ ํด๋น ๋ถ๊ฐ ์ ๋ณด๋ฅผ ์ ์ฝ์ด๋ณด๋ฉด GCLIB
์ด๋ผ๋ ๋ฌธ์๋ฅผ ํ์ธํ ์ ์๋ค.
๊ฒฐ๋ก ๋ถํฐ ์ด์ผ๊ธฐํ๋ฉด CGLIB (๋ผ์ด๋ธ๋ฌ๋ฆฌ)๊ฐ ๊ฐ์ ํ ๊ฒ์ด๊ณ , ์ ๊ฐ์ ํ๋ํ๋ฉด AppConfig ํด๋์ค๋ @Configuration ์ด๋ ธํ ์ด์ ์ด ๋ถ๊ธฐ ๋๋ฌธ์ด๋ค.
์์ํ ๊ฒฐ๊ณผ์ ๋ฌ๋ฆฌ ๋ถ๊ฐ ์ ๋ณด๊ฐ ๋ถ์๋ค๋ ๊ฒ์ ๊ฐ๋ฐ์๊ฐ ์ง์ ์์ฑํ ํด๋์ค๊ฐ ์๋, CGLIB ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ AppConfig ํด๋์ค๋ฅผ ์์
๋ฐ์ ์์์ ํด๋์ค
๋ฅผ ๋ง๋ค์ด ์คํ๋ง ๋น์ผ๋ก ๋ฐํํ ๊ฒ์ด๋ค.
๋ณธ๋ก ์ผ๋ก ๋์์ ์ฐ๋ฆฌ๋ memberRepository ๋ผ๋ ์ค๋ณต๋ ์ธ์คํด์ค๊ฐ ์ ํ๋ฒ๋ง ํธ์ถ๋๋์ง ์ถ๋ก ํ ์ ์๊ฒ ๋๋๋ฐ, ๋ฐ๋ก AppConfig@GCLIB ์ด๋ผ๋ CGLIB ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ํตํด ์์ฑ๋ ์์์ ํด๋์ค๊ฐ ๋ฐ์ดํธ์ฝ๋๋ฅผ ์กฐ์ํ์ฌ MemberRepository ์ ์ฑ๊ธํค
์ ๋ณด์ฅํด์ฃผ๊ธฐ ๋๋ฌธ์ด๋ค. (์ค์ ๋ก CGLIB ์ ๋ด๋ถ ๊ธฐ์ ์ ์ฌ์ฉํ๋ ๊ฒ์ ๋งค์ฐ ๋ณต์กํ๋ค๊ณ ํ๋ค.)
๊ฒฐ๋ก
@Bean ์ด๋
ธํ
์ด์
๋ง ์ฌ์ฉํ๋ฉด ์คํ๋ง ๋น์ผ๋ก ๋ฑ๋ก๋์ง๋ง, ์ฑ๊ธํค์ ๋ณด์ฅ๋ฐ์ง ๋ชปํ๋ค. ์ฑ๊ธํค์ด ๋ณด์ฅ๋๋ ์ด์ ๋ ์คํ๋ง ์ปจํ
์ด๋์ @Configuration
์ด๋
ธํ
์ด์
์ด ์๊ธฐ ๋๋ฌธ์ด๋ค. (๊ทธ๋ฌ๋ฏ๋ก ์คํ๋ง ์ค์ ์ ๋ณด ํด๋์ค์ ํญ์ @Configuration ์ ๋ถ์ผ ๊ฒ)
'Backend > ๐ฟ Spring' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Springboot] log ์ ์ฉ ์ log symbol: variable log ์๋ฌ (0) | 2022.03.04 |
---|---|
์ปดํฌ๋ํธ ์ค์บ (0) | 2022.02.07 |
์คํ๋ง ์ปจํ ์ด๋์ ์คํ๋ง ๋น (0) | 2022.02.04 |
[Spring] Service, ServiceImpl ์ ๊ด๊ณ (feat. OCP) (1) | 2022.02.02 |
๊ฐ์ฒด ์งํฅ ์ค๊ณ์ ์คํ๋ง (2) | 2022.01.26 |