-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[6주차](이승철, 이세원, 권용현) #5
Comments
4장 예외throws SQLException의 유무 차이 public void deleteAll() throws SQLException {
this.jdbcContext.executeSQL("delete from users");
}
public void deleteAll() {
this.jdbcTemplate.update("delete from users");
} update라는 함수에서 런타임 예외를 터쳐버리기 (런타임 예외로 전환해서) 예외처리의 잘못된 습관
예외의 종류와 특징
unchecked exception은 예상하지 못했던 상황에서 발생하는 것이 아니기 때문에 catch, throws를 강요하지 않는다. service 계층을 순수하게 가져가기 위해서는 SQLException을 제거해야한다. 예외 처리 방법
예외 복구해결해서 정상상태로 돌려놓기 예외 처리 회피단지 회피하는 것이 아니다. 예외 전환
예외처리 전략런타임 예외의 보편화
1번과 같은 상황 -> 체크 예외가 적합 그러나, api를 잘 숙지해서 예외의 종류와 원인, 활용방법을 잘 알고 런타임 예외를 사용해야겠죠? 애플리케이션 예외출금 상황에서
SQLException은 어디로 갔을까?SQLException은 복구가 불가능한 상황이 대부분이라고 볼 수 있다. 호환성 없는 SQLException DB 에러정보SQLException 안에 담긴 에러 코드는 다른 데이터베이스와 호환되지 않는다. DB 에러 코드 매핑을 통한 전환결국 에러 코드 매핑파일을 이용해서, DB마다 다른 에러코드 일관성있게 Exception 클래스로 매핑한다 DAO 인터페이스결국 DB 구현체가 다를 때 구체화된 클래스를 만들고 인터페이스의 함수를 구현하는 방식을 택할 것이다. throws Exception으로 처리하는 것은 무책임한 선언이라고 하였다. 하지만, JDBC는 여전히 SQLException이다. 예외추상화와 DataAccessException 계층구조그래서 스프링은 예외들을 추상화하여 DataAccessException을 제공하는 것이다. ORM 기술에만 존재하는 에러는 어떻게 처리할까? 생각해봅시다. 예외가 왜 있을까? 정리
|
AOP
트랜잭션 코드의 분리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;
}
} 여기서 비즈니스 로직을 담당하는 코드와 트랜잭션 코드는 서로 주고받는 것도 없는, 완벽하게 독립적인 코드이다. UserService 안에 트랜잭션 로직이 자리잡지 않도록, 아예 트랜잭션 코드가 존재하지 않는 것처럼 사라지게 할 수는 없을까? DI적용을 위한 트랜잭션 분리DI는 기본적으로 실제 사용할 오브젝트의 클래스 정체는 감춘 채 인터페이스를 통해 간접적으로 접근하는 것이다. 한번에 두 개의 UserService 인터페이스 구현 클래스를 동시에 이용한다면? public interface UserService {
void add(User user);
void upgradeLevels();
} public class UserServicelmpl implements UserService {
UserDao userDao;
MailSender mailSender;
public void upgradeLevels() {
List<User> users = userDao.getAll();
for (User user : users) {
if (canUpgradeLevel(user)) {
upgradeLevel(user);
}
}
}
} 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 DefaultTransactionDefinitionO);
try {
userService.upgradeLevels();
this.transactionManager.commit(status);
} catch (RuntimeException e) {
this.transactionManager.rollback(status);
throw e;
}
}
} 이와 같이, UserServiceTx는 비즈니스 로직을 전혀 갖지 않고 고스란히 다른 UserService 구현 오브젝트에 기능을 위임한다. 이렇게 되면, 클라이언트가 UserService라는 인터페이스를 통해 사용자 관리 로직을 이용하려고 할 때 먼저 트랜잭션을 담당하는 오브젝트가 사용돼서 트랜잭션에 관련된 작업을 진행해주고, 실제 사용자 관리 로직을 담은 오브젝트가 이후에 호출돼서 비즈니스 로직에 관련된 작업을 수행하게 된다. 고립된 단위 테스트위 사진에서 볼 수 있듯이, UserService의 구현 클래스들이 동작하려면 세가지 타입의 의존 오브젝트가 필요하다. Mockito 프레임워크Mockito라는 프레임워크는 사용하기도 편리하고, 코드도 직관적이라 퇴근 많은 인기를 끌고 있다.
다이내믹 프록시와 팩토리 빈이전의 코드를 보면 트랜잭션이라는 기능은 사용자 관리 비즈니스 로직과는 성격이 다르기 때문에 아예 그 적용 사실 자체를 밖으로 분리한 UserServiceTx라는 클래스를 만들었고, UserServiceImpl에는 트랜잭션 관련 코드가 하나도 남지 않게 되었다.이처럼, 부가기능 외의 나머지 모든 기능은 원래 핵심기능을 가진 클래스로 위임해줘야 한다. 핵심기능은 부가기능을 가진 클래스의 존재 자체를 모른다. 클라이언트는 인터페이스를 통해서만 핵심기능을 사용하게 하고, 부가기능 자신도 같은 인터페이스를 구현한 뒤에 자신이 그 사이에 끼어들어야 한다. 이렇게 마치 자신이 클라이언트가 사용하려고 하는 실제 대상인 것처럼 위장해서 클라이언트의 요청을 받아주는 것을 대리자, 대리인과 같은 역할을 한다고 해서 프록시(proxy)라고 부른다. 프록시를 통해 최종적으로 요청을 위임받아 처리하는 실제 오브젝트를 타깃 또는 실체라고 부른다. 다이내믹 프록시자바에는 java.lang.reflect 패키지 안에 프록시를 손쉽게 만들 수 있도록 지원해주는 클래스들이 있다. 프록시의 구성과 프록시 작성의 문제점
리플렉션 Method lengthMethod = String.class.getMethod("length");
int length = lengthMethod.invoke(name); // int length = name.length(); invoke() 메소드는 메소드를 실행시킬 대상 오브젝트와 파라미터 목록을 받아서 메소드를 호출한 뒤에 그 결과를 Object 타입으로 돌려준다. 다이내믹 프록시 적용다이나믹 프록시는 프록시 팩토리에 의해 런타임 시 다이내믹하게 만들어지는 오브젝트다. 다이내믹 프록시 오브젝트는 타깃의 인터페이스와 같은 타입으로 만들어진다. 프록시 팩토리에게 인터페이스 정보만 제공해주면 해당 인터페이스를 구현한 클래스의 오브젝트를 자동으로 만들어주기 때문이다. public class UppercaseHandler implements InvocationHandler {
Hello target;
public UppercaseHandler(Hello target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object!] args) throws Throwable {
String ret = (String)method.invoke(target, args);
return ret.toUpperCase();
}
} Hello proxiedHello = (Hello)Proxy.newProxyInstance(
getClass().getClassLoader(),
new Class[] {Hello.class}, // 구현할 인터페이스
new UppercaseHandler (new HelloTarget())); 프록시 팩터리 빈 방식의 한계: 한 번에 여러 개의 클래스에 공통적인 부가기능을 제공하는 일은 불가능하다. TransactionHandler의 중복을 없애고 모든 타깃에 적용 가능한 싱글톤 빈으로 만들어서 적용할 수는 없을까? 스프링의 프록시 팩토리 빈ProxyFactoryBean이 생성하는 프록시에서 사용할 부가기능은 MethodInterceptor 인터페이스를 구현해서 만든다.
MethodInterceptor과 InvocationHandler의 차이점
public void simpleProxy() {
Hello proxiedHello = (Hello)Proxy.newProxyInstance( // JDK 다아내믹 프록시 생성
getClass().getClassLoader(),
new Class[] {Hello.class},
new UppercaseHandler(new HelloTarget())
);
}
public void proxyFactoryBean() {
ProxyFactoryBean pfBean = new ProxyFactoryBean();
pfBean.setTarget(new HelloTarget()); // 타깃 설정
pfBean.addAdvice(new UppercaseAdvice()); // 부가기능을 담은 어드바이스를 추가한다. 여러 개를 추가할 수도 있다.
Hello proxiedHello = (Hello) pfBean.getObject(); // FactoryBean이므로 getObject()로 생성된 프록시를 가져온다.
System.out.println(proxiedHello.sayHello("JaeDoo"));
System.out.println(proxiedHello.sayHi("JaeDoo"));
System.out.println(proxiedHello.sayThankYou("JaeDoo"));
}
static class UppercaseAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
/*
리플렉션의 Method와 달리 MethodInvocation은
메소드 정보와 함께 타깃 오브젝트를 알고 있기 때문에
메소드 실행 시 타깃 오브젝트를 전달할 필요가 없다.
*/
String ret = (String)invocation.proceed();
return ret.toUpperCase(); // 부가기능 적용
}
}
public interface Hello { // 타깃과 프록시가 구현할 인터페이스
String sayHello(String name);
String sayHi(String name);
String sayThankYou(String name);
}
static class HelloTarget implements Hello { // 타깃 클래스
@Override
public String sayHello(String name) { return "Hello " + name;}
@Override
public String sayHi(String name) { return "Hi " + name;}
@Override
public String sayThankYou(String name) {return "Thank You " + name;}
} 어드바이스: 타깃이 필요 없는 순수한 부가기능
포인트컷: 부가기능 적용 대상 메소드 선정 방법
|
The text was updated successfully, but these errors were encountered: