ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [spring] AOP 프로그래밍 분석하기();
    Spring 2020. 2. 25. 03:43
    반응형
    DI 이외의 또 다른 스프링 프레임워크의 큰 특징인 AOP에 대해 알아보자..!
    (최범균 님의 스프링 5 서적을 보며 참고하여 정리한 글인데, 최범균 님은 참 글을 이해하기 쉽게 잘 쓰시는 거 같다..)

     

    AOP

    AOP(Aspect Oriented Programming)


    여러 객체에 공통으로 적용할 수 있는 기능을 분리해서 재사용성을 높여주는 프로그래밍 기법이다. AOP는 핵심 기능공통 기능의 구현을 분리함으로써 핵심 기능을 구현한 코드의 수정 없이 공통 기능을 적용할 수 있게 만들어 준다.

     

    즉, 핵심 기능의 코드는 수정하지 않으면서 공통 기능의 구현을 추가하는 것이 AOP의 목적이다.

     

    아래 예제를 보자..!

    public class CalcMain {
    	public static void main(String args[]) {
    		
    		Calc calc = new Calc();
    		System.out.println("합계는 : " + calc.fromZeroToN(10000));
    				
    	}
    }
    
    public class Calc {
    
    	public int fromZeroToN(int n) {
    		
    		Long startTime = System.nanoTime(); // 시작시간
    		
    		int sum = 0;
    		int index = 0;
    		
    		while(index < n+1) {
    			sum += index;
    			index++;
    		}
    		
    		Long endTime = System.nanoTime(); // 종료시간
    		System.out.println("수행시간은 : " + (endTime-startTime)); // 수행시간 구하기
    		
    		return sum;
    	}
    }
    

    fromZeroToN 메서드는 0부터 파라미터의 n 까지의 합을 리턴해주는 간단한 메서드다.

    또한 메서드의 startTime과 endTime의 차이로 메서드 수행 시간을 체크하고 있다.

    핵심 기능은 0부터 n까지의 합이고, 공통 기능은 수행 시간을 구하는 부분이 된다.

     

    이런 식으로 매번 수행 시간을 체크하는 부분을 넣는다면..? 유지보수의 불편함은 끔찍할 것 같다. 중복 코드가 넘쳐나고 위 예제는 nano단위로 구했지만 ms단위로 구하는 로직으로 바뀔 수도 있는데.. 그걸 전부 다 일일이 바꿔주어야 하니깐 시간이 배로 들것이다.(실제로 전 직장에서 그랬음..ㅋㅋ)

    으아아아악

     

    이때 등장하는 것이 바로 프록시 객체이다.

     

    프록시 객체(Proxy)


    핵심 기능의 실행은 다른 객체에 위임하고 부가적인 기능(공통 기능)을 제공하는 객체를 프록시(Proxy)라고 부른다.

    (실제 핵심 기능을 실행하는 객체는 '대상 객체'라는 표현을 쓴다.)

    public class CalcMain {
    	public static void main(String args[]) {
    		
    		Calc calc = new Calc();
    		ProxyCalc proxyCalc = new ProxyCalc(calc);
    		System.out.println("합계는 : " + proxyCalc.fromZeroToN(10000));
    				
    	}
    }
    
    public class ProxyCalc {
    
    	private Calc calc;
    	
    	public ProxyCalc(Calc calc) { // 생성자를 통해 Calc 객체 넘겨받기
    		this.calc = calc;
    	}
    	
    	public int fromZeroToN(int n) {
    		
    		Long startTime = System.nanoTime(); // 시작시간
    		
    		int result = calc.fromZeroToN(n); // 핵심기능을 위임
    		
    		Long endTime = System.nanoTime(); // 종료시간
    		System.out.println("수행시간은 : " + (endTime-startTime)); // 수행시간 구하기
    		
    		return result;
    	}
    }
    public class Calc {
    
    	public int fromZeroToN(int n) {
    		
    		int sum = 0;
    		int index = 0;
    		
    		while(index < n+1) {
    			sum += index;
    			index++;
    		}
    
    		return sum;
    	}
    }
    

    중간에 ProxyCalc 클래스를 생성하고 생성자를 통해 Calc 객체를 넘겨준다. 넘겨받은 Calc객체를 통해 핵심 기능을 호출하기만 하고 ProxyCalc 클래스에서는 공통 기능인 시간 구하기에만 집중할 수 있다. 핵심 기능과 공통 기능을 완전히 나누어놓았기 때문에 유지보수 하기 수월해졌다.

     

    프록시 객체를 사용하여 AOP를 구현한다면 생기는 장점으로는...

    1. 기존 코드(핵심 기능이 포함된 클래스)를 변경하지 않고 공통 기능을 실행시킬 수 있다.
    2. 공통 기능을 핵심 기능이 포함된 클래스에 하나씩 넣어줄 필요가 없이 프록시 객체를 이용해서 작업할 수 있어서 코드 중복량이 줄어든다.

    그림으로 보는 Proxy 객체의 수행 사이클

     

     

    사실 우리는 ProxyCalc 클래스(프록시 객체)를 직접 만들 필요가 없다. 스프링 AOP는 프록시 객체를 제공해주므로 나는 공통 기능을 구현한 클래스만 알맞게 구현하면 된다.

     

    스프링에 제공하는 AOP 방식은 '런타임에 프록시 객체를 생성해서 공통 기능을 삽입하는 방식'이다.

    (이외에도 컴파일 시점에 코드에 공통기능을 삽입하는 방법, 클래스 로딩 시점에 바이트 코드에 공통 기능을 삽입하는 방법이 있지만 AspectJ와 같은 AOP 전용 도구를 사용해서 적용해야 하고 프록시 객체를 이용하는 방식이 가장 널리 사용되고 있다고 한다.)

     

     

    AOP 주요 용어

    용어 의미
    Advice

    공통 기능을 핵심 로직에 언제 적용할지를 정의함.

    ex) 메서드를 호출하기 전, 메서드가 끝난 후...

    Joinpoint

    Advice를 적용 가능한 지점을 의미한다. 메서드 호출, 필드 값 변경 등이 Joinpoint에 해당한다.

    스프링은 Proxy를 이용해서 AOP를 구현하기 때문에 메서드 호출에 대한 Joinpoint만 지원한다.

    Pointcut

    Joinpoint의 부분 집합으로서 실제 Advice가 적용되는 Joinpoint를 나타낸다.

    스프링에서는 정규 표현식이나 AspectJ의 문법을 이용하여 Pointcut을 정의할 수 있다.

    Weaving

    Advice를 핵심 로직 코드에 적용하는 것을 weaving이라고 한다.

    Aspect

    공통 기능을 의미한다. 여러 객체에 공통으로 적용되는 기능을 Aspect라고 한다.

    ex) 트랜잭션 시작, 트랜잭션 종료, 수행 시간 구하기

    스프링은 Proxy를 이용해서 메서드를 호출 시점에 Aspect(공통 기능)를 적용하기 때문에 구현 가능한 Advice 종류는

    종류 설명
    Before Advice 대상 객체의 메서드 호출 전에 공통 기능을 실행한다.
    After Returning Advice 대상 객체의 메서드가 익셉션 없이 실행된 이후에 공통 기능을 실행한다.
    After Throwing Advice 대상 객체의 메서드를 실행하는 도중 익셉션이 발생한 경우에 공통 기능을 실행한다.
    After Advice 익셉션 발생 여부에 상관없이 대상 객체의 메서드 실행 후 공통 기능을 실행한다.
    Around Advice 대상 객체의 메서드를 실행 전, 후 또는 익셉션 발생 시점에 공통 기능을 실행한다.

    물론 Around Advice가 가장 널리 사용되고 있다.

     

    스프링 AOP 구현


    스프링 AOP를 구현하기 위해선 aspectjweaver를 의존성 추가해야 한다.

    이 모듈은 스프링이 AOP를 구현할 때 사용한다. 스프링 프레임워크 AOP 기능은 spring-aop 모듈이 제공하는데, Spring-context 모듈을 의존 대상에 추가하면 aop 모듈도 함께 의존 대상에 포함된다. aspectjweaver 모듈은 AOP를 설정하는데 필요한 애노테이션을 제공하므로 추가해 주어야 한다.

    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.9.5</version>
    </dependency>

     

    구현 절차

    1. Aspect(공통 기능)로 사용할 클래스에 @Aspect 애노테이션을 붙인다. (어떤 것을 적용시킬 건지)
    2. @Pointcut 애노테이션으로 공통 기능을 적용할 Pointcut을 정의한다. (어디에 적용할 건지)
    3. 공통 기능을 구현한 메서드에 @Around 애노테이션을 적용한다. (어떤 식으로 적용할 건지)
    @Aspect
    public class AspectCalc {
    
    	@Pointcut("execution(public int aop.*.*(..))")
    	private void publicTarget() {}
    	
    	@Around("publicTarget()")
    	public Object measure(ProceedingJoinPoint joinPoint) throws Throwable {
    		
    		Long startTime = System.nanoTime(); // 공통 기능
    		
    		Object result = joinPoint.proceed(); // 핵심 기능 
    		
    		Long endTime = System.nanoTime(); // 공통 기능
    		System.out.println("수행시간은 : " + (endTime-startTime)); // 공통 기능
    		
    		return result;
    	}
    }
    • @Aspect : 공통 기능을 나타내는 애노테이션
    • @Pointcut : 공통 기능을 적용할 대상 설정 애노테이션. execution 명시자 표현식은 Advice를 적용할 메서드를 지정할 때 사용한다. (execution 명시자로 검색해보면 많은 자료가 나온당!)
    • @Around : Around Advice 설정, 애노테이션 값을 Pointcut 메서드의 이름과 맞춰준다. @Pointcut에 정의된 설정에 적합한 빈 객체의 메서드에 measure() 메서드를 적용한다.

     

    measure() 메서드의 ProceedingJoinPoint 타입 파라미터는 프록시 대상 객체(핵심 기능이 포함된 객체)의 메서드를 호출할 때 사용한다. (proceed() 메서드를 사용해서 대상 객체 메서드를 호출한 것처럼..)

     

    ProceedingJoinPoint 타입 파라미터를 이용한 proceed() 메서드 이전, 이후로 공통 기능을 추가해주면 된다.(예시에서는 startTime, endTime, 수행 시간 구하기)

     

    @Configuration
    @EnableAspectJAutoProxy
    public class AppCtx {
    
    	@Bean
    	public AspectCalc aspectCalc() {
    		return new AspectCalc(); 
    	}
    	
    	@Bean
    	public Calc calc() {
    		return new Calc();
    	}
    }
    public class CalcMain {
    	public static void main(String args[]) {
    		
            AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppCtx.class);
            Calc calc = ctx.getBean("calc", Calc.class);
            System.out.println("합계는 : " + calc.fromZeroToN(10000));
            
    	}
    }

    공통기능 클래스, 핵심기능 클래스를 bean 객체로 설정 클래스를 통해 등록해주고 실행시키면 스프링 AOP가 적용된다.

     

    반응형

    댓글

Designed by Tistory.