AOP는 IoC/DI , 서비스 추상화와 함께 스프링의 3대 기반 기술 중 하나이다. 스프링이 DI를 사용하는 이유가 무엇이었나 한번 생각을 해보자. 만약 Client쪽에서 어떠한 클래스를 직접 참조하게 되면 그 클래스의 비즈니스 로직을 수정이 수정되면 client쪽 서비스가 정상 작동하지 않을 수 있다. 그렇기 때문이 이 두 오브젝트의 결합도를 낮추기 위해 인터페이스를 사용하여 의존관계를 주입해주고, 오브젝트간의 결합도를 낮춘다고 했다.
AOP에서도 이러한 DI가 중요하게 사용되는데, 기존에 Client가 UserService라는 객체를 직접 참조했다고 가정해보자. 이는 강한 결합도를 띄고 있는 상태이므로 Client <- UserServiceInterface <- UserServiceImpl라는 구조로 변경할 수 있다. 하지만 이것보다 더 좋은 AOP 적 프로그램은 UserServiceInterface를 두가지 오브젝트로 구현하는 것이다.
- UserServiceInterface <- UserServiceImpl : 인터페이스의 추상메소드를 실제 비즈니스 로직으로 구현
- UserServiceInterface <- UserServiceTx : UserServiceImpl 과 같이 구현 오브젝트를 DI받고, 그 오브젝트에 모든 기능을 위임
public class UserServiceTx implements UserService{
UserService userService;
PlatformTransactionManager transactionManager;
public void setTransactionManager(PlatformTransactionManager mngr){
this.transactionManager = mngr;
}
public void setUserService(UserService userService){
this.userService = userService;
}
public void add(User user){
this.userService.add(user);
}
public void upgradeLevels(){
TransactionStatus status =
//getTransaction()에 대한 로직은 여기서 처리하지 않는다. DI를 받는다.
this.transactionManager.getTransaction(
new DefaultTransactionDefinition() );
try{
// upgradeLevels()에 대한 로직은 여기서 처리하지 않는다. DI를 받는다.
userService.upgradeLevels();
this.transactionManager.commit(staus);
}catch(RuntimeException e){
this.transactionManager.rollback(status);
throw e;
}
}
}
6.3 다이나믹 프록시와 팩토리 빈
6.3.1 프록시와 프록시 패턴, 데코레이터 패턴
(부터 정리)
트랜잭션 경계 설정 코드를 비즈니스로직에서 분리해 낼 때, 추상화를 통한 전략패턴을 적용한다면 트랜잭션의 기능의 구현 내용만을 분리한 것이지, 트랜잭션을 적용한다는 사실은 그대로 코드에 남아있다.
그렇기 때문에 다음과 같은 방법을 적용할 수 있다. 부가기능을 하는 클래스를 핵심기능 클래스에서 완전히 독립시키는 것이다. 그리고 부가기능을 하는 클래스에서 핵심기능 클래스를 사용하면된다. 즉 부가기능 외의 나머지 기능은 원래 핵심 기능을 가진 클래스로 위임하는 것이다.
하지만 이렇게 되면 핵심기능을 바로 호출, 부가기능 클래스를 누락할 위험이 있기 때문에 부가기능 클래스는 자신이 마치 핵심기능을 가진 클래스 인 것 처럼 꾸며야 한다. 이를 위해서는 클라이언트는 반드시 인터페이스를 통해서만 핵심기능을 사용하게 하고, 부가기능 자신도 같은 인터페이스를 구현한 뒤에 자신이 그 사이에 끼어들어야 한다.
이렇게 클라이언트가 실제 사용하려는 대상인 것 처럼 위장해서 요청을 대리해주는 객체를 우리는 프록시(Proxy)라고 부르기로 했다. 그리고 프록시를 통해 최종적으로 요청을 위임받아 처리하는 실제 오브젝트를 타깃(Target) 또는 실체(Real Subject)라고 부른다.
프록시는 클라이언트가 타깃에 접근하는 '방법'을 제어하기 위해 사용되거나, 타깃에 '부가적인 기능을 부여' 해주기 위해 사용된다.
데코레이터 패턴
데코레이터 패턴은 타깃에 부가적인 기능을 런타임시에 다이내믹하게 부여해주기 위해 프록시를 사용하는 패턴이다.
이름이 데코레이터 패턴인 이유는 물건에 리본이나 상자나 부가적인 포장을 해주는 것처럼 핵심기능을 데코레이팅해준다고 해서 데코레이터 패턴이다. 리본을 붙일건지 상자로 포장할것인지는 런타임시에 결정된다.
그림 6-11과 같이 데코레이터 패턴에서 데코레이터 객체는 여러개일 수 있다. 그리고 프록시로써 동작하는 각 데코레이터는 다 인터페이스로 접근하기 때문에 자신이 타깃으로 위임하는지, 다음단계의 데코레이터 프록시로 위임하는지 알지 못한다. 그래서 다음 위임 대상은 그 전 데코레이터의 인터페이스를 선언하고, 위임 대상을 런타임시에 주입받을 수 있다.
인터페이스를 통한 데코레이터 정의와 런타임시의 다이내믹한 구성 방법은 스프링의 DI를 이용하면 편리하다. 데코레이터 빈의 프로퍼티로 같은 인터페이스를 구현한 다른 데코레이터 또는 타깃 빈을 설정하면 된다.
@Getter
@Setter
public class Book{
private String title;
}
/*서브젝트 역할*/
public interface BookService{
void rent(Book book);
}
public class BookServiceProxy implements BookService{
//타겟객체로 위임
private final RealBookService realBookService;
public BookServiceProxy(RealBookService realBookService){
this.realBookService = realBookService;
}
@Override
puic void rent(Book book){
// 데코레이터 객체에서 부여해주고 싶은 부가기능 메소드를 호출.
bugabuga();
// rent라는 비즈니스 로직은 타겟의 것을 사용한다.
realBookService.rent(book);
}
public void bugabuga(){
System.out.println("이것은 프록시의 부가기능 입니다");
}
}
@Service
public class RealBookService implements BookService{
public void rent(Book book){
System.out.println("The Name of Book you rent is " + book.getName());
}
}
public class Client{
public static void main(String[] args){
//인터페이스를 통해서만 접근
BookService bookService = new BookServiceProxy(new RealBookService());
bookService.rent(new Book());
}
}
프록시 패턴
프록시 패턴은 데코레이터 패턴과 달리 기능을 부여해주는 것이 아니라 타깃에 접근하는 방식을 제어하기 위해 사용하는 방식이다.
타깃 오브젝트의 생성이 복잡하거나 당장 필요하지 않은 경우에, 클라이언트에게 타깃 오브젝트가 아닌 프록시 객체를 넘겨준다. 그리고 실제 그 프록시 객체가 호출이 되면 그 시점에 타깃 오브젝트를 생성하면 편리하다.
또한 레이어에 따라 클라이언트의 접근 권한이 달라진다고 할 때, 특정 레이어에 클라이언트가 접근하여 프록세의 메소드를 사용하려고 하면 접근 불가 예외처리를 해줄 수 도 있다.
프록시 패턴에서는 데코레이터 패턴과는 다르게 자신이 만들거나 접근할 타깃 클래스의 직접적인 정보를 알아야 한다. 구체적인 생성 방법을 알아야 하기 때문이다.
6.3.2 다이내믹 프록시
프록시는 타깃객체를 변경하지 않고 부가 기능을 부여할 수도 있고, 접근을 제한할 수도 있지만 매번 프록시 객체에서
- 타겟 주입
- 부가 기능 수행
- 타겟 위임 ( 타겟의 메소드 호출 )
- 부가 기능 수행
이러한 과정을 거쳐야 하기 때문에 매우 번거롭고 부가 기능을 수행하는 코드가 중복될 가능성이 높아진다.
리플렉션
리플렉션 API를 사용하면, 프록시 객체를 구현할 때 구체적인 타겟 타입을 지정하지 않고도 구현이 가능하다.
프록시 객체는 InvocationHandler 인터페이스를 상속받고 , invoke라는 메소드에서 타겟의 메소드와 부가 기능을 구현해주면 된다.
// 프록시 객체 - InvocationHandler를 상속받는다.
public class TransactionHandler implements InvocationHandler {
private Object target; // 타겟은 Object의 형태로 받는다.
private PlatformTransactionManager transactionManager;
private String pattern;
public void setTarget(Object target){
this.target=target;
}
public void trasactionManager(TrasactionManager trasactionManager){
this.transactionManager = transactionManager;
}
public void setPattern(String pattern){
this.pattern = pattern;
}
@Override
public Object invoke(Object proxy , Method method, Object[] args) throws Throwable{
if(method.getName().startsWith(pattern)){
// 패턴에 일치하면 트랜잭션을 타야한다고 보고 트랜잭션 시작 메소드를 호출한다.
return invokeInTransaction(method, args);
}else{
// 트랜잭션이 필요 없으므로 타겟의 메소드를 바로 호출한다.
return method.invoke(target, args);
}
}
private Object invokeInTransaction(Method method, Object[] args) throws Throwable{
TransactionStatus status =
// 트랜잭션을 시작한 뒤에
this.transactionManager.getTransaction(new DefaultTransactonDefinition());
try{
// 타겟의 메소드를 호출한다.
Object ret = method.invoke(target, args);
this.transactionManager.commit(status);
return ret;
} catch (InvocationTargetException e){
this.transactionManager.rollback(status);
throw e.getTargetException();
}
}
}
// 클라이언트
public void updrageAllOrNothing() throws Exception {
//...
// 프록시 생성
TransactionHandler txHandler = new TrasactionHandler();
txHandler.setTarget(testUserService);
txHandler.setTransactionManager(transactionManager);
txHandler.setPattern("upgradeLevels");
// 클라이언트 객체는 다이내믹 프록시를 생성
UserService txUserService = (UserSerivce)Proxy.newProxyInstance(
getClass().getClassLoader(), new Class[]{ UserService.class } , txHandler);
)
}
'Study > Spring' 카테고리의 다른 글
Spring Batch에서 병렬 처리하기 (0) | 2022.06.20 |
---|---|
Servlet programming 처리 과정 (0) | 2022.06.20 |
[스프링 웹 MVC 활용] 03. URI 패턴 및 요청 매개변수(단순 타입) (1) | 2022.03.01 |
[스프링 웹 MVC 활용] 02. 핸들러 메소드의 Argument와 Return 타입 (0) | 2022.03.01 |
[스프링 웹 MVC 활용] 01. HTTP Request 맵핑하기 (0) | 2022.03.01 |