ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring AOP
    Spring 2019. 5. 10. 18:01

     

     

    이번에 프로젝트 지원을 나오면서 JCO 이슈나 , 동적인 연동 관련된 이슈들이 나와....

     

    고민을 조금 해본 결과 AOP를 하면 어떨까 싶어 스타트했다.

     

    진심 제대로 정리된 글을 찾기도 어렵고, 삽질도 많이 해보고, 되게 간단하게 정리 가능한 것 같으면서도 어렵게 되어있어서 작업하면서 삽질했던 방향으로 일단 정리를 해보겠다. ( 추후 언제가 될지 모르지만 추가적으로 Append할 예정 )

     

    1. AOP란 

    AOP는 간단하게 말해서 쉽게 이해하려면 제 3자의 입장에서 메소드들을 총괄 하는 매니저 역할을 한다고 이해하고 있다.

    무슨뜻이냐. A Method / B Method / C Method 가 각각 있다고 예를 들고

     

    [A Class]
    
    public class A {
    
        public void a() {
          //a method를 지칭한다.
        }
    
    }
    
    [B Class]
    
    public class B {
    
        public void b() {
          //b method를 지칭한다.
        }
    
    }
    
    
    [C Class]
    
    public class C {
    
        public void c() {
          //c method를 지칭한다.
        }
    
    }

     

    위와 같이 있을때 각각 연관성 없는 A/B/C Class의 a/b/c Method를 호출 전 / 호출 후 / 성공 여부 등등 시점에 맞춰 특별한 Method를 호출 할 수 있게 해줄수 있는 기능이다.

     

    가장많이 사용하는 예시로

    a Method / b Method / c Method에 대하여 각각 Log를 찍기 번거로울때 logging 처리를 위하여 사용할 경우도 aop를 사용한다.

     

     

    간단한 예시는 위와 같고, 결론으로 들어와 삽질 부터 설명한다.

     

    2. dispatchar-context.xml

    우리 회사 사내는 dispatchar-context.xml / application-context.xml / transaction-context.xml 등 여러개의 설정들을 각각 분리해서 사용하고 있는데 aop 설정이 같이 뭉쳐져 있어 생각도 안하고 넘어간것도 있고...

     

    애초에 aop 설정관련된 내역을 설명하는 모든 블로그에서 context-component-scan에 존재하는 annotation이 같은 xml내에 존재해야지만 반영된다는 내역이 단 1도 없었따.... 그냥 설정만 한 블로그가 태반이였다고ㅗㅗㅗㅗ ( 나만 모르는걸수도 있다.. 이거 발견하고 개발자 때려쳐야하나 진심 깊은 고민했다. )

     <aop:aspectj-autoproxy />
     <bean class="a.com.test.ExecutionLoggingAdvisor" />
    
    
    <context:component-scan base-package="a.*" 
    							use-default-filters="false">
    		<context:include-filter type="annotation"
    			expression="org.springframework.stereotype.Controller" />
    </context:component-scan>

     

    무튼 개발자 분들은 저와 같이 실수하지말고, aop 설정시에 사용하려는 aspectj bean을 등록하시면, listener에서 읽는 동일한 spring xml 설정에서 context component-scan 을 따라가니 실수하지마세여.... ㅠ_ㅠ...

     

    3. PointCut

    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” 으로 끝나는 모든 빈

    PointCut은 위와같이 설정하면 되는데, 저는 쓰는 IDE가 Intellij라서 자동으로 현재 execution이 매칭이 되어있는지 확인이 가능한데.. 이클립스는 가능한지 모르겠습니다.

     

    4. @Around / @Before / @After / @AfterThrowing / @AfterReturning

     

    이제 본격적으로 삽질한 소스인데.

     

    Controller만 들어왔을때 부터 2일 삽질하곤 나머지 나올때부턴 팍팍 진도가 나갓다.

     

    1) @Around : 모든 시점에 해당 어노테이션을 가지고 있는 메소드가 호출된다. 

    2) @Before : PointCut에서 잡아놓은 method 타겟을 호출하기 이전에 호출한다. ( log로 예를들면 이제 들어간다~~~ 라고 쓰고 method 타겟 실행되고 @After 호출이 되는 것. )

    3) @After : PointCut에서 잡아놓은 method 타겟을 호출하기 이후에 호출한다

    4) @AfterThrowing / @AfterReturning : 여기서 부터 중요. 

     

    내가 제일 하고싶었던 작업이 

     

    @AfterThrowing / @AfterReturning 이놈들로 이루어져야하는데 하다보니깐

     

    우리회사 jco / 동적연동은 구현체로 이루어져야하기 때문에 return type이 void로 구성되어있더라....

     

    그래서 returning은 정확히 활용은 못하고.. 다른 방식으로 활용하기로 하였고..

     

    ( 메소드... 파라미터 명이랑 매칭되는 value 처리해서 각각 db 동적으로 삽입하고싶었는데... 그건 jdk 1.8 부터 제공되는 Method method = signature.getMethod(); 으로~~ method.getParmeters로 해결가능 할 것 같은데.. 사내는 jdk 1.7을 써서 불가능 할 것 같다. ( 바로 포기 )

     

    @Aspect
    @Component
    public class JcoAopExecutionAdvisor {
    
        private static Logger logger = LoggerFactory.getLogger("jcoAopExecution");
    
        public JcoAopExecutionAdvisor(){
    
        }
    
        @Around("approvalPointMethod()")
        private Object loggingApprovalAround(ProceedingJoinPoint joinPoint)
                throws Throwable {
    
            Object result = null;
            try{
                Object[] args = joinPoint.getArgs() == null? null : joinPoint.getArgs();
                StringBuffer log = new StringBuffer();
                log.append("\r\n*************************************************************************");
                log.append("\r\n********     APPROVAL LOGGING AROUND JOIN POINT LOGGING          ********");
                log.append("\r\n controller-class               = " + joinPoint.getTarget().getClass().getName());
                log.append("\r\n method-name                    = " + joinPoint.getSignature().getName());
                if(null != args) {
                    for(int i=0; i < args.length; i++){
                        log.append("\r\n APPROVAL PARAMETER          ="  + args[i].toString());
                    }
                }
                log.append("\r\n*************************************************************************\r\n");
                this.writeLog(log.toString());
    
                result = joinPoint.proceed();
    
            }catch (Exception e){
                e.printStackTrace();
            }
    
           return result;
        }
    
        @AfterReturning("approvalPointMethod()")
        private void loggingApprovalBeforeSuc(JoinPoint joinPoint) throws Throwable{
    
            try{
                Object[] args = joinPoint.getArgs() == null? null : joinPoint.getArgs();
                StringBuffer log = new StringBuffer();
                log.append("\r\n*************************************************************************");
                log.append("\r\n********     APPROVAL LOGGING BEFORE JOIN POINT LOGGING          ********");
                log.append("\r\n controller-class               = " + joinPoint.getTarget().getClass().getName());
                log.append("\r\n method-name                    = " + joinPoint.getSignature().getName());
    			
                
                /*  JDK 1.8 에서는 아래와 같은 코드로 사용이 가능하다 하니, 필요하신 분들은 사용하세요.
                MethodSignature signature = (MethodSignature) joinPoint.getSignature();
    
                Method method = signature.getMethod();
    
    
                for (int i = 0; i < method.getParameters().length; i++) {
                    parameterName = method.getParameters()[i].getName();
                    if (parameterName.equals("id"))
                        id = (Long) parameterValues[i];
                    if (parameterName.equals("token"))
                        token = (String) parameterValues[i];
                }
    
                System.out.println(id + " : " + token);*/
    
    
                if(null != args) {
                    for(int i=0; i < args.length; i++){
                        log.append("\r\n APPROVAL PARAMETER          ="  + args[i].toString());
                    }
                }
                log.append("\r\n*************************************************************************\r\n");
                this.writeLog(log.toString());
    
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        
        
        @AfterThrowing(value = "approvalPointMethod()" , throwing = "exception")
        private void loggingApprovalBeforeSuc(JoinPoint joinPoint,Exception exception) throws Throwable{
    
            try{
                Object[] args = joinPoint.getArgs() == null? null : joinPoint.getArgs();
                StringBuffer log = new StringBuffer();
                log.append("\r\n*************************************************************************");
                log.append("\r\n********     APPROVAL LOGGING BEFORE JOIN POINT LOGGING          ********");
                log.append("\r\n controller-class               = " + joinPoint.getTarget().getClass().getName());
                log.append("\r\n method-name                    = " + joinPoint.getSignature().getName());
    
                if(null != args) {
                    for(int i=0; i < args.length; i++){
                        log.append("\r\n APPROVAL PARAMETER          ="  + args[i].toString());
                    }
                }
                log.append("\r\n APPROVAL ERROR EXCEPTION LOG =" + exception.getMessage());
    
                log.append("\r\n*************************************************************************\r\n");
                this.writeLog(log.toString());
    
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    
    
    
        @Pointcut("execution(* a.com.test.*(..))")
        public void approvalPointMethod() {
        }
    
    
        private void writeLog(String log) {
            switch(this.logLevel) {
                case TRACE:
                    logger.trace(log);
                    break;
                case DEBUG:
                    logger.debug(log);
                    break;
                case INFO:
                    logger.info(log);
                    break;
                case WARN:
                    logger.warn(log);
                    break;
                case ERROR:
                    logger.error(log);
            }
    
        }
    
    }
    

     

     

    정리하자면 위와 같이 일단 사용하였고, 자세한 내역은 제거하였으나, 의도한 내역으로는 위와 같은 상황을 사용하여 return value를 받아와 동적으로 parameter를 name / value 매칭 시켜, 각각 db화 시킨 후 batch 나, 화면단으로 확인 가능하도록 자동화 처리를 할 생각이였다.

     

    도움이 되었길 바랍니당...

     

    댓글

Designed by black7375.