4. AOP
AOP란
- Aspect-Oriented Programming / 애스펙트 지향 프로그래밍
- 애플리케이션 전체에 걸쳐 사용되는 기능을 재사용할 수 있는 컴포넌트에 담을 수 있게 해줌
- 기존 비즈니스 로직에 영향을 주지 않고 필요한 추가처리를 곳곳에 넣을 수 있는 개발 기법
- 인증이나 로깅 등
- 소프트웨어 시스템 내부의 관심사들을 서로 분리하는 기술
- 시스템은 보통 특정한 기능을 책임지는 여러개의 컴포넌트로 구성된다
- 각 컴포넌트는 특정 기능 외에 로깅이나 트랜잭션 관리, 보안등의 시스템 서비스도 수행해야 하는 경우가 있다
- 이러한 시스템 서비스는 여러 컴포넌트에 관련되는 경향이 있다 (횡단관심사)
- 이러한 관심사가 여러 컴포넌트에 퍼지게 되면, 중복되는 코드가 나타난다
- 애플리케이션 코드에 산재해서 나타나는 부가적인 기능을 독립적으로 모듈화
- 시스템 서비스를 모듈화해 컴포넌트에 선언적으로 적용 가능
- 예
- 게시판 기능을 만들 때
- 게시글 쓰기나 목록읽기와 같은 핵심 로직을 구현할 때
- 트랜잭션 적용이나 보안검사같은 공통 기능 코드를 핵심로직에 삽입할 필요가 없다
- AOP라이브러리가 공통기능을 알맞게 삽입해주기 때문
스프링에서의 AOP
- 메서드의 실행시간을 출력할 때
- AOP 미적용 시
public void write(Article article) { long start = System.currentTimeMillis(); try { System.out.println("articleServiceImpl2.write() 메서드 호출"); articleDao.insert(article); } finally { long finish = System.currentTimeMillis(); long time = finish - start; System.out.println("수행시간: " + time + "밀리초"); } }
- AOP 적용 시
package mysite.spring.di; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.aspectj.lang.ProceedingJoinPoint; import org.springframework.util.StopWatch; public class LoggingAspect { private Log log = LogFactory.getLog(getClass()); public Object logging(ProceedingJoinPoint joinPoint) throws Throwable { log.info("기록 시작 : " + joinPoint.toString()); long start = System.currentTimeMillis(); try { Object retValue = joinPoint.proceed(); return retValue; } finally { long finish = System.currentTimeMillis(); long time = finish - start; log.info("기록 종료"); log.info(joinPoint.getSignature().getName() + "메서드 실행 시간 : "+ time + "밀리초"); } } }
- AOP 미적용 시
- logging()메서드는 Aspect가 적용되는 메서드의 실행시간을 구한 뒤 Log를 통해 출력하도록 구현된다
- JoinPoint : 공통 관심사항이 적용될 수 있는 지점 / 메서드 호출 시, 객체 생성 시 등
- ProceddingJoinPoint : Aspect가 적용되는 객체 및 메서드에 대한 정보를 담고 있다
AOP 설정
- XML 파일
<?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/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<bean id="loggingAsp" class="mysite.spring.di.LoggingAspect" />
<aop:config>
<aop:pointcut id="servicePointcut" expression="execution(* *..*Service.*(..))" />
<aop:aspect id="loggingAspect" ref="loggingAsp">
<aop:around pointcut-ref="servicePointcut" method="logging" />
</aop:aspect>
</aop:config>
</beans>
- loggingAspect의 logging()메서드를 이름이 Service로 끝나는 인터페이스를 구현한 모든 클래스의 모든 메서드 앞, 뒤로 실행
package com.spring.di;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainForAop {
public static void main(String[] args) {
String configLocations = "applicationContext.xml";
ApplicationContext context = new ClassPathXmlApplicationContext(configLocations);
ArtService articleService = (ArtService) context.getBean("articleService3");
articleService.insert(new ArticleBean());
}
}
- BeanFactory 대신 ApplicationContext 사용
- 스프링 설정파일로 commonConcern.xml 추가
- 스프링은 프록시를 이용해 AOP를 구현
- MainForAOP클래스가 사용하는 객체는 ArticleServiceImpl 객체가 아니라 스프링이 런타임에 생성한 프록시 객체가 된다
- 프록시가 내부적으로 다시 logging()메서드를 호출하여 ArticleServiceImpl 객체에 적용된다
용어 및 기타 사항
- Advice - 언제 공통관심기능을 핵심로직에 적용할지를 정의
- Joinpoint - Advice를 적용가능한 시점을 의미
- Pointcut - Joinpoint의 부분집합으로서 실제로 Advice가 적용되는 Joinpoint를 나타낸다
- Weaving - Advice를 핵심 로직코드에 적용하는 것
- Aspect - 여러 객체에 공통으로 적용되는 공통 관심 사항
- Advice 종류
- Before Advice : 대상 객체의 메서드 호출 전에 공통 기능 실행
- After Returning Advice : 대상 객체의 메서드가 예외 없이 실행한 이후에 공통 기능 실행
- After Throwing Advice : 대상 객체의 메서드를 실행하는 도중 예외가 발생한 경우에 공통 기능 실행
- After Advice : 대상 객체의 메서드를 실행하는 도중에 예외가 발생했는지의 여부와 상관없이 메서드 실행 후 공통 기능 실행
- Around Advice : 대상객체의 메서드 실행 전, 후, 또는 예회 발생 시점에 공통 기능 실행
- Advice 정의 태그
- aop:before : 메서드 실행 전에 적용되는 Advice 정의
- aop:after-returning
- aop:after-throwing
- aop:after
- aop:around
- Pointcut 표현식
<aop:config>
<aop:pointcut id="servicePointcut" expression="execution(* *..*Service.*(..))" />
<aop:aspect id="loggingAspect" ref="loggingAsp">
<aop:around pointcut-ref="servicePointcut" method="logging" />
</aop:aspect>
</aop:config>
- execution 명시자 : Advice를 적용할 메서드를 명시할 때 사용
execution(수식어패턴?리턴타입패턴 클래스이름패턴?이름패턴(파라미터패턴)
- 수식어패턴 : 생략 가능, public, protected 등
- 리턴타입패턴 : 리턴타입 명시
- 클래스이름패턴, 이름패턴 : 클래스이름 및 메서드 이름을 패턴으로 명시
- 파라미터패턴 : 매칭될 파라미터에 대해 명시
- : 모든 값을 표현
- .. : 0개 이상이라는 의미
AOP 예시
- 설정 xml
<bean name="loggingAsp" class="com.di.aop.LogingAspect"></bean>
<aop:config>
<aop:pointcut expression="execution(* *..*Service.*(..))" id="servicePointCut"/>
<aop:aspect id="logAspect" ref="loggingAsp">
<aop:around method="logging" pointcut-ref="servicePointCut"/>
</aop:aspect>
</aop:config>
<bean name="loggingDaoAsp" class="com.di.aop.LoggingDAOAspect"></bean>
<aop:config>
<aop:pointcut expression="execution(* *..*DAO*.*(..))" id="daoPointCut"/>
<aop:aspect id="logDaoAspect" ref="loggingDaoAsp">
<aop:before method="before" pointcut-ref="daoPointCut"/>
<aop:after-returning method="afterReturing" pointcut-ref="daoPointCut"/>
<aop:after-throwing method="afterError" pointcut-ref="daoPointCut"/>
</aop:aspect>
</aop:config>
- Aspect
package com.di.aop;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aspectj.lang.JoinPoint;
public class LoggingDAOAspect {
private Log log = LogFactory.getLog(getClass());
public void before(JoinPoint joinPoint) {
log.info("[ 기록 시작, target="+ joinPoint.getTarget()+"]");
log.info("메서드 - " + joinPoint.getSignature().getName());
Object[] argsArr=joinPoint.getArgs();
for(int i=0;i<argsArr.length;i++) {
log.info(i+"번째 매개변수 - " + argsArr[i]);
}
}
public void afterReturing(JoinPoint joinPoint) {
log.info("[ 기록 끝 target="+ joinPoint.getTarget()+"]");
}
public void afterError(JoinPoint joinPoint) {
log.info("DAO error : joinPoint="+ joinPoint);
for(Object obj : joinPoint.getArgs()) {
log.info("DAO error : 매개변수 - " + obj);
}
}
}