springframework (4)

 

 

 

 

 

 

 

JPA(Java Persistence API)란?

 

 

➤ JPA(Java Persistence API)란?

 

  • ORM(Object-Relation Mapping) 기술의 표준 사양(또는 명세, Specification)
  • Java의 인터페이스로 사양이 정의되어 있고 JPA라는 표준 사양을 구현한 구현체는 따로 있다.

 

 

➤ Hibernate ORM

 

  • JPA 표준 사양을 구현한 구현체 : Hibernate ORM, EcliipseLink, DataNucleus
  • Hibernate ORM은 JPA에서 정의해둔 인터페이스를 구현한 구현체 중 하나이다.
  • JPA에서 지원하는 기능 이외에도 자체적으로 사용하도록 지원하는 API 도 있다.

 

 

※ Jakarta Persistence

 

Java Persistence API 의 약자이지만 현재는 Jakarta Persistence 라고도 불린다.

2019년에 나온 JPA 3.0 버전 부터 Jakarta Persistence 라는 이름으로 바뀌었다.

Jakarta Persistence 라고 바뀌면서 패키지와 속성도 javax.persistence 에서 jakarta.persistence 로 이름이 바뀌었다.

엔터프라이즈 Java 애플리케이션에서 관계형 데이터 관리를 설명하는 Jakarta EE 애플리케이션 프로그래밍 인터페이스 사양(specification)이다.

 

 

 

➤ 데이터 액세스 계층에서의 JPA 위치

 

 

 

데이터 액세스 계층에서 JPA는 상단에 위치한다.

데이터 저장, 조회 등의 작업은 JPA를 거쳐 JPA의 구현체인 Hibernate ORM을 통해서 이뤄진다.

Hibernate ORM은 내부적으로 JDBC API를 이용해서 데이터베이스에 접근한다.

 

 

 

➤ JPA(Java Persistence API)에서 P(Persistence)의 의미

 

Persistence는 영속성, 지속성이라는 뜻이다.

그렇다면 무엇을 지속시키는 것일까?

엔티티 객체 정보를 지속시킨다는 의미이다.

 

 

▶️ 영속성 컨텍스트(Persistence Context)

 

영속성 컨텍스트(Persistence Context)

 

ORM(Object-relational-mapping)은 객체(Object)와 데이터베이스 테이블의 매핑을 통해 엔티티 클래스 객체 안에 포함된 정보를 테이블에 저장하는 기술이다.

JPA에서는 테이블과 매핑되는 엔티티 객체 정보를 영속성 컨텍스트(Persistence Context)라는 곳에 보관해서 애플리케이션 내에서 오래 지속되도록 한다.

엔티티 정보는 데이터베이스 테이블에 데이터를 저장, 수정, 조회, 삭제하는데 사용된다.

 

 

JPA API 중 엔티티 정보를 영속성 컨텍스트에 저장(persist)하는 API를 사용하면 영속성 컨텍스트의 1차 캐시에 엔티티 정보가 저장된다.

 

 

JPA 영속성 컨텍스트

  • EntityManager 클래스에 의해서 관리된다. -> EntityManagerFactory 객체를 Spring으로부터 DI 받을 수 있다.
  • EntityManagerFactory의 createEntityManager() 메서드를 이용해서 EntityManager 객체를 얻을 수 있다. -> JPA API 메서드 사용 가능
  • persist() 메서드: 영속성 컨텍스트에 객체의 정보들이 저장된다.
  • 저장되었는지 확인하려면 find(조회할 엔티티 클래스의 타입, 조회할 엔티티 클래스의 식별자 값) 메서드 사용

 

 

 

➤ JPA API로 영속성 컨텍스트 이해하기

 

 

▶️ JPA API를 사용하기 위한 사전 준비

 

 

✅ build.gradle 설정

 

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	compileOnly 'org.projectlombok:lombok'
	runtimeOnly 'com.h2database:h2'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

 

Spring Data JPA 기술을 포함한 JPA API를 사용할 수 있다.

 

 

✅ application.yml에 아래 코드 추가

jpa:
	hibernate:
		ddl-auto: create  # (1) 스키마 자동 생성
	show-sql: true      # (2) SQL 쿼리 출력

 

※ yml에서 추가한 코드가 제대로 적용되지 않을 때는 공백 레벨을 잘 확인해보자.

 

Spring Data JDBC에서는 schema.sql 파일에 테이블 생성을 위한 스키마를 직접 작성해주어야 하지만 JPA는 (1)의 코드 하나를 추가함으로써 JPA가 자동으로 데이터베이스에 테이블을 생성해 준다.

 

 

▶️ @GeneratedValue

 

식별자를 생성해주는 전략을 지정할 때 사용

이 애너테이션을 엔티티의 멤버 변수에 추가하면 데이터베이스 테이블에서 기본키가 되는 식별자를 자동으로 설정해준다.

 

 

▶️ 영속성 컨텍스트에 저장하기

 

persist() 를 호출 -> 1차 캐시에 객체가 저장되고, 객체가 쓰기 지연 SQL 저장소에 INSERT 쿼리 형태로 등록이 된다.

영속성 컨텍스트에 객체를 저장하지만 실제 테이블에 정보를 저장하지는 않는다.

 

JPA가 내부적으로 테이블을 자동으로 생성하고, 테이블의 기본키를 할당해준다.

 

 

▶️ 데이터베이스 테이블에도 데이터 저장하기

 

EntityManagergetTrancsation() 메서드를 이용하여 Transaction 객체를 얻는다.

(JPA에서는 Transaction 객체를 기준으로 DB 테이블에 데이터를 저장한다.)

Transaction을 시작하기 위해서 begin() 메서드 사용

➡️ persist() 로 객체를 영속성 컨텍스트에 저장

➡️ commit() 메서드 호출하여 영속성 컨텍스트에 저장되어 있는 객체를 DB 테이블에 저장한다.

 

 

✔️ persist() : 영속성 컨텍스트의 1차 캐시에 엔티티 클래스의 객체가 저장 + ‘쓰기 지연 저장소’에 INSERT 쿼리가 등록

✔️ commit() : ‘쓰기 지연 SQL 저장소’에 등록된 모든 INSERT 쿼리가 실행 + 실행된 INSERT 쿼리는 쓰기 지연 SQL 저장소에서 제거됨

✔️ find() : 1차 캐시에서 해당 객체가 있는지 조회하고, 없으면 테이블에 SELECT 쿼리를 전송해서 조회

 

 

 

➤ 영속성 컨텍스트와 테이블에 엔티티 업데이트

 

find() 메서드로 1차 캐시에 저장된 객체를 불러와서 객체에 setter로 데이터를 변경

commit() 메서드로 SQL 저장소에 등록된 UPDATE 쿼리가 실행된다.

 

✅ setter 메서드로 값을 변경만 해도 commit() 시점에 UPDATE 쿼리가 실행되는 이유?

영속성 컨텍스트에 엔티티가 저장될 경우 저장되는 시점의 상태를 그대로 가지고 있는 스냅샷을 생성한다.

그 후 엔티티의 값을 setter 메서드로 변경한 후, commit()을 하면 변경된 엔티티와 이전에 이미 떠 놓은 스냅샷을 비교한 후 변경된 값이 있으면 쓰기 지연 SQL 저장소에 UPDATE 쿼리를 등록하고 UPDATE 쿼리를 실행

 

 

➤ 영속성 컨텍스트와 테이블의 엔티티 삭제

 

EntityManager의 remove() 메서드를 통해 1차 캐시에 있는 엔티티를 제거하도록 요청

Transaction의 commit() 을 실행하면 영속성 컨텍스트에 1차 캐시에 있는 엔티티를 제거하고, 쓰기 지연 SQL 저장소에 등록된 DELETE 쿼리가 실행된다.

 

 

 

▶️ EntityManager의 flush() API

 

transaction의 commit() 메서드를 호출하면 JPA 내부적으로 flush() 메서드가 호출되어 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영한다.

 

 

 

※ 추가로 학습해야할 자료들

 

JPA와 Hibernate 에서 Entity Lifecycle Model

https://thorben-janssen.com/entity-lifecycle-model/

 

Entity Lifecycle Model in JPA & Hibernate

The entity lifecycle model is one of the core concepts of JPA. It defines if an entity gets loaded, if changes get persisted, and much more

thorben-janssen.com

 

 

 

 

읽어주셔서 감사합니다.

오개념에 대한 지적은 언제나 환영입니다. 😄

 

 

 

 

 

 

 

Spring Data JDBC를 통한 데이터 액세스 계층 구현 (서비스, 레포지토리 구현)

 

 

➤ Repository Interface

 

Spring Data JDBC, Spring Data JPA에서 데이터 액세스 계층에서 데이터베이스와 상호작용하는 역할을 하는 인터페이스를 repository라고 한다.

 

기본적으로 CrudRepository를 상속하는 repository 인터페이스 작성한다.

ex) public interface MemberRepository extends CrudRepository<Member, long> { }

 

CrudRepository 인터페이스를 통해 데이터를 데이터베이스 테이블에 저장, 조회, 수정, 삭제가 가능하다.

 

 

 

➤ 쿼리 메서드(Query Method)

 

Spring Data JDBC에서 쿼리 메서드(Query Method)를 지원한다.

 

✅ ‘find + By + SQL 쿼리문에서 WHERE 절의 칼럼명 + (WHERE 절 칼럼의 조건이 되는 데이터)’

형식으로 쿼리 메서드(Query Method)를 정의하면 조건에 맞는 데이터를 테이블에서 조회한다.

ex) findByName(String name);

 

리턴값으로 SQL 질의를 통한 결과 데이터를 받아서 엔티티 클래스의 객체로 지정해준다.

그리고 Spring Data JDBC 에서 Optional을 지원하기 때문에 리턴값을 Optional로 래핑해준다.

래핑해주는 이유는?

Optional을 사용해서 리턴값을 래핑하면 Service 클래스에서 이 Optional을 이용해서 코드를 더 효율적이고 간결하게 구성할 수 있다.

 

WHERE 절의 조건 칼럼을 여러 개 지정하고 싶다면 ‘And’를 사용하면 된다.

ex) findByPhoneAndName(String phone, String name);

 

쿼리 메서드에 작성한 칼럼명은 내부적으로 테이블의 칼럼명으로 바뀌지만

Spring JDBC 입장에서는 엔티티 클래스를 바라보고 작업한다.

반드시 엔티티 클래스의 멤버 변수명을 적어주어야 한다.

테이블의 칼럼명으로 적는다. ❌

 

(단어를 일치시키면 가장 좋지만 Java에서는 CamelCase 표기법을 사용하고 테이블 칼럼명은 언더스코어(_) 표기법을 사용하기 때문에 2 단어 이상 작성시 이름이 다를 수 있다.)

 

 

➤ @Query 메서드

 

쿼리 메서드명에 작성하지 않고 직접 쿼리문을 작성해서 질의를 할 경우 사용

Attribute로 쿼리문을 작성한다.

동적 쿼리 파라미터(named parameter)

콜론(:) 뒤에는 findBy~()의 괄호안에 동적 쿼리 파라미터(named parameter)를 작성해준다.

ex) @Query(“SELCET * FROM ORDER WHERE ORDER_ID = :orderId”);

 

단순한 쿼리의 경우 쿼리 메서드를 이용하는 것이 간결한 코드 유지와 생산성 면에서 바람직하다.

 

 

➤ Repository 인터페이스 Service 에서 사용하기

 

Service에서 DI를 주입해서 Repository 생성

 

Repository는 인터페이스인데 구현 클래스를 작성하지 않았지만 사용 가능하다.

Repository 인터페이스 구현 클래스는 Spring Data JDBC에서 내부적으로 Java 리플렉션 기술과 Proxy 기술을 이용해서 repository 인터페이스의 구현 클래스 객체를 생성해준다.

 

비즈니스 로직에서 어떤 검증이 필요한 로직은 검증하는 로직을 추출해서 메서드를 작성 후 호출한다. ➡️ 코드의 간결성, 가독성 향상

ex) 회원 정보 리소스를 데이터베이스에 Insert할 경우 이미 Insert된 리소스인지 여부를 검증하는 로직 분리 (findVerifiedMember)

 

 

✅ Optional.ofNullable(...)

파라미터로 전달받은 엔티티 클래스 객체는 클라이언트 쪽에서 선택적으로 수정할 수 있기 때문에 멤버 변수에 null 이 있을 수도 있다.

이처럼 멤버 변수 값이 null 일 경우

Optional.of()가 아닌 Optional.ofNullable()을 이용해서 null 값을 허용할 수 있다.

null 이더라도 NullPointerException이 발생하지 않고 원하는 다음 메서드를 호출할 수 있다.

ifPresent() 메서드를 이용해서 null 값이 아니라면 코드가 실행 되도록 작성한다. (람다식으로 작성)

이 때 값이 null 이라면 실행되지 않는다.

ex) Optional.ofNullable(coffee.getName).ifPresent(name -> findCoffee.setName(name));

 

 

✅ delete 를 사용하여 회원 정보 삭제 ?

실무에서는 회원 정보 자체를 테이블에서 삭제하기 보다 MEMBER_STATUS 같은 칼럼을 두어 상태 값만 변경한다.

회원의 회원 가입 상태를 ‘가입’ , ‘휴면’ , ‘탈퇴’ 등의 상태 정보로 나누어 관리하는 것이 바람직하다.

 

 

✅ orElseThrow()

Optional의 메서드인 orElseThrow()를 이용하면 해당 객체가 null 이 아닐경우에는 해당 객체를 리턴하고 null 이라면 괄호 안의 예외를 던진다.

 

 

✅ 참고) @Builder

Lombok에서 지원

Builder를 사용하고자 하는 클래스 위에 @Builder 애너테이션을 추가한다.

객체를 생성할 때 빌더를 함수를 통해 호출하고 셋팅하고자 하는 필드값을 하나씩 지정해주고 마지막에 build()로 닫아서 작동시킨다.

생성자 파라미터가 많을 경우 builder를 사용하면 가독성이 좋아진다.

자기 자신을 리턴하기 때문에 체인형식으로 생성해줄 수 있다.

최종적으로 build()를 해야 만들어진다.

Member.builder()
	.name("김아무개")
   	.city("서울")
   	.email("kim123@gmail.com")
   	.phone("010-1111-2222")
   	.build();

 

 

https://projectlombok.org/features/Builder

 

@Builder

 

projectlombok.org

 

 

 

 

읽어주셔서 감사합니다. ^^ 좋은하루 보내세요.

오개념에 대한 지적은 늘 환영입니다.

 

 

 

 

 

 

 

 

❑ 타입별 Advice

 

 

➤ Advice 순서

 

어드바이스는 기본적으로 순서를 보장하지 않는다.

순서를 지정하라면 @Aspect 적용 단위로 org.springframework.core.annotation.@Order 애너테이션을 적용하면 됨

  • 어드바이스 단위가 아닌 클래스 단위로 적용할 수 있다.
  • 하나의 aspect에 여러 어드바이스가 존재하면 순서를 보장받을 수 없다.

aspect를 별도의 클래스로 분리해야 한다.

 

 

 

➤ Advice 종류

 

 

▶️ Before

  • join point 실행 이전에 실행한다.
  • Before Advice 구현한 메서드는 일반적으로 리턴 타입이 void 이다.
  • 메서드에서 예외를 발생시킬 경우 대상 객체의 메서드가 호출되지 않는다.
  • 작업 흐름을 변경할 수 없다.

 

 

▶️ After returning

  • join point가 정상 완료된 후 실행한다.
  • returning 속성에 사용된 이름은 advice 메서드의 매개변수 이름과 일치해야 함

 

 

▶️ After throwing

  • 메서드가 예외를 던지는 경우에 실행한다.
  • 메서드 실행이 예외를 던져서 종료될 때 실행한다.
  • throwing 속성에 사용된 이름은 advice 메서드의 매개변수 이름과 일치해야 함

 

 

▶️ After (finally)

  • join point의 동작(정상 또는 예외)과는 상관없이 실행한다.
  • 메서드 실행 후 공통 기능을 실행한다.
  • try~finally의 finally를 생각하면 된다.
  • 일반적으로 리소스를 해제하는데 사용한다.

 

 

▶️ Around ⭐️

  • 메서드 호출 전후에 수행하며 가장 강력한 advice이다.
  • 메서드 실행 전후, 예외 발생 시점에 공통 기능을 실행한다.
    • 조인 포인트 실행 여부 선택 - joinPoint.proceed()
    • 전달 값 변환 - joinPoint.proceed(args[])
    • 반환 값 변환
    • 예외 변환
  • try~catch~finally 가 들어가는 구문 처리가 가능하다.
  • 어드바이스의 첫 번째 매개변수는 ProceedingJoinPoint를 사용해야 한다.
  • proceed()를 통해 대상을 여러번 실행할 수 있다.

 

 

@Around만 있어도 모든 기능 수행이 가능하다!

하지만 target등 고려해야할 사항이 있을 때 정상적으로 작동되지 않는 경우도 있다.

@Before, @After 어드바이스는 기능은 적지만 원하는대로 작동되고 코드도 단순하다.

 

좋은 설계는 @Around만 사용해서 모두 해결하는 것보다는 제약을 가지더라도 실수를 미연에 방지하는 것이다.

제약을 두면 문제 자체가 발생하지 않고 역할이 명확해진다.

 

 

 

 

 

❑ Pointcut 표현식

 

 

➤ 포인트컷과 표현식&지시자

 

▶️ 포인트컷

관심 조인 포인트를 결정

어드바이스가 실행되는 시기를 제어

AspectJ는 포인트컷을 편리하게 표현하기 위한 표현식을 제공

ex) @Pointcut(“execution(* hello.aop.order..*(..))”)

 

 

▶️ 포인트컷 지시자(Pointcut Designator, PCD)

 

포인트컷 지시자 종류

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

 

 

 

➤ Pointcut 표현식 결합

 

&&, ||, ! 를 사용하여 결합할 수 있다.

이름으로 포인트컷 표현식을 참조할 수도 있다.

 

 

▶️ 일반적인 pointcut 표현식들

 

// 모든 공개 매서드 실행
execution(public * *(..))

// set 다음 이름으로 시작하는 모든 메서드 실행
execution(* set*(..))

// AccountService 인터페이스에 의해 정의된 모든 메서드의 실행
execution(* com.xyz.service.AccountService.*(..))

// service 패키지에 정의된 메서드 실행
execution(* com.xyz.servic.*.*(..))

// service 패키지 또는 해당 하위 패키지 중 하나에 정의된 메서드 실행
execution(* com.xyz.service..*.*(..))



// Spring AOP에서만 메서드 실행하는 표현식들

// service 패키지 내의 모든 조인 포인트
within(com.xyz.service.*)

// service 패키지 또는 하위 패키지 중 하나 내의 모든 조인 포인트
within(com.xyz.service..*)

// AcountService 프록시가 인터페이스를 구현하는 모든 조인 포인트
this(com.xyz.service.AccountService)

// AcountService 대상 객체가 인터페이스를 구현하는 모든 조인 포인트
target(com.xyz.service.AccountService)

// 단일 매개변수를 사용하고 런타임에 전달된 인수가 Serializable 과 같은 모든 조인포인트
args(java.io.Serializable)

// 대상 객체가 @Transactional 애너테이션이 있는 조인 포인트
@target(org.springframework.transaction.annotation.Transactional)

// 실행 메서드에 @Transactional 애너테이션이 있는 조인 포인트
@annotation(org.springframework.transaction.annotation.Transactional)

// 단일 매개 변수를 사용하고 전달된 인수의 런타임 유형이 @Classified 애너테이션을 갖는 조인 포인트
@args(com.xyz.security.Classified)

// tradeService 라는 이름을 가진 스프링 빈의 모든 조인 포인트
bean(tradeService)

// 와일드 표현식 *Service 라는 이름을 가진 스프링 빈의 모든 조인 포인트
bean(*Service)

 

 

 

 

 

❑ JoinPoint

 

 

➤ AOP 적용 위치

 

  • AOP는 메서드 실행 위치 뿐만 아니라 다양한 위치에 적용할 수 있다.
  • 적용 가능 지점 : 생성자, 필드 값 접근, static 메서드 접근, 메서드 실행
  • AOP 를 수행하는 메서드는 이 JoinPoint 인스턴스를 인자로 받게 된다.
  • JointPoint 인스턴스에서 조인 포인트의 정보를 얻어내야 한다.

 

 

➤ JoinPoint

 

  • AOP를 적용할 수 있는 지점을 의미하는 추상적 개념
  • 프로그램 실행 중 지점을 나타낸다.
  • AspectJ를 사용해서 컴파일 시점과 클래스 로딩 시점에 적용하는 AOP는 바이트코드를 실제 조작하기 때문에 해당 기능을 모든 지점에 적용할 수 있다.
  • 프록시 방식을 사용하는 Spring AOP메서드 실행 지점에만 AOP를 적용할 수 있다.
  • 프록시는 메서드 오버라이딩 개념으로 동작한다.
  • 생성자나 static 메서드, 필드값 접근에는 프록시 개념이 적용될 수 없다. (프록시는 스프링 AOP에서 사용하기 때문)
  • 프록시를 사용하는 스프링 AOP의 조인 포인트는 메서드 실행으로 제한된다.
  • 프록시 방식을 사용하는 스프링 AOP는 스프링 컨테이너가 관리하는 스프링 빈에만 AOP를 적용할 수 있다.
  • JoinPoint 메서드는 advice의 종류에 따라 사용방법이 다르지만 기본적으로 advice 메서드에 매개변수로 선언만 하면 된다.

 

 

 

➤ JoinPoint Interface의 주요 기능

 

  • JoinPoint.getArgs() : JoinPoint에 전달된 인자를 배열로 반환한다.
  • JoinPoint.getThis() : AOP 프록시 객체를 반환한다.
  • JoinPoint.getTarget() : AOP가 적용된 대상 객체를 반환한다. (클라이언트가 호출한 비즈니스 메서드를 포함하는 비즈니스 객체를 반환)
  • JoinPoint.getSignature() : advice되는 메서드에 대한 설명을 반환한다.
  • JoinPoint.toString() : advice되는 방법에 대한 유용한 설명을 인쇄한다.

 

 

➤ ProceedingJoinPoint Interface의 주요 기능

 

▶️ ProceedingJoinPoint Interface

얜 또 뭐야...

https://www.javadoc.io/doc/org.aspectj/aspectjrt/1.7.2/org/aspectj/lang/ProceedingJoinPoint.html

 

ProceedingJoinPoint - aspectjrt 1.7.2 javadoc

Latest version of org.aspectj:aspectjrt https://javadoc.io/doc/org.aspectj/aspectjrt Current version 1.7.2 https://javadoc.io/doc/org.aspectj/aspectjrt/1.7.2 package-list path (used for javadoc generation -link option) https://javadoc.io/doc/org.aspectj/as

www.javadoc.io

 

JoinPoint 인터페이스를 extends하는 인터페이스 이다
  • proceed() : 다음 어드바이스나 타겟을 호출한다.

 

 

 

 

❑ 애너테이션(Annotation)을 이용한 AOP

 

 

➤ Spring에서의 AOP

 

AOP는 스프링 IoC를 보완하여 매우 강력한 미들웨어 솔루션을 제공한다.

 

▶️ Spring AOP 지원

  • @AspectJ 애너테이션 스타일
  • 스키마 기반 접근

 

 

▶️ @AspectJ 지원

 

  • @AspectJ는 애너테이션이 있는 일반 자바 클래스로 aspect를 선언하는 스타일을 말한다.
  • AspectJ 5 릴리스의 일부로 AspectJ 프로젝트에 의해 도입되었다.
  • 스프링은 pointcut 구문 분석 및 일치를 위해 AspectJ가 제공하는 라이브러리를 사용하여 AspectJ 5와 동일한 애너테이션을 해석한다.
  • AOP 런타임은 여전히 순수한 스프링 AOP이며, AspectJ 컴파일러 위버(weaver)에 의존하지 않는다.

 

 

▶️ @AspectJ 지원 활성화

 

  • Spring 설정에서 @AspectJ 에 기반한 Spring AOP 설정과 이러한 aspect에 의해 advice되는 자동 프록시 빈에 대한 Spring 지원을 활성화 해야한다.
  • @AspectJ 지원은 XML 또는 Java 스타일 설정으로 활성화할 수 있다.
  • Java 설정으로 @AspectJ 지원 활성화 방법
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {

}
  • XML 설정으로 @AspectJ 지원 활성화 방법
<aop:aspectj-autoproxy/>

 

 

 

➤ Aspect 선언

 

@AspectJ 지원이 활성화

-> @AspectJ aspect(@Aspect 애너테이션이 있음)이 있는 클래스로 애플리케이션 컨텍스트에 정의된 모든 빈이 Spring에서 자동으로 감지

-> Spring AOP를 구성하는데 사용된다.

 

import org.aspectj.lang.annotation.Aspect;

@Aspect
public class AspectExample {

}

 

 

 

➤ Pointcut 선언

  • pointcut 선언은 이름과 매개변수를 포함하는 Signature와 메서드 실행을 정확히 결정하는 pointcut 표현식의 두 부분으로 구성된다.
  • pointcut 표현식은 @Pointcut 애너테이션 사용

 

 

 

 

읽어주셔서 감사합니다. 좋은하루 되세요 😉

오개념 지적은 환영입니다

 

 

 

 

 

 

 

❑ 관점 지향 프로그래밍(Aspect-Oriented Programming, AOP)

 

 

AOP(관점 지향 프로그래밍)가 OOP(객체 지향 프로그래밍)을 대체? ❎

횡단 관심사(부가 관심사)를 깔끔하게 처리하기 위해 OOP의 부족한 부분을 보조하는 목적으로 생겨난 것이다.

 

부가 관심사

 

 

 

❑ AOP가 필요한 이유

 

 

➤ AOP 기본 개념

 

핵심 기능(Core Concerns) : 업무 로직을 포함하는 기능

부가 기능(CROSS-CUTTING CONCERNS) : 핵심 기능을 도와주는 부가적인 기능 (로깅, 보안, 트랜잭션 등)

Aspect : 부가 기능을 정의한 코드인 Advice와 Advice를 어디에 적용할지 결정하는 포인트컷(PointCut)을 합친 개념 (Advice + PointCut -> Aspect)

 

 

 

➤ 객체 지향 프로그래밍(OOP)

 

  • 공통된 목적을 띈 데이터와 동작을 묶어 하나의 객체로 정의
  • 객체를 적극적으로 활용하여 기능을 재사용할 수 있는 것이 큰 장점
  • 객체를 잘 활용하기 위해선 관심사 분리(Separation of Concerns, SoC)의 디자인 원칙을 준수해야함

 

 

 

➤ OOP의 한계

 

  • 비즈니스 코드에 트랜잭션, 보안, 로깅 등의 코드가 존재하게 된다.
  • 위의 기능은 업무와는 관련없지만 필수적인 부가 기능이라서 빠질 수 없다.
  • 부가 기능은 불특정 다수 클래스에 존재하게 된다.
  • 비즈니스 클래스에 횡단 관심사와 핵심 관심사가 공존하게 된다.
  • 메소드 복잡도 증가 -> 비즈니스 핵심 로직 코드 파악하기 어려움
  • 부가 기능의 불특정 다수 메소드 반복적으로 구현 -> 횡단 관심사의 모듈화가 어려움

 

관심사의 분리는 모듈화의 핵심이다.

OOP만으로는 횡단 관심사 코드를 깔끔하게 분리하고 비즈니스 코드에 적용하기 어려웠다.

이러한 문제를 해결하기 위해 AOP가 등장했다.

 

 

 

➤ AOP의 핵심 기능과 부가 기능

 

 

▶️ 핵심 기능(Core Concerns)

객체가 제공하는 고유의 기능(업무 로직 등)

 

 

▶️ 부가 기능(CROSS-CUTTING CONCERNS)

핵심 기능을 보조하기 위해 제공되는 기능

로그 추적 로직, 보안, 트랜잭션 기능 등

단독으로 사용되지 않고 핵심 기능과 함께 사용됨

 

 ⬇️ 핵심 기능과 부가 기능 모델

핵심 기능과 부가 기능

 

특징

  • 핵심 기능인 XX로직과 부가 기능인 로그 추적 로직이 하나의 객체에 들어간다.
  • 부가 기능이 필요한 경우엔 위와 같이 합쳐져서 하나의 로직을 완성하게 된다.
  • 서비스를 실행하면 핵심 기능과 부가 기능이 함께 실행된다.

 

 

 

➤ 공통으로 사용하는 부가 기능

 

부가 기능은 보통 여러 클래스에서 함께 사용한다.

이러한 부가 기능은 횡단 관심사가 된다.

부가 기능을 여러 곳에 적용하려면 중복 코드가 생기게 되고 그렇게 되면 수정할 때 각각의 클래스를 찾아가면서 수정해야 한다.

 

 

➤ AOP가 필요한 이유

 

소프트웨어에서 변경지점은 하나가 될 수 있도록 잘 모듈화가 되어야 한다.

부가 기능을 애플리케이션 전반에 적용하는 문제는 OOP 방식으로는 해결이 어렵기 때문에 AOP가 필요하다.

 

 

 

 

❑ AOP 기본 용어 정리

 

 

 

AOP 기본 용어

 

 

 

➤ Aspect(애스팩트)

 

여러 객체에 공통으로 적용되는 기능

Advice + Pointcut 을 모듈화하여 애플리케이션에 포함되는 횡단 기능

여러 어드바이스와 포인트컷이 함께 존재함

 

 

 

➤ Join Point (조인 포인트)

 

  • AOP를 적용할 수 있는 모든 지점을 나타내는 추상적인 개념이다.
  • 클래스 초기화, 객체 인스턴스화, 메서드 호출, 필드 접근, 예외 발생과 같은 애플리케이션 실행 흐름에서의 특정 포인트를 의미한다.
  • 애플리케이션에 새로운 동작을 추가하기 위해 조인포인트에 관심 코드(aspect code)를 추가할 수 있다.
  • 횡단 관심은 조인포인트 전/후에 AOP에 의해 자동으로 추가된다.
  • 스프링 AOP는 프록시 방식을 사용하므로 조인 포인트는 항상 메서드 실행 지점으로 제한된다.

 

 

 

➤ Advice(어드바이스)

 

  • join point에서 수행되는 코드를 의미한다.
  • Aspect를 언제 핵심 코드에 적용할 지를 정의한다.
  • 시스템 전체 aspect에 API 호출을 제공한다.
  • 부가 기능에 해당된다.

 

 

 

➤ Pointcut(포인트컷)

 

  • 조인포인트 중에서 어드바이스가 적용될 위치를 선별하는 기능이다.
  • AspectJ 표현식을 사용해서 지정한다.
  • 프록시를 사용하는 스프링 AOP는 메서드 실행 지점만 포인트컷으로 선별 가능하다.

 

 

 

➤ Weaving(위빙)

 

  • pointcut으로 결정한 target의 joinpoint에 advice를 적용하는 것이다. -> advice를 핵심코드에 적용하는 것
  • 핵심 기능 코드에 영향을 주지 않고 부가 기능을 추가할 수 있다.
  • AOP 적용을 위해 aspect 객체에 연결한 상태이다.

 

 

 

➤ AOP 프록시(proxy)

 

  • AOP 기능을 구현하기 위해 만든 프록시 객체이다.
  • 스프링에서 AOP 프록시는 JDK 동적 프록시나 CGLIB 프록시 중 하나이다.

※ 프록시에 대한 자세한 내용은 글 하단부에 작성했다.

 

 

➤ Target(타겟)

 

  • 핵심 기능을 담고 있는 모듈
  • 부가 기능을 적용할 대상
  • Advice 받는 객체, 포인트컷으로 결정

 

 

➤ Advisor(어드바이저)

 

하나의 어드바이스와 하나의 포인트 컷으로 구성된다.

 

 

 

 

❑ 추가 학습

 

 

➤ Spring Proxy(프록시)

 

Spring AOP는 프록시 기반이다.

Spring에서 사용하는 프록시는 JDK dynamic 프록시와 CGLIB 프록시로 두가지다.

메서드를 직접 부르지 않고 프록시를 통해서 메서드를 불러온다.

프록시는 대리자 역할을 한다고 볼 수 있다.

 

 

▶️ JDK Dynamic proxy vs. CGLIB proxy

 

※ JDK Dynamic proxy (JDK 동적 프록시)

  • JDK와 함께 사용할 수 있다.
  • Reflaction을 통해 동적으로 프록시 객체 생성
  • 해당 타겟에 의해 구현된 모든 인터페이스가 프록시 된다.
  • 인터페이스를 구현하는 경우 스프링은 자동으로 JDK 동적 프록시를 사용한다.

 

 

※ CGLIB

  • 스프링이 프록시를 만들기 위해 사용하는 third party library 이다.
  • 클래스 상속을 통해 프록시 객체 생성
  • 구현한 인터페이스가 없을 때 사용한다.

 

 

참고) 프록시 공식 레퍼런스

https://docs.spring.io/spring-framework/docs/3.0.0.M3/reference/html/ch08s06.html

 

8.6 Proxying mechanisms

Spring AOP uses either JDK dynamic proxies or CGLIB to create the proxy for a given target object. (JDK dynamic proxies are preferred whenever you have a choice). If the target object to be proxied implements at least one interface then a JDK dynamic proxy

docs.spring.io

 

 

 

➤ Spring AOP와 AspectJ

 

 

▶️ Spring AOP

  • 목적 : Spring IoC를 통한 간단한 AOP 구현 -> 완전한 AOP를 의도한 것이 아님
  • Spring Container에 의해 관리되는 bean에만 적용 가능

 

 

▶️ AspectJ

  • 목적 : 완전한 AOP를 제공하는것
  • Spring보다 강력하지만 복잡하다
  • 모든 객체에 적용이 가능하다

 

 

참고) Bealdung 자료 번역 글

https://logical-code.tistory.com/118

 

Spring AOP와 AspectJ 비교하기

Thanks to @ㅅㅈㅎ 님 덕분에 3-5 첫번째 문장의 오타를 수정했습니다. 감사합니다! (더 간편합니다다. ⇢ 더 간편합니다.) @김성수 님 덕분에 3-2. Weaving의 오타를 수정했습니다. 감사합니다! (컴파일

logical-code.tistory.com

 

 

 

 

읽어주셔서 감사합니다.

오개념에 대한 지적은 언제나 환영입니다. 

 

 

1