study/Spring

13_spring_(AOP)

스파이크12 2020. 3. 17. 08:51

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

공통되는 부분을 따로 빼내어 필요한 시점에 해당코드를 추가해주는 기술

Advice : 공통되는 부분을 따로 빼내어 작성하는 메소드

JoinPoint : Advice를 적용될 수 있는 모든 관점(시점)

PointCut : JoinPoint 중 실제 Advice를 적용할 부분

Weaving : 그 시점에 공통 코드를 끼워 넣는 작업(런타임 시 위빙 (Spring)

Aspect : Advice + PointCut (동적) , 여러 객체에 공통으로 적용되는 기능을 분리하여 작성한 클래스

Proxy : 대상 객체를 직접 접근하지 못하게 '대리인'으로써 요청을 대신 받는 기술

@Before("pointcut") / After("pointcut") / Around("pointcut")

* 모든 어드바이스는 JoinPoint 타입의 파라미터를 첫번재 매개변수로 선언해야한다.

단, Around 어드바이스는 joinPoint의 하위 클래스인 ProceedingJoinPoint 타입의 파라미러를 필수로 선언해야한다

LogAdvice

@Component // Spring 컨테이너가 @Aspect가 적용된 객체를
			// 제어(IOC)해야 하므로 Bean 으로 등록되어 있어야 함
@Aspect // 해당 클래스가 AOP에 사용될 것이라는 걸 명시
		// Aspect = advice + pointcut
public class LogAdvice {

	// Pointcut 지정 : advice가 적용될 부분 지정
    // execution : 특정 객체(메소드)가 실행(호출) 되는 시점
    // execution([접근제한자] [리턴타입] [클래스명] [메소드명] [파라미터])
    
    // * : 모두
    // .. : 이하 모두
    // *Impl : 클래시명 마지막이 Impl인 클래스
    
    // Aspect는 필드선언이 안됨 메소드로 작성
    // 별도의 Pointcut을 지정하여 필요할때 호출하여 사용
    // @Pointcut("execute(* com.kh.spring..*Impl.*(..))")
    // public void implPointcut() {}
    
    // before Advice
    //@Before("execute(* com.kh.spring.*Impl.*(..))")
    //@Before("implPointcut()")
    @Before("CommonPointcut.implPointcut()")
    public void startLog(){
    	System.out.println("[log] : 비즈니스 로직 시작");
    }
    
    // @After : 예외 발생 여부와 관계 없이 무조건 실행됨
    // after advice
    // @After("execution("* com.kh.spring.*Impl.*(..))")
    @After("CommonPointcut.implPointcut()")
    public void endLog() {
		System.out.println("[log] : 비즈니스 로직 종료");
    }
}

공용으로 사용될 Pointcut을 모아둔 클래스

// 공용으로 사용될 Pointcut을 모아둔 클래스
public class CommonPointcut {
	
    @Pointcut("execution(* com.kh.spring.*Impl.*(..))")
    public void implPointcut(){}
}

BeforeAdvice 

@Component
@Aspect
public class BeforeAdvice {
	
    @Before("CommonPointcut.implPointcut()")
    public void beforeLog(JoinPoint jp) { 
    	
        // jp.getTarget() : 대상 객체 반환
        // jp.getSigniture() : 대상 객체 메소드 정보 반환
        String className = jp.getTarget().getClass().getSimpleName();
        String methodName = jp.getSignature().getName();
        
        System.out.println("-------------------------------------------");
        System.out.println("[전처리] : " + className + " - "
        								+ methodName + "() - start");
    }
}

AfterAdvice 

@Component // bean 등록
@Aspect // advice + pointcut
public class AfterAdvice {
	
	@After("CommonPointcut.implPointcut()")
    public void afterLog(JoinPoint jp) {
    	// jp.getTarget() : 대상 객체 반환
        // jp.getSigniture() : 대상 메소드 반환
        String className = jp.getTarget().getClass().getSimpleName();
        String methodName = jp.getSignature().getName();
        
        System.out.println("[후처리] : " + className + " - "
									+ methodName +"() - end");
        System.out.println("------------------------------------------------------------");
    }

AroundAdvice

@Component // bean 등록
@Aspect // advice+pointcut
public class AroundAdvice {

	// @Around = @Before + @After
    // ProceedingJoinPoint.proceed() : 전, 후 처리 기준점 역할
    // ProceedingJoinPoint는 JoinPoint를 상속받은 클래스
    @Around("CommonPointcut.implePointcut()")
    public Object aroundLogs(ProceedingJoinPoint jp) throws Throwable {
    
    	// 메소드 수행시간 체크
        String methodName = jp.getSignature().getName();
        
        // StopWatch : 스프링에서 제공하는 스톱워치 클래스
        StopWatch sw = new StopWatch();
        sw.start(); // 시간 측정 시작
        // 여기까지가 Before
        
        Object obj = jp.proceed();
        
        // 여기이후가 After
        sw.stop(); // 시간측정종료
        
        System.out.println(methodName + "() 수행시간 : "
				+ sw.getTotalTimeMillis() + "(ms)");
		
		return obj;
    }
}
        

AfterReturningAdvice (리턴된 데이터 중간에 가져오기)

@Component // bean 등록
@Aspect // advice + pointcut
@public class AfterReturningAdvice {

	// login*(*) -> 메소드명이 login으로 시작하는 메소드이면서
    //				매개변수(파라미터)가 한개인 메소드
    // 리턴된 데이터 강탈
    @AfterReturning(pointcut = "execution("* com.kh.spring..*Impl.login*(*))",
    				returning = "returnObj")
    // 위에 선언한 returnObj 가 파라미터로 들어감
    public void loginLog(JoinPoint jp, Object returnObj) {
    
    	// 접속자 IP 얻어오기
        HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder
        	.currentRequestAttributes()).getRequest();
            
        String ip = request.getRemoteAddr();
        
        String logMsg = "[IP : " + ip + "] : ";
        
        if(returnObj instanceof Member) {
        	Member member = (Member)returnObj;
            if(member.getMemberId().equals("admin")) {
				logMsg += "관리자 로그인";
			} else {
				logMsg += "ID : " + member.getMemberId() + "로그인";
			}
            
            System.out.println(logMsg);
            
        }
    }
}
            

AfterThrowingAdvice (중간에 에러잡기)

@Component
@Aspect
public class AfterThrowingAdvice {

	@AfterThrowing(pointcut = "CommonPointcut.implPointcut()",
    		throwing = "exceptionObj")
    public void exceptionLog(JoinPoint jp, Exception exceptionObj) {
    	
        // SyntaxErrorException
        // -> "SQL 구문 에러"
        
        String logMsg = "예외 발생 내용 : ";
        
        if(exceptionObj instanceof IllegalArgumentException) {
        	logMsg += "부적합한 값 입력";
        } else if(exceptionObj instanceof BadSqlGrammarException) {
			logMsg += "SQL 문법 오류";
		} else {
			logMsg += "기타 예외 발생";
		}
		
		System.out.println(logMsg);
	}
}