🌟 AOP를 이해하려면 필연적인 등장배경과 스프링이 그것을 도입한 이유, 그 적용을 통해 얻을 수 있는 장점이 무엇인지에 대한 충분한 이해가 필요
🌟 AOP의 적용 대상 : 선언적 트랜잭션 기능
🌟 서비스 추상화를 통해 많은 근본적인 문제를 해결했던 트랜잭션 경계설정 기능을 AOP를 이용해 더욱 세련되고 깔끔한 방식으로 바꿔보자
🚨 UserService를 깔끔하게 다듬어왔지만 찜찜함. 트랜잭션 경계설정을 위해 넣은 코드 때문. 스프링이 제공하는 깔끔한 트랜잭션 인터페이스를 썼음에도 비즈니스 로직이 주인이어야 할 메소드 안에 이름도 긴 트랜잭션 코드가 너무 많은 자리를 차지하고 있음. 그래도 트랜잭션의 경계를 분명히 비즈니스 로직의 전후에 설정돼야 함...
public void upgradeLevels() throws Exception {
TransactionStatus status = this.transactionManager
.getTransaction(new DefaultTransactionDefinition());
try {
List<User> users = userDao.getAll();
for (User user : users) {
if(canUpgradeLevel(user)) {
upgradeLevel(user);
}
}
this.transactionManager.commit(status);
} catch (Exception e) {
this.transactionManager.rollback(status);
throw e;
}
}
public void upgradeLevels() throws Exception {
TransactionStatus status = this.transactionManager
.getTransaction(new DefaultTransactionDefinition());
try {
upgradeLevelsInternal(); //바뀐 코드
this.transactionManager.commit(status);
} catch (Exception e) {
this.transactionManager.rollback(status);
throw e;
}
}
private void upgradeLevelsInternal() {
List<User> users = userDao.getAll();
for (User user : users) {
if(canUpgradeLevel(user)) {
upgradeLevel(user);
}
}
}
🌟 트랜잭션 코드를 클래스 밖으로 뽑아내기(서로 직접적인 정보를 주고 받는 것이 없으므로 UserService에서 빼내기)
public interface UserService {
void add(User user);
void upgradeLevels();
}
//트랜잭션 코드를 제거한 UserService 구현 클래스
package springbook.user.service;
...
public class UserServiceImpl implements UserService {
UserDao userDao;
MailSender mailSender;
public void upgradeLevels() {
List<User> users = userDao.getAll();
for (User user : users) {
if (canUpgradeLevel(user)) {
upgradeLevel(user);
}
}
}
...
//위임 기능을 가진 UserServiceTx 클래스
package springbook.user.service;
...
public class UserServiceTx implements UserService {
//UserService를 구현한 다른 오브젝트를 DI받는다.
UserService userService;
public void setUserService(UserService userService) {
this.userService = userService;
}
//DI받은 UserService 오브젝트에 모든 기능을 위임
public void add(User user) {
userService.add(user);
}
public void upgradeLevels() {
userService.upgradeLevels();
}
}
//트랜잭션이 적용된 UserServiceTx
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.upgradeLevels();
this.transactionManager.commit(status);
} catch (RuntimeException e) {
this.transactionManager.rollback(status);
throw e;
}
}
}
이제 남은 것은 설정파일을 수정하는 부분
클라이언트가 UserService라는 인터페이스를 통해 사용자 관리 로직을 이용하려고 할 때 먼저 트랜잭션을 담당하는 오브젝트가 사용돼서 트랜잭션에 관련된 작업을 하고, 실제 사용자 관리 로직을 담은 오브젝트가 이후에 호출돼서 비즈니스 로직에 관련된 작업을 수행하도록 만듦
@Test public void upgradeLevels() throws Exception {
...
MockMailSender mockMailSender = new MockMailSender();
userServiceImpl.setMailSender(mockMailSender);
//분리된 테스트 기능이 포함되도록 수정한 upgradeAllOrNothing()
@Test public void upgradeAllOrNothing() throws Exception {
TestUserService testUserService = new TestUserService(users.get(3).getId());
testUserService.setUserDao(userDao);
testUserService.setMailSender(mailSender);
UserServiceTx txUserService = new UserServiceTx();
txUserService.setTransactionManager(transactionManager);
txUserService.setUserService(testUserService);
userDao.deleteAll();
for(User user : users) userDao.add(user);
try {
txUserService.upgradeLevels();
//트랜잭션 기능을 분리한 오브젝트를 통해 예외 발생용 TestUserService가 호출되게 해야 한다.
fail("TestUserServiceException expected");
}
...