티스토리 뷰
SPRING BOOT + JAVA + STS
AspectAdvice.java
package com.yd.scoville;
import java.util.Arrays;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class AspectAdvice {
private Logger logger = LoggerFactory.getLogger("LoggerAspect");
@Around("execution(* com.yd.scoville.repository..*Repository.*(..)) or "+
"execution(* com.yd.scoville.service..*Service.*(..)) or "+
"execution(* com.yd.scoville.controller..*Controller.*(..))")
Object logPrint(ProceedingJoinPoint joinPoint) throws Throwable {
Object[] args = joinPoint.getArgs();
logger.info(Arrays.toString(args));
long startTime = System.currentTimeMillis();
long endTime;
String type = "";
String typeName = joinPoint.getSignature().getDeclaringTypeName();
String name = joinPoint.getSignature().getName();
Object result;
if(typeName.indexOf("Controller") > -1) {
type = "Controller \t: ";
}
else if(typeName.indexOf("Service") > -1) {
type = "Service \t: ";
}
else if(typeName.indexOf("Repository") > -1) {
type = "Repository \t\t: ";
}
logger.info(" start : " + type + typeName + "." + name);
result = joinPoint.proceed();
endTime = System.currentTimeMillis();
logger.info(" end : " + type + typeName + "." + name + " : " + (endTime - startTime));
return result;
}
}
어노테이션
@EnableAspectJAutoProxy
@SpringBootApplication
public class ScovilleApplication {
public static void main(String[] args) {
SpringApplication.run(ScovilleApplication.class, args);
}
}
@EnableAspectJAutoProxy 를 Root에 달아주어야 AOP가 가능
SPRING BOOT + KOTLIN
aop/LoggerAspect.kt
package com.sunnyfactory.jy.configuration
import org.aspectj.lang.ProceedingJoinPoint
import org.aspectj.lang.annotation.Around
import org.aspectj.lang.annotation.Aspect
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.context.annotation.Profile
import org.springframework.stereotype.Component
@Component
@Aspect
@Profile("dev")
class AspectAdvice {
private val logger: Logger = LoggerFactory.getLogger("LoggerAspect")
@Around("""execution(* com.sunnyfactory.jy.repository..*Repository.*(..))||
execution(* com.sunnyfactory.jy.service..*Service.*(..))||
execution(* com.sunnyfactory.jy.controller..*Controller.*(..))""")
fun logPrint(joinPoint: ProceedingJoinPoint): Any {
var type:String = ""
val declaringTypeName: String = joinPoint.signature.declaringTypeName
val signatureName: String = joinPoint.signature.name
val result: Any
val startTime = System.currentTimeMillis()
val endTime: Long
if(declaringTypeName.indexOf("Controller") > -1) {
type = "Controller \t: "
}
else if(declaringTypeName.indexOf("Service") > -1) {
type = "Service \t: ";
}
else if(declaringTypeName.indexOf("Repository") > -1) {
type = "Repository \t\t: "
}
logger.info("start : " + type + signatureName)
result = joinPoint.proceed()
endTime = System.currentTimeMillis();
logger.info("end : " + type + signatureName + " : " + (endTime - startTime));
return result
}
}
의존성
// https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop
compile("org.springframework.boot","spring-boot-starter-aop","2.1.7.RELEASE")
Root Annotation
@EnableAspectJAutoProxy
@SpringBootApplication
class ApiApplication
fun main(args: Array<String>) {
runApplication<ApiApplication>(*args)
}
AOP(Aspect Oriented Programming)
관점형 프로그래밍 기능은 기존의 비즈니스 로직 외 작성해야 하는 코드를 별개로 분리함으로써
개발자가 좀 더 비즈니스 로직에만 집중해서 처리할 수 있는 방법을 제공한다.
비즈니스로직을 작성할때 가지는 의문으로 '어떻게 기존의 코드를 수정하지 않고 코드의 앞이나 뒤에서 필요한 기능이 동작하도록 작성할 수 있을까?' 다.
이에 대한 해결 실마리는 프록시 패턴이라는 것에 있다.
외부에서 특정한 객체(target)를 호출하면, 실제 객체를 감싸고 있는 바깥쪽 객체(Proxy)를 통해서 호출이 전달된다. Proxy 객체는 AOP의 기능이 적용된 상태에서 호출을 받아 사용되고, 실제 객체와 동일한 타입을 자동으로 생성할 수 있기 때문에 외부에서는 실제 객체와 동일한 타입으로 호출할 수 있다.
AOP를 사용하기 위한 라이브러리
AOP의 설정은 크게 어노테이션을 이용하는 방법과 XML을 이용하는 방법으로 나뉘는데, XML을 이용하는 설정은 'servlet-context.xml'이나 'root-context.xml'에서 사용하는 XML 네임스페이스가 필요하므로 설정 메뉴를 이용해서 'aop' 네임스페이스를 추가해야한다.
root-context.xml에서 AOP의 설정을 통한 자동적인 Proxy 객체 생성을 위해서 다음과 같은 설정을 미리 추가한다.
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
Advice 생성하기
AOP 적용하기
@Aspect 어노테이션은 AOP 기능을 하는 클래스의 선언에 추가
@Component 는 스프링의 빈으로 인식되기 위한 설정.
제대로 적용됐다면 위 이미지처럼 왼쪽에 화살표가 생기는걸 확인할 수 있다.
모양에 따라서 Before, After, Around 타입의 Advice가 적용됐다는걸 표현한다.
(AOP가 적용된 모습)
만일 실행된 결과에 INFO로 시작하는 로그가 기록되지 않았다면 /src/main/resources 내의 log4j.xml의 설정이 아래와 같은지 확인해야 한다.
<!-- Root Logger -->
<root>
<priority value="info" />
<appender-ref ref="console" />
</root>
JoinPoint와 ProceedingJoinPoint
AOP를 이해하기 위한 용어
용어 |
설명 |
Aspect |
공통 관심사에 대한 추상적인 명칭. 예를 들면 로깅이나 보안, 트랜잭션과 같은 기능 자체에 대한 용어 |
Advice |
실제로 기능을 구현한 객체 |
Join Points |
공통 관심사를 적용할 수 있는 대상. Spring AOP에서는 각 객체의 메소드가 이에 해당 |
Pointcuts |
여러 메소드 중 실제 Advice가 적용될 대상 메소드 |
target |
대상 메소드를 가지는 객체 |
Proxy |
Advice가 적용되었을 때 만들어지는 객체 |
Introduction |
target에는 없는 새로운 메소드나 인스턴스 변수를 추가하는 기능 |
Weaving |
Advice와 target이 결합되어서 프록시 객체를 만드는 과정 |
- Advice : 실제 적용시키고 싶은 코드 자체, 개발자가 만드는 것은 Aspect가 아닌 클래스를 제작하고 @Advice를 적용하는 것임, 예를 들어 로그 출력 기능, 파라미터 체크 기능 자체는 Aspect라고 용어로 부르지만, 실제 구현 시에는 Advice를 제작한다고 표현.
- target : 실제 비즈니스 로직을 수행하는 객체를 의미, 용어 그대로 Aspect를 적용해야 하는 대상 객체를 의미함
- Join points : 작성된 Advice가 활약할 수 있는 위치를 의미, 예를 들어 BoardService에는 등록, 수정, 삭제만을 골라서 Advice를 적용할 수 있는데, 이때 BoardService의 모든 메소드는 JoinPoint가 됨
- PointCuts : 여러 Join points 중에서 Advice를 적용할 대상을 선택하는 정보, 이를 통해서 특정 메소드는 Advice가 적용된 형태로 동작함.
Advice의 종류
타입 |
기능 |
Before Advice |
target의 메소드 호출 전에 적용 |
After returning |
target의 메소드 호출 이후에 적용 |
After throwing |
target의 예외 발생 후 적용 |
After |
target의 메소드 호출 후 예외의 발생에 관계없이 적용 |
Around |
target의 메소드 호출 이전에 이후 모두 적용(가장 광범위하게 사용) |
'스프링' 카테고리의 다른 글
타임리프(thymeleaf) 사용하기 (0) | 2020.12.31 |
---|---|
@Valid 를 이용한 검증 및 Advice를 이용한 에러처리 (0) | 2020.06.10 |
JAVA SPRING 인터셉터 (Interceptor) (0) | 2018.07.26 |
JAVA SPRING 트랜잭션(Transaction) (0) | 2018.07.26 |
스프링(spring) 이클립스(eclipse) STS 환경설정 (0) | 2017.11.07 |