-
Spring AOPSpring 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 나, 화면단으로 확인 가능하도록 자동화 처리를 할 생각이였다.
도움이 되었길 바랍니당...
'Spring' 카테고리의 다른 글
Filter / InterCeptor / AOP (0) 2020.11.19 Spring Mybatis Batch Processing (0) 2019.04.10 cannot change the executor type when there is an existing transaction (0) 2019.04.10 웹페이지에서 Layout 처리 하기 위하여 표기 방법 (0) 2017.06.12 Json Date Log를 보기 쉽게 하는법 (0) 2017.06.12