스프링

 
Aspect는 또 어떤 일들을 수행할 수 있을까?

특정 메소드(ex. 객체 생성 과정 추적) 호출을 로깅할 경우 aspect가 도움이 될 수 있다. 기존 방법대로라면 log() 메소드를 만들어 놓은 후, 자바 소스에서 로깅을 원하는 메소드를 찾아 log()를 호출하는 형태를 취해야할 것이다. 여기서 AOP를 사용하면 원본 자바 코드를 수정할 필요 없이 원하는 위치에서 원하는 로깅을 수행할 수 있다. 이런 작업 모두는 aspect라는 외부 모듈에 의해 수행된다.

또 다른 예로 예외 처리가 있다. Aspect를 이용해 여러 클래스들의 산재된 메소드들에 영향을 주는 catch() 조항(clause)을 정의해 어플리케이션 전체에 걸친 지속적이고 일관적으로 예외를 처리할 수 있다.

 

출처  : http://www.javajigi.net/display/OSS/Aspect-Oriented+Programming+in+Java

 

 AOP 개념

몇몇 중심적인 AOP개념을 명시함으로써 시작해보자. 이 개념들은 Spring에 종속적인 개념이 아니다. 운 나쁘게도 AOP전문용어는 특히 직관적이지 않다. 어쨌든 Spring이 그 자신의 전문용어를 사용했다면 좀더 혼란스러울것이다.

  • Aspect: 다중 객체에 영향을 주는 concern의 모듈화. 트랜잭션 관리는 J2EE애플리케이션의 crosscutting concern의 좋은 예제이다. Spring AOP에서, aspect는 정규 클래스나 @Aspect 어노테이션(@Aspect 스타일)으로 주석처리된 정규 클래스를 사용하여 구현된다.

 

  • Joinpoint: 메소드 수행이나 예외를 다루는 것과 같은 프로그램의 수행기간 동안의 point. Spring AOP에서, joinpoint는 언제나 메소드 호출을 나타낸다. Join point정보는 org.aspectj.lang.JoinPoint 타입의 파라미터를 선언하여 advice에서 사용가능하다.

 

  • Advice: 특정 joinpoint에서 aspect에 의해 획득되는 액션. advice의 다른 타입은 "around," "before" 과 "after" advice를 포함한다. advice 타입은 밑에서 언급된다. Spring을 포함한 많은 AOP프레임워크는 인터셉터로 advice를 모델화하고 joinpoint "주위(around)"로 인터셉터의 묶음(chain)을 유지한다.

 

  • Pointcut: join point에 일치하는 속성. advice는 pointcut표현과 관련있고 pointcut에 일치하는 join point에서 수행된다(예를 들어, 어떤 이름을 가진 메소드의 수행). pointcut표현에 의해 일치하는 join point의 개념은 AOP에 집중된다. Spring은 디폴트로 AspectJ pointcut언어를 사용한다.

 

  • Introduction: (중간(inter)-타입 선언으로 알려진) 타입에서 추가적인 메소드나 필드의 선언. Spring은 프록시화된 객체를 위해 새로운 인터페이스(와 관련 구현물)를 소개한다. 예를 들어, 간단한 캐시를 위해, IsModified 인터페이스를 구현하는 bean을 만들기 위한 소개(introduction)를 사용한다.

 

  • 대상 객체: 객체는 하나이상의 aspect에 의해 충고된다. 또한 advised 객체를 참조한다. Spring AOP가 런타임 프록시를 사용하여 구현되기 때문에, 이 객체는 언제나 프록시화된 객체가 될것이다.

 

  • AOP 프록시: aspect 규칙(advise메소드수행과 기타등등)을 구현하기 위하여 AOP프레임워크에 의해 생성되는 객체. Spring에서. AOP프록시는 JDK 동적 프록시나 CGLIB 프록시가 될것이다. 노트 : 프록시 생성은 스키마-기반과 Spring 2.0에서 소개된 aspect선언의 @AspectJ스타일의 사용자에게 명백하다.

 

  • Weaving: 다른 애플리케이션 타입이나 advised객체를 생성하기 위한 객체를 가지는 aspect 연결. 이것은 컴파일 시점(예를 들어, AspectJ 컴파일러를 사용하여), 로그시점 또는 런타임에 수행될수 있다. 다른 Java AOP프레임워크처럼 Spring은 런타임시 직조(weaving)를 수행한다.

Advice 타입

  • Before advice: joinpoint전에 수행되는 advice. 하지만 joinpoint를 위한 수행 흐름 처리(execution flow proceeding)를 막기위한 능력(만약 예외를 던지지 않는다면)을 가지지는 않는다.

 

  • After returning advice: joinpoint이 일반적으로 예를 들어 메소드가 예외를 던지는것없이 반환된다면 완성된 후에 수행되는 advice.

 

  • After throwing advice: 메소드가 예외를 던져서 빠져나갈때 수행되는 advice

 

  • After (finally) advice: join point를 빠져나가는(정상적이거나 예외적인 반환) 방법에 상관없이 수행되는 advice.

 

  • Around advice: 메소드 호출과 같은 joinpoint주위(surround)의 advice. 이것은 가장 강력한 종류의 advice이다. Around advice는 메소드 호출 전후에 사용자 정의 행위를 수행할수 있다. 그것들은 joinpoint를 처리하거나 자기 자신의 반환값을 반환함으로써 짧게 수행하거나 예외를 던지는 것인지에 대해 책임을 진다.

 

Around advice는 가장 일반적인 종류의 advice이다. Nanning Aspects와 같은 대부분의 인터셉션-기반의 AOP프레임워크는 오직 around advice만을 제공한다.

AspectJ처럼 Spring이 advice타입의 모든 범위를 제공하기 때문에 우리는 요구되는 행위를 구현할수 있는 최소한의 강력한 advice타입을 사용하길 권한다. 예를 들어 당신이 메소드의 값을 반환하는 캐시만을 수정할 필요가 있다면 around advice가 같은것을 수행할수 있다고하더라도 around advice보다 advice를 반환한 후에 구현하는게 더 좋다. 대부분 특정 advice타입을 사용하는것은 잠재적으로 적은 에러를 가지는 간단한 프로그래밍 모델을 제공한다. 예를 들어 당신은 around advice를 위해 사용되는 JoinPointproceed()메소드를 호출할 필요가 없고 나아가 그것을 호출하는것을 실패할수도 있다.

Spring 2.0에서, 모든 advice파라미터는 정적으로 타입화된다. 그래서 객체 배열보다는 적절한 타입(예를 들면, 메소드 수행의 반환값의 타입)의 advice파라미터를 가지고 작업한다.

pointcut에 의해 일치하는 join point의 개념은 오직 인터셉션만을 제공하는 예전 기술과 구분되는 AOP의 핵심이다. pointcut은 OO구조의 단독으로 대상화되도록 해준다. 예를 들어, 선언적인 트랜잭션 관리를 제공하는 around advice는 다중 객체에 걸쳐있는 메소드에 적용될수 있다(서비스 레이어내 모든 비니지스 작동과 같은).


 

그럼 우선 포인트 컷을 정의하는 방법부터 보자.

포인트컷(Pointcut) 정의하기

포인트컷은 결합점(Join points)을 지정하여 충고(Advice)가 언제 실행될지를 지정하는데 사용된다. Spring AOP는 Spring 빈에 대한 메소드 실행 결합점만을 지원하므로, Spring에서 포인트컷은 빈의 메소드 실행점을 지정하는 것으로 생각할 수 있다.

다음 예제는 egovframework.rte.fdl.aop.sample 패키지 하위의 Sample 명으로 끝나는 클래스의 모든 메소드 수행과 일치할 'targetMethod' 라는 이름의 pointcut을 정의한다.

@Aspect
public class AspectUsingAnnotation {
   ...
   @Pointcut("execution(public * egovframework.rte.fdl.aop.sample.*Sample.*(..))")
   public void targetMethod() {
       // pointcut annotation 값을 참조하기 위한 dummy method
   }
   ...
}

포인트컷 지정자(Designators)

Spring에서 포인트컷 표현식에 사용될 수 있는 지정자는 다음과 같다. 포인트컷은 모두 public 메소드를 대상으로 한다.

  • execution: 메소드 실행 결합점(join points)과 일치시키는데 사용된다.

  • within: 특정 타입에 속하는 결합점을 정의한다.

  • this: 빈 참조가 주어진 타입의 인스턴스를 갖는 결합점을 정의한다.

  • target: 대상 객체가 주어진 타입을 갖는 결합점을 정의한다.

  • args: 인자가 주어진 타입의 인스턴스인 결합점을 정의한다.

  • @target: 수행중인 객체의 클래스가 주어진 타입의 어노테이션을 갖는 결합점을 정의한다.

  • @args: 전달된 인자의 런타입 타입이 주어진 타입의 어노테이션을 갖는 결합점을 정의한다.

  • @within: 주어진 어노테이션을 갖는 타입 내 결합점을 정의한다.

  • @annotation: 결합점의 대상 객체가 주어진 어노테이션을 갖는 결합점을 정의한다.

포인트컷 표현식 조합하기

포인트컷 표현식은 '&&', '||' 그리고 '!' 를 사용하여 조합할 수 있다.

  @Pointcut("execution(public * *(..))")
   private void anyPublicOperation() {}

   @Pointcut("within(com.xyz.someapp.trading..*)")
   private void inTrading() {}

   @Pointcut("anyPublicOperation() && inTrading()")
   private void tradingOperation() {}

포인트컷 정의 예제

Spring AOP에서 자주 사용되는 포인트컷 표현식의 예를 살펴본다.

Pointcut

선택된 Joinpoints

execution(public * *(..))  public 메소드 실행

execution(* set*(..)) 이름이 set으로 시작하는 모든 메소드명 실행

execution(* set*(..)) 이름이 set으로 시작하는 모든 메소드명 실행

execution(* com.xyz.service.AccountService.*(..))  AccountService 인터페이스의 모든 메소드 실행

execution(* com.xyz.service.*.*(..))  service 패키지의 모든 메소드 실행

execution(* com.xyz.service..*.*(..))  service 패키지와 하위 패키지의 모든 메소드 실행

within(com.xyz.service.*)  service 패키지 내의 모든 결합점 (클래스 포함)

within(com.xyz.service..*) service 패키지 및 하위 패키지의 모든 결합점 (클래스 포함)

this(com.xyz.service.AccountService)   AccountService 인터페이스를 구현하는 프록시 개체의 모든 결합점

target(com.xyz.service.AccountService)   AccountService 인터페이스를 구현하는 대상 객체의 모든 결합점

args(java.io.Serializable)    하나의 파라미터를 갖고 전달된 인자가 Serializable인 모든 결합점

@target(org.springframework.transaction.annotation.Transactional)    대상 객체가 @Transactional 어노테이션을 갖는 모든 결합점

@within(org.springframework.transaction.annotation.Transactional)   대상 객체의 선언 타입이 @Transactional 어노테이션을 갖는 모든 결합점

@annotation(org.springframework.transaction.annotation.Transactional)  실행 메소드가 @Transactional 어노테이션을 갖는 모든 결합점

@args(com.xyz.security.Classified)  단일 파라미터를 받고, 전달된 인자 타입이 @Classified 어노테이션을 갖는 모든 결합점

bean(accountRepository)   “accountRepository” 빈

!bean(accountRepository)   “accountRepository” 빈을 제외한 모든 빈

bean(*)  모든 빈

bean(account*)  이름이 'account'로 시작되는 모든 빈

bean(*Repository)  이름이 “Repository”로 끝나는 모든 빈

bean(accounting/*)  이름이 “accounting/“로 시작하는 모든 빈

bean(*dataSource) || bean(*DataSource)  이름이 “dataSource” 나 “DataSource” 으로 끝나는 모든 빈


 

콘솔 로그 출력을 보면 충고(Advice)가 적용되는 순서는 다음과 같다.

  • @Before

  • @Around (대상 메소드 수행 전)

  • 대상 메소드

  • @Around (대상 메소드 수행 후)

  • @After(finally)

  • @AfterReturning




 

예제는 class에 annotation을 이용한 logging 전략과,

 

execution을 이용한 logging 전략을 이용한다,


 

package com.aspect;

import org.apache.commons.lang.builder.ToStringBuilder;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import com.entity.UserVo;


//spring boot AOP example
// @author 정명성
// create date : 2015. 12. 29.
// com.aspect.AdviceLogging.java
//
@Aspect
@Component
public class AdviceLogging {
    
    Logger logger = LoggerFactory.getLogger(getClass());
    
    @Before("execution(* com..*Controller.*(..))" 
                + "execution(* com..*Service.*(..))")
    public void loggingAdvice(JoinPoint joinPoint) {
        
        logger.info("method path : " + joinPoint.getSignature());
        
       //
      //코드 변경 
     // 참고사이트 : http://stackoverflow.com/questions/27659523/retrieve-parameter-value-from-proceedingjoinpoint
      // method parameter 가져오기 부분 추가
       
        Object params[] = joinPoint.getArgs();
        for(Object param : params) {
            if(param instanceof UserVo) {
                logger.info(ToStringBuilder.reflectionToString((UserVo)param));
            }
        }
        
        
   //
        //    AOP Logging Result
         //   2016-02-22:13:28:06.361 INFO  7727 --- [main] com.aspect.AdviceLogging.loggingAdvice(AdviceLogging.java:31) : method path : User com.user.controller.UserRestController.createUser(UserVo) 
         //   2016-02-22:13:28:06.371 INFO  7737 --- [main] com.aspect.AdviceLogging.loggingAdvice(AdviceLogging.java:36) : com.entity.UserVo@42ac84a9[name=홍길동,email=test@test.com,gender=male] 
       //
    }
    
    
//
// annotation을 이용한 Logging
 // @param joinPoint
  //
    @Before("@within(Loggings)")
    public void loggingAdvice2(JoinPoint joinPoint) {
    
        logger.info("annotation method path : " + joinPoint.getSignature());
        
        Object params[] = joinPoint.getArgs();
        for(Object param : params) {
            if(param instanceof UserVo) {
                logger.info(ToStringBuilder.reflectionToString((UserVo)param));
            }
        }

    }

}
 

 

annotation class

 

package com.aspect;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface Loggings {
    String value() default "";
}
 

 

예제 프로젝트 :

https://github.com/audtjddld/angularjs-springboot-jpa-example/tree/changeProcess/demo2/src/main/java/com/aspect



 

출처 :

http://ldg.pe.kr/framework_reference/spring/ver2.x/html/aop.html

http://www.egovframe.org/wiki/doku.php?id=egovframework:rte:fdl:aop:aspectj

출처 : 코드로 배우는 스프링 웹 프로젝트


 

* AOP 와 관련된 용어

스프링의 AOP 지원은 개발자에게 다음과 같이 말한다.  개발의 핵심적인 비즈니스 로직을 개발하는 데에만 집중하고, 나머지 부가적인 기능은

설정을 통해서 조정하라.

 

AOP 를 처음 접할 때 가지는 의문은 어떻게 기존의 코드를 수정하지 않고 코드의 앞이나 뒤에서 필요한 기능이 동작하도록 작성할 수 있을 가 이다.

이에 대한 해답의 실마리는 실제 개발자가 작성한 코드와 컴파일되거나 실행 시에 동작하는 코드가 다르다는 데 있다.

 

이에 대한 구현 방식은 소위 프록시(proxy) 패턴 이라는 방식을 통해서 구현한다.

외분에서 특정한 객체(target) 를 호출하면, 실제 객체를 감싸고 있는 바깥쪽(proxy) 를 통해서 호출이 전달된다. Poxy 객체는 AOP 의 기능이 적용된

상태에서 호출을 받아 사용되고, 실제 객체와 동일한 타입을 자동으로 생성할 수 있기 때문에 외부에서는 실제 객체와 동일한 타입으로 호출할 

수 있다.

 

Aspect  -  공통 관심사에 대하 추상적인 명칭, 예를 들어 로깅이나 보안, 트랜잭션과 같은 기능 자체에 대한 용어

Advice - 실제로 기능을 구현한 객체

Join Points  - 공통 관심사를 적용할 수 있는 대상. Spring AOP 에서는 각 객체의 메소드가 이에 해당

Pointcut  - 여러 메소드 중 실제 Advice 가 적용될 대상 메소드

target  - 대상 메소드를 가지는 객체

Proxy - Advice 가 적용되었을 때 만들어지는 객체

Introduction - target 에 없는 새로운 메소드나 인스턴스 변수를 추가하는 기능

Weaving - Advice 와 traget 이 결합되어서 프록시 객체를 만드는 과정

 

Advice : 실제 적용시키고 싶은 코드 자체, 개발자가 만드는 것은 Aspect 가 아닌 클래스를 제작하고 @Advice를 적용하는 것임. 예를 

들어 로그 출력 기능, 파라미터 체크 기능 자체는 Aspect 라는 용어로 부르지만, 실제 구현 시에는 Advice 를  제작한다고 표현.

 

target : 실제 비즈니스 로직을 수행하는 객체를 의미. 용어 그대로 Aspect 를 적용해야 하는 대상 객체를 의미함.

 

Join points :  작성되 Advice 가 활약할 수 있는 위치를 의미. 예를 들어 BoardService 에서 등독, 수정, 삭제만을 골라서 Advice 를 적용할 수 있는데,

이때 BoardService 의 모든 메소드는 JoinPoint 가 됨.

 

PointCuts : 여러 Join Points 중에서 Advice 를 적용할 대상으로 선택하는 정보, 이를 통해서 특정 메소드는 Advice가 적용된 형태로 동작함.

 

Advice의 종류

Advice 는 실제 구현된 클래스로 생각할 수 있는데,  Advice 의 타입은 다시 아래와 같이 분류 된다.

Before Advice   : target 의 메소드 호출 전에 적용

After returning : target 의  메소드 호출 이후에 적용

After throwing : target 의 예외 발생 후 적용

After : target 의 메소드 호출 후 예외의 발생에 관계없이 적용

Around : target 의 메소드 호출 이전과 이후 모두 적용 ( 가장 광범위하게 사용됨)

 

Around  는 메소드의 호출 자체를 제어할 수 있기 때문에 가장 강력하다고 할 수 있다.

과거에는 개발 시 특정 인터페이스를 구현하는 형태로 Advice 를 작성하곤 했지만, 최근 스프링에서는 애노테이션만을 이용해도 모든 설정이

가능하다.

 

 

 

 Filter, Interceptor, AOP  의 차이 

 

자바 웹프로그래밍을 구현하다보면 공통적인 업무를 추가해야할 것들이 많다. 공통적인 업무에는 로그인처리(세션체크), pc웹과 모바일웹의 분기, 로그 확인, 페이지 인코딩 변환, 권한체크, XSS(Cross site script)방어 등이 있는데 이러한 공통업무에 관련된 코드를 모든 페이지 마다 작성 해야한다면 중복된 코드가 많아지게 되고 업무량이 상당히 증가할 것이다. 이러한 공통업무를 프로그램 흐름에서 앞, 중간, 뒤에 추가하여 자동으로 처리할 수 있는 방법이 있는데 서블릿에서 지원하는 서블릿 필터, 스프링 프레임워크를 사용하면 쓸 수 있는 인터셉터, AOP가 있다. 앞서 AOP개념을 정리할 때 언급한 것처럼 개발자는 좀더 핵심로직에 집중하고, 부가로직으로부터 자유로워지게 도와주는 역할을 한다.

그렇다면 요청에 흐름에 따라 필터, 인터셉터, AOP의 차이점에 대해 알아보자.

1. Filter, Interceptor, AOP의 흐름

Filter, Interceptor, AOP의 실행순서


사진 및 내용 출처: 심해펭귄의 심해도서관, [Spring] Interceptor, filter, AOP의 차이

위 그림을 보면 필터와 인터셉터의 차이를 구분할 수가 있다. 작업처리를 위해 컨트롤러가 실행되기 전에 사용한다는 점에서 별반 차이가 없어보이지만 흐름을 보면 명확히 호출되는 시점이 다르다. 실행되는 메서드를 기준으로 다시 설명해보자면, 서버를 실행시켜 서블릿이 올라오는 동안 init이 실행되고, 그후 doFilter가 실행된다. 그후 컨트롤러에 들어가기 전에 preHandler가 실행된다. AOP가 실행된 후에 컨트롤러에서 나와 postHandler, after Completion, doFilter순서로대로 진행되고 destroy가 실행된다.

2. Filter, Interceptor, AOP의 개념

01) Filter(필터)

말그대로 요청과 응답을 거른뒤 정제하는 역할을 한다.

서블릿 필터는 DispatcherServlet 이전에 실행이 되는데 필터가 동작하도록 지정된 자원의 앞단에서 요청내용을 변경하거나, 여러가지 체크를 수행할 수 있다. 또한 자원의 처리가 끝난 후 응답내용에 대해서도 변경하는 처리를 할 수가 있다.
필터는 web.xml에 등록하는데 대표적으로 인코딩 변환, 로그인 여부확인, 권한체크, XSS방어 등의 요청에 대한 처리로 사용된다.

<!-- 한글 처리를 위한 인코딩 필터 -->
<filter>
    <filter-name>encoding</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>encoding</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

등록한 encoding는 이름은 encoding이고, 값은 UTF-8인 파라미터를 정의하고 있다. 필터를 /* 에 맵핑하여 필터가 servlet, jsp뿐만 아니라 이미지와 같은 모든 자원의 요청에도 호출 된다는 것을 의미한다.

필터의 실행메서드

  • init() - 필터 인스턴스 초기화
  • doFilter() - 전/후 처리
  • destroy() - 필터 인스턴스 종료

02) Interceptor(인터셉터)

요청을 가로챈다(작업 전/후)

앞서 말했던 것처럼 필터와 인터셉터는 호출 시점이 다르다. 필터는 스프링과 무관하게 지정된 자원에 대해 동작한다. 스프링은 DistpatcherServlet으로부터 시작되므로 필터는 스프링 컨텍스트 외부에 존재하게 된다. 하지만 인터셉터는 스프링의 DistpatcherServlet이 컨트롤러를 호출하기 전, 후로 끼어들기 때문에 스프링 컨텍스트 내부에 존재하게된다. 그리고 스프링 내의 모든 객체(bean) 접근이 가능하다. 인터셉터는 여러 개를 사용할 수 있고 로그인 체크, 권한체크, 프로그램 실행시간 계산작업 로그확인, 업로드 파일처리등에 사용된다.

인터셉터의 실행메서드

  • preHandler() - 컨트롤러 메서드가 실행되기 전
  • postHanler() - 컨트롤러 메서드 실행직 후 view페이지 렌더링 되기 전
  • afterCompletion() - view페이지가 렌더링 되고 난 후

03) AOP

관점 지향 프로그래밍

출처 : http://doublesprogramming.tistory.com/133

 

 

 Interceptor, filter, AOP의 차이 - 2

DEV/spring 2015.11.11 14:27

Trackback : 0 Comment : 0

Interceptor, filter, AOP의 차이

[Spring] Interceptor, filter, AOP의 차이

 

스프링에서 interceptor, filter, aop 셋 다 무슨 행동을 하기 전에 먼저 실행하거나, 실행 한 후에 추가적인 행동을 할 때 사용 되는 기능들입니다. 기능적으로는 비슷하지만 내부 구현적으로는 큰 차이가 있습니다. 한참 구글링을 해서 얻어 본 결과를 정리해 봅니다.

 

표를 설명해보자면, interceptor와 filter는 서블릿 단위에서 실행됩니다. 반면에 AOP는 메소드의 앞에 Proxy 패턴을 이용해서 실행됩니다.

 

그래서 실행순서에서도 차이가 생깁니다. filter가 가장 겉에 있고, 그 안에 interceptor 그리고 그 안에 aop가 들어있는 식의 구조입니다.

request가 filter를 거쳐 interceptor 쪽으로 들어가고 aop를 거쳐 다시 나오면서 interceptor와 filter를 거치는 식 입니다.

 

실행되는 메소드를 기준으로 설명하면, 서버를 실행시켜 서블릿이 올라오는 동안에 init이 실행되고, 그 후 dofilter가 실행됩니다.

그 후 컨트롤러에 들어가기 전에 preHandler가 실행되고, aop가 실행된 후에 컨트롤러에서 나와 postHandler, after Completion, dofilter 순서대로 진행되고, 서블릿 종료시 destory가 실행 될 것입니다.



출처: http://dev-eido.tistory.com/entry/Interceptor-filter-AOP의-차이 [하면 되긴 하냐....]

 

 

36. 실질적으로 AOP를 적용해 보자.  AOP  로그 적용하기  

 

위 내용을 보면 상단히 어렵다.  무슨 내용인지 모르겠다.   단어는 알겠는데 용어자체가 이것이 이 AOP 에서 무엇을 행하는 지 이해하는 것 자체가 어렵다. 그냥   AOP 를 개발한 개발자가 이 부분이 이런 역할 하는 것이 advice  ,Join points  .. 기타 등으로 해서 개발한 API 라 생각하자.

실제로 많이 적용하고 많이 사용하지 않는 이상 잊어버리기 싶다.  

그냥 간단하게 생각 하자.  먹고 자고 싸고 하는 것은 누구나 한다.  학교나 직장에  지하철이나 버스로이동 할때 교통카드로 체크해서  가는 것은 같다. 

이런 공통적인 부분을 간단하게 처리 해 놓자 라는 것이다.  그래서 공통부분은 AOP에게 맞기고 개발자는 핵심적인 비즈니스 로직을 개발하는 데에만 집중하자는 것이다.

필터, 인터셉터, AOP 에 대한 차이점을 적어 놓았는데  AOP 를 이해하려면 필터와 유사, 인터셉터와 유사    한 것들 이라고 생각하면 이해가 쉬운것 같다.

리펙토링을 하면 보통 공통 부분을 분류해서 코드를 간편화하고 깔끔하게 처리한다.  AOP 를 이렇게 생각하자.  어디까지나 필자 생각이다.

 

프로젝트를 보면 수 많은 클래스들이 존재하고  각 클래스마다  여러  메소들이 존재한다.

그리고 각 메소들 마다 로그를 만든다. 왜 냐하면 개발시에 어느 메소드에서 버그가 일어났지 확인하기 위해 서이다. 

이런 공통 부분을 처리 해보자. 

다음 필자가 만든 소스의 SampleController 보면  각 메소들 마다  로그를 출력하는 코드를 넣은 것을 볼 수 있다.  AOP 처리를 하자.

 

package net.macaronics.web.controller;


import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import net.macaronics.web.domain.MemberVO;
import net.macaronics.web.domain.ProductVO;
import net.macaronics.web.service.MemberService;

@Controller
public class SampleController {

	private static final Logger logger =LoggerFactory.getLogger(SampleController.class);
	
	@Autowired
	private MemberService service;
	
	
	@RequestMapping("doA")
	public void doA()  throws Exception{
		logger.info("doA called...............");
	}
	

	@RequestMapping("doB")
	public void doB()  throws Exception{
		logger.info("doB called ...............");
	}
	
	@RequestMapping("doC")
	public String doC(@ModelAttribute("msg" ) String msg, String msg2 )  throws Exception{
		if(msg==null){
			logger.info(" msg - null");
		}else{
			logger.info(" msg -- \" \" ");
		}
		if(msg.equals("")) logger.info("msg : '''''");
		logger.info(" msg2 :  {} " , msg2);
		
		logger.info("doC called ............ msg - {}", msg);
		return "result";
	}
	
	
	@RequestMapping("/doD")
	public String doD(Model model)  throws Exception{
		logger.info("doD");
		ProductVO product=new ProductVO("Sample Product", 1000);
		
		model.addAttribute(product);
		
		return "result";
		
	}
	
	@RequestMapping("/doE")
	public String doE(ProductVO vo) throws Exception{
		return "result";	
	}
	
	@RequestMapping("/doF")
	public String doF(RedirectAttributes rttr) throws Exception{
		logger.info("doF call Redirect");
		rttr.addAttribute("msg1", " msg1 ");
		rttr.addFlashAttribute("msg2", "this is msg2");
		return "redirect:/doG";
	}
	
	@RequestMapping("/doG")
	public void doG(String msg2, HttpServletRequest request)  throws Exception{
		
		logger.info("msg1 :  {} " , request.getAttribute("msg1"));
		//logger.info("msg1 :  {} " , msg1);
		logger.info("msg2 :  {} " , msg2);
	}
	
	@RequestMapping("/doJson")
	public @ResponseBody ProductVO doJson()  throws Exception{
		ProductVO vo =new ProductVO("상품 2", 70000);
		return vo;
	}
	
	
	
	
	@RequestMapping("/doJSON")
	public @ResponseBody Map<String, Object> doJSON()  throws Exception{
		Map<String, Object> map=new HashMap<>();
		map.put("name", "홍길동");
		map.put("age", "21");
		logger.info(" doJSON ( )  -  {} " , map.toString()); 
		return map;
	}
	
	
	@RequestMapping("/testError")
	public String errorTest() throws Exception{
		logger.info(" errorTest ( )  - start " ); 
		int a=1;
		int b=a/0;
		logger.info(" errorTest ( )  -  {} " , b); 
		return "home";
	}
	
	
	@RequestMapping("/ajaxError")
	public @ResponseBody String ajaxErrorTest() throws Exception{
		logger.info(" ajaxError ( )  - start " ); 
		int a=1;
		int b=a/0;
		logger.info(" ajaxError ( )  -  {} " , b); 
		return "home";
	}
	
	@RequestMapping("/memberList")
	public String errorReadListMember() throws Exception{
		logger.info(" errorReadListMember() " ); 
		List<MemberVO> list =service.errorReadListMember();
		
		return "home";
	}
	
	
	
	
}









 

①  AOP 을 적용하기 위해서는 다음 라이브러리가 필요하다. 

1. AspectJ Weaver,  

2. AspectJ Runtime

메이븐 저장소에 가보자.  다시 말하지만  스프링 버전과 라이브러리가 맞지 않으면  에러가 발생한다.

AspectJ Weaver

https://mvnrepository.com/artifact/org.aspectj/aspectjweaver

( 1.8.10 로서  2016년도  버전이며 가장 많이 사용하는 것 같다. 그러나 이 버전으로 실행 가능한 사람은 이 버전을 사용하시고.

이 버전이 안 된다면 필자와 같은 버전 사용 하세요. )

<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.6.11</version>
</dependency>

 

 

AspectJ Runtime

<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjrt -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.6.8</version>
</dependency>

( 이것 역시 1.6.8 사용합시다.

 거의 없을 것 같지만 ,  0.1% 로 존재 한다 가정하에 , 어디까지나 1 ~ 36 단계 까지 필자의 글을 따라 했을 유저가 존재했을 경우입니다.  )

서버를 실행 해보자.  버그 없이 잘 구동 되는지 확인 해 보자.

 

② root-context.xml 과 servlet-context.xml 의 Namespace 를 체크하자.

 

 

 

③ servlet-context.xml 에 다음과 같이  추가하자.  

제일 위 상단에  넣어야 한다.   왜냐 하면은  공통 처리 부분 이니깐 다른 클래스 보다 실행 전에  처리를 위함이라 생각 해 두자.

이것도 설정을 잘못 해서 에러 나는 경우가 참 많다. 주의 하자.

    <!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->

    <!-- aop의 설정을 통한 자동적인 Proxy 객체 설정 -->
    <aop:aspectj-autoproxy />
    <aop:config /> <!--     XML 방식으로 AOP 기능을 설정할 때  사용 -->
        
    <!-- Enables the Spring MVC @Controller programming model -->
    <annotation-driven />


 

④ 그림과 같이 LogAdvice 클래스를 만들자.

 

 

package config.aop;

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 //스프링에서 관리하는 bean
@Aspect //aop bean
public class LogAdvice {

    //로그 수집 객체
    private static final Logger logger =LoggerFactory.getLogger(LogAdvice.class);
    
    
   //ProceedingJoinPoint 클래스는 around 에서 만 가능 
   // @Around("execution( *  net.macaronics.web.controller..*Controller.*(..))"
       //     + " or execution( * net.macaronics.web.persistence..*Impl.*(..))"
       //     + " or execution( * net.macaronics.web.service..*Impl.*(..))")
    public Object logPrint(ProceedingJoinPoint joinPoint) throws Throwable{
        
        long start =System.currentTimeMillis();
        Object result =joinPoint.proceed();
        
        String type =joinPoint.getSignature().getDeclaringTypeName();
        
        String name="";
        
        if(type.indexOf("Controller") > -1){
            name ="Controller \t : ";
        }else if(type.indexOf("Service") > -1){
            name ="ServiceImpl \t: ";
        }else if(type.indexOf("DAO") > -1){
            name ="persistence(DAO) \t: ";
        }
        //클래스  +  매소드 이름
        
        logger.info( "\n\n\n******* => " + name+type+"."+joinPoint.getSignature().getName() +"()");
    

        //파라미터 값
        logger.info(Arrays.toString(joinPoint.getArgs()));
        
        long end =System.currentTimeMillis();
        long time =end-start;
        logger.info(result +" 실행시간 :" + time);    
        return joinPoint.proceed();
    }
    
// ProceedingJoinPoint 클래스는 around 에서 만 가능
    @Before("execution( *  net.macaronics.web.controller..*Controller.*(..))"
            + " or execution( * net.macaronics.web.persistence..*Impl.*(..))"
            + " or execution( * net.macaronics.web.service..*Impl.*(..))")
    public void logPrintBefore(JoinPoint joinPoint) throws Throwable {

        Class clazz = joinPoint.getTarget().getClass();
        String className = joinPoint.getTarget().getClass().getSimpleName();
        String methodName = joinPoint.getSignature().getName();

        String type = joinPoint.getSignature().getDeclaringTypeName();

        String name = "";

        if (type.indexOf("Controller") > -1) {
            name = "Controller \t : ";
        } else if (type.indexOf("Service") > -1) {
            name = "ServiceImpl \t: ";
        } else if (type.indexOf("DAO") > -1) {
            name = "persistence(DAO) \t: ";
        }
        // 클래스 + 매소드 이름
        logger.info( "\n\n\n*******  Macaroncis AOP Annotation.beforeTargetMethod executed. => " );
        logger.info(" MVC 영역 - {} ,  Type - {} ", name , type);
        logger.info("클래스 명 - {}, 메서드 명 - {} ", className, methodName);
        
        // 파라미터 값
        logger.info("파라미터 값 : {}"  , Arrays.toString(joinPoint.getArgs()));
        
    }

    
}





Aspect  -  공통 관심사에 대하 추상적인 명칭, 예를 들어 로깅이나 보안, 트랜잭션과 같은 기능 자체에 대한 용어

execution(public * *(..))  public 메소드 실행

execution(* set*(..)) 이름이 set으로 시작하는 모든 메소드명 실행

execution(* set*(..)) 이름이 set으로 시작하는 모든 메소드명 실행

execution(* com.xyz.service.AccountService.*(..))  AccountService 인터페이스의 모든 메소드 실행

execution(* com.xyz.service.*.*(..))  service 패키지의 모든 메소드 실행

execution(* com.xyz.service..*.*(..))  service 패키지와 하위 패키지의 모든 메소드 실행

 

  다음 이미지 처럼 화살표가 표시되는지 확인하자. 표시가 안되면 적용이 안 된 것이다.

 

 

테스트를 진행해 보자.

필자의 소스  SampleControllerTest  실행 시켜 보자.

package net.macaronics.web.controller;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.contex 
                                     
                    				  
                    				  
                    				    
									    

 

about author

PHRASE

Level 60  라이트

죽음이란 육체로부터의 해방이다. -소크라테스

댓글 ( 5)

댓글 남기기

작성