[인프런] 백기선 님의 강의 「예제로 배우는 스프링 입문 (개정판)」 을 듣고 정리
스프링 AOP
AOP란 Aspect-Oriented Programming의 약자로 관점 지향 프로그래밍이란 뜻이다.
흩어진 코드를 한 곳으로 모은다고 생각하면 된다.
코드를 예시로 들자면,
class A {
method a() {
AAAA
오늘은 7월 4일 미국 독립기념일입니다.
BBBB
}
method b() {
AAAA
저는 아침에 운동을 다녀와서 밥을 먹고 빨래를 했습니다.
BBBB
}
}
class B {
method c() {
AAAA
점심은 마파두부를 먹었습니다.
BBBB
}
}
이와 같이 각기 다른 메소드, 다른 클래스에서 'AAAA', 'BBBB' 부분이 반복되고 있다. 이런 공통 부분을 따로 모아서 별도의 클래스, 별도의 메소드로 빼면 이렇게 된다.
class A {
method a() {
오늘은 7월 4일 미국 독립기념일입니다.
}
method b() {
저는 아침에 운동을 다녀와서 밥을 먹고 빨래를 했습니다.
}
}
class B {
method c() {
점심은 마파두부를 먹었습니다.
}
}
class AAAABBBB {
method aaaabbbb(JoinPoint point) {
AAAA
point.execute();
BBBB
}
}
이런게 AOP다.
좀 더 자세히 예를 들자면, 다음과 같은 코드가 있다.
public String functionA() {
model.put("owner", new Owner());
return "owners/findOwners";
}
여기에 스프링에서 제공하는 StopWatch를 사용해 해당 코드를 실행하는데 걸리는 시간을 측정해보려고 한다.
그럼 다음과 같이 나타낼 수 있다.
public String functionA() {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// 기존 코드
model.put("owner", new Owner());
stopWatch.stop();
System.out.println(stopWatch.prettyPrint());
return "owners/findOwners";
}
위 코드와 같이 다른 메소드들도 기존 코드의 위아래에 StopWatch 코드를 추가함으로서 걸리는 시간을 측정할 수 있다. 하지만 그러려면 일일히 시간을 측정하고자 하는 모든 코드의 앞뒤에 StopWatch 코드를 추가해주어야 한다. 여기서 이런 중복되는 코드를 없애면서도 시간을 측정할 수 있도록 해주는게 AOP다.
AOP를 구현하는데에는 여러가지 방법이 있는데, 크게 3가지 방법이 있다.
- 컴파일
- 바이트 코드 조작
- 프록시 패턴
이다.
첫번째로 컴파일 방법은 자바코드를 컴파일 하면,
A.java → A.class
이렇게 컴파일이 된다. 이 과정에서 중간에 컴파일러가 StopWatch 코드를 끼워넣어 주는 것이다.
A.java ㅡ(AOP)→ A.class
이렇게. 이런 역할을 하는 컴파일러가 있다. (AspectJ)
두번째로 바이트 코드를 조작하는 방법이 있다.
컴파일을 하면(A.java → A.class) .class 파일이 있고 이 파일을 클래스 로더가 읽어와서 메모리에 올리는 데 이때 코드를 조작하는 것이다.
즉, A.java → A.class ㅡ(AOP) → 메모리
세번째로 프록시 패턴이 있다.
프록시 패턴이란 스프링 AOP가 사용하는 방법인데, 디자인 패턴 중 하나를 사용해서 AOP와 같은 효과를 내는 방법이다.
프록시 패턴
기존 코드를 건드리지 않고 새 기능을 추가하는 방법
예를 들기 위해 간단한 코드를 하나 짜보자.
Payment란 인터페이스가 있고,
// Payment 인터페이스
public interface Payment {
void pay(int amount);
}
해당 인터페이스의 객체를 가지고 있고 buySomething이란 함수가 있는 Store클래스에,
// Store 클래스
public class Store {
Payment payment;
public Store(Payment payment) {
this.payment = payment;
}
public void buySomething(int amount) {
payment.pay(amount);
}
}
Payment인터페이스의 pay함수를 Override하는 현금 결제 Cash 클래스가 있다.
// Cash 클래스
public class Cash implements Payment {
@Override
public void pay(int amount) {
System.out.println(amount + " 현금 결제");
}
}
여기에 마찬가지로 pay함수를 Override하는 카드 결제 CreditCard 클래스를 추가하자.
// CreditCard 클래스
public class CreditCard implements Payment {
Payment cash = new Cash();
@Override
public void pay(int amount) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
if(amount > 100) {
System.out.println(amount + " 카드 결제");
} else {
cash.pay(amount);
}
stopWatch.stop();
System.out.println(stopWatch.prettyPrint());
}
}
여기에 기존 코드 앞뒤로 StopWatch 코드도 넣었다. (성능 측정을 위한 코드를 넣은 프록시 클래스 코드)
그리고 Test 코드를 하나 만들었다.
class StoreTest {
@Test
public void testPay() {
Payment creditCard = new CreditCard(); // 원래는 그냥 new Cash();
Store store = new Store(creditCard);
store.buySomething(100);
}
}
이 테스트 코드를 실행하면 다음과 같은 결과가 뜬다.
100 현금 결제
StopWatch '': running time = 62901 ns
---------------------------------------------
ns % Task name
---------------------------------------------
000062901 100%
Test 코드의 store.buySomething(1000); 이 코드를 100이상으로 바꾸면 '카드 결제'가 뜬다.
기존 코드(Payment 인터페이스, Store 클래스, Cash 클래스)를 건드리지 않고 CreditCard란 프록시 클래스를 추가함으로서 클라이언트가 프록시 클래스 코드를 사용하도록 코드를 바꾼거다. 기존 코드를 건드리지 않았다는게 중요!
이게 AOP를 프록시 패턴으로 구현하는 방법이다.
이런게 스프링 AOP에서는 자동으로 이루어진다.
즉, 원래는 Cash와 같은 클래스가 Bean으로 등록이 되어야 하는데, 이걸로 등록을 하라고 설정을 해놨지만, 이런 프록시가 자동으로 생겨서 Cash 대신 만든 프록시가 등록이 되고 그래서 클라이언트가 원래 Bean으로 등록을 해야하는 Cash가 아니라 CreditCard를 대신 쓰게 되는 일이 스프링 내부에서 발생하는 것이다.
예) @Transactional 이라는 Annotation이 붙어있으면 해당 타입의 객체가, 해당 객체의 프록시가 새로 만들어진다.
원래 JDBC에서 Transaction 처리를 하려면 SQL문 앞뒤에 코드가 붙게 된다. setAutoCommit을 false로 하고, SQL을 실행하고 맨 마지막에 Commit을 하거나 Rollback을 하는 코드가 들어간다. 그 코드를 생략할 수 있게끔 해주는 것이 이 @Transactional 이다. 이 Annotation을 붙이면 앞뒤로 해당 코드들을 넣어주는데, 넣어주는 방법이 위의 프록시 패턴을 사용하는 방법과 같다.
이러한 메커니즘이 상당히 복잡하기 때문에 이를 감춰두고 비즈니스 로직 구현에만 집중할 수 있도록 도와준다.
다음 사이트에 프록시 패턴에 대해 아주 설명이 잘 나와있다고 한다.
참고링크 ☞ https://refactoring.guru/design-patterns/proxy
Proxy
There are dozens of ways to utilize the Proxy pattern. Let’s go over the most popular uses. Access control (protection proxy). This is when you want only specific clients to be able to use the service object; for instance, when your objects are crucial p
refactoring.guru
스프링 AOP 적용 예제
성능을 측정하고 싶은 메소드에 Annotation을 붙여놓음으로서 성능을 측정할 수 있다.
@LogExecutionTime 으로 메소드 처리 시간 로깅하기
@LogExecutionTime Annotation (어디에 적용할지 표시 해두는 용도)
@Target(Element Type.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecutionTime {
}
@Target은 이 Annotation을 어디에 쓸 수 있는지를 나타내고,
@Retention은 이 Annotation 정보를 언제까지 유지할 것인지를 나타낸다.
이것은 그냥 Annotation을 나타낸것으로 이 코드만 있으면 아무런 작용을 하지 못한다.
실제 Aspect (@LogExecutionTime Annotation 달린 곳에 적용)
@Component
@Aspect
public class LogAspect {
Logger logger = LoggerFactory.getLogger(LogAspect.class);
@Around("@annotation(LogExecutionTime)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint)
StopWatch stopWatch = new StopWatch();
stopWatch.start();
Object proceed = joinPoint.proceed();
stopWatch.stop();
logger.info(stopWatch.prettyPrint());
return proceed;
}
}
@Component Annotation을 사용해 Bean으로 등록을 한다.
@Around Annotation을 사용하면 JoinPoint를 받을 수 있다. JoinPoint란 어노테이션을 붙인 메소드, 즉 타겟을 가리킨다.
위 코드는 타겟 메소드를 받아 실행해보고 결과가 있다면 리턴하는 코드다. 중간에 StopWatch를 사용해 성능을 측정하고 그것을 logger를 사용해서 출력한다.
@Around("@annotation(LogExecutionTime)") → 이 코드의 의미는 「LogExecutionTime이란 Annotation 주변에다가 이 코드를 적용하겠다.」는 뜻이다.
스프링 AOP는 이 밖에도 공부할 게 많다. @Around 말고도 @After, @Before 등등 Annotation 없이 적용하는 방법도 있고.
스프링 PSA
PSA란 Portable Service Abstraction의 약자로, 잘 만든 서비스라고 한다. 좀 더 정확히는 교체가 용이한 서비스 추상화라고 보면 된다.
스프링은 주로 다양한 Service Abstraction을 제공하는데, 그 중 스프링 MVC와 관련된 Service Abstraction에 대해 살펴보자.
@Controller라는 Annotation을 사용하면 요청을 매핑할 수 있는 컨트롤러 역할을 수행하는 클래스가 된다. 그래서 클래스 안에다가 @GetMapping, @PostMapping으로 요청을 매핑할 수 있다. 매핑이란 특정 URL에 해당하는 요청이 들어왔을 때 그 요청을 해당 메소드가 처리하게끔 한다는 것이다. 이렇게 스프링 웹 MVC를 사용해서 Servlet 애플리케이션을 간단하게 개발할 수 있다.
이밖에도 스프링 웹 MVC는 여러 복잡한 인터페이스들, 여러 기반시설들을 기반으로 Servlet으로 코딩을 할 수도 있고, Reactive로 코딩을 할 수도 있다. 그 뒷단에 있는 서버를 마음대로 톰캣, 제티, 네티, 언더토우 등 마음대로 바꿔가며 쓸 수도 있다. 코드를 거의 바꾸지 않고도. 이 스프링 웹 MVC가 PSA 중 하나다.
다음으로는, 스프링 트랜잭션에 대해 알아보자. 스프링 트랜잭션은 앞에서 말한 @Transactional Annotation을 생각해보면 된다. 보통 트랜잭션 처리를 JDBC에서 하려면
Connection dbConnection = null;
PreparedStatement preparedStatement = null;
...
try {
dbConnection = getDBConnection();
dbConnection.setAutoCommit(false);
// SQL문들
dbConnection.commit();
} catch {
...
}
로 여러 SQL문이 실행되더라도 Commit을 하지 않는 처리를 해주고, SQL문을 다 실행한 다음 Commit 처리를 해줍니다. 원래는 이런식으로 트랜잭션 처리를 해야한다. 로우 레벨로 한다면.
그런데 스프링이 제공해주는 추상화 계층 레벨은 이 @Transactional이란 Annotation을 제공해준다. 그래서 이 @Transactional만 붙여주면 자동으로 트랜잭션 처리를 해준다. 이것도 역시 여러가지 다양한 기술로 바꿔서 쓸 수 있는 PSA 중 하나다.
'공부 > Spring' 카테고리의 다른 글
[스프링 인 액션] Chapter 3 데이터로 작업하기 (0) | 2022.05.08 |
---|---|
[스프링 인 액션] Chapter 2 웹 어플리케이션 개발하기 (0) | 2022.04.18 |
[스프링 인 액션] Chapter 1 스프링 시작하기 (0) | 2022.04.08 |
[스프링 프레임워크 Core] 스프링 IoC 컨테이너와 빈 (0) | 2020.06.06 |
스프링 IoC (0) | 2020.05.29 |