본문 바로가기

학원/복기

[Spring] AOP(Aspect Oriented Programming)

AOP  (Aspect Oriented Programming)

AOP는 Aspect Oriented Programming의 약자로 관점 지향 프로그래밍이라고 불린다.
모듈에서 공통으로 처리되는 사항, 즉 횡단 관심사항을 별도로 모듈화해서 재사용성을 극대화하기 위한 프로그래밍 방법이다. 

 

AOP 주요 개념 및 구성요소

- 핵심 관심 : 시스템의 핵심 가치와 목적이 드러난 관심 영역, 적용하고자 하는 핵짐 비지니스 로직 

  • 비즈니스 클래스 = XXXServiceImpl 클래스
  • 비즈니스 클래스에는 비즈니스 메서드가 구현되어 있으며, 비즈니스 메서드가 가진 코드를 핵심 관심 코드라고 한다.

- 횡단 관심 : 쉽게 분리된 모듈로 작성하기 힘든 요구 사항, 공통 모듈

  • 횡단 관심 클래스 = XXXAdvice 클래스
  • 횡단 관심 클래스에는 비즈니스 메서드마다 실행될 공통의 로직이 들어있으며, 공통 로직을 횡단 관심 코드라고 한다. 

- Aspect : 횡단 관심사를 구현한 모듈 ex. 로깅을 담당하는 Aspect, 트랜잭션 관리를 담당하는 Aspect 등 
- Joint Point : Aspect가 적용될 수 있는 지점 / 합류 지점, 메소드 실행시점 
- Advice :  Aspect가 Join Point에서 수행할 동작을 정의한 것 ex) 메서드 실행 전에 로깅을 추가하는 Before Advice, 메서드 실행 후에 처리를 수행하는 After Advice 등
- Pointcut : 어떤 Join Point에 어떤 Advice를 적용할 것인지 지정하는 것, 일종의 필터 역할을 함. 특정 패키지, 클래스, 메서드 등을 선택하여 포함하거나 제외할 수 있음
- Weaving : Aspect를 실제 코드에 적용하는 과정을 의미 


    

AOP는 OOP(Object-Oriented Programming)의 보완적인 개념으로, OOP에서는 기능을 클래스로 모듈화하여 사용하지만, AOP는 관심사를 횡단적으로 분리하여 모듈화한다. 
이를 통해 코드의 중복을 최소화하고, 유지보수성을 높여준다.

 


 

 

 

먼저 일반적인 객체 지향 프로그래밍(OOP) 방식으로 메소드를 호출해보자

 

package xyz.itwill06.oop;

public class OopOne {
	public void display1() {
		System.out.println("*** OopOne 클래스의 display1() 메소드 호출 ***");
	}
	
	public void display2() {
		System.out.println("*** OopOne 클래스의 display2() 메소드 호출 ***");
	}
	
	public void display3() {
		System.out.println("*** OopOne 클래스의 display3() 메소드 호출 ***");
	}
}


public class OopTwo {
	public void display1() {
		System.out.println("*** OopTwo 클래스의 display1() 메소드 호출 ***");
	}
	
	public void display2() {
		System.out.println("*** OopTwo 클래스의 display2() 메소드 호출 ***");
	}
	
	public void display3() {
		System.out.println("*** OopTwo 클래스의 display3() 메소드 호출 ***");
	}
}


public class OopApp {
	public static void main(String[] args) {
		OopOne one=new OopOne();
		OopTwo two=new OopTwo();
		
		one.display1();
		one.display2();
		one.display3();
		
		two.display1();
		two.display2();
		two.display3();
	}
}

 

 

 

OOP에서 메소드를 호출할 때 마다 로그파일에 기록하고 싶다면 어떻게 해야할까 (누가 언제 무엇을 했는지)

 

[OopOne]

[OopTwo]

[OopLogger]

[OopApp]

 

 

핵심관심코드: 프로그램 실행에 핵심적인 기능을 제공하는 명령이다. ex.데이터 처리 명령

횡단관심코드: 프로그램 실행에 보조적인 기능을 제공하는 명령이다. ex 로그처리, 권한 처리, 트랜잭션 처리, 예외 처리 등 

 

OOP의 문제점은 모듈화(캡슐화)가 너무 강력해서 핵심관심코드와 횡단관심코드를 분리하여 프로그램을 작성하는 것이 어렵다는 것이다. 

 

코드의 중복성이 높아 유지보수의 효율성이 감소된다. 

 


 

이번엔 AOP 프로그램을 작성해보자

 

package xyz.itwill06.oop;

//횡단관심코드가 삽입될 핵심관심코드로 작성된 메소드를 추상메소드로 선언 - PointCut
public interface Aop {
	void display1();
	void display2();
	void display3();
}

//핵심관심 모듈
public class AopOne implements Aop {

	@Override
	public void display1() {
    	//핵심관심코드
		System.out.println("*** AopOne 클래스의 display1() 메소드 호출 ***");
	}

	@Override
	public void display2() {
		System.out.println("*** AopOne 클래스의 display2() 메소드 호출 ***");
	}

	@Override
	public void display3() {
		System.out.println("*** AopOne 클래스의 display3() 메소드 호출 ***");
	}
}

//핵심관심모듈
public class AopTwo implements Aop {

	@Override
	public void display1() {
		System.out.println("*** AopTwo 클래스의 display1() 메소드 호출 ***");
	}

	@Override
	public void display2() {
		System.out.println("*** AopTwo 클래스의 display1() 메소드 호출 ***");
	}

	@Override
	public void display3() {
		System.out.println("*** AopTwo 클래스의 display1() 메소드 호출 ***");
	}
}

//main 메소드 
public class AopApp {
	public static void main(String[] args) {
		AopOne one=new AopOne();
		AopTwo two=new AopTwo();
		
		one.display1();
		one.display2();
		one.display3();
		
		two.display1();
		two.display2();
		two.display3();
	}
}

 

핵심관심 모듈이란 핵심관심코드로 작성된 메소드가 선언된 클래스를 뜻한다. 

횡단관심코드가 삽입될 메소드는 인터페이스를 상속받아 작성해야 한다.

 

 

횡단관심코드로 작성된 메소드가 선언된 Advice 클래스를 선언해준다.

 

[AopLogger]

package xyz.itwill06.oop;

//횡단관심코드로 작성된 메소드가 선언된 클래스 - 횡단관심모듈 : Advice 클래스
public class AopLogger {
	public void beforelog() {
    	//횡단관심코드
		System.out.println("### 메소드의 명령(핵심관심코드)이 실행되기 전에 기록될 내용");
	}
}

 

핵심관심코드에 횡단관심코드가 삽입된 메소드를 제공하는 클래스인 Proxy 클래스를 선언한다

 

 

[AopProxy]

package xyz.itwill06.oop;

//핵심관심코드에 횡단관심코드가 삽입된 메소드를 제공하는 클래스 - Proxy 클래스 : Aspect
public class AopProxy implements Aop {
	//핵심관심코드의 메소드가 선언된 클래스의 객체를 저장하기 위한 필드
	// => 필드의 자료형을 인터페이스로 선언하여 모든 자식클래스의 객체가 저장되도록 설정
	private Aop target;
	
	//횡단관심코드의 메소드가 선언된 클래스의 객체를 저장하기 위한 필드
	private AopLogger logger;
	
	//생성자를 이용하여 필드에 객체를 전달받아 저장하거나 객체를 직접 생성하여 필드에 저장 - 의존성 주입(DI)
	public AopProxy(Aop target) {
		this.target=target;
		logger=new AopLogger();
	}
	
	//인터페이스를 상속받아 오버라이드 선언된 메소드(Pointcut)에서 핵심관심코드에 횡단관심코드가 
	//삽입(Weaving)되어 명령이 실행되도록 작성
	// => 핵심관심코드가 작성된 메소드 호출 전 또는 후에 횡단관심코드가 작성된 메소드를 호출할 수 있다.
	// => 횡단관심코드가 삽입될 위치 : JoinPoint
	@Override
	public void display1() {
		logger.beforelog();//횡단관심코드가 작성된 메소드 호출
		target.display1();//핵심관심코드가 작성된 메소드 호출
	}

	@Override
	public void display2() {
		logger.beforelog();
		target.display2();
	}

	@Override
	public void display3() {
		logger.beforelog();
		target.display3();
	}
}

 

[AopApp]

package xyz.itwill06.oop;

public class AopApp {
	public static void main(String[] args) {
		//AopOne one=new AopOne();
		//AopTwo two=new AopTwo();
		
		AopProxy one=new AopProxy(new AopOne());
		AopProxy two=new AopProxy(new AopTwo());
		
		one.display1();
		one.display2();
		one.display3();
		
		two.display1();
		two.display2();
		two.display3();
	}
}

 

 

 

메소드의 명령(핵심관심코드)이 실행되기 전에 먼저 횡단관심코드가 실행된다.

 

 

이처럼 핵심관심코드와 횡단관심코드를 분리한 후 proxy 클래스를 이용해 실행할 수 있도록 만들어주는 것을 AOP(관점지향프로그래밍)라고 한다.


스프링 Aop를 사용해 AopProxy 클래스를 자동으로 만들어줄 수 있다.

 

 

[xyz.itwill07.aop] 패키지

 

@Data
public class Hewon {
	private int num;
	private String name;
}


public interface HewonDAO {
	int insertHewon(Hewon hewon);
	Hewon selectHewon(int num);
	List<Hewon> selectHewonList();
}

//핵심관심모듈(Core Concern Module)
public class HewonDAOImpl implements HewonDAO {

	@Override
	public int insertHewon(Hewon hewon) {
		System.out.println("*** HewonDAOImpl 클래스의 insertHewon(Hewon hewon) 메소드 호출 ***");
		return 0;
	}

	@Override
	public Hewon selectHewon(int num) {
		System.out.println("*** HewonDAOImpl 클래스의 selectHewon(int num) 메소드 호출 ***");
		return null;
	}

	@Override
	public List<Hewon> selectHewonList() {
		System.out.println("*** HewonDAOImpl 클래스의 List<Hewon> selectHewonList() 메소드 호출 ***");
		return null;
	}
	
}

 

 

핵심관심모듈(Core Concern Module)은 핵심관심코드로 작성된 메소드로 선언된 클래스를 뜻한다.

핵심관심코드는 데이터 처리를 위한 필수적인 명령이다.

 

 

service 인터페이스와 service 클래스 선언

//service 인터페이스
public interface HewonService {
	void addHewon(Hewon hewon);
	Hewon getHewon(int num);
	List<Hewon> getHewonList();
}


//핵심관심모듈
public class HewonServiceImpl implements HewonService {
	@Setter
	private HewonDAO hewonDAO;
	
	@Override
	public void addHewon(Hewon hewon) {
		System.out.println("*** HewonServiceImpl 클래스의 addHewon(Hewon hewon) 메소드 호출 ***");
		hewonDAO.insertHewon(hewon);
	}

	@Override
	public Hewon getHewon(int num) {
		System.out.println("*** HewonServiceImpl 클래스의 getHewon(int num) 메소드 호출 ***");
		return hewonDAO.selectHewon(num);
	}

	@Override
	public List<Hewon> getHewonList() {
		System.out.println("*** HewonServiceImpl 클래스의 List<Hewon> getHewonList() 메소드 호출 ***");
		return hewonDAO.selectHewonList();
	}
}

 

핵심관심모듈의 클래스를 Spring Bean으로 등록해준다

 

[07-1_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"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

	<!-- 핵심관심모듈의 클래스를 Spring Bean으로 등록 -->
	<bean class="xyz.itwill07.aop.HewonDAOImpl" id="hewonDAO"/>
	<bean class="xyz.itwill07.aop.HewonServiceImpl" id="hewonService">
		<!-- hewonDAO 필드에 injection 처리 -->
		<property name="hewonDAO" ref="hewonDAO"/>
	</bean>
</beans>

 

package xyz.itwill07.aop;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class HewonAopApp {
	public static void main(String[] args) {
		ApplicationContext context=new ClassPathXmlApplicationContext("07-1_aop.xml");
		HewonService service=context.getBean("hewonService", HewonService.class);
		System.out.println("============================================================");
		service.addHewon(null);
		System.out.println("============================================================");
		service.getHewon(0);
		System.out.println("============================================================");
		service.getHewonList();
		System.out.println("============================================================");
		((ClassPathXmlApplicationContext)context).close();
	}
}

 

 

 

 


횡단관심코드를 핵심관심코드가 실행되기 전 또는 후에 삽입되도록 처리해보자

 

횡단관심코드를 가지고 있는 클래스(횡단관심모듈)를 선언해주자

 

횡단관심모듈(CrossCutting Concern Module)은 횡단관심코드로 작성된 메소드가 선언된 클래스이며 Advice 클래스라고도 부른다.

횡단관심코드는 데이터 처리 명령을 제외한 보조적인 기능을 제공하는 명령이다. ex) 로그 처리, 보안(권한) 처리, 트랜잭션 처리, 예외 처리 등의 명령

 

JoinPoint는 핵심관심코드를 기준으로 횡단관심코드가 삽입되어 동작될 위치를 표현한다.

JoinPoint 중에서도 타겟메소드의 명령 실행전에 삽입되어 실행될 명령을 작성한 메소드를 Before Advice Method라고 부른다. 

타겟 메소드란 핵심관심모듈의 메소드 중 PointCut 표현식으로 지정되어 횡단관심코드가 삽입될 메소드를 뜻한다.

PointCut 표현식은 핵심관심모듈의 메소드 중 특정 메소드만 지정하기 위한 언어이다.

 

//횡단관심모듈(CrossCutting Consern Module) = Advice 클래스
public class HewonAdvice {
	//Before Advice Method
	public void beforeLog() {
		System.out.println("### [before]핵심관심코드 실행 전 삽입되어 실행될 횡단관심코드 ###");
	}
}

 

[07-1_aop.xml]

 

Spring AOP 기능을 사용하기 위해선 엘리먼트가 필요하다.

 

Spring Bean Configuration File에서 Spring AOP 기능을 구현하기 위해 AOP 네임스페이스를 추가하여 spring-aop.xsd 파일의 엘리먼트를 제공받아 사용할 수 있도록 설정해주어야 한다.

 

 

 

Spring AOP(Aspect Oriented Programming - 관점 지향 프로그래밍)

AspectJ 컴파일러에 의해 프로그램 실행시 핵심관심코드와 횡단관심코드를 결합(Weaving)되어 실행될 수 있도록 만들어주는 기능을 제공한다. 

Spring AOP 기능을 사용하기 위해서는 반드시 aspectjrt 라이브러리와 aspectjweaver 라이브러리가 프로젝트에 빌드 처리 되어있어야 하는데, 메이븐을 이용하면 된다. : pom.xml 

 

 

https://mvnrepository.com/

 

 

[pom.xml]

 

기존에 있던 aspectj와 버전을 똑같이 맞추어 주어야 한다.

 

 

aspectjrtAspectJ 컴파일러로 생성된 Proxy 클래스를 실행하기 위한 라이브러리이다. 

aspectjweaver는 AspectJ 컴파일러로 핵심관심코드와 횡단관심코드가 결합된 클래스(Proxy 클래스)를 생성하기 위한 라이브러리이다.

 

 

 

[07-1_aop.xml]

 

config : Spring AOP 관련 설정을 제공하기 위한 엘리먼트

 

aspect : 핵심관심코드에 횡단관심코드를 원하는 위치(JoinPoint)에 삽입하여 실행되도록 설정하기 위한 엘리먼트

  • 횡단관심코드가 삽입될 위치(JoinPoint)를 하위 엘리먼트로 설정한다.
  • 하위 엘리먼트 : before, after, after-returning, after-throwing, around
  • ref 속성 : 횡단관심모듈로 작성된 클래스의 Spring Bean의 식별자(beanName)를 속성값으로 설정한다. 
    • → 횡단관심코드의 메소드를 호출하기 위해 객체를 제공받기 위한 속성

 

before : 타겟메소드의 명령(핵심관심코드) 실행전 횡단관심코드로 삽입되어 실행되도록 설정하는 엘리먼트

  • method 속성 : 횡단관심코드의 메소드 이름을 속성값으로 설정한다. 
  • pointcut 속성 : 핵심관심모듈로 작성된 클래스의 Spring Bean의 메소드 중 횡단관심코드가 삽입될 메소드를 지정하기 위한 PointCut 표현식을 속성값으로 설정한다. 즉, 타겟메소드를 지정한다.
    • execition 명시자, within 명시자, bean 명시자를 사용하여 타겟메소드를 설정하며 명시자에 검색패턴문자연산자를 사용할 수 있다. 
      • PointCut 표현식에서 사용 가능한 검색패턴문자 : ..(0개 이상), *(1개 이상), ?(0개 또는 1개)
      • PointCut 표현식에서 사용 가능한 연산자 : !(Not), &&(And), ||(Or) 

 


 

 execition 명시자를 이용하여 타겟메소드를 지정하는 방법

 

 execition 명시자는 메소드의 머릿부를 표현하여 타겟메소드를 지정한다.

 

형식) 

exection([접근제한자] 반환형 [패키지.클래스.]메소드명(자료형,자료형,...))

 

클래스 대신 인터페이스를 사용하는 것도 가능하다. 인터페이스를 사용하면 인터페이스를 상속받은 모든 자식클래스를 표현해준다.

 

타겟메소드로 지정되는 클래스는 반드시 Spring Bean으로 등록된 클래스만 설정 가능하다.

 

반환형 또는 매개변수의 자료형이 클래스(인터페이스)인 경우 패키지를 포함하여 표현해야 한다.

 

<aop:config>
	<aop:aspect ref="hewonAdvice">
		<!-- 타겟메소드를 addHewon()로 지정하기 -->
		<aop:before method="beforeLog" pointcut="execution(void addHewon(xyz.itwill07.aop.Hewon))"/>
	</aop:aspect>
</aop:config>

 

 

[HewonAopApp] main 실행

 

 

모든 메소드를 타겟 메소드로 지정하기 (메소드의 매개변수가 0개 이상인 메소드를 타겟 메소드로 지정) 

..
<aop:before method="beforeLog" pointcut="execution(* *(..))"/>
..

 

 

 

메소드의 매개변수가 1개 이상인 메소드를 타겟 메소드로 지정(매개변수가 반드시 존재하는 메소드)

<aop:before method="beforeLog" pointcut="execution(* *(*))"/>

 

 

xyz.itwill07.aop 패키지(하위 패키지 미포함)의 모든 클래스의 모든 메소드를 타겟메소드로 지정

<aop:before method="beforeLog" pointcut="execution(* xyz.itwill07.aop..*(..))"/>

 

 

xyz.itwill07.aop 패키지와 하위 패키지의 모든 클래스의 모든 메소드를 타겟메소드로 지정
<aop:before method="beforeLog" pointcut="execution(* xyz.itwill07.aop..*(..))"/>
			
Hewon이라는 이름으로 시작되는 모든 클래스의 메소드를 타겟 메소드로 지정
<aop:before method="beforeLog" pointcut="execution(* xyz.itwill07.aop.Hewon*.*(..))"/>
			
get으로 시작되는 모든 메소드를 타겟 메소드로 지정
<aop:before method="beforeLog" pointcut="execution(* get*(..))"/>
			
get으로 시작되지 않는 나머지 모든 메소드를 타겟 메소드로 지정
<aop:before method="beforeLog" pointcut="!execution(* get*(..))"/>
			
매개변수가 int인 메소드 또는 반환형이 int인 메소드를 타겟 메소드를 지정
<aop:before method="beforeLog" pointcut="execution(* *(int)) or execition(int *(..))"/>

 

 


 

wihin 명시자를 사용하여 타겟메소드를 지정하는 방법

 

Spring Bean으로 등록된 클래스에 작성된 모든 메소드를 타겟메소드로 지정한다.

 

형식)

within(패키지명.클래스명)

 

<aop:before method="beforeLog" pointcut="within(xyz.itwill07.aop.HewonDAOImpl)"/>
<aop:before method="beforeLog" pointcut="within(xyz.itwill07.aop.HewonServiceImpl)"/>
			
<!-- xyz.itwill07.aop 패키지에 있는 모든 클래스의 메소드를 타겟 메소드로 지정 -->
<aop:before method="beforeLog" pointcut="within(xyz.itwill07.aop.*)"/>

 


bean 명시자를 사용하여 타겟메소드를 지정하는 방법

 

Spring Bean의 BeanName으로 등록된 클래스의 모든 메소드를 타겟메소드로 지정한다.

 

형식)

bean(beanName)

 

<aop:before method="beforelog" pointcut="bean(hewonDAO)"/>
<aop:before method="beforeLog" pointcut="bean(hewonService*)"/>

 


pointcut

 

pointcut은 Spring Bean으로 등록된 클래스(핵심관심모델)의 메소드 중 횡단관심코드에 삽입될 타깃메소드를 지정하기 위한 엘리먼트이다.

많이 사용되는 PointCut 표현식을 저장하여 타겟 메소드로 제공하기 위해 사용한다.

 

aspect 엘리먼트를 선언하기 전에 작성(전역)하거나 aspect 엘리먼트의 하위 엘리먼트로 작성(지역)할 수 있다.

  • expression 속성 : 타겟 메소드를 지정하기 위한 PointCut 표현식을 속성값으로 설정
  • id 속성 : pointcut 엘리먼트를 구분하기 위한 식별자를 속성값으로 설정

pointcut-ref 속성 : pointcut 엘리먼트의 식별자를 속성값으로 설정

 

<aop:pointcut expression="execution(* xyz.itwill07.aop.HewonDAO.*(..))" id="hewonDAOPointcut"/>
<aop:pointcut expression="execution(* xyz.itwill07.aop.HewonService.*(..))" id="hewonServicePointcut"/>
			
<aop:before method="beforelog" pointcut-ref="hewonDAOPointcut"/>

 


 

[HewonAdvice]에 afterLog() 메소드를 생성

//횡단관심모듈(CrossCutting Consern Module) = Advice 클래스
public class HewonAdvice {
	...
	
	//타겟메소드의 명령이 실행된 후 무조건 삽입되어 실행될 명령을 작성한 메소드 - After Advice Method
	public void afterLog() {
		System.out.println("### [after]핵심관심코드 실행 후 무조건 삽입되어 실행될 횡단관심코드 ###");
	}
}

 

after 

after은 타겟메소드의 명령(핵심관심코드) 실행 후 횡단관심코드를 무조건 삽입하여 실행되도록 설정하는 엘리먼트이다.

 

<aop:after method="afterLog" pointcut-ref="hewonServicePointcut"/>

 

//횡단관심모듈(CrossCutting Consern Module) = Advice 클래스
public class HewonAdvice {
	...
    
	//타겟메소드의 명령이 정상적으로 실행된 후 삽입되어 실행될 명령을 작성한 메소드 - After Returning Advice Method
	public void afterReturningLog() {
		System.out.println("### [after-returning]핵심관심코드가 정상적으로 실행 후 삽입되어 실행될 횡단관심코드 ###");
	}
}

 

 

after-returning

: 타겟메소드의 명령(핵심관심코드)이 정상적으로 실행된 후 횡단관심코드를 삽입하여 실행되도록 설정하는 엘리먼트 

 

<aop:after-returning method="afterReturningLog" pointcut-ref="hewonServicePointcut"/>

 

package xyz.itwill07.aop;

//횡단관심모듈(CrossCutting Consern Module) = Advice 클래스
public class HewonAdvice {
	...
	//타겟메소드의 명령 실행시 예외 발생된 후 삽입되어 실행될 명령을 작성한 메소드 - Advice Throwing Advice Method
	public void afterThrowingLog() {
		System.out.println("### [after-throwing]핵심관심코드 실행시 예외가 발생된 경우 삽입되어 실행될 횡단관심코드 ###");
	}
}

 

after-throwing

: 타겟메소드의 명령(핵심관심코드)이 실행될 때 예외가 발생된 경우 횡단관심코드를 삽입하여 실행되도록 설정하는 엘리먼트 

<aop:after-throwing method="afterThrowingLog" pointcut-ref="hewonServicePointcut"/>

 

package xyz.itwill07.aop;

import org.aspectj.lang.ProceedingJoinPoint;

public class HewonAdvice {
	...
	
	//타겟메소드의 명령 실행 전과 후에 삽입되어 실행될 명령을 작성한 메소드 - Around Advice Method
	public void aroundLog(ProceedingJoinPoint joinPoint) throws Throwable {
		System.out.println("### [around]핵심관심코드 실행 전 삽입되어 실행될 횡단관심코드 ###");
		joinPoint.proceed();//타겟메소드 호출 - 핵심관심코드 실행
		System.out.println("### [around]핵심관심코드 실행 후 삽입되어 실행될 횡단관심코드 ###");
	}
}

 

around 

: 타겟메소드의 명령(핵심관심코드)이 실행전과 후에 횡단관심코드를 삽입하여 실행되도록 설정하는 엘리먼트

 

<aop:around method="aroundLog" pointcut-ref="hewonServicePointcut"/>

예제)

 

 

package xyz.itwill07.aop;

//핵심관심모듈
public class JoinPointBean {
	public void add() {
		System.out.println("*** JoinPointBean 클래스의 add() 메소드 호출 ***");		
	}
	
	public void modify(int num, String name) {
		System.out.println("*** JoinPointBean 클래스의 modify(int num, String name) 메소드 호출 ***");		
	}
	
	public void remove(int num) {
		System.out.println("*** JoinPointBean 클래스의 remove(int num) 메소드 호출 ***");		
	}
	
	public String getName() {
		System.out.println("*** JoinPointBean 클래스의 getName() 메소드 호출 ***");		
		return "홍길동";
	}
	
	public void calc(int num1, int num2) {
		System.out.println("*** JoinPointBean 클래스의 calc(int num1, int num2) 메소드 호출 ***");
		System.out.println("몫 = "+(num1/num2));
	}
}

 

07-2_param.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.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
	
	<!-- 핵심관심모듈의 클래스를 Spring Bean으로 등록 -->
	<bean class="xyz.itwill07.aop.JoinPointBean" id="joinPointBean"></bean>
</beans>

 

package xyz.itwill07.aop;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class JoinPointApp {
	public static void main(String[] args) {
		ApplicationContext context=new ClassPathXmlApplicationContext("07-2_param.xml");
		JoinPointBean bean=context.getBean("joinPointBean", JoinPointBean.class);
		System.out.println("============================================================");
		bean.add();
		System.out.println("============================================================");
		bean.modify(1000, "홍길동");
		System.out.println("============================================================");
		bean.remove(2000);
		System.out.println("============================================================");
		bean.getName();
		System.out.println("============================================================");
		bean.calc(20, 10);
		System.out.println("============================================================");
		((ClassPathXmlApplicationContext)context).close();
	}
}

 

 

 

메소드가 호출되기 전에 

 

 

JoinPointAdvice

package xyz.itwill07.aop;

//횡단관심모듈 - Advice 클래스
public class JoinPointAdvice {
	//Before Advice Method
	public void beforeDisplay() {
		System.out.println("### [before] 핵심관심코드 실행 전 삽입되어 실행될 횡단관심코드 ###");
	}
}

 

	<!-- 핵심관심모듈의 클래스를 Spring Bean으로 등록 -->
	<bean class="xyz.itwill07.aop.JoinPointBean" id="joinPointBean"/>
	
	<!-- 횡단관심모듈의 클래스를 Spring Bean으로 등록 -->
	<bean class="xyz.itwill07.aop.JoinPointAdvice" id="joinPointAdvice"/>
	
	<aop:config>
		<aop:aspect ref="joinPointAdvice">
			<aop:before method="beforeDisplay" pointcut="execution(* *(..))"/>
		</aop:aspect>
	</aop:config>

 

main 실행

 


 

Around Advice Method를 제외한 나머지 Advice Method는 일반적으로 반환형을 [void]로 작성하고 매개변수를 작성하지 않거나 JoinPoint 인터페이스를 자료형으로 사용하는 매개변수로 선언이 가능하다.


만약 Advice 클래스의 메소드를 작성 규칙에 맞지 않게 작성할 경우 IllegalArgumentException이 발생한다.

 

JoinPoint 객체는 타겟 메소드 관련 정보가 저장된 객체이다.

 

스프링 컨테이너가 Advice 클래스의 메소드를 호출할 때 타겟 메소드 관련 정보가 저장된 JoinPoint 객체를 생성하여 매개변수에 전달한다.

 

Advice 클래스의 메소드에서 타겟 메소드에 대한 정보가 필요한 경우 매개변수를 선언하여 JoinPoint 객체를 전달받아 메소드를 호출하여 필요한 정보를 반환받아 사용할 수 있다.

 

public class JoinPointAdvice {
	//Before Advice Method
	public void beforeDisplay(JoinPoint joinPoint) {
		System.out.println(joinPoint.getTarget().getClass().getName());
	}
}

 

joinPoint.getTarget() : 타겟 메소드를 호출한 객체(핵심관심모듈의 Spring Bean)를 반환하는 메소드

Object.getClass() : 객체를 생성한 클래스의 Class(Clazz)를 반환하는 메소드

Class.getName() : Class 객체에 저장된 클래스의 이름(패키지 포함)을 반환하는 메소드 

 

 

main 실행

 

 

Class.getSimpleName() : Class 객체에 저장된 클래스의 이름(패키지 제외)을 반환하는 메소드

 

System.out.println(joinPoint.getTarget().getClass().getSimpleName());

 

 

 

JoinPoint.getSignature() : 타겟 메소드의 정보가 저장된 Signature 객체를 반환하는 메소드

Signature.getName() : 타겟 메소드의 이름을 반환하는 메소드 

 

System.out.println(joinPoint.getSignature().getName());

 

 

 

 

JoinPoint.getArgs() : 타겟 메소드의 매개변수에 저장된 모든 값(객체)을 제공받아 Object 배열로 반환하는 메소드

System.out.println(joinPoint.getArgs());

 

Object 객체의 1차원 배열

 

public class JoinPointAdvice {
	//Before Advice Method
	public void beforeDisplay(JoinPoint joinPoint) {
		String className=joinPoint.getTarget().getClass().getSimpleName();
		String methodName=joinPoint.getSignature().getName();
		Object[] params=joinPoint.getArgs();
		
		System.out.print("### [before]"+className+" 클래스의 "+methodName+"(");
		for(int i=0;i<params.length;i++) {
			System.out.print(params[i]);
			if(i < params.length-1) {
				System.out.print(", ");
			}
		}
		System.out.println(") 메소드 호출 ###");
	}
}

 

 

이처럼 기록해야할 때 많이 사용한다.

 

 

 

또 다른 예시

 

After Advice Method 선언

//After Advice Method 
	public void displayMessage() {
		Object[] params=joinpoint.getArgs();
		System.out.println("### [after]사원번호가 "+params[0]+"인 사원정보를 삭제 하였습니다. ###");
	}

 

remove가 실행될 때만 호출되어 실행

..
<aop:after method="displayMessage" pointcut="execution(void remove(int))"/>
..

 

 

 

 

 

 

 


 

0801

 

After Returning Advice Method 선언

 

  • JoinPoint 인터페이스의 매개변수 외에 Object 클래스의 매개변수를 선언할 수 있다.
  • 스프링 컨테이너는 Object 클래스의 매개변수에 타겟메소드의 반환값을 제공받아 전달해준다.
  • 타겟메소드에서 반환되는 값(객체)의 자료형이 고정되어 있는 경우 Object 클래스 대신 반환되는 값(객체)의 자료형으로 매개변수를 작성할 수도 있다. 
//Advice 클래스
public class JoinPointAdvice {
    public void displayName(JoinPoint joinPoint, Object object) {	
        String methodName=joinPoint.getSignature().getName();
        String userName=(String)object;//Object 타입이기 때문에 형변환을 권장 
        System.out.println("### [after-returning]"+methodName+" 메소드의 반환값 = "+userName);
    }
}

 

Object 클래스의 매개변수가 선언된 경우 Srping Bean Configuration File의 AOP설정에서 after-returning 엘리먼트에 반드시 returing 속성을 사용하여 반환값을 저장할 매개변수의 이름을 속성값으로 지정해주어야 한다.

(after-returning 엘리먼트에 returning 속성이 없거나 속성값이 잘못된 경우 IllegalArgumentException이 발생)

 

returning 속성에는 타겟메소드의 반환값을 전달받아 저장하기 위한 After Returning Advice Method의 매개변수 이름을 속성값으로 설정해주면 된다.

 

[07-2_param.xml]

..
<aop:after-returning method="displayName"
	pointcut="execution(java.lang.String getName())" returning="object"/>
..

 


 

After Throwing Advice Method 선언

public void exceptionHandle() {
	System.out.println("### [after-throwing]핵심관심코드 실행시 예외가 발생한 경우 실행될 횡단관심코드 ###");
}

 

..
<aop:after-throwing method="exceptionHandle" 
	pointcut="execution(void calc(int, int))"/>
..

 

예외가 발생될 때 호출된다.

 

 

After Throwing Advice Method에는 Joint 인터페이스의 매개변수 외에 Exception 클래스의 매개변수 선언이 가능하다.

  • 스프링 컨테이너는 Exception 클래스의 매개변수에 타겟메소드에서 발생된 Exception 객체를 제공받아 전달한다.
  • 타겟메소드에서  발생되는 예외가 고정되어 있는 경우 Exception 클래스 대신 필요한 예외 클래스로 매개변수 작성이 가능하다.
  • Exception 클래스의 매개변수가 선언된 경우 Spring Bean Configuration File의 AOP 설정에서 after-throwing 엘리먼트에 반드시 throwing 속성을 사용하여 발생된 예외를 저장하기 위한 매개변수의 이름을 속성값으로 지정해주어야 한다.
  • after-throwing 엘리먼트에 throwing 속성이 없거나 속성값이 잘못된 경우 IllegalArgumentException이 발생한다.

 

public void exceptionHandle(JoinPoint joinPoint, Exception exception) {
	String methodName=joinPoint.getSignature().getName();
		
	System.out.println("### [after-returning]"+methodName+" 메소드에서 발생된 예외 = "
			+exception.getMessage());
}

 

throwing 속성에는 타겟메소드에서 발생된 예외를 전달받아 저장하기 위한 After Throwing Advice Method의 매개변수 이름을 속성값으로 설정하면 된다.

 

<aop:after-throwing method="exceptionHandle" 
	pointcut="execution(void calc(int, int))" throwing="exception"/>

 

 


 

 

Around Advice Method 메소드는 반환형을 Object 클래스로 작성하고 매개변수는 ProceedingJoinPoint 인터페이스로 작성하여 선언한다.

  • 반환형을 Object 클래스로 작성하는 이유는 타겟메소드의 반환값을 제공받아 반환 처리하기 위해서이다.
  • 타겟메소드 관련 정보를 ProceedingJoinPoint 인터페이스의 매개변수로 제공받아 Around Advice Method에서 사용한다. 

ProceedingJoinPoint 객체는 타겟메소드 관련 정보를 저장하기 위한 객체이다. 

  • JoinPoint 객체와 다른점은 타겟 메소드를 직접 호출하기 위한 메소드인 procced()를 제공해준다는 것이다. (나머지는 JoinPoint 객체와 동일)

 

Around Advice Method 선언

public Object display(ProceedingJoinPoint joinPoint) throws Throwable {
	System.out.println("### [around]핵심관심코드 실행 전 삽입되어 실행될 횡단관심코드 ###");
	Object result=joinPoint.proceed();//타겟메소드를 호출 - 핵심관심코드 실행
	System.out.println("### [around]핵심관심코드 실행 후 삽입되어 실행될 횡단관심코드 ###");
	return result;//타겟메소드의 반환값을 반환 처리
}

 

ProceedingPoint.procced() : 타겟메소드를 호출하는 메소드 - 핵심관심코드 실행

  • 타겟메소드를 호출하여 바환되는 결과값을 변수에 저장해 반환처리 한다.
  • Throwable(Error 클래스와 Exception 클래스의 부모클래스) 객체가 발생되므로 반드시 예외 처리하거나 예외를 전달하면 된다. 
<aop:around method="display" pointcut="execution(* modify(..))"/>