Spring AOP 개념과 핵심 용어
Spring AOP는 관점 지향 프로그래밍으로, 로깅, 트랜잭션, 보안 같은 공통 관심사를 비즈니스 로직에서 분리하여 모듈화와 재사용성을 높입니다. 주요 용어는 아래와 같습니다.
- Join Point (연결점): 향상(advice)을 적용할 수 있는 지점, 보통 메서드 호출을 의미합니다.
- Pointcut (포인트컷): 실제로 향상을 적용할 메서드입니다. 예를 들어 UserService의 save()와 delete() 메서드가 포인트컷이 됩니다.
- Advice (조언): 포인트컷에 실행될 공통 기능입니다. 예를 들어 LogUtils의 printLog() 메서드가 advice입니다.
- Aspect (관점): 포인트컷과 advice를 결합한 모듈입니다. 로깅, 트랜잭션 등이 aspect입니다.
- Target (대상 객체): advice가 적용될 원본 객체입니다. 예를 들어 UserService 객체가 target입니다.
- Weaving (위빙): advice를 target에 적용하는 과정입니다. 컴파일, 로드타임, 런타임에 수행됩니다.
- Proxy (프록시): advice가 적용된 후 생성된 객체로, target을 감싸서 호출을 가로챕니다.
XML 기반 AOP 설정
Spring의 IOC 컨테이너가 자동으로 프록시를 생성하므로, 개발자는 연결점을 선택하고 aspect를 정의한 뒤 XML에 설정만 추가하면 됩니다.
AOP XML 요소
| 요소 | 설명 |
|---|---|
| <aop:config> | AOP 설정의 루트 요소 |
| <aop:aspect> | aspect 정의 |
| <aop:pointcut> | 포인트컷 정의 |
| <aop:before> | 메서드 호출 전 실행되는 advice |
| <aop:after> | 메서드 종료 후 실행되는 advice (finally) |
| <aop:after-returning> | 메서드 정상 반환 후 실행 |
| <aop:around> | 메서드 호출 전후를 감싸는 advice |
XML AOP 예제
UserDao 인터페이스
public interface UserDao {
void insert();
void delete();
void update();
void select();
}
UserDaoImpl 클래스
public class UserDaoImpl implements UserDao {
@Override
public void insert() {
System.out.println("사용자 정보 추가");
}
@Override
public void delete() {
System.out.println("사용자 정보 삭제");
}
@Override
public void update() {
System.out.println("사용자 정보 수정");
}
@Override
public void select() {
System.out.println("사용자 정보 조회");
}
}
XmlAdvice 클래스 (aspect 구현)
public class XmlAdvice {
public void before(JoinPoint jp) {
System.out.println("[전] 대상: " + jp.getTarget() + ", 메서드: " + jp.getSignature().getName());
}
public void afterReturning(JoinPoint jp) {
System.out.println("[반환 후] 메서드: " + jp.getSignature().getName());
}
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("[주변] 시작");
Object result = pjp.proceed();
System.out.println("[주변] 종료");
return result;
}
public void afterThrowing() {
System.out.println("[예외] 발생!");
}
public void after() {
System.out.println("[후] finally 실행");
}
}
applicationContext-xml.xml 설정
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<aop:aspectj-autoproxy/>
<context:component-scan base-package="com.xml"/>
<bean id="userDao" class="com.xml.UserDaoImpl"/>
<bean id="xmlAdvice" class="com.xml.XmlAdvice"/>
<aop:config>
<aop:pointcut id="allMethods" expression="execution(* com.xml.UserDaoImpl.*(..))"/>
<aop:aspect ref="xmlAdvice">
<aop:before method="before" pointcut-ref="allMethods"/>
<aop:after-returning method="afterReturning" pointcut-ref="allMethods"/>
<aop:around method="around" pointcut-ref="allMethods"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="allMethods"/>
<aop:after method="after" pointcut-ref="allMethods"/>
</aop:aspect>
</aop:config>
</beans>
테스트 클래스
public class TestXmlAop {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext-xml.xml");
UserDao dao = ctx.getBean("userDao", UserDao.class);
dao.delete();
System.out.println();
dao.insert();
System.out.println();
dao.select();
System.out.println();
dao.update();
}
}
애너테이션 기반 AOP 설정
Spring은 자바 애너테이션을 통해 간결하게 AOP를 구성할 수 있습니다.
주요 애너테이션
| 애너테이션 | 설명 |
|---|---|
| @Aspect | 해당 클래스를 aspect로 선언 |
| @Pointcut | 포인트컷 표현식 정의 |
| @Before | 메서드 실행 전 advice |
| @After | 메서드 실행 후 advice (finally) |
| @Around | 메서드 실행 전후를 감싸는 advice |
| @AfterReturning | 메서드 정상 반환 후 advice |
| @AfterThrowing | 메서드 예외 발생 후 advice |
애너테이션 예제
UserDaoImpl 클래스 (@Component 추가)
@Component("userDao")
public class UserDaoImpl implements UserDao {
@Override
public void insert() {
System.out.println("사용자 정보 추가");
}
@Override
public void delete() {
System.out.println("사용자 정보 삭제");
}
@Override
public void update() {
System.out.println("사용자 정보 수정");
}
@Override
public void select() {
System.out.println("사용자 정보 조회");
}
}
AnnoAdvice 클래스 (애너테이션 기반 aspect)
@Aspect
public class AnnoAdvice {
@Pointcut("execution(* com.xml.UserDaoImpl.*(..))")
public void pointcut() {}
@Before("pointcut()")
public void before(JoinPoint jp) {
System.out.println("[전] 대상: " + jp.getTarget() + ", 메서드: " + jp.getSignature().getName());
}
@AfterReturning("pointcut()")
public void afterReturning(JoinPoint jp) {
System.out.println("[반환 후] 메서드: " + jp.getSignature().getName());
}
@Around("pointcut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("[주변] 시작");
Object result = pjp.proceed();
System.out.println("[주변] 종료");
return result;
}
@AfterThrowing("pointcut()")
public void afterThrowing() {
System.out.println("[예외] 발생!");
}
@After("pointcut()")
public void after() {
System.out.println("[후] finally 실행");
}
}
applicationContext.xml 설정
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<aop:aspectj-autoproxy/>
<context:component-scan base-package="com.xml"/>
<bean id="annoAdvice" class="com.xml.AnnoAdvice"/>
</beans>
테스트 클래스
public class TestAnnotationAop {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao dao = ctx.getBean("userDao", UserDao.class);
dao.delete();
System.out.println();
dao.insert();
System.out.println();
dao.select();
System.out.println();
dao.update();
}
}