Tiny Bunny [Spring] AOP 어노테이션 사용 - 솜님의 블로그
솜님의 블로그
카테고리
작성일
2024. 10. 16. 23:42
작성자
겨울솜사탕
<bean class="com.koreait.app.biz.common.LogAdvice" id="la" />
<bean class="com.koreait.app.biz.common.CheckAdvice" id="ca" />

스프링 컨테이너에서 Advice를 사용하기 위해 객체가 필요하고,

해당 객체를 생성하기 위해 bean을 사용해 xml에 태그를 걸어준다.

 

Advice 생성한 <bean>이 new 역할을 대신하니까,

클래스 파일에 @ 어노테이션을 달아준다.

 

Advice가 횡단 관심사이고, 이것은 서비스 레이어와 겹치기 때문에 @Service를 사용한다.

또한 이 어노테이션을 달아준 클래스도 component 스캔 대상이기 때문에 

xml에 <component-scan 태그를 달아준다.

 


<aop:pointcut>

클래스 안에서 누구랑 연결할지 정의할 수 있다.

포인트컷 메서드를 만들고, 그 위쪽에 @Pointcut 어노테이션을 달아준다.

 

@Pointcut("execution(* com.koreait.app.biz..*Impl.select*(..))")
	public void bPointcut() {} // 참조 메서드

포인트컷 어노테이션은 대소문자 구분되는 다른 어노테이션이 있어서

대소문자를 꼭 구분해줘야 한다.

또 아래의 메서드는 참조하기 위해 만든 메서드라 참조  메서드라 불린다.

 

그리고 누구랑, 언제 결합할지를 작성해야 한다.

이 어노테이션은 xml파일에서 aop 설정시 공통 로직 호출 시점을 설정해주는 이름과 동일하다.

 

 

- 핵심 관심 후

- 핵심 관심 반환 후

- 핵심 관심 에러 후

- 핵심 관심 전 후

- 핵심 관심 전

 

 

@Service
@Aspect
public class TestAcvice {
	
	@AfterReturning(pointcut="PointcutCommon.cPointcut()", returning="jp")
	public void print(JoinPoint jp) { // 바인드 변수
		System.out.println("현재 이 어드바이스랑 연결된 조인포인트의 메서드명");
		System.out.println("== 포인트컷의 메서드명");
		
		// 메서드 시그니처를 가지고 온다.
		String methodName = jp.getSignature().getName();
		System.out.println(methodName);
		
		System.out.println("현재 이 어드바이스랑 연결된 조인포인트의 매개변수 정보");
		System.out.println("==포인트컷의 매개변수 정보");
		Object[] args = jp.getArgs();

		BoardDTO boardDTO = (BoardDTO)args[0];
		
		System.out.println(boardDTO.getWriter()+"님이 글을 등록했습니다.");
		
	}
}

@AfterReturning == 핵심 관심 반환 후

piontcut=은 참조하는 포인트컷 이름을 적어주면 되는데,

현재 실습에서는 PointcutCommon에 모두 모여있기 때문에, PointcutCommon안의 dPoincut() 을 사용한다는 의미에서

PointCommon.dPointcut() 이라고 작성해준다.

 

또, 인자에는 나의 비즈니스 메서드를 줄래? 하고 스프링 컨테이너에게 부탁할 수 있다.

   - 여기서 모든 비즈니스 메서드?  == 조인 포인트

인자에 넣은 JoinPoint jp 는 바인드 변수라고 하며,

자동으로 나에게 해당하는 조인포인트를 가지고 온다.

동적 바인딩도 특정 자료형을 찾아 걔를 수행시켜 주는데, 

나에게 걸려있는 조인포인트를 가져온다고 해서 바인드 변수라고 한다.

 

나 (Advice 메서드)랑 insert랑 (발생되는 기능) 크로스가 되니까,

위빙처리 할 시점에 어드바이스가  인지할 수 있어야 한다.

그걸 바인드 변수가 땡겨올 수 있고 가장 많이 사용하는 방식이

- 어떤 매개변수랑 연결되어있는지

- 어떤 메서드를 사용하는지.

 

그리고 Advice와 Piontcut을 연결해주는 @Aspect 어노테이션도 서비스 어노테이션 아래에 적어준다.

이 어노테이션을 스캔해야 하니까 스캔 범위를 xml에 넣어주어야 하고,

Spring에게 AOP를 쓰고 있는 상태임을 알려줘야  한다.

 

// 기존 aop 설정
<aop:aspect ref="la">
	<aop:after method="insertBoardLog" pointcut-ref="aPointcut"/>
</aop:aspect>


// 변경된 aop 설정(사용중임을 알려줌)
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

 

 


추가 내용 및 정리

포인트컷들만 모아두는 클래스를 만든다.

@Aspect
public class PointcutCommon { // 참조메서드를 모아두는 클래스

	@Pointcut("execution(* com.koreait.app.biz..*Impl.*(..))")
	public void aPointcut() {} 
	
	@Pointcut("execution(* com.koreait.app.biz..*Impl.select*(..))")
	public void bPointcut() {}
	
	@Pointcut("execution(* com.koreait.app.biz..BoardServiceImpl.insert(..))")
	public void cPointcut() {}
	
	@Pointcut("execution(* com.koreait.app.biz..MemberServiceImpl.insert(..))")
	public void dPointcut() {}
	
}

참조 메서드들만 클래스에 따로 모아두었기 때문에 

기존 클래스에서 작성한 참조 메서드들은 지워준다.

 

대신 어디에 있는 Pointcut 인지 알아야 하기 때문에 클래스명.포인트컷명 으로 적어주어야 한다

ex) PointcutComm.aPointcut()

 

 

 

🍀 정리
new의 역할을 하는 <bean> 대신 @어노테이션을 달아 관리한다.
   : 서비스 레이어와 결합되기 때문에 같은 메모리 공간을 차지하는게 좋아서 @Service 어노테이션을 달아준다.
pointcut을 표현해줄 메서드를 만들어주는데, 이는 참조 메서드라고 한다.
   : 이 곳에는 동작 시점과, 누구랑 연결할지를 적어준다.
     이때 반환이 있는 애들은 pointcut 이름 뒤에 , 바인드 변수명을 적어준다
     >> ex) @AfterReturning(pointcut="aPoincut()",returning="returnObj)
                >> 속성 값이 하나인 경우는  "aPointcut()" 처럼 속성명 기재를 생략할 수 있는데.
                     이렇게 속성 값이 2개 이상인 경우는 속성명을 다 채워줘야 한다.
Advice와 Pointcut을 연결해주는 @Aspet  어노테이션을 달아줘야 한다. (pointcut 모아둔 클래스에도!)
어노테이션을 스캔해야 하니까 스캔범위를 추가해주고, 
Spring에게 런타임 시 위빙처리 해야한다~ 라고 aop 쓰고 있는 상태임을 알려주는 코드를 xml에 작성한다.

이렇게 관리하면 코드의 관련성이 있는 것들끼리만 모아지게 돼서 응집도가 높아지게 된다.

 

 

 


실습

// Pointcommon

package com.koreait.app.biz.common;

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

@Aspect
public class PointcutCommon { // 참조메서드를 모아두는 클래스

	@Pointcut("execution(* com.koreait.app.biz..*Impl.*(..))")
	public void aPointcut() {} 
	
	@Pointcut("execution(* com.koreait.app.biz..*Impl.select*(..))")
	public void bPointcut() {}
	
	@Pointcut("execution(* com.koreait.app.biz..BoardServiceImpl.insert(..))")
	public void cPointcut() {}
	
	@Pointcut("execution(* com.koreait.app.biz..MemberServiceImpl.insert(..))")
	public void dPointcut() {}
	
}

 

 

1) 서비스 기능에 반환이 있는 경우,

그 반환값이 배열일 땐 아무 동작 X / 반환값이 DTO일 때 어떤 DTO인지 로그 표시

 

// 메서드

	@AfterReturning(pointcut="PointcutCommon.bPointcut()", returning="returnObj")
	public void selectPrint(Object returnObj) { // 바인드 변수
		// 서비스 기능에 반환이 있는 경우, 
		// 그 반환값이 DTO 일때 >> 어떤 DTO인지 로그 찍어주세요
		
		if (returnObj != null) {
			// 받아온 클래스명의 이름 끝자리가 "DTO" 인지 반환받는다.
			boolean isDto=returnObj.getClass().getSimpleName().endsWith("DTO");

			// 맞다면
			if(isDto) {
				System.out.println("✨ DTO 객체 반환 / DTO명 : ["+returnObj.getClass().getSimpleName()+"]");
			}
			else {
				System.out.println("DTO 객체가 아닙니다.");
			}
		}
	}

 

반환된 값의 끝 이름이 DTO임을 확인해서 DTO 여부를 구분한다.

 

▼ 실행 결과

회원가입에서 ID를 입력하면

 

로그 출력된다.

 

 

 

2) CUD에 대해서, 실행 전 " DB 접근 발생" / 실행 후 "DB 변경 완료" 로그

 

	@Before("PointcutCommon.aPointcut()")
	public void databaseBefore(JoinPoint jp) {
		
//		CUD에 대해서 (DB에 변경사항이 발생..!!)
//		전에 DB 접근 발생! 이라고 로그
//		후에 DB 변경 완료! 로그

		String methodName = jp.getSignature().getName();
		
		if(methodName.equals("selectAll") || methodName.equals("selectOne")) {
			System.out.println("select 기능입니다.");
		}
		else {
			System.out.println("✨ DB 접근 발생!");
		}
	}
	
	@AfterReturning(pointcut="PointcutCommon.aPointcut()",returning="jp")
	public void databaseAfter(JoinPoint jp) {
		
//		CUD에 대해서 (DB에 변경사항이 발생..!!)
//		전에 DB 접근 발생! 이라고 로그
//		후에 DB 변경 완료! 로그

		String methodName = jp.getSignature().getName();
		
		if(methodName.equals("selectAll") || methodName.equals("selectOne")) {
			System.out.println("select 기능입니다.");
		}
		else {
			System.out.println("✨ DB 변경 완료!");
		}
	}

@Before와 @AfterReturning 으로 나눠주었다.

 

▼ 실행 결과

글 작성을 하게 되면

 

 

'Spring' 카테고리의 다른 글

[Spring] 트랜잭션  (0) 2024.10.21
[Spring] 템플릿 패턴  (0) 2024.10.20
[Spring] AOP 관점지향프로그래밍  (0) 2024.10.15
[Spring] 비동기 처리  (0) 2024.10.15
[Spring] 2-Layerd 아키텍처  (0) 2024.10.10