JAVA를 잡아라!...
Spring Framework_@어노테이션_AOP 설정 본문
#1 스프링 컨테이너의 장점
1. IoC 로 의존주입을 통한 낮은 결합도 보장
2. AOP 를 지원해서 높은 응집도 보장
#1-1 IoC
- new를 컨테이너가 대신 해줌
- 서블릿(FrontController)을 new == 서블릿 컨테이너(톰캣, 웹 서버)
- POJO(DAO, Action,...)를 new == 스프링 컨테이너
- 컨테이너 > .xml(설정파일)
- DI(의존주입)을 통한 낮은 결합도 보장
#1-2 AOP
- 어떤 비즈니스로직이어도 항상 등장하는 로직이 있음
- 하나의 컨트롤러(액션, 서비스)에서 C,R,U,D → 한번에 처리를 했지만
- 다른 파트에서도 수행하는 로직이 있다면 그것을 공통 로직이라고 인지를해서 분리해서 관리
C : 인증 로깅 C 트랜잭션 예외처리
R : 인증 로깅 R
U : 인증 로깅 (보안) U 트랜잭션 예외처리
D : 인증 로깅 (보안) D 트랜잭션 예외처리
* 데이터에 변화를 줄수있는 U, D경우 프로그램(어플리케이션)에서 보안에 신경을 많이 쓸수 있음
[결론]
비즈니스 메서드(비즈니스 로직, 핵심 관심, CRUD)에서 횡단 관심(공통 로직)을 분리
→ 관심 분리(Separation of Concern) (이때 Concern == 관심, 로직)
→ 로직(관심)들끼리 코드를 관리 → 높은 응집도를 보장
#2 공통로직 : Advice
예제) 로그 찍기 System.out.println
① 항상 찍는 로그는 공통 로직이네?
② Advice 클래스로 분리 == 관심 분리
③ Advice를 Service의 멤버변수로 설정
▶ 멤버변수로 설정을 하면 의존 관계 발생
▶ 의존주입 해야함
④ 비즈니스 로직 수행 시 호출
++ plus
Aspect 애스팩트 (== 결합, 위빙, weaving)
PointCut(핵심로직)과 Advice(횡단관심)의 조합 → 애스팩트 처리
AOP 설정과 관련된 어노테이션
@어노테이션 종류 | 설명 |
@Aspect |
|
@PointCut |
|
@Before |
|
@After |
|
@Around |
|
@AfterReturning |
|
@AfterThrowing |
|
#3 AOP 설정
▶ pom.xml_dependency 추가
▶ 혹시 모르니 Maven 강제 업데이트
▶ applicationContext.xml_AOP 관련 스키마 추가
#3-1 AOP 설정_applicationContext.xml
1. Advice 클래스 생성 (객체 new)
- 각 클래스는 Advice 타입에 맞는 메서드를 갖고 있으며, 이 메서드는 AOP 동작 시점에 실행됨
<bean class="com.spring.biz.common.BeforeAdvice" id="bAdvice" />
<bean class="com.spring.biz.common.AfterReturningAdvice" id="aRAdvice" />
<bean class="com.spring.biz.common.AfterThrowingAdvice" id="aTAdvice" />
<bean class="com.spring.biz.common.AroundAdvice" id="aAdvice" />
2. AOP 설정
- <aop:config> 태그는 AOP 설정을 위한 최상위 태그
- 뭐랑 뭘 연결할지 작성
<aop:config>
...
</aop:config>
2-1. Pointcut 정의
- <aop:pointcut> 태그는 Advice를 적용할 대상 메서드를 정의
- allPointcut : com.spring.biz 패키지 내 모든 Impl 클래스의 모든 메서드를 대상 (모든 biz 메서드)
- selectPointcut : com.spring.biz 패키지 내 모든 Impl 클래스의 select류 메서드를 대상 (R에만 해당하는 biz 메서드)
<aop:pointcut id="allPointcut" expression="execution(* com.spring.biz..*Impl.*(..))" />
<aop:pointcut id="selectPointcut" expression="execution(* com.spring.biz..*Impl.select*(..))" />
2-2. Aspect 처리 (== 위빙)
- Pointcut(핵심로직)과 Advice(횡단관심)의 조합(결합, 위빙)
- <aop:aspect> 태그는 Advice 클래스를 참조하고,
- 내부에 <aop:~~~> 태그를 사용해 Pointcut과 Advice(method="사용할 메서드")를 연결
<aop:aspect ref="bAdvice">
<aop:before pointcut-ref="allPointcut" method="beforePrintLog" />
</aop:aspect>
<aop:aspect ref="aRAdvice">
<aop:after-returning pointcut-ref="selectPointcut" method="afterReturningPrintLog" />
</aop:aspect>
<aop:aspect ref="aTAdvice">
<aop:after-throwing pointcut-ref="allPointcut" method="afterThrowingPrintLog" />
</aop:aspect>
<aop:aspect ref="aAdvice">
<aop:around pointcut-ref="allPointcut" method="aroundPrintLog" />
</aop:aspect>
#3-2 AOP 설정_@어노테이션
1. 스프링 컨테이너에서 @Aspect 어노테이션이 붙은 클래스를 스캔해 자동으로 빈 생성 및 기능 수행
2. Advice 클래스 생성 (객체 new) → @Service
- BeforeAdvice.java
- AfterReturningAdvice.java
- AfterThrowingAdvice.java
- AroundAdvice.java
- 상세 코드는 맨 하단 참조
3-1. Pointcut 정의
- @Pointcut : Advice를 적용할 대상 메서드를 정의
- @Aspect : 애스팩트 처리
- allPointcut() : com.spring.biz 패키지 내 모든 Impl 클래스의 모든 메서드를 대상 (모든 biz 메서드)
- selectPointcut() : com.spring.biz 패키지 내 모든 Impl 클래스의 select류 메서드를 대상 (R에만 해당하는 biz 메서드)
@Aspect
public class PointcutCommon {
@Pointcut("execution(* com.spring.biz..*Impl.*(..))")
public void allPointcut() {}
@Pointcut("execution(* com.spring.biz..*Impl.select*(..))")
public void selectPointcut() {}
}
3-2 Advice 동작 시점 (포인트컷과 결합할 Advice 지정)
- @Before("PointcutCommon.allPointcut()")
- @AfterReturning(pointcut="PointcutCommon.selectPointcut()", returning="returnObj")
- @AfterThrowing(pointcut="PointcutCommon.allPointcut()", throwing="exceptObj")
- @Around("PointcutCommon.allPointcut()")
- 상세 코드는 맨 하단 참조
3-3. Aspect 결합
- @Aspect : Pointcut과 Advice에 처리
[ Advice 클래스 코드 ]
BeforeAdvice.java
// @Component로 달아도되지만, boardDAO 서비스랑 결합함!, 같은 레이어랑 달면 좋기때문에 유리!
@Service
@Aspect
public class BeforeAdvice {
// pointcut="어드바이스가 적용될 메서드(Joinpoint)"
@Before("PointcutCommon.allPointcut()")
// JoinPoint 인자를 통해 현재 비즈니스 메서드의 정보를 확인할 수 있음
public void beforePrintLog(JoinPoint jp) {
String methodName = jp.getSignature().getName();
System.out.println(" [BeforeAdvice] : " + methodName);
Object[] args = jp.getArgs();
// 인자가 DTO 1개일 확률이 높기때문에 1개만 써도됨
System.out.println(" 비즈니스 메서드에서 사용하는 인자 : " + args[0]);
for(Object arg:args) {
System.out.println(arg);
}
System.out.println(" [BeforeAdvice] 비즈니스 메서드 수행 전 로그 출력");
}
}
AfterReturningAdvice.java
- returning : 데이터 타입을 Object로 지정해놔서 모든 자료형이 들어올 수 있음
- 동적바인딩
- 메서드 호출 시, 현재 메모리에 존재하는 객체를 실제로 수행(호출)할 수 있게 하는 것
returnObj 변수는 메서드가 실행되고 반환되는 값의 실제 자료형에 따라 동적으로 결정됨
ex. 메서드가 MemberDTO 객체를 반환하면, returnObj는 MemberDTO 타입의 객체가 됨
즉, 현재 동작하고 있는 객체나 메서드를 자동으로 인지하는 것임! - 바인딩은 객체 지향의 핵심으로, 다형성(상속)을 구현한 상황에서 사용 가능(오버라이딩할 경우)
예를 들어, 부모 클래스에 정의된 메서드를 자식 클래스에서 오버라이딩(재정의)할 경우에 발생
이때 부모 클래스의 참조 변수가 자식 클래스의 인스턴스를 가리키고 있는 경우,
해당 메서드 호출 시에는 실제로 실행되는 메서드가 자식 클래스에서 오버라이딩한 메서드가 됨
이 과정에서 동적으로 적절한 메서드가 선택되는 것 == 동적 바인딩
@Service
@Aspect
public class AfterReturningAdvice {
// pointcut="어드바이스가 적용될 메서드(Joinpoint)", returning="메서드 반환 값을 저장하는 객체"
@AfterReturning(pointcut="PointcutCommon.selectPointcut()", returning="returnObj")
public void afterReturningPrintLog(JoinPoint jp, Object returnObj) {
String methodName = jp.getSignature().getName();
System.out.println(" [AfterReturningAdvice] : "+methodName);
Object[] args = jp.getArgs();
System.out.println(" 비즈니스 메서드에서 사용하는 인자 : "+args[0]);
for(Object arg:args) {
System.out.println(arg);
}
System.out.println(" 비즈니스 메서드의 OUTPUT : "+returnObj);
if(returnObj instanceof MemberDTO) {
MemberDTO mDTO = (MemberDTO)returnObj;
if(mDTO! = null) {
if(mDTO.getRole().equals("ADMIN")) {
System.out.println("[관리자 로그인]");
System.out.println(mDTO.getName()+"님 입장");
}
else {
System.out.println("[사용자 로그인]");
}
}
}
System.out.println(" [AfterReturningAdvice] 비즈니스 메서드 수행 후 로그 출력");
}
}
AfterThrowingAdvice.java
@Service
@Aspect
public class AfterThrowingAdvice {
// pointcut="어드바이스가 적용될 메서드(Joinpoint)", throwing="메서드에서 발생한 예외를 저장하는 객체"
@AfterThrowing(pointcut="PointcutCommon.allPointcut()", throwing="exceptObj")
public void afterThrowingPrintLog(JoinPoint jp, Object exceptObj) {
String methodName = jp.getSignature().getName();
System.out.println(" [AfterThrowingAdvice] : "+methodName);
Object[] args = jp.getArgs();
System.out.println(" 비즈니스 메서드에서 사용하는 인자 : "+args[0]);
for(Object arg:args) {
System.out.println(arg);
}
if(exceptObj instanceof NumberFormatException) {
System.out.println("값이 숫자가 아님");
}
else if(exceptObj instanceof ArithmeticException) {
System.out.println("수학적인 오류");
System.out.println("일부러 발생시킨 에러");
}
else {
System.out.println("미확인 에러");
}
System.out.println(" [AfterThrowingAdvice] 비즈니스 메서드 수행 중 예외 발생");
}
}
AroundAdvice.java
@Service
@Aspect
public class AroundAdvice {
@Around("PointcutCommon.aPointcut")
// ProceedingJoinPoint pjp : 메서드 인자로 반드시 지금실행하고 있는 비즈니스 메서드(포인트컷)을 가져야함
// try-catch 반드시 예외를 발생시킴
public Object aroundPrintLog(ProceedingJoinPoint pjp) throws Throwable {
String methodName = pjp.getSignature().getName();
System.out.println(" [BEFORE]");
StopWatch sw = new StopWatch();
sw.start();
// 비즈니스 메서드 잠시 탈취 --> 비즈니스 메서드 수행
// 반환할 자료형을 몰라서 Object로 타입 지정
Object obj = pjp.proceed();
sw.stop();
System.out.println(" [AFTER]");
System.out.println(" [AroundAdvice] : " + methodName);
System.out.println(" 메서드 수행 시간 : " + sw.getTotalTimeMillis() + "(ms)초");
return obj; // 비즈니스 메서드 수행 결과 return
}
}
++
'Spring' 카테고리의 다른 글
Spring Framework_ControllerVersion01_DispatcherServlet, HandlerMapping, Controller, ViewResolver (0) | 2024.03.04 |
---|---|
Spring Framework_DAO_JdbcTemplate (0) | 2024.03.04 |
Spring Framework_@어노테이션_빈(Bean) 및 의존성(DI) 주입 (0) | 2024.02.29 |
Spring Framework_컨테이너 & 빈 관리 (0) | 2024.02.29 |
Spring Framework_의존성 주입(Dependency Injection, DI) (0) | 2024.02.27 |