AOP : CrossCuttingConcern을 해결해주는 방법.
부가기능 : 로깅, 보안 등 각 레이어에 걸쳐서 적용할 부가적 기능
AOP
-> 레이어의 핵심기능(관심사)과 부가기능을 분리하게 해줌
각 비즈니스 로직 앞-뒤에 끼어들어갈 부가기능을 어떻게 추가할 수 있을까.
컴파일시점
AspectJ 같은 AOP 프레임워크가 소스코드를 컴파일 전에 부가기능을 소스에 삽입하는방식
클래스 로딩시점
클래스 로딩할때 바이트 코드에다가 부가기능을 삽입한다.
런타임 시점
스프링에서 제공하는 AOP 방식.
사용할 객체의 프록시를 이용해서 프록시가 본 비지니스전에 부가기능을 호출해서 사용하는 방식.
스프링 AOP 프록시 두가지 방식
-JDK 다이내믹 프록시(인터페이스 기반) :인터페이스를 구현한 객체가 프록시의 타겟이 된다.
-CGLib 프록시(클래스 기반)
프록시는 일을 대신 해주는 패턴이었다.
프록시는 클라이언트의 요청을 받아서 적정한 선,후 처리후 타겟을 반환해준다.
즉. 프록시는 타겟을 감싸서 타겟의 요청을 대신 받아준다.
//JDK 프록시 사용예시
//타겟 클래스 (실제)
class CalculatorImpl implements Calculator{
@Override
public int add(int a, int b) {
return a+b;
}
}
interface Calculator{
int add(int a, int b);
}
//핸들러, 프록시가 할일 정의
class LoggingInvocationHandler implements InvocationHandler {
private static final Logger log = LoggerFactory.getLogger(LoggingInvocationHandler.class);
private final Object target;
public LoggingInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
//부가 기능(로그) 먼저 실행
log.info("{} executed",method.getName());
//주요 기능 , 즉 진짜 일을하는 객체를 반환
return method.invoke(target,objects);
}
}
public class JdkProxyTest {
public static void main(String[] args) {
var calculator = new CalculatorImpl();
var proxyInstance = (Calculator) Proxy.newProxyInstance(
LoggingInvocationHandler.class.getClassLoader(),
new Class[]{Calculator.class},
new LoggingInvocationHandler(calculator));
var add = proxyInstance.add(1,2);
}
}
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
어노테이션을 읽고, 실제 스프링 AOP 방식으로 aspect(뒤에나옴)를 적용하게 해준다.
프록시로 구현되어 런타임에 적용되고
xml은 오래된 방식이라 aspectJ의 어노테이션 방식이 권장됨.
[추가 조사]
위빙(뒤에 나옴) 과정에서 프록시객체가 생성된다.
이 프록시 객체가 생성됨으로써 특정 조인포인트에 어드바이스가 적용된다.
타겟 (핵심기능을 가지고있는 모듈, 부가기능이 적용될 대상)
조인포인트 (부가기능이 적용될 수 있는 수많은 포인트(메서드)들 )
포인트 컷(어느 조인포인트를 선택할지 선별하는 정규표현식 : 기준)
에스펙트 : 어드바이스 + 포인트 컷, 부가기능의 set.
-> 부가기능(로깅기능)을 포함한 타겟.
-> 어떤 내용을 어디에 적용할지.
=> 스프링에서는 이 aspect를 빈으로 등록해서 사용
어드바이스(적용될 부가기능의 내용)
:Before, After,Around,After Returning, After Throwing
->어떤 조인포인트에서, 이 작업을 Before에 할지 언제할지 결정
위빙(부가기능이 적용되는 과정) == 타겟의 조인 포인트에 어드바이즈를 적용하는 과정.
-> 타겟의 특정메소드를 선정해서, 어떠한 방식을 통해, 어드바이스를 적용하는게 위빙이다.
//지정자: 타겟의 여러 조인트 포인트중에서 어드바이스를 어떻게 적용시킬지 AOP에게 알려주는 키워드들
//@Around("execution()") -> 메서드를 실행하는 시점에 내용을 적용하겠다.
//public 메소드에 접근할때, 리턴 타입이 뭐든(*)
//org.prgrms.kdt..* 해당 위치 뭐든
//(..) 파라미터 뭐든
//@Around("org.prgrms.kdt.aop.CommonPointCut.repositoryInsertMethodPointCut()")
@Around("@annotation(org.prgrms.kdt.aop.TrackTime)") // 해당 에노테이션에서 부가기능을 실행하겠다.
public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("Before method called. {}", joinPoint.getSignature().toString());
var startTime = System.nanoTime();
var result = joinPoint.proceed();
var endTime = System.nanoTime() - startTime;
log.info("After method call with return result -> {} and time taken {} nanoseconds", result,endTime);
return result;
}
어드바이스들
1. execute
2. within : 특정 클래스를 쓸 수 있음
어드바이스들을 미리 지정하고 메소드명으로 소환이 가능한 @PointCut을 쓸 수 있음.
트랜잭션 -> ACID을 유지
중간에 망가지면 -> 롤백.
성공적으로 마무리되면 중간 과정을 커밋.
트랜잭션 매니저 :
->
트랜잭션 템플릿
지금까지직접 트랜잭션 매니저, 템플릿 가지고 우리가 호출해가면서 씀.
(programatic 한 트랜잭션 매니징)
-> 스프링에서는 @Tranactional을 통해 선언적 트랜잭션 관리가 가능
//에러가 나면 롤백한다 -> 이런건 사실상 부가기능임. 주요로직이 아니다.
@Transactional이 트랜잭션관리
-> 이는 사실 aop 기술이 적용되고 그 안엔 프록시가 작동한다.
여러 db sql 을 실행하는 서비스 단에 적용해서 뭔가 잘못되면 전체 롤백이 가능하도록함.
@Trasactional 어노테이션을 달면? (아직 몰라도될듯)
1.@Trasactional을 적용한 메서드가 있는 클래스 타입의 프록시가 생성됨
2. 위처럼 스프링 AOP에 의해서 자동으로 생성된 프록시에
3. 트랜잭션 관련 기능들이(부가기능) 앞뒤로 붙게된다.
@EnableTransactionManagement
-> 실행하는 스프링프로젝트에 달아줘야 트랜잭션 관리 가능
트랜잭션 전파 : 트랜잭션이 처리되는 과정 중에, 또다른 트랜잭션이 진행되는 것.
롤백이 이어지는 상황이 있을 수 있다.
@Trasactional(propagation= )
REQUIRED | 바깥에 트랜잭션 있으면 사용하고 없으면 안에서 만들어 사용 |
REQUIRED_NEW | 바깥에 트랜잭션 있던없던 안에서는 만들어 사용 (바깥 트랜잭션의 커밋 롤백에 영향을 주지 않는다.) |
SUPPORTS | 바깥에 트랜잭션이 있으면 사용하고 없으면 안만들고 안씀 |
NOT_SUPPORTED | 바깥에 트랜잭션이 있어도 그 트랜잭션 잠시 중단시키고, 메소드 실행종료되면 다시 실행되게 함. 안에서는 안만든다. |
MANDATORY | 실행할때 반드시 이전에 만들어진 트랜잭션이 존재해야함 |
NEVER | 트랜잭션 진행 상황에서 실행 될 수 없다. 만약 이미 진행 중인 트랜잭션이 존재하 면 예외 발생 |
NESTED | 이미 진행 중인 트랜잭션이 존재하면 중첩된 트랜잭션에서 실행되어야 함(이미 진행 중인 트랜잭션이 존재하면 중첩된 트랜잭션에서 실행되어야 함) |
@Trasactional(isolation = )
트랜잭션이 이뤄지면서 다른 트랜잭션도 이뤄질 수 있다.
따라서 이런 트랜잭션 사이가 지독하게 엮이는 일이 생길 수 있다.
-> 그러면 아직 커밋도 안된 값이 다른데서 읽힌다거나.. 제법 복잡해진다.
그래서 이런 트랜잭션이 얼마나 다른 트랜잭션에 독립적인지.
독립성의 단계를 나눈게 Trasaction isolation 레벨이다.
isolation 레벨 / 발생문제 | dirty read | non-repeatable read | phantom reads |
---|---|---|---|
READ_UNCOMMITTED | Yes | Yes | Yes |
READ_COMMITTED | No | Yes | Yes |
REPEATABLE_READ | No | No | Yes |
SERIALIZABLE | No | No | No |
READ_UNCOMMITTED : 커밋 안된것도 읽음
READ_COMMITTED : 커밋된 내용만 읽음
REPEATABLE_READ : 한 트랜잭션에서 먼저 읽어진 데이터의 경우에 다른 트랜잭션에서 변경,삭제되는 걸 방지한다.
SERIALIZABLE: 한 트랜잭션에서 먼저 읽어진 데이터의 경우에 다른 트랜잭션에서 새로 insert되는 것을 방지한다/ 막는다.
dirty read : (수정중인 데이터를 읽을 수 있는가?) 커밋되지 않은 것도 읽으므로, 롤백되어도 이미 읽어버린 후라서 돌이킬수없음. 지저분하게 읽게됨
non-repeatable read : 한트랜잭션에서 앞-뒤로 같은 쿼리값을 읽는데 중간에 다른 트랜잭션에서 값을 바꾸면 앞-뒤 쿼리의 결과가 다름.
일관성 없는 읽기.
phantom reads : 한트랜잭션에서 앞-뒤로 같은 쿼리값을 읽는데 중간에 다른 트랜잭션에서 값을 insert하면 앞-뒤 쿼리의 결과가 다름. 갑자기 뭐가 새로 생김.
없었는데? 있었습니다.
@Trasactional(isolation = )로 수준을 설정해줄 수 있다.