스프링의 핵심 원리와 고급 기술들을 깊이 있게 학습하고 사용
- 스프링의 핵심
- Thread Local
- Design Pattern
- Aspect-Oriented Programming
동시성 문제란 Thread Safe 하지 않게 개발된 어플리케이션에서 동시에 여러 request 가 발생했을 때 여러 쓰레드가 동시에 어플리케이션 로직을 호출할 때 발생한다.
여러 쓰레드가 동시에 어플리케이션 로직을 호출하게 되면 의도하지 않은 방향으로 로직이 호출되게 되는데, 여기서 의도하지 않은 방향이란 로직의 순서가 꼬일 수 있고, 이로 인해 발생하는 문제는 예측이 불가능하다.
대표적으로 스프링에서 Bean 등록을 하게 되면 default 로 Singleton Pattern 등록이 되는데, 이를 동시에 request 하였을 때 발생하거나 static 같은 공용 필드에 접근하였을 때 발생한다.
동시성 문제가 발생하지 않는 경우
- 지역 변수에서는 동시성 문제가 발생하지 않는다. (지연 변수는 쓰레드마다 각각 다른 메모리 영역이 할당 된다.)
- 값을 읽기만 하면 발생하지 않는다.
이와 같은 동시성 문제를 해결하기 위한 방법이 ThreadLocal 이다.
ThreadLocal 은 해당 쓰레드만 접근할 수 있는 특별한 저장소를 말한다.
아래는 일반적인 변수 필드에 접근하는 경우의 이미지이다.
위와 같이 여러 쓰레드가 하나의 변수 필드에 동시에 접근하게 되면 처음 쓰레드가 보관한 데이터는 사라질 수 있다.
이와 반대로 ThreadLocal 을 사용하면 아래와 같이 저장소가 생성 된다.
ThreadLocal 은 각 쓰레드마다 별도의 내부 저장소를 제공한다. 그러므로 여러 쓰레드가 하나의 변수에 동시에 접근하는 경우는 사라지게 된다.
ThreadLocal 클래스를 이용하면 된다.
Ex.
ThreadLocal<String> str = new ThreadLocal<>();
ThreadLocal 을 모두 사용하였다면 ThreadLocal.remove() 를 통해 쓰레드 로컬에 저장된 값을 제거해야 한다.
- 삭제하는 이유는 WAS(톰켓) 처럼 쓰레드 풀을 사용하는 경우에 여러 쓰레드를 자원의 효율성을 고려하여 재사용하게 되는데, 과거에 사용하였던 쓰레드가 중복으로 호출 될 수가 있기 때문이다.
- Template Method Pattern
- Strategy Pattern
- Template CallBack Pattern
- Proxy Patternf
- Decorator Pattern
부모 클래스에서 알고리즘의 골격인 템플릿을 정의하고, 일부 변경되는 로직은 자식 클래스에 정의하는 것
이렇게 Template Method Pattern 을 사용하게되면 자식 클래스가 알고리즘 전체 구조를 변경하지 않고 특정 부분만 재정의 할 수 있다.
즉, 상속과 오버라이딩을 통한 다형성으로 문제를 해결하는 것이다.
하지만 단점도 존재한다.
Template Method Pattern 은 상속을 사용한다는 점이다. 상속은 부모와 자식이 강한 의존관계를 가지게 된다는 점이다.
자식은 부모 클래스의 기능 전부 필요 하지 않을 수 있지만 상속을 하게 되면 모두 갖게 된다.
interface 를 사용하여 프로그램이 실행중에 변하는 알고리즘의 구현체를 선택할 수 있는 패턴이다. 이는 상속이 아니라 위임으로 문제를 해결하는 방법이다.
Strategy Pattern 은 Template Method Pattern 의 부모와 자식 관계의 강한 의존관계를 가지는 단점을 개선한 패턴이다.
Template 는 변하지 않는 부분을 뜻하고 Callback 은 A 코드의 인수로 실행 가능한 B코드(Callback) 넘겨를 A코드를 실행하는 것을 말한다.
자바 언어에서는 실행 가능한 코드를 인수로 넘기기 위해서는 객체가 필요한데, 객체는 전달해도 되고 java8 부터는 lamda 를 전달할 수 도 있다.
이러한 패턴들은 실제로 스프링 안에서 많이 사용하는 패턴들이다.
XxxTemplate 객체가 이러한 패턴을 사용해서 만들었다.
프록시 패턴은 클라이언트가 A기능을 사용하고자 할때 B 라는 기능을 통해서 A 기능을 사용하는 것이다.
굳이 B 기능을 통해서 A 기능을 사용해야 되냐는 의문을 가질 수 있지만 프록시 패턴의 핵심은 A 기능의 접근을 제어하는 것이다.
프록시 사용 사례
- 접근 제어
- 권한에 따른 접근 제어
- 캐싱
- 지연 로딩
- 부가 기능 추가
Proxy Pattern 을 이용하여 부가기능을 추가하는 패턴이다.
Proxy Pattern 과 Decorator Pattern 가 무슨 차이인지 의문점을 가질 수 있다.
여기서 중요한점은 의도(intent) 이다.
Proxy Pattern 의 의도는 접근 제어의 목적으로 사용하고, Decorator Pattern 은 새로운 기능을 추가하는 것이다.
또, Decorator Pattern 은 프록시 패턴을 중복으로 사용하여 부가기능을 계속 추가할 수 있다.
해석하면 관점 지향 프로그래밍이라 하는데, 이는 어플리케이션을 바라는 관점을 하나하나의 기능에서 횡단 관심사(cross-cutting concerns) 관점으로 달리보는 것이다.
AOP 의 목적은 OOP를 대체하는 것이아니라 OOP를 지향하며 개발할 시에는 횡단 관심사 영역을 매끄럽게 처리할 수 없는데, 이러한 부분을 보조하는 것이다.
AOP 를 사용하면 핵심 기능과 부가 기능이 코드상 완전히 분리되어 관리할 수 있다.
부가 기능 로직은 실제 로직에 추가되는 방법은 총 3가지가 있다.
- 컴파일 시점
- 클래스 로딩 시점
- 런타임 시점(프록시)
.java 소스 코드를 컴파일러를 사용하여 .class 를 만드는 시점에 부가 기능 로직을 추가하는 방법이다.
이 때는 AspectJ 가 제공하는 컴파일러를 사용해야 한다는 특징이 있다.
컴파일된 .class 파일을 열어보면 부가 기능 코드가 추가된 것을 확인할 수 있다. (즉, 부가 기능 코드를 핵심 기능이 있는 코드에 추가한다고 보면 된다.)
컴파일 시점에 부가 기능을 적용하려먼 특별한 컴파일러를 사용해야되고, 복잡하다는 단점이 있다.
클래스 로딩 시점에 적용하는 것은 자바를 실행하면 자바 언어는 .class 파일을 JVM 내부의 클래스 로더에 보관하게 된다. 이 과정에서 .class 파일을 조작 후 JVM 에 올리는 방법이다.
이렇게 클래스 로딩 시점에 .class 파일을 조작하는 건 모니터링 툴들이 많이 사용한다.
이러한 방식의 단점은 자바를 실행할 때 특별한 옵션(java -javaagent) 을 통해 클래스 로더 조작기를 지정해야 하는데, 이부분이 번거롭고 운영하기 어렵다.
런타임 시점은 컴파일 과정이 끝나고, 클래스 로더에도 .class 파일이 모두 올라간 다음을 말한다.
즉, 자바의 main 메서드가 이미 실행된 다음이다.
런타임 시점의 AOP 는 프록시 방식을 사용한다.
그러므로 '컴파일 시점', '클래스 로딩 시점' 이 두가지 방식과 다르게 코드 외적으로 뭔가 관리할 필요가 없어진다.
다만, 프록시를 사용하므로 AOP 기능의 일부 제약이 있다. (큰 문제는 없다.)
스프링 AOP 는 런타임 시점에 AOP 가 적용된다. (프록시 방식)
프록시 방식의 AOP 는 메서드 실행 지점에만 AOP 를 적용할 수 있다. (오버라이딩 개념으로 동작하므로 생성자나 static 메서드, 필드 값 접근에는 프록시 개념을 적용할 수 없다.)
그리고, 스프링 컨테이너가 관리할 수 있는 스프링 빈에만 AOP 를 적용할 수 있다.위와 같은 단점이 있는데도 불구하고 프록시 방식을 사용하는 이유가 무엇인지에 대해서 의문점을 가질 수 있다.
하지만 이유는 간단하다.
프록시 방식을 쓰면 제한되는 부분이 크게 제약되는 부분이 아니라는 점이다.
실제 실무에서 프록시 방식의 단점으로 인해 개발하는데 문제가 되지 않고, 오히러 '컴파일 시점'과 '클래스 로더 시점' 에 사용해야될 컴파일러나 java agent 를 관리하는 것이 더 까다롭다.
- Join Point
- 추상적인 개념이며 AOP 를 적용할 수 있는 모든 지점
- Spring AOP 는 Proxy 방식을 사용하므로 항상 메소드 실행 지점으로 제한된다.
- Pointcut
- Join Point 중에서 Advice 가 적용될 위치를 선별하는 기능
- 주로 AspectJ 표현식을 사용해서 지정
- Spring AOP 는 메서드 실행 지점만 Pointcut 으로 선별할 수 있다.
- Target
- Advice 를 받는 객체
- Advice
- 부가 기능
- Around (주변), Before (전), After (후) 와 같은 다양한 종류의 Advice가 있다.
- Aspect
- Advice + Pointcut 을 모듈화한것이다. (@Aspect를 생각하면 된다.)
- Advisor
- 하나의 Advisor 와 하나의 Pointcut 으로 구성되어 있다. (Spring AOP 에서만 사용되는 특별한 용어)
- Weaving
- Pointcut 으로 결정한 Target 의 Join Point 에 Advice 를 적용하는 것
- AOP 적용을 위해 Aspect 객체에 연결한 상태 - 3가지 방법
- 컴파일 타임
- 클래스 로드 타임
- 런타임
- Spring AOP 는 런타임 방식의 프록시 패턴을 사용
- AOP Proxy
- AOP 기능을 구현하기 위해 만든 프록시 객체이다
- Spring 에서는 AOP 프록시를 JDK 동적 프록시 or CGLIB 프록시를 사용한다. (default 는 CGLIB 이다)