AOP는 Aspect-Oriented Programming의 약자이다. 이는 관점(Aspect) 지향 프로그래밍이라고 부른다.
스프링 DI가 의존성에 대한 주입이라면, 스프링 AOP는 로직(code) 주입이라고 할 수 있다.
예를 들어, 다음 그림에서는 입금, 출금, 이체 모듈에서 로깅, 보안, 트랜잭션 기능이 반복적으로 나타나고 있다.
프로그램에서 데이터베이스를 연동할 경우, 쿼리를 날릴 때 항상 DB 커넥션을 맺어줘야 한다.
1) DB 커넥션 준비
2) Statement 객체 준비
try {
3) DB 커넥션 연결
4) Statement 객체 세팅
5) 쿼리 실행
} catch ... {
6) 예외 처리
} finaly {
7) DB 자원 반납
}
스프링 AOP는 로직 주입이라고 했는데, 객체 지향에서 로직(코드)가 있는 곳은 당연히 메서드의 안쪽이라고 할 수 있다.
메서드에서 코드를 주입할 수 있는 곳은 총 5군데가 있다.
그림 출처: Love Me Some Coding | Love Some Coding | Love Coding | Folau Kaveinga
다음은 기존 코드에 AOP를 적용하는 예제이다.
do something
부분을 제외한 나머지 부분은 모두 공통적으로 나타난다.public class Boy {
public void runSomething() {
System.out.println("Start");
try {
// do something
} catch (Exception e) {
System.out.println(e.getMessage());
} finally {
System.out.println("Exit");
}
System.out.println("End");
}
}
public class Girl {
public void runSomething() {
System.out.println("Start");
try {
// do something
} catch (Exception e) {
System.out.println(e.getMessage());
} finally {
System.out.println("Exit");
}
System.out.println("End");
}
}
public class Start {
public static void main(String[] args) {
Boy romeo = new Boy();
Girl juliet = new Girl();
romeo.runSomething();
juliet.runSomething();
}
}
먼저, Boy와 Girl의 공통 메서드를 추려 인터페이스를 생성한다.
public interface Person {
void runSomething();
}
이후 인터페이스를 구현하면서 횡단 관심사를 제거한다.
public class Boy implements Person {
public void runSomething() {
// do something
}
}
public class Girl implements Person {
public void runSomething() {
// do something
}
}
기존 코드에서 제거한 횡단 관심사는 따로 클래스로 만들어 준다.
@Aspect
: 이 클래스를 AOP에서 사용하겠다는 의미@Before
: 대상 메서드 실행 전에 이 메서드를 실행하겠다는 의미JoinPoint
: @Before
에서 선언된 Boy.runSomething()
메서드를 의미runSomething()
메서드가 시작된 직후에 코드를 삽입한다.@Before("execution(* runSomething())")
로 수정@Aspect
public class MyAspect {
@Before("execution(public void Boy.runSomething())")
public void before(JoinPoint joinPoint) {
System.out.println("Start");
}
}
실제 코드를 실행하면, 분리했던 횡단 관심사가 아래처럼 들어가게 된다.
public class Boy implements Person {
public void runSomething() {
**System.out.println("Start");**
// do something
}
}
이후, 스프링 프레임워크 기반에서 구동되도록 main() 메서드를 수정하고 설정 파일을 추가한다.
runSomething()
실행public class Start {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("/context.xml");
Person romeo = context.getBean("boy", Person.class);
romeo.runSomething();
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<aop:aspectj-autoproxy />
<bean id="myAspect" class="MyAspect" />
<bean id="boy" class="Boy" />
</beans>
CGLiB 라이브러리를 사용하면 인터페이스 없이 AOP를 적용할 수 있다.
스프링은 프록시 패턴을 이용해 횡단 관심사를 핵심 관심사에 주입한다.
romeo.runSomething()
을 호출하면, 프록시가 그 요청을 받아 진짜 romeo 객체(인스턴스)에게 요청을 전달한다.runSomething()
메서드에서 주고받는 내용을 감시하거나 조작할 수 있다.따라서, 예제 코드를 보면 설정 파일에서 다음과 같은 코드를 사용했었다.
<aop:aspectj-autoproxy />
용어 | 의미 |
---|---|
Aspect | Advisor의 집합체 |
Advisor | 어디서, 언제, 무엇을 |
Advice | Pointcut에 적용할 로직 |
JoinPoint | Aspect 적용이 가능한 모든 지점 |
Pointcut | Aspect 적용 위치 지정자 |
예제에선 다음과 같이 사용한다.
public void Boy.runSomething()
가@Before
: 시작되기 전에public void before()
: before() 메서드를 실행하라@Aspect
public class MyAspect {
@Before("execution(public void Boy.runSomething())")
public void before(JoinPoint joinPoint) {
System.out.println("Start");
}
}
예제에선 다음과 같이 사용한다.
@Before
: 지정된 Pointcut이 시작되기 전public void before()
: before() 메서드를 실행@Aspect
public class MyAspect {
**@Before**("execution(public void Boy.runSomething())")
public void before(JoinPoint joinPoint) {
System.out.println("Start");
}
}
위의 예제에 따르면, JoinPoint는 다음(볼드체)과 같다.
romeo.runSomething()
을 호출한 상태일 경우, JoinPoint는 romeo 객체의 runSomething()
메서드이다.juliet.runSomething()
을 호출한 상태일 경우, JoinPoint는 juliet 객체의 runSomething()
메서드이다.@Aspect
public class MyAspect {
@Before("execution(public void Boy.runSomething())")
public void before(**JoinPoint joinPoint**) {
System.out.println("Start");
}
}
// 대괄호는 생략 가능하다는 의미이다.
[접근제한자패턴] **리턴타입패턴** [패키지&클래스패턴.]**메서드이름패턴(파라미터패턴)** [throws 예외패턴]
// 사용 예제
@Before("execution( **public void Test.Boy.runSomething()** )")
@Before("execution( *** runSomething()** )")
📔 스프링 입문을 위한 자바 객체 지향의 원리와 이해