본문 바로가기
Spring 프레임워크/이론

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

by Hwanii_ 2023. 8. 10.
728x90

[ 참고 ]
객체 지향 프로그래밍 이란 ?
Object Oriented Programming. 즉, OOP 라고 부른다.
객체 지향 프로그래밍은
응집도가 낮고,   
응집도가 낮다는것은,
관련 없는 다양한 기능이 하나의 클래스에 섞여 있음을 의미 하며,
이는 유지보수가 어렵고, 코드의 가독성과 이해도를 낮출 수 있게 된다.
이러한 객체 지향 프로그래밍의 단점을 보완하기 위해서,
AOP == 관점 지향 프로그래밍을 사용 할 수 있다.

 

/*

자체 솔루션 (프레임워크) 에서 이러한 AOP 개념을 사용하기에,

파악 하기 위해서는 아래의 내용을 이해 해야 한다.

*/
 
1.
"Spring 프레임워크는 IOCAOP를 지원하는 경량의 프레임워크"
AOP 이란 ?
AOP는 Aspect Oriented Programming 의 약자로, 관점 지향 프로그래밍을 의미 한다.
프로그래밍 패러다임 중 하나이며,
프로그램의 여러 부분에 걸쳐 있는 관심사를 분리하여,
모듈화 하고,
이렇게 모듈화된 관심사들을 캡슐화 하는 개념을 의미 한다.
모듈화 == 재사용성 증가 == 높은 응집도 == 가독성 증가 == 유지 보수 용이
 
2.
예시)
LoginController 클래스 내부에 login() 메서드가 있다고 가정해보자.
 
login() 메서드 내부의 코드는 아래와 같이 구성 되어 있다고 해보자.
 
1) 사용자로부터 데이터가 잘 들어왔는지 개발자용 로그
2) 보안, 인증, 허가 (권한 확인 단계)
3) 비즈니스 메서드 (== CRUD == 핵심 로직 == 핵심 관심) 실행 ( selectOne() )
4) 트랜잭션, 보안 관제 로그, ..
 
위와 같은 여러 XxxController 클래스가 존재하고,
여러 종류의 사용자 요청에 따라서,

각각의 요청에 맞는 XxxController 내부의 Xxx() 메서드를 수행 하게 된다.
 
이때,
1) 사용자로부터 데이터가 잘 들어왔는지 개발자용 로그
2) 보안, 인증, 허가 (권한 확인 단계)
4) 트랜잭션, 보안 관제 로그, ..
은 공통적으로 계속해서 Xxx() 메서드 내부에서 구성 되는 코드이고,
 
3) 비즈니스 메서드 (== CRUD == 핵심 로직 == 핵심 관심) 실행
은 사용자의 요청에 따라서 매번 달라 지게 된다.
 
어 ? 그러면, 3은 계속 달라지는데,
1, 2, 4는 달라지지 않구나 ~ 라는것을 알 수 있고,
 
이때, "관심 분리" 라는 개념을 사용 해보자는 생각을 할 수 있게 된다.
 
3.
관심 분리 (Separation of Concerns) == 로직 분리 이란 ?
 
관심 분리는 관점 지향 프로그래밍의 핵심 개념 중 하나로,
이 개념은, 각 모듈 또는 클래스는 하나의 목적을 가지며,
그 목적에 집중 해야 하고,
각 모듈이나 클래스는 다른 부분과의 결합도를 최소화 하고 독립적으로 유지 되어야 함을 의미 한다.
 
AOP는 비즈니스 로직과 횡단 관심 이라 불리는,

부가적인 관심사를 분리하여 개발 할 수 있도록 도와 준다.
 
1) 사용자로부터 데이터가 잘 들어왔는지 개발자용 로그
2) 보안, 인증, 허가
3) 비즈니스 메서드 (== CRUD == 핵심 로직 == 핵심 관심)
4) 트랜잭션, 보안 관제 로그, ..
 
1, 2, 4 를
공통 로직 == 횡단 관심 == 횡단 로직 
이라 부르며,
 
3 을
비즈니스 메서드 == 핵심 관심 == 핵심 로직 == CRUD
이라 부른다.
 
횡단 관심 이란 ?
Cross-cutting Concerns 라고 하며,
보안, 로깅, 트랜잭션 관리 등과 같은 부가적인 관심사를 지칭하는 용어 이다.
 
4.
AOP의 주요 구성은 아래와 같다.
 
1) Advice (어드바이스)
횡단 관심사의 실제 동작을 구현한 코드 이다.
횡단 관심 == 공통 로직
비즈니스 메서드를 기준으로, 전 / 후 / 함께 / .. 언제든지 동작 시점을 설정 할 수 있다.
 
before : 비즈니스 메서드 이전에 어드바이스가 호출 된다.
after : 비즈니스 메서드 이후에 어드바이스가 호출 된다.
after-returning : 비즈니스 메서드가 메서드 수행 이후에 수행 결과를 반환 하면, 어드바이스가 호출 된다.
after-throwing : 비즈니스 메서드가 메서드 수행 이후에 에러가 발생 하게 되면, 어드바이스가 호출 된다.
★ around ★
비즈니스 메서드를 수행 하기 전과 수행 하기 후에 모두 호출 된다.
around가 진행 되는 동안에 비즈니스 메서드가 비즈니스 메서드가 around 내부에서 호출 되는 것이다.
 
2) Pointcut (포인트컷)
핵심 관심 == 핵심 로직 == 비즈니스 메서드 == CRUD 이다.
공통 로직 == 횡단 관심인 Advice가 결합될 대상을 의미 한다.
포인트컷은 횡단 관심 == 공통 로직이 적용될 시점을 지정하며,
AOP 에서 어드바이스가 적용될 위치를 결정 한다.
 
3) Aspect (어스펙트)
어드바이스와 포인트컷의 결합으로,
어드바이스와 포인트컷의 결합 그 자체를 의미 한다.
어스펙트 설정에 따라, 위빙이 처리 된다.
 
4) Weaving (위빙)
포인트컷으로 지정한 핵심 관심 == 핵심 로직 == 비즈니스 메서드 == CRUD가 호출 될 때,
어드바이스에 해당 하는 횡단 관심 == 공통 로직 메서드가 삽입 되는,
과정 그 자체를 의미 한다.
 
참고로, 스프링은 런타임 위빙 방식을 사용 한다.
 
5) Joinpoint (조인포인트)
예비 포인트컷  이다.
포인트컷이 될 수 있는 대상들을 의미 한다.
즉,
핵심 관심 == 핵심 로직 == 비즈니스 메서드 == CRUD
후보들 이라고 생각 할 수 있다.
 
5.
이제 "관심 분리" 개념으로 관점 지향 프로그래밍 (AOP) 를 진행 해 보려고 한다.
 
횡단 관심 == 공통 로직 으로 로그 관련 부분을 아래에서 정리.
 
AOP (관점 지향 프로그래밍) 을 하기 위해서,
AOP 라이브러리를 추가 해야 한다.
 
1)
pom.xml 설정 파일 내부에서 라이브러리를 추가 하면 된다.
 

<!-- AOP -->
      	<dependency>
	         <groupId>org.aspectj</groupId>
	         <artifactId>aspectjrt</artifactId>
	         <version>${org.aspectj-version}</version>
      	</dependency>
      	<dependency>
	         <groupId>org.aspectj</groupId>
	         <artifactId>aspectjweaver</artifactId>
	         <version>1.8.8</version>
      	</dependency>

 

 
2)
applicationContext.xml 에 AOP 네임스페이스를 추가 한다.
 

AOP 로그는, 설정 완료 되는 시점이,
사용자의 *.do 요청을 하는 시점 보다,
더 먼저 수행 되어야 하기 때문 이다.

 
즉,
XxxController 가 호출 되기 전에, 선행 되어야 하기 때문에,
applicationContext.xml 에 추가 하면 된다.
web.xml 에서 applicationContext.xml 을 루트 컨테이너로 설정 했었다.
 
https://hwanii96.tistory.com/329

 

레이어 개념 정리

[ 결론 ] 비즈니스 로직 & 서비스 계층은 applicationContext.xml 에서 객체화 설정 작업을 한다. 즉, DAO, Service 류의 객체화는 applicationContext.xml 에서 작업 한다. 이때, DAO, Service 류는 서버가 시작할 때 "

hwanii96.tistory.com

 

<?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:context="http://www.springframework.org/schema/context"
   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/context http://www.springframework.org/schema/context/spring-context-4.2.xsd
      http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">

 
applicationContext.xml 에 AOP 네임스페이스를 추가 하거나, 상단에 위의 코드를 직접 작성 하면 된다.
 
3)
모든 XxxController 가 공통적으로 사용 하는 로그니까,
로그를 작성 하기 위한 클래스를,
com.spring.biz.common 패키지 내부에 생성 하면 된다.
 

 
6.

[ .xml 파일 설정 방식 ]

 

----------------------------------------------------------------------------

 

(@어노테이션 방식 설정은 다음글에서 작성할 예정 이다)

++ 23.08.11 추가

 

링크 : https://hwanii96.tistory.com/334

 

AOP : 관점 지향 프로그래밍 (@ 어노테이션 방식으로 로그 찍기)

 

hwanii96.tistory.com

 

----------------------------------------------------------------------------
아래는 applicationContext.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:context="http://www.springframework.org/schema/context"
   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/context http://www.springframework.org/schema/context/spring-context-4.2.xsd
      http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">
      
    <context:component-scan base-package="com.spring.biz"/>
      
    <bean id = "logAdvice" class ="com.spring.biz.common.LogAdvice" />
	<bean id = "logAdvice02" class ="com.spring.biz.common.LogAdvice02" />
	<bean id = "aroundAdvice" class = "com.spring.biz.common.AroundAdvice" />
	<bean id = "throwingAdvice" class = "com.spring.biz.common.ThrowingAdvice" />
	<bean id = "returningAdvice" class = "com.spring.biz.common.ReturningAdvice" />
    
    <aop:config>
   	
		<aop:pointcut expression = "execution(* com.spring.biz..*Impl.*(..))" id = "aPointcut"/>
		<aop:pointcut expression = "execution(* com.spring.biz..*Impl.select*(..))" id = "bPointcut"/>
   		<aop:pointcut expression = "execution(* com.spring.biz..*Impl.insert*(..))" id = "cPointcut"/>
   		<aop:pointcut expression = "execution(* com.spring.biz..*Impl.*(..))" id = "dPointcut"/>
   		<aop:pointcut expression = "execution(* com.spring.biz..*Impl.*(..))" id = "ePointcut"/>
		
		<aop:aspect ref = "logAdvice">
        	<aop:before method = "printLog" pointcut-ref = "aPointcut" />
      	</aop:aspect>
		
		<aop:aspect ref = "logAdvice02">
        	<aop:after method = "printLog02" pointcut-ref = "bPointcut" />
      	</aop:aspect>
      	
		<aop:aspect ref = "aroundAdvice">
			<aop:around method = "aroundPrintLog" pointcut-ref = "cPointcut" />
		</aop:aspect>
		
		<aop:aspect ref = "throwingAdvice">
			<aop:after-throwing method = "throwingPrintLog" pointcut-ref = "dPointcut"/>
		</aop:aspect>
		
		<aop:aspect ref = "returningAdvice">
			<aop:after-returning method = "returningPrintLog" pointcut-ref = "ePointcut"/>
		</aop:aspect>
		
   	</aop:config>
    
</beans>

 

<bean id = "logAdvice" class ="com.spring.biz.common.LogAdvice" />

<bean id = "logAdvice02" class ="com.spring.biz.common.LogAdvice02" />

<bean id = "aroundAdvice" class = "com.spring.biz.common.AroundAdvice" />

<bean id = "throwingAdvice" class = "com.spring.biz.common.ThrowingAdvice" />

<bean id = "returningAdvice" class = "com.spring.biz.common.ReturningAdvice" />

 
로그 (로그 기능을 하는 메서드) 가 작성된 클래스 (자료형) 을 객체화 하기 위한 <bean> 태그 이다.
 

<aop:pointcut expression = "execution(* com.spring.biz..*Impl.*(..))" id = "aPointcut"/>

<aop:pointcut expression = "execution(* com.spring.biz..*Impl.select*(..))" id = "bPointcut"/>

<aop:pointcut expression = "execution(* com.spring.biz..*Impl.insert*(..))" id = "cPointcut"/>

<aop:pointcut expression = "execution(* com.spring.biz..*Impl.*(..))" id = "dPointcut"/>

<aop:pointcut expression = "execution(* com.spring.biz..*Impl.*(..))" id = "ePointcut"/>

 
<aop:pointcut> 태그는 핵심 로직 == 비즈니스 메서드 == CRUD == 핵심 관심 을 의미 한다.

 

execution()

>> 메서드 실행 지점을 선택하는 키워드 이다.
 
execution() 괄호 안의 문법은 다음과 같다.
 
*
>> OUTPUT 자리 이다. (모든 리턴 타입을 의미 한다)
 
com.spring.biz..*Impl
>> com.spring.biz 패키지 아래에 있는 모든 클래스 중에서,
이름이 Impl 로 끝나는 클래스를 선택 한다.
 
이때,
*Impl. 뒤에 바로 *을 붙히면, 모든 CRUD 메서드를 지정 하고,
*Impl. 뒤에 .insert* 라던지, .select* 라던지, .update* 라던지, .delete* 를 붙혀주면,
insert 또는 select 또는 update 또는 delete 로 시작하는 메서드 이름을 선택 한다.

 

*은 0개 이상의 문자열을 나타내는것을 의미한다.

 
(..)
>> INPUT 자리 이다. (모든 매개변수를 의미 한다)
 
그렇게 설정한 실행 시점을 id = "이름" 으로 해서 pointcut의 이름을 정해준다. (== 식별자)
 
[ 참고 ]
위와 같이,  포인트컷을 여러개로 설정 하는 이유 ?
 
C, U, D는 데이터에 대한 확실한 검증이 필요 하다.
따라서, 상대적으로 보안, 유효성 검사, 트랜잭션, .. 등이 많이 필요해진다.
 
반면에,
R은 데이터에 대한 확실한 검증이 크게 필요 없다.
DB에 변화를 주는게 아니라, 단순히 읽는 용도의 기능을 수행 하기 때문이다.
따라서, 보려고 하는 데이터에 접근 할 수 있는 권한이 있는지만 확인 하면 된다.
 
위와 같은 이유로 인해 pointcut을 여러개 설정 한다.
일부 pointcut 에만 어드바이스가 (== 공통 로직 == 횡단 관심) 동작 할 수 있도록 설정을 한다는 의미 이다.
 

<aop:aspect ref = "logAdvice">

<aop:before method = "printLog" pointcut-ref = "aPointcut" />

</aop:aspect>

 

<aop:aspect ref = "logAdvice02">

<aop:after method = "printLog02" pointcut-ref = "bPointcut" />

</aop:aspect>

 

<aop:aspect ref = "aroundAdvice">

<aop:around method = "aroundPrintLog" pointcut-ref = "cPointcut" />

</aop:aspect>

 

<aop:aspect ref = "throwingAdvice">

<aop:after-throwing method = "throwingPrintLog" pointcut-ref = "dPointcut"/>

</aop:aspect>

 

<aop:aspect ref = "returningAdvice">

<aop:after-returning method = "returningPrintLog" pointcut-ref = "ePointcut"/>

</aop:aspect>

 

<aop:aspect> 태그는,

어드바이스 (공통 로직 == 횡단 관심) 와 포인트컷 (CRUD == 비즈니스 메서드 == 핵심 로직 == 핵심관심) 을

결합 하는 기능을 가진다.

 

logAdvice 라는 참조변수로 객체화된 것을 참조 하고,

객체화된 클래스 내부에 printLog 라는 이름을 가진 메서드를,

aPointcut 이라는 이름으로 세팅해 놓은 포인트컷(CRUD == 비즈니스 메서드) 에 연결하여,

어드바이스 (공통 로직 == 횡단 관심) 를 설정 한다.

 

위의 작성된 코드는 각각의 유형인 4가지의 어드바이스 이다.

 

7.

로그 메서드가 작성 되어 있는 클래스를 확인 하기.

 

 

 

 

 

 

around 는 다른 유형의 어드바이스와 == 공통 로직 == 횡단 관심  다르게,

메서드 시그니쳐가 다르다.

이유는, 외부의 비즈니스 메서드를 호출 하기 때문 이다.

따라서, ProceedingJoinPoint pjp를 인자로 맞춰 줘야 한다.

 

8.

서버를 돌려서, 로그를 직접 확인 해보자.

 

 

서버가 열리면, 메인페이지에는, 게시글을 보여주기 위해서,

selectAll() 메서드가 호출 된다.

 

이때, 로그가 아래와 같이 뜨는것을 확인 할 수 있다.

 

 

 

BoardDAO 클래스 내부에 selectAll() 메서드 내부에 log를 직접 작성했었는데,

selectAll() 실행 전에 로그가 호출 되는것을 볼 수 있다.

 

9.

Impl 내부에 있는 insert() 메서드에 대해서는,

around 어드바이스랑 어스펙트 되도록 설정을 해뒀다.

 

 

10.

after-throwing 은 예외가 발생 할 때, 호출 시키는 로그로써,

공부를 위해 강제로 예외를 발생 시켜 보자.

이때, 짚고 넘어가야할 부분은 다음과 같다.

 

일단, 기본적으로 DAO 클래스 내부까지 들어와서 발생하는 에러는,

각 CRUD 메서드 내부에 존재하는 try-catch 문 때문에,

예외처리를 기본적으로 하게 된다.

 

그러면 어디에다가 강제로 예외를 발생 시킬 수 있을까 ?

 

바로 DAO를 직접 사용 하면 결합도가 높기 때문에,

@Service() 어노테이션을 사용 해서,

DAO 대신에 XxxService 참조변수를 주체로 해서 특정한 CRUD 메서드를 호출하는식으로

코드를 작성 했었다.

 

따라서, 여기서 예외를 발생 시키면 된다.

 

 

작성자 ID가 TEST02 이면 발생하는 예외를 만들었고,

 

이 insert() 메서드가 사용되는 곳에서는 try-catch 문으로 작성해 주면 된다.

 

 

 

 

 

++ 추가 내용 업데이트

 

https://hwanii96.tistory.com/333

 

AOP : 관점 지향 프로그래밍 (.xml 파일 설정으로 로그 찍기)

https://hwanii96.tistory.com/332 AOP (Aspect Oriented Programming) (관점 지향 프로그래밍) [ 참고 ] 객체 지향 프로그래밍 이란 ? Object Oriented Programming. 즉, OOP 라고 부른다. 객체 지향 프로그래밍은 응집도가 낮

hwanii96.tistory.com

 

반응형