데브코스 19일차 TIL

Heesu Song·2025년 3월 29일

데브코스 - 백엔드

목록 보기
14/32
post-thumbnail

복습

AOP - Proxy Pattern

  • 주 관심사에서 벗어난 횡단 관심사들을 모듈화 해서 관리 → 유지 보수성과 가독성을 올림
  • IoC이기 때문에 Proxy 객체를 대신 생성 → JDK 동적 Proxy와 CGLIB를 차용

프록시 패턴이 어려우면 디자인 패턴을 공부해볼 것 (GoF)

구상 클래스(Concrete Class)

  • new로 바로 생성할 수 있는 클래스
  • CGLIB → 구상 클래스를 동적으로 상속받은 클래스를 하나 만들어줌

보호 프록시 vs 가상 프록시

  • 접근을 제한하는 방식에 따라 구분

CGLIB(상속)

런타임 때 위빙 → 실행이 됐을 때

Enhancer

CGLIB에서 제공하는 클래스로 프록시 객체를 동적으로 생성할 때 사용.

  • 인터페이스 없이도 프록시 생성 가능
  • 대상 객체를 상위 클래스로 설정해줌
  • final 클래스나 final 메서드에서는 사용불가능

JDK 동적 Proxy

Reflection 방식 → 클래스 파일로 된 것을 꺼내다가 사용

 @Test
    @DisplayName("언어차원(JDK, Reflection)에서 제공하는 프록시 패턴 테스트")
    void T1() throws Exception {

        MockServiceImpl targetObject = new MockServiceImpl();
        SubLogicInvocationHandler handler = new SubLogicInvocationHandler(targetObject);

        MockService proxy = (MockService) Proxy.newProxyInstance(
                MockService.class.getClassLoader()
                , new Class[]{MockService.class}
                , handler
        );

        proxy.logic1();
        proxy.logic2();

    }
  • MockService - 인터페이스

  • MockServiceImple - 구현 클래스

  • 첫번째 인자 MockService.class.getClassLoader() → 클래스 로더가 필요

  • 두번째 인자 new Class[]{MockService.class} → 구현할 인터페이스 목록(목록인 이유는 다중 구현일 수도 있어서)


Spring - AOP 실습

인터페이스 - JDK 동적 Proxy

실제로 AOP를 사용할 때 MethodInterceptor를 사용

advice

ProxyFactory → 프록시 객체를 생성해줌 (SpringBoot에서는 어노테이션으로 쉽게 생성해줌)

  • addAdvice() → 안에 위에서 작성한 advice를 넣어주면 됨 (SubLogicInterceptor)
  • 인터페이스가 있는지 확인 → 있으면 JDK
  • 인터페이스이기 때문에 다운캐스팅 가능

구상클래스 - CGLIB

new 키워드를 사용해서 직접 객체 생성

상속받은 객체는 CGLIB가 동적으로 만들어주기 때문에 우리는 몰라도 됨

@Test
    @DisplayName("CGLIB 적용!")
    void test1() throws Exception {

        UserService userService = new UserService();

        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(UserService.class);
        enhancer.setCallback(new UserServiceInterceptor(userService));

        UserService proxy = (UserService) enhancer.create();
        proxy.createUser();

    }

❗️SpringAOP의 문제❗️

  • JDK랑 CGLIB를 적용할 때 구현해야 되는 advice가 다름 → 범용성이 떨어짐
  • 이 문제를 해결하기 위한 방법 → MethodInterceptor 인터페이스 제공(aopalliance의) → ProxyFactory가 advice를 가지고 방식에 맞게 구현해줌
  • SpringBoot에서는 이 과정까지 Bean을 이용해서 생략해줌 → Aspect(Advice) 만 만듦면 대상 클래스를 Proxy로 바꿔서 등록해준다. (추상화)

SpringBoot - AOP 실습

Advice 종류

@Before대상 메서드 실행 전에 Advice 실행
@After대상 메서드 실행 후에 Advice 실행
@AfterReturning정상적으로 실행 완료 후 Advice 실행
@AfterThrowing예외 발생 시 Advice 실행
@Around대상 메서드 실행 전/후 모두 Advice 실행

Aspect 적용 코드

@Slf4j
@Aspect //내부적으로 프록시를 만들어서 적용시켜줌
@Component
public class LoggingAspect {

    //AopService 의 logic()
    //메서드를 구분하기 위해 패키지를 지정해주면 됨
    @Around("execution(public void io.shs0160.greppaop.spring_proxy.app.AopService.logic())")
    //point Cut을 표현식으로 지정 -> execution(접근제어자 반환타입 [선언타입]메서드이름(파라미터) [예외])
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        //around -> 전 후 다 적용
        //join point 를 실행되는 시점으로 잡을 수 있음(언제)
        log.info("[LoggingAspect] 횡단 관심사 로깅 시작!");
        Object result = joinPoint.proceed();
        log.info("[LoggingAspect] 횡단 관심사 로깅 종료!");

        //point cut 으로 어디에 적용시킬건지 결정
        return result;
    }

}

💡Point Cut 표현식
-
어떤 메서드에 Advice를 적용할지 결정하는 표현식

execution(접근제어자 리턴타입 패키지.클래스.메서드(매개변수))

*모든 값 (와일드카드)
..하위 패키지, 여러 개의 인자를 받음
+해당 클래스와 모든 자식 클래스 포함 (상속 포함)
  • execution(*) : 메서드 실행을 기준으로 Pointcut 지정
  • within(*) : 특정 클래스나 패키지 내부의 메서드만 지정
  • target() vs this() : 프록시 적용 여부에 따라 다름
  • args(*) : 특정 인자를 가진 메서드만 지정
  • @annotation(*) : 특정 애노테이션이 붙은 메서드만 지정

Pointcut 표현식

execution( com.example.service..*(..))com.example.service 패키지의 모든 클래스, 모든 메서드에 적용
execution( com.example.service.MyService.(..))MyService 클래스의 모든 메서드에 적용
execution( com.example.service.MyService.get(..))MyService 클래스의 get으로 시작하는 모든 메서드에 적용
execution(public * *(..))모든 public 메서드에 적용
execution(* *(..))모든 메서드에 적용
execution( com.example...*(..))com.example 패키지와 그 하위 패키지 전체에 적용
execution( com.example...*(String, ..))첫 번째 인자가 String인 모든 메서드에 적용

특정 조건을 추가한 표현식

within(com.example.service..*)com.example.service 패키지 내부의 모든 클래스에 적용
within(com.example.service.MyService)특정 클래스(MyService) 내부의 메서드에 적용
this(com.example.service.MyService)MyService의 인스턴스(프록시 포함)에 적용
target(com.example.service.MyService)MyService의 원본 객체에 적용
args(java.lang.String)인자가 String 타입인 메서드에 적용
@within(org.springframework.stereotype.Service)@Service 애노테이션이 붙은 클래스 내부의 메서드에 적용
@annotation(org.springframework.transaction.annotation.Transactional)@Transactional이 붙은 메서드에 적용

Test 코드

Test 클래스 만드는 방법 → Shift + Cmd + T

//통합 테스트
@Slf4j
@SpringBootTest
public class AopServiceTests {
    //TDD -> 테스트 주도 개발
    //DDD -> 도메인 주도 개발

    @Autowired
    AopService aopService;

    @Autowired
    NonAopService nonAopService;

    @Test
    @DisplayName("주입 여부 확인")
    void 주입_확인() throws Exception {
        //log.info("AopService = {}", aopService);
        aopService.logic();
        log.info("============");
        //point cut 때문에 non은 적용이 안돼있는걸 볼 수 있음
        nonAopService.logic1();
    }
}

결과 확인

Controller로 테스트

@Controller
@RequiredArgsConstructor
public class AopController {

    private final AopService aopService;

    @ResponseBody
    @GetMapping("/aop")
    public String aop() {
        aopService.logic();
        return "OK";
    }
}

결과 확인


Spring MVC

요청을 받고 처리

종속성

  • Implementation: 컴파일 + 런타임 시 필요
  • compileOnly : 컴파일 시 필요하지만, 실행 시에는 필요 없음
  • RuntimeOnly: 컴파일 시에는 필요 없고, 실행 시에만 필요

@Controller → Controller 역할을 하는 Bean 생성 / ServletContext에 올라감

  • Http Response와 Request를 받을 수 잇음

@RequestMapping → path와 method를 기재해 줘야함

@GetMapping

→ 동작원리

  • 메서드 안에 @RequestMapping 이 들어있음
  • @PostMapping 도 똑같다

@ResponseBody

  • 더 이상 뷰를 찾지 않고 메서드에서 return하는걸 응답에 직접 넣어준다.
  • 반환하는 타입에 따라서
    • String -> text/html
    • 그 외 -> application/JSON

view가 없어서 직접 html을 보낼 수 있음

직렬화 → 객체를 통신 가능한 단위로 바꾸는 것

  • jackson 라이브러리로 가능 → JSON형식으로 변환
  • 어떤 값이든 key, value로 관리 가능

AOP는 김영한 강의 들을 때 SpringBoot로 써본적이 있었는데, 이번에 Spring으로 직접 만들어 사용해보니까 생각했던거 보다 훨씬 어려웠다.. 그래도 이제 더 이상 이렇게 깊게 공부할 일 없을테니까 괜찮겠지ㅜ
(AOP 안녕,,~ 한동안은 보지 말자)

와~! 데브코스를 시작한 지 벌써 한 달이 지났다.
처음에는 너무 시간이 안 가서 5개월을 어떻게 버티지 막막했는데 나름 적응된 것 같아 다행이다.
솔직히 초반에는 배우는 내용이 나한테 어렵기도 하고, 뭔가 나보다 전부 앞서 나가있는 것 같다는 느낌이 들어서 우울한 생각을 많이 하기도 했다.😢 앞으로 5개월이나 남았는데 계속 이런 식이면 어떡하지? 하는 걱정도 했고, ‘내가 이 길을 가는 게 맞나’하는 의구심도 들었다. 물론 그 고민은 여전히 ing이다…ㅎ

그래도 이제는 최대한 공부에만 집중해 보려고 노력하는 중이다. 부트캠프를 하면서 정말 열심히 하는 사람들의 모습에 자극을 받기도 했고, 일단 지금 내가 놓여진 이 상황에 최선을 다 해야 나중에 덜 후회할거 같아서.. 물론 이제 Spring을 하기 시작하니까 나름 재미있어서 그런 것도 사실이다..ㅎ
아직 4개월 이라는 긴 시간이 남았지만 후회없게 (제발..)최선을 다 해보자! 화이팅~

profile
Abong_log

0개의 댓글