포스트 작성 목적
Proxy란 무엇인가?? 스프링에서 Transcational 어노테이션이 동작하기 위해서는 Proxy의 역할이 중요하다고 한다. Proxy에 대해 알아보며, 이 Proxy가 어떻게 스프링에게 Transactional 동작을 도와주는지 알아보자.
Proxy란?..
프록시란 클라이언트가 자신(Proxy)를 통해서 다른 네트워크서비스에 간접적으로 접속할 수있게 해주는 컴퓨터시스테이나. 응용프로그램이다. 즉, 클라이언트와 서버사이에 존재하며, 서버와 클라이어튼의 대리자 역할로 통신을 수행한다.
스프링에서의 Proxy란?
Proxy는 대리업무를 수행하는 시스템이나 응용프로그램이라는 것을 알게되었다. 그렇다면 스프링에서의 Proxy는 무엇을 뜻하며 어떤 역할을 수행할까?? 먼저 스프링에서의 Proxy를 알기위해서는 스프링의 AOP라는 개념을 알아야 한다고 한다.
AOP란?
AOP는
Aspect Oriented Programming
의 약자로,
관점 지향 프로그래밍
으로 불린다.
이는 쉽게 말해
어떤 로직을 기준으로, 핵심적인 관점과 부가적인 관점으로 나누어 보고, 다시 드 관점을 기준으로 각각 모듈화
하겠다는 의미이다. 여기서 모듈화란 어떤 공통된 로직이나 기능을 하나의 단위로 묶는 것을 말한다.
예를 들면
핵심적인 관점은 우리가 적용하고자 하는 핵심 비지니스로직
이 된다. 이에 반해
부가적인 관점은 핵심 로직을 실행키위한 DB연결, 로깅, 파일 입출력
등이 있겠다.
AOP에서 각 관점을 기준으로 로직을 모듈화 한다는 것은 코드들을 부분적으로 나누어 모듈화 하겠다는 뜻이다. 이때 소스 코드상에서 다른 부분에 계속 반복해서 쓰이는 코드들을 발견할 수있는데, 이것을 흩어진 관심사(Crosscutting Concern)이라 부른다.
https://engkimbs.tistory.com/746
위 그림처럼 흩어진 관심사를 Aspcet로 모듈화 하고 핵심적인 비지니스 로직에서 분리, 재활용 하겠다는 것이 AOP의 취지이다. 위에 그림에서 공통기능은 직접적으로 호출되지 않는다. 핵심로직을 구현한 코드를 컴파일하거나, 컴파일된 클래스를 로딩하거나, 또는 로딩한 클래스의 객체를 생성할 때,
Proxy객체를 통해 호출할 때 AOP가 적용
된다.
AOP의 주요개념
Aspect : 위에서 설명한 흩어진 관심사를 모듈화 한것, 주로 부가기능을 모듈화한다.
Target : Aspect를 적용하는 곳 (클래스, 메소드...)
Advice : 실질적으로 어떤 일을 해야할 지에 대한 것, 실질적인 부가기능을 담은 구현체
Joint Point : Advice가 적용될 위치, 끼어들 수 있는 지점, 메소드 진입 지점, 생성자 호출 시점, 필드에 서 값을 꺼내올 때 등 다양한 시점에 적용이 가능하다.
Point Cut : Joint Point의 상세한 스팩을 정의한것, "A"란 메소드의 진입시점에 호출할것 과 같이 더욱 구체적으로 Advice가 실행될 지점을 정할 수 있음
Weaving : Advice를 핵심로직코드에 적용하는것, 즉 공통코드를 핵심로직 코드에 삽입하는 행위
스프링 AOP의 특징
프록시 패턴 기반의 AOP구현체, 프록시 객체를 쓰는 이유는 접근 제어 및 부가기능을 추가하기 위해서임
스프링 빈에만 AOP를 적용가능
모든 AOP기능을 제공하지 않고, 스프링 IoC와 연동, 엔터프라이즈 어플리케이션에서 가장 흔한 문제(중복코드, 프록시클래스 작성의 번거로움, 객체들 간 관계 복잡도 증가...) 에 대한 해결책을 지원하는 것이 목적
Weaving의 3가지 방법
컴파일시에 Weaving하기
클래스 로딩 시에 Weaving하기
런타임시에 Weaving하기
일반적으로 1,2번째 방법은 AspectJ라이브러리를 추가해 구현할때 사용된다.
스프링AOP에서 사용하는 방식은 런타임시에 Weaving하는 방식으로 소스코드나 클래스 정보자체를 변경하지 않는다. 대신,
프록시를 생성해 AOP를 적용
한다. 프록시 기반의 AOP는 핵심로직을 구현할 객체에 직접 접근하는것이 아니라, 아래 그림과 같이 중간에 프록시를 생성, 프록시를 통해 핵심 로직의 객체에 접근한다.
https://wan-blog.tistory.com/17
이때 프록시는 핵심 로직을 실행하기 전 또는 후에 공통 모듈 기능을 적용하는 방식으로 AOP를 구현한다.
스프링 AOP에 대하여!...
스프링은 자체적으로 프록시 기반의 AOP를 지원한다. 즉 스프링AOP는 메소드 호출 Joint Point만 지원한다. 필드 값 변경과 같은 JointPoint를 사용하려면 AspectJ등과 같은 프레임워크를 이용해야 한다.
스프링은 크게 3가지 방식으로 AOP를 제공한다.
XMl스키마 기반의 POJO클래스를 이용한 AOP
Aspect에서 정의한 @Aspect 어노테이션 기반 AOP
스프링 API를 통한 AOP
어떤 방식이든 내부적으로 프록시를 이용하기 때문에, 프록시를 통한 메소드 호출만 AOP를 적용할 수 있다. 예를 들어 내부클래스내에서 다른 메소드를 호출한다면, 그것은 프록시를 거치는것이 아니기 때문에, AOP가 적용되지 않는다.
프록시를 이용한 AOP구현
스프링은 Aspect의 적용대상이 되는 객체에 대한 프록시를 만들어 제공한다. 비지니스 로직에 접근할 때 대상 객체로 바로 접근하는게 아닌
프록시를 통해 간접적으로 접근
한다. 이 과정에서 프록시는 공통기능을 실행한 뒤, 대상 객체의 실제 메소드를 호출하거나 또는 대상객체의 실제 메소드를 호출 후에 공통기능을 실행한다.
https://wan-blog.tistory.com/17
대상 타겟은 결국 빈(Bean)객체가 생성 되는데, 런타임시에 오브젝트 생성 설정에 따라서 스프링 컨테이너가 지정한 빈(Bean) 객체에 대한 프록시 객체를 생성,
원본 빈(Bean)객체 대신에 프록시 객체를 사용
하게 한다.
프록시 객체를 생성하는 방식은 대상 객체가 인터페이스를 구현하고 있느냐 없느냐에 따라 달라지며 2가지 방식이 존재한다.
JDK Dynamic Proxy
스프링은 자바 리플렉션 aPI가 제공하는 java.lang.reflect.Proxy를 이용해 프록시 객체를 생성한다. 아래그림처럼 NConnectionMaker, DConnectionMaker와 같이 동일한 인터페이스를 구현하게 되면 UserDao와 같은 클라이언트는 인터페이스를 통해 필요한 메소드를 호출한다. 하지만, 인터페이스 기반으로 프록시 객체를 생성하기 때문에, 인터페이스에 선언되지 않은 메소드에 대해서는 AOP가 적용되지 않는다.
https://wan-blog.tistory.com/17
CGLIB
대상객체가 인터페이스를 구현하고 있지 않고 바로 클래스를 사용한다면 CGLIB을 이용한다. CGLIB은 대상 클래스를 상속 받아 프록시를 구현한다. 따라서 클래스가 final인 경우에는 프록시 생성을 할수 없다.
강제로 CGLIB를 이용해 Proxy를 생성하는법@EnableAspectJAutoProxy 어노테이션 속성으로 proxyTargetClass라는 속성이 있다. 이 값을 true로 할당하면 인터페이스가 있어도 CGLIB 방식으로 프록시를 생성한다.트랜잭션이란 : 비지니스 로직에서 쪼개질수 없는 하나의 단위작업Transaction의 ACID원칙
원자성(Atomic) : 하나의 트랜잭션은 모두 하나의 단위로 처리한다. A와 B작업이 하나의 트랜잭셕으로 묶여 있는 경우 A는 성공, B는 실패할 경우 해당 작업단위는 실패로 끝나야 한다. A,B모두 RollBack되야 한다는 원칙
일관성(Consistency) : 트랜잭션이 성공했다면 DB의 모든 데이터는 일관성을 유지해야한다.
격리성(Isolation) : 트랜잭션으로 처리되는 중간에 외부에서의 간섭은 없어야한다.
영속성(Durability) : 트랜잭션이 성공적으로 처리되면 그 결과는 영구적으로 보존되야 한다.
@Transactional 의 원리
public class BooksImpl implements Books { public void addBooks(List<String> bookNames) { bookNames.forEach(bookName -> this.addBook(bookName)); } @Transactional public void addBook(String bookName) { Book book = new Book(bookName); bookRepository.save(book); book.setFlag(true); } } 출처: https://mommoo.tistory.com/92 [개발자로 홀로 서기]
위와 같은 코드는 this.addBook() 메소드 실행시 @Transcational이 선언되었음에도, 실패하게된다. 왜일까??
public class BooksProxy { private final Books books; private final TransactonManager manager = TransactionManager.getInstance(); public BooksProxy(Books books) { this.books = books; } public void addBook(String bookName) { try { manager.begin(); books.addBook(bookName); manager.commit(); } catch (Exception e) { manager.rollback(); } } } 출처: https://mommoo.tistory.com/92 [개발자로 홀로 서기]
BooksProxy가 addBooks()메소드를 수행하면 아래와 같이 작동한다.BooksProxy::addBooks → BooksImpl::Book 즉, BooksImpl 내부의 코드(addBooks)가 수행되기 되고, 해당 메소드는 프록시로 감싼 메소드가 아니기 때문에
@Transactional
기능이 적용되지 않는것이다.
스프링은 @Transactional이 선언된 메소드를 실행하기전 transaction.begin()코드를 생성한다. 메소드가 실행된 이후에는 transaction.commit()메소드를 실행한다. 즉 아래 코드처럼 원 객체가 아닌, Aspect가 적용된 프록시 객체를 생성한다.
그럼 드디어, 스프링에서 Transactional이 어떻게 동작하는지 알아보자. 다음의 예시 코드를 보자!
한 번에 이루어지는 작업의 단위를 말한다.
스프링의 @Transactional
포스트 작성 후 알게된 점
프록시의 개념에 대해서 잘 모르고 있었다. 우연히 스프링부트와 JPA 강의를 들었을때 잠깐 들었었는데... 그때 어렴풋이 들었던 기억이 이제서야 이해가 간다. 스프링에서 프록시는 AOP의 관점의 부가기능들, 즉 Aspect들을 개발자 대신, 대리하여 핵심로직에 추가해주는 역할을 하고, 스프링은 이 부가기능이 추가된 새로운 프록시객체를 우리가 사용하게 한다. 즉 개발자는 원본객체를 사용하지 않는다.
위의 예제처럼, 우리는 BooksImpl 객체를 직접적으로 사용하지 않는다. Transaction에 관련된 Aspect코드들, begin,commit등이 추가된 프록시 객체(BooksProxy)를 통해 핵심 비지니스로직을 수행한다.
만약 이런 프록시가 없었다면... 모든 코드에 개발자가 일일이 transaction.begin, commit, rollback등 예외처리까지 다 했어야 했을 것이다.
Reference
[Spring] @Transactional 사용시 주의해야할 점
'프로그래밍 > Java' 카테고리의 다른 글
Set과 Queue -Reboot(1) (0) | 2021.05.27 |
---|---|
LRU 캐시 (0) | 2021.05.26 |
Lombok 파해쳐보기 - 1(미완) (0) | 2021.05.24 |
ArrayList VS LinkedList (0) | 2021.05.21 |
java.lang -2 System클래스와 로깅 (0) | 2021.05.20 |