분리
를 허용해서 모듈성
을 증가시키는 것이 목적인 프로그래밍 패러다임횡단 관심사
종단 관심사
위 사진에서 횡단 관심사는 (로킹, 보안, 트랜잭션) 과 같은 중복되는 관심사이며
종단관심사는 회원가입, 정보수정, 게시물관리 와 같은 비즈니스 로직이다
결론 : AOP
는 횡단 관심사를 모듈로써 사용할 수 있게 하는 개념
AOP의 장점 : 객체들이 독립적으로 작용 -> 관점지향프로그래밍 적용시 더 (객체지향화 됨)
✅ Filter
vs Interceptor
vs AOP
Filter
: 일반적으로 스프링과 무관하게 전역적으로 처리해야 하는 작업들을 처리할때 사용
Interceptor
: 로그인 체크, 권한체크, 프로그램 실행시간 계산작업 로그확인 등의 작업할 때 사용
AOP
: 주로 '로깅', '트랜잭션', '에러 처리' 등 비즈니스단의 메소드
에서 조금 더 세밀하게 조정하고 싶을 때 사용 , Interceptor나 Filter와는 달리 메소드 전후의 지점에 자유롭게 설정이 가능
✅ AOP 용어
Aspect
: 공통의 기능을 저장하는 클래스 생성
Advice
: 타겟에 제공할 부가기능을 담고 있는 모듈
pointcut
: 어드바이스를 적용할 타겟의 메서드를 선별하는 정규표현식
표현식은 execution으로 시작하고, 메서드의 Signature를 비교하는 방식
Join Point
: Advice가 적용될 수 있는 위치
XML방식 및 어노테이션 방식에서 주로 사용하는 방법
- 공통의 관심사(기능)를 저장하는 클래스 생성
Aspect
pointcut
설정 : 어느지점에서 실행할지를 결정하는 것 -> 어떤 메소드에서 실행할지(타겟) -> 표현식으로 써야함advisor
: 실행할 시점을 설정 (위에서 설정한 타겟이 실행되기 전에 할껀지 후에할껀지 등등)
ex) : before, after, Around(실행한 전후에), afterRetruning(리턴된후에), afterThrowing- 실행할 메소드 : 공통의 로직을 구현하는 메소드,
JoinPoint 클래스
제공
라이브러리 등록
<!-- pom.xml -->
<!-- AspectJ -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${org.aspectj-version}</version>
</dependency>
xml 방식
예시
// aop-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<!-- xml 방식으로 aop 적용하기 -->
<bean id="loggerAspect" class="com.bs.spring.common.aop.LoggerAspect"/>
<aop:config>
<aop:aspect ref="loggerAspect">
<aop:pointcut expression="execution(* com.bs.spring..*(..))" id="logPointcut"/>
execution(모든접근제어자 memo밑에있는 모든클래스안의 모든 메소드 매개변수개수 상관x
<aop:before method="loggerBefore" pointcut-ref="logPointcut"/>
<aop:after method="loggerAfter" pointcut-ref="logPointcut"/>
</aop:aspect>
</aop:config>
<!-- annotation 방식으로 AOP적용하기 -->
<aop:aspectj-autoproxy/>
<!-- 이 태그가 존재해야, aop가 있다는것을 인식 -->
<!-- 6. springbeanconfiguration.xml 파일에서 <aop:aspectj-autoproxy/>선언해줌 -->
<!-- 어노테이션방식에서만 작성하면됨 -->
</beans>
어노테이션 방식
순서
aspect
역할을 할 클래스를 생성- 클래스를 bean으로 등록
@Component
(클래스선언부)- 클래스를 aspect 등록
@Aspect
(클래스선언부)- @PointCut으로 포인트컷 설정
-> (4-1) : 메서드 시그니처 패턴execution
-> (4-2) : 타입 시그니처 패턴within
- @Befer, @After, @Around... 실행메소드에 설정 ->
advisor
설정 (태그는 따로 안써도됨)- springbeanconfiguration.xml 파일에서 <aop:aspectj-autoproxy/>선언해줌
-> 어노테이션방식에서만 작성하면됨
어노테이션방식 예시 1
package com.bs.spring.common.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
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.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Component // 2. 클래스를 bean으로 등록 `@Component`
@Aspect // 3. 클래스를 aspect 등록
public class AnnoLoggerAspect { // 1. aspect 역할을 할 클래스를 생성
// 4. @PointCut으로 포인트컷 설정
@Pointcut("within(com.bs.spring.member..*)")
public void loggerTest() {}
// 5. @Befer, @After, @Around... 실행메소드에 설정 -> advisor 설정
@Before("loggerTest()") // member 패키지 밑에 모든 함수들을 호출하기전에 발동
public void loggerBefore(JoinPoint jp) {
log.debug("======= anootation aop =======");
Signature sig = jp.getSignature();
log.debug(sig.getDeclaringTypeName()+" "+sig.getName());
log.debug("==============================");
}
@Pointcut("execution(* com.bs.spring.memo..*(..))")
public void memoLogger() {}
@After("memoLogger()")
public void afterLogger(JoinPoint jp) {
log.debug("======= anootation aop after =======");
Signature sig = jp.getSignature();
log.debug(sig.getDeclaringTypeName()+" "+sig.getName());
log.debug("=============aop after =================");
}
// Around -> 메소드 실행 전,후에 특정 로직을 실행할 때 사용
// 실행하는 시간 체킹할때 많이 사용함
// 반환형 : object, 매개변수타입 ProceedingJoinPoint
@Around("execution(* com.bs.spring..*DaoImpl.*(..))")
public Object daoLogger(ProceedingJoinPoint join) throws Throwable{
// 전, 후 로직은 한번에 설정할 수 있다
// 전, 후를 구분하는 구문은 ProceedingJoinPoint 클래스가 제공하는 proceed() 메소드를 이용
// proceed() 메소드가 호출한 다음 라인은 후 처리, 그 전 라인은 전처리
// proceed() 메소드는 Object를 반환한다
// 메소드 실행시간 체크하기
StopWatch stop = new StopWatch();
stop.start();
log.debug("================== around logger dao before =====================");
log.debug("-------- 전처리 내용 구현 ---------");
log.debug("==========================================================");
Signature sig = join.getSignature();
String classMethod = sig.getDeclaringType().getName()+sig.getClass();
// 여기까지가 전처리 내용
Object obj = join.proceed(); // proceed 메소드를 기준으로 전,후 나눔
stop.stop();
log.debug("================== around logger dao afetr =====================");
log.debug("-------- 후처리 내용 구현 ---------");
log.debug("실행시간 : "+stop.getTotalTimeMillis()+"ms");
log.debug("==========================================================");
return obj; // 반환값은 따로 안쓰이며, 내부적으로 쓰임
}
// 에러가 발생했을때 발동
@AfterThrowing(pointcut = "loggerTest()", throwing="e")
public void afterThrowinglogger(JoinPoint jo, Throwable e) {
log.debug("에러발생!!!");
Signature sig = jo.getSignature();
log.debug("{}",sig.getDeclaringTypeName()+" "+sig.getName());
log.debug("{}",e.getMessage());
StackTraceElement[] stacktrace = e.getStackTrace();
for(StackTraceElement element : stacktrace) {
log.debug("{}",element);
}
}
}
어노테이션방식 예시 2
package com.bs.spring.common.aop;
import javax.servlet.http.HttpSession;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import com.bs.spring.common.exception.AuthenticationException;
import com.bs.spring.member.Member;
@Component
@Aspect
public class AuthenticationCheckAop {
// Aop로 차단하기
@Before("execution(* com.bs.spring.memo..*(..))")
public void checkcheck(JoinPoint jp) {
// 로그인정보를 확인하여 아이디 확인하여 admin이면 통과! admin 아니면 막기!
// spring이 제공하는 RequestContextHolder 클래스 이용해서 session 값을 가져올 수 있다
HttpSession session = (HttpSession)RequestContextHolder.currentRequestAttributes()
.resolveReference(RequestAttributes.REFERENCE_SESSION);
Member loginMember = (Member)session.getAttribute("loginMember");
if(loginMember==null || !loginMember.getUserId().equals("admin")) {
throw new AuthenticationException("서비스 이용 권한이 부족합니다.");
// 사용자가 만든 AuthenticationException 예외 발생
}
}
}
너무 좋은 글이네요. 공유해주셔서 감사합니다.