티스토리 뷰
[Spring] 스프링이 제공하는 다양한 유틸성 클래스들(StringUtils, PatternMatchUtils, ReflectionUtils 등)
망나니개발자 2022. 4. 17. 10:00이번에는 스프링이 제공하는 다양한 유틸성 클래스들에 대해서 알아보고자 합니다. 스프링은 다양한 유틸성 클래스들을 만들어 두었는데, 그 중에서도 우리가 사용할 가능성이 있는 유틸성 클래스들 몇가지에 대해 살펴보도록 하겠습니다.
1. 스프링이 제공하는 다양한 유틸성 클래스들
[ 문자열 처리를 위한 StringUtils ]
스프링은 간단한 문자열 처리를 위한 StringUtils를 제공하고 있다. 자주 사용되는 메소드들로는 다음과 같은 것들이 있으니, 필요에 따라 사용하도록 하자.
- hasLength: 해당 문자열이 비어있지 않은지 검사함(공백 문자열이면 true)
- hasText: 해당 문자열이 공백을 포함해 비어있지 않은지 검사함(공백 문자열이면 false)
- containsWhiteSpace: 해당 문자열이 공백을 갖고 있는지 검사함
- trimAllWhitespace: 해당 문자열의 모든 공백을 제거함
- 기타 등등
그 외에도 대/소문자 처리, 배열에 특정 문자열 더하기, 특정 문자열로 자르기, path 관련 처리 등 다양한 기능을 제공하고 있으니 자세한 내용은 공식 문서를 참고하도록 하자.
[ 간단한 패턴 매치를 위한 PatternMatchUtils ]
스프링은 간단한 패턴 매치를 판별하기 위한 PatternMatchUtils를 제공하고 있다. 해당 유틸에서는 표현식들 중에 *만 검사를 해준다. 그러므로 간단히 *을 사용해 패턴 매치를 하는 경우에 사용해주도록 하자.
- simpleMatch: 어떤 문자열이 특정 패턴에 매칭되는지를 검사함
[ 빈에 대한 처리를 위한 BeanUtils ]
스프링은 빈으로 만들 클래스 또는 객체에 대해 처리를 위한 BeanUtils를 제공하고 있다. 스프링은 빈을 생성하기 위한 메소드 자체나 생성에 필요한 기능들을 제공하고 있다.
- findMethod: 클래스에서 선언된 메소드를 찾음
- findDeclaredMethod: 클래스에서 선언된 메소드를 찾음
- getParameterNames: 생성자의 파라미터 목록을 찾음
- getResolvableConstructor: 사용가능한 생성자를 찾음, 파라미터가 여러 개면 적은 것부터 찾음
- instantiateClass: 클래스 또는 생성자로 객체를 생성함
- 기타 등등
findMethod와 findDeclaredMethod는 거의 동일하다고 보면 된다. 다만 내부적으로 먼저 클래스의 getMethod를 호출하는지, getDeclaredMethod를 호출하는지만 다르다.
그 외에도 property 검사나 복사 등 많은 기능들을 제공하고 있지만 대부분 리플렉션을 위한 기능들인데, 스프링에서는 보다 많은 리플렉션 기능을 제공하는 ReflectionUtils 역시 존재한다.
[ 리플렉션 사용을 위한 ReflectionUtils ]
스프링은 리플렉션 API를 보다 편리하게 사용하기 위한 ReflectionUtils를 제공한다. 스프링은 내부적으로 리플렉션을 적극 활용하고 있기 때문에 리플렉션이 필요한 상황이라면 해당 클래스를 살펴보도록 하자.
- accessibleConstructor: 접근가능한 생성자를 찾음
- declaresException: 메소드가 명시적으로 throw하고 있는지를 검사함
- findField: 클래스에서 특정 필드를 찾음
- findMethod: 클래스에서 특정 메소드를 찾음
- getDeclaredMethods: 클래스의 모든 메소드를 찾음
- getAllDeclareMethods: 부모 클래스를 포함해 클래스의 모든 메소드를 찾음
- getField: 객체에서 필드의 실제 값을 찾음
- setField: 객체에서 필드의 실제 값을 설정함
- invokeMethod: 특정 메소드를 호출함
- makeAccessible: 접근 가능여부를 true로 설정함
- 기타 등등
getField나 setField처럼 실제 객체의 값을 조작하거나 메소드를 호출하는 경우에는 접근 제어자가 private이면 접근이 불가능하다. 만약 접근이 불가능한 상태에서 해당 기능을 호출하려고 하면 다음과 같은 예외가 발생한다. 그러므로 접근 가능 여부를 true로 바꿔주어야 하는데, 필드 객체의 setAccessible를 true로 설정하던가 ReflectionUtils에서 makeAccesible을 호출해주어야 한다.
java.lang.IllegalStateException: Could not access method or field
일반적인 비지니스 애플리케이션을 개발한다면 사실 프로덕션 코드에서 리플렉션을 사용할 경우는 거의 없다. 라이브러리나 프레임워크 등을 개발할 때 주로 사용될 수 있다. 하지만 테스트 코드에서는 리플렉션이 필요한 상황이 있는데, 대표적인 경우가 @Value로 가져오는 값을 설정해줄 때이다. 일반적으로 private으로 변수를 선언하므로 리플렉션으로 직접 변수를 set해주어야 한다. 테스트 코드에서는 ReflectionTestUtils를 제공하므로 테스트 코드에서 리플렉션을 사용할 때 참고하도록 하자.
- getField: 객체에서 필드의 실제 값을 찾음
- setField: 객체에서 필드의 실제 값을 설정함
- invokeMethod: 객체의 메소드를 호출함
- 기타 등등
ReflecitonTestUtils의 메소드는 접근 가능이 불가능한 상태여도 바로 호출이 가능하도록 내부적으로 makeAccessible을 호출해주고 있다. 그러므로 프로덕션 코드에서만 사용할 수 있는 ReflectionUtils보다 편리하게 리플렉션을 사용할 수 있다.
[ 파일 복사를 위한 FileCopyUtils ]
스프링은 파일 복사를 위한 FileCopyUtils를 제공하고 있다. 파일을 바이트 배열로 복사하거나 문자열로 복사하는 기능도 제공하고 있으므로 필요에 따라 참고해서 사용하도록 하자.
- copy: 파일을 복사함
- copyToByteArray: 바이트 배열로 파일을 복사함
- copyToString: 문자열로 파일을 복사함
[ 시스템 프로퍼티를 위한 SystemPropertyUtils ]
스프링은 시스템 프로퍼티를 처리하기 위한 SystemPropertyUtils를 제공하고 있다. 예를 들어 "myname"로 "mangkyu"인 시스템 프로퍼티가 존재한다고 하자. 그러면 Spring은 이 값을 얻기 위해 ${myname}으로 placeholder를 사용하는데, 이 값을 치환해주는 것이 SystemPropertyUtils이다. resolvePlaceholders 메소드는 오버라이딩이 되어 있는데, 경우에 따라서 값이 없으면 에러를 발생시키거나 무시시키거나 할 수 있다.
- resolvePlaceholders: placeholder에 해당하는 값을 치환함
[ InputStream, OutputStream 처리를 위한 StreamUtils ]
스프링은 InputStream, OutputStream 처리를 위한 StreamUtils를 제공하고 있다. 예를 들어 InputStream에 있는 내용을 String 으로 가져오는 등의 작업을 해야 하는 경우에 StreamUtils를 사용하면 된다. 상당히 자주 사용되어 알아두면 편리하다.
- copy: 입력(byte[], InputStream, String)을 출력 스트림으로 복사함
- copyRange: 주어진 입력 스트림의 특정 범위 만큼을 출력 스트림으로 복사함
- copyToByteArray: 입력 스트림을 byte[]로 복사함
- copyToString: 입력 스트림을 String으로 복사함
- 기타 등등
2. 스프링이 제공하는 다양한 유틸성 클래스들의 특징
[ 추상 클래스로 선언된 유틸성 클래스들 ]
앞서 살펴보았듯 스프링은 다양한 유틸성 클래스들을 제공하고 있다. 스프링은 내부적으로 유틸성 클래스에 대한 컨벤션을 가지고 있는데, 그것은 바로 모든 유틸성 클래스를 추상 클래스로 선언한다는 것이다. 대표적으로 PatternMatchUtils를 살펴보도록 하자.
public abstract class PatternMatchUtils {
public static boolean simpleMatch(@Nullable String pattern, @Nullable String str) {
...
}
public static boolean simpleMatch(@Nullable String[] patterns, String str) {
...
}
}
일반적으로 유틸성 클래스는 final 클래스로 선언하고 private 생성자를 만들어 두는 것이 좋다. 하지만 스프링은 초기부터 이러한 컨벤션을 가지고 있었기 때문에 일관성을 갖추고자 모두 abstract로 맞춰주는 작업도 진행한다. 실제로 스프링의 작업 내역을 보면 이러한 흔적들이 남아있는 것을 확인할 수 있다. 물론 추상 클래스는 익명 객체로 생성이 가능하기 때문에 final 클래스 + private 생성자 조합이 더 좋다.
위에서 적은 것들 외에도 Class의 이름과 메소드 등을 위한 ClassUtils와 Aop 관련된 기능을 위한 AopUtils 와 테스트 코드에서 사용하기 위한 AopTestUtils, 트랜잭션 처리를 위한 TransactionSynchronizationUtils 등도 제공하고 있다. 실제로 많은 유틸성 클래스들을 제공하고 있으니 한번 살펴보도록 하자.