Aspect Oriented Programming의 약자로 관점 지향 프로그래밍이라고 불린다.
관점 지향은 쉽게 말해 어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점으로 나누어서 보고 그 관점을 기준으로 각각 모듈화하겠다는 것이다.
AOP에서 각 관점을 기준으로 로직을 모듈화한다는 것은 코드들을 부분적으로 나누어서 모듈화하겠다는 의미다.
이때, 소스 코드상에서 다른 부분에 계속 반복해서 쓰는 코드들을 발견할 수 있는 데 이것을 흩어진 관심사 (Crosscutting Concerns)라 부른다. 4
위와 같이 흩어진 관심사를 Aspect로 모듈화하고 핵심적인 비즈니스 로직에서 분리하여 재사용하겠다는 것이 AOP의 취지다 .
● Joinpoint : ‘클래스의 인스턴스 생성 시점’, ‘메소드 호출 시점’ 및 ‘예외 발생 시점’ 과 같이 어플리케이션을 실행할 때 특정 작업이 시작되는 시점을 의미.
● Advice : 조인포인트에 삽입되어 동작할 수 있는 코드를 말함.
● Pointcut : 여러 개의 조인포인트를 하나로 결합한(묶은) 것을 말함.
● Advisor : 어드바이스와 포인트컷을 하나로 묶어 취급한 것을 말함.
● Weaving : 어드바이스를 핵심 로직 코드에 삽입하는 것을 말함.
● Target : 핵심 로직을 구현하는 클레스.
● Aspect : 여러 객체에 공통으로 적용되는 공통 관점 사항을 말함.
먼저 AspectJ Weaver 추가 한다.
MessageInter.interface
- 간단한 문자열 출력을 구현한 MessageImpl.class
public interface MessageInter {
void sayHi();
}
public class MessageImpl implements MessageInter{
//핵심로직 클래스 - target
private String name;
public void setName(String name) {
this.name = name;
}
public void sayHi() {
System.out.println("SayHi Method-name: " + name);
//이 메소드에서 많은 로직처리로 지연시간이 있다고 가정함
System.out.println("처리완료");
System.out.println();
}
}
main.class : AOP적용 전
- 기존 context 출력이다.
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("aopinit.xml");
//AOP적용 전.
MessageInter inter = (MessageInter)context.getBean("targetBean");
inter.sayHi();
}
sayHi()
의 메소드내용이 출력되었다.
이제 AOP,Proxy를 적용해보자.
LoggingAdvice.class : Advice클래스
- Advice : 타겟에 제공할 부가기능을 담고 있는 모듈이다.
- Aspect(관심사항)를 클래스로 구현한 advice.class
- 이는 joinpoint에 삽입되어 동작할 코드이다.
public class LoggingAdvice implements MethodInterceptor{
//MethodInterceptor는 자동으로 AroundAdvice를 지원
public Object invoke(MethodInvocation invocation) throws Throwable {
//target(sayHi) 메소드 이름 얻기
String methodName = invocation.getMethod().getName();
System.out.println
("핵심 로직 수행 전 관심사항 : 호출될 타겟 메서드 이름 : " + methodName);
Object object = invocation.proceed(); //핵심로직 수행 - ex)sayHi
System.out.println("핵심로직 수행 후 뭔가를 처리");
return object;
}
}
implements MethodInterceptor
으로 인터페이스 상속을 하고, 필수 구현클래스인 invoke메소드를 생성한다.aopinit.xml
- xml에서 AOP관련 연결정보를 모두 정의 해야한다.
<!-- 핵심 로직 클래스 : target -->
<bean id="targetBean" class="pack.MessageImpl">
<property name="name" value="금요일" />
</bean>
<!-- AOP관련 -->
<!-- Advice(Aspect 클래스) : target으로 weaving -->
<bean id="loggingAdvice" class="pack.LoggingAdvice" />
<!-- Proxy를 통한 간접 접근 -->
<bean id="proxyFactoryBean"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" >
<ref bean="targetBean" />
</property>
<property name="interceptorNames" >
<list><!-- value가 여러개일땐 list -->
<value>defaultPointcutAdvisor</value> <!-- Advisor기술 -->
</list>
</property>
</bean>
<!-- Advisor (Advice + pointcut) 연결역할 -->
<bean id="defaultPointcutAdvisor"
class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="advice">
<ref bean="loggingAdvice"/>
</property>
<!-- 어드바이스를 적용할 타겟의 메서드를 선별하는 정규표현식 -->
<property name="pointcut">
<bean class="org.springframework.aop.support.JdkRegexpMethodPointcut">
<property name="pattern">
<value>.*sayHi*.</value>
</property>
</bean>
</property>
</bean>
ProxyFactoryBean 을 만드는것이 궁극적인 목적이라 생각해보자.
target
으로 사용할 객체 interceptorNames
으로 사용할 Advisortarget 은 핵심로직 클래스 Bean으로 가장위에 선언 되었고,
interceptorNames 에는 Advisor 명을 기술하였다.
Advisor는 Advice + pointcut 두개를 연결한 것이므로
LoggingAdvice
: 위에서 별도로 작성했던 advice용 클래스의 bean.pointcut
: 어드바이스를 적용할 타겟의 메서드를 선별하는 정규표현식Main.class AOP연결 후.
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("aopinit.xml");
//AOP적용 후. proxy패턴을 사용하여 코드를 분리
MessageInter inter2 = (MessageInter)context.getBean("proxyFactoryBean");
inter2.sayHi();
}
결과
AOP 이전
SayHi Method-name: 금요일
.....처리완료
===AOP이후===
핵심 로직 수행 전 관심사항 : 호출될 타겟 메서드 이름 : sayHi
SayHi Method-name: 금요일
.....처리완료
핵심로직 수행 후 뭔가를 처리
Article interface와 그의 구현메소드 (Model)
public interface ArticleInter {
void selectAll();
}
public class ArticleDao implements ArticleInter{
public void selectAll() {
System.out.println("상품 db 전체 자료 읽기");
}
}
Logic interface와 그의 구현메소드 (controller)
public interface LogicInter {
void selectData_process1();
void selectData_process2();
}
public class LogicImpl implements LogicInter {
private ArticleInter articleInter;
//constuctor injection
public LogicImpl(ArticleInter articleInter) {
this.articleInter = articleInter;
}
public void selectData_process1() {
System.out.println("selectData_process1() 호출");
articleInter.selectAll();
}
public void selectData_process2() {
System.out.println("selectData_process2() 처리");
int t = 0;
while(t<5) {
try {
Thread.sleep(1000);
System.out.print(".");
t++;
} catch (Exception e) {}
}
System.out.println("처리 완료");
}
}
ProfileAdvice.class : advice용 클래스.
- around로써 앞뒤에 기능이 위치하고, 단순출력만 추가했다.
- 뒤에서 선언할 pointcut의 조건에 맞게 해당 advice가 적용 될 예정이다.
public class ProfileAdvice { //advice용
public Object kbs(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("핵심 메소드 전 뭔가를 처리");
StopWatch stopWatch = new StopWatch();
stopWatch.start();
System.out.println("핵심 로직처리");
Object object = joinPoint.proceed();
System.out.println("핵심 메소드 후 뭔가를 마무리");
stopWatch.stop();
System.out.println("처리 소요시간 : " + stopWatch.getTotalTimeSeconds());
return object;
}
}
이제 advice와 controller,model을 연결해보자.
initaop.XML
- namespace의 정규식으로 ProfileAdvice 기능을 입힐 멤버를 필터링했다.
<!-- 일반 객체 생성 : 핵심 로직-->
<bean id="logicImpl" class="aop1.LogicImpl">
<constructor-arg>
<ref bean="articleDao" />
</constructor-arg>
</bean>
<bean id="articleDao" class="aop1.ArticleDao" />
<!-- AOP설정 -->
<!-- advice용 클래스의 bean. -->
<bean id="profileAdvice" class="aop1.ProfileAdvice" />
<!-- nameSpace 활용. 정규식에 정의된 필터링 대상에게. -->
<aop:config>
<aop:aspect ref="profileAdvice">
<aop:pointcut expression="execution(* *..*LogicInter*.*(..)) or
execution(public void selectAll())"
id="poi"/>
<!--
AroundAdvice를 사용(핵심메소드의 앞뒤)
advice클래스에서 invoke가 아닌
직접지정한 메소드명이기때문에 명시해줘야한다
-->
<aop:around method="kbs" pointcut-ref="poi"/>
</aop:aspect>
</aop:config>
JointPoint
: Advice가 적용될 위치, 끼어들 수 있는 지점. 메서드 진입 지점, 생성자 호출 시점, 필드에서 값을 꺼내올 때 등 다양한 시점에 적용가능
PointCut
: JointPoint의 상세한 스펙을 정의한 것. 'A란 메서드의 진입 시점에 호출할 것'과 같이 더욱 구체적으로 Advice가 실행될 지점을 정할 수 있음
여기서 execution의 정규식을 확인하면 된다.