WebApplication

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

coolcode 2022. 12. 25. 23:39
AOP 

메서드 안의 주기능과 보조기능을 분리한 후 선택적으로 메서드에 적용해서 사용한다는 개념

AOP를 사용하면 전체 코드에 흩어져 있는 보조 기능을 공통으로 묶어 관리할 수 있고, 설정을 통해 이러한 보조 기능을 특정 메서드나 클래스에 적용할 수 있음

ex) 주요 기능(회원/상품/주문관리 등)은 아니지만 보안/ 로깅/ 트랜잭션 처리 와  같이 모든 기능에 공통으로 필요로 하는 작업을 AOP로 처리함

 

스프링에서의 AOP 기능

관련 용어 정리

- aspect : 구현하고자 하는 보조 기능

- advice : aspect의 실제 구현체(class)를 의미하며 메서드 호출을 기준으로 여러 joinpoint에서 실행

- joinpoint : advice를 적용하는 지점으로 스프링에서는 메서드 결합점만 제공

- pointcut : advice가 적용되는 대상을 지정함. package / class/ method 이름을 정규식으로 지정하여 사용

- target : advice가 적용되는 클래스

- weaving : advice를 주기능에 적용하는 것

스프링에서의 AOP기능 구현 방법 > AOP관련 API 또는 @Aspect 어노테이션의 사용

 

스프링 API를 이용한 AOP 기능 구현 

target(적용대상) 클래스 지정 > advice(보조기능구현) 클래스 지정 > 설정 파일에서 pointcut 설정 > 설정 파일에서 advice와 pointcut을 결합하는 advisor 설정 > 설정파일에서 스프링의 proxyFactoryBean 클래스를 이용해 target클래스에 advice 설정 > getBean() 메서드로 bean 객체에 접근해 사용

>  AOP기능을 수행하는 advice 인터페이스들의 추상 메서드 종류

void before(Method method, Object[] ars, Object target) throws Throwable{
    // MethodBeforeAdvice 인터페이스의 추상메서드에 대한 구현부
    // 해당 메서드 실행 전 
}

 // Method method : 대상 객체에서 실행된 메서드를 나타내는 메서드 객체
 // Object[] args : 메서드 인자 목록
 // Object target : 대상 객체
 

void afterReturnig(Object returnValue, Method method, Object[] ars, Object target, Exception ex){
    // ArterReturningAdvice 인터페이스의 추상메서드에 대한 구현부
    // 해당 메서드 실행 후
}

 // Object returnValue : 대상 객체의 메서드가 반환하는 값
 // Exception ex : 발생한 예외 타입
 

void afterThrowing(Method method, Object[] ars, Object target, Exception ex){
    // TrowsAdvice 인터페이스의 추상메서드에 대한 구현부
    // 해당 메서드에서 예외 발생 시
}
 
 
Object invoke(MethodInvocation invocation)throw Trowable{
    // MethodInterceptor 인터페이스의 추상메서드에 대한 구현부
    // 해당 메서드의 실행 전후와 예외 발생 시
}

// MethodInvocation invocation : 대상 객체의 모든 정보를 담고 있는 객체(호출된 메서드, 인자 등)

> 이중 MethodInterceptor는 invoke() 메서드를 이용해 다른 세가지 인터페이스들의 기능을 동시 수행할 수 있음

// AOP 설정 XML
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN"
 "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
	
<beans>
	<bean id="calcTarget" class="com.pro.yoon.Calculator" />  //target class bean
	<bean id="logAdvice" class="com.pro.yoon.LoggingAdvice" />  //logging advice bean

	<bean id="proxyCal" class="org.springframework.aop.framework.ProxyFactoryBean">  
    //ProxyFactoryBean : target과 advice를 엮어줌
		<property name="target" ref="calcTarget"/> //target bean을 calcTarget bean으로 지정함
		<property name="interceptorNames">  
        //Spring ProxyFactoryBean의 interceptorNames 속성에 
        //'logAdvice'를 advice bean으로 설정하여 target 클래스의 메서드 호출시 logAdvice를 실행함
			<list>  
				<value>logAdvice</value>  //<value>태그를 사용하여 advice를 추가해주면 됨
			</list>
		</property>
	</bean>
</beans>

> 주기능인 target클래스 Calculator와 log를 확인하는 보조기능의 advice 클래스 예제

package com.pro.yoon;

// Target class
public class Calculator {
	
	public void add(int x, int y) {
		int result = x+y;
		System.out.println("결과 ::" + result);
	}
	
	public void subtract(int x, int y) {
		int result = x-y;
		System.out.println("결과 ::" + result);
	}

	public void multiply(int x, int y) {
		int result = x*y;
		System.out.println("결과 ::" + result);
	}

	public void devide(int x, int y) {
		int result = x/y;
		System.out.println("결과 ::" + result);
	}
}
package com.pro.yoon;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

// Advice class
public class LoggingAdvice implements MethodInterceptor{

	@Override
	public Object invoke(MethodInvocation invocation) throws Throwable {
		System.out.println("[메서드 호출 전 : LoggingAdvice]");
		System.out.println(invocation.getMethod() + "메서드 호출 전"); // 메서드 호출 전 수행 구문

		Object object = invocation.proceed(); 
        // invocation을 이용해 실행하고자 하는 target 클래스의 메서드를 호출하고 그 return 값을 object에 담아 리턴
		
		System.out.println("[메서드 호출 후 : LoggingAdvice]");
		System.out.println(invocation.getMethod() + "메서드 호출 후"); // 메서드 호출 후 수행 구문
		
		return object;
	}
}

// 실행 클래스 CalcTest

package com.pro.yoon;

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

public class CalcTest {
	public static void main(String[] args) {
		ApplicationContext context = new ClassPathXmlApplicationContext("AOPTest.xml"); 
        // AOPTest.xml을 찾아 읽고 빈을 생성함
		Calculator cal = (Calculator)context.getBean("proxyCal"); 
        // id가 proxyCal인 bean(=ProxyFactoryBean)을 찾음
		cal.add(100, 20); // Target 클래스의 메서드 실행 시 logAdvice를 실행함
		System.out.println();
		
		cal.subtract(100, 20); 
		System.out.println();
		
		cal.multiply(100, 20); 
		System.out.println();
		
		cal.devide(100, 20); 
		System.out.println();
		
	}	
}


// 결과

[메서드 호출 전 : LoggingAdvice]
public void com.pro.yoon.Calculator.add(int,int)메서드 호출 전
결과 ::120
[메서드 호출 후 : LoggingAdvice]
public void com.pro.yoon.Calculator.add(int,int)메서드 호출 후

[메서드 호출 전 : LoggingAdvice]
public void com.pro.yoon.Calculator.subtract(int,int)메서드 호출 전
결과 ::80
[메서드 호출 후 : LoggingAdvice]
public void com.pro.yoon.Calculator.subtract(int,int)메서드 호출 후

[메서드 호출 전 : LoggingAdvice]
public void com.pro.yoon.Calculator.multiply(int,int)메서드 호출 전
결과 ::2000
[메서드 호출 후 : LoggingAdvice]
public void com.pro.yoon.Calculator.multiply(int,int)메서드 호출 후

[메서드 호출 전 : LoggingAdvice]
public void com.pro.yoon.Calculator.devide(int,int)메서드 호출 전
결과 ::5
[메서드 호출 후 : LoggingAdvice]
public void com.pro.yoon.Calculator.devide(int,int)메서드 호출 후

> advice 클래스는  target클래스의 모든 메서드 호출 시 적용됨

> 실제 스프링에서는 특정 패키지/클래스/메서드 이름에만 AOP  기능을 적용할 수 있음 

 

 AOP 핵심 정리

초기 용어 / 개념 정리 

> 타겟, 프록시, 위빙, 어드바이스, 포인트컷, 애스펙트

적용 시점 및 포인트컷을 활용한 aspect 관리 /  aspectj 표현식 활용 방법

타겟 : aop가 걸릴 대상  / 프록시 : aop가 걸린 대상 (타겟과 프록시는 타입이 같음 / 형식이 완전 같은 객체)

** aop를 건다 > 추가 기능

//예시

class Target { 
	void hello() {
	sysout("1"); 
   } 
   
   void bye() {
	sysout("2"); 
   } 
}

// Target 타입의 객체 생성시 호출 가능한 메서드 : hello(), bye() 


class TargetTest {
	Target t1 = new Target();  
    Target t2 = 어떠한 작업을 통한 프록시 생성;  // 호출 가능한 메서드 : hello(), bye() 로 동일함   
}

// hello() bye() 라는 이 두개의 메서드가 joinpoint

조인 포인트 : aop가 적용 가능한 지점 (적용될 후보 메서드/ 언제나 메서드임)

포인트컷 : aop가 적용될 조건식 (메서드 or 클래스 or 패키지/ 적용될 후보의 조건식)

> 포인트컷이 조인 포인트보다 좀더 구체적이고 좁은 개념임 

ex) select * from tab1 이게 조인포인트라면 , 포인트컷은 select * from tab1 where bno = '123'과 같이 적용 범위가 좁아짐

쿼리에서 where를 조건절이라고 하듯 포인트컷도 조건식임

어드바이스 : 적용된 추가 메서드 / 적용 시점에 따라 before, after, afterReturning, around, afterThrowing

예시) 목적에 따라 advice 클래스를 before로 만들거나 타겟 클래스의 특정 메서드에 합치고자 하는 경우

// before로 만들 클래스 
class A { 
	void print() { 
    	sysout("A"); 
    }
}

Target 클래스에 1을 출력하는 hello()메서드와 2를 출력하는 bye()메서드가 있을 때, hello()메서드에만 before를 건다면 t1.hello() 호출 시 그냥 타겟 클래스이니 1만 출력할 것이고 t2.hello() 를 호출하면 t2인스턴스는 비포가 적용된 프록시니까 A 가 나오고 1을 출력

타겟은 변화가 없으니 원래 일을 그대로 수행하고 프록시는 추가된 어드바이스에 따라/ 포인트컷에 따라 추가 기능이 붙음

포인트컷 인터페이스의 핵심은 어떤 클래스 어떤 메서드에 적용할 것이냐 즉 '어디에' 정보만 있음 

클래스 필터는 클래스 동일 여부만 묻고 methodMatcher는 메서드 시그니처를 분석해서 참거짓 여부를 판단함

>>>> 결론, 포인트컷은 어떤 클래스 어떤 메서드인가 를 구분하는 boolean의 구성이라는 것

weaving : 어디에(포인트컷) 무엇을(어드바이스) 적용하는 과정으로 관념적 의미

aspect : 어디에(포인트컷) 무엇을(어드바이스) 적용하는 객체 / 자바의 어드바이저(좀더 범용적)와 같은 역할

proxy : 위빙이 적용된 것을 프록시라 부름 (advisor나 aspect를 이용해 새로 생성된 객체)

/* 정리 */

aspect = advice + pointcut

advisor = advice + pointcut   OR   advisor = advice + target

즉, AOP는 개발자가 어떤 메서드 작업을 수행하는데 조건에 따라 추가 기능 수행을 하고자 할 때 전후 처리로 자유롭게 등록해서 쓰는 기능