HyunsooZo's TIL logo HyunsooZo's TIL

Resource Interface 구현체

자바의 표준 클래스들은 다양한 Resource(URL,File 등)에 접근할 때 충분한 기능을 제공하지 않는다.
그래서 Spring은 필요한 기능을 만들어 따로 제공한다.

∙ URLResource

java.net.URL 을 래핑한 버전.
다양한 종류(ftp:,file:,http: 등의 prefix로 접근유형을 판단) 의 Resource에 접근 가능하지만 기본적으로는 http(s)로 원격접근

∙ ClassPathResource

classPath(소스코드를 빌드한 결과(기본적으로 target/classes폴더)) 하위의 리소스 접근 시 사용

∙ FileSystemResource

이름과 같이 file을 다루기 위한 리소스 구현체

∙ ServletContextResource,InputStreamResource,ByteArrayResource

servlet 어플리케이션 루트 하위 파일, InputStream,ByteArrayInput 스트림을 가져오기 위한 구현체

[예시]

public interface Resource extends InputStreamSource{

    boolean exists();
    boolean isReadable();
    boolean isOpen();
    boolean isFile();
    URL getURL() throws IOExeption;
    URI getURI() throws IOExeption;
    File getFile() throws IOExeption;
    ReadableByteChannel readableChannel() throws IOExeption;
    long contentLength() throws IOExeption;
    long lastModified() throws IOException;
    Resource createRelative(String relativePath) throws IOException;
    String getFilename();
    String getDescription();
}

Spring ResourceLoader

Spring 프로젝트 내 Resource를 로딩할 때 사용하는 기능
기본적으로 applicationContext에 구현되어있음.
프로젝트 내 (주로 class path하위 파일)에 접근 할 때 사용
대부분의 사전정의 파일들은 자동로딩되나, 추가로 파일이 필요한 경우 사용.
public interface ResourceLoader {
    Resource getResource(String location);
    ClassLoader getClassLoader();
}

AOP 관점지향 프로그래밍

여러 메서드에서 동일한 코드가 반복된다? -> AOP 로 처리!

AOP (Aspect Oriented Programming)
공통적인 관심사(로깅,트랜잭션,인증)를 여러 메서드의 호출 전/후에 원할때마다 추가
OOP로 처리하기 까다로운부분을 AOP처리하여 손쉽게 공통기능 추가/수정/삭제
AOP의 기본개념 설명
Aspect 여러클래스나 기능에 결쳐있는 관심사를 모듈화함.
AOP중에서도 @Transactional(트랜색션관리),@Cacheable 기능
Advice AOP에서 실제로 적용하는 기능을 뜻함(로깅,트랜잭션, 캐시 , 인증 등)
Join Point 모듈화된 특정 기능이 실행될 수 있는 연결포인트
Pointcut Join Point중에서 해당 Aspect를 적용할 대상을 뽑을 조건식</span
Target Object Advice가 적용될 대상 Object
AOP Proxy 대상 Object에 Aspect를 적용하는 경우 Advice를 덧붙이기 위해 하는 작업
주로 CGLIB(실행 중 실시간 코드생성)프록시를 사용하여 처리
Weaving Advice를 비즈니스 로직 코드에 삽입

AspectJ Library

AspectJ는 AOP를 제대로 사용하기 위한 (거의)필수 라이브러리
Spring AOP로는 AOP 사용기법에 한계가 존재.

Aspect 생성

package org.xyz;
import org.aspectj.lang.annotation.Aspect;

@Aspect
@Component // 해당 Aspect를 Spring의 Bean으로 등록해 사용
public class UsefulAspect{

}

Pointcut 선언 및 결합

package org.xyz;
import org.aspectj.lang.annotation.Aspect;

@Aspect
@Component // 해당 Aspect를 Spring의 Bean으로 등록해 사용
public class UsefulAspect{

    @Pointcut("execution(public **(..))") //모든 public 메서드 대상
    private void anyPublicOperation(){}

    @Pointcut("within(com.xyz.myapp.trading..*)") //특정 패키지 대상
    private void inTrading(){}

    @Pointcut("anyPublicOperation() && inTrading()")// 두 조건을 모두 만족하는 대상
    private void tradingOperation(){} 
}

Advice 정의

package org.xyz;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.ProceedingJoingPoint;



@Aspect
public class BeforeExample{

    // dataAccessOperation이라는 미리 정의된 포인트컷 바로 전에 doAccesscheck 실행
    @Before("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
    public void doAccessCheck(){...}
}

@Aspect
public class AfterReturningExample{

    // dataAccessOperation이라는 미리 정의된 포인트컷 리턴 후에 doAccesscheck 실행
    @AfterReturning("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
    public void doAccessCheck(){...}
}


@Aspect
public class AroundExample{

    // dataAccessOperation이라는 미리 정의된 포인트컷 전과 후에 doAccesscheck 실행
    @Around("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
    public Object doBasicProfiling(ProceedingJoingPoint pjp) throws Throwable{
        //start stopwatch
        Object retVal = pjp.proceed();
        //stop stopwatch
        return retVal;
}

Validation

Validation?

주로 사용자 또는 타 서버의요청(http request)내용에서 잘못된 내용이 있는지 확인하는 행위
- DATA 검증
필수데이터 존재 유무
문자열의 길이나 숫자형 데이터 값의 범위
email, 신용카드 번호 등 특정 형식에 맞춘 데이터
- 비즈니스 검증
서비스 정책에 따라 데이터를 확인하여 검증
예) 배달앱인 경우 배달요청 시 해당 주문건이 결제완료 건인지 확인 등
경우에 따라 외부 API를 호출하거나 DB의 데이터 조회하여 검증할때도 있음.

Spring에서의 Validation

1. Java Bean Validation(추천..) javaBean으로 간편하게 개별 데이터 검증
최근 가장 많이 활용되는 방법 중 하나이며, 아래 코드 처럼 javaBean내 Annotation으로 검증방법 명시

public class MemberCreationRequest{
    @NotBlank(message="이름을 입력해주세요")
    @Size(max=64 , message="이름의 최대 길이는 64자 입니다")
    private String name;
    @Min(0,"나이는 0보다 커야합니다.")
    private int age;
    @Email("이메일형식이 잘못되었습니다.")
    private int email;
    //Getter&Setter Area...//
}

@PostMapping(value="/member")
public MemberCreationResponse createMember(
    @Valid @RequestBody final MemberCreationRequest memberCreationRequest){...}
)

위 처럼 dto에 어노테이션 명시 후 아래처럼 @Valid어노테이션을 해당 @RequestBody에 달게되면, Java Bean Validation을 수행 한 수 문제가 없을 때만 메서드 내부로 진입된다.
검증실패시에는 MethodargumentNotValidException발생

2. Spring Validator Interface

support : 이 Validator가 동작할 조건을 정의, 주로 class 타입비교
validate : 원하는 검증을 진행

public class Person{
    private String name;
    private int age;
    //getter & setter .... //
}
public class PersonValidator implements Validator{
    public boolean supports(Class clazz){
        return Person.class.equals(clazz);
    }
    public void validate(Object obj,Errors e){
        ValidationUtils.rejectIfEmpty(e,"name","name.empty");
        Person p = (Person) obj;
        if(p.getAge()<0){
            e.rejectValue("age","negativevalue");
        }else if (p.getAge()>110){
            e.rejectValue("age","too.old");
        }
    }
}

주의사항

- validation이 너무 무분별하게 적용되면 테스트/유지보수성 저하
- 가능한 validation은 로직 초기에 수행 후 실패시 exception 발생이 편리함

실무활용패턴(추천)

-요청 dto에서 java bean validation으로 단순 데이터 검증 (1차) -로직 초기에 2차로 비즈니스 검증 수행 후 실패시 Custom Exeption 발생시키고, 예외 처리하여 응답 생성

Data Binding

사용자나 외부 서버의 요청 데이터를 특정 도메인 객체에 저장해 프로그램 Request에 담아주는 것.

Converter<S,T>Interface S(source)라는 타입을 받아 T(Target)이라는 타입으로 변환해주는 인터페이스

[Interface 모양]

package org.springframework.core.conver.converter;

public interface Converter<S,T>{
    T convert(S source);
}

[사용 예시]

GET / user-info
x-auth-user : {"id":123,"name":"Paul"}

//유저 객체
public class XAuthUser{
    private int id;
    private String name;
    ...
    //getter&setter Area
}

@GetMapping("/user-info")
public UserInfoResponse getUserInfo(
    @RequestHeader("x-auth-user") XAuthUser xAuthUser){
        ...(logic)
    }
)

@Component
public class XAuthUserConverter implements Converter<String,XAuthUser>{
    @Override
    public XAuthUser convert(String source){
        return objectMapper.readValue(source,XAuthUser.class);
    }
}

Formatter

특정객체<-> String 간의 변환 담당

[예시]
아래 메서드 설명
print : API요청에 대한 응답을 줄 때, Date형식으로 된 데이터를 특정 locale에 맞춘 String으로 변환
parse : API요청을 받아올 때, String으로 된 “2021-01-01 13:15:00”과 같은 날짜 형식의 데이터를 Date로 변환

package org.springframework.format.datetime;

@Component
public final class DateFormatter implements Formatter<Date>{
    public String print(Date date, Locale locate){
        return getDateFormat(locale).format(date);
    }

    public Date parse(String formatted, Locale locate) throws ParseException {
        return getDateFormat(locale).parse(formatted);    
    }

    //등등.....
}

SpEL

SpEL : Spring Expression Language

#{<expression string>} 방식으로 preperty 설정.

@Component 
public class SimpleComponent{
    @Value("#{ 1 + 1 }")
    int two; // 2

    @Value("#{ 2 eq 2 }")
    boolean isTrue; // true

    @Value("${ server.hostname }")
    String hostName; // www.server.com
    
    //등등 ....
}
TOP