회사에서 업무가 바껴서 Spring Boot Framework단을 더 만질일이 생겼다

그래서 다시한번 복습!

 

그치만 사진이 올라가야 이해하기 쉬운데... 집에서 마저 올리길..


 

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

애스팩트(=관점) 를 사용한 프로그래밍 방식 

횡단 관심사를 깔끔하게 처리 

 

AspectJ 프레임워크

스프링의 AOP는 AspectJ의 문법을 차용하며, AspectJ가 제공하는 기능 일부만 제공

횡단 관심사의 깔끔한 모듈화

  • 오류 검사 및 처리
  • 동기화
  • 성능 최적화 (캐싱)
  • 모니터링 및 로깅

 

AspectJ 에서 AOP 적용 방식

  • 컴파일 시점
  • 클래스 로딩 시점
  • 런타임 시점(프록시) → 스프링에서 사용하는 방식

Spring AOP 관련 용어들 다시 정리 

Join Point "어디서 AOP를 적용할 수 있지?"
Spring AOP 기준: public 메서드 호출 시점 (즉, 메서드 실행 전후 등)
Pointcut Advice 적용할 Join Point(메서드)들을 패턴으로 골라내는 규칙
Join Point들 중 실제로 Advice가 적용될 타깃을 골라내는 규칙
예: "com.example.service 패키지 아래의 모든 메서드"
Target 어드바이스를 받는 객체
핵심 비즈니스 로직이 구현된 클래스
Advice 부가 기능
특정 조인포인트에서 Aspect에 의해 취해지는 조치
Around, Before, After 와 같은 다양한 종류의 어드바이스가 있음
Aspect "공통 관심사"의 덩어리. 
어드바이스+포인트컷을 모듈화 한 것
@Aspect
Weaving Advice를 실제 Target Object 코드에 붙이는 과정
Spring AOP는 런타임에 프록시(Proxy) 객체를 만들어 위빙함
Proxy Advice가 주입된, Target Object를 감싸는 대리 객체
실제 Service 객체 대신 프록시를 Bean으로 주입해서 AOP 적용

자동 프록시 생성기가 하는 일

  1. @Aspect를 보고 Advisor로 변화 후 저장
  2. Advisor를 기반으로 프록시 생성

1.@Aspect를 Advisor로 변환 후 저장

  1. 스프링 애플리케이션 로딩 시점에 자동 프록시 생성기를 호출
  2. 자동 프록시 생성기는 스프링 컨테이너에서 @Aspect 어노테이션이 붙은 스프링 빈 모두 조회
  3. @Aspect 어드바이저 빌더를 통해 @Aspect 애노테이션 정보를 기반으로 어드바이저 생성
  4. 생성한 어드바이저를 @Aspect 어드바이저 빌더 내부 저장

 

*참고 

@Aspect 어드바이저 빌더

BeanFactoryAspectAdvisorBuilder 클래스

Aspect 의 정보를 기반으로 포인트컷 , 어드바이스 , 어드바이저를 생성보관

 

2. Advisor를 기반으로 프록시 생성

 

1.스프링 빈 대상이 되는 객체 생성 (@Bean , 컴포넌트 스캔)

2.전달된 객체를 빈 저장소에 등록하기 직전에 빈 후처리기에 전달(PostBeanProcessor??)

3-1. 스프링 컨테이너에서 Advisor 빈을 모두 조회

3-2. @Aspect 어드바이저 빌더 내부에 저장된 Advisor 를 모두 조회
4. 앞서 3-1, 3-2에서 조회한 Advisor 에 포함되어 있는 포인트컷을 사용해서
해당 객체가 프록시를 적용할 대상인지 아닌지 판단한다. 이때 객체의 클래스 정보는 물론이고, 해당 객체의
모든 메서드를 포인트컷에 하나하나 모두 매칭해본다. 그래서 조건이 하나라도 만족하면 프록시 적용
대상이 된다. 예를 들어서 메서드 하나만 포인트컷 조건에 만족해도 프록시 적용 대상이 된다.
5. 프록시 적용 대상이면 프록시를 생성하고 프록시를 반환한다. 그래서 프록시를 스프링
빈으로 등록한다. 만약 프록시 적용 대상이 아니라면 원본 객체를 반환해서 원본 객체를 스프링 빈으로
등록한다.
6. 반환된 객체는 스프링 빈으로 등록된다.

최근에 업무하면서

TaskExecutor과 TaskScheduler를 모두 쓸 일이 있었다

 

그래서 개념에 대해서 깊게 보고싶어서

토비의 스프링을 펼쳐보았다^^7777

역시 이거만한게 없다..

 

정리해보쟛...ㅁ7ㅁ8

 

 


1. TaskExecutor

task실행기.. 그렇담 task는 뭘까?

 

Task : 독립적으로 실행한 가능한 작업

 

스프링은 이러한 task들을 다양하게 실행하도록 추상화하여 TaskExecutor라는 인터페이스를 제공한다.

 

package org.springframework.core.task;

import java.util.concurrent.Executor;

@FunctionalInterface
public interface TaskExecutor extends Executor {
    void execute(Runnable var1);
}

 

java5의 Executor 인터페이스를 상속한다.

 

Runnable타입의 태스크를 받아 실행하는데,

다음 인터페이스는 독립적인 스레드에 의해 실행 되도록 의도된 오브젝트를 만들 때 주로 사용된다.

package java.lang;

@FunctionalInterface
public interface Runnable {
    void run();
}

 

Spring의 TaskExecutor는 java.lang.concurrent 패키지 의 Executor와 똑같은 메소드를 가지고 있다. 

 

package java.util.concurrent;

public interface Executor {
    void execute(Runnable var1);
}

 

그럼에도 스프링에서 다시 만든이유는 다음과 같다.

 

1. 다른 기술의 태스크 실행기에 대한 어댑터를 제공 ( Quartz , CommonJ WorkManager...)

2. 스프링에 최적화된 방식의 태스크 실행기 확장 

 

반드시 비동기 독립적 스레드에서 실행될 필요는 없지만 ,

대부분 비동기로 쓴닷 ㅎ_ㅎ

 

 

2. TaskExecutor 구현체

 

2.1 ThreadPoolExecutor

corePoolSize, maxPoolSize , queueCapacity 속성을 설정할 수 있다.

지정된 크기의 스레드 풀을 이용하며 , 작업 요청은 큐를 통해 관리된다.

가장 대표적인 태스크 실행기다.

 

 

   @Bean
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(5);
        executor.setQueueCapacity(500);
        executor.setThreadNamePrefix("async-");
        executor.initialize();
        return executor;
    }

 

2.2 SimpleThreadPoolTaskExecutor

Quartz의 SimpleThreadPool을 이용해 만들어진 태스크 실행기이다.

 

2.3 WorkManagerTaskExecutor

CommonJ WorkManager의 태스크 실행기에 대한 어댑터이다.

 

 

2.4 SyncTaskExecutor

별도의 스레드에서 수행되는게 아니라 호출한 스레드 상에서 호출

 

 


3. TaskScheduler 

다음 인터페이스는 주어진 태스크를 조건에 따라 실행하거나 반복하는 작업을 수행

태스크의 실행조건은

1) 특정시간

2) 일정한 간격을 두고 반복

3) Trigger인터페이스를 구현해서 유연한 조건 

 - > cron 서버의 실행시간 설정 포맷을 활용한 cronTrigger가 가장 대표적인 구현 클래스이다. 

 

package org.springframework.scheduling;

import java.time.Duration;
import java.time.Instant;
import java.util.Date;
import java.util.concurrent.ScheduledFuture;
import org.springframework.lang.Nullable;

public interface TaskScheduler {
    @Nullable
    ScheduledFuture<?> schedule(Runnable var1, Trigger var2);

    default ScheduledFuture<?> schedule(Runnable task, Instant startTime) {
        return this.schedule(task, Date.from(startTime));
    }

    ScheduledFuture<?> schedule(Runnable var1, Date var2);

    default ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Instant startTime, Duration period) {
        return this.scheduleAtFixedRate(task, Date.from(startTime), period.toMillis());
    }

    ScheduledFuture<?> scheduleAtFixedRate(Runnable var1, Date var2, long var3);

    default ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Duration period) {
        return this.scheduleAtFixedRate(task, period.toMillis());
    }

    ScheduledFuture<?> scheduleAtFixedRate(Runnable var1, long var2);

    default ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, Instant startTime, Duration delay) {
        return this.scheduleWithFixedDelay(task, Date.from(startTime), delay.toMillis());
    }

    ScheduledFuture<?> scheduleWithFixedDelay(Runnable var1, Date var2, long var3);

    default ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, Duration delay) {
        return this.scheduleWithFixedDelay(task, delay.toMillis());
    }

    ScheduledFuture<?> scheduleWithFixedDelay(Runnable var1, long var2);
}

 

 

4. TaskScheduler 구현체

 

4.1 ThreadPoolTaskScheduler

JDK의 ShcdeuledThreadPoolExecutor 스케쥴러에 대한 어댑터이다.

 

 

    @Bean
    public TaskScheduler scheduler() {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        scheduler.setPoolSize(10);
        scheduler.setThreadNamePrefix("Scheduler-Thread-");
        scheduler.initialize();
        return scheduler;
    }

 

 

4.2 TimerManagerTaskScheduler

 

참고) 유연한 조건을 이용!할때 쓰는 Trigger 인터페이스

 

package org.springframework.scheduling;

import java.util.Date;
import org.springframework.lang.Nullable;

public interface Trigger {
    @Nullable
    Date nextExecutionTime(TriggerContext var1);
}

 

그 Trigger 인터페이스를 구현한 CronTrigger  CronExpression을 지원한다.

package org.springframework.scheduling.support;

import java.util.Date;
import java.util.TimeZone;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.TriggerContext;

public class CronTrigger implements Trigger {
    private final CronSequenceGenerator sequenceGenerator;

    public CronTrigger(String expression) {
        this.sequenceGenerator = new CronSequenceGenerator(expression);
    }

    public CronTrigger(String expression, TimeZone timeZone) {
        this.sequenceGenerator = new CronSequenceGenerator(expression, timeZone);
    }

    public String getExpression() {
        return this.sequenceGenerator.getExpression();
    }

    public Date nextExecutionTime(TriggerContext triggerContext) {
        Date date = triggerContext.lastCompletionTime();
        if (date != null) {
            Date scheduled = triggerContext.lastScheduledExecutionTime();
            if (scheduled != null && date.before(scheduled)) {
                date = scheduled;
            }
        } else {
            date = new Date();
        }

        return this.sequenceGenerator.next(date);
    }

    public boolean equals(Object other) {
        return this == other || other instanceof CronTrigger && this.sequenceGenerator.equals(((CronTrigger)other).sequenceGenerator);
    }

    public int hashCode() {
        return this.sequenceGenerator.hashCode();
    }

    public String toString() {
        return this.sequenceGenerator.toString();
    }
}

 

 


5. Annotaion 활용 ( @Scheduled , @Async )

 

5.1 @Scheduled

태스크 역할을 맡은 메소드에 직접 스케줄 정보를 어노테이션을 통해 부여해 수케줄이 적용되게 해준다.

@Scheduled 부여되는 메소드는 파라미터를 가질 수 없으며 반드시 void형 리턴 타입이어야한다.

 

-  fixedDelay : 이전 작업이 끝난 시점부터 일정시간이 지난후에 동작하도록 설정 ,

 이전 작업이 끝난후로 부터 정해진 시간이 지난후 다음작업이 시작된다.

@Schduled (fixedDelay=60000)
public void testFixedDelay(){...}

 

- fixedRate : 밀리초로 설정된 일정한 시간간격으로 메소드 실행 

@Schduled (fixedRate=60000)
public void testFixedRate(){...}

 

- cron : Cron expression 으로 메소드 실행

@Schduled (cron = "0 0 12 1 * ?")
public void testCron(){...}

 

 

5.2 @Async 

TaskExecutor를 코드로 사용하지 않고도 비동기 실행이 가능하게 해주는 어노테이션이다.

( 그런데 설정이 필요하면.. Bean으로 만들어주는게 좋다.. threadpool size 등등..)

 

리턴타입은  void 또는 Future 타입이어야한다.

메소드는 다른 코드에 의해 직접 호출 되므로 파라미터를 가질 수 있다. 

 

더자세한 내용은 이전에 포스팅한 이글을 참조한다.

https://hyeonyeee.tistory.com/55

 

Spring에서 Async 처리 (@Async )

Spring에서 Async처리를 해보겠다..! 블로그에 정리되어 있는게 많았는데 그중에 어떤 방법을 택할까 고민을 했다. 가장 간단한 방법으로 구현하였다. @Async annotaion을 붙여주는 방법이다. 1. @EnableAsyn

hyeonyeee.tistory.com

 

 

 


6. 결론

 

정리하다보니 더 자세히 이해가 되었다.

그리고 이전에 개발한 코드가 더 잘 와닿게 되었다 후후

 

그래서 한마디로 정리하자고 하면

 

TaskExecutor는 task를 주로 비동기적으로 처리할때 쓰고

TaskScheduler는 스케줄링할때 쓴다.

 

이 두개 모두 @Async , @Scheduled 라는 어노테이션으로 대체가 가능한데,

자세한 설정이 필요하면

bean으로 만들어 주는것이좋다~ (@Configuraion...)

 

그럼 정리 끝 - 

스프링에서 Filter, Interceptor, AOP는 모두 어떤 동작 이전/이후에 추가적으로 처리해주어야 할때 사용한다.

기능적으로는 비슷한데 내부 구현적으로는 차이가 있다.

 

Filter, Interceptor : Sevlet 단위에서 실행

AOP : 메소드 앞에 proxy 패턴의 형태로 실행

 

그래서 실행 순서도 차이가 있다 ???

 

Filter -> Interceptor -> AOP -> Interceptor -> Filter 순이다

이미지 참조 : blog.naver.com/platinasnow/220035316135

 

Request -> Servlet Filter -> Dispatcher Servlet -> HandlerInterceptor -> Controller

 

1. 서버를 기동시켜 서블릿이 올라오는 동안 init 실행 되고 FilterdoFilter 실행

2. 컨트롤러 이전에 InterceptorpreHandler실행

3. 컨트롤러 빠져나온다음 InterceptorpostHandler, afterCompletion 실행

4. FilterdoFilter 진행

5. 서블릿 종료시 destroy 실행

 

AOP는 주로 비지니스 로직단에서 로깅,트랜잭션, 공통예외처리 에서사용한다.

 

 

참고

hayunstudy.tistory.com/53

 

# Filter , Interceptor , AOP 사용목적 정리

Filter,Interceptor,AOP 모두 요청 중간에 가로채서 사전처리,사후처리를 하는 의미는 비슷하다. 로깅처리 또한 셋다 가능. 차이점이라고 하면 , 1. 셋의 시점이 다르다는것 : Filter -> Interceptor -> AOP 2. 적

hayunstudy.tistory.com

junshock5.tistory.com/142

 

Filter, Intercepter, AOP 차이점

[ 배경 ] 자바 웹 개발을 하다 보면, 공통적으로 처리해야 할 업무가 생깁니다. 예를 들어 로그인 관련 세션 처리, 사용자 권한 체크, XSS(Cross Site Script) 방어 로직, PC Mobile 플랫폼 분기 처리, 로그

junshock5.tistory.com

 

+ Recent posts