SpringBoot 를 Default 상태에서 ( 어떠한 설정 없이 ) Embedded Tomcat 구동 시 다음과 같은 오류가 발생될 수 있다. 

 

 *************************** 
 APPLICATION FAILED TO START
 ***************************

 Description:
 
 Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.

 Reason: Failed to determine a suitable driver class

 Action:

 Consider the following:

 If you want an embedded database (H2, HSQL or Derby), please put it on the classpath.
 If you have database settings to be loaded from a particular profile you may need to activate it
 (the profiles local are currently active).

 

오류 원인은 SpringBoot 내장 톰캣 구동 시  DB 연결 시도가 실패하여 발생되는 것이다. 

 

해결 방법은 2가지 이다. 

1. DB 연결 설정을 추가 또는 설정에 잘못된 부분이 있는지 확인한다.

2. DB 연결 Lib 를 제외하고 내장 톰캣을 수행한다. 

 

여기서는 DB 연결 없이 내장 톰캣 수행 방법을 기입해보겠다. 

 

우선 DB 을 위한 설정이 있다면 수행되지 않게 처리한다.

 

다음은 @SpringBootApplication 이 선언된 부분을 찾는다. 

 

그리고 다음과 같이 변경한다.


// @SpringBootApplication  // ( 기존 선언 주석처리 )
@SpringBootApplication ( exclude = {DataSourceAutoConfiguration.class} )

즉, 자동 DB 연결 class 를 제외하고 내장 톰캣을 구동하게 선언한 것이다. 

 

만약, SpringBoot 내장 톰캣 구동이 실패할 경우, 소스 내부적 또는 Properties 내에서 DB 연결 설정부분이 

있는지 찾아보기를 바란다.

개인적인 사정으로 글이 많이 늦었습니다. 이전 글을 올린 지 한달 가까이가 지났네요. ;;

 

이번에는 AOP 글은 실제 AOP 사용 및 결과에 대한 예제입니다. 

 

AOP 표현법만 참고 하시려면, 해당 AOP Pointcut 표현법 링크를 클릭해주세요. 

 

SpringBoot 에서 AOP 를 활용하려면 다음과 같은 조건이 필요합니다. 

 

아래는 build.gradle 파일에 선언된 AOP 관련 lib 입니다. 

(버전정보는 본인의 SpringBoot 버전에 맞추어 사용하면 됩니다. )

implementation 'org.springframework.boot:spring-boot-starter-api:2.4.5'

 

그 다음은 @SpringBootApplication 이 선언된 메소드에 AOP 를 사용한다고 선언해야 합니다. 

내부적으로 많은 원리가 있을거라고 생각하지만.. ㅋ 깊게 공부하지 않았으니 SKIP..

일단 AOP 사용을 위해 @EnableAspectJAutoProxy 를 선언해줍니다. 

( 아래 샘플의 CONF_FILE= "...." 은 그냥 SpringBoot 수행 시 설정파일이라 무시하셔도 무관합니다. )


@EnableAspectJAutoProxy
@SpringBootApplication
public class SpringBootApplication
{
    private static final String CONF_FILE = "spring.config.location=classpath:application.yml"

    public static void main( String[] args )
    {
        new SpringApplicationBuilder( SpringBootApplication.class )
                .properties( CONF_FILE )
                .run ( args );
    }
}

서두가 길었습니다. 이제 실제 AOP 사용 예제와 그 결과를 확인해보겠습니다. 

 

샘플은 Before , Around, After 순으로 해보겠습니다. 

( Before, Around .... 부분이 궁금하시면 앞서 설명한 AOP 는 무엇인가? 페이지를 참고 해주세요.  

 

AOP 호출 테스트를 위한 Control 함수 입니다. 

 @RestController
 public class AopTestPartCTRL
 {
     // slf4j Logger 사용
     private static final Logger loclog = LoggerFactory.getLogger( AopTestPartCTRL.class );

     @RequestMapping( value = "aopTestPart1" )
     public ResponseEntity aopTestCtrl_1( HttpServletRequest request, @RequestParam(required=false) String testParam )
     {
         // 단순히 controller 메소드명을 로깅
         loclog.info( "CTRL Method ---> {}", Thread.currentThread().getStackTrace()[1].getMethodName() );

         // URL ( ~~/aopTestPart1 ) 호출 시 200 성공 및 'AOP-TEST_PART_1' 이 리턴
         return new ResponseEntity( "AOP-TEST_PART_1", HttpStatus.OK );
     }
 }

 

[ @Before Aop ]

 @Component
 @Aspect
 public class TestAspectOrder_1
 {
     // slf4j Logger 사용
     private static final Logger loclog = LoggerFactory.getLogger( TestAspectOrder_1.class );

     @Before( "execution(* com.foxy.wany..*Test.Wany*(..) )"
     public void beforeAopTest_1( JoinPoint jp )
     {
          Object args[] = jp.getArgs();
          
         // beforeAopTest_1 호출 대상 메소드
         loclog.info( "Before ---> Target Method : {}", jp.getSignature().getName() );          

         // beforeAopTest_1 호출 대상 메소드 인자 갯수
         loclog.info( "Before ---> Target Method Param len : {}", args.length );          
     }
 }

 

[ RESULT ]

 Before ---> Target Method : aopTestCtrl_1
 Before ---> Target Method param len : 2
 CTRL Method ---> aopTestCtrl_1

위 결과에서 보듯이 Before AOP 가 먼저 호출 후에 controller 메소드 aopTestPart1 가 호출 되었습니다.

 

 

[ @After , @AfterReturning Aop ]

 @Component
 @Aspect
 public class TestAspectOrder_1
 {
     // slf4j Logger 사용
     private static final Logger loclog = LoggerFactory.getLogger( TestAspectOrder_1.class );


     @After( "execution(* com.foxy.wany..*Test.Wany*(..) )" 
     public void afterAopTest_1( JoinPoint jp )
     {
          Object args[] = jp.getArgs();
          
         // afterAopTest_1 호출 대상 메소드
         loclog.info( "After---> Target Method : {}", jp.getSignature().getName() );          

         // afterAopTest_1 호출 대상 메소드 인자 갯수
         loclog.info( "After ---> Target Method Param len : {}", args.length );          
     }


     @AfterReturning( value="execution(* com.foxy.wany..*Test.Wany*(..) )" , returning="returnData" 
     public void afterAopTest_1( JoinPoint jp , ResponseEntity  returnData )
     {
          Object args[] = jp.getArgs();
          
         // afterAopTest_1 호출 대상 메소드
         loclog.info( "AfterReturn---> Target Method : {}", jp.getSignature().getName() );          

         // afterAopTest_1 호출 대상 메소드 인자 갯수
         loclog.info( "AfterReturn ---> Target Method Param len : {}", args.length );       

         // afterAopTest_1 호출 대상 메소드의 리턴 데이터
         loclog.info( "AfterReturn returning ---> {}", returnData );         
     }

 }

 

[ RESULT ]

 CTRL Method ---> aopTestCtrl_1
 AfterReturn ---> Target Method : aopTestCtrl_1
 AfterReturn ---> Target Method param len : 2
 AfterReturn returning ---> <200 OK OK, AOP-TEST_PART_1, []>
 After ---> Target Method : aopTestCtrl_1
 After ---> Target Method param len : 2

위와 같은 결과가 나옵니다. 

@After 특성상 대상 메소드 호출 완료 후 AOP 가 호출되는 것을 확인할 수 있으며, @After 과 @AfterReturning 의 차이

점은 @AfterReturning 은 대상 메소드의 리턴값을 받을 수 있는 것입니다. 

 

단 주의할 이 2가지 정도 있습니다.

1. @AfterReturning 내 returning 에 선언된 'returnData' 명칭이 afterAopTest_1 메소드 내 parameter 명이

   'returnData'과 동일 해야 합니다. 

2. 대상 메소드 리턴 타입과 afterAopTest_1 메소드 내 parameter 타입이 동일해야 합니다. 

 

만약 위 1,2 가 수락되지 않을 경우, compile 오류 또는 @AfterReturning 이 호출 되지 않습니다. 

 

 

[ @Around ]

 @Component
 @Aspect
 public class TestAspectOrder_1
 {
     // slf4j Logger 사용
     private static final Logger loclog = LoggerFactory.getLogger( TestAspectOrder_1.class );


     @Around( "execution(* com.foxy.wany..*Test.Wany*(..) )" 
     public Object aroundAopTest_1( ProceedingJoinPoint pjp )
     {
          Object args[] = jp.getArgs();
          
         // afterAopTest_1 호출 전 
         loclog.info( "Around before ---> Target Method : {}", pjp.getSignature().getName() );          

         // afterAopTest_1 호출 전
         loclog.info( "Around before ---> Target Method Param len : {}", args.length );          

         // afterAopTest_1 수행
         Object rtnObj = pjp.proceed();

         // afterAopTest_1 호출 후 리턴값
         loclog.info( "Around after ---> rtnObj  : {}", rtnObj );          

         return rtnObj;
     }
 }

 

[ RESULT ]

 Around before ---> Target Method : aopTestCtrl_1
 Around before ---> Target Method param len : 2

 CTRL Method ---> aopTestCtrl_1

 Around after ---> <200 OK OK, AOP-TEST_PART_1, []>

위와 같은 결과가 나옵니다.

@Around 의 경우는 pjp.proceed();  호출 전/후 로 나눌 수 있습니다. 

pjp.proceed(); 호출 전에 @Around 내 기능을 수행하고, proceed(); 호출 시점에서 대상 메소드 로직이 수행됩니다. 

pjp.proceed(); 호출 후 다시 @Around 내 기능이 수행되는 것입니다. 

 

즉, @Around 에서 proceed() 를 호출 하지 않을 경우는 대상 메소드를 호출하지 않습니다. 

때문에 반드시 proceed() 를 선언해 주어야 합니다. 

 

또한 @Around 선언된 메소드는 리턴 타입을 가져야 할 수도 있습니다. 

즉, @Around 대상 메소드가 리턴 타입이 존재한다면, @Around 내 에서도 반드시 리턴 타입을 가져야 합니다. 

 Object rtnObj = pjp.proceed();
 . . . . 중략 . . . .
 return rtnObj;

위 처럼 return rtnObj; 을 선언해 주어야, @Around 대상 메소드의 리턴값이 정상적으로 반환됩니다. 

만약 return 해주지 않는다면, 대상 메소드의 리턴값은 처리될 수 없습니다. 

 

 

 

'Framework > SpringBoot' 카테고리의 다른 글

[ 2 ] AOP Pointcut 표현방법  (0) 2021.04.30
[ 1 ] AOP 는 무엇인가?  (0) 2021.04.22

앞서 AOP 란 무엇인가? 에 대해 간략하게 정리해보았다. 

 

그럼 AOP 를 사용하기 위해 표현은 어떻게 하는지 알아보자.

앞의 이야기를 종합해보면 Pointcut 은 Advice 가 적용될 비지니스 로직 또는 메소드를 지정한다고

생각할 수 있다. 

 

즉 지정 경로에 있는 지정 Class 내 모든 메소드 또는 지정 경로의 모든 메소드에 Advice 를

적용할 수 있게 하는 필터링이다.

 

포인트컷을 사용하는 지시자는 3가리로 나눌 수 있다. 

1. execution : 페키지 경로, 클래스명을 이용하여 Advice 수행 메서드를 지정할 수 있다. 

2. within     : 지정한 경로(package) 하위 모든 메서드를 지정한다. 

3. bean      : SpringMVC 2.5 버전부터 사용가능하며, Bean 이름을 이용하여 메서드를 지정한다. 

 

 

[ execution 표현식 ]

: 개인적으로 가장 확장성 있고, 디테일하게 포인트컷을 만들 수 있는 방법이라고 생각된다. 

  표현식은 아래 A ~ D 까지의 순서를 따라가면서 설정한다.

( 표현식 내 * : 모든 값 표현 , .. : 하위경로 또는 복수개 를 의미 )

 

A) 수식어 선언

- 메서드의 public, protected 등을 선언

  ( 생략가능. - 개인적으론 그냥 생략해서 사용함 )

 

B) 리턴타입 선언

- 메서드의 리턴타입 ( * , void , !void )

   * : 모든 리턴타입에 적용

  void : 리턴 타입이 void 인 메서드 적용

  !void : 리턴 타입이 void 가 아닌 메서드 적용

 

C_1) package 선언

- Advice 가 적용될 package 에 지정된 클래스 또는 클래스명 패턴을 명시한다.

Ex_1) com.foxy.wany.test : 경로를 구체적으로 명시.

Ex_2) com.foxy.wany..    : com.foxy.wany 이하 하위 경로

 

C_2) class 선언

- Advice가 적용될 메서드를 지정한다. 단, 위 C_1 에 선언된 경로 내에서의 메서드이다.

EX_1) AopTest   : 클래스 명만 명시. 지정된 Class 만 Advice 대상

EX_2) *Test       : Test 로 끝나는 클래스들이 Advice 대상 

EX_3) Aop*       : Aop로 시작하는 클래스들이 Advice 대상

EX_4) AopTest+ : AopTest 클래스로부터 파생된 모든 자식 클래스가 대상 인터페이스

                       뒤에 + 가 붙으면, 인터페이스를 구현한 모든 클래스가 대상

 

D) 메서드 parameter 명시

- Advice 가 적용될 메서드 parameter 갯수를 지정한다.

  단, 위 C_1 & C_2 조건에 부합되어야 한다.

  ( 전부를 표현해도 무관하다. )

EX_1) *(..)                   :  모든 메서드가 대상

EX_2) *(*)                   :  매개변수가 1개인 모든 메서드 대상.

EX_3) *(*, *)                :  매개변수가 2개인 모든 메서드 대상.

EX_4) *(String, ..)          :  첫번째 매개변수 타입은 String 이며, 1개 이상의 매개변수를 가지는 메서드 대상

EX_5) WanyMethod*(..)  : 메서드명이 WanyMethod 로 시작하는 모든 메서드가 대상

EX_5) *Method(..)         : 메서드명이 Method 로 끝내는 모든 메서드 대상

 

위 A ~ D 까지를 종합하여 Spring에서 Before 단계의 execution 예제이다.


@Before( "execution(* com.foxy.wany..*Test.Wany*(..) )" )
public void beforeAOP( JoinPoint jp ) { ..... }

--> 위 Pointcut 내용은 다음과 같다.
      *                      : public , protected .. 무관
      com.foxy.wany..  : com.foxy.wany 패키지 이하 모든 경로 에서
      *Test                 : Test 로 끝나는 클래스 내에 선언된 메서드들 중
      .Wany*(..)           : Wany 로 시작하는 모든 메서드(매개변수 무관) 들은 beforeAOP 의 대상이 된다.  

 

 

[ within 표현식 ]

- 특정 타입 (Interface , Class) 에 속하는 메서드 대상.

EX_1) com.foxy.wany.*  : com.kcp.wany 내 모든 메소드 호출

EX_2) com.foxy.wany..* : com.kcp.wany 패키지 & 하위 패키지 내 모든 메소드

 


@Around( "within(com.foxy.wany..*)" )
public Object withinTest( ProceedingJoinPoint proceedJP )

    .... 
}

 

[ BEAN 표현식 ]

- Spring Bean 에 등록된 Bean ID 를 패턴으로 설정


@After( "bean(ktwAopTest)"
public void afterAopTest( JoinPoint jp )
{
    .... 수행 .... 
}


@Component
public TestAopClass
{
    @Bean( name = "ktwAopTest" )
    public void aopBeanMethod()
    {
        . . . . 
    }
}

 

다음은 Spring AOP 의 간단한 예제와 활용 ( 예외 처리, Aop 수행 순서 적용 등.. ) 에 대하여 

설명하겠다.

'Framework > SpringBoot' 카테고리의 다른 글

[ 3 ] AOP 사용 예제  (0) 2021.06.14
[ 1 ] AOP 는 무엇인가?  (0) 2021.04.22

AOP ( Aspect Oriented Programming ): 관점 지향 프로그래밍이다. 

관점... 뭔가 어렵다. 

개인적으로 이해하기 쉽게 얘기한다면, 바라보는 시점 또는 관심사를 시작으로 한 프로그래밍 이란 의미이다.

 

예를 한번 들어보자. 

A 프로그램은 1 ~ 100까지 더하는 프로그램이다.

B 프로그램은 101 ~ 199까지 더하는 프로그램,

C는 200 ~ 299까지 더하는 총 3개의 프로그램이 있다고 생각해보자.

 

그런데 개발자는 위 A, B, C 프로그램이 수행하는 도중 덧셈 결과가 3의 배수일 경우는

중간 결괏값을 출력하고 싶다. 

 

관점을 추가해보자. 위 A, B, C 프로그램 내  '3의 배수일 경우 중간 결괏값 출력' 관점을 적용시키고 싶다. 

주 목표는 A, B, C 프로그램의 결과값을 얻는 것이고, 관점( 덧셈 결과가 3의 배수일 경우 중간 결과값 출력 )

은 주 목표에 영향을 끼치지 않은 상태에서 원하는 중간값을 얻는 것이다. 

 

아래 그림에서 주황색 화살표(Module)를 A, B, C이라고 치면, 

중간에 Cross cutting concern 은 중간 출력 값 처리 부분이다. 

 

( AOP 횡단 처리 )

Spring AOP 의 구현 방식은 Proxy 를 이용하여 구현된다. 

즉, 외부에서 AOP 에 선언된 객체를 호출하게 되면, 객체를 감싸고 있는 Proxy를 통해서 호출되는 방식이다. 

 Caller --> Proxy (AOP) --> 지정된 객체

1. Caller 가 Proxy 에 요청해서 횡단 관심사(AOP) 기능을 수행

2. Proxy 가 지정된 객체로 가서 객체의 기능을 실행

3. Proxy 설정에 따라 다시 Proxy 로 돌아와서 기능을 실행

 

 

[ AOP 용어 설명 ]

Aspect AOP의 추상적 명칭. ( 말그대로 관점 )
Advice Proxy 내 실제로 기능을 구현한 객체
Join Point Advice를 적용할 위치를 의미

예를 들어 특정 Package 내 ...Proc로 끝나는 클래스의 모든 메소드에
Advice를 적용한다면, ....Proc로 끝나는 클래스의 모든 메소느는 Join Point 가 된다
Pointcut Join Point를 선별 또는 기능을 정의한 모듈의 의미
또한 서비스 성격에 따라 어떤 Join point를 사용할지 결정
Target 실제 비지니스 로직을 수행하는 객체. Advice를 적용할 대상을 의미
Proxy  어드바이스가 적용되었을 떄 생성되는 객체

 

[ AOP 종류 (Spring 표기 기반) ]

@Before Target 메소드 실행 전 Advice 실행
@After Target 메소드 실행 시 Exception 이 발생해도 Advice 실행
@AfterReturning Target 메소드 정상 실행 후 Advice 실행
@AfterThrowing Target 메소드 실행 중 Exception 발생 시 Advice 실행
@Around Target 메소드 실행 전/후 또는 Exception 발생 시 Advice 실행

위 내용을 간단한 예시로 정리해보자


@Before( pointcut )
public void beforeAopTest( JoinPoint jp )
{
    .... Advice ....
}

 

 

다음에는 pointcut 선언 방법과 간단한 예제 글을 올리도록 하겠습니다. 

 

'Framework > SpringBoot' 카테고리의 다른 글

[ 3 ] AOP 사용 예제  (0) 2021.06.14
[ 2 ] AOP Pointcut 표현방법  (0) 2021.04.30

+ Recent posts