관심분리 (로그) << 스프링에서는 제어의 역전이 가능

■ PointcutCommon.java

package com.example.biz.common;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

@Aspect 
public class PointcutCommon { //그냥 설정이어서 new 해주지 않아도 된다
	//결합도 낮추고 응집도를 높임
	//>>코드 반복 줄이기
	//>>관련 있는 코드들끼리 묶어놓기
	//좋은 설계
	 
	@Pointcut("execution(* com.example.biz..*Impl.get*(..))") //get으로 시작하는 메서드만 적용(getall,getone)
	// 반환값 확인 (After-Returning), 예외 처리 (After-Throwing), 실행시간 측정 (Around) 등에 주로 사용
	public void aPointcut() {}
	
	@Pointcut("execution(* com.example.biz..*Impl.*(..))")//Impl로 끝나는 클래스의 모든 메서드에 적용
	public void bPointcut() {}
	//실행 전,후 로깅 (Before, After) 등에 주로 사용
}

■ applicationContext.xml 은 이제 엄청 가벼워짐

aop:aspectj-autoproxy/

■ LogAdvice.java

package com.example.biz.common;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Service;
import org.springframework.util.StopWatch;

import com.example.biz.board.BoardVO;
import com.example.biz.member.MemberVO;

@Service // 스프링 빈으로 인식
@Aspect  // AOP 기능을 담고 있는 클래스임을 명시
public class LogAdvice {

    // ===== BEFORE =====
    @Before("PointcutCommon.bPointcut()") // 모든 메서드에 대해 실행 전 수행
    public void before(JoinPoint jp) {
        System.out.println("BEFORE 공통 로그");

        // 메서드 이름 출력
        String methodName = jp.getSignature().getName();
        System.out.println("\\t메서드 시그니쳐 출력 [" + methodName + "]");
    }

    @After("PointcutCommon.bPointcut()") // 모든 메서드에 대해 실행 후 수행
    public void after(JoinPoint jp) {
        System.out.println("AFTER 공통 로그");

        Object[] args = jp.getArgs(); // 인자 목록 추출
        System.out.println("\\t인자 개수 >> " + args.length + "개");

        // 첫 번째 인자가 MemberVO일 경우 회원 이름 출력
        if (args.length > 0 && args[0] instanceof MemberVO) {
            MemberVO vo = (MemberVO) args[0];
            String name = vo.getName();
            System.out.println("\\t가입한 회원의 이름 [" + name + "]");
        }
    }

    @AfterReturning(pointcut = "PointcutCommon.aPointcut()", returning = "returnObj")
    public void returning(JoinPoint jp, Object returnObj) throws Exception {
        System.out.println("RETURNING 공통 로그");

        System.out.print("\\t");
        if (returnObj instanceof BoardVO) {
            System.out.println("게시판 관련 서비스");
        } else if (returnObj instanceof MemberVO) {
            System.out.println("회원 관련 서비스");

            MemberVO vo = (MemberVO) returnObj;
            if ("ADMIN".equals(vo.getMrole())) {
                System.out.println("\\t관리자가 로그인했습니다.");
            }
        } else {
            System.out.println("확인되지않은 서비스");
        }
    }

    @AfterThrowing(pointcut = "PointcutCommon.aPointcut()", throwing = "exceptObj")
    public void throwing(JoinPoint jp, Exception exceptObj) {
        System.out.println("THROWING 공통 로그");

        System.out.println("\\t에러 리포트 [" + exceptObj.getMessage() + "]");
        System.out.print("\\t[");
        if (exceptObj instanceof IllegalArgumentException) {
            System.out.print("일부러 발생시킨 예외");
        } else {
            System.out.print("확인되지않은 예외");
        }
        System.out.println("]");
    }

    @Around("PointcutCommon.aPointcut()") // get* 메서드를 전후로 감싸서 실행 시간 측정
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("AROUND 공통 로그 시작");

        StopWatch sw = new StopWatch(); // 실행 시간 측정용 객체
        sw.start();

        Object obj = pjp.proceed(); // 실제 비즈니스 로직 실행

        sw.stop();
        String methodName = pjp.getSignature().getName();

        System.out.println("\\t수행한 서비스 메서드명 [" + methodName + "]");
        System.out.println("\\t걸린시간 [" + sw.getTotalTimeMillis() + "]ms");

        System.out.println("AROUND 공통 로그 끝");

        // getMember 메서드에서 일반회원이면 예외 발생 (테스트 목적)
        if ("getMember".equals(methodName) && obj instanceof MemberVO) {
            MemberVO vo = (MemberVO) obj;
            if ("USER".equals(vo.getMrole())) {
                throw new Exception("일반회원 로그인");
            }
        }
        return obj;
    }
}

내가 한 코드를 @으로 바꿔보자 - 실습을 위한 코드

package com.example.biz.common;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;

import com.example.biz.member.MemberVO;

@Component  // 스프링 빈으로 등록
@Aspect     // AOP 적용 클래스임을 명시
public class Logadvice {

    // BEFORE: 메서드 실행 전 동작
    @Before("PointcutCommon.bPointcut()")
    public void before(JoinPoint jp) {
        System.out.println("BEFORE 공통 로그");

        String methodName = jp.getSignature().getName();
        System.out.println("메서드 시그니쳐 출력 >> " + methodName);

        Object[] args = jp.getArgs();
        System.out.println("인자 개수 >> " + args.length);

        for (Object arg : args) {
            System.out.println(arg);
        }
    }

    // AFTER: 메서드 실행 후 동작
    @After("PointcutCommon.bPointcut()")
    public void after(JoinPoint jp) {
        System.out.println("AFTER 공통 로그");

        String methodName = jp.getSignature().getName();
        System.out.println("메서드 시그니쳐 출력 >> " + methodName);

        Object[] args = jp.getArgs();
        System.out.println("인자 개수 >> " + args.length);

        for (Object arg : args) {
            System.out.println(arg);
            if (arg instanceof MemberVO) {
                String name = ((MemberVO) arg).getName();
                System.out.println("가입자 이름 >> " + name);
            }
        }
    }

    // AROUND: 메서드 실행 전후 감싸기 (예외 발생 로직 포함 가능)
    @Around("PointcutCommon.aPointcut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("AROUND 공통 로그 시작");

        String methodName = pjp.getSignature().getName();
        System.out.println("실행 메서드명 >> " + methodName);

        Object[] args = pjp.getArgs();
        System.out.println("인자 개수 >> " + args.length);
        for (Object arg : args) {
            System.out.println(arg);
        }

        StopWatch sw = new StopWatch();
        sw.start();

        Object obj = pjp.proceed(); // 실제 메서드 실행

        sw.stop();
        System.out.println("AROUND 공통 로그 끝 >> " + sw.getTotalTimeMillis() + "ms");

        return obj;
    }

    // AFTER-RETURNING: 메서드 정상 반환 후
    @AfterReturning(pointcut = "PointcutCommon.aPointcut()", returning = "returnObj")
    public void returning(JoinPoint jp, Object returnObj) throws Exception {
        System.out.println("[RETURNING] 공통 로그 실행");

        String methodName = jp.getSignature().getName();
        System.out.println("실행된 메서드명 >> " + methodName);

        if (returnObj instanceof MemberVO) {
            MemberVO vo = (MemberVO) returnObj;

            if ("ADMIN".equals(vo.getMrole())) {
                System.out.println("관리자가 로그인했습니다.");
            } else {
                System.out.println("일반회원입니다. 예외를 발생시킵니다.");
                throw new IllegalArgumentException("일반회원 로그인 시도 예외 발생");
            }
        } else {
            System.out.println("Member가 아닙니다.");
        }
    }

    // AFTER-THROWING: 예외 발생 시
    @AfterThrowing(pointcut = "PointcutCommon.aPointcut()", throwing = "exceptObj")
    public void throwing(JoinPoint jp, Throwable exceptObj) {
        System.out.println("[THROWING] 예외 발생 로그 실행");

        String methodName = jp.getSignature().getName();
        System.out.println("예외가 발생한 메서드명 >> " + methodName);

        if (exceptObj instanceof IllegalArgumentException) {
            System.out.println("일부러 발생시킨 IllegalArgumentException 예외");
        } else if (exceptObj instanceof RuntimeException) {
            System.out.println("일반회원 로그인 예외 // 메시지: " + exceptObj.getMessage());
        } else {
            System.out.println("확인되지 않은 예외 발생");
        }
    }
}

logger사용해라

xml 에서 @ 으로 AOP 변경시, advice 끼리의 catch는 지원안됨!(logger로 지원하기때문)

  1. advice에서 하나하나 try - catch 처리하거나
  2. service에서 thorw exception해야함!!! << 권장 (@으로 변경했을경우에 권장)