| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 8 | 9 | 10 | 11 | 12 | 13 | 14 |
| 15 | 16 | 17 | 18 | 19 | 20 | 21 |
| 22 | 23 | 24 | 25 | 26 | 27 | 28 |
- TodayILearned
- 프레임워크
- SpringMVC
- Oracle
- sqldeveloper
- 자바스크립트
- 오라클
- maven
- PWA
- JavaScript 내장객체
- 국비지원
- 메이븐
- web
- sql
- 서브쿼리
- js
- 자바프로그래밍
- mybatis
- framework
- TIL
- tdd
- 스프링
- 생활코딩
- HTML
- javaprogramming
- javascript
- progressive web app
- springaop
- CSS
- 프로그레시브웹앱
- Today
- Total
1cm
자바 프로그래밍_Day_105_Spring AOP 본문

2022. 01. 14
Spring AOP 구조

* AOP를 적용하면 실제 타겟 클래스, 비즈니스 로직 등만 작성하면 되고, 실제 런타임 시 aspect를 통해 부가 기능이 Weaving되는 형태로 처리가 된다고 보면 된다. -> 부가적인 기능에 대해서는 실제 작성하는 코드에서는 신경쓸 필요가 없음
Spring AOP 특징과 구현 방식
- XML 기반의 aop 네임스페이스를 통한 AOP 구현
1. 부가기능을 제공하는 Advice클래스 작성
2. XML 설정 파일에 <aop:config>를 이용해서 Aspect를 설정(즉, 어드바이스와 포인트컷 설정)
- @Aspect 어노테이션 기반의 AOP 구현
1. @Aspect 어노테이션을 이용해서 부가기능을 제공하는 Aspect 클래스를 작성한다.
(이 때, Aspect 클래스는 어드바이스를 구현하는 메소드와 포인트컷을 포함한다.)
2. XML 설정 파일에 <aop:aspect-autoproxy />를 설정
- Spring AOP 구현 방식 - 태그와 어노테이션
| 태그 | <aop:before> | 메소드 실행 전에 적용되는 어드바이스를 정의 |
| <aop:around> | 메소드 호출 이전, 이후, 예외 발생 등 모든 시점에 적용 가능한 어드바이스를 정의 | |
| <aop:after> | 메소드가 정상적으로 실행되는지 또는 예외를 발생시키는지 여부에 상관없는 어드바이스를 정의 | |
| <aop:after-returning> | 메소드가 정상적으로 실행된 후에 적용되는 어드바이스를 정의 | |
| <aop:after-throwing> | 메소드가 예외를 발생시킬 때 적용되는 어드바이스를 정의 (try-catch 블록에서 catch블록과 유사) |
|
| 어노테이션 | @Before("pointcut") | - 타겟 객체의 메소드가 실행되기 전에 호출되는 어드바이스 - JoinPoint를 통해 파라미터 정보를 참조할 수 있다. |
| @Around("pointcut") | - 타겟 객체의 메소드 호출 전과 후에 실행 될 코드를 구현할 어드바이스 | |
| @After("pointcut") | - 타겟 객체의 메소드가 정상 종료 됐을 때와 예외가 발생했을 때 모두 호출되는 어드바이스로, 반환 값을 받을 수 없다. | |
| @AfterReturning(Pointcut="", Returning="") | - 타겟 객체의 메소드가 정상적으로 실행을 마친 후에 호출되는 어드바이스 - 리턴 값을 참조할 때는 returning 속성에 리턴 값을 저장할 변수 이름을 지정해야 된다. |
|
| @AfterThrowing(Pointcut="", throwing="") | - 타겟 객체의 메소드에서 예외가 발생하면 호출되는 어드바이스 - 발생된 예외를 참조할 때는 throwing 속성에 발생한 예외를 저장할 변수이름을 지정해야 한다. |
Advice 작성하기
Advice의 종류
| Before Advice | 타겟의 메소드가 실행되기 이전(before) 시점에 처리해야 할 필요가 있는 부가기능 정의 -> Joinpoint 앞에서 실행되는 Advice |
| Around Advice | 타겟의 메소드가 호출되기 이전(before) 시점과 이후(after) 시점에 모두 처리해야 할 필요가 있는 부가기능 정의 -> Joinpoint 앞과 뒤에서 실행되는 Advice |
| After Returning Advice | 타겟의 메소드가 정상적으로 실행된 이후 (after) 시점에 처리해야 할 필요가 있는 부가기능 정의 -> Joinpoint 메소드 호출이 정상적으로 종료된 뒤에 실행되는 Advice |
| After Throwing Advice | 타겟의 메소드에 예외가 발생된 이후(after) 시점에 처리해야 할 필요가 있는 부가기능 정의 -> 예외가 던져질 때 실행되는 Advice |
JoinPoint
JoinPoint Interface
- JoinPoint는 스프링 AOP 혹은 AspectJ에서 AOP의 부가기능을 지닌 코드가 적용되는 지점을 뜻하며, 모든 어드바이스는 org.aspectj.lang.JoinPoint 타입의 파라미터를 어드바이스 메소드의 첫 번째 매개변수로 선언해야 함.
단, Around 어드바이스는 JoinPoint의 하위 클래스인 ProcedingJoinPoint 타입의 파라미터를 필수적으로 선언해야 함
JoinPoint Interface 메소드
| getArgs() | 메소드의 매개 변수를 반환한다. |
| getThis() | 현재 사용 중인 프록시 객체를 반환한다. |
| getTarget() | 대상 객체를 반환한다. |
| getSignature() | 대상 객체 메소드의 설명(메소드 명, 리턴 타입 등)을 반환한다. |
| toString() | 대상 객체 메소드의 정보 출력 |
실습
> CharacterTest.java
/*
* AOP 용어 정리
* 1. Aspect
* - AOP 횡단 관심사(한 애플리케이션의 여러 부분에 공통적으로 사용하고 있는 기능)를 Aspect라는 특별한 클래스로 모듈화해서 관리한다.
* - Aspect는 Advice와 PointCut을 합친 것이다.
*
* 2. JoinPoint
* - Advice를 적용할 수 있는 모든 지점을 JoinPoint라고 한다.
* - 즉, JoinPoint는 애플리케이션 실행에 공통적인 기능을 끼워 넣을 수 있는 지점(Point)을 말한다.
* (메소드 호출 지점이나 예외 발생 등)
*
* 3. Advice
* - Aspect가 해야 하는 작업(공통적으로 사용하고 있는 기능)과 언제 그 작업을 수행해야 하는지 정의하는 것을 AOP 용어로 Advice라고 한다.
* - 스프링 AOP에서는 5 종류의 Advice를 제공한다.
* Before Advice : 어드바이스 대상 메소드가 호출되기 전에 어드바이스 기능을 수행한다.
* After Advice : 결과와 상관없이 어드바이스 대상 메소드가 완료된 후에 어드바이스 기능을 수행한다.
* After-Returning Advice : 어드바이스 대상 메소드가 성공적으로 완료된 후에 어드바이스 기능을 수행한다.
* After-Throwing Advice : 어드바이스 대상 메소드가 예외를 던진 후에 어드바이스 기능을 수행한다.
* Around Advice : 어드바이스 대상 메소드 호출 전과 후에 어드바이스 기능을 수행한다.
*
* 4. PointCut
* - Advice가 적용될 조인 포인트의 영역을 좁히는 일을 한다.
* - Advice는 Aspect가 해야하는 '작업'과 '언제' 그 작업을 수행해야 하는지 정의하는 것이라면 PointCut은 어디에 어드바이스를 적용할지 정의하는 것이다.
* - PointCut을 지정하기 위해서는 AspectJ 포인트 커트 표현식을 통해서 지정해 줄 수 있다.
*
*/
> root-context.xml 의 aop:config 부분
<!-- bean을 aspect로 선언하여 사용 -->
<aop:config>
<aop:aspect ref="characterAspect">
<aop:before
pointcut="execution(* com.kh.aop.character.Character.quest(..))"
method="beforeQuest"/>
</aop:aspect>
</aop:config>
execution : 메소드 실행에 대한 joinpoint 시점 지정(아래 method) -> 괄호 내 -> 매개값의 (..) : 어떤 매개타입이든 상관 없다는 뜻. 한정적으로 주고 싶다면 String, int 등 타입을 지정해줄 수 있다.
AfterAdvice 생성해주기
<!-- bean을 aspect로 선언하여 사용 -->
<aop:config>
<aop:aspect ref="characterAspect">
<aop:before
pointcut="execution(* com.kh.aop.character.Character.quest(..))"
method="beforeQuest"/>
<aop:after method="afterQuest"/>
</aop:aspect>
</aop:config>

afterQuest 메소드 생성



퀘스트 진행 중에 오류가 발생했는데 퀘스트 완료가 된 상황 발생 -> 상황별로 정상적으로 성공 시, 실패 시 각각의 처리가 달라야 하는 경우 -> After-Returning Advice 혹은 After-Throwing Advice를 사용해주면 된다.
> CharacterTest.java

> root-context.xml

> CharacterAspect.java

> Character.java

그리고 테스트 실행 시 예외가 발생하면 questFail이 실행된 것을 확인할 수 있다.

중복되어있는 pointcut 정리해주기 (root-context.xml)

<aop:config>
<aop:aspect ref="characterAspect">
<aop:pointcut
id="questPointCut"
expression="execution(* com.kh.aop.character.Character.quest(..))"/>
<aop:before
pointcut="execution(* com.kh.aop.character.Character.quest(..))"
method="beforeQuest"/>
<!--
<aop:after
pointcut="execution(* com.kh.aop.character.Character.quest(..))"
method="afterQuest"/>
-->
<aop:after-returning
pointcut-ref="questPointCut"
method="questSuccess"/>
<aop:after-throwing
pointcut-ref="questPointCut"
method="questFail"/>
</aop:aspect>
</aop:config>
aop:around
- root-context.xml
<aop:around
pointcut-ref="questPointCut"
method="questAdvice"/>
- CharacterAspect.java
// ProceedingJoinPoint 매개값은 필수
public void questAdvice(ProceedingJoinPoint jp) {
try {
// before 어드바이스에 대한 기능을 수행
// 타겟 객체(Character)의 메소드(quest)를 실행 시킨다.
jp.proceed();
// after-returning 어드바이스에 대한 기능을 수행
} catch (Throwable e) {
// after-throwing 어드바이스에 대한 기능을 수행
e.printStackTrace();
}
}
-> 앞에서 세 가지 어드바이스를 적용 했던걸 around로 한번에 처리해줄 수가 있다.
// ProceedingJoinPoint 매개값은 필수
public void questAdvice(ProceedingJoinPoint jp) {
try {
// before 어드바이스에 대한 기능을 수행
System.out.println("퀘스트 준비중...");
// 타겟 객체(Character)의 메소드(quest)를 실행 시킨다.
jp.proceed();
// after-returning 어드바이스에 대한 기능을 수행
System.out.println("퀘스트 수행 완료..");
} catch (Throwable e) {
// after-throwing 어드바이스에 대한 기능을 수행
System.out.println("에러가 발생하였습니다. 다시 시도해 주세요.");
}
}

테스트가 실패하는 이유
-> questAdvice에서 반환하는 값이 없기 때문에 테스트에서 null이라고 나옴
// ProceedingJoinPoint 매개값은 필수
public String questAdvice(ProceedingJoinPoint jp) {
String result = null;
try {
// before 어드바이스에 대한 기능을 수행
System.out.println("퀘스트 준비중...");
// 타겟 객체(Character)의 메소드(quest)를 실행 시킨다.
// jp.proceed();
// 타겟 객체의 메소드에 리턴값이 있을 경우
result = (String) jp.proceed();
// after-returning 어드바이스에 대한 기능을 수행
System.out.println("퀘스트 수행 완료..");
} catch (Throwable e) {
// after-throwing 어드바이스에 대한 기능을 수행
System.out.println("에러가 발생하였습니다. 다시 시도해 주세요.");
}
return result;
}
String으로 반환값을 만들어줘서, 타겟 메소드에서 리턴값이 있을 경우 result에 값을 담아서 반환해줄 수 있도록 해준다.-> test메소드에도 전달됨


그러면 테스트도 통과된다.
만약 파라미터로 전달되는 값을 quest에 전달해주기 전에 advice에서도 가져오고, 값 변경 후 다시 전달해주고 싶을 때
// ProceedingJoinPoint 매개값은 필수
public String questAdvice(ProceedingJoinPoint jp) {
String result = null;
String questName = "[" + (String) jp.getArgs()[0] + "]";
// 실제 타겟 메소드의 매개값을 가져올 수 있다.
// System.out.println(jp.getArgs()[0]);
try {
// before 어드바이스에 대한 기능을 수행
System.out.println("퀘스트 준비중...");
// 타겟 객체(Character)의 메소드(quest)를 실행 시킨다.
// jp.proceed();
// 타겟 객체의 메소드에 리턴값이 있을 경우
// result = (String) jp.proceed();
// 타겟 객체의 메소드에 파라미터 값을 변경해서 전달해줄 경우
result = (String) jp.proceed(new Object[] {questName});
// after-returning 어드바이스에 대한 기능을 수행
System.out.println("퀘스트 수행 완료..");
} catch (Throwable e) {
// after-throwing 어드바이스에 대한 기능을 수행
System.out.println("에러가 발생하였습니다. 다시 시도해 주세요.");
}
return result;
}
그리고 Character.java 살짝 변경해서 테스트 실행시 통과하는 것을 확인할 수 있다.

CharacterTest.java에서 [강철검]과 같은 값이 있는지에 대한 테스트도 통과하는 것을 확인할 수 있다.

Aspect를 참고해서 Character를 감싸고 있는 객체를 만들어서(Proxy 객체) 처리하게 된다.
프록시 패턴 -> 인터페이스의 역할을 한다고 생각하면 됨


aspect의 내용을 참고해서 그 내용이 적용된 proxy 객체를 만듦-> proxy내에서 Character를 처리하고 그 값을 리턴해준다. (오른쪽 그림 참고)
- Spring AOP 구현을 어노테이션 기반으로 동작방법 바꿔보기
@Aspect // 일반적인 자바 클래스가 아니라 Aspect임을 나타낸다.
public class CharacterAspect {
@Before("execution(* com.kh.aop.character.Character.quest(..))")
// quest가 수행되기 전 찍을 내용
public void beforeQuest(JoinPoint jp) {
// 실제 JoinPoint, 대상이 되는 메소드의 선언부분도 가져올 수 있음 (이름)
System.out.println(jp.getSignature().getName());
// 타겟 객체에 대한 클래스 타입
System.out.println(jp.getSignature().getDeclaringTypeName());
// 필요로 하면 JoinPoint도 주입해줄 수 있고, 매개값도 받아 올 수 있다.
System.out.println(jp.getArgs()[0] + "퀘스트 준비중...");
}
매개값을 가져오는 방법 - 표현식 args(파라미터)을 사용해서 값을 가져올수도 있다. (AspectJ)
@After("execution(* com.kh.aop.character.Character.quest(..)) && args(questName)")
// afterQuest 생성
public void afterQuest(String questName) {
// 퀘스트 수행 결과에 상관없이 수행 후에 필요한 부가 작업을 수행한다.
System.out.println("퀘스트 수행 완료..");
}
AspectJ 포인트커트 표현식
* * AspectJ 포인트커트 표현식
* - 스프링 AOP에서 포인트커트는 AspectJ의 포인트 커트 표현식을 이용해서 정의한다.
* - 스프링 AOP에서 지원되는 AspectJ의 포인트 커트 표현식
* execution([접근지정자] 리턴타입 클래스이름.메소드명(파라미터) : 메소드 실행에 대한 조인포인트를 지정한다.(메소드명에는 실제 실행되는 메소드명)
* 접근지정자 : public, private, .. 값을 적어준다.(생략 가능)
* 리턴 타입 : 메소드의 반환값을 의미한다.
* 클래스 이름 : 클래스의 풀 패키지명이 포함된 이름을 적어둔다.
* " * " : 모든 값을 표현한다.
* " .. " : 0개 이상을 의미한다.
* args(파라미터) : 타겟 메소드에 전달되는 파라미터 값을 어드바이스에 전달하기 위한 파라미터를 지정한다.
* (이 표현식을 통해 타겟 메소드에 전달되는 파라미터를 advice에 전달해줄 수 있다. 이 때 파라미터와 advice의 이름이 같아야 한다.)
@AfterReturning 적용(테스트 성공)
@AfterReturning(
value = "execution(* com.kh.aop.character.Character.quest(..)) && args(questName)",
returning = "result"
)
// 퀘스트 성공 시
public void questSuccess(String questName, String result) {
// 퀘스트 수행 완료 후에 필요한 부가 작업을 수행한다.
System.out.println(result);
System.out.println(questName + " 퀘스트 수행 완료..");
}
@AfterThrowing 적용(테스트 실패)
@AfterThrowing(
value = "execution(* com.kh.aop.character.Character.quest(..)) && args(questName)",
throwing = "exception"
)
// 퀘스트 실패 시
public void questFail(String questName, Exception exception) {
// 퀘스트 수행 중에 예외를 던졌을 때 필요한 부가 작업을 수행한다.
System.out.println(exception.getMessage());
System.out.println("에러가 발생하였습니다. 다시 시도해 주세요.");
}
그리고 나서 Character.java에 예외 처리 구문을 살려서 테스트를 진행하면,

퀘스트 실패 구문을 출력해준다.

중복되는 pointcut 정리
// 중복되는 포인트컷 정리
@Pointcut("execution(* com.kh.aop.character.Character.quest(..)) && args(questName)")
public void questPointCut(String questName) {
}
메소드에 @Pointcut 어노테이션을 붙여줬다.
메소드의 몸체는 비어있음 -> 메소드는 마커역할만 한다고 보면 됨.

메소드 이름을 호출하듯이 받아올 매개값과 함께 적어주면 된다.
Around Advice 처리
@Around("execution(* com.kh.aop.character.Character.quest(..))")
// ProceedingJoinPoint 매개값은 필수
public String questAdvice(ProceedingJoinPoint jp) {
String result = null;
String questName = "[" + (String) jp.getArgs()[0] + "]";
// 실제 타겟 메소드의 매개값을 가져올 수 있다.
// System.out.println(jp.getArgs()[0]);
try {
// before 어드바이스에 대한 기능을 수행
System.out.println("퀘스트 준비중...");
// 타겟 객체(Character)의 메소드(quest)를 실행 시킨다.
// jp.proceed();
// 타겟 객체의 메소드에 리턴값이 있을 경우
// result = (String) jp.proceed();
// 타겟 객체의 메소드에 파라미터 값을 변경해서 전달해줄 경우
result = (String) jp.proceed(new Object[] { questName });
// after-returning 어드바이스에 대한 기능을 수행
System.out.println("퀘스트 수행 완료..");
} catch (Throwable e) {
// after-throwing 어드바이스에 대한 기능을 수행 (예외 발생 시)
System.out.println(e.getMessage());
System.out.println("에러가 발생하였습니다. 다시 시도해 주세요.");
}
return result;
}
- 실습문제
> root-context.xml
<!--
AspectJ 어노테이션을 사용한 Aspect 적용을 위한 프록시 설정 -> 어노테이션 기반으로 AOP를 적용할 수 있음
-->
<aop:aspectj-autoproxy proxy-target-class="true" />
> CharacterTest.java
@Test
void attackTest() {
System.out.println(character.getWeapon().attack());
}
> CharacterAspect.java
// 실습 문제
// Swoard, Bow의 attack 메소드 실행 시
// @Around 어드바이스를 사용하여 코드작성 하세요.
// 1. attack 메소드 정상 동작
// 공격을 준비 중 입니다.
// "검을 휘두른다." or "활을 쏜다. 슉!" 출력
// 공격을 성공했습니다.
// 2. attack 메소드 실행중에 예외 발생 시
// 공격을 준비 중 입니다.
// 에러가 발생했습니다.
1차적으로 CharacterAspect.java
@Around("execution(* com.kh.aop.weapon.Sword.attack())")
public String attackAdvice(ProceedingJoinPoint jp) {
String result = null;
try {
System.out.println("공격을 준비중 입니다.");
result = (String) jp.proceed();
System.out.println(result);
System.out.println("공격을 성공했습니다.");
} catch (Throwable e) {
System.out.println("에러가 발생했습니다.");
}
return result;
}
CharacterTest.java
@Test
void attackTest() {
assertThat(character.getWeapon().attack()).isNotNull();
}
sword 적용은 끝났고, bow도 같이 볼 수 있도록

root-context에 bow bean도 추가해줬고,

@Around에서
weapon 패키지 내에 존재하는 모든 클래스에 적용할 수 있도록 *을 붙여줬다.
-> 모든 클래스의 attack 메소드를 advice의 포인트 컷으로 설정 -> attack 메소드 실행 시 advice가 weaving된다.
실습 전체 코드
> CharacterAspect.java
package com.kh.aop.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
@Aspect // 일반적인 자바 클래스가 아니라 Aspect임을 나타낸다.
public class CharacterAspect {
// 포인트 커트를 한 번만 정의하고 필요할 때마다 참조할 수 있는 @Pointcut 어노테이션을 제공한다.
// 중복되는 포인트컷 정리
@Pointcut("execution(* com.kh.aop.character.Character.quest(..)) && args(questName)")
public void questPointCut(String questName) {
}
// @Before("execution(* com.kh.aop.character.Character.quest(..))")
// quest가 수행되기 전 찍을 내용
public void beforeQuest(JoinPoint jp) {
// 실제 JoinPoint, 대상이 되는 메소드의 선언부분도 가져올 수 있음 (이름)
System.out.println(jp.getSignature().getName());
// 타겟 객체에 대한 클래스 타입
System.out.println(jp.getSignature().getDeclaringTypeName());
// 필요로 하면 JoinPoint도 주입해줄 수 있고, 매개값도 받아 올 수 있다.
System.out.println(jp.getArgs()[0] + " 퀘스트 준비중...");
}
// @After("execution(* com.kh.aop.character.Character.quest(..)) && args(questName)")
// afterQuest 생성
public void afterQuest(String questName) {
// 퀘스트 수행 결과에 상관없이 수행 후에 필요한 부가 작업을 수행한다.
System.out.println(" 퀘스트 수행 완료..");
}
// @AfterReturning(
// value = "questPointCut(questName)",
// returning = "result"
// )
// 퀘스트 성공 시
public void questSuccess(String questName, String result) {
// 퀘스트 수행 완료 후에 필요한 부가 작업을 수행한다.
System.out.println(result);
System.out.println(questName + " 퀘스트 수행 완료..");
}
// @AfterThrowing(
// value = "questPointCut(questName)",
// throwing = "exception"
// )
// 퀘스트 실패 시
public void questFail(String questName, Exception exception) {
// 퀘스트 수행 중에 예외를 던졌을 때 필요한 부가 작업을 수행한다.
System.out.println(exception.getMessage());
System.out.println("에러가 발생하였습니다. 다시 시도해 주세요.");
}
@Around("execution(* com.kh.aop.character.Character.quest(..))")
// ProceedingJoinPoint 매개값은 필수
public String questAdvice(ProceedingJoinPoint jp) {
String result = null;
String questName = "[" + (String) jp.getArgs()[0] + "]";
// 실제 타겟 메소드의 매개값을 가져올 수 있다.
// System.out.println(jp.getArgs()[0]);
try {
// before 어드바이스에 대한 기능을 수행
System.out.println("퀘스트 준비중...");
// 타겟 객체(Character)의 메소드(quest)를 실행 시킨다.
// jp.proceed();
// 타겟 객체의 메소드에 리턴값이 있을 경우
// result = (String) jp.proceed();
// 타겟 객체의 메소드에 파라미터 값을 변경해서 전달해줄 경우
result = (String) jp.proceed(new Object[] { questName });
// after-returning 어드바이스에 대한 기능을 수행
System.out.println("퀘스트 수행 완료..");
} catch (Throwable e) {
// after-throwing 어드바이스에 대한 기능을 수행 (예외 발생 시)
System.out.println(e.getMessage());
System.out.println("에러가 발생하였습니다. 다시 시도해 주세요.");
}
return result;
}
// 실습 문제
// Swoard, Bow의 attack 메소드 실행 시
// @Around 어드바이스를 사용하여 코드작성 하세요.
// 1. attack 메소드 정상 동작
// 공격을 준비 중 입니다.
// "검을 휘두른다." or "활을 쏜다. 슉!" 출력
// 공격을 성공했습니다.
// 2. attack 메소드 실행중에 예외 발생 시
// 공격을 준비 중 입니다.
// 에러가 발생했습니다.
@Around("execution(* com.kh.aop.weapon.*.attack())")
public String attackAdvice(ProceedingJoinPoint jp) {
String result = null;
try {
System.out.println("공격을 준비중 입니다.");
result = (String) jp.proceed();
System.out.println(result);
System.out.println("공격을 성공했습니다.");
} catch (Throwable e) {
System.out.println("에러가 발생했습니다.");
}
return result;
}
}
> Character.java
package com.kh.aop.character;
import com.kh.aop.weapon.Weapon;
import lombok.Data;
@Data
public class Character {
private String name;
private int level;
private Weapon weapon;
public String quest(String questName) throws Exception {
if(true) {
throw new Exception("Quest 처리 중 예외 발생");
}
System.out.println(questName + " 퀘스트 진행 중..");
return questName + " 퀘스트 진행 중";
}
}
> Bow.java
package com.kh.aop.weapon;
import lombok.Data;
@Data
public class Bow implements Weapon {
private String name;
@Override
public String attack() throws Exception {
// if (true) {
// throw new Exception();
// }
return "활을 쏜다. 슉!";
}
}
> Sword.java
package com.kh.aop.weapon;
import lombok.Data;
@Data
public class Sword implements Weapon {
private String name;
@Override
public String attack() throws Exception {
// if (true) {
// throw new Exception();
// }
return "검을 휘두른다.";
}
}
> Weapon.java
package com.kh.aop.weapon;
public interface Weapon {
// 무기마다 공격 방식이 다르기 때문에 어떻게 공격할 것인지에 대한 설정 - 추상 메소드
public String attack() throws Exception;
}
> root-context.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"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
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">
<!--
AspectJ 어노테이션을 사용한 Aspect 적용을 위한 프록시 설정 -> 어노테이션 기반으로 AOP를 적용할 수 있음
-->
<aop:aspectj-autoproxy proxy-target-class="true" />
<bean id="character" class="com.kh.aop.character.Character" p:name="동동이" p:level="100" p:weapon-ref="bow"/>
<bean id="sword" class="com.kh.aop.weapon.Sword" p:name="강철검"/>
<bean id="bow" class="com.kh.aop.weapon.Bow" p:name="최종병기 활"/>
<!--
Aspect로 지정할 객체는 반드시 빈으로 선언 되어야 한다.
(xml, annotation으로 설정 시에도 모두 빈으로 선언이 되어야 한다.)
-->
<bean id="characterAspect" class="com.kh.aop.aspect.CharacterAspect"/>
<!-- bean을 aspect로 선언하여 사용 -->
<!--
<aop:config>
<aop:aspect ref="characterAspect">
<aop:pointcut
id="questPointCut"
expression="execution(* com.kh.aop.character.Character.quest(..))"/>
<aop:before
pointcut="execution(* com.kh.aop.character.Character.quest(..))"
method="beforeQuest"/>
<aop:after
pointcut="execution(* com.kh.aop.character.Character.quest(..))"
method="afterQuest"/>
<aop:after-returning
pointcut-ref="questPointCut"
method="questSuccess"/>
<aop:after-throwing
pointcut-ref="questPointCut"
method="questFail"/>
<aop:around
pointcut-ref="questPointCut"
method="questAdvice"/>
</aop:aspect>
</aop:config>
-->
</beans>
> CharacterTest.java
package com.kh.aop.character;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
@ExtendWith(SpringExtension.class)
@ContextConfiguration(locations = {"classpath:root-context.xml"})
class CharacterTest {
/*
* AOP 용어 정리
* 1. Aspect
* - AOP 횡단 관심사(한 애플리케이션의 여러 부분에 공통적으로 사용하고 있는 기능)를 Aspect라는 특별한 클래스로 모듈화해서 관리한다.
* - Aspect는 Advice와 PointCut을 합친 것이다.
*
* 2. JoinPoint
* - Advice를 적용할 수 있는 모든 지점을 JoinPoint라고 한다.
* - 즉, JoinPoint는 애플리케이션 실행에 공통적인 기능을 끼워 넣을 수 있는 지점(Point)을 말한다.
* (메소드 호출 지점이나 예외 발생 등)
*
* 3. Advice
* - Aspect가 해야 하는 작업(공통적으로 사용하고 있는 기능)과 언제 그 작업을 수행해야 하는지 정의하는 것을 AOP 용어로 Advice라고 한다.
* - 스프링 AOP에서는 5 종류의 Advice를 제공한다.
* Before Advice : 어드바이스 대상 메소드가 호출되기 전에 어드바이스 기능을 수행한다.
* After Advice : 결과와 상관없이 어드바이스 대상 메소드가 완료된 후에 어드바이스 기능을 수행한다.
* After-Returning Advice : 어드바이스 대상 메소드가 성공적으로 완료된 후에 어드바이스 기능을 수행한다.
* After-Throwing Advice : 어드바이스 대상 메소드가 예외를 던진 후에 어드바이스 기능을 수행한다.
* Around Advice : 어드바이스 대상 메소드 호출 전과 후에 어드바이스 기능을 수행한다.
*
* 4. PointCut
* - Advice가 적용될 조인 포인트의 영역을 좁히는 일을 한다.
* - Advice는 Aspect가 해야하는 '작업'과 '언제' 그 작업을 수행해야 하는지 정의하는 것이라면 PointCut은 어디에 어드바이스를 적용할지 정의하는 것이다.
* - PointCut을 지정하기 위해서는 AspectJ 포인트 커트 표현식을 통해서 지정해 줄 수 있다.
*
*
* * AspectJ 포인트커트 표현식
* - 스프링 AOP에서 포인트커트는 AspectJ의 포인트 커트 표현식을 이용해서 정의한다.
* - 스프링 AOP에서 지원되는 AspectJ의 포인트 커트 표현식
* execution([접근지정자] 리턴타입 클래스이름.메소드명(파라미터) : 메소드 실행에 대한 조인포인트를 지정한다.(메소드명에는 실제 실행되는 메소드명)
* 접근지정자 : public, private, .. 값을 적어준다.(생략 가능)
* 리턴 타입 : 메소드의 반환값을 의미한다.
* 클래스 이름 : 클래스의 풀 패키지명이 포함된 이름을 적어둔다.
* " * " : 모든 값을 표현한다.
* " .. " : 0개 이상을 의미한다.
* args(파라미터) : 타겟 메소드에 전달되는 파라미터 값을 어드바이스에 전달하기 위한 파라미터를 지정한다.
* (이 표현식을 통해 타겟 메소드에 전달되는 파라미터를 advice에 전달해줄 수 있다. 이 때 파라미터와 advice의 이름이 같아야 한다.)
*
*/
@Autowired
private Character character;
@Test
void test() {
}
@Test
void create() {
System.out.println(character);
assertThat(character).isNotNull();
assertThat(character.getWeapon()).isNotNull();
}
@Test
void questTest() throws Exception {
assertThat(character.quest("강철검")).contains("[강철검]");
}
@Test
void attackTest() throws Exception {
assertThat(character.getWeapon().attack()).isNotNull();
}
}'국비지원_Java > Java Programming_2' 카테고리의 다른 글
| 자바 프로그래밍_Day_107_Spring MVC, mybatis 연동 (0) | 2022.02.15 |
|---|---|
| 자바 프로그래밍_Day_106_Spring AOP / Annotation (0) | 2022.02.07 |
| 자바 프로그래밍_Day_104_Spring DI / AOP (0) | 2022.01.31 |
| 자바 프로그래밍_Day_103_Spring DI (0) | 2022.01.27 |
| 자바 프로그래밍_Day_102_인터페이스 구현 평가 (0) | 2022.01.21 |