Spring의 2번째 특징인 AOP에 대한 포스팅입니다. 이제 막 공부하는 것이라 틀린 점이 있다거나 의견이 다른 부분이 있다면 댓글로 남겨주시면 감사드리겠습니다.
1. AOP(관점지향 프로그래밍)
1.1 AOP란?
AOP
는 Aspect Oriented Programming
의 약자로 OOP
와 같은 프로그래밍 패러다임의 하나입니다. OOP
와 상충되는 의미가 아닙니다. OOP
를 돕는 프로그래밍 패러다임입니다. " AOP
란 횡단 관심사를 분리함으로써 모듈성을 증가시키는 것이 목적인 프로그램입니다."라고 Wiki에 나와있습니다. 역시 어렵습니다. 그렇다면 횡단 관심사를 분리 시킨다는 것은 무엇일까요?
*횡단 관심사?
어플리케이션의 핵심기능은 아니지만,
어플리케이션을 구성하는 중요한 요소임과 동시에 부가적인 기능을 담당하는 것입니다. 예를 들자면 트랜잭션, 로깅, 성능 분석 등
이 있습니다.
package com.java.ex;
import org.springframework.stereotype.Component;
public class Car {
private int speed;
public void accelerate() {
System.out.println("accelerate 실행");
this.speed += 10;
System.out.println("accelerate 종료");
}
public void breakCar() {
System.out.println("breakCar 실행");
this.speed = 0;
System.out.println("breakCar 실행");
}
}
위의 코드에서는 부가기능(Log를 출력하는 코드)
이 여러곳에 산재되어있는 것을 볼 수 있습니다.
이것을 부가기능
의 입장에서 바라본다면 다음과 같이 중복되는 코드를 찾을 수 있습니다. AOP
는 기존의 OOP
에서 바라보던 관점을 부가기능
의 입장에서 보았을 때 공통적인 요소들을 따로 모듈화 하는 것입니다. 다시 말해 코드가 핵심 코드(핵심 관심사)
+부가기능(횡단 관심사)
로 이루어져 있다고 보았을 때, 반복되는 부가기능
을 핵심코드
로 부터 분리하는 것이 AOP의 핵심
입니다.
1.2 AOP가 왜 필요할까?
물론 AOP가 없더라도 프로그래밍은 가능합니다. 하지만 프로그래밍을 하다보면 공통적인 기능이 많이 발생합니다. 가장 간단한 예로 어떤 메소드가 실행되는지를 확인하기 위해 메소드 실행 전후로 Log
를 출력하는 등의 기능이 있습니다. 이러한 코드를 계속해서 작성한다면 OOP
를 방해하게 됩니다. 따라서 이러한 문제를 해결(횡단 관심사를 코드로 부터 분리
)하기 위해서 Spring에서는 AOP
를 지원합니다.
*OOP를 방해한다?
1. 추상화를 방해
public class Car {
private int speed;
public void accelerate() {
this.speed += 10;
}
public void breakCar() {
this.speed = 0;
}
}
OOP
에서는 Car
라는 Class가 있을때 Car
Class는 Car
와 관련된 행위 및 속성만 존재해야하며 해당 Class의 목적과 관련되지 않는 것들은 포함이 되지 않아야 합니다. 위의 코드는 간단히 출발과 정지를 할 수 있는 Car
클래스 입니다.
xxxxxxxxxx
public class Car {
private int speed;
public void accelerate() {
System.out.println("accelerate 메소드 실행");
this.speed += 10;
System.out.println("accelerate 메소드 종료");
}
public void breakCar() {
System.out.println("breakCar 메소드 실행");
this.speed = 0;
System.out.println("breakCar 메소드 종료");
}
}
이 클래스에 Log
를 출력하기 위해서는 다음과 같이 코드가 변경될 것입니다. Log
를 출력하는 기능은 Car
Class와는 관련이 없지만 Car
Class안에 로그를 출력하기 위한 코드를 기술하므로써 해당 객체와 관련이 없는 코드가 추가되어 추상화를 방해
하게 됩니다.
2. OOP의 목적인 코드의 재사용성과 중복제거를 방해
또한 Log
를 출력하는 코드가 계속해서 반복되며 중복을 일으키고, 만약 특정한 조건에 Log
를 출력하는 코드가 기입된다면 해당 코드를 재사용하기도 어렵게 될 것
입니다.
*상속을 사용하면 되지 않나요?
상속을 사용한다면 Log 출력 코드를 해당 Class에 넣지 않을 수도 있고 Log 기능이 필요한 Class에서만 상속하여 사용할 수 있지 않을까요? 물론 가능은 할 것입니다. 하지만 Java에서는 다중상속을 지원하지 않기 때문에 좋지 못할 뿐더러, 상속의 목적에 맞지 않습니다. 상속은 어떤 클래스를 확장할 목적을 가지고 있습니다. 따라서 상속을 이용하여 Log를 출력한다면 코드의 가독성을 떨어뜨리고 좋지못한 설계가 될 것입니다.
1.3 AOP 용어
아래의 용어들은 Spring에 국한된 의미가 아닌 AOP 프레임워크
들에서 공통적으로 사용되는 용어들입니다.
1. Target
- 부가기능을 부여할 대상을 의미합니다. 앞선 예제의 Car
Class안의 accelerate(), brakeCar()
가 해당.
2. Aspect
- OOP
에서 각 모듈들을 Object(객체)
로 부르는 것과 비슷하게 부가기능 모듈
을 Aspect
라고 부릅니다. Aspect
는 부가 기능을 정의한 Advice
와 Advice
의 적용 위치를 결정하는 PointCut(포인트 컷)
을 포함합니다.
3. Advice
- Aspect가 무엇을
언제
할지를 정의하고 있습니다.
- 부가기능을 담은 구현체를 의미합니다.
Advice의 종류
xxxxxxxxxx
Before : 메소드 실행전에 advice 실행
After returning : 메소드가 정상적으로 실행된 후에 advice 실행
After throwing : 메소드 실행중 Exception 발생시 advice 실행
After : 메소드 실행중 Exception이 발생하여도 advice 실행
around : 메소드 실행 전/후 (Exception이 발생해도) advice 실행
4. JoinPoint
- Advice
가 적용될 수 있는 위치를 의미합니다.
- Spring
에서는 메소드 JoinPoint
만을 제공합니다.
5. PointCut
- 부가기능이 적용될 메소드(어디에)
를 선정하는 방법을 의미합니다. 즉 Advice
를 적용할 JoinPoint
들을 정의한 것입니다.
6. Proxy
- 이름에서 알 수 있듯 타켓에대한 요청을 대신 받아 메소드 실행의 전처리, 후처리를 실행합니다.
7. Weaving(crossCutting)
- PointCut
에 의해서 Joinpoint
에 Advice를 삽입하는 과정을 의미합니다.
2. Spring AOP 사용법
2.1 Xml 파일을 이용한 AOP
우선 xml을 사용하여 AOP를 사용하는 방법을 알아보겠습니다.
우선 AspectJ
라이브러리를 이용하기 위해 pom.xml
을 수정해야합니다. 프로젝트안에 포함된 pom.xml을 연뒤 하단의 pom.xml
탭을 클릭합니다. 그 후 maven repository(라이브러리 저장소)
에서 얻어온 AspecJ의 의존성을 <dependencies>
태그안에 추가합니다.
xpackage com.java.ex;
public class Car {
private int speed;
public void accelerate() {
this.speed += 10;
}
public void breakCar() {
this.speed = 0;
}
}
AOP를 적용할 간단한 Class를 생성합니다. Spring에서는 Method
에 대해서만 JoinPoint
를 제공하기 때문에 accelerate()
와 breakCar()
에 Advice
가 적용될 것입니다. 하지만 Aspect J
를 이용하면 다양한 곳에 Advice
를 추가할 수 있습니다. Aspect J
에 대해서는 다음 포스팅에서 알아보겠습니다.
xxxxxxxxxx
package com.java.ex;
import org.aspectj.lang.ProceedingJoinPoint;
public class LogAop {
public Object logging(ProceedingJoinPoint joinPoint) throws Throwable{
String methodName = joinPoint.getSignature().toShortString();
System.out.println(methodName + "is Start.");
try {
Object obj = joinPoint.proceed();
return obj;
}
finally {
System.out.println(methodName + "is Finished.");
}
}
}
그 후 횡단관심사(부가기능)
를 정의합니다. 간단하게 메소드의 실행 전후로 print를 하도록 했습니다. 간단하게 분석을 해보자면, logging 메소드
의 첫줄에서 인자로 받아온 joinPoint를 이용하여 Method의 이름을 받아오고 있습니다. try문 안의 joinPoint.proceed()
메소드의 경우 핵심관심사
를 실행하도록 하는 메소드입니다. 즉, looging
메소드는 부가기능을 실행한 뒤 핵심기능을 실행한 뒤 다시 부가기능을 실행하도록 되어있는 형태입니다.
AOP를 설정하기 위해 BeanConfiguration 파일을 생성한뒤 하단의 Namespaces
탭으로 이동하여 aop를 체크해줍니다.
xxxxxxxxxx
<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/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd">
<bean id="car" class="com.java.ex.Car"/>
<bean id="logAop" class="com.java.ex.LogAop"/>
<aop:config>
<aop:aspect id="logAspect" ref="logAop">
<aop:pointcut expression="within(com.java.ex.Car)" id="carAccelAop"/>
<aop:around pointcut-ref="carAccelAop" method="logging"/>
</aop:aspect>
</aop:config>
</beans>
다시 BeanConfiguration 파일의 Source로 돌아오면 위와 같이 xmlns:aop="http://www.springframework.org/schema/aop"
가 추가되었음을 볼 수 있습니다. 우선 앞서 생성한 Class들을 IOC컨테이너가 관리하기 위해 Bean으로 등록을 해줍니다. 그 후 <aop:confg>
태그를 이용하여 aop관련 설정을 진행합니다. 앞서 용어를 살펴보았을때 aspect
는 pointcut과 advice
를 포함한다고 했습니다. 따라서 <aop:config>
태그 안에 다시 <aop:aspect>
태그를 선언하고 그안에 <aop:pointcut>
과 <aop:around>
태그(핵심 관심사 전후로 advice를 실행하는 advice)를 추가해 줍니다.
호출 결과입니다. car의 메소드는 전혀 수정하지 않고 반복되는 코드인 횡단관심사(부가기능)
을 성공적으로 추가했습니다.
2.2 Annotation(@)을 이용한 AOP
이번에는 Xml을 수정하지 않고 간단하게 Annotation을 부여하여 AOP를 하는방법을 알아보겠습니다. AspectJ
를 이용하기 위해 2.1
과 같이 pom.xml
을 수정하여 AspectJ에 대한 의존성을 추가합니다.
그 후 xml 파일로 이동하여 하단의 Namespaces
탭을 클릭한 뒤 aop
와 context
를 체크합니다. context
의 경우 scan을 통해 Bean을 등록하기 위함입니다.
xxxxxxxxxx
<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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd">
<aop:aspectj-autoproxy/>
<context:component-scan base-package="com.java.ex/>
</beans>
그후 다시 xml의 source
탭으로 돌아와 <aop:aspectj-autoproxy/>
와 <context:component-scan base-package="bean으로 등록될 클래스들이 있는 package경로"
를 추가합니다.
xxxxxxxxxx
package com.java.ex;
import org.springframework.stereotype.Component;
public class Car {
private int speed;
public void accelerate() {
this.speed += 10;
}
public void breakCar() {
this.speed = 0;
}
}
Car
class의 경우 코드 변경이 일어나지 않습니다. 다만 편의를 위해 bean을 자동으로 등록하는 @Component
Annotation만을 추가했습니다.
xxxxxxxxxx
package com.java.ex;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
public class LogAop {
("execution(* com.java.ex.Car.accelerate(..))")
public Object logging(ProceedingJoinPoint joinPoint) throws Throwable {
String methodName = joinPoint.getSignature().toShortString();
try {
System.out.println(methodName + "is Start.");
Object obj = joinPoint.proceed();
return obj;
}finally {
System.out.println(methodName + "is Finish.");
}
}
}
LogAop
Class의 경우 마찬가지로 Bean으로 등록하기 위해 @Component
Annotation을 추가했습니다. 이후 @Aspect
Annotation을 통해 해당 클래스가 횡단관심사(부가기능)
임을 알립니다. 마지막으로 횡단관심사(부가기능)
로써 실행될 Method위에 @Around(Advice)
Annotation을 추가합니다. 또한 @Around
에는 당연히 PointCut
을 포함시켜야 합니다.
xxxxxxxxxx
package com.java.ex;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("aop.xml");
Car car = context.getBean("car", Car.class);
car.accelerate();
}
}
3. AOP 적용방법
AOP를 적용하는 방법?이 무엇일까요. 우리가 작성한 AOP를 실제 코드에 반영하는 방법을 말합니다. 즉, 각각의 코드들에서 모듈화 시킨 횡단관심사를 원하는 곳에서 실행하도록 개발자가 작성한 것을, 실제 프로그램에서 적용하는 방법을 말합니다. 적용 방법은 크게 아래의 세가지 방법이 존재합니다.
3.1 컴파일 타임 (Aspect J)
- 성능 부하가 없다
- 조인 포인트가 다양하다
- 별도의 컴파일 과정이 필요
3.2 로드 타임 (Java Agent)
3.3 런 타임 (Spring AOP)
- 프록시 기반의 AOP
- 스프링 빈에만 AOP를 적용 가능
4. AOP 요약
AOP의 중요한 점은 핵심기능의 코드를 변경하지 않고서 부가기능을 실행
하도록 한 것입니다. 따라서 핵심코드와, 횡단관심사(부가기능)을 완전히 모듈화 했습니다. 또한 각 횡단 관심사를 모듈화 함으로써 반복되는 코드를 제거 했습니다.
'FrameWork > Spring' 카테고리의 다른 글
Spring - Spring MVC 동작과정 -5 (0) | 2019.04.03 |
---|---|
Spring - AOP관련 Annotation - 4 (0) | 2019.03.29 |
Spring - @Bean 어노테이션과 @Component 어노테이션(DI) - 2 (6) | 2019.03.26 |
Spring - Spring을 왜 사용하나요?(DI) - 1 (15) | 2019.03.26 |
SpringBoot - JSP 환경 설정(Gradle , Maven) (0) | 2019.01.29 |