<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 |