AOP 1

์ž๋ฐ”, ์Šคํ”„๋ง ๊ฐœ๋ฐœ์ž๋“ค์˜ ์ข…์ฐฉ์—ญ์ด์ž, ์ฃผ๊ธฐ์ ์œผ๋กœ ๋‹ค์‹œ ๋Œ์•„์˜ค๋Š” ๊ทธ๊ณณ, <ํ† ๋น„์˜ ์Šคํ”„๋ง 3.1>์„ ๋“œ๋””์–ด ์ฝ๋Š”๋‹ค.

ํ•œ์ฐฝ ๊ฐœ๋ฐœ ๊ฑธ์Œ๋งˆ๋ฅผ ๋ง‰ ๋–ผ๊ณ  ์Šคํ”„๋ง์œผ๋กœ ์•„์žฅ์•„์žฅ ๊ธฐ์–ด๋‹ค๋‹ˆ๋ฉฐ CRUD๋ฅผ ํ•  ๋•Œ์ฆˆ์Œ, ํ† ๋น„์˜ ์Šคํ”„๋ง์„ ์ฐฌ์–‘ํ•˜๊ณ  ์žˆ๋Š” ๋งŽ์€ ๊ฐœ๋ฐœ์ž๋“ค์„ ๋ณด์•˜๊ณ , ์—ญ์‹œ ์ง€์‹์ด ๊นŠ์–ด์ง€๋ ค๋ฉด, ์ฑ…์„ ํ†ตํ•ด์„œ ์ง€์‹์„ ์ •๋ฆฌํ•˜๊ณ  ๊นŠ์ด๋ฅผ ๋‹ค์ง€๋Š” ์‹œ๊ฐ„์ด ํ•„์š”ํ•˜๊ฒ ๊ตฌ๋‚˜, ์ƒ๊ฐํ–ˆ๋‹ค. ํ•˜์ง€๋งŒ, ๊ฐœ๋ฐœ์„ ์ด์ œ ๋ง‰ ๋ฐฐ์šด ๊ทธ๋•Œ์˜ ๋‚˜์—๊ฒŒ ํ† ๋น„ ์ฑ…์€ ๋„ˆ๋ฌด๋‚˜๋„ ๋ฌด์‹œ๋ฌด์‹œํ–ˆ๊ณ , ํ† ๋น„์ฑ…์„ ์ฝ๋‹ค๊ฐ€ ๊ฐœ๋ฐœ์„ ํฌ๊ธฐํ•œ ์‚ฌ๋žŒ๋“ค์˜ ์ฆ์–ธ๋„ ์—ฌ๋Ÿฟ ์ฝ๊ณ  ๋‚˜๋‹ˆ, ์ด ์ฑ…์€ ๊ฐœ๋ฐœ์ž๋กœ์„œ ๋ ˆ๋ฒ ๋ฃจ์—…์ด ํ•„์š”ํ•  ๋•Œ์ฆˆ์Œ, ๋‹ค์‹œ ๊บผ๋‚ด๋ด์•ผ๊ฒ ๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ์ง€๊ธˆ์ด๋‹ค. ํ‡ด์‚ฌ ํ›„, ์ด์ง์„ ์ค€๋น„ํ•˜๋Š” ๋™์•ˆ, ์ง€์‹์˜ ๊นŠ์ด๋ฅผ ์ข€ ๋” ๋‹ค์ ธ๋†“๊ณ  ์‹ถ๋‹ค๋Š” ์ƒ๊ฐ์ด ๋“ค์—ˆ๊ณ , ์ง‘์•ž ๋„์„œ๊ด€์—์„œ (์•„๋ฌด๋„ ๋นŒ๋ ค๊ฐ€์ง€ ์•Š๋Š”) ํ† ๋น„๋‹˜์˜ ์ฑ…์„ ๋นŒ๋ ค์„œ ์ฝ๊ธฐ ์‹œ์ž‘ํ–ˆ๋‹ค. ์ฒ˜์Œ์—๋Š” ๊ฐ€๋ณ๊ฒŒ ์ฝ๊ธฐ ์‹œ์ž‘ํ–ˆ์ง€๋งŒ, ์ฝ์„ ์ˆ˜๋ก ์ธ์‚ฌ์ดํŠธ๊ฐ€ ์Œ“์—ฌ, ๊ธฐ๋กํ•˜๋ฉด์„œ ์ œ๋Œ€๋กœ ์ฝ๊ณ  ์‹ถ์–ด์กŒ๋‹ค. ๊ทธ๋ž˜์„œ ๋‚˜์˜ ์„ธ์ปจ ๋ธŒ๋ ˆ์ธ์ธ ์ด๊ณณ ๋ธ”๋กœ๊ทธ์— ์งง์€ ๊ธ€๋“ค๋กœ ๊ทธ ๋‚ด์šฉ์„ ์ ์–ด๋ณด๋ฉฐ, ํ† ๋น„๋‹˜์œผ๋กœ๋ถ€ํ„ฐ ์–ป์€ ์ธ์‚ฌ์ดํŠธ๋ฅผ ๋‚ด๊ฒƒ์œผ๋กœ ๋งŒ๋“ค์–ด๋ณด๊ณ ์ž ํ•œ๋‹ค.

์ง€๊ธˆ๊นŒ์ง€

  • ์„œ๋น„์Šค ์ถ”์ƒํ™” ๊ธฐ๋ฒ•์„ ํ†ตํ•ด์„œ ํŠธ๋žœ์žญ์…˜ ๊ธฐ์ˆ ์— ๋…๋ฆฝ์ ์œผ๋กœ ๊ฐœ์„ 

  • ํ•˜์ง€๋งŒ, ํŠธ๋žœ์žญ์…˜ ๊ฒฝ๊ณ„ ์„ค์ •์„ ์œ„ํ•ด UserService ๋‚ด๋ถ€์— ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง๋ณด๋‹ค ํŠธ๋žœ์žญ์…˜์„ ์œ„ํ•œ ์ฝ”๋“œ๊ฐ€ ๋” ๋งŽ์€ ๋ฒ”์œ„๋ฅผ ์ฐจ์ง€ํ•˜๋Š” ๊ฒƒ, ํŠธ๋žœ์žญ์…˜ ์ฝ”๋“œ์™€ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์ด ์„ž์ด๊ฒŒ ๋˜๋Š” ๊ฒƒ์ด ๋ถˆ๋งŒ์กฑ์Šค๋Ÿฝ๋‹ค.

์–ด๋–ป๊ฒŒ ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ์„๊นŒ?

  • UserService ์˜ ์‹œ์ž‘๋ถ€ํ„ฐ ๋๊นŒ์ง€ ํŠธ๋žœ์žญ์…˜ ๋ฒ”์œ„๊ฐ€ ์„ค์ •๋˜๊ธฐ ๋•Œ๋ฌธ์— ์ฝ”๋“œ๊ฐ€ ๊ทธ๊ณณ์— ์กด์žฌํ•˜๋Š” ๊ฒƒ์€ ๋ถˆ๊ฐ€ํ”ผํ•ด๋ณด์ž„.

  • ๊ทธ๋Ÿผ ๊ฒ‰์œผ๋กœ ๋ณด๊ธฐ์— ์•„์˜ˆ ์—†๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ด๊ฒŒ๋งŒ์ด๋ผ๋„ ํ•  ์ˆ˜๋Š” ์—†์„๊นŒ?

  • ํ˜„์žฌ ๊ตฌ์กฐ๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

    • Client : UserServiceTest โ†’ UserService

    • ๋ฌธ์ œ์  : ์ง์ ‘์ ์œผ๋กœ ํด๋ž˜์Šค์— ์˜์กดํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ•ํ•œ ๊ฒฐํ•ฉ์ƒํƒœ์ด๋‹ค.

  • ๋ฐ”๊พผ๋‹ค๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด ๋ฐ”๊ฟ”๋ณผ ์ˆ˜ ์žˆ์ง€ ์•Š๋‚˜?

    • ๋ฐ”๊พธ๋ ค๋Š” ์ตœ์ข… ๊ตฌ์กฐ

      • UserServiceTest - client

      • UserService - interface

        • UserServiceImpl - business logic

        • UserServiceTx - transaction

    • ์ด๋ ‡๊ฒŒ ๋ฐ”๊พธ๋ฉด ํด๋ผ์ด์–ธํŠธ ์ž…์žฅ์—์„œ ๋ณด๊ธฐ์—๋Š” ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ๋ฟ์ธ ์ฝ”๋“œ์— ํŠธ๋žœ์žญ์…˜์ด ์ ์šฉ๋œ ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ผ ๊ฒƒ์ด๋‹ค.

1์ฐจ ๊ฐœ์„ 

  • UserService

    • interface ํ™” ํ•œ๋‹ค.

  • UserServiceImpl

    • mail, upgradeLevels() ์ œ์™ธํ•˜๊ณ  ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์ด์™ธ์˜ ํŠธ๋žœ์žญ์…˜ ์ฝ”๋“œ๋Š” ๋ชจ๋‘ ์ œ๊ฑฐํ•œ๋‹ค.

  • UserServiceTx

    • ํŠธ๋žœ์žญ์…˜์—๋งŒ ์ง‘์ค‘ํ•˜๋ฏ€๋กœ, add(), upgradeLevels() ์—์„œ๋Š” DI ๋ฐ›์€ ๋‹ค๋ฅธ UserService ๊ตฌํ˜„์ฒด์˜ ๋งค์†Œ๋“œ๋ฅผ ํ˜ธ์ถœํ•ด์„œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์— ๊ด€์—ฌํ•˜์ง€ ์•Š๋Š”๋‹ค.

    • upgradeLevels() ์— transaction ๊ด€๋ จ ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค. ์ด๋•Œ ํŠธ๋žœ์žญ์…˜ ์ฝ”๋“œ ์‚ฌ์ด์— ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ํ˜ธ์ถœํ•˜๋Š” ๋ถ€๋ถ„์€ DI ๋ฐ›์€ ๋‹ค๋ฅธ UserService ๊ตฌํ˜„์ฒด๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค.

  • ์„ค์ •์ •๋ณด ๋ณ€๊ฒฝํ•˜๊ธฐ

    • ์ด ๊ตฌ์กฐ๋กœ ๊ฐœ์„ ํ•˜๊ฒŒ ๋˜๋ฉด ์ตœ์ข… ์˜์กด๊ด€๊ณ„๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

      • client : UserServiceTest โ†’ UserServiceTx โ†’ UserServiceImpl

    • ๋”ฐ๋ผ์„œ ์„ค์ •ํŒŒ์ผ๋„ ์•„๋ž˜์™€ ๊ฐ™์ด ๋ณ€๊ฒฝ

      • bean : userServiceTx โ†’ transactionManager, userServiceImpl

      • bean : userServiceImpl โ†’ userdao, mailSender

  • ํ…Œ์ŠคํŠธ ๋ณ€๊ฒฝํ•˜๊ธฐ

    • userService ๋ฅผ ์ง์ ‘ ์ƒ์„ฑํ›„ ์ฃผ์ž…ํ•˜๋˜ ๋ฐฉ์‹ โ†’ DI ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๋ณ€๊ฒฝ

    • @AutoWired ์˜ ๊ฒฝ์šฐ, ๊ฐ€์žฅ ๋จผ์ € ์ผ์น˜ํ•˜๋Š” ํƒ€์ž…์˜ ํด๋ž˜์Šค๋ฅผ ๊ฒ€์‚ฌํ•˜๊ณ , ์—†๋‹ค๋ฉด, bean ์„ค์ •์—์„œ ์ด๋ฆ„์ด ๊ฐ™์€ ํด๋ž˜์Šค๋ฅผ ๋งคํ•‘ํ•ด์ค€๋‹ค. ์ด ๊ฒฝ์šฐ, userService ๋Š” ์ธํ„ฐํŽ˜์ด์Šคํ™” ๋˜์–ด ํด๋ž˜์Šค๊ฐ€ ์‚ฌ๋ผ์กŒ์œผ๋‹ˆ, userService ๋ผ๋Š” ๋นˆ ์ด๋ฆ„์„ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” userServiceTx ๊ฐ€ ๋งคํ•‘๋  ๊ฒƒ์ด๋‹ค.

  • 1์ฐจ ๊ฐœ์„ ์œผ๋กœ ์ธํ•œ ์žฅ์ 

    • ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ๋‹ด๋‹นํ•˜๋Š” userServiceImpl ์—์„œ ํŠธ๋žœ์žญ์…˜๊ณผ ๊ฐ™์€ ๊ธฐ์ˆ ์ ์ธ ๋‚ด์šฉ์„ ์‹ ๊ฒฝ์“ฐ์ง€ ์•Š์•„๋„ ๋œ๋‹ค.

    • ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ๊ฐ€ ํ›จ์”ฌ ์‰ฌ์›Œ์กŒ๋‹ค.

public interface UserService {
   void add(User user);
   void upgradeLevels();
}

public class UserServiceImpl implements UserService { ... }

public class UserServiceTx implements UserService {
	UserService userService;
	PlatformTransactionManager transactionManager;

	public void setTransactionManager(PlatformTransactionManager transactionManager) {
		this.transactionManager = transactionManager;
	}

	public void setUserService(UserService userService) {
		this.userService = userService;
	}

	public void add(User user) {
		this.userService.add(user);
	}

	public void upgradeLevels() {
		TransactionStatus status = this.transactionManager.getTransaction(new DefaultTransactionDefinition());
		try {
			//์ฃผ์ž…๋ฐ›์€ ๋น„์ฆˆ๋‹ˆ์Šค๋กœ์ง ๊ตฌํ˜„์ฒด์ธ userService ๋‚ด ๋งค์†Œ๋“œ๋ฅผ ํ˜ธ์ถœ. 
			//์ด ํŠธ๋žœ์žญ์…˜ ํด๋ž˜์Šค๋Š” ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์— ์ „ํ˜€ ๊ด€์—ฌํ•˜์ง€ ์•Š๋Š”๋‹ค. 			
			userService.upgradeLevels();  
			this.transactionManager.commit(status);
		} catch (RuntimeException e) {
			this.transactionManager.rollback(status);
			throw e;
		}
	}
}

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="/test-applicationContext.xml")
public class UserServiceTest {

	@Test
	public void upgradeAllOrNothing() {
		TestUserService testUserService = new TestUserService(users.get(3).getId());
		testUserService.setUserDao(userDao);
		testUserService.setMailSender(mailSender);
		
		//ํด๋ผ์ด์–ธํŠธ์ธ Test -> ํ”„๋ก์‹œ์ธ UserServiceTx ํ˜ธ์ถœ -> ํ…Œ์ŠคํŠธ์šฉ ๊ตฌํ˜„์ฒด TestUserService ํ˜ธ์ถœ
		UserServiceTx txUserService = new UserServiceTx();
		txUserService.setTransactionManager(transactionManager);
		txUserService.setUserService(testUserService);
		 
		userDao.deleteAll();			  
		for(User user : users) userDao.add(user);
		
		try {
			txUserService.upgradeLevels();   
			fail("TestUserServiceException expected"); 
		}
		catch(TestUserServiceException e) { 
		}
		
		checkLevelUpgraded(users.get(1), false);
	}

}
<bean id="userService" class="springbook.user.service.UserServiceTx">
   <property name="transactionManager" ref="transactionManager" />
   <property name="userService" ref="userServiceImpl" />
</bean>

<bean id="userServiceImpl" class="springbook.user.service.UserServiceImpl">
   <property name="userDao" ref="userDao" />
   <property name="mailSender" ref="mailSender" />
</bean>

๊ณ ๋ฆฝ๋œ ๋‹จ์œ„ํ…Œ์ŠคํŠธ์˜ ํ•„์š”์„ฑ

  • ํ…Œ์ŠคํŠธ ๋‹จ์œ„๋Š” ์ž‘์„์ˆ˜๋ก ์ข‹๋‹ค. ํ•˜๋‚˜์˜ ํ…Œ์ŠคํŠธ์—์„œ๋Š” ํ•˜๋‚˜์˜ ๊ธฐ๋Šฅ๋งŒ์„ ์ฒดํฌํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์•ผํ•œ๋‹ค.

  • ๊ตฌ์กฐ๊ฐ€ ์—‰๋ง์ด๋ผ๋ฉด ํ•˜๋‚˜์˜ ๊ธฐ๋Šฅ์„ ํ…Œ์ŠคํŠธ ํ•˜๊ธฐ ์œ„ํ•ด, ์ˆ˜์‹ญ๊ฐœ์˜ ์˜์กด์„ฑ๊ณผ ์˜ํ–ฅ๋„๋ฅผ ํ™•์ธํ•ด์•ผํ•  ๊ฒƒ์ด๋‹ค.

    • ์˜ˆ๋ฅผ ๋“ค๋ฉด, UserService ์˜ ๋ ˆ๋ฒจ์—… ๊ธฐ๋Šฅ์ธ upgradeLevels() ๋ฅผ ํ…Œ์ŠคํŠธ ํ•˜๊ธฐ ์œ„ํ•ด์„œ UserDao, UserDaoJdbc, DataSource, DB, TransactionManager, JavaMailSenderImpl, JavaMail, MailServer ๊นŒ์ง€ ๋ชจ๋“  ๋ถ€๋ถ„์ด ์™„๋ฒฝํ•˜๊ฒŒ ์„ธํŒ…๋˜์–ด์žˆ์–ด์•ผ ํ•œ๋‹ค.

  • ์˜ค๋ธŒ์ ํŠธ ๊ฐ„ ์˜์กด๋„๋ฅผ ๋‚ฎ์ถ”๊ณ  ๊ณ ๋ฆฝ๋œ ํ…Œ์ŠคํŠธ๊ฐ€ ๊ฐ€๋Šฅํ•˜๋„๋ก ๊ตฌ์กฐ๋ฅผ ๋Š์ž„์—†์ด ๊ฐœ์„ ํ•ด์•ผํ•œ๋‹ค.

๊ฐœ์„ ํ•œ UserServiceTest ์˜ upgradeLevels()

  • getAll()์‹œ์—๋Š” ๋ฏธ๋ฆฌ ์ค€๋น„๋œ ์‚ฌ์šฉ์ž๋“ค์˜ ๋ชฉ๋ก์ธ ์Šคํ…์ด ํ•„์š”ํ•˜๊ณ  update() ์‹œ์—๋Š” ๋ชฉ ์˜ค๋ธŒ์ ํŠธ๋กœ์„œ ๋™์ž‘ํ•˜๋Š” ํ…Œ์ŠคํŠธ ๋Œ€์—ญ์ด ํ•„์š”ํ•˜๋‹ค. โ†’ MockUserDao ๋ฅผ ๋‚ด๋ถ€ ์Šคํƒœํ‹ฑ ํด๋ž˜์Šค๋กœ ์ƒ์„ฑํ•ด๋ณด์ž. (UserDaoImpl)

  • ์ด๋ ‡๊ฒŒ ๋˜๋ฉด UserServiceTest ๋Š” ์™ธ๋ถ€ UserDao ๋‚˜ MailSender ์™€ ๊ฐ™์€ ์™ธ๋ถ€ ๋นˆ์— ์˜์กดํ•  ํ•„์š”๊ฐ€ ์—†์œผ๋ฏ€๋กœ, @AutoWired ๋กœ ์ธํ•ด ์ฃผ์ž…๋ฐ›๋Š” ๊ฒƒ์ด ์•„๋‹Œ, ๋‚ด๋ถ€์—์„œ UserServiceImpl์„ ์ง์ ‘ ์ƒ์„ฑ ํ•ด์คŒ์œผ๋กœ์จ ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค. MockUserDao ์—ญ์‹œ ์ง์ ‘ DI ํ•ด์ค€๋‹ค.

  • ์žฅ์ 

    • ์ด์ œ DAO ๋‚˜ ์™ธ๋ถ€ ๋กœ์ง์— ์˜์กดํ•˜์ง€ ์•Š์€ UserService ๋งŒ์˜ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง๋งŒ ํšจ์œจ์ ์œผ๋กœ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ๋‹ค.

    • ๋”๊ตฐ๋‹ค๋‚˜ ํ…Œ์ŠคํŠธ ์‹œ๊ฐ„๋„ DB ๋ฅผ ๊ฐ”๋‹ค๊ฐ€ ์˜ค๋Š” ์‹œ๊ฐ„์ด ์ค„์–ด๋“ค๊ธฐ ๋•Œ๋ฌธ์— ์ด์ „๊ณผ ๋น„๊ตํ•˜๋ฉด 500๋ฐฐ์ด์ƒ ๋นจ๋ผ์ง„ ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค. ํ…Œ์ŠคํŠธ ๊ฐœ์ˆ˜๊ฐ€ ๋Š˜์–ด๋‚ ์ˆ˜๋ก, ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ํฌ๊ธฐ๊ฐ€ ์ปค์งˆ์ˆ˜๋ก ๊ทธ ํšจ๊ณผ๋Š” ์—„์ฒญ๋‚˜๋‹ค.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
    locations = {"/test-applicationContext.xml"}
)
public class UserServiceTest {

    @Test
    public void upgradeLevels() throws Exception {
        UserServiceImpl userServiceImpl = new UserServiceImpl();
        UserServiceTest.MockUserDao mockUserDao = new UserServiceTest.MockUserDao(this.users, (UserServiceTest.MockUserDao)null);
        userServiceImpl.setUserDao(mockUserDao);

        UserServiceTest.MockMailSender mockMailSender = new UserServiceTest.MockMailSender();
        userServiceImpl.setMailSender(mockMailSender);
        
        userServiceImpl.upgradeLevels();

        List<User> updated = mockUserDao.getUpdated();
        Assert.assertThat(updated.size(), CoreMatchers.is(2));

        this.checkUserAndLevel((User)updated.get(0), "joytouch", Level.SILVER);
        this.checkUserAndLevel((User)updated.get(1), "madnite1", Level.GOLD);

        List<String> request = mockMailSender.getRequests();
        Assert.assertThat(request.size(), CoreMatchers.is(2));
        Assert.assertThat((String)request.get(0), CoreMatchers.is(((User)this.users.get(1)).getEmail()));
        Assert.assertThat((String)request.get(1), CoreMatchers.is(((User)this.users.get(3)).getEmail()));
    }

    static class MockMailSender implements MailSender {
        private List<String> requests = new ArrayList();

        MockMailSender() {
        }

        public List<String> getRequests() {
            return this.requests;
        }

        public void send(SimpleMailMessage mailMessage) throws MailException {
            this.requests.add(mailMessage.getTo()[0]);
        }

        public void send(SimpleMailMessage[] mailMessage) throws MailException {
        }
    }

    static class MockUserDao implements UserDao {
        private List<User> users;
        private List<User> updated;

        private MockUserDao(List<User> users) {
            this.updated = new ArrayList();
            this.users = users;
        }

        public List<User> getUpdated() {
            return this.updated;
        }

        public List<User> getAll() {
            return this.users;
        }

        public void update(User user) {
            this.updated.add(user);
        }

        public void add(User user) {
            throw new UnsupportedOperationException();
        }

        public void deleteAll() {
            throw new UnsupportedOperationException();
        }

        public User get(String id) {
            throw new UnsupportedOperationException();
        }

        public int getCount() {
            throw new UnsupportedOperationException();
        }
    }

    static class TestUserService extends UserServiceImpl {
        private String id;

        private TestUserService(String id) {
            this.id = id;
        }

        protected void upgradeLevel(User user) {
            if (user.getId().equals(this.id)) {
                throw new UserServiceTest.TestUserServiceException();
            } else {
                super.upgradeLevel(user);
            }
        }
    }

    static class TestUserServiceException extends RuntimeException {
        TestUserServiceException() {
        }
    }

}

๋‹จ์œ„ํ…Œ์ŠคํŠธ์™€ ํ†ตํ•ฉํ…Œ์ŠคํŠธ

  • ๋‹จ์œ„ํ…Œ์ŠคํŠธ์˜ ๋ฒ”์œ„๋Š” ์ •ํ•˜๊ธฐ ๋‚˜๋ฆ„์ด์ง€๋งŒ, ๋ณดํ†ต์˜ ๊ฒฝ์šฐ ํ…Œ์ŠคํŠธ ๋Œ€์ƒ ํด๋ž˜์Šค๋ฅผ ๋ชฉ ์˜ค๋ธŒ์ ํŠธ ๋“ฑ์˜ ํ…Œ์ŠคํŠธ ๋Œ€์—ญ์„ ์ด์šฉํ•ด ์˜์กด ์˜ค๋ธŒ์ ํŠธ๋‚˜ ์™ธ๋ถ€์˜ ๋ฆฌ์†Œ์Šค๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋„๋ก ๊ณ ๋ฆฝ์‹œ์ผœ์„œ ํ…Œ์ŠคํŠธ ํ•˜๋Š” ๊ฒƒ์„ ๋งํ•œ๋‹ค.

  • ํ†ตํ•ฉํ…Œ์ŠคํŠธ์˜ ๊ฒฝ์šฐ, ๋‘ ๊ฐœ ์ด์ƒ์˜, ์„ฑ๊ฒฉ์ด๋‚˜ ๊ณ„์ธต์ด ๋‹ค๋ฅธ ์˜ค๋ธŒ์ ํŠธ๊ฐ€ ์—ฐ๋™ํ•˜๋„๋ก ๋งŒ๋“ค์–ด ํ…Œ์ŠคํŠธํ•˜๊ฑฐ๋‚˜, ์™ธ๋ถ€์˜ DB๋‚˜ ํŒŒ์ผ, ์„œ๋น„์Šค ๋“ฑ์˜ ๋ฆฌ์†Œ์Šค๊ฐ€ ์ฐธ์—ฌํ•˜๋Š” ํ…Œ์ŠคํŠธ๋ฅผ ๋งํ•œ๋‹ค.

    • ๋‘ ๊ฐœ ์ด์ƒ์˜ ๋‹จ์œ„๊ฐ€ ๊ฒฐํ•ฉํ•ด์„œ ๋™์ž‘ํ•˜๋ฉด์„œ ํ…Œ์ŠคํŠธ๊ฐ€ ์ˆ˜ํ–‰๋˜๋Š” ๊ฒƒ

  • ๊ฐ€์ด๋“œ๋ผ์ธ

    • ๋‹จ์œ„ํ…Œ์ŠคํŠธ๋ฅผ ๋จผ์ € ๊ณ ๋ คํ•œ๋‹ค.

    • ํ•˜๋‚˜์˜ ํด๋ž˜์Šค๋‚˜ ์„ฑ๊ฒฉ๊ณผ ๋ชฉ์ ์ด ๊ฐ™์€ ๊ธด๋ฐ€ํ•œ ํด๋ž˜์Šค๋ฅผ ๋ช‡ ๊ฐœ ๋ชจ์•„์„œ, ์™ธ๋ถ€์™€์˜ ์˜์กด๊ณผ๋‚˜๊ณ„๋ฅผ ๋ชจ๋‘ ์ฐจ๋‹จํ•˜๊ณ  ํ•„์š”์— ๋”ฐ๋ผ ์Šคํ…์ด๋‚˜ ๋ชฉ ์˜ค๋ธŒ์ ํŠธ ๋“ฑ์˜ ํ…Œ์ŠคํŠธ ๋Œ€์—ญ์„ ์ด์šฉํ•˜๋„๋ก ํ…Œ์ŠคํŠธ๋ฅผ ๋งŒ๋“ ๋‹ค.

    • ์™ธ๋ถ€ ๋ฆฌ์†Œ์Šค๋ฅผ ์‚ฌ์šฉํ•ด์•ผ๋งŒ ๊ฐ€๋Šฅํ•œ ํ…Œ์ŠคํŠธ๋งŒ ํ†ตํ•ฉํ…Œ์ŠคํŠธ๋กœ ๋งŒ๋“ ๋‹ค.

    • DAO ์™€ ๊ฐ™์ด DB ์— ์˜์กด์ ์ด๊ณ  ๋กœ์ง์ด ์—†๋Š” ์ฝ”๋“œ์˜ ๊ฒฝ์šฐ๋Š” ํ†ตํ•ฉํ…Œ์ŠคํŠธ๋กœ ๋งŒ๋“œ๋Š” ๊ฒƒ์ด ๋‚ซ๋‹ค. ์ด ํ…Œ์ŠคํŠธ๋ฅผ ์ž˜ ์ž‘์„ฑํ•ด๋‘์–ด์•ผ, ๋‚˜์ค‘์— DAO ๊ฐ€ ์“ฐ์ด๋Š” ๋‹ค๋ฅธ ๊ณณ์—์„œ๋„ ๋ชฉ์˜ค๋ธŒ์ ํŠธ๋ฅผ ํ†ตํ•ด DAO ์—ญํ• ์„ ๋Œ€์ฒดํ•  ์ˆ˜ ์žˆ๋‹ค. DAO ์—์„œ๋„ ์ž˜ ๋™์ž‘ํ•˜๋ฆฌ๋ผ๋Š” ๋ฏฟ์Œ์ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

    • ๋‹จ์œ„ํ…Œ์ŠคํŠธ๋ฅผ ๋ชจ๋‘ ํ†ต๊ณผํ–ˆ๋‹ค๊ณ  ํ•ด์„œ, ์„œ๋กœ ์—ฐ๊ด€๋œ ์ฝ”๋“œ๋“ค์ด ๋ฐ˜๋“œ์‹œ ์ž˜ ๋™์ž‘ํ•˜๋ฆฌ๋ผ๋Š” ๋ณด์žฅ์€ ์—†๋‹ค. ๊ทธ๋ž˜์„œ ์—ฌ๋Ÿฌ๊ฐœ์˜ ๋‹จ์œ„๊ฐ€ ์˜์กด๊ด€๊ณ„๋ฅผ ๊ฐ€์ง€๊ณ  ๋™์ž‘ํ•  ๋•Œ๋ฅผ ์œ„ํ•œ ํ†ตํ•ฉํ…Œ์ŠคํŠธ๋Š” ๋ฐ˜๋“œ์‹œ ํ•„์š”ํ•˜๋‹ค. ํ•˜์ง€๋งŒ ์ด ํ†ตํ•ฉํ…Œ์ŠคํŠธ ๋งˆ์ € ๋‹จ์œ„ํ…Œ์ŠคํŠธ๊ฐ€ ์ถฉ๋ถ„ํžˆ ์ž˜ ์ด๋ฃจ์–ด์กŒ๋‹ค๋ฉด, ์ž‘์„ฑํ•˜๊ธฐ๊ฐ€ ํ›จ์”ฌ ์ˆ˜์›”ํ•˜๋‹ค.

    • ์Šคํ”„๋ง ํ…Œ์ŠคํŠธ ์ปจํ…์ŠคํŠธ ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ์ด์šฉํ•˜๋Š” ํ…Œ์ŠคํŠธ ์—ญ์‹œ ํ†ตํ•ฉํ…Œ์ŠคํŠธ์ด๋‹ค. ๊ฐ€๋Šฅํ•˜๋ฉด ์Šคํ”„๋ง์˜ ์ง€์›์—†์ด ์ง์ ‘ ์ฝ”๋“œ ๋ ˆ๋ฒจ์˜ DI ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด์„œ ๋‹จ์œ„ํ…Œ์ŠคํŠธ ํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค. ํ•˜์ง€๋งŒ ์Šคํ”„๋ง ์„ค์ • ์ž์ฒด๋„ ํ…Œ์ŠคํŠธ ๋Œ€์ƒ์ด๊ณ  ๋ณด๋‹ค ์ถ”์ƒ์ ์ธ ๋ ˆ๋ฒจ์—์„œ ํ…Œ์ŠคํŠธ ํ•ด์•ผํ•  ๊ฒฝ์šฐ๋„ ์žˆ๋‹ค.

  • ํ…Œ์ŠคํŠธ ์ž˜ ์ž‘์„ฑํ•˜๋ฉด ์ฝ”๋“œ ํ’ˆ์งˆ์€ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋†’์•„์ง„๋‹ค. ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ๋•Œ, ์–ด๋–ป๊ฒŒ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ์„๊นŒ ๊ณ ๋ฏผํ•˜๋Š” ์Šต๊ด€์„ ๋“ค์ด๋„๋ก ํ•˜์ž.

๋ชฉ ํ”„๋ ˆ์ž„์›Œํฌ

  • ๋‹จ์œ„ํ…Œ์ŠคํŠธ๊ฐ€ ์ค‘์š”ํ•œ๊ฑด ์•Œ์ง€๋งŒ, ์ž‘์„ฑํ•˜๊ธฐ๊ฐ€ ๋„ˆ๋ฌด ๊ท€์ฐฎ๋‹ค. ์ค€๋น„๊ณผ์ •์ด ๋„ˆ๋ฌด ๊ธธ๊ณ  ๊ณ ๋‹จํ•˜๋‹ค. ํ•ด์ค˜์•ผํ•  ๊ฒƒ์ด ๋งŽ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

  • ๋‹คํ–‰ํžˆ๋„ ๋ชฉ ์˜ค๋ธŒ์ ํŠธ๋ฅผ ํŽธ๋ฆฌํ•˜๊ฒŒ ์ž‘์„ฑํ•˜๋„๋ก ๋„์™€์ฃผ๋Š” ๋‹ค์–‘ํ•œ ๋ชฉ ์˜ค๋ธŒ์ ํŠธ ์ง€์› ํ”„๋ ˆ์ž„์›Œํฌ๊ฐ€ ์กด์žฌํ•œ๋‹ค!

  • Mockito ํ”„๋ ˆ์ž„์›Œํฌ - ์‚ฌ์šฉํ•˜๊ธฐ ํŽธํ•จ

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
    locations = {"/test-applicationContext.xml"}
)
public class UserServiceTest {

    @Test
    public void mockUpgradeLevels() throws Exception {
        UserServiceImpl userServiceImpl = new UserServiceImpl();
        UserDao mockUserDao = (UserDao)Mockito.mock(UserDao.class);
        Mockito.when(mockUserDao.getAll()).thenReturn(this.users);
        userServiceImpl.setUserDao(mockUserDao);

        MailSender mockMailSender = (MailSender)Mockito.mock(MailSender.class);
        userServiceImpl.setMailSender(mockMailSender);

        userServiceImpl.upgradeLevels();

        ((UserDao)Mockito.verify(mockUserDao, Mockito.times(2))).update((User)Matchers.any(User.class));
        ((UserDao)Mockito.verify(mockUserDao, Mockito.times(2))).update((User)Matchers.any(User.class));
        ((UserDao)Mockito.verify(mockUserDao)).update((User)this.users.get(1));
        Assert.assertThat(((User)this.users.get(1)).getLevel(), CoreMatchers.is(Level.SILVER));

        ((UserDao)Mockito.verify(mockUserDao)).update((User)this.users.get(3));
        Assert.assertThat(((User)this.users.get(3)).getLevel(), CoreMatchers.is(Level.GOLD));

        ArgumentCaptor<SimpleMailMessage> mailMessageArg = ArgumentCaptor.forClass(SimpleMailMessage.class);
        ((MailSender)Mockito.verify(mockMailSender, Mockito.times(2))).send((SimpleMailMessage)mailMessageArg.capture());

        List<SimpleMailMessage> mailMessages = mailMessageArg.getAllValues();
        Assert.assertThat(((SimpleMailMessage)mailMessages.get(0)).getTo()[0], CoreMatchers.is(((User)this.users.get(1)).getEmail()));
        Assert.assertThat(((SimpleMailMessage)mailMessages.get(1)).getTo()[0], CoreMatchers.is(((User)this.users.get(3)).getEmail()));
    }

}

ํ”„๋ก์‹œ์™€ ํƒ€๊ฒŸ

  • ํ˜„์žฌ ๊ตฌ์กฐ๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

    • client : UserServiceTest โ†’ ํ•ต์‹ฌ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์œผ๋กœ ์ฐฉ๊ฐํ•˜๋ฉฐ UserService์˜ ๊ตฌํ˜„์ฒด ํ˜ธ์ถœ

    • UserServiceTx ๋Š” UserService๋ฅผ ๊ตฌํ˜„ํ•œ ์ƒํƒœ, ํŠธ๋žœ์žญ์…˜ ์ฝ”๋“œ ์ดํ›„ ํ•ต์‹ฌ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ์œ„ํ•ด ๋˜ ๋‹ค๋ฅธ UserService ๊ตฌํ˜„์ฒด์ธ UserServiceImpl ํ˜ธ์ถœ

    • UserServiceImpl ๋Š” ํ•ต์‹ฌ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์‹คํ–‰

  • ์ฆ‰, ํด๋ผ๋ฆฌ์–ธํŠธ โ†’ (ํ•ต์‹ฌ๊ธฐ๋Šฅ ์ธํ„ฐํŽ˜์ด์Šค + ๋ถ€๊ฐ€๊ธฐ๋Šฅ) ํ”„๋ก์‹œ โ†’ (ํ•ต์‹ฌ๊ธฐ๋Šฅ ์ธํ„ฐํŽ˜์ด์Šค + ํ•ต์‹ฌ๊ธฐ๋Šฅ) ํƒ€๊นƒ์˜ ํ˜•ํƒœ๋กœ ์š”์ฒญ์ด ์ „๋‹ฌ๋˜๋ฉฐ, ํด๋ผ์ด์–ธํŠธ๋Š” ๋งˆ์น˜ ์ž์‹ ์ด ํƒ€๊นƒ์˜ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•œ๋‹ค๊ณ  ์ฐฉ๊ฐํ•˜๊ณ , ํ”„๋ก์‹œ๋Š” ํด๋ผ์ด์–ธํŠธ์˜ ์š”์ฒญ์„ ๋ฐ›์•„ ์ตœ์ข…์ ์œผ๋กœ ํƒ€๊นƒ์—๊ฒŒ ๊ทธ ์š”์ฒญ์„ ์œ„์ž„ํ•œ๋‹ค. ํƒ€๊นƒ์€ ์œ„์ž„๋ฐ›์€ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•œ๋‹ค.

  • ํ”„๋ก์‹œ๋Š” ๋‘ ๊ฐ€์ง€๋กœ ๊ทธ ๋ชฉ์ ์— ๋”ฐ๋ผ ๊ตฌ๋ถ„๋œ๋‹ค.

    • ์ฒซ์งธ, ํด๋ผ์ด์–ธํŠธ๊ฐ€ ํƒ€๊นƒ์— ์ ‘๊ทผํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ œ์–ดํ•˜๊ธฐ ์œ„ํ•ด์„œ โ†’ ํ”„๋ก์‹œ ํŒจํ„ด

    • ๋‘˜์จฐ, ํƒ€๊นƒ์—๊ฒŒ ๋ถ€๊ฐ€์ ์ธ ๊ธฐ๋Šฅ์„ ๋ถ€์—ฌํ•˜๊ธฐ ์œ„ํ•ด์„œ โ†’ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ํŒจํ„ด

  • ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ํŒจํ„ด

    • ๋ชฉ์  : ํƒ€๊นƒ์—๊ฒŒ ๋ถ€๊ฐ€์ ์ธ ๊ธฐ๋Šฅ์„ ๋Ÿฐํƒ€์ž„์‹œ์— ๋‹ค์ด๋‚˜๋ฏนํ•˜๊ฒŒ ๋ถ€์—ฌํ•ด์ฃผ๊ธฐ ์œ„ํ•ด์„œ ํ”„๋ก์‹œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ํŒจํ„ด

    • ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ํ†ตํ•ด ์—ฐ๊ฒฐ๋˜๊ธฐ ๋•Œ๋ฌธ์— ์—ฌ๋Ÿฌ ๊ฐœ์˜ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๊ฐ€ ๊ฐ™์ด ์“ฐ์ผ ์ˆ˜ ์žˆ๋‹ค.

    • ์˜ˆ๋ฅผ ๋“ค๋ฉด ์•„๋ž˜์™€ ๊ฐ™์€ ์‹

      • ํด๋ผ๋ฆฌ์–ธํŠธ โ†’ ๋ผ์ธ๋„˜๋ฒ„ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ โ†’ ์ปฌ๋Ÿฌ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ โ†’ ํŽ˜์ด์ง• ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ โ†’ ํƒ€๊นƒ : ์†Œ์Šค์ฝ”๋“œ ์ถœ๋ ฅ ๊ธฐ๋Šฅ

    • ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ํ†ตํ•ด ์œ„์ž„ํ•˜๋Š” ๋ฐฉ์‹์ด๊ธฐ ๋•Œ๋ฌธ์— ํƒ€๊นƒ์ด ์–ด๋–ค ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋กœ๋ถ€ํ„ฐ ์—ฐ๊ฒฐ๋˜๋Š”์ง€ ์•Œ ์ˆ˜ ์—†๋‹ค. ๋Ÿฐํƒ€์ž„์‹œ์— ํด๋ผ์ด์–ธํŠธ๋ฅผ ํ†ตํ•ด์„œ ๋‹ค์ด๋‚˜๋ฏนํ•˜๊ฒŒ ์ƒ์„ฑ๋˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

  • ํ”„๋ก์‹œ ํŒจํ„ด

    • ๋ชฉ์  : ํƒ€๊นƒ์˜ ๊ธฐ๋Šฅ์„ ํ™•์žฅํ•˜๊ฑฐ๋‚˜ ์ถ”๊ฐ€ํ•˜์ง€ ์•Š์œผ๋ฉด์„œ ์ ‘๊ทผํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ œ์–ดํ•ด์ฃผ๋Š” ํ”„๋ก์‹œ๋ฅผ ์ด์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.

    • ์ฝ”๋“œ์—์„œ ์ž์‹ ์ด ๋งŒ๋“ค๊ฑฐ๋‚˜ ์ ‘๊ทผํ•  ํƒ€๊นƒ ํด๋ž˜์Šค ์ •๋ณด๋ฅผ ์•Œ๊ณ  ์žˆ๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ๋‹ค.

  • ๋‘ ๊ฐœ์˜ ํŒจํ„ด์„ ๊ฐ™์ด ์‚ฌ์šฉํ•˜๊ธฐ๋„ ํ•œ๋‹ค.

JDK์˜ ๋‹ค์ด๋‚ด๋ฏน ํ”„๋ก์‹œ

  • ๊ฐœ๋ฐœ์ž๋“ค์€ ํ”„๋ก์‹œ์˜ ์žฅ์ ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  ํ”„๋ก์‹œ๋ฅผ ์ž‘์„ฑํ•˜๊ธฐ์— ๊ท€์ฐฎ์•„ํ•œ๋‹ค.

  • ํ”„๋ก์‹œ ์ž‘์„ฑ์˜ ๋ฌธ์ œ์ 

    • ๋ถ€๊ฐ€ ๊ธฐ๋Šฅ์ด ํ•„์š”์—†๋Š” ๋งค์†Œ๋“œ๋„ ์ผ์ผํžˆ ๊ตฌํ˜„ํ•ด์„œ ํƒ€๊นƒ์œผ๋กœ ์œ„์ž„ํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ๋งŒ๋“ค์–ด์ค˜์•ผ ํ•œ๋‹ค.

      • ์˜ˆ) UserService ์˜ add()

    • ๋˜ํ•œ ์ธํ„ฐํŽ˜์ด์Šค์˜ ๋ฉ”์†Œ๋“œ์— ์ถ”๊ฐ€๋‚˜ ๋ณ€๊ฒฝ์ด ์ผ์–ด๋‚  ๋•Œ๋งˆ๋‹ค ํ•จ๊ป˜ ์ˆ˜์ •ํ•ด์ฃผ์–ด์•ผ ํ•œ๋‹ค.

    • ํ”„๋ก์‹œ ๋‚ด๋ถ€์—์„œ ์ค‘๋ณต์ฝ”๋“œ๊ฐ€ ๋งŽ์•„์งˆ ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ๋‹ค.

      • ์˜ˆ) transaction ์„ ํ•„์š”๋กœํ•˜๋Š” ๋งค์„œ๋“œ๊ฐ€ upgradeLevels() ์ด์™ธ์—, add(), delete() ๋“ฑ๋“ฑ ๋งŽ์•„์งˆ ๊ฒฝ์šฐ, ๋งค๋ฒˆ transaction ์ฝ”๋“œ๋กœ ๊ฐ์‹ธ๊ณ  ๊ทธ ๋‚ด๋ถ€์—์„œ ์š”์ฒญ์„ ์œ„์ž„ํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ์งœ์•ผํ•œ๋‹ค. ๊ฐ™์€ ๊ตฌ์กฐ์˜ ์ฝ”๋“œ๊ฐ€ ๋ฐ˜๋ณต๋  ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์•„์ง€๋Š” ๊ฒƒ์ด๋‹ค.

  • ์ด๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ• : JDK ๋‹ค์ด๋‚ด๋ฏน ํ”„๋ก์‹œ

  • JDK ๋‹ค์ด๋‚ด๋ฏน ํ”„๋ก์‹œ๋Š” ๋ฆฌํ”Œ๋ ‰์…˜ ๊ธฐ๋Šฅ์„ ์ด์šฉํ•ด์„œ ํ”„๋ก์‹œ๋ฅผ ๋งŒ๋“ค์–ด์ค€๋‹ค.

HelloUppercase ์˜ˆ์ œ๋ฅผ ํ†ตํ•œ ๋‹ค์ด๋‚ด๋ฏน ํ”„๋ก์‹œ ๋™์ž‘๊ณผ์ • ํ™•์ธ

  • ์ง์ ‘ ๋‹ค์ด๋‚ด๋ฏน ํ”„๋ก์‹œ๋ฅผ ๊ตฌํ˜„ํ–ˆ์„ ๋•Œ

    • ์ธํ„ฐํŽ˜์ด์Šค์˜ ๋ชจ๋“  ๋งค์†Œ๋“œ๋ฅผ ๊ตฌํ˜„ํ•ด ์œ„์ž„ํ•˜๋„๋ก ์ฝ”๋“œ๋ฅผ ๋งŒ๋“ค์–ด์•ผ ํ•˜๊ณ 

    • ๋ถ€๊ฐ€๊ธฐ๋Šฅ์ธ ๋ฆฌํ„ด๊ฐ’์„ ๋Œ€๋ฌธ์ž๋กœ ๋ฐ”๊พธ๋Š” ๊ธฐ๋Šฅ์ด ๋ชจ๋“  ๋ฉ”์†Œ๋“œ์— ์ค‘๋ณต๋˜์–ด ๋‚˜ํƒ€๋‚œ๋‹ค.

  • ๋‹ค์ด๋‚ด๋ฏน ํ”„๋ก์‹œ๋ฅผ ์ ์šฉํ–ˆ์„ ๋•Œ

    • ๋™์ž‘๋ฐฉ์‹

      • ํด๋ผ์ด์–ธํŠธ โ†’ ํ”„๋ก์‹œ ํŒฉํ† ๋ฆฌ์—๊ฒŒ ํ”„๋ก์‹œ๋ฅผ ์š”์ฒญํ•˜๊ณ , ๋‹ค์ด๋‚ด๋ฏน ํ”„๋ก์‹œ์˜ ๋งค์†Œ๋“œ๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค.

      • ๋‹ค์ด๋‚ด๋ฏน ํ”„๋ก์‹œ๋Š” InvocationHandler ๋ฅผ ํ†ตํ•ด ๋งค์†Œ๋“œ ์ฒ˜๋ฆฌ๋ฅผ ์š”์ฒญํ•œ๋‹ค.

      • InvocationHandler ์—์„œ๋Š” ์š”์ฒญ์„ ํƒ€๊นƒ์—๊ฒŒ ์œ„์ž„ํ•˜๊ณ  ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ๋‹ค์ด๋‚ด๋ฏน ํ”„๋ก์‹œ์—๊ฒŒ ์ „๋‹ฌํ•œ๋‹ค.

      • ๋‹ค์ด๋‚ด๋ฏน ํ”„๋ก์‹œ๋Š” ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ๋ฆฌํ„ด๊ฐ’์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

    • ๋‹ค์ด๋‚ด๋ฏน ํ”„๋ก์‹œ์˜ sayHello(), sayHi(), sayThankYou() โ†’ InvocationHandler ์˜ invoke(Method) โ†’HelloTarget์˜ sayHello(), sayHi(), sayThankYou()

...
public class DynamicProxyTest {
    public DynamicProxyTest() {
    }

    @Test
    public void simpleProxy() {
        DynamicProxyTest.Hello hello = new DynamicProxyTest.HelloTarget();
        Assert.assertThat(hello.sayHello("Toby"), CoreMatchers.is("Hello Toby"));
        Assert.assertThat(hello.sayHi("Toby"), CoreMatchers.is("Hi Toby"));
        Assert.assertThat(hello.sayThankYou("Toby"), CoreMatchers.is("Thank You Toby"));

        DynamicProxyTest.Hello proxiedHello = (DynamicProxyTest.Hello)Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{DynamicProxyTest.Hello.class}, new DynamicProxyTest.UppercaseHandler(new DynamicProxyTest.HelloTarget(), (DynamicProxyTest.UppercaseHandler)null));
        Assert.assertThat(proxiedHello.sayHello("Toby"), CoreMatchers.is("HELLO TOBY"));
        Assert.assertThat(proxiedHello.sayHi("Toby"), CoreMatchers.is("HI TOBY"));
        Assert.assertThat(proxiedHello.sayThankYou("Toby"), CoreMatchers.is("THANK YOU TOBY"));
    }

    interface Hello {
        String sayHello(String var1);

        String sayHi(String var1);

        String sayThankYou(String var1);
    }

    static class HelloTarget implements DynamicProxyTest.Hello {
        HelloTarget() {
        }

        public String sayHello(String name) {
            return "Hello " + name;
        }

        public String sayHi(String name) {
            return "Hi " + name;
        }

        public String sayThankYou(String name) {
            return "Thank You " + name;
        }
    }

    //์ง์ ‘ ๊ตฌํ˜„ํ–ˆ์„ ๋•Œ
    static class HelloUppercase implements DynamicProxyTest.Hello {
        DynamicProxyTest.Hello hello;

        public HelloUppercase(DynamicProxyTest.Hello hello) {
            this.hello = hello;
        }

        public String sayHello(String name) {
            return this.hello.sayHello(name).toUpperCase();
        }

        public String sayHi(String name) {
            return this.hello.sayHi(name).toUpperCase();
        }

        public String sayThankYou(String name) {
            return this.hello.sayThankYou(name).toUpperCase();
        }
    }

    //invokationHandler ๋กœ ๊ตฌํ˜„ํ–ˆ์„ ๋•Œ 
    static class UppercaseHandler implements InvocationHandler {
        Object target;

        private UppercaseHandler(Object target) {
            this.target = target;
        }

        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Object ret = method.invoke(this.target, args);
            return ret instanceof String && method.getName().startsWith("say") ? ((String)ret).toUpperCase() : ret;
        }
    }
}

๋‹ค์ด๋‚ด๋ฏน ํ”„๋ก์‹œ ์žฅ์ 

  • Hello ์ธํ„ฐํŽ˜์ด์Šค์˜ ๋ฉ”์†Œ๋“œ๊ฐ€ ์—„์ฒญ๋‚˜๊ฒŒ ๋Š˜์–ด๋‚ฌ์„ ๋•Œ, ๋งค๋ฒˆ ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•ด์ฃผ์ง€ ์•Š์•„๋„ ๋œ๋‹ค.

    • ์˜ˆ) 3๊ฐœ์—์„œ 30๊ฐœ๊ฐ€ ๋œ๋‹ค๋ฉดโ€ฆ? ๋งค๋ฒˆ ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผํ•œ๋‹ค. ๋‹ค์ด๋‚ด๋ฏน ํ”„๋ก์‹œ๊ฐ€ ๋งŒ๋“ค์–ด์งˆ ๋•Œ, ์ถ”๊ฐ€๋œ ๋ฉ”์†Œ๋“œ๊ฐ€ ์ž๋™์œผ๋กœ ํฌํ•จ๋˜๊ณ , ๋ถ€๊ฐ€๊ธฐ๋Šฅ์€ invoke() ๋ฉ”์†Œ๋“œ์—์„œ ์ด๋ฃจ์–ด์ง€๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

  • ๋ฆฌํ„ดํƒ€์ž…์ด ๋‹ค์–‘ํ•œ ๊ฒฝ์šฐ๋Š” ์–ด๋–ป๊ฒŒ ํ• ๊นŒ?

    • ๋ฆฌํ„ดํƒ€์ž…์„ ํ™•์ธํ•ด์„œ ์ŠคํŠธ๋ง์ธ ๊ฒฝ์šฐ๋งŒ ๋Œ€๋ฌธ์ž๋กœ ๋ฐ”๊ฟ”์ฃผ์–ด๋„ ๋œ๋‹ค.

  • ํƒ€๊นƒ์˜ ์ข…๋ฅ˜์— ์ƒ๊ด€์—†์ด ์ ์šฉ์ด ๊ฐ€๋Šฅํ•˜๋‹ค. UpperCaseHandler ๋ผ๊ณ  ์ œ๋„ค๋Ÿดํ•˜๊ฒŒ ๋ช…๋ช…ํ•œ ๋’ค, ์ด ํ”„๋ก์‹œ๊ฐ€ ํ•„์š”ํ•œ ๊ณณ์ด ์ž‡๋‹ค๋ฉด, ์–ด๋–ค ํƒ€๊นƒ์ด๋ผ๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

  • ๋ฆฌํ„ดํƒ€์ž… ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ๋ฉ”์†Œ๋“œ์˜ ์ด๋ฆ„์—๋„ ์กฐ๊ฑด์„ ๊ฑฐ๋Š” ๊ฒƒ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

    • ๋ฉ”์†Œ๋“œ ์ด๋ฆ„์ด say ๋กœ ์‹œ์ž‘ํ•˜๋Š” ๊ฒฝ์šฐ๋งŒ ์ ์šฉํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด, ์•„๋ž˜์˜ ์ฝ”๋“œ์—์„œ์™€ ๊ฐ™์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

      • return ret instanceof String && method.getName().startsWith("say") ? ((String)ret).toUpperCase() : ret;

๊ทธ๋ ‡๋‹ค๋ฉด, ๋ณธ๊ฒฉ์ ์œผ๋กœ UserServiceTx ์—๋„ ๋‹ค์ด๋‚ด๋ฏน ํ”„๋ก์‹œ๋ฅผ ์ ์šฉํ•ด๋ณด์ž!

  • TransactionHandler ์™€ ๋‹ค์ด๋‚ด๋ฏน ํ”„๋ก์‹œ๋ฅผ ์Šคํ”„๋ง์˜ DI ๋ฅผ ํ†ตํ•ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ๋งŒ๋“ค์–ด์•ผ ํ•œ๋‹ค.

  • ๋ฌธ์ œ์ 

    • ๋‹ค์ด๋‚ด๋ฏน ํ”„๋ก์‹œ๋Š” ์ผ๋ฐ˜์ ์ธ ์Šคํ”„๋ง์˜ ๋นˆ์œผ๋กœ๋Š” ๋“ฑ๋กํ•  ๋ฐฉ๋ฒ•์ด ์—†๋‹ค.

    • ์Šคํ”„๋ง ๋นˆ์€ ๊ธฐ๋ณธ์ ์œผ๋กœ ํด๋ž˜์Šค ์ด๋ฆ„๊ณผ ํ”„๋กœํผํ‹ฐ๋กœ ์ •์˜๋˜๋Š”๋ฐ, ๋ฆฌํ”Œ๋ ‰์…˜ API ๋ฅผ ์ด์šฉํ•˜์—ฌ ๋นˆ ์ •์˜์— ๋‚˜์˜ค๋Š” ํด๋ž˜์Šค ์ด๋ฆ„์„ ๊ฐ€์ง€๊ณ  ๋นˆ ์˜ค๋ธŒ์ ํŠธ๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.

    • ๋ฌธ์ œ๋Š” ๋‹ค์ด๋‚ด๋ฏน ํ”„๋ก์‹œ ์˜ค๋ธŒ์ ํŠธ์˜ ํด๋ž˜์Šค๋Š” ์–ด๋–ค ๊ฒƒ์ธ์ง€ ์•Œ ์ˆ˜๊ฐ€ ์—†๋‹ค. Proxy ํด๋ž˜์Šค์˜ newProxyInstance() ๋ผ๋Š” ์Šคํƒœํ‹ฑ ํŒฉํ† ๋ฆฌ ๋งค์†Œ๋“œ๋ฅผ ํ†ตํ•ด์„œ๋งŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

ํŒฉํ† ๋ฆฌ ๋นˆ์„ ์ด์šฉํ•œ ํ•ด๊ฒฐ๋ฐฉ๋ฒ•

  • ํด๋ž˜์Šค ์ •๋ณด๋ฅผ ๊ฐ€์ง€๊ณ  ๋””ํดํŠธ ์ƒ์„ฑ์ž๋ฅผ ํ†ตํ•ด ์˜ค๋ธŒ์ ํŠธ๋ฅผ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ• ์ด์™ธ์— ๋‹ค์–‘ํ•œ ๋ฐฉ๋ฒ•์ด ์žˆ๋‹ค. ๊ทธ ์ค‘ ํ•˜๋‚˜๊ฐ€ ํŒฉํ† ๋ฆฌ ๋นˆ์„ ์ด์šฉํ•œ ๋นˆ ์ƒ์„ฑ ๋ฐฉ๋ฒ•์ด๋‹ค.

  • ํŒฉํ† ๋ฆฌ ๋นˆ์„ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•๋„ ์—ฌ๋Ÿฌ๊ฐ€์ง€๊ฐ€ ์žˆ๋Š”๋ฐ ๊ฐ€์žฅ ๊ฐ„๋‹จํ•œ ๋ฐฉ๋ฒ•์€ FactoryBean ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

  • ์˜ˆ์ œ : private ์ƒ์„ฑ์ž๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” Message ํด๋ž˜์Šค

    • private ์ƒ์„ฑ์ž๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ํด๋ž˜์Šค๋Š” ์Šคํ”„๋ง์—์„œ ๋นˆ์œผ๋กœ ๋“ฑ๋กํ•  ์ˆ˜ ์—†๋‹ค. (์‚ฌ์‹ค ๋ฆฌํ”Œ๋ ‰์…˜์˜ ๊ถŒํ•œ์ด ๋ง‰๊ฐ•ํ•˜์—ฌ ๊ฐ€๋Šฅ์€ ํ•œ๋ฐ, ์ง์ ‘ ์ƒ์„ฑํ•˜์ง€๋ง๊ณ  ์Šคํƒœํ‹ฑ ๋ฉ”์†Œ๋“œ๋ฅผ ํ†ตํ•ด ์šฐํšŒํ•˜๋„๋ก ์ •์˜ํ•ด๋’€์œผ๋ฏ€๋กœ, ๊ทธ๋ฅผ ๋”ฐ๋ฅด๋Š” ๊ฒƒ์ด ๊ถŒ์žฅ๋œ๋‹ค.)

Message ์˜ˆ์ œ

...
public class Message {
    String text;

    private Message(String text) {
        this.text = text;
    }

    public String getText() {
        return this.text;
    }

    public static Message newMessage(String text) {
        return new Message(text);
    }
}

...
public class MessageFactoryBean implements FactoryBean<Message> {
    String text;

    public MessageFactoryBean() {
    }

    public void setText(String text) {
        this.text = text;
    }

    public Message getObject() throws Exception {
        return Message.newMessage(this.text);  //bean์—์„œ ์ƒ์„ฑํ•ด์ฃผ๋Š” proxy 
    }

    public Class<? extends Message> getObjectType() {
        return Message.class;
    }

    public boolean isSingleton() {
        return true;
    }
}

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class FactoryBeanTest {
    @Autowired
    ApplicationContext context;

    public FactoryBeanTest() {
    }

    @Test
    public void getMessageFromFactoryBean() {
        Object message = this.context.getBean("message");
        Assert.assertThat(message, CoreMatchers.is(Message.class));
        Assert.assertThat(((Message)message).getText(), CoreMatchers.is("Factory Bean"));
    }

    @Test
    public void getFactoryBean() throws Exception {
        Object factory = this.context.getBean("&message");
        Assert.assertThat(factory, CoreMatchers.is(MessageFactoryBean.class));
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans ...>
   <bean id="message" class="springbook.learningtest.spring.factorybean.MessageFactoryBean">
      <property name="text" value="Factory Bean" />
   </bean>

</beans>

UserService ์— ์ ์šฉ

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
    locations = {"/test-applicationContext.xml"}
)
public class UserServiceTest {

    @Test
    @DirtiesContext
    public void upgradeAllOrNothing() throws Exception {
        UserServiceTest.TestUserService testUserService = new UserServiceTest.TestUserService(((User)this.users.get(3)).getId(), (UserServiceTest.TestUserService)null);
        testUserService.setUserDao(this.userDao);
        testUserService.setMailSender(this.mailSender);
        
	//factoryBean ๋‚ด๋ถ€์—์„œ ๋งŒ๋“ค์–ด์ง€๋Š” proxy ๊ฐ์ฒด์™€ ๊ทธ์— ์ฃผ์ž…๋˜๋Š” UserServiceImpl์„ ํ…Œ์ŠคํŠธ์šฉ์œผ๋กœ ๋งŒ๋“  TestUserService ๋กœ ๊ต์ฒดํ•ด์ฃผ๊ธฐ ์œ„ํ•œ ์ž‘์—…
	TxProxyFactoryBean txProxyFactoryBean = (TxProxyFactoryBean)this.context.getBean("&userService", TxProxyFactoryBean.class);
        txProxyFactoryBean.setTarget(testUserService);
        UserService txUserService = (UserService)txProxyFactoryBean.getObject();

        this.userDao.deleteAll();
        Iterator var5 = this.users.iterator();

        while(var5.hasNext()) {
            User user = (User)var5.next();
            this.userDao.add(user);
        }

        try {
            txUserService.upgradeLevels();
            Assert.fail("TestUserServiceException expected");
        } catch (UserServiceTest.TestUserServiceException var6) {
        }

        this.checkLevelUpgraded((User)this.users.get(1), false);
    }
}

...
public class TxProxyFactoryBean implements FactoryBean<Object> {
    Object target;
    PlatformTransactionManager transactionManager;
    String pattern;
    Class<?> serviceInterface;

    public TxProxyFactoryBean() {
    }

    public void setTarget(Object target) {
        this.target = target;
    }

    public void setTransactionManager(PlatformTransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }

    public void setPattern(String pattern) {
        this.pattern = pattern;
    }

    public void setServiceInterface(Class<?> serviceInterface) {
        this.serviceInterface = serviceInterface;
    }

    public Object getObject() throws Exception {
        TransactionHandler txHandler = new TransactionHandler();
        txHandler.setTarget(this.target);
        txHandler.setTransactionManager(this.transactionManager);
        txHandler.setPattern(this.pattern);

        return Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{this.serviceInterface}, txHandler);
    }

    public Class<?> getObjectType() {
        return this.serviceInterface;
    }

    public boolean isSingleton() {
        return false;
    }
}

...
public class TransactionHandler implements InvocationHandler {
    Object target;
    PlatformTransactionManager transactionManager;
    String pattern;

    public TransactionHandler() {
    }

    public void setTarget(Object target) {
        this.target = target;
    }

    public void setTransactionManager(PlatformTransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }

    public void setPattern(String pattern) {
        this.pattern = pattern;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return method.getName().startsWith(this.pattern) ? this.invokeInTransaction(method, args) : method.invoke(this.target, args);
    }

    private Object invokeInTransaction(Method method, Object[] args) throws Throwable {
        TransactionStatus status = this.transactionManager.getTransaction(new DefaultTransactionDefinition());

        try {
            Object ret = method.invoke(this.target, args);
            this.transactionManager.commit(status);
            return ret;
        } catch (InvocationTargetException var5) {
            this.transactionManager.rollback(status);
            throw var5.getTargetException();
        }
    }
}
<bean id="userService" class="springbook.user.service.TxProxyFactoryBean">
   <property name="target" ref="userServiceImpl" />
   <property name="transactionManager" ref="transactionManager" />
   <property name="pattern" value="upgradeLevels" />
   <property name="serviceInterface" value="springbook.user.service.UserService" />
</bean>

<bean id="userServiceImpl" class="springbook.user.service.UserServiceImpl">
   <property name="userDao" ref="userDao" />
   <property name="mailSender" ref="mailSender" />
</bean>

ํ”„๋ก์‹œ ํŒฉํ† ๋ฆฌ ๋นˆ ๋ฐฉ์‹์˜ ์žฅ์ ๊ณผ ํ•œ๊ณ„

  • ์žฅ์ 

    • ํ”„๋ก์‹œ ํŒฉํ† ๋ฆฌ ๋นˆ์˜ ์žฌ์‚ฌ์šฉ์„ฑ : ์ด์ œ ํŠธ๋žœ์žญ์…˜์ด ํ•„์š”ํ•œ ๋ชจ๋“  ํด๋ž˜์Šค์— ์ฝ”๋“œ ๋ณ€๊ฒฝ์—†์ด ๋นˆ ์„ค์ •์ •๋ณด๋งŒ ์ถ”๊ฐ€ํ•จ์œผ๋กœ์จ ๋ถ€๊ฐ€๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค. (์œ„์˜ xml ์ฝ”๋“œ ์ฐธ๊ณ )

    • ํ”„๋ก์‹œ๋ฅผ ์ ์šฉํ•  ๋Œ€์ƒ์ด ๊ตฌํ˜„ํ•˜๊ณ  ์žˆ๋Š” ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ํ”„๋ก์‹œ ํด๋ž˜์Šค๋ฅผ ์ผ์ผํžˆ ๋งŒ๋“ค์ง€ ์•Š์•„๋„ ๋œ๋‹ค. invocationHandler ๋งŒ ์ด์šฉํ•˜๋ฉด ๋œ๋‹ค.

    • ๋ถ€๊ฐ€์ ์ธ ๊ธฐ๋Šฅ์ด ์—ฌ๋Ÿฌ ๋ฉ”์†Œ๋“œ์— ๋ฐ˜๋ณต์ ์œผ๋กœ ๋‚˜ํƒ€๋‚˜์ง€ ์•Š์•„๋„ ๋œ๋‹ค. invocationHandler ์—๋งŒ ์ ์šฉ์‹œ์ผœ์ฃผ๋ฉด ๋œ๋‹ค.

  • ํ•œ๊ณ„

    • ๋ฉ”์†Œ๋“œ ๋‹จ์œ„๋กœ ์ผ์–ด๋‚˜๋Š” ์ผ์ด๊ธฐ ๋•Œ๋ฌธ์— ํ•œ ๋ฒˆ์— ์—ฌ๋Ÿฌ๊ฐœ์˜ ํด๋ž˜์Šค์— ๊ณตํ†ต์ ์ธ ๋ถ€๊ฐ€๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๋Š” ์ผ์ด ๋ถˆ๊ฐ€ํ•˜๋‹ค.

    • ํ•˜๋‚˜์˜ ํƒ€๊นƒ์— ์—ฌ๋Ÿฌ๊ฐœ์˜ ๋ถ€๊ฐ€๊ธฐ๋Šฅ์„ ์ ์šฉํ•˜๋ ค๊ณ  ํ•  ๋•Œ๋„ ๊ฐ™์€ ๋ฌธ์ œ๊ฐ€ ์žˆ๋‹ค. ๋น„๋ก ์ฝ”๋“œ์˜ ์žฌ์‚ฌ์šฉ์„ฑ์€ ๋†’์˜€์ง€๋งŒ, ๋นˆ ์„ค์ •์ •๋ณด๊ฐ€ ์—„์ฒญ๋‚˜๊ฒŒ ๋Š˜์–ด๋‚˜๊ฒŒ ๋œ๋‹ค. ์‹ฌ์ง€์–ด๋Š” ์ด ์„ค์ •์ •๋ณด๋„ ํƒ€๊นƒ๊ณผ ์ธํ„ฐํŽ˜์ด์Šค๋งŒ ๋‹ค๋ฅธ ์ฑ„๋กœ ๊ฐ™์€ ๋ฐฉ์‹์˜ ์ฝ”๋“œ๊ฐ€ ์ค‘๋ณต๋œ๋‹ค.

  • ์–ด๋–ป๊ฒŒ ํ•ด๊ฒฐํ• ๊นŒ?

    • ์Šคํ”„๋ง์˜ ํ”„๋ก์‹œ ํŒฉํ† ๋ฆฌ ๋นˆ

๋‹ค์ŒํŽธ์— ๊ณ„์†!

Last updated