본문 바로가기

개발기술/Spring

스프링 부가기능 (Resource, AOP, 유효성검증, 데이터바인딩,spel)

외부자원 가져오기

앱은 단순히 내부코드로만 동작할 수 없고 결국 자원을 끌어올 때가 있다. 외부 API, 외부 url, 외부 이미지, 내부 file system의 자원, 내부 패키지내 클래스라던지 등등 자원을 사용할 필요성이 있다. java 기본 라이브러리에서는 자원을 가지고오는 기능들이 충분하지않고 사용하기 어렵게 되어있어 스프링은 resource interface를 통해서 이러한 기능들을 제공한다. 

각 자원의 종류별로 resource interface를 상속하는 구체적인 구현체들이 있으며 이를 사용하여 resouce를 다룬다. 그리고 스프링컨테이너인 applicationcontext에 구현되어있는 resourceloader을 사용하여서 자원들을 불러온다. 마찬가지로 applicatiocontext같은 개체도 초기설정시에 설정값을 가져올때 resourceloader(.getresource())을 사용한다.

AOP 관점지향프로그래밍

개념

관점지향 프로그래밍(AOP)은 각 메소드에 동일한 로직이 반복적으로 적용될 필요가 있을 때, 이를 공통의 관심사(Cross-Cutting Concern)로 정의하고 효율적으로 관리하는 기법이다. 이러한 공통 관심사에는 로깅, 트랜잭션, 캐싱 등이 포함될 수 있다. OOP(객체지향 프로그래밍)에서도 동일한 로직을 도입할 수 있지만, 상속이나 의존성 관리가 복잡해질 수 있다. 이를 해결하기 위해 Spring은 AOP 기능을 제공하여 공통 로직을 분리하고 쉽게 적용할 수 있도록 돕는다.

  • AOP: Aspect Oriented Programming 의 핵심은 공통 관심 사항(cross-cutting concern) vs 핵심 관심 사항(core concern) 분리하는 것. 공통관심사항을 작동하는 코드를 만들고 적용할 대상을 지정해주면된다. 그러면 AOP는 JavaBean으로 등록된 객체를 프록시로 생성하여 이에 대한 메소드 호출을 가로채어, 추가적인 코드동작들을 메소드 호출 전후에 주입하는 방식으로 작동한다.
  • AOP Proxy : Advice를 적용하기 위해 대상 객체에 프록시를 생성하고 처리하는 작업을 의미한다.  주로 CGLIB(Code Generation Library, 실행 중에 실시간으로 코드를 생성하는 라이브러리)를 사용하여 런타임에 프록시 객체를 생성한다.
  • Advice :  실제로 AOP에서 동작하는 기능(예: 로깅, 트랜잭션, 캐싱)을 의미하며, 메소드의 동작을 "조언(advising)"하는 역할을 한다. 
  • Joint Point : 모듈화된 특정 기능이 실행될 수 있는 연결 지점으로, 메소드 호출이나 예외 발생 같은 이벤트를 의미한다.
  • Point Cut : Joint point들을 조건문으로 선택적으로 적용하는 것을 point cut이라고함. Joint point 중에서 해당 Aspect를 적용할 대상을 뽑을 조건식
  • Target Object : Advice가 적용될 대상 오브젝트
  • Weaving : Advice를 비즈니스 로직 코드에 삽입하는 과정을 의미한다. 이 작업은 런타임, 컴파일타임, 또는 클래스 로딩 시점에 이루어질 수 있다.

 

AOP 구현 방식

 

@Component
@Aspect
public class TimeTraceAop {

    // Conditional property to enable or disable the tracing
    @Value("${aop.trace.enabled:true}") // This can be set in application.properties
    private boolean isTraceEnabled;

    @Around("execution(* hello.hellospring..*(..))")
    public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {

        // Check if tracing is enabled
        if (!isTraceEnabled) {
            return joinPoint.proceed(); // Skip timing and proceed if tracing is disabled
        }

        // Proceed with tracing if enabled
        long start = System.currentTimeMillis();
        System.out.println("START: " + joinPoint.toString());

        try {
            return joinPoint.proceed(); // Proceed with the method execution
        } finally {
            long finish = System.currentTimeMillis();
            long timeMs = finish - start;
            System.out.println("END: " + joinPoint.toString() + " " + timeMs + "ms");
        }
    }
}

 

클래스를 @Aspect로 표기하고 @component 혹은 Config에서 JavaBean으로 등록해준다. 그리고 메소드의 시작지점에 원하는 코드를 작성한 후에 jointPoint.procced()를 실행하고 finally로 메소드의 마지막점에 시작하길 원하는 코드를 작성한다. 그리고 메소드 상위에 @Around로 advice임을 정의하며, around 내에 pointCut을 같이 정의한다.

  • @Aspect: 해당 클래스가 Aspect임을 나타냅니다
  • @Around: 메서드 실행 전후에 실행될 Advice를 정의합니다. advice가 적용될 pointCut 표현식도 같이 지정할 수 있다.
  • @annotation(): 특정 어노테이션이 붙은 메서드를 지정합니다
  • ProceedingJoinPoint.proceed(): @Around Advice에서 원본 메서드를 실행합니다

PointCut 표현식

Spring AOP에서 어떤 메소드에 Advice(로직)를 적용할지 지정하는 데 사용됩니다.

  • execution: 특정 메소드를 지정하여 해당 메소드의 실행을 조인 포인트로 지정합니다. 
  • args: 메소드 인자를 기준으로 메소드를 선별하여 조인 포인트로 지정합니다.
  • @annotation: 주어진 어노테이션을 가진 메소드를 조인 포인트로 지정합니다.

 

 

유효성검증

유효성 검증단계는 제대로 데이터가 흐르고있는지 혹은 프로세스 작동이 되고있는지 확인하는 단계이다. 주로 input되는 데이터가 규격에 맞는지 그리고 어떤 작업이후 흘러야하는 데이터가 제대로 흐르는지 확인하는 절차로 결국 데이터가 유효한지 확인하는 것이 주이다. 유효성검증방식은 두가지가 있는데

  • 첫번째 JavaBean을 활용한 어노테이션 기반 검증 : JavaBean에 어노테이션을 사용하여 제약 조건을 설정하고, 컨트롤러의 파라미터에 @Valid 어노테이션으로 유효성 검증을 실행하는 방식입니다.
    • Javabean규약
      • Private 필드, Public Getter와 Setter로 접근 제어.
      • No-argument Constructor: 인스턴스 생성을 위해 기본 생성자가 필요.
      • Serializable 인터페이스 구현: 객체 직렬화 필요 시 사용.
    • JavaBean 검증annotation
      • @NotNull("오류시 응답값") : null이 아닌지 검사. String 값이 아닌 wrapper class에 적용
      • @NotBlank("오류시 응답값" ): 문자열이 null이 아니고 공백이 아닌지 검사.
      • @Min(value), @Max(value): 숫자의 최소/최대 값 설정.
      • @Size(min, max): 문자열의 길이 설정.
      • @Pattern: 특정 패턴의 문자열을 확인
      • @Email : 이메일 형식인지 확
  • 두번째는 Validator 인터페이스를 활용한 커스텀 검증 :
    • 클래스 내부에는 support와 validate이라는 두가지 메소드가 존재
      • supports(Class<?> clazz): 해당 Validator가 특정 클래스의 객체만 검증하도록 설정. Spring이 validate() 실행 전에 호출합니다. support method는 주로 class가 검증하고자 하는 class에 속하는 지 검증함. support method는 개발자가 실행하지 않아도 spring에서 validate실행전에 먼저 실행해줌
      • validate(Object target, Errors errors): 구체적인 검증 로직을 구현하여, 보다 세밀한 검증 조건을 설정할 수 있습니다. 데이터 검증뿐 아니라, 로직의 작동 여부까지 검증할 수 있습니다. 
    • 개발자가 메소드를 정의하는 것이기 때문에 보다 세부적인 검증이 가능하며 단순, 데이터의 검증뿐아니라 로직의 작동에 대해서도 검증가능하다.

  Validation은 코드가 너무 흩어져있으면 만약, 검증조건이 바뀌었을때 일괄적으로 수정적용하여 관리하기가 어렵다. 그러한 관점에서 별도로 클래스를 만드는 두번째 방식보다는 Request DTO에 검증조건을 표기하는 첫번째 방식이 관리에 더 효과적이다. 그리고 Validation은 최대한 프로그램의 초기에 진행하는 것이 자원관리 및 rollback 관점에서 좋다.

  주로 사용되는 방식은 요청 DTO에 요청이 들어올시 1차적으로 validation을 진행하고, 로직초기에 비즈니스 검증 수행 후 실패시에 Custom Exception을 throw하도록 예외처리를 한다.

  

데이터바인딩 ; Converter, Formatter

데이터 검증후 내부시스템의 request에 담기 위해서 Converter interface를 사용한 구현체를 component로 생성하면, input type과 output type을 보고서 자동적으로 mapping하여 데이터를 목표하는 dto로 변환해준다. 동일한 논리로 formatter는 string을 dto로 변환시키는 converter라고 할 수 있음. 

 

SpEL : Spring Expression

  Expression Language(표현언어) : 짧고 간단한 문법을 통해 필요한 데이터나 설정 값을 얻을 수 있게하는 특별한 형태의 표현식에 가까운 간편한 언어(그래프 접근방식으로 데이터 접근이(earth.usa.california.xxx) 가능)

  EL 중에서 Spring에 특화된 것이 SpEL임. 주로 환경변수를 parsing 하여얻어와서 변수에 담아주는 것을 위해 @Value annotion에서 많이 사용한다. 이는 상황 및 환경에 따라서 변해야하는 값들을 복잡한 수정 후 재배포 과정없이 환경변수 변경만으로 적용시키고자할 때 많이 사용한다. 이 환경변수들은 개발자가 직접 설정하고 생성할 수 있다. 

  SpEL의 작동방식은 parser 구현체를 만들고 이곳에 문자열의 형식으로 문자열 혹은 연산키워즈(SpEL) 인풋하면 문자열의 내용을 평가하여 연산 규칙에 맞게 parsing해서 값을 가져온다.

  • @Value : 속성파일에서 값을 주입하는 annotation
  • '${}' : 환경변수에서 값을 파싱한다.
    • (@Value("${config.value}") - ${} 안에 SpEL이 들어가서 config를 접근하여 value를 확인한다. 주로많이 사용
  • #{<expression string>} : 값을 평가하여 string을 생성한다.
    • #{1+1} : 2
    • #{2 eq 2} : true
  • Parametervariable : #variablename, 메소드 파라미터 변수를 지칭한다
  • Special Variable : authentication.principal.id
  • Method : hasRole('ROLE_NAME')
scheduler:
  scrap:
    yahoo: "0 0 1 * * *"
@Scheduled(cron = "${scheduler.scrap.yahoo}")
public void yahooFinanceScheduling() {
@PreAuthorize("hasRole('ADMIN') or (hasRole('USER') and #userId == authentication.principal.id)")
public void performAction(Long userId) {
    // Method logic here
}

그 외 Annotation

  • @DateTimeFormat (attribute) : is used for formatting date and time fields.
    • purpose
      • Validation: It ensures that incoming date and time strings conform to a specified format before they are processed further. This prevents data errors that could occur from incorrectly formatted dates.
      • Consistency: By enforcing a specific format, it standardizes date and time data across different parts of an application, from the web layer to the business layer.
    • attribute 
      • iso: This attribute can be used to specify an ISO date time format (like ISO.DATE, ISO.TIME, or ISO.DATE_TIME)
      • style: You can specify a style pattern code which consists of two characters: one for the date style and one for the time style (e.g., "SS" for short date and short time).

JSON Parsing

  • attribute 
    • iso: This attribute can be used to specify an ISO date time format (like ISO.DATE, ISO.TIME, or ISO.DATE_TIME)
    • style: You can specify a style pattern code which consists of two characters: one for the date style and one for the time style (e.g., "SS" for short date and short time).

Logger

서비스의 동작상태를 기록으로 남기는 것. Print도 유사한 기능을 할 수 있지만, 콘솔에 출력되는 한정적인 기능만 제공하기에 한계가 있다. Logger의 경우에는 로그저장, 과거로그 지우기 등 기능을 제공한다. 라이브러리로는 log4J, logback 등이 있음.

 

logback의 경우 스프링에 내장되어있어 별도로 dependency를 추가할 필요가 없다. resource file에 log의 config 파일만 추가한 후, spring의 config 파일에 해당 config 파일을 알려주기만 하면 된다. 

 

로그에는 아래와 같은 5가지 종류가 있다.

  • error : 심각한 문제 발생; DB와 연결이 끊어짐 등
  • warning : 로직상 유효성, 당장 서비스에 운영은 문제가 없으나 지켜봐야하는 
  • info : 서비스 운영에 필요한 정보; schedule 진행성공기록
  • debug : 세세하게 보고싶을 때, 개발단계
  • trace : 개발단계

로그 configuration : 우선, 검색을 통해서 외부소스를 활용하고 필요에 따라 공부하는 것을 추천.

 

MDC(Mapped Diagnostic Context) 로그시스템 :  웹 애플리케이션의 경우 여러 사용자가 동시에 시스템을 이용하기 때문에, 단순한 로그 메시지로는 어떤 사용자 요청에서 발생한 로그인지 추적하기 어려운 경우가 많습니다. 이럴 때 MDC를 사용하면, 각 사용자나 요청에 대한 고유한 식별 정보를 로그에 추가할 수 있습니다. 이렇게 추가된 정보는 로그 분석 시, 어떤 사용자나 트랜잭션과 관련된 로그인지 명확하게 구별할 수 있도록 해줍니다. 예를 들어, "userId=12345", "transactionId=abcde"와 같은 정보가 로그에 자동으로 포함되어, 로그 파일에서 관련된 이벤트들을 빠르게 찾아낼 수 있습니다.

 

https://0soo.tistory.com/246