본문 바로가기
Dev/Spring

[Spring] AOP(Aspected-Oriented Programming)

by ssyoni 2022. 2. 5.
반응형

AOP(Aspected-Oriented Programming)

  • 스프링과 별개로 방법론이다.
  • OOP는 사용자 관점에서의 주 업무 로직을 위주로 구현하는 것. 반면 AOP는 주 업무 로직을 구현함에 있어서 개발자나 운영자 및 관리자가 필요에 따라서 코드를 끼워 넣게 되는 부가적인 기능(ex. 로그 처리, 트렌젝션 등...)을 모듈화 하는 것.
  • 즉, 사용자 관점, 개발자 관점, 운영자 관점, 등등..여러 관점들을 고려해서 로직을 분리하고 모듈화 하는 방법론이기 때문에 Aspected 관점 지향적 프로그램이 이라고 한다.

Concern - Primary(Core) Concern & Cross-cutting concern

  • primary(Core) Concern : 핵심 로직
  • Cross-cutting Concern : 핵심로직을 위아래로 감싸고 있는 부가적인 공통 로직

AOP 구현 방식

  • 프록시로 위임과 부가 작업을 통해 AOP를 구현한다.

Proxy

  1. 프록시란? 기존 코드에 영향을 주지 않고 타깃의 기능을 확장하고 접근방법을 제어하는 방법
  2. 프록시의 역할
  • 타깃으로 요청을 위임
  • 부가기능을 수행
  1. 프록시를 만들면 번거로운 이유
  • 타깃의 인터페이스를 구현하고 위임하는 작업이 번거롭다. 부가기능이 필요 없는 메소드까지도 구현해서 일일이 위임해줘야 하는 작업이 필요하다. → 작업량이 많아질수록 부담스러워짐. 또한 타깃의 메소드가 변경되면 함께 수정해줘야 하는 번거로움이 있음.
  • 부가기능 코드가 중복될 경우가 많다. EX) 커밋, 롤백 하는 트랜젝션일 경우 DB를 사용하는 모든 기능에 트랜잭션이 들어간다.

⇒ 다이나믹 프록시를 활용한 단점 보완

Dynamic Proxy

  • 프록시팩토리에 의해 런타임 시 다이내믹하게 만들어지는 오브젝트.
  • 프록시팩토리에게 인터페이스 정보를 제공해주면 해당 인터페이스를 구현한 클래스의 오브젝트를 자동으로 만들어준다
  • 인터페이스를 기반으로 Proxy를 생성해주는 방식
  • 부가기능은 InvocationHandler를 구현한 오프젝트에 담는다.
  • InvocationHandler는 다음과 같은 메서드 한 개만 가진 인터페이스이다.
public Object invoke(Object proxy, Method method, Object[] args)
  • 순수 자바로 proxy 만들기
    // 타깃 클래스
    package noums.aop.entity;
    
    public class ExamImpl implements Exam {
    	
    	private int kor;
    	private int eng;
    	private int math;
    	
        public ExamImpl() {
    		// TODO Auto-generated constructor stub
    	}
        
        public ExamImpl(int kor, int eng, int math) {
    		this.kor = kor;
    		this.eng = eng;
    		this.math = math;
    	}
    	
    	public int getKor() {
    		return kor;
    	}
    
    	public void setKor(int kor) {
    		this.kor = kor;
    	}
    
    	public int getEng() {
    		return eng;
    	}
    
    	public void setEng(int eng) {
    		this.eng = eng;
    	}
    
    	public int getMath() {
    		return math;
    	}
    
    	public void setMath(int math) {
    		this.math = math;
    	}
    
    	@Override
    	public int total() {
    		// 부가로직 
    //		long start = System.currentTimeMillis();
    		// 핵심로직 
    		int result = kor+eng+math;
    		
    //		try {
    //			Thread.sleep(200);
    //		} catch (InterruptedException e) {
    //			// TODO Auto-generated catch block
    //			e.printStackTrace();
    //		}
    //		
    //		// 부가로직 
    //		long end = System.currentTimeMillis();
    //				
    //		String message = (end-start) + "ms 시간 소요 ";
    //		System.out.println(message);
    		return result;
    	}
    	@Override
    	public float avg() {
    		
    		// 핵심업무 
    		float result = total() / 3.0f;
    		
    		return result;
    	}
    	
    	
    
    }
    
    package noums.aop;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    import noums.aop.entity.Exam;
    import noums.aop.entity.ExamImpl;
    
    public class Project {
    
    	public static void main(String[] args) {
    		
    		Exam exam = new ExamImpl(1,1,1); // 타깃은 인터페이스를 통해 접근하는 습관을 들이자
    		
    		//자바에서 제공 proxy  
    		// loader : 실제 로드할 객체 , 
    		// interfaces : 핵심로직이 구현한 인터페이스. 핵심로직이 여러개의 인터페이스를 구현하고있을 수 있기 때문에 배열로 선언해준다.
    		// h : 부가기능을 꽂을 수 있는 부분 
    		Exam proxyExam = (Exam) Proxy.newProxyInstance(Exam.class.getClassLoader(), 
    				new Class[] {Exam.class}, 
    				new InvocationHandler() {
    					
    					@Override
    					public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    						// 부가기능 
    						long start = System.currentTimeMillis();
    						
    						// 핵심기능 호출 
    						Object result = method.invoke(exam, args); // args : exam 에서 구현하고있는 메서드들을 파라미터로 가져옴 
    						
    						// 부가기능 
    						long end = System.currentTimeMillis();
    								
    						String message = (end-start) + "ms 시간 소요 ";
    						System.out.println(message);
    						return result;
    					}
    				}
    				);
    		
    		System.out.println("total is "+ proxyExam.avg());
    	}
    
    }
    
     
    1. Exam인터페이스 제공
    2. 프록시팩토리에게 나이내픽 프록시 만들어달라고 요청
    3. Exam인터페이스의 모든 메소드를 구현한 오브젝트 생성
    4. InvokationHandler 인터페이스를 구현한 오브젝트를 제공 → 다이내믹 프록시가 받은 모든 요청을 InvokationHandler 의 invoke() 메소드로 보내줌.
    5. Exam인터페이스의 메소드가 아무리 많아도 invoke 메소드 하나로 처리할 수 있다.
  •  

Sprnig AOP

스프링은 프록시 기반의 AOP 기능을 제공한다. 런타임 시점에 타겟 클래스에 대한 부가기능을 실행해줄 프록시 빈을 생성해준다. (자동 프록시 생성기)

용어

  • Aspect : 공통 코드를 모듈화 한 것
  • Target : Aspect 가 적용되는 곳
  • Advice : 순수하게 부가기능만 실제로 구현된 부분. 부가기능 구현체
    • Advice 종류
      • Before
      • After returnning
      • After throwing : 예외가 발생된 후 동작
      • Around : 메소드의 실행 자체를 제어할 수 있다.
  • Joint point : Advice 가 Target에 적용되는 시점
  • Point cut : Joint point의 상세 스펙을 정의한 것
    • pointcut 종류
execution(@execution) 메소드를 기준으로 Pointcut을 설정
within(@within) 특정한 타입(클래스)을 기준으로 Pointcut을 설정
this 주어진 인터페이스를 구현한 객체를 대상으로 Pointcut을 설정
args(@args) 특정한 파라미터를 가지는 대상들만을 PointCut으로 설정
@annotation 특정한 어노테이션이 적용된 대상들만을 Pointcut으로 설정
@Aspect
@Component
public class MenuCheckAspect {

    @Around("execution(public * noums.study.pizzahouse.controller.OrderController.*(..))")
    public Object menuCheck(ProceedingJoinPoint joinPoint) throws Throwable {

        Object proceed = null;
        System.out.println(">>>> MenuCheckAspect start ...");

        try {
                proceed = joinPoint.proceed(); // 다름 로직 수행 
            }
        } catch (Exception e) {
        }

        System.out.println(">>>> MenuCheckAspect end ...");
        return proceed;
    }
}







출처 - newlecture 유튜브 강의
http://ldg.pe.kr/framework_reference/spring/ver2.x/html/aop.html

반응형

댓글