1. 1차캐시

1차 캐시는 영속성 컨텍스트의 핵심 구성 요소 중 하나다. 이는 @Id (Entity의 식별자)를 키로, 해당 Entity 객체를 값으로 가지는 내부 맵 구조를 가지고 있다. 이러한 구조 덕분에, 특정 Entity를 조회할 때 데이터베이스를 직접 조회하기 전에 1차 캐시에서 해당 Entity를 찾을 수 있어 성능 이점을 가져오게 된다. 그러나, 이 1차 캐시의 생명 주기는 트랜잭션과 연결되어 있어 그 이점은 트랜잭션 범위에서만 유효하다.

2. 동일성 보장

JPA는 동일한 트랜잭션 내에서 같은 식별자를 가진 Entity에 대해 동일성(==)을 보장하게 한다. 이는 1차 캐시 덕분에 가능하다.

 

3. 트랜잭션을 지원하는 쓰기 지연

JPA는 Entity를 영속화(em.persist())할 때 즉시 데이터베이스에 쿼리를 전송하지 않는다. 대신, 해당 쿼리는 "쓰기 지연 SQL 저장소"에 임시로 저장되며, 트랜잭션이 커밋되는 시점에 데이터베이스로 전송된다.

4. 변경 감지 (더티 체킹)

JPA는 1차 캐시 내의 Entity 객체의 상태 변화를 감지할 수 있다. 이를 "더티 체킹"이라고 하며, 이 기능 덕분에 개발자는 직접 업데이트 쿼리를 작성할 필요 없이 객체의 상태만 변경하면 JPA가 이를 감지하고 적절한 쿼리를 생성해 데이터베이스에 반영한다.

'Java > JPA' 카테고리의 다른 글

JPA - 상속관계 매핑, @MappedSuperclass  (0) 2023.10.04
JPA - 연관관계  (0) 2023.10.01
JPA - 기본 키 매핑 : @GeneratedValue  (0) 2023.09.23
JPA - 플러시, 준영속 상태  (0) 2023.09.20
JPA - 영속성 컨텍스트(1)  (0) 2023.09.18

1. 영속성 컨텍스트란?

영속성 컨텍스트는 JPA의 핵심 기능 중 하나로, 엔티티 인스턴스를 영구적으로 저장하고 관리하는 환경이다.

2. 엔티티 매니저

엔티티 매니저는 JPA에서 제공하는 API로, 이를 통해 영속성 컨텍스트에 접근하고 엔티티에 대한 CRUD 연산을 할 수 있다.

3. 엔티티의 생명주기

엔티티는 크게 4단계의 생명주기를 가진다.

비영속(new / transient)

  • 영속성 컨텍스트와 전혀 관계없는 새로운 상태

영속 (managed)

  • 영속성 컨텍스트에 관리되는 상태

준영속 (detached)

  • 영속성 컨텍스트에 저장되었다가 분리된 상태

삭제 (deleted)

4. 영속성 컨텍스트의 이점

영속성 컨텍스트에는 다음과 같은 이점이 있다.

  1. 1차 캐시
    • 영속성 컨텍스트 내부에는 엔티티를 저장하기 위한 메모리 공간인 1차 캐시가 존재한다.
    • 엔티티 매니저를 통해 엔티티를 조회하면, 먼저 1차 캐시에서 해당 엔티티를 찾아본다. 1차 캐시에 존재한다면 데이터베이스에 접근하지 않고 캐시된 값을 반환한다. 이로 인해 성능이 향상된다.
  2. 동일성 보장
    • 같은 트랜잭션 안에서 같은 엔티티를 조회하면 항상 동일한 인스턴스를 반환한다.
    • 이러한 동일성은 객체 지향 프로그래밍의 중요한 특징을 지원한다. 데이터베이스의 같은 레코드를 기반으로 생성된 엔티티가 메모리에서도 동일한 것을 보장하기 때문에 데이터의 일관성을 유지할 수 있다.
  3. 트랜잭션을 지원하는 쓰기 지연
    • 영속성 컨텍스트는 트랜잭션 내에서의 변경사항을 모아두고, 트랜잭션 커밋 시점에 한꺼번에 데이터베이스에 반영한다.
    • 이렇게 변경사항을 한 번에 데이터베이스에 반영하면 네트워크 사용량이 줄어들고, 데이터베이스 액세스 횟수도 감소하여 성능이 향상된다.
    • 또한, 중간에 오류가 발생하면 트랜잭션 롤백을 통해 이전 상태로 쉽게 되돌릴 수 있다.
  4. 지연로딩
    • 연관된 엔티티나 컬렉션을 실제로 사용하는 시점에 데이터베이스에서 로딩하는 기능이다.
    • 초기 로딩 시 필요하지 않은 데이터를 로딩하지 않기 때문에 성능을 최적화할 수 있다.
    • 애플리케이션의 반응 시간을 향상시킬 수 있으며, 불필요한 데이터베이스 접근을 줄일 수 있다.

'Java > JPA' 카테고리의 다른 글

JPA - 상속관계 매핑, @MappedSuperclass  (0) 2023.10.04
JPA - 연관관계  (0) 2023.10.01
JPA - 기본 키 매핑 : @GeneratedValue  (0) 2023.09.23
JPA - 플러시, 준영속 상태  (0) 2023.09.20
JPA - 영속성컨텍스트(2)  (0) 2023.09.20
package com.example.demo.exam.internalcall;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class CallServiceV0 {
    public void external(){
        log.info("call external");
        internal(); // 내부 메서드 호출(this.internal())
    }

    public void internal(){
        log.info("call internal");
    }
}

위같은 경우처럼 external() 메서드 내부에 internal(); 메서드를 호출하게 되면 internal메서드는 내부에서 external 내부에서 호출되기 때문에 프록시객체가 생성되지 않는다.

aop=void com.example.demo.exam.internalcall.CallServiceV0.external()
call external
call internal
target=class com.example.demo.exam.internalcall.CallServiceV0$$EnhancerBySpringCGLIB$$e7d7dbab

 

이 경우에는 internal() 메서드 자체를 따로 클래스로 빼서 호출하면 해결된다.

 

package com.example.demo.exam.internalcall;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class InternalService {

    public void internal(){
        log.info("call internal");
    }
}
package com.example.demo.exam.internalcall;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@RequiredArgsConstructor
public class CallServiceV3 {
    private final InternalService internalService;
    public void external(){
        log.info("call external");
        internalService.internal();
    }
}

이렇게 되면 원하는대로 프록시가 2번 호출이 된다.

aop=void com.example.demo.exam.internalcall.CallServiceV3.external()
call external
aop=void com.example.demo.exam.internalcall.InternalService.internal()
call internal

'Java > 스프링 AOP' 카테고리의 다른 글

스프링 AOP - 매개변수 전달  (0) 2023.09.16
스프링 AOP - execution(2)  (0) 2023.09.14
스프링 AOP - execution(1)  (0) 2023.09.12
Spring AOP - 어드바이스 종류  (0) 2023.09.11
Spring AOP - 포인트컷 분리  (0) 2023.09.07
package com.example.demo.pointcut;

import com.example.demo.member.MemberService;
import com.example.demo.member.annotation.ClassAop;
import com.example.demo.member.annotation.MethodAop;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;

@Slf4j
@Import(ParameterTest.ParameterAspect.class)
@SpringBootTest
public class ParameterTest {

    @Autowired
    MemberService memberService;

    @Test
    void success(){
        log.info("memberService Proxy ={}",memberService.getClass());
        memberService.hello("helloA");
    }

    @Slf4j
    @Aspect
    static class ParameterAspect{
        @Pointcut("execution(* com.example.demo.member..*.*(..))")
        private void allMember(){
        }

        @Around("allMember()")
        public Object logArgs1(ProceedingJoinPoint joinPoint) throws Throwable {
            Object arg1 = joinPoint.getArgs()[0];
            log.info("[logArgs1]{}, arg={}",joinPoint.getSignature(),arg1);
            return joinPoint.proceed();
        }

        @Around("allMember() && args(arg, ..)")
        public Object logArgs1(ProceedingJoinPoint joinPoint,Object arg) throws Throwable {
            log.info("[logArgs2]{}, arg={}",joinPoint.getSignature(),arg);
            return joinPoint.proceed();
        }

        @Before("allMember() && args(arg,..)")
        public void logArgs3(String arg){
            log.info("[logArgs3] arg={}",arg);
        }

        //this : 스프링 컨테이너에 올라간 녀석을 의미 - class com.example.demo.member.MemberServiceImpl$$EnhancerBySpringCGLIB$$e12e6b06
        @Before("allMember() && this(obj)")
        public void thisArgs(JoinPoint joinPoint, MemberService obj){
            log.info("[this]{}, obj={}",joinPoint.getSignature(),obj.getClass());
        }

        //target : 실제 객체를 의미 - class com.example.demo.member.MemberServiceImpl
        @Before("allMember() && target(obj)")
        public void targetArgs(JoinPoint joinPoint, MemberService obj){
            log.info("[target]{}, obj={}",joinPoint.getSignature(),obj.getClass());
        }

        @Before("allMember() && @target(annotation)")
        public void atTarget(JoinPoint joinPoint, ClassAop annotation){
            log.info("[@target]{}, annotation={}",joinPoint.getSignature(),annotation);
        }

        @Before("allMember() && @within(annotation)")
        public void atWithin(JoinPoint joinPoint, ClassAop annotation){
            log.info("[@within]{}, annotation={}",joinPoint.getSignature(),annotation);
        }

        @Before("allMember() && @annotation(annotation)")
        public void atAnnotation(JoinPoint joinPoint, MethodAop annotation){
            log.info("[@annotation]{}, annotationValue={}",joinPoint.getSignature(),annotation.value());
        }

    }
}
  			@Around("allMember()")
        public Object logArgs1(ProceedingJoinPoint joinPoint) throws Throwable {
            Object arg1 = joinPoint.getArgs()[0];
            log.info("[logArgs1]{}, arg={}",joinPoint.getSignature(),arg1);
            return joinPoint.proceed();
        }
[logArgs1]String com.example.demo.member.MemberServiceImpl.hello(String), arg=helloA

@Around("allMember()")

전체 인수를 가져온다.

현재는 첫 번째 인수만을 가져오고 있다.

 @Around("allMember() && args(arg, ..)")
        public Object logArgs1(ProceedingJoinPoint joinPoint,Object arg) throws Throwable {
            log.info("[logArgs2]{}, arg={}",joinPoint.getSignature(),arg);
            return joinPoint.proceed();
        }
[logArgs2]String com.example.demo.member.MemberServiceImpl.hello(String), arg=helloA

@Around("allMember() && args(arg, ..)")

여러 인수들 중 arg 인수를 가져온다.

@Before("allMember() && args(arg,..)")
        public void logArgs3(String arg){
            log.info("[logArgs3] arg={}",arg);
        }
[logArgs3] arg=helloA

@Before("allMember() && args(arg,..)")

바로 위와 동일하지만 차이점은 @Before를 사용하여 ProceedJoinPoint를 사용하지 않아도 인수를 받아 올 수 있다.

 @Before("allMember() && this(obj)")
        public void thisArgs(JoinPoint joinPoint, MemberService obj){
            log.info("[this]{}, obj={}",joinPoint.getSignature(),obj.getClass());
        }
[this]String com.example.demo.member.MemberServiceImpl.hello(String), obj=class com.example.demo.member.MemberServiceImpl$$EnhancerBySpringCGLIB$$af58c20f

@Before("allMember() && this(obj)")

현재 스프링 컨테이너에 올라와있는 객체를 가져오기때문에 프록시로 변조된 memberService를 가져온다.

@Before("allMember() && target(obj)")
        public void targetArgs(JoinPoint joinPoint, MemberService obj){
            log.info("[target]{}, obj={}",joinPoint.getSignature(),obj.getClass());
        }
[target]String com.example.demo.member.MemberServiceImpl.hello(String), obj=class com.example.demo.member.MemberServiceImpl

@Before("allMember() && target(obj)")

실제 적용되는 객체 자체를 가져온다.

@Before("allMember() && @target(annotation)")
        public void atTarget(JoinPoint joinPoint, ClassAop annotation){
            log.info("[@target]{}, annotation={}",joinPoint.getSignature(),annotation);
        }
[@target]String com.example.demo.member.MemberServiceImpl.hello(String), annotation=@com.example.demo.member.annotation.ClassAop()

@Before("allMember() && @target(annotation)")

@target은 부모 클래스의 메서드까지 어드바이스를 적용한다.

@Before("allMember() && @within(annotation)")
        public void atWithin(JoinPoint joinPoint, ClassAop annotation){
            log.info("[@within]{}, annotation={}",joinPoint.getSignature(),annotation);
        }
[@within]String com.example.demo.member.MemberServiceImpl.hello(String), annotation=@com.example.demo.member.annotation.ClassAop()

@Before("allMember() && @within(annotation)")

@within은 자기 자신의 클래스에 정의된 메서드에만 어드바이스를 적용한다

@Before("allMember() && @annotation(annotation)")
        public void atAnnotation(JoinPoint joinPoint, MethodAop annotation){
            log.info("[@annotation]{}, annotationValue={}",joinPoint.getSignature(),annotation.value());
        }
[@annotation]String com.example.demo.member.MemberServiceImpl.hello(String), annotationValue=test value

@Before("allMember() && @annotation(annotation)")

메서드의 어노테이션을 전달 받는다.

 

@target과 @within의 차이

 

공부할때 @target은 부모까지 호출하고 @within은 지정한것만 호출한다고 했는데, 이 테스트 예제에서는 나오지 않아서 직접 실험해보았다.

BaseService라는 인터페이스가 아닌 클래스를 생성한다.

package com.example.demo.member;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class BaseService {
    public void baseMethod() {
        log.info("BaseService.BaseMethod");
    }

    public void commonMethod() {
        log.info("BaseService.commonMethod");
    }
}

이후 MemberServiceImpl에 BaseService를 extends 한다.

package com.example.demo.member;

import com.example.demo.member.annotation.ClassAop;
import com.example.demo.member.annotation.MethodAop;
import org.springframework.stereotype.Component;

@ClassAop
@Component
public class MemberServiceImpl extends BaseService implements MemberService{
    @Override
    @MethodAop("test value")
    public String hello(String param) {
        return "ok";
    }
    public String internal(String param){
        return "ok";
    }

    @Override
    public void commonMethod() {

    }
}

테스트케이스에 메서드들을 추가한다.

baseMethod는 오버라이딩하지 않았으므로 부모클래스인 BaseService에서 호출된다.

@Test
    void success(){
        log.info("memberService Proxy ={}",memberService.getClass());
        memberService.hello("helloA");
        memberServiceImpl.baseMethod();
        memberServiceImpl.commonMethod();
    }

확인해보면 @target은 오버라이드하지 않은 baseMethod까지 인식하는것을 볼 수 있다.

memberService Proxy =class com.example.demo.member.MemberServiceImpl$$EnhancerBySpringCGLIB$$8a95afdc
[@annotation]String com.example.demo.member.MemberServiceImpl.hello(String), annotationValue=test value
[@target]String com.example.demo.member.MemberServiceImpl.hello(String), annotation=@com.example.demo.member.annotation.ClassAop()
[@within]String com.example.demo.member.MemberServiceImpl.hello(String), annotation=@com.example.demo.member.annotation.ClassAop()
[@target]void com.example.demo.member.MemberServiceImpl.baseMethod(), annotation=@com.example.demo.member.annotation.ClassAop()
BaseService.BaseMethod
[@target]void com.example.demo.member.MemberServiceImpl.commonMethod(), annotation=@com.example.demo.member.annotation.ClassAop()
[@within]void com.example.demo.member.MemberServiceImpl.commonMethod(), annotation=@com.example.demo.member.annotation.ClassAop()

'Java > 스프링 AOP' 카테고리의 다른 글

스프링 AOP - 내부호출 문제  (0) 2023.09.17
스프링 AOP - execution(2)  (0) 2023.09.14
스프링 AOP - execution(1)  (0) 2023.09.12
Spring AOP - 어드바이스 종류  (0) 2023.09.11
Spring AOP - 포인트컷 분리  (0) 2023.09.07
 @Test
    void typeExactMatch() {
        pointcut.setExpression("execution(* com.example.demo.member.MemberServiceImpl.*(..))");
        Assertions.assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
    }

    @Test
    void typeExactMatchSuperType() {
        pointcut.setExpression("execution(* com.example.demo.member.MemberService.*(..))");
        Assertions.assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
    }

    @Test
    void typeMatchInternal() throws NoSuchMethodException {
        pointcut.setExpression("execution(* com.example.demo.member.MemberServiceImpl.*(..))");

        Method internalMethod = MemberServiceImpl.class.getMethod("internal", String.class);
        Assertions.assertThat(pointcut.matches(internalMethod, MemberServiceImpl.class)).isTrue();
    }

    @Test
    void typeMatchNoSuperTypeMethodFalse() throws NoSuchMethodException {
        pointcut.setExpression("execution(* com.example.demo.member.MemberService.*(..))");

        Method internalMethod = MemberServiceImpl.class.getMethod("internal", String.class);
        Assertions.assertThat(pointcut.matches(internalMethod, MemberServiceImpl.class)).isFalse();
    }
    //String 타입의 파라미터 허용
    //(String)
    @Test
    void argsMatch() {
        pointcut.setExpression("execution(* *(String))");
        Assertions.assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
    }

    //파라미터가 없어야 함
    //()
    @Test
    void argsMatchNoArgs() {
        pointcut.setExpression("execution(* *())");
        Assertions.assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isFalse();
    }

    //정확히 하나의 파라미터만 허용, 모든 타입 허용
    //(Xxx)
    @Test
    void argsMatchStar() {
        pointcut.setExpression("execution(* *(*))");
        Assertions.assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
    }

    //숫자와 무관하게 모든 파라미터, 모든 타입 허용
    //(), (Xxx), (Xxx, Xxx)
    @Test
    void argsMatchAll() {
        pointcut.setExpression("execution(* *(..))");
        Assertions.assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
    }

    //String 타입으로 시작, 숫자와 무관하게 모든 파라미터, 모든 타입 허용
    //(String), (String, Xxx), (String, Xxx, Xxx)
    @Test
    void argsMatchComplex() {
        pointcut.setExpression("execution(* *(String, ..))");
        Assertions.assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
    }

타입 매칭 - 부모 타입에 있는 메서드만 허용

부모 타입을 표현식에 선언한 경우 부모 타입에서 선언한 메서드가 자식 타입에 있어야 매칭에 성공한다.

즉, 부모 타입에 있는 메서드만 가능하다

 

파라미터 매칭

  • (String) : 정확하게 String 타입 파라미터
  • () : 파라미터가 없어야 한다.
  • (*) : 정확히 하나의 파라미터, 단 모든 타입을 허용한다.
  • (*, *) : 정확히 두 개의 파라미터, 단 모든 타입을 허용한다.
  • (..) : 숫자와 무관하게 모든 파라미터, 모든 타입을 허용한다. 파라미터가 없어도 된다. 0..* 로 이해하면 된다.
  • (String, ..) : String 타입으로 시작해야 한다. 숫자와 무관하게 모든 파라미터, 모든 타입을 허용한다. 

 

'Java > 스프링 AOP' 카테고리의 다른 글

스프링 AOP - 내부호출 문제  (0) 2023.09.17
스프링 AOP - 매개변수 전달  (0) 2023.09.16
스프링 AOP - execution(1)  (0) 2023.09.12
Spring AOP - 어드바이스 종류  (0) 2023.09.11
Spring AOP - 포인트컷 분리  (0) 2023.09.07

executuon은 포인트컷 지시자 라고 하는데, 포인트컷 지시자에는 execution외에도 여러가지 표현지시자들이 존재한다.

  • execution : 메소드 실행 조인 포인트를 매칭한다. 스프링 AOP에서 가장 많이 사용하고, 기능도 복잡하다.
  • within : 특정 타입 내의 조인 포인트를 매칭한다.
  • args : 인자가 주어진 타입의 인스턴스인 조인 포인트
  • this : 스프링 빈 객체(스프링 AOP 프록시)를 대상으로 하는 조인 포인트
  • target : Target 객체(스프링 AOP 프록시가 가르키는 실제 대상)를 대상으로 하는 조인 포인트
  • @target : 실행 객체의 클래스에 주어진 타입의 애노테이션이 있는 조인 포인트
  • @within : 주어진 애노테이션이 있는 타입 내 조인 포인트
  • @annotation : 메서드가 주어진 애노테이션을 가지고 있는 조인 포인트를 매칭
  • @args : 전달된 실제 인수의 런타임 타입이 주어진 타입의 애노테이션을 갖는 조인 포인트
  • bean : 스프링 전용 포인트컷 지시자, 빈의 이름으로 포인트컷을 지정한다.

 

어노테이션 인터페이스 ClassAOP

@Target의 ElementType코드의 위치나 범위를 나타내는 열거형 타입이며, @Target 어노테이션과 함께 사용되어, 어노테이션의 적용 범위를 제한한다.

ElementType.TYPE의 TYPE클래스, 인터페이스(어노테이션 타입 포함), 또는 열거형 선언에 어노테이션을 사용할 수 있음을 나타낸다.

@Retention의 RetentionPolicy어노테이션이 어디까지 유지되는지를 결정하며

SOURCE, CLASS, RUNTIME 으로 나뉜다.

package com.example.demo.member.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ClassAop {
}

어노테이션 인터페이스 MethodAOP

package com.example.demo.member.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD) //메서드에 붙이는 어노테이션
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodAop {
    String value();
}

서비스와 구현체

package com.example.demo.member;

public interface MemberService {
    String hello(String param);
}

package com.example.demo.member;

import com.example.demo.member.annotation.ClassAop;
import com.example.demo.member.annotation.MethodAop;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

@ClassAop
@Component
public class MemberServiceImpl implements MemberService{
    @Override
    @MethodAop("test value")
    public String hello(String param) {
        return "ok";
    }

    public String internal(String param){
        return "ok";
    }
}

ExecutionTest

package com.example.demo.pointcut;

import com.example.demo.member.MemberServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;

import java.lang.reflect.Method;

@Slf4j
public class ExecutionTest {
    AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
    Method helloMethod;

    @BeforeEach
    public void init() throws NoSuchMethodException {
        helloMethod = MemberServiceImpl.class.getMethod("hello",String.class);
    }

    @Test
    void printMethod(){
        //public java.lang.String com.example.demo.member.MemberServiceImpl.hello(java.lang.String)
        log.info("helloMethod={}",helloMethod);
    }

    @Test
    void exactMatch() {
        //public java.lang.String com.example.demo.member.MemberServiceImpl.hello(java.lang.String)
        pointcut.setExpression("execution(public java.lang.String com.example.demo.member.MemberServiceImpl.hello(java.lang.String))");
        Assertions.assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
    }

    @Test
    void allMatch() {
        pointcut.setExpression("execution(* *(..))");
        Assertions.assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
    }

    @Test
    void nameMatch() {
        pointcut.setExpression("execution(* hello(..))");
        Assertions.assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
    }

    @Test
    void nameMatchStar1() {
        pointcut.setExpression("execution(* hel*(..))");
        Assertions.assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
    }

    @Test
    void nameMatchStar2() {
        pointcut.setExpression("execution(* *el*(..))");
        Assertions.assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
    }

    @Test
    void nameMatchFalse() {
        pointcut.setExpression("execution(* nono(..))");
        Assertions.assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isFalse();
    }

    @Test
    void packageExactMatch1() {
        pointcut.setExpression("execution(* com.example.demo.member.MemberServiceImpl.hello(..))");
        Assertions.assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
    }

    @Test
    void packageExactMatch2() {
        pointcut.setExpression("execution(* com.example.demo.member.*.*(..))");
        Assertions.assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
    }

    @Test
    void packageExactFalse() {
        pointcut.setExpression("execution(* com.example.*.*(..))");
        Assertions.assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isFalse();
    }

    @Test
    void packageMatchSubPackage1() {
        pointcut.setExpression("execution(* com.example..*.*(..))");
        Assertions.assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
    }

    @Test
    void packageMatchSubPackage2() {
        pointcut.setExpression("execution(* com.example..*.*(..))");
        Assertions.assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
    }
}

execution 문법

execution(접근제어자? 반환타입 선언타입?메서드이름(파라미터) 예외?)

  • 메소드 실행 조인 포인트를 매칭한다.
  • ?는 생략할 수 있다.
  • * 같은 패턴을 지정할 수 있다
  • '.' : 정확하게 해당 위치의 패키지
  • '..' : 해당 위치의 패키지와 그 하위 패키지도 포함

 

'Java > 스프링 AOP' 카테고리의 다른 글

스프링 AOP - 매개변수 전달  (0) 2023.09.16
스프링 AOP - execution(2)  (0) 2023.09.14
Spring AOP - 어드바이스 종류  (0) 2023.09.11
Spring AOP - 포인트컷 분리  (0) 2023.09.07
Spring AOP 용어정리  (0) 2023.09.06
package com.example.demo.order.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;

@Slf4j
@Aspect
public class AspectV6Advice {

   @Around("com.example.demo.order.aop.Pointcuts.orderAndService()")
    //com.demo.order 패키지와 하위 패키지 이면서 클래스 이름이 *Service
    public Object doTransaction(ProceedingJoinPoint joinPoint) throws Throwable{
        try{
            //@Before
            log.info("[트랜잭션 시작] {}",joinPoint.getSignature());
            Object result = joinPoint.proceed();
            //@AfterReturning
            //@After
            log.info("[트랜잭션 커밋] {}",joinPoint.getSignature());
            return result;
        }catch (Exception e){
            //@AfterThrowing
            log.info("[트랜잭션 롤백] {}",joinPoint.getSignature());
            throw e;
        }finally {

            log.info("[리소스 릴리즈] {}",joinPoint.getSignature());
        }
    }
    //기존에서 쓰던 ProceedingJoinPoint는 @Around에서만 사용하는 파라미터다. 여기서는 JoinPoint를 사용한다.
    //파라미터를 비워놔도 해당 포인트컷 내에서 코드가 실행된다. 무조건적으로 해당 위치에서 프로세스가 실행되기 전에 실행된다는 이야기.
    @Before("com.example.demo.order.aop.Pointcuts.orderAndService()")
    public void doBefore(JoinPoint joinPoint){
        log.info("[before] {} ",joinPoint.getSignature());
    }

    //@AfterReturning은 return한 값을 조작할 수 는 있으나, return 자체를 변경할 수는 없다.
    @AfterReturning(value = "com.example.demo.order.aop.Pointcuts.orderAndService()", returning = "result")
    public void doReturn(JoinPoint joinPoint,Object result){
        log.info("[return] {} return={} ",joinPoint.getSignature(),result);
    }
    //해당 포인트컷의 리턴타입이 무엇이냐에 따라서 여러 타입으로도 받을 수 있다.
    @AfterReturning(value = "com.example.demo.order.aop.Pointcuts.allOrder()", returning = "result")
    public void doReturn2(JoinPoint joinPoint,String result){
        log.info("[return2] {} return={} ",joinPoint.getSignature(),result);
    }

    @AfterThrowing(value = "com.example.demo.order.aop.Pointcuts.orderAndService()",throwing = "ex")
    public void doThrowing(JoinPoint joinPoint,Exception ex){
        log.info("[ex] {} message={} ",ex);
    }
    //@After는 finally 로직과 비슷하다고 보면 된다. 즉, 할수있는 것이 많이 없다.
    @After(value = "com.example.demo.order.aop.Pointcuts.orderAndService()")
    public void doAfter(JoinPoint joinPoint){
        log.info("[after] {} ",joinPoint.getSignature());
    }

}
  • @Before
    • 조인포인트를 실행하기 전 
    • @Around와 다르게 작업 흐름을 변경할 수 없다.
    • ProceedingJoinPoint.proceed()를 사용하지 않는다.
    • 메서드 종료 시 자동으로 다음 타겟이 호출된다.
@AfterReturning(value = "hello.aop.order.aop.Pointcuts.orderAndService()",
returning = "result")
public void doReturn(JoinPoint joinPoint, Object result) {
 log.info("[return] {} return={}", joinPoint.getSignature(), result);
}
  • @AfterReturning
    • 메서드 실행이 정상적으로 반환될 때 실행 
    • returning 속성에 사용된 이름은 어드바이스 메서드의 매개변수 일므과 일치해야 한다.
    • returning 절에 지정된 타입의 값을 반환하는 메서드만 대상으로 실행한다.
    • @Around와 다르게 반환되는 객체를 변경할 수 는 없다. 반환 객체를 변경하려면 @Around를 사용해야 한다.하지만 조작할 수는 있다.
@AfterThrowing(value = "hello.aop.order.aop.Pointcuts.orderAndService()",
throwing = "ex")
public void doThrowing(JoinPoint joinPoint, Exception ex) {
 log.info("[ex] {} message={}", joinPoint.getSignature(), ex.getMessage());
}
  • AfterThrowing
    • 메서드 실행이 예외를 던져서 종료될 때 실행
    • throwing 속성에 사용된 이름은 어드바이스 메서드의 매개변수 이름과 일치해야 한다.
    • throwing 절에 지정된 타입과 맞는 예외를 대상으로 실행한다.
  •  @After
    • 메서드 실행이 종료되면 실행된다.
    • 정상 및 예외 반환 조건을 모두 처리한다.
    • 일반적으로 리소스를 해제하는 데 사용한다.
  • @Around
    • 메서드의 실행주변에서 실행된다. 메서드 실행 전후에 작업을 수행한다.
    • 가장 강력한 어드바이스
      • 조인 포인트 실행여부 선택 
      • 전달 값 변환 : joinPoint.proceed(args[])
      • 반환 값 변환
      • 예외 변환
      • 트랜직션 처럼 try~catch~finally 모두 들어가는 구문 처리 가능
    • 어드바이스의 첫 번째 파라미터는 ProceedingJoinPoint를 사용해야 한다
    • proceed()를 통해 대상을 실행한다.
    • proceed()를 여러번 실행 할 수도 있음(재시도)

 

'Java > 스프링 AOP' 카테고리의 다른 글

스프링 AOP - execution(2)  (0) 2023.09.14
스프링 AOP - execution(1)  (0) 2023.09.12
Spring AOP - 포인트컷 분리  (0) 2023.09.07
Spring AOP 용어정리  (0) 2023.09.06
Spring AOP - 핵심기능, 부가기능 , 애스펙트  (0) 2023.09.05

@Aspect는 애스펙트라는 표식이지 컴포넌트 스캔이 아니다. 따라서 AOP를 사용하려면 스프링 빈으로 등록해야한다.

스프링 빈으로 등록하는 방법은 다음과 같다.

  • @Bean 을 사용해서 직접 등록
  • @Component 컴포넌트 스캔을 사용해서 자동 등록
  • @Import 주로 설정 파일을 추가할 때 사용( @Configuration )

@Import 는 주로 설정 파일을 추가할 때 사용하지만, 이 기능으로 스프링 빈도 등록할 수 있다.

package com.example.demo.order.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

@Slf4j
@Aspect
public class AspectV1 {

    @Around("execution(* com.example.demo ..*(..)) ")
    public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable{
        log.info("[log] {}",joinPoint.getSignature()); // join point 시그니처
        return joinPoint.proceed();
    }
}

 

기존의 @Aspect를 사용할때는 위와 같이 @Around만 사용했지만,  @Pointcut도 같이 사용 가능하다.

 

package com.example.demo.order.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

@Slf4j
@Aspect
public class AspectV2 {
    //패키지와 하위 패키지
    @Pointcut("execution(* com.example.demo ..*(..)) ")
    private void allOrder(){} //pointcut signature

    @Around("allOrder()")
    public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable{
        log.info("[log] {}",joinPoint.getSignature()); // join point 시그니처
        return joinPoint.proceed();
    }
}

@Pointcut

@Pointcut에 포인트컷 표현식을 사용한다.

메서드 이름과 파라미터를 합쳐서 포인트컷 시그니처(signature)라 한다.

메서드의 반환 타입은 void여야 한다.

코드 내용은비워둔다.

@Around 어드바이스에서는 포인트컷을 직접 지정해도 되지만, 포인트컷 시그니처를 사용해도 된다. 여기서는
@Around("allORder()")를 사용한다.

private, public 같은 접근제어자는 내부에서만 사용하면 private를 사용해도 되지만, 다른 애스팩트에서 참고하려면 public을 사용해야한다.

 

결과적으로 AspectV1과 같은 기능을 수행하지만, 이렇게 분리하면 하나의 포인트컷 표현식을 여러 어드바이스에서 함께 사용할 수 있다. 또한, 다른 클래스에 있는 외부 어드바이스에서도 포인트컷을 함께 사용 할 수 있다.

'Java > 스프링 AOP' 카테고리의 다른 글

스프링 AOP - execution(1)  (0) 2023.09.12
Spring AOP - 어드바이스 종류  (0) 2023.09.11
Spring AOP 용어정리  (0) 2023.09.06
Spring AOP - 핵심기능, 부가기능 , 애스펙트  (0) 2023.09.05
@Aspect 프록시  (0) 2023.08.31

  • 조인 포인트(Join point)
    • 어드바이스가 적용될 수 있는 위치, 메소드 실행, 생성자 호출, 필드 값 접근, static 메서드 접근 같은 프로그램 실행 중 지점 
    • AOP를 적용할 수 있는 모든 지점
    • 스프링 AOP는 프록시 방식을 사용하므로 조인 포인트는 항상 메소드 실행 지점으로 제한
  • 포인트컷(Pointcut)
    • 조인 포인트 중에서 어드바이스가 적용될 위치를 선별하는 기능
    • 주로 AspectJ 표현식을 사용해서 지정
    • 프록시를 사용하는 스프링 AOP는 메서드 실행 지점만 포인트컷으로 선별 가능
  • 어드바이스(Advice)
    • 부가기능
    • 특정 조인 포인트에서 Aspect에 의해 취해지는 조치
    • Around, Before, After 와 같은 다양한 종류의 어드바이스가 존재
  • 타겟(Tartget)
    • 어드바이스를 받는 객체, 포인트컷으로 결정
  • 애스펙트(Aspect)
    • 어드바이스 + 포인트컷을 모듈화 한 것
    • @Aspect
    • 여러 어드바이스와 포인트컷이 함께 존재
  • 어드바이저(Advisor)
    • 1 어드바이스 + 1 포인트컷
    • 스프링 AOP에서만 존재하는 용어
  • 위빙(Weaving)
    • 포인트컷으로 결정한 타겟의 조인 포인트에 어드바이스를 적용하는것
    • AOP 적용을 위해 애스펙트를 객체에 연결한 상태
      • 컴파일 타임
      • 로드 타임
      • 런타임 - 스프링 AOP는 런타임, 프록시 방식, 위의 두 방식은 AspectJ가 필요하다.
  • AOP 프록시
    • AOP 기능을 구현하기 위해 만든 프록시 객체, 스프링에서 AOP 프록시는 JDK 동적 프록시 또는 CGLIB 프록시이다.

'Java > 스프링 AOP' 카테고리의 다른 글

Spring AOP - 어드바이스 종류  (0) 2023.09.11
Spring AOP - 포인트컷 분리  (0) 2023.09.07
Spring AOP - 핵심기능, 부가기능 , 애스펙트  (0) 2023.09.05
@Aspect 프록시  (0) 2023.08.31
실시간 하이픈 붙이기  (0) 2023.08.30

핵심기능은 애플리케이션의 주요 비즈니스 로직(상품 검색, 주문, 결제 등의 기능 등)을 의미하고,

부가기능은 애플리케이션의 여러 부분에 걸쳐 반복적으로 나타나는 기능(로깅, 트랜잭션 관리, 보안 체크, 에러 처리 등)을 의미한다.

Aspect는 여러 객체나 함수에 걸쳐서 반복되는 공통적인 관심사(기능)를 모듈화한 것을 의미합니다.

AOP는  핵심 로직에서 반복적으로 나타나는 부분(횡단 관심사)을 모듈화하여 여러 곳에 적용하는 기술이다. 이로 인해 코드의 중복성을 줄이고, 모듈의 재사용성을 높일 수 있어 많은 프레임워크와 애플리케이션에서 활용된다.

 

핵심 기능은 애플리케이션의 핵심 비즈니스 로직을, 부가 기능은 그 로직을 실행하는 동안 필요한 추가적인 작업들을 의미한다. AOP는 이 두 기능을 효과적으로 분리하고 관리하는 방법을 제공한다.

 

'Java > 스프링 AOP' 카테고리의 다른 글

Spring AOP - 포인트컷 분리  (0) 2023.09.07
Spring AOP 용어정리  (0) 2023.09.06
@Aspect 프록시  (0) 2023.08.31
실시간 하이픈 붙이기  (0) 2023.08.30
빈 후처리기 (2) 스프링 AOP  (0) 2023.08.24

+ Recent posts