파게로그

AOP(Aspect Oriented Programming) 본문

콤퓨타 왕기초/Spring

AOP(Aspect Oriented Programming)

파게 2021. 4. 21. 20:26

AOP(Aspect Oriented Programming)

비즈니스 로직 외에 개발자나 관리자가 프로그램 구현이나 테스트 등을 위해 사용하는, 사용자는 인식할 수 없는 코드가 존재한다. 곧, OOP가 사용자 관점에서 이루어진 프로그래밍이라면, AOP는 개발자 또는 운영자 관점 또한 고려하는 것이다.

어플리케이션의 특정 모듈에게 기대하는 행동, 즉 우리가 구현하기를 원하는 기능이 concern이라면 비즈니스 로직과 같은 것은 Primary Concern(Core Concern, 핵심 기능)이며, 여기에 해당하면 객체로, 그리고 실질적인 업무는 메서드로 만들어진다. 하지만 로그 처리, 보안 처리, 트랜잭션 처리 등 메서드의 앞이나 뒤에 포함되는 부분은 Cross-cutting Concern(공통 기능)으로서 어플리케이션을 통틀어 거의 모든 모듈에서 요구된다.

이름이 Cross-cutting Concern인 것은 다음과 같은 그림에서 프로그램의 실행 방향을 세로로 본다면, 이와 반대로 가로로 잘라낼 수 있기 때문이다. 이러한 Cross-cutting Concern은 떼어내거나 붙일 수 있어야 하는데 이를 직접 구현하고자 한다면 소스코드를 가지고 있어야만 하기에 불가능한 경우가 존재하며, 그렇지 않더라도 Primary Concern의 수정이 수반된다. 그러나 AOP를 통해서 마치 비즈니스 로직 속에 다른 로직이 꽂혀있는 것처럼 프로그램을 설계할 수 있다.

 

 

 

cross-cutting을 호출할 수도, core concern을 바로 호출할 수도 있다.

 

 

 

AOP를 Spring 없이 구현한다면?

사용자는 Proxy를 호출하게 된다. 비즈니스 로직과 부가적인 코드가 함께 있을 때는 다음과 같이 작성했을 것이다.

 

Program.java

package spring.aop;

import spring.aop.entity.Exam;
import spring.aop.entity.MidTermExam;

public class Program {
	public static void main(String[] args) {
		Exam exam = new MidTermExam(50, 40, 30, 20);
		System.out.printf("SUM: %d, AVG: %.2f\n", exam.sum(), exam.avg());
	}
}

 

MidTermExam.java

package spring.aop.entity;

public class MidTermExam implements Exam {
	int kor;
	int eng;
	int math;
	int com;
	
	public MidTermExam() {
		
	}
	
	public MidTermExam(int kor, int eng, int math, int com) {
		this.kor = kor;
		this.eng = eng;
		this.math = math;
		this.com = com;
	}

	@Override
	public int sum() {
		long start = System.currentTimeMillis();
		
		/* CORE CONCERNS BEGIN */
		int res = kor + eng + math + com;
		try {
			Thread.sleep(200);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		/* CORE CONCERNS END */
		
		long end = System.currentTimeMillis();
		String msg = (end - start) + "ms";
		System.out.println(msg);
		
		return res;
	}

	@Override
	public double avg() {
		double res = sum() / 4.0;
		return res;
	}
}

 

 

이것을 Proxy를 이용하여 분리해보자. MidTermExam 클래스에는 비즈니스 로직만 남겨두고, Program 클래스를 아래와 같이 수정한다.

 

Program.java

package spring.aop;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import spring.aop.entity.Exam;
import spring.aop.entity.MidTermExam;

public class Program {
	public static void main(String[] args) {
		Exam exam = new MidTermExam(50, 40, 30, 20);
		
		Exam proxy = (Exam)Proxy.newProxyInstance(
				MidTermExam.class.getClassLoader() // loader: 실질적인 개체를 로드함
				, new Class[] {Exam.class} // interfaces: 개체가 구현하는 인터페이스 정보
				, new InvocationHandler() {
					@Override
					public Object invoke(Object proxy, Method method, Object[] args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
						long start = System.currentTimeMillis();
						
						Object result = method.invoke(exam, args);
						try {
							Thread.sleep(300);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
                        
						long end = System.currentTimeMillis();
						String msg = (end - start) + "ms";
						System.out.println(msg);
						
						return result;
					}
				});
		
		System.out.printf("SUM: %d, AVG: %.2f\n", proxy.sum(), proxy.avg());
	}
}
Comments