YJ의 새벽

Spring ( AOP 관점 지향 프로그래밍 ) 본문

Spring/Spring

Spring ( AOP 관점 지향 프로그래밍 )

YJDawn 2023. 5. 12. 18:06

 

 

 

 

  • Spring AOP ??

-- 관점 지향 프로그래밍 !!!  

-- 중복되는 공통 코드부분을 별도의 영역으로 분리 !!!

-- 소스코드의 중복을 줄이고, 필요할때마다 가져다 쓸수있게 객체화 하는 기술 !!! 

 

 

 

 

 

***  Aspect ??   :   Advice + Pointcut     ==  기능이 수행.

   -- 동작코드를 의미하는 Advice 와

      작성한 Advice가 실제로 적용된 메소드인 Pointcut 을 합친 개념.

   -- AOP 개념을 적용하면 핵심기능 코드 사이에 끼어있는 부가기능을 독립적인 요소로 구분해 낼 수 있고,
      이렇게 구분된 부가기능 Aspect는 런타임 시에 필요한 위치에 동적으로 참여하게 할 수 있다.

 

 

 

--  pom.xml  설정정보 확인 .

 

 

 

--- servlet-context.xml  에 proxy 추가.

** proxy-target-class :   프록시가 적용될 클래스가 인터페이스면 false   ,   일반클래스면  true 

   -- Spring AOP 는 기본적으로 인터페이스를 상속받은 프록시가 생성되어 코드를 수행함.

   -- 일반클래스일경우 cglib 라이브러리를 이용하여 일반 클래스를 상속받아 프록시를 생성한다.

 

 

 

 

 

 

 

 

 

 

 

*** Tomcat 서버 -> overview  -> configuration -> Arguments 에서

-Djava.net.preferIPv4Stack=true   추가 !!   ( ip 주소를 알아볼수있게 함. )

 

 

 

 

 

 

 

----------------------------------------------------------AOP 예제-----------------------------------------------------------------------------------

 

 

 

 

---- Pointcut 을 모아놓은 클래스를 따로 만든다.  CommonPointcut.class

package edu.kh.comm.common.aop;
import org.aspectj.lang.annotation.Pointcut;
public class CommonPointcut {

	// 회원 서비스용 Pointcut
	@Pointcut("execution(* edu.kh.comm.member..*Impl.*(..))")
	public void memberPointcut() {
	}

	// 모든 ServiceImpl 클래스용 Pointcut
	@Pointcut("execution(* edu.kh.comm..*Impl.*(..))")
	public void implPointcut() {
	}
}

 

 

 

 

 

---- BeforeAspect  .class      ( Pointcut 에 지정된 메서드가 수행되기 전 advice(메서드) 수행 )

  -- JoinPoint 인터페이스는 항상 첫번째 매개변수로 작성 ~!! 

  -- @Before( Pointcut 모아놓은클래스.메서드 )   로 작성.

@Slf4j
@Component
@Aspect
public class BeforeAspect {

	// JoinPoint 인터페이스 : 
	//		advice가 적용되는 Target Object (ServiceImpl)의
	//		정보, 전달되는 매개변수, 메서드, 반환값, 예외 등을 얻을 수 있는 메서드를 제공
	
	// (주의사항) JoinPoint 인터페이스는 항상 첫 번째 매개변수로 작성 되어야 한다!
	
	@Before("CommonPointcut.implPointcut()")
	public void serviceStart(JoinPoint jp) {
		
		String str = "----------------------------\n";
		
		// jp.getTarget() : aop 가 적용된 객체 ( 각종 ServiceImpl )
		String className = jp.getTarget().getClass().getSimpleName();   // 간단한 클래스명(패키지명제외)
		
		// jp.getSignature() : 수행되는 메서드 정보
		String methodName = jp.getSignature().getName();
		
		// jp.getArgs() : 메서드 호출시 전달된 매개변수
		String param = Arrays.toString( jp.getArgs() );
		
		str += "Start : " + className + " - " + methodName + "\n";
		// Start : MemberServiceImpl - login
		str += "Parameter : " + param + "\n";
		
		
		// ip 를 얻어오기 !!!!!!!!!!!!!
		try {
			// HttpServletRequest 얻어오기
			// 단, 스프링 스케줄러 동작시 예외 발생 ( 스케줄러는 요청 객체가 존재하지않음 )
			HttpServletRequest req = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
			
			Member loginMember = (Member) req.getSession().getAttribute("loginMember");
			
			// ip : xxx.xxx.xxx.xxx ( email : test01@naver.com )
			str += "ip : "+ getRemoteAddr(req);
			
			if ( loginMember != null ) { //로그인상태인경우
				str += " (email : "+ loginMember.getMemberEmail() + ")";
			}
		}catch( Exception e ) {
			str += "[스케줄러 동작]";
		}
		log.info(str);
	}
	
	
	public static String getRemoteAddr(HttpServletRequest request) {
        String ip = null;
        ip = request.getHeader("X-Forwarded-For");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 
            ip = request.getHeader("Proxy-Client-IP"); 
        } 
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 
            ip = request.getHeader("WL-Proxy-Client-IP"); 
        } 
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 
            ip = request.getHeader("HTTP_CLIENT_IP"); 
        } 
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 
            ip = request.getHeader("HTTP_X_FORWARDED_FOR"); 
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 
            ip = request.getHeader("X-Real-IP"); 
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 
            ip = request.getHeader("X-RealIP"); 
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 
            ip = request.getHeader("REMOTE_ADDR");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 
            ip = request.getRemoteAddr(); 
        }
		return ip;
	}
}

 

 

 

 

 

 

---- AfterAspect . class      ( Pointcut 에 지정된 메서드가 수행된 후 advice(메서드) 수행 )

 

@Slf4j
@Component
@Aspect
@Order(1)
public class AfterAspect {
	@After("CommonPointcut.implPointcut()")
	public void serviceEnd(JoinPoint jp) {

		String str = "----------------------------\n";

		// jp.getTarget() : aop 가 적용된 객체 ( 각종 ServiceImpl )
		String className = jp.getTarget().getClass().getSimpleName(); // 간단한 클래스명(패키지명제외)

		// jp.getSignature() : 수행되는 메서드 정보
		String methodName = jp.getSignature().getName();


		str += "End : " + className + " - " + methodName + "\n";
		// End : MemberServiceImpl - login
		str += "---------------------------------\n";
		
		log.info(str);
	}
}

 

 

 

 

 

 

 

---- AfterReturningAspect . class    ( 반환값을 가져오는 기능 )

 

@Slf4j
@Component
@Aspect
@Order(5)  // 큰 숫자 순으로 먼저 실행
public class AfterReturningAspect {

	// @AfterReturning  :  기존 @After + 반환값 얻어오기 기능
	// returning ="returnObj"   :  반환 값을 저장할 매개변수를 지정
	
	@AfterReturning(pointcut = "CommonPointcut.implPointcut()" , returning ="returnObj")
	public void serviceReturnValue(Object returnObj) {
		
		log.info("Return Value : "+returnObj);
	}
}

 

 

 

 

 

 

 

 

---- AroundAspect . class  ( 전처리 @Before   ,  후처리 @After  한번에 작성가능한 기능  ) 

 

@Slf4j
@Component
@Aspect
@Order(3)
public class AroundAspect {

	// @Around : 전처리(@Before)와 후처리(@After)를 한 번에 작성 가능한 advice
	
	@Around("CommonPointcut.implPointcut()")
	public Object runningTime( ProceedingJoinPoint jp ) throws Throwable {
		
		// ProceedingJoinPoint 인터페이스 : 전/후 처리 관련기능, 값을 얻어올 수 있는 메서드제공
		
		// proceed() 메소드 호출 전  : @Before advice 작성
		// proceed() 메소드 호출 후  : @After advice 작성
		// 메소드 마지막에 proceed()의 반환값을 리턴해야함
		
		long startMs = System.currentTimeMillis();
		
		Object obj = jp.proceed(); // 전/후 처리 나누는 기준
		
		long endMs = System.currentTimeMillis();
		
		log.info("Running Time : " + ( endMs - startMs ) + "ms" );
		
		return obj;
	}
}

 

 

 

 

 

 

 

 

 

 

 

 

---- AfterThrowingAspect . class   (   기존 @After + 던져지는 예외 얻어오기 기능  ) 

 

@Slf4j
@Component
@Aspect
public class AfterThrowingAspect {
	
	// @AfterThrowing : 기존 @After + 던져지는 예외 얻어오기 기능
	// throwing="exceptionObj" : 던져진 예외를 저장할 매개변수를 지정
	
	@AfterThrowing(pointcut = "CommonPointcut.implPointcut()", throwing="exceptionObj")
	public void serviceReturnValue(Exception exceptionObj) {
		
		String str = "<<exception>> : " + exceptionObj.getStackTrace()[0];
		
		str += " - " + exceptionObj.getMessage();
		
		log.error(str);
	}
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

'Spring > Spring' 카테고리의 다른 글

Spring ( Web Socket ( 메시지 ) )  (0) 2023.05.15
Spring ( @Scheduled )  (0) 2023.05.11
Spring 19 ( 댓글 , 대댓글 삽입,수정,삭제 )  (1) 2023.05.10
Spring 18 ( 게시글 삭제 )  (0) 2023.05.09
Spring 17 ( 게시글 작성/수정)  (0) 2023.05.08
Comments