AOP & 타입 ?

sally·2023년 7월 31일
0

spring

목록 보기
1/2

이번 달 글 주제 선정이… 자신 없고, 조심스럽습니다 ㅎㅎ
(다른 주제는 더 못 쓰겠네요 😑)

AOP 에 대한 기반은 토비님의 스프링 3.0 vol1,2 입니다.

코드 수준에서 주요 관점, 학습 포인트, 리팩토링 과정, 고민거리 들을 스텝마다 알려주는 책은 흔하지 않다고 생각하며, 매우 좋아요~ 👍

트랜잭션 적용 클래스가 늘면서 중복을 줄이고 자동화 하려는 과정이 진행 됩니다.

처음 reflection 을 사용하는 코드

2가지가 중요하게 보였는데요.

  • Method
  • target

근데 target 이 이상하게 전역변수로 있더라구요.

거기에 target은 Object 타입, 별도로 interface 타입을 알려줘야 하고 … (물론 DI와 데코레이터 패턴 개념으로 이유도 있고, 이해도 되지만, 그래도 등록 과정이 낯설었습니다.)

빈 등록시 중복 요소가 늘어나니

이런 점들을 개선하기 위해 Spring ProxyFactoryBean 방식에서는

Advice가 MethodInvocation 전달로 target 을 전달하게 하면서 보다 독립적으로 분리 됩니다.

  • Advisor 의 Advice, pointcut 이 보이기 시작

과정을 코드레벨에서 비교해보면 (아래 코드 이미지)
1 번째는 리플렉션 기반 Dynamic proxy로 InvocationHandler
2 번째는 Spring ProxyFactoryBean 방식으로 → 녹색 target 없어졌죠 ?
(속 내용은 단순하지 않으니 전 여기까지 … 코..코…콜)

1 번째

2 번째

제가 왜 Object target 에 집착하게 됐는지는 ….
DAO 구현으로 인터페이스와 그 구현체 Impl 등록이 낯설었거든요.
제가 스프링을 전혀 몰..몰…모르는게 많긴 하겠지만 .. 어쨌든 그런 내적 고민이 있을 수도… 그럴수도…

아무튼, 그렇게 ProxyFactoryBean 이 어차피 만들어 주게 했고, 적용할 클래스의 메서드에 대해 프록시 교체 방향으로 가게 됩니다.

  • 빈 후처리기 : 제목 보고서 … 설마 빈 등록후 ? 넵, (이름을 잘 지어야 한다)

인터페이스 타입과 데코레이터 패턴 특성상 위임 대상을 알아야 하다보니 프로퍼티로 가지고, 그 코드 기반으로 어떤 리플렉션 작업 후 타입으로 인한 빈 등록 문제, 비슷한 빈 등록 중복 문제 들을 해결해 나가는 과정들이 이어 집니다.

  • 메서드를 찾아가는 건 시작이었을 뿐
  • 빈과 타입은 밀접한 관계 ?

빈과 타입 관계 ?

registerBean() 매개변수가 여러 유형이 있는데, 이름을 지정해서 전달해도
중심에는 beanDefinition 으로, 이름 지정 된 경우 이름도 같이 전달 됩니다.

동일 타입 사용경험 ? 당연히 충돌 → @Primary 든 @Qualify 지정
이름과 타입 조회 차이는 타입 안정성…?

  • 이름 통한 빈 조회는 Object 반환
// HelloController helloController = (HelloController)applicationContext.getBean("helloController");
@Override
public Object getBean(String name) throws BeansException {
	assertBeanFactoryActive();
	return getBeanFactory().getBean(name);
}

// HelloController helloController = applicationContext.getBean(HelloController.class);
@Override
public <T> T getBean(Class<T> requiredType) throws BeansException {
	assertBeanFactoryActive();
	return getBeanFactory().getBean(requiredType);
}


스프링 사용하면서 DI 활용 안 하는 건 나쁜짓 ?

트랜잭션 지원을 예시로 어떻게 리팩토링 했을지 과정이 토비님 책에 자세히 나옵니다.

위에서 언급 했듯이 Obejct target과 인터페이스 타입을 알려주는 빈 등록 방식을 보면서
2가지를 주요하게 봤습니다.

  • 첫 번 째는 타입
  • 두 번째는 DI

메서드만 적용과 타입

메서드 레벨 관점에서 걍 이것저것 상상해 보면💭

  • 람다 (?) : excute-around-pattern
  • 전략패턴 : 확장(교체)
  • 템플릿 패턴 : 이런 동작하고, 이 타이밍에 너를!

사실 부가기능 이지만, 뭔가 주요 기능이니 이렇게 까지 중복되고 많이 쓴다는 느낌도 부정할 수 없었을 거 같은데요.
과정을 보며 세세한 차이점을 인지 하다보면, 포인트 컷 중심으로 타켓 찾고, 타겟은 프록시 생성과 빈 교체 대상이 됩니다.

  • 타입을 찾는게 아니라 포인트 컷이 기준
  • 타입은 클라이언트 입장에서의 OCP와, 클라이언트와 target 사이에서 interceptor 하기 위한 프록시를 위해

트랜잭션 부가 기능이 의외로 애매모호 한게 재밌었어요.

주요 기술 스택이면 스프링 프레임워크 단위에서 추상레벨에 의해 빈 등록 되어 관리대상이 되게 하는데, 그렇게 추상화한 PlatformTransactionManager 로도 안 되는 점

  • 메서드 별 커스터 마이징
  • 메서드는 클래스 → 타입이 가지고 있고, 스프링 컨테이너 DI 도 좋으니 OOP를 준수하자
  • 그렇게 변경 영향은 없는데, 중복 ?!
  • 객체는 책임과 역할을 SRP 준수하며 가지는데 트랜잭션 기능이 그렇게 갖고 있어도 될텐데 쉽게 안되겠는 상황인 것 같..습니다. 크흠

데코레이터 패턴?

어쨌든, 로우레벨에서 고민해보는 저라면
데코레이터 패턴이다 하고 보니까 target 이랑 보이지 않았을까,

작은 단위 메서드 관점에서만 보면
트랜잭션 기능을 각자 상속 받든,
기능 가진 클래스를 DI로 던져주든,
기능을 방문하게 하든,
기능 구현체 빈 등록 해주면 되지 않나 싶어질 거 같은데요
(기본적인.. ㅎㅎ)

과연 … 제가 어떤 방황을 할까 생각 해 봤어요.
(네, 쓸데없는짓 한다고 당일 고생 좀 했어요 🌧)

개인적이고 주관적인 경험으로 비지터 패턴에서 타입을 주요하게 봤던게 떠올라 시도해 봤어요 (중요 내용은 X) 🌌

디자인 패턴 인프런 강의 선생님, 백기선님 예제 코드로 시작해 봤습니다.
(비지터 패턴이 아니라 문제 코드 ㅎㅎㅎㅎ)

일부러 살짝 만든 나쁜 코드

  • 이름만 넘겨주면 되잖아 ? 상속으로 간단하게 ~

    • Shape 구현체에서 필요 기능만 처리 합니다.

      public abstract class Shape {
      	private final Device device;
      
      	public Shape(Device device) {
      		this.device = device;
      	}
      
      	public void printTo() {
      		final String message = String.format("print %s to %s", this.name(), device.name());
      		System.out.println(message);
      	}
      
      	protected abstract String name();
      }
      
  • 그냥 그곳에 가면 그곳의 룰이 있겠지

    • audience 가 각 타입 별 갈 곳을 알고 있어요

      public class Audience implements Human {
      	private final Ticket ticket;
      
      	public Audience(Ticket ticket) {
      		this.ticket = ticket;
      	}
      
      	@Override
      	public void move(Zootopia zootopia) {
      		if (zootopia instanceof PandaWorld) {
      			// 위치 확인  - 예약 -> 방사장
      				// 스마트 줄서기 예약 확인
      				// 현장 줄서기 확인
      			if (zootopia.checkReservation(this.ticket)) {
      				// 관람 - 푸바오, 아이바오, 러바오
      				see(zootopia.withCaution());
      				// 실외 방사장 - 아이바오, 러바오
      				// 실내 방사장 - 푸바오
      			}
      		} else if (zootopia instanceof PpuPpaTown) {
      			// 관람 - 카피바라, 왈라비
      			see(zootopia.withCaution());
      		}
      	}
  • 사실 관람객이 동물원을 방문 하겠죠.

    • 예약, 동물원 내 동물들, 사육사와 권한 등

      public class Audience implements Human {
      	private final Ticket ticket;
      
      	public Audience(Ticket ticket) {
      		this.ticket = ticket;
      	}
      
      	@Override
      	public void move(Zootopia zootopia) {
      		zootopia.visit(this);
      	}
  • Visitor 가 트랜잭션 전담하면서 방문객들한테 트랜잭션을 부여해 준다면 ?

    • 트랜잭션 내부에서 해당 타입 받아서 동작하게 한다면 ?
    • 이렇게 하다보니, 서비스 구현과 DAO 관계가 되지 않나 싶어졌어요.
    • 트랜잭션 전파는 ? 🙃 어떻게 되긴 되겠지만 stop

같은 문제 ? ㅎㅎㅎ

첫 번째 Shape 코드에서 타입 기준 로직 처리 무시하고
기능만 보고 필요 데이터만 전달하게 하는 (절차지향적) 방식은 간단하고 좋을 수 있지만,
바로 다음 예제 처럼 조금만 복잡해지고 로직 많아지면, 좋지 않은게 보였는데요.

비지터 패턴 학습 하면서 놀란게 this 전달 부분 이었습니다. ㅋㅋㅋ
(제가 당시 이 방법 밖에 없는데? 수정하면서도, 이래도 되나 .. 고민고민)

상속

SuperClazz의 fisrt(), second()

public void first() {
	log.info("super - first[{}]", this.getClass());
}

public void second() {
	 log.info("super - second[{}]", this.getClass());
   third();
}

SubClazz

public class SubClazz extends SuperClazz{
   @Override
   public void second() {
			log.info("sub - second[{}]", this.getClass());
   }
}

내부에서 모두 this 를 통해 getClass()를 호출 하고,
클라이언트에서 아래와 같이 호출 하면

sub.first();
sub.second();
superClazz.second();


self 참조

  • 재정의 하지 않은 sub.first() 는 부모 클래스로 올라가는데 this.getClass() 는 SubClazz 가 나옵니다.
  • 재정의한 sub.second()는 자신의 메서드를 호출하고 동일하게 this.getClass() 는 SubClazz 가 나옵니다.

메서드 별로 적용하게 하자니 타입 별 중복이 발생하고,
인터페이스 2개 등록하고 상속하자 생각도 들 때, CGLIB 가 지원 되는 거죠.

저번에 알림서비스 Pull/Push 모델에서는 해당 시점에 내가 데이터를 받을 것인가, 전달 할것인가 이벤트 관점을 봤었는데,

오늘은 메서드 전/후 동작 처리 관점이 타입과 DI 를 통해 어떻게 구현하게 하는가 주관적 관심이었습니다.

Aspect

그래서, 애스팩트 관점이 신선 했습니다.

객체 지향 프로그래밍을 많은 분들이 중시하면서 지향하고 싶어 했을 텐데 벽에 부딪히는 과정들이 다가왔고 (나는 다행이다😊)
결국 어떻게 결정을 내릴지!

그렇게 스프링 프레임워크가 다가 옵니다.

(모..모릅니다)

스프링
객체 지향 프로그래밍 기반으로 그 특징을 살린 비즈니스 개발 프로세스를 지원해주는 범용성 엔터프라이즈 애플리케이션

AOP aspect oriented programming

  • aspect : 어드바이저 단위 (포인트컷, 어드바이스)
  • 스프링은 가장 기본적인 aspect 단위로, 메서드 레벨의 포인트컷과 어드바이스로 구성된 프록시 방식의 AOP 를 지원 합니다.

리플렉션 기반으로 동작하면서 method 와 target 기준

각각 독립적 단위로 분리 하여도 객체 지향 프로그래밍의 한계선에서
aspect 관점을 정의 했습니다.

이 부분은 결국 POJO 기반의 프로그래밍 집중하도록 프레임워크가 대신 작업하도록 생각 했습니다.


제가 느낀 스프링 컨테이너는 빈등록이나 가져오는 과정에서도 타입이 매우 중요하게 느껴졌어요.

앞에서의 스토리를 더 이어나가면 빈 후처리기로 구현체를 등록하고 포인트컷이 맞으면 프록시 생성해서 프록시를 등록한 빈과 교체하고 DI 합니다.

  • 확장 ≠ 호출시점에 대한 핵심 비즈니스 로직에 부가적 기능을 동적으로 부여

인터페이스와 타입

인터페이스 사용하면 보다 해당 객체의 호출되는 기능이 제한적으로 관리하면서 변경 영향을 줄일 수 있습니다.

  • 정보은닉, 캡슐화, 저는 여기에 +알파

자세한 내용은 책을 참고해주세요 😅

부족하거나 틀린 부분은 알려주시면 감사하겟습니다.

감사합니다~


오늘 왠지 길어지면서 잘 정리 못 한듯 ...

날이 더워 카페에 갔다.
(더위에 약함) 분명 일기예보에는 비가 그렇게 많이 오지 않았었다.
비오더라도 버티고 오면 되겠지 가볍게 나갔고,
블로그 글 쓰다 비지터 패턴 예제를 구현하며 잠시.. 푸바오 동물원 상상에 빠져 예약과 티켓 끊으며 이거까지 해야하나 ? 하면서 못 헤어나오고 있었는데 …
카페 창가쪽에 비 셀 때는 비가 많이 오긴 오네 싶었는데 …
고개 드니 어느새 10 군데가 넘고, 바닥이 흥건 해지고, 폭염의 날씨는 비가 오면서 쌀쌀해지고, 아이스를 들이 붙던 저는 살짝 저체온증을 느끼며
정전을 피해 카페 밖 테라스에서 우산을 기다리다가 집에 왔는데 …
집 가까이라 괜찮을 줄 알았으나 옷과 신발은 다 젖고 ..
이 더운 여름날 한 달 넘게 엘레베이터 교체 중이라
(이 더위에, 이 습기에… 한 달 충분히 적응 안 됨 -_- )
꾸역꾸역 계단을 올라오며 집에오니 갑작스런 폭우로 뉴스에 나오고 있더라구요. ㅎㅎㅎ
정온 동물인 저는 … 이만 쉬어야 겠습니다. ?

profile
sally의 법칙을 따르는 bug Duck

2개의 댓글

comment-user-thumbnail
2023년 7월 31일

잘 읽었습니다. 좋은 정보 감사드립니다.

1개의 답글