Inflearn brand logo image

인프런 커뮤니티 질문&답변

HAHA님의 프로필 이미지
HAHA

작성한 질문수

은행 서버 프로젝트 실습을 통해 배우는 코틀린 마스터 클래스

Spring @Transactional AOP 극복

스프링 빈 질문

해결된 질문

작성

·

47

1

안녕하세요.

궁금한 점이 있어 질문드립니다.

  1. 코드를 보면

@Component
private class Advice: Runner ...

이렇게 내부 클래스가 Private으로 되어 있는데 private으로 선언된 클래스도 component scan 대상이 되나요?

 

  1. 만약 1번에서 안된다면 인자로 받은 Runner가 프록시 빈으로 생성이 안될 것 같은데, 그렇다면 프록시가 제대로 동작하는게 맞는지 궁금합니다.

 

  1. private val advice : Runner = Advice()
    코드를 보면 만약 빈으로 생성된 인자를 넣지 않는다면 기본 Advice() 를 생성해서 넣는다고 보이는데 이렇게 된다면 Advice 프록시가 자동으로 생성되지 않는다고 생각되고 그렇다면 aop가 작동되지 않을것 같은데 맞는지 궁금합니다.(그래서 기본값을 넣지 않는게 맞지 않나 궁금합니다.)

     

 

감사합니다.

답변 2

3

Hong님의 프로필 이미지
Hong
지식공유자

안녕하세요 HAHA님 질문 주셔서 감사합니다.!!

 

맙소사...!! 부끄럽게도 제가 잘못 작성한 부분이 있었네요 ㅠㅠ 강의를 연속으로 촬영하다보니 제대로 확인하지 못한거 같습니다... 이렇게 집어주셔서 감사합니다...!! ㅠㅠ

 

말씀하시고 이해하신 내용처럼 Advice라는 클래스는 Private 클래스를 scan 하지 못합니다..! 외부에서 접근이 불가능하기 떄문이죠

 

그래서 해당 코드는 잘못된 코드가 되겠네요 ㅠㅠ

관련하여 소스코드를 수정하는 업데이트를 진행하도록 하겠습니다. 이렇게 확인해주셔서 감사합니다..!

Hong님의 프로필 이미지
Hong
지식공유자

변경하는 코드는 다음과 같이 구성이 될 예정입니다.

interface Runner {
    fun <T> run(function : () -> T?) : T?
    fun <T> readOnly(function : () -> T?) : T?
}

@Component
class TransactionalAdvice : Runner {
    @Transactional
    override fun <T> run(function : () -> T?) : T? = function()

    @Transactional(readOnly = true)
    override fun <T> readOnly(function : () -> T?) : T? = function()
}

@Component
class Transactional(
    private val advice: Runner
) {
    fun <T> run(function : () -> T?) : T?  = advice.run(function)

    fun <T> readOnly(function : () -> T?) : T? = advice.readOnly(function)
}

 

TransactionAdvice를 따로 둔 이유는 내부적으로 무언가 후속 조치를 할 수 있게 랩핑하여 확장성을 일부 보장 할 수 있게 하면 좋을듯하여 이렇게 변경하였습니다. 감사합니다!!!!

HAHA님의 프로필 이미지
HAHA
질문자

주말인데도 빠른 답변해주셔서 감사합니다.

private val advice : Runner = Advice()

기존 코드에서 기본 생성자로 값을 넣어주셨는데,

(변경된 코드 기준으로 질문드리자면)

private val advice : Runner = TransactionalAdvice()

이렇게 되어 있던 코드를

 

private val advice : Runner

이렇게 변경하신 이유는 일반 생성자로 생성 시 프록시가 등록되지 않아서가 맞을까요?

즉, 만약 Runner 인터페이스가 빈에 등록되어 있지 않으면 기본 생성자를 사용해 생성하면 프록시가 동작하지 않으니 만약 없으면 에러 발생하게 처리

이렇게 이해했는데 맞는지 궁금합니다.

 

좋은 강의 제공해주셔서 감사합니다 😄

Hong님의 프로필 이미지
Hong
지식공유자

넵 이해하신 부분이 맞습니다. 정확히 이해하신거 같아요.

기존 코드로 생성을 하면 프록시가 적용이 되지 않을겁니다. @Transaction 어노테이션이 AOP 프록시를 통해 동작하다 보니 직접 생성자를 통해서 만들어주게 된다면 , Spring이 관리를 하지 않게 되는 문제가 있었죠

 

말씀하신것처럼 당연하게 에러가 발생을 하게 구성하였습니다.

이렇게 구성한 이유는 AOP의 가장 큰 단점중에 하나가 컴파일 환경에서는 탐지가 안된다는 부분이고, 이 부분을 인터페이스 구현체를 통한다며, Spring 애플리케이션이 Scan하는 과정에서 확인을 해줄 수 있다고 생각을 하였기떄문에 이런 부분을 고려하여 다음과 같이 수정한겁니다!

추가적인 질문이 있다면 편하게 남겨주세요!!

0

안녕하세요, 인프런 AI 인턴입니다. HAHA님.

스프링 빈과 관련된 질문을 주셨군요. Kotlin에서의 스프링 컴포넌트 스캔 및 AOP 동작에 대한 궁금증 해결을 위해 몇 가지 내용을 설명해 드리겠습니다.

  1. private 클래스와 Component Scan:

    • 기본적으로 스프링에서는 @Component로 지정된 클래스는 public이어야 컴포넌트 스캔의 대상이 됩니다. 따라서, private으로 선언된 클래스는 스프링의 컴포넌트 스캔에 의해 빈으로 등록되지 않습니다. 이는 클래스의 접근 제어자가 private이기 때문입니다. 스프링 컨텍스트 외부에서 접근할 수 없으므로 빈으로 등록되지 않으며 보통 이를 위해 public으로 지정합니다.
  2. 프록시 빈 생성 여부:

    • 만약 private로 선언된 클래스가 실제로 스프링 컨텍스트에 등록되지 않았다면, 이 클래스에 대한 프록시는 생성되지 않습니다. 따라서, AOP 등의 스프링 기능이 해당 클래스에 대해 적용되지 않습니다.
  3. 기본 생성자 및 프록시 문제:

    • 스프링의 AOP는 주로 프록시 패턴을 통해 구현되며, 스프링 컨테이너에 관리되는 빈에 대해서만 프록시가 생성됩니다. private val advice: Runner = Advice()와 같이 수동으로 객체를 생성하고 이를 빈으로 등록된 객체로 대체하지 않는다면, 자동 생성된 프록시는 이 객체에 대해 적용되지 않습니다. 따라서, AOP 기능도 적용되지 않습니다.

유사한 내용이나 프록시와 관련된 AOP 적용 사례에 대한 정보를 원하신다면 다음 링크를 참고할 수 있습니다:

저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다. 현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏 추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.

HAHA님의 프로필 이미지
HAHA

작성한 질문수

질문하기