본문 바로가기
Programming/Languages (Java, etc)

[Java] AOP 1편 - 핵심 기술 Proxy, Dynamic proxy, Factory bean

by kghworks 2023. 12. 2.

목차

  • AOP란
  • 프락시(Proxy)의 정의, 프락시 패턴
  • 다이내믹 프락시
  • 다이내믹 프락시 예제
  • 한계
  • 스프링의 ProxyFactoryBean : 어드바이저

 

  Spring 3대 기술 (DI, PSA, AOP) 중 하나인 AOP는 프락시 기반으로 동작한다. Spring AOP를 보기에 앞서, 프락시의 개념을 알아보고, 다이내믹 프락시를 통해 동적으로 프락시를 생성해본다. 팩토리 빈을 통해 다이내믹 프록시를 빈으로 등록해 Spring DI로 편하게 사용해 본다. 그리고 스프링 ProxyFactoryBean을 통해 어드바이저 (Advisor)의 개념을 익힌다.

 이어지는 포스팅에서 스프링 AOP를 본격적으로 다룬다.


AOP란? (Aspect Oriented Programming(

 

 애플리케이션을 설계하다면 코드 여기저기에 공통적으로 존재하는 코드들이 있다. 트랜잭션 처리, 로깅, 보안 처리, 인증/인가 등이 해당된다. 이러한 부가기능은 해당 핵심기능 (부가기능이 적용된 코드)에 적용되어 완성된다. 이러한 부가 기능들은 모듈화가 어렵다는 특징을 가진다. 부가기능은 핵심기능에 적용되어야 의미가 있기 때문에 핵심 기능에 의존하여 작동하지, 단독으로 작동할 수 없기 때문이다. 

 

 이렇게 애플리케이션 여기저기 중복으로 존재하는 부가기능을 모듈화 한 것을 Aspect라고 한다. 이 모듈은 핵심기능에 부가되어야만 의미를 갖는 특징이 있다. Advice는 Aspect에 구현되어 있는 부가기능을 말한다. Pointcut은 Aspect 모듈이 추가될 대상을 선정하는 로직을 담은 오브젝트를 말한다. 애플리케이션의 핵심기능으로부터 부가기능들을 분리해 내 Aspect 모듈을 만들어 프로그래밍하는 것을 AOP (Aspect Oriented Programming, 관점 지향 프로그래밍)이라고 한다.

 

 

AOP 예시 : DB 트랜잭션 처리 모듈

 DB 트랜잭션은 DB에 접속하는 애플리케이션의 필수 요소이다. 이 트랜잭션에 대한 처리 (커밋, 롤백, 예외 등)는 각 비즈니스 로직 (핵심 로직)들로부터 분리해 낼 수 있는 부가기능이다. 따라서 트랜잭션 처리를 다음과 같이 AOP로 개발할 수 있다.

 

  • Aspect : 비즈니스 로직에 대한 트랜잭션 처리 모듈 (보통 싱글톤으로 관리)
  • Advice : 비즈니스 로직 앞/뒤로 존재하는 트랜잭션 기능 (e.g. 트랜잭션 시작, 커밋, 롤백, 예외에 대한 롤백 등)
  • Pointcut : 트랜잭션 처리 모듈을 적용해야 하는 비즈니스로직 선별 알고리즘 (회원 가입, 송금, 주문, 입고 등을 선별)
  • Joinpoint : 어드바이스 적용 위치 

프락시(Proxy)의 정의, 프락시 패턴

 

 

프록시 예제

 프록시 (Proxy)란 클라이언트가 사용하려는 대상인 것처럼 행동해서 클라이언트의 요청을 받아 실제 객체에 요청을 위임하는 객체를 말한다. 이때 타깃 (Target)은 클라이언트가 직접 사용하려고 했던 객체이자 프락시의 대상 객체이며, 타깃은 부가기능 (Proxy)의 존재를 모른다.  프락시는 주로 타깃의 기능을 확장 (프락시 패턴)하거나, 타깃에 대한 접근방법 (데코레이터 패턴)을 제어하기 위해 사용한다. 

 타깃에 대한 부가기능 (공통 기능, 트랜잭션, 로깅 등)을 처리할 때 아주 유용한 것이 프락시다. 알겠지만 Spring AOP가 프록시 기반으로 동작한다.

 

프록시 패턴 (Proxy pattern)

 클라이언트가 타깃에 접근하는 방식을 변경하는 패턴이다. 타깃에 대해 기능을 확장하거나, 추가하지 않는다. 타겟 오브젝트를 생성하지 않고 레퍼런스만 가지고 싶을 때 아주 유용하다. 클라이언트는 타겟 오브젝트를 생성하지 않고도, 타겟에 대한 프락시를 가지고 있음으로서 타겟에 대한 참조를 가진다.

 Remote Object에 아주 유용한데, JPA의 lazy loading 시 참조하게 되는 프록시 객체가 대표적이다. 실제 참조가 일어날 때 타깃 오브젝트를 생성하므로 성능상 유리한 부분을 가질 수 있다. (Target의 참조가 일어나기 전까지 DB Select가 발생하지 않음)

 타깃에 대한 접근제어 시에도 유용하게 사용할 수 있다. 타깃이 특정 레이어에서 write -> read-only로 수정하고 싶을 때와 같은 경우이다. java.util.Collections.unmodifiableCollection()은 Collection에 대한 프락시를 만들어 wirte 메서드 호출 시 UnsupportedOperationException를 발생시키는 예시이다.

 

 데코레이터 패턴 (Decorator pattern)

 타깃의 부가기능을 프락시를 통해 추가하는 패턴이다. 타깃의 코드 수정이 필요 없다. 컴파일 시점에는 프락시와 타겟의 관계가 정의되어있지 않으나, 클라이언트가 동적으로 관계를 맺는다. 프락시는 1개 이상 가능하며, 이때 각 프록시를 하나의 데코레이터 (Decorator)라고 한다. 데코레이터는 요청을 위임하는 대상이 인터페이스이기 때문에 대상이 타깃인지, 또 다른 데코레이터인지 알지 못한다. 

 대표적으로 java.io.InputStream / OutputStream이 있다. 이 타깃에는 java.io.BufferdInputStream과 같은 여러 데코레이터를 클라이언트가 붙여줄 수 있다.

 

InputStream in = new BufferedInputStream(new FileInputStream("test.txt"));

데코레이터 패턴

 

프락시 구현하기

프록시 UML

@Data
@RequiredArgsConstructor
public class User {

  private final String name;

  private final int age;

  private final String group;
}

public interface UserView {

  String viewName(Idol user);

  String viewAge(Idol user);

  String viewGroup(Idol user);
}


public class UserViewProxy implements UserView {

  private UserView userView;

  public UserViewProxy(IdolView userView) {
    this.userView = userView;
  }

  @Override
  public String viewName(Idol user) {
    return userView.viewName(user) + " 입니다.";
  }

  @Override
  public String viewAge(Idol user) {
    return userView.viewAge(user) + " 입니다.";
  }

  @Override
  public String viewGroup(Idol user) {
    return userView.viewGroup(user) + " 입니다.";
  }
}


public class UserViewTarget implements UserView {

  @Override
  public String viewName(Idol user) {
    return user.getName();
  }

  @Override
  public String viewAge(Idol user) {
    return user.getAge() + "세";
  }

  @Override
  public String viewGroup(Idol user) {
    return user.getGroup();
  }
}

...

@Test
void simpleProxy() {
    UserView userViewProxy = new UserViewProxy(new UserViewTarget());
    assertThat(userViewProxy.viewName(karina), org.hamcrest.Matchers.is("Karina 입니다."));
}

다이내믹 프록시 (Dynamic proxy)

 앞에서 본 프락시의 가장 큰 문제는 클라이언트가 매번 프록시 (핵심 기능 구현체)를 작성해야 하는 것이다. 타깃 수만큼 프락시 클래스 수가 많아지는 단점이 있다.

 JDK에서 제공하는 java.lang.reflect 패키지를 사용해 매번 프락시를 작성하지 않고 런타임에 프록시를 만들 수 있게 한다. 이를 다이내믹 프록시 (Dynamic proxy)라 한다. 다이내믹 프록시는 프락시 팩토리 (Proxy Factory)가 동적으로(Runtime) 만드는 오브젝트이다. 다이내믹 프락시는 타겟 인터페이스와 같은 타입니다. 클라이언트는 카겟 인터페이스를 통해 다이내믹 프락시를 사용하게 된다. 즉, 프락시 팩터리에게 구현해야할 인터페이스 정보를 주면, 프록시 팩터리가 알아서 프록시를 생성해 준다. 단, 필요한 부가기능들은 직접 작성해야 하는데 InvocationHadler를 구현하여 작성한다. 클라이언트는 java.lang.reflect.Proxy가 생성해 주는 다이내믹 프락시 (Proxy의 하위 클래스)를 사용한다.

 

다이내믹 프록시를 적용한 UserView

@AllArgsConstructor
public class IdolViewHandler implements InvocationHandler {

    IdolView target;

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String ret = (String) method.invoke(target, args);
        return ret + " 입니다.";
    }

}

...

@Test
void simpleProxy() {
    // 다이내믹 프록시 오브젝트 생성
    IdolView userViewHandler = (IdolView) java.lang.reflect.Proxy.newProxyInstance(
            IdolView.class.getClassLoader(), // 다이내믹 프록시 클래스 로딩에 사용할 클래스 로더
            new Class[]{IdolView.class}, // 구현해야하는 인터페이스 (1개 이상 가능)
            new IdolViewHandler(new IdolViewTarget()) // 부가 기능을 담은 InvocationHandler
    );

    assertThat(userViewHandler.viewName(karina), org.hamcrest.Matchers.is("Karina 입니다."));

}

 

 프락시 클래스를 직접 생성하지 않았다. 그러나 다이내믹 프락시를 동적으로 생성하는 클라이언트의 코드가 조금 복잡해졌다. 


다이내믹 프록시 예제 : 트랜잭션 기능 추가하기

프락시를 통해 부가기능을 추가하는 대표적인 예제로 트랜잭션 기능이다. Spring Data JDBC의 JDBC Template을 사용하면 트랜잭션 관리를 포함한 DB 커넥션 관리를 편하게 할 수 있지만, 서비스 레이어에 트랜잭션을 부여하고 싶을 수 있다. @Transactional을 붙이면 되지만 지금은 없이 해보자.   

 

다이내믹 프록시를 통해 트랜잭션 기능 추가

@Setter
@AllArgsConstructor
@NoArgsConstructor
public class TransactionHandler implements InvocationHandler {

    private Object target;

    private PlatformTransactionManager transactionManager;


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        TransactionStatus transactionStatus = this.transactionManager.getTransaction(new DefaultTransactionDefinition());
        try {
            Object ret = method.invoke(target, args);
            return ret;
        } catch (Exception e) {
            this.transactionManager.rollback(transactionStatus);
            throw e;

        }
    }
}


@Service("userServiceWithoutTransaction")
@AllArgsConstructor
public class UserServiceWithoutTransaction implements UserService {

    private UserDao userDao;


    @Override
    public void updateNameGroupUpper() {


        List<User> userList = userDao.findAll();

        userList.stream().forEach(member -> {
            member.setGroupName(member.getGroupName().toUpperCase());
            userDao.updateGroupName(member);
        });
    }

    // ...
}

...

@Autowired
private PlatformTransactionManager transactionManager;

@Qualifier("userServiceWithoutTransaction")
@Autowired
private UserService userServiceWithoutTransaction;

@Test
void transactionTestHandler() {
    setUp();

    TransactionHandler transactionHandler = new TransactionHandler();
    transactionHandler.setTarget(userServiceWithoutTransaction);
    transactionHandler.setTransactionManager(transactionManager);


    userService = (UserService) Proxy.newProxyInstance(
            getClass().getClassLoader(),
            new Class[]{UserService.class},
            transactionHandler
    );

    userService.updateNameGroupUpper();
    checkName();

}

 

팩토리 빈 (Factory Brean) 사용해서 프락시 생성 빈 만들기

 앞선 테스트 코드의 transactionTestHandler()를 보면 다이내믹 프락시를 만드는 코드가 너무 복잡하다. Spring Bean으로 다이내믹 프록시를 등록하여 DI를 통해 편하게 하는 방법은 없을까? 다이내믹 프락시는 Runitme에 만들어지는 객체여서 스프링 빈으로 만들 수 없다. 그러나 팩토리 빈 (Factory Bean, 스프링을 대신해서 오브젝트를 생성하는 스프링 빈)을 사용해 볼 수 있다. 이러면 다이내믹 프락시를 빈으로 등록하여 편리하게 DI 받아 사용할 수 있다.

 

@Configuration
public class AppConfig {
   	// ...
   
    @Bean
    public TxProxyFactoryBean userService() {
        TxProxyFactoryBean txProxyFactoryBean = new TxProxyFactoryBean();
        txProxyFactoryBean.setTarget(userServiceWithoutTransaction());
        txProxyFactoryBean.setTransactionManager(transactionManager());
        txProxyFactoryBean.setInterfaceOfTarget(UserService.class);
        return txProxyFactoryBean;
    }
}



@Setter
public class TxProxyFactoryBean implements FactoryBean<Object> {

    Object target;

    PlatformTransactionManager transactionManager;

    Class<?> interfaceOfTarget;

    @Override
    public Object getObject() throws Exception {
        TransactionHandler transactionHandler = new TransactionHandler();
        transactionHandler.setTarget(target);
        transactionHandler.setTransactionManager(transactionManager);

        return Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{interfaceOfTarget}, transactionHandler);
    }

    @Override
    public Class<?> getObjectType() {
        return interfaceOfTarget;
    }

    @Override
    public boolean isSingleton() {
        return false;
    }
}

...

@Test
@Disabled
@DirtiesContext
void factoryBeanTransaction() throws Exception {
    TxProxyFactoryBean txProxyFactoryBean = applicationContext.getBean("&userService", TxProxyFactoryBean.class);
    txProxyFactoryBean.setTarget(userService);

    UserService idolService = (UserService) txProxyFactoryBean.getObject();

    idolService.updateNameGroupUpper();

    checkName();
}

 

 

 다이내믹 프락시를 생성하는 테스트 코드가 매우 간결해졌다. 아래처럼 빈 설정 시 여러 타깃에 동일한 트랜잭션 기능을 부가해줄 수도 있다. 

@Configuration
public class AppConfig {

    // ... 

    @Bean
    public TxProxyFactoryBean userService() {
        TxProxyFactoryBean txProxyFactoryBean = new TxProxyFactoryBean();
        txProxyFactoryBean.setTarget(userServiceWithoutTransaction()); // 여러 타깃 지정 가능
        txProxyFactoryBean.setTransactionManager(transactionManager());
        txProxyFactoryBean.setInterfaceOfTarget(UserService.class); // 타깃에 따른 인터페이스 지정
        return txProxyFactoryBean;
    }

    @Bean
    public TxProxyFactoryBean orderService() {
        TxProxyFactoryBean txProxyFactoryBean = new TxProxyFactoryBean();
        txProxyFactoryBean.setTarget(orderServiceWithoutTransaction()); // 여러 타깃 지정 가능
        txProxyFactoryBean.setTransactionManager(transactionManager());
        txProxyFactoryBean.setInterfaceOfTarget(OrderService.class); // 타깃에 따른 인터페이스 지정
        return txProxyFactoryBean;
    }
    // ...
}

한계

 JDK에서 제공하는 다이내믹 프락시를 통해 클라이언트는 매번 타깃 인터페이스 구현체를 작성하지 않아도 된다. 프록시 팩토리 빈을 통해 다이내믹 프록시를 빈으로 등록하여 편하게 DI 받아 사용할 수도 있게 되었다. 그러나 한계점이 명확하다.

 

  • 타깃 오브젝트의 메서드 별로 서로 다른 부가기능을 제공하고 싶다면?
  • 하나의 타깃에 여러 부가기능을 적용한다면?

 

 먼저 앞선 예시들은 모두 타깃 오브젝트 전체에 프락시를 적용하는 것이었다. 타깃 오브젝트의 특정 메서드에만 프락시를 적용하고 싶다면 어떻게 해야할까? IvocationHadler 구현체의 invoke 메서드에서 reflection을 사용해 적용하고 싶은 메서드에만 부가기능을 추가하는 방법도 있겠으나, 타겟 오브젝트에 수정이 있을때마다 같이 수정되어야 한다. 즉 팩토리빈과 타겟오브젝트가 강하게 결합한다.

 그리고 타겟 오브젝트 하나에 여러 부가기능을 적용한다면, 그만큼 InvocationHandler 구현체가 늘어나고, 스프링 빈 설정이 매우 장황하고 복잡해진다.

 


스프링의 ProxyFactoryBean : 어드바이저 (Advisor)

 지금까지는 JDK가 제공하는 다이내믹 프록시를 보았다. JDK의 다이내믹 프록시를 스프링 DI를 사용하기 위해  스프링에서 제공하는 org.springframework.beans.factory.FactoryBean을 사용했다. 그러나 그 한계가 존재했다. 스프링은 org.springframework.aop.framework.ProxyFactoryBean을 제공하여 부가기능 로직과 프록시를 제공하는 빈을 완벽히 분리한다. 템플릿/콜백 패턴과, 전략 패턴이 적용되게 된다.

 

 먼저 기존의 팩토리 빈을 사용하는 다이내믹 프록시 전략을 보자

JDK 다이내믹 프록시를 사용하는 전략

 문제는 TxProxyFactoryBean이 타깃과 강하게 결합되어 있다는 것과 타깃에 여러 프락시를 적용하고 싶을 때 프락시 팩토리빈이 늘어나고, 빈 설정이 장황해진다는 것이었다. 스프링이 제공하는 ProxyFactoryBean을 사용하면 아래와 같은 구조를 가질 수 있다. 

 

스프링 ProxyFactoryBean 구조

 

 비슷한 구조이지만 다이내믹 프락시를 생성하는 PrxoyFactoryBean이 DefatulPointcutAdvisor라는 것을 사용한다. 이 부분을 통해 앞에 JDK에서 만났던 한계를 극복하게 된다. 즉 타깃과 강하게 결합되어 있던 부분을 DefaultPointcutAdvisor로 분리해 낸다. (템플릿/콜백) ProxyFactoryBean은 오로지 다이내믹 프락시만 생성하고, 부가기능에 대한 구현은 DefaultPointcutAdvisor가 담당한다.

 

어드바이저 (Advicer) = 어드바이스 (Advice) + 포인트컷 (Pointcut)

스프링 ProxyFactoryBean은 어드바이스와 포인트 컷으로 나뉜다. 어드바이스 (Advice)는 타깃 오브젝트에 추가할 부가기능을 담은 오브젝트다. addAdvice(Advice)로 1개 이상의 어드바이스를 추가할 수 있다. 

 포인트컷 (Pointcut)은 부가기능을 적용할 타깃의 메서드를 선정하는 알고리즘을 담은 오브젝트이다. 프락시는 먼저 포인트컷으로 어드바이스 적용 대상인지 확인 후 어드바이스에게 부가기능을 요청한다. 

 이렇게 1개 이상의 어드바이스와 포인트컷이 만나면 하나의 어드바이저 (Advisor)가 되어 ProxyFactoryBean을 통해 다이내믹 프락시가 생성된다. FactoryBean 구현체에 타깃 인터페이스를 지정 (FactoryBean.setTarget()) 해줘야 하지만 ProxyFactoryBean은 타깃 인터페이스를 자동으로 검출해서 알아낸다. 따라서 프락시의 타깃을 사전에 정의할 필요가 없다. 

 

템플릿 / 콜백 패턴

어드바이저는 템플릿 / 콜백 패턴이 적용된 예시다. 어드바이스는 템플릿으로 여러 타깃에 자유롭게 적용이 가능하다. MethodInvocation 오브젝트는 콜백으로 어드바이스를 적용할 타깃에 대한 정보를 가지고, 콜백 형태로 실행된다. 

 

ProxyFactoryBean 예제 : 트랜잭션 기능 추가하기

@AllArgsConstructor
public class TxAdvice implements MethodInterceptor {

    private PlatformTransactionManager transactionManager;

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {

        TransactionStatus transactionStatus = this.transactionManager.getTransaction(new DefaultTransactionDefinition());
        try {
            Object ret = invocation.proceed();
            this.transactionManager.commit(transactionStatus);
            return ret;
        } catch (Exception e) {
            this.transactionManager.rollback(transactionStatus);
            throw e;
        }

    }
}

@Configuration
public class AppConfig {
    // ...

    @Bean
    public TxAdvice transactionAdvice() {
        return new TxAdvice(transactionManager());
    }

    @Bean
    public NameMatchMethodPointcut transactionPointcut() {
        NameMatchMethodPointcut nameMatchMethodPointcut = new NameMatchMethodPointcut();
 	    nameMatchClassMethodPointcut.setMappedNames("update*", "delete*"); //DML에만 트랜잭션 적용
        return nameMatchMethodPointcut;
    }

    @Bean
    public DefaultPointcutAdvisor transactionAdvisor() {
        DefaultPointcutAdvisor defaultPointcutAdvisor = new DefaultPointcutAdvisor();
        defaultPointcutAdvisor.setAdvice(transactionAdvice());
        defaultPointcutAdvisor.setPointcut(transactionPointcut());
        return defaultPointcutAdvisor;
    }

    @Bean
    public ProxyFactoryBean userService() {
        ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
        proxyFactoryBean.setTarget(userServiceWithoutTransaction());
        proxyFactoryBean.setInterceptorNames("transactionAdvisor");
        return proxyFactoryBean;
    }

}

...

@Test
@DirtiesContext
void proxyFactoryBeanTransaction() throws Exception {
    ProxyFactoryBean txProxyFactoryBean = applicationContext.getBean("&userService", ProxyFactoryBean.class);
    UserService userService = (UserService) txProxyFactoryBean.getObject();

    userService.updateNameGroupUpper();

    checkName();
}

ProxyFactoryBean으로부터 생성한 다이내믹 프록시

 

ProxyFactoryBeand을 자세히 보면 아래와 같은 구조이다.

Advisor와 Target으로 이루어진 ProxyFactoryBean

 

아래 그림을 통해 테스트 코드에서 얻은 userService 빈 오브젝트는 Proxy인 것을 알 수 있다. proxiedInterfaces 필드를 확인해 보면 타깃 인터페이스 UserService를 정상적으로 탐색했다.

userService 오브젝트는 java.lang.reflect.Proxy 자식 클래스이자 class jdk.proxy2.$Proxy

 

@Test
@DirtiesContext
void proxyFactoryBeanTransaction() throws Exception {
    ProxyFactoryBean txProxyFactoryBean = applicationContext.getBean("&userService", ProxyFactoryBean.class);
    txProxyFactoryBean.setTarget(userService);
    UserService userService = (UserService) txProxyFactoryBean.getObject();

    System.out.println("class           : " + userService.getClass());
    System.out.println("super class     : " + userService.getClass().getSuperclass().getName());
    System.out.println("interfaces      : " + userService.getClass().getInterfaces()[0].getName());
    System.out.println("target class    : " + txProxyFactoryBean.getTargetClass().getName());
    System.out.println("advisor         : " + txProxyFactoryBean.getAdvisors()[0].getClass().getName());
    System.out.println("advice          : " + txProxyFactoryBean.getAdvisors()[0].getAdvice().getClass().getName());
    System.out.println("pointcut        : " + ((DefaultPointcutAdvisor) (txProxyFactoryBean.getAdvisors()[0])).getPointcut().getClass().getName());
}

userService 프록시 관련 정보

어드바이저의 재사용성

 이미 만들어둔 어드바이저를 여러 타깃에 적용할 수 있다. Bean 관계도로 보면 아래와 같다. 

여러 서비스에 transactionAdvisor를 추가할 수 있다.

 

    @Bean
    public ProxyFactoryBean userService() {
        ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
        proxyFactoryBean.setTarget(userServiceWithoutTransaction());
        proxyFactoryBean.setInterceptorNames("transactionAdvisor");
        return proxyFactoryBean;
    }
    
    @Bean
    public ProxyFactoryBean orderService() {
        ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
        proxyFactoryBean.setTarget(orderServiceWithoutTransaction());
        proxyFactoryBean.setInterceptorNames("transactionAdvisor");
        return proxyFactoryBean;
    }
    
    @Bean
    public ProxyFactoryBean deliveryService() {
        ....

 

 한 가지 불편한 점은 어드바이저의 대상이 추가될 때마다 새로운 ProxyFactoryBean을 등록해주어야 한다는 것이다. 트랜잭션 어드바이저를 적용해야 할 서비스가 100개라면 빈 설정에 100개의 설정이 되어야 한다. 당연히 스프링 AOP는 자동으로 ProxyFactoryBean을 생성해 준다.

 

지금까지 JDK AOP, 다이내믹 프락시,  스프링 FactoryBean을 통한 DI, 스프링 ProxyFactoryBean을 통한 어드바이저 재사용성을 보았다. 이제 스프링이 제공하는 AOP를 통해 ProxyFactoryBean을 자동으로 생성해 보자. (아래 포스팅에서 이어짐)

 

2023.12.03 - [Programming/JAVA] - [Spring] AOP 2편 - Spring의 AOP과 @Transactional

 

[Spring] AOP 2편 - Spring의 AOP과 @Transactional

2023.12.02 - [Programming/JAVA] - [Java] AOP 핵심 기술 - Proxy, Dynamic proxy, Factory bean [Java] AOP 핵심 기술 - Proxy, Dynamic proxy, Factory bean 목차 AOP란 프락시(Proxy)의 정의, 프락시 패턴 다이내믹 프락시 다이내믹 프

kghworks.tistory.com

 


참고

https://product.kyobobook.co.kr/detail/S000000935360

 

토비의 스프링 3.1 세트 | 이일민 - 교보문고

토비의 스프링 3.1 세트 | 애플리케이션 아키텍처 설계부터 프레임워크 제작까지 다룬 스프링 가이드북!『토비의 스프링 3.1 세트』는 스프링을 처음 접하거나 스프링을 경험했지만 스프링이 어

product.kyobobook.co.kr

https://docs.spring.io/spring-framework/reference/data-access/jdbc.html 

https://www.baeldung.com/spring-jdbc-jdbctemplate 

댓글