AOP(Aspect-Oriented Programming)
관점 지향 프로그래밍
관점지향은 쉽게 말해 어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점으로 나누어서 보고 그 관점을 기준으로 각각 모듈화하겠다는 것이다. 스프링 DI가 의존성(new)에 대한 주입이라면 스프링AOP는 로직(code)에 대한 주입이다.
아래의 그림을 보면 입금, 출금, 이체 모듈 모두 <로깅, 보안, 트랜잭션> 기능이 반복적으로 나타나는 것을 볼 수 있다.
이처럼 다수의 모듈에 공통적으로 나타나는 부분이 존재하는 것을 "횡단 관심사"라고 한다.
핵심 관심사는 모듈별로 다르지만 횡단 관심사는 모듈별로 중복되어 나타나는 부분이다. 프로그래머의 입장에서 "반복/중복은 분리해서 한 곳에서 관리하라"는 말이 떠오르지만 AOP에서는 더욱 진보된 방법을 사용한다.
스프링 AOP는 로직주입이며 로직주입을 한다면 어디에 주입할 수 있을까?
객체지향에서 로직이 있는 곳은 당연히 메소드의 안쪽이다. 그럼 메소드에서 코드를 주입할 수 있는 곳은 몇 군데일까?
Around / Before / After / AfterReturning / AfterThrowing 5군데이다.
예시
public class Boy {
public void runSomething() {
System.out.println("열쇠로 문을 열고 집에 들어간다.");
try {
System.out.println("컴퓨터로 게임을 한다.");
} catch (Exception ex) {
if (ex.getMessage().equals("집에 불남")) {
System.out.println("119 에 신고한다.");
}
} finally {
System.out.println("소등하고 잔다.");
}
System.out.println("자물쇠를 잠그고 집을 나선다.");
}
}
위의 Boy라는 클래스에는 현재 핵심관심사와 횡단관심사가 한 메소드 안에 같이 사용되고있다.
-개선-
public interface Person {
void runSomething();
}
public class Boy implements Person {
public void runSomething() {
System.out.println("컴퓨터로 게임을 한다.");
}
}
Person 인터페이스를 구현하도록 Boy.java를 작성한다.
그리고 핵심 관심사가 아닌 부분, 횡단 관심사는 작성하지 않는다.
<?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="aop002.MyAspect" />
<bean id="boy" class="aop002.Boy" />
</beans>
XML파일 수정하자.
@Aspect
public class MyAspect {
@Before("execution(* runSomething())")
public void before(JoinPoint joinPoint) {
System.out.println("얼굴 인식 확인: 문을 개방하라");
// System.out.println("열쇠로 문을 열고 집에 들어간다.");
}
}
MyAspect코드를 작성하자.
- @Aspect는 이 클래스를 이제 AOP에서 사용하겠다는 의미이다.
- @Before는 대상 메서드 실행 전에 이 메서드를 실행하겠다는 의미다.
메소드의 joinPoint는 Boy.runSomething()을 의미한다. 이제 사용자가 얼굴을 인식하고 문을 여는 것이 아니라
스프링 프레임워크가 사용자를 인식해 자동으로 문을열어준다.
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathApplicationContext;
public class Start {
public static void main(String[] args) {
ApplicationContext context = new ClassPathxmlAppliationContext("aop002/aop002.xml");
Person romeo = context.getBean("boy", Person.class);
romeo.runSomething();
}
}
이제 Start.java를 스프링 프레임워크 기반에서 구동될 수 있게 작성하자.
Boy의 핵심관심사만 남기기 위해 4개의 파일(인터페이스, Boy, XML, Aspect파일)의 수정을 해야했지만,
Boy는 핵심 관심사만 가지게 되었고 추가 개발과 유지보수가 굉장히 편리해졌다.
AOP를 Boy.java에 적용함으로써 단일책임원칙도 자연스레 적용할 수 있게 되었다.
프록시를 이용한 메서드 간접호출
<aop:aspectj-autoproxy />는 무엇인가?
- 프록시 패턴을 이용해 횡단 관심사를 핵심 관심사에 주입하는 것
메서드를 호출하면 "프록시"가 그 요청을 받아서 진짜 객체에게 전달한다.
이때 "프록시"는 그냥 전달만 하지 않고 주고 받는 내용을 감사하거나 조작할 수 있다.
스프링 AOP는 프록시를 사용한다. 그런데 스프링 AOP에서 재미있는 것은 호출하는 쪽(romeo.runSomething() 메서드 호출)에서나 호출당하는 쪽(romeo 객체) 그 누구도 프록시가 존재하는지조차 모른다는 것이다. 오직 스프링프레임워크만 프록시의 존재를 안다.
결국, <aop:aspectj-autoproxy />는 스프링 프레임워크에게 AOP 프록시를 사용하라고 알려주는 지시자이다.
스프링 AOP의 핵심
- 스프링 AOP는 인터페이스(interface) 기반이다.
- 스프링 AOP는 프록시(proxy) 기반이다.
- 스프링 AOP는 런타임(runtime) 기반이다.
PSA(Portable Service Abstraction)
일관성 있는 추상화
서비스 추상화의 예로 JDBC를 들 수 있다.
JDBC라고 하는 표준 스펙이 있기에 오라클을 사용하든, MySQL을 사용하든, MS-SQL을 사용하든 Connection, Statement, ResultSet을 이용해 공통된 방식으로 코드를 작성할 수 있다. 데이터베이스 종류에 관계없이 같은 방식으로 제어할 수 있는 이유는 디자인 패턴에서 설명했던 어댑터(변환기) 패턴을 활용했기 때문이다.
다수의 기술을 공통의 인터페이스로 제어할 수 있게 한 것을 서비스 추상화라고 한다.
스프링 프레임워크에서는 서비스 추상화를 위해 다양한 어댑터를 제공한다.
예를 들어, OXM(Object XML Mapping : 객체와 XML 매핑) 기술만 하더라도 Castor, JAXB, XMLBeans, JiBX, XStream 등 다양한 기술이 있는데, 이 다양한 기술들이 제공하는 API는 제각각이다. 스프링은 제각각인 API를 위한 어댑터를 제공함으로써 실제로 어떤 OXM 기술을 쓰든 일관된 방식으로 코드를 짤 수 있게 지원한다.
또한 하나의 OXM 기술에서 다른 OXM 기술로 변경할 때 큰 변화 없이 세부 기술을 교체해서 사용할 수 있게 해준다. 이처럼 서비스 추상화를 해주면서 그것도 일관성 있는 방식을 제공한다고 해서 이를 PSA (일관성 있는 서비스 추상화)라고 한다. 스프링은 OXM뿐만 아니라 ORM, 캐시, 트랜잭션 등 다양한 기술에 대한 PSA를 제공한다.
참고
스프링 입문을 위한 자바 객체지향의 원리와 이해
'스프링' 카테고리의 다른 글
Spring Bean (0) | 2022.02.20 |
---|---|
[SpringBoot] PasswordEncoder 적용하기 (0) | 2022.02.18 |
의존성주입 DI(Dependency InJection)의 종류 (0) | 2022.02.09 |
스프링 (0) | 2022.02.09 |
1장 오브젝트와 의존관계 (0) | 2021.12.01 |