본문 바로가기
IT/Spring

AOP - 필요할 때 마다 알아서 해 줄테니 넌 핵심 로직에 집중해

by 최고영회 2020. 1. 31.
728x90
반응형
SMALL

AOP (Aspect Oriented Programming) - 관점지향 프로그래밍


다중 상속이 불가능한 Java에서 기능을 구현하는 핵심 기능 코드와 공통 기능 코드가 섞여 있어서 효율성이 떨어짐
핵심기능과 공통기능을 분리 시켜놓고, 공통 기능을 필요로 하는 핵식 기능들에서 사용하는 방식을 말합니다.

AOP 주요 용어

  • Aspect: 공통 기능
  • Advice: Aspect의 기능 자체
              Aspect를 공통 기능이라고 크게 묶었다면 Advice는 그 안의 세부적인 주요 기능이라고 생각하면 됨
  • Joinpoint: Advice를 적용해야 하는 부분 (ex. 메서드)
  • Pointcut: Joinpoint의 부분으로 실제 Advice가 적용된 부분
  • Weaving: Advice를 핵심 기능에 적용하는 행위

Spring AOP 특징

  • Proxy 기반
  • Spring Bean 에만 AOP 적용 가능
  • 모든 AOP 기능을 제공하는 것이 아닌 IoC와 연동하여 중복코드, 프록시 클래스 작성의 번거로움, 객체들 간 관계 복잡도 증가 등을 해결하는 것이 목적

Aspect 실행 시점

  • @Before : Target method가 호출되기 전 수행
  • @After : Target method 결과와 관계없이 완료 되면 수행  (Finally 처럼)
  • @AfterReturning : Target method가 성공적으로 결과값을 반환 후에 수행
  • @AfterThrowing : Target method 수행 중 예외를 던지면 수행
  • @Around : Target method 실행 전후

적용예

INFO*****

- 특정 메서드들의 동작 여부 결정
- 캐시 초기 발생시키는 메서드마다 알림 기능 처리

xml 설정

<aop:aspectj-autoproxy proxy-target-class="true" />

 

Component

 

@Aspect
@Component
@DependsOn("schemaInitializer")
public class CollectAnalysisAdvice {

  private static final Logger logger = LoggerFactory.getLogger(CollectAnalysisAdvice.class);
	
  @Autowired
  CollectAnalysisService collectAnalysisService;
	
  // 분석서버 캐시 초기화 서비스(사용자 계정, 모니터링 객체)
  private static final String[] ANALYSIS_SERVICE_OBJ_NAME_ARR = new String[]{"CertAccount", "Monitoring"};
	
  /**
   * 수집서버로 동작 시 실행하지 않는 메소드 설정 (패킷 분석부)
   * 
   * @param thisJoinPoint
   */
  @Around("execution(* com.xxx.common.util.LogScheduler.insertAttachInfo())"
  	+ " || execution(* com.xxx.infoxx.analyzer.component.xxcheduler.syncUserIp()) "
  	+ " || execution(* com.xxx.infoxx.analyzer.component.xxxForHour.makeReportData()) "
  	+ " || execution(* com.xxx.infoxx.analyzer.component.xxxForDay.makeReportData()) ")
  public void skipScheduleMethodForCollectServer(ProceedingJoinPoint thisJoinPoint) {
    if (isCollectServer()) {
      return;
    }
    try {
      thisJoinPoint.proceed();
    } catch (Throwable e) {
    logger.error("An error occurred while check skip method for collect server. [{}] ", thisJoinPoint.getSignature().toString(), e);
    }
  }

  /**
   * 캐시 초기화 시 수집/분석 서버에 캐시 초기화 알림
   * 
   * @param joinPoint
  */
  @After("@annotation(org.springframework.cache.annotation.CacheEvict) || @annotation(org.springframework.cache.annotation.Caching)")
  public void sendRefreshRequestAfterCacheEvict(final JoinPoint joinPoint) {
    String className = joinPoint.getSignature().getDeclaringTypeName();

    if ( SystemConfigurer.SETUP_DATA.isCollectServer() && className.contains("ServiceObject") ) {
      collectAnalysisService.sendRefreshRequest();
    }
  }
}

 

decxxx

- 사용자별 최근 사용 문서 redis 에 갱신(`최근 사용` 이라는 키워드에 대한 공통 처리)
- INFOxxx 와 다른점은 method 들을 직접 설정하거나 method name 규칙을 이용하지 않고 필요한 곳에
  annotation 을 직접 설정하여 사용하도록 함

Configuration 설정

@EnableAspectJAutoProxy

사용자 정의 Annotation

@Retention(RetentionPolicy.RUNTIME) 
@Target(ElementType.METHOD) 
public @interface RecentlyLayoutUpdator {}

Compnent
 - joinpoint method 에서 사용하는 parameter 를 활용

@Aspect
@Component
@Slf4j
public class RedisUtils {
  @Autowired
  private RedisTemplate<String, String> redis;
	
  /**
   * 사용자의 최근 사용문서 정보 갱신 
   * @param userNo
   * @param docLayoutNo
   */
  @Pointcut("@annotation(com.xxx.dexxx.xxx.component.RecentlyLayoutUpdator)")
  public void updator() {}
	
  @AfterReturning("updator()")
  public void updateRecentlyDocLayout(JoinPoint jp) {
    long userNo = 0; 
    int layoutNo = 0;
    Object[] paramValues = jp.getArgs();
		
    MethodSignature sigature = (MethodSignature)jp.getSignature();
    Method method = sigature.getMethod();

    String paramName;
    for(int i=0; i<method.getParameters().length; i++) {
      paramName = method.getParameters()[i].getName();
      if (paramName.equals("userNo") || paramName.equals("requesterNo")) {
	userNo = (Long)paramValues[i];
      } else if (paramName.equals("docDto")) {
	DocDto docDto = (DocDto)paramValues[i];
	layoutNo = docDto.getDocLayout();
      } else if (paramName.equals("layoutNo") || paramName.equals("docLayoutNo")) {
	layoutNo = (Integer)paramValues[i];
      }
    }

    String recentlyLayoutNos = redis.opsForValue().get(Constant.REDIS_RECENTLY_LAYOUT_PREFIX + userNo);
    recentlyLayoutNos = CommonUtils.makeRecentlyDocLayout(recentlyLayoutNos, layoutNo);
    redis.opsForValue().set(Constant.REDIS_RECENTLY_LAYOUT_PREFIX + userNo, recentlyLayoutNos);
  }
}
@Transactional
@RecentlyLayoutUpdator
public DocLayout findDocLayoutWithUser(long userNo, int layoutNo) {
  DocLayout layout = checkValidUserGrant(layoutNo, userNo);
  .....
}
@Transactional
@RecentlyLayoutUpdator
public DocDto requestElucidate(long requesterNo, DocDto docDto) {
  Doc doc = Doc.of(docDto, modelMapper);
  doc.setRequestDate(LocalDateTime.now());
  ....
}	
728x90
반응형
LIST