티스토리 뷰

스프링

AOP (Aspect Oriented Programming)

Lurutia 2018. 7. 25. 21:31

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를 사용하기 위한 라이브러리

Proxy 객체를 생성할 필요가 있는데, 스프링에서 Proxy 객체를 만드는 방법은 크게 JDK의 InvocationHandler를 이용하는 Dynamic Proxy 방식과 CGLIB를 이용하는 방식이 사용된다.
 
<!--  AOP -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${org.springframework-version}</version>
</dependency>

 

pom.xml에 추가

 

또한 AspectJ 언어의 문법을 이용하기 때문에 이와 관련된 라이브러리 역시 추가해 준다.

 

<!-- AspectJ -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${org.aspectj-version}</version>
</dependency>
 
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>${org.aspectj-version}</version>
</dependency>
 

AOP의 설정은 크게 어노테이션을 이용하는 방법과 XML을 이용하는 방법으로 나뉘는데, XML을 이용하는 설정은 'servlet-context.xml'이나 'root-context.xml'에서 사용하는 XML 네임스페이스가 필요하므로 설정 메뉴를 이용해서 'aop' 네임스페이스를 추가해야한다.

 

root-context.xml에서 AOP의 설정을 통한 자동적인 Proxy 객체 생성을 위해서 다음과 같은 설정을 미리 추가한다.

<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

 

Advice 생성하기

AOP기능을 위해서 package를 생성한다 ex)kr.chosun.aop

 

root-context.xml에서 해당패키지를 인식할 수 있도록 설정한다.
<context:component-scan base-package="kr.chosun.aop"/>
<aop:config>
</aop:config>
 
AOP의 기능을 설정할 수 있도록 <aop:config>를 추가할 수 있다.
위 설정은 XML을 이용해서 AOP기능을 이용할 때 사용하며 여기선 어노테이션을 이용해서 적용한다.
 

AOP 적용하기

SampleAdvice (kr.chosun.aop package)

 

SampleAdvice.java
다운로드

 

 

 

@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

ProceedingJoinPoint는 JoinPoint의 하위 인터페이스며 proceed를 통해서 target을 실행시킨다. 
proceed()는 특이하게도 Exception보다 상위의 Throwable을 처리해야 한다.
 
@Around 어노테이션을 이용하는 경우는 반드시 Object타입을 리턴해야한다. 다른 Advice와는 달리 @Around는 메소드를 직접 호출하고, 결과를 반환해야만 정상적인 처리가 된다.

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의 메소드 호출 이전에 이후 모두 적용(가장 광범위하게 사용) 

 

 

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2025/02   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28
글 보관함