[Spring] 프록시를 이용한 AOP 구현, AOP 기반의 Transaction모듈 및 Spring TransactionManager
프록시 서버 (Proxy Server)
클라이언트와 서버 사이에 존재하며 대리로 통신을 수행하는 것을 프록시(Proxy)라고 하며,
그 중계 기능을 하는 주체를 프록시 서버 (Proxy Server)라 한다.
Aspect Oriented Programming
관심사는 *공통기능과 *핵심기능으로 나눈다. 이같이 관심사를 분리함으로써 느슨한 결합을 만든다.
공통기능을 모아둔 객체를 Advice 객체라고 하는데
이 객체를 관심사로 등록해 놓으면 사전/사후 처리를 할 수 있다.
Advice 객체를 적용할 지점(method)을 join point라 하는데,
복수의 join point를 하나의 point cut으로 묶고, 적용시기를 결정해서 Proxy Server에게 알려준다.
📜 TimerAdvice.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class TimerAdvice {
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
// 사전(공통기능) 처리 start..
// 적용할 지점, 타겟(method) 원형 정보, 中 이름 가져오기
String methodName = joinPoint.getSignature().getName();
// parameter 정보 가져오기
Object[] params = joinPoint.getArgs();
for(Object o : params) {
System.out.println(o + ",");
}
System.out.println();
StopWatch sw = new StopWatch();
sw.start();
// 사전(공통기능) 처리 end.
Object obj = joinPoint.proceed(); // ✨ Target method Invocation, 실제 핵심 기능을 담당하는 method 호출
// 사후(공통기능) 처리 start..
sw.stop();
System.out.println("[LOG] " + methodName + " 의 총 실행 ms: " + sw.getTotalTimeMillis() + "ms");
// 사후(공통기능) 처리 end.
return obj;
}
}
⚠️ ProceedingJoinPoint joinPoint
적용할 지점인 Target(method)에 대한 정보가 있다.
Spring은 프록시를 이용하여 AOP를 구현한다
비즈니스 로직에 접근할 때 대상 객체에 바로 접근하는 게 아닌 프록시를 통해 간접적으로 접근한다.
프록시 객체에 요청 이전(혹은 이후)에 대한 추가적인 로직(공통로직)을 작성한다.
프록시를 통해 최종적으로 요청을 위임받아 처리하는 실제 객체를 Target이라 부르는데,
프록시는 Target이 맡은 역할에 관여하지 않으면서 Target을 제어할 수 있는 위치에 있기 때문에, 추가적인 역할을 할 수 있다.
즉, 클라이언트와 서버사이에 프록시 서버를 두고, 프록시는 Target인 것처럼 위장하여
클라이언트의 요청을 받고, 추가적인 작업후에(사전처리일 때) Target의 핵심기능을 담고 있는 method를 호출한다.
이런 Proxy의 단점은 Target 수 만큼의 프록시 클래스를 만들어줘야 하고,
때문에 코드 중복의 단점을 가지게 된다.
런타임 시점에 프록시 클래스를 만들어 주는 동적 프록시
동적 프록시 기술을 활용하면 개발자가 일일이 프록시 클래스를 만들지 않아도 된다.
JDK 동적 프록시는 Interface 기반으로 동적 프록시를 생성한다.
📜 AInterface.java
📜 AImpl.java
1
2
3
4
5
6
7
8
9
10
11
12
public interface AInterface {
String call();
}
@Slf4j
public class AImpl implements AInterface {
@Override
public String call() {
log.info("A 호출");
return "a";
}
}
📜 BInterface.java
📜 BImpl.java
1
2
3
4
5
6
7
8
9
10
11
12
public interface BInterface {
String call();
}
@Slf4j
public class BImpl implements BInterface {
@Override
public String call() {
log.info("B 호출");
return "b";
}
}
📜 TimeInvocationHandler.java
- JDK 동적 프록시가 제공하는
InvocationHandler
를 구현하면- JDK 동적 프록시에 공통 로직을 담는 Custom 클래스를 만들 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Slf4j
public class TimeInvocationHandler implements InvocationHandler {
private final Object target; // Proxy는 항상 Proxy가 호출할 대상인 Target이 필요
public TimeInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log.info("TimeProxy 실행");
StopWatch sw = new StopWatch();
sw.start();
Object obj = method.invoke(target, args); // Target method Invocation, 실제 핵심 기능을 담당하는 method 호출
// method를 호출하는 부분이 동적인 것을 의미 (AImpl or BImpl의 method 호출)
sw.end();
log.info("TimeProxy 종료. 총 실행 ms={}", sw.getTotalTimeMillis());
return obj;
}
}
Spring Transaction
데이터 조작이 일어나는 여러 작업 프로세스를 하나의 단위로 묶어 원자성을 부여한 것을 말한다.
⚠️ 하나라도 작업이 실패하면 모두 실패한 것으로 간주
Spring
에서 AOP구현은 프록시를 이용한다.
프록시에서 사전 처리로 transaction Advisor객체를 관심사로 등록하여 transaction을 생성하고,
Custom Advisor객체 안의 공통기능 실행 및 핵심기능을 호출한다.
그 후에, transcation Advisor객체에서 commit or rollback 처리한 뒤
프록시를 통해 요청에 대한 응답을 받는 과정을 거친다.
Spring Transaction Manager
Spring에서 사용하는 db 접근 기술에는 JDBC
JPA
등의 다양한 기술이 있다.
각각의 기술은 서로 다른 transaction 처리방법을 제공한다.
때문에 db접근 기술을 바꾼다면 transaction 처리 코드를 모두 바꿔야한다.
이런 상황을 *객체지향적으로 해결하기 위해 Spring은 TransactionManager 인터페이스로 규격을 맞추고 각각의 구현체를 만들었다.
따라서, Spring에서 이미 구현해놓은 구현체를 주입받아 db에 접근하면 된다.
(⚠️ 개발자가 쓰는 db접근 기술에 알맞은 구현체를 자동으로 등록해준다.)
📜 PlatformTransactionManager.java
1
2
3
4
5
6
7
public interface PlatformTransactionManager extends TransactionManager {
TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- oracle 연결 -->
<beans:bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource"
p:driverClassName="${jdbc.oracle.driver}"
p:url="${jdbc.oracle.url}"
p:username="${jdbc.oracle.username}"
p:password="${jdbc.oracle.password}"
p:maxActive="10" />
<!-- transaction 관련 설정 -->
<beans:bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="datasource" ref="dataSource" />
</beans:bean>
db와 연결할 수 있는 정보가 들어있는 객체를 말한다.
Spring에서는 기본적으로 Connection Pool을 사용해서 db connection을 관리한다.
transaction도 connection을 사용해서 이뤄지기 때문에
transactionManager는 DataSource(Connection Pool)에 대한 참조를 가지고 있다.
transaction 전파
- REQUIRED
- 트랜잭션이 필요하다, 이미 존재하면 해당 트랜잭션을 사용
- REQIORES_NEW
- 항상 새로운 트랜잭션을 시작한다, 이미 존재하면 잠시 중지하고 새롭게 시작
- MANDATORY
- 트랜잭션이 필요하다, 존재하지 않을 경우 Exception 발생
- NESTED
- 이미 트랜잭션이 존재하면 중첩된 트랜잭션에서 실행