안녕하세요! 저는 일상 개발 업무 중에 Spring 프레임워크에 내장된 '개발자의 강력한 조력자'를 발견했습니다. 오늘은 이 훌륭한 기능에 대해 여러분과 공유하고자 합니다.
일상 개발 작업에서 같은 코드를 반복해서 작성하고 계신가요? 저도 처음 회사에 입사했을 때 문자열 처리나 날짜 계산을 위한 유틸리티 클래스를 직접 만들었습니다. 어느 날 선배가 "그거, Spring이 제공하고 있어"라고 알려줘서 놀랐습니다!
Spring 프레임워크에는 사실 I/O 작업, Bean 변환, 조건 판정 등 다양한 상황에서 사용할 수 있는 강력한 유틸리티 클래스가 내장되어 있습니다. 이것들을 사용하면 단 한 줄의 코드로 수십 줄의 처리를 구현할 수 있습니다. 예를 들어, StringUtils.hasText 메소드 하나로 null 판정, 빈 문자열 판정, 공백 문자 판정을 포함한 8줄 이상의 코드가 필요 없어집니다. 말 그대로 개발자의 '스위스 아미 나이프'입니다!
이 글에서는 Spring 내장 24개의 강력한 유틸리티 클래스를 소개합니다. 이것들을 활용하면 개발 효율이 크게 향상될 것입니다!
이 유틸리티들은 spring-beans 패키지에 포함되어 있으며, 매우 유용한 4개의 클래스를 제공합니다.
BeanUtils
JavaBeans의 정적 유틸리티 메소드: Bean의 인스턴스화, Bean 프로퍼티 타입 확인, Bean 프로퍼티 복사 등에 사용합니다. 다음은 사용 예입니다:
```java
/**1.객체 프로퍼티 복사*/
User source = new User(1L, "PackXg") ;
User target = new User() ;
BeanUtils.copyProperties(source, target) ;
/**2.객체 인스턴스화*/
User instance = BeanUtils.instantiateClass(User.class) ;
/**3.지정된 타입이 Bean 프로퍼티와 데이터 바인딩 시나리오의 '단순' 값 타입인지 확인*/
Class<?> type = User.class.getDeclaredField("name").getType() ;
boolean isSimpleType = BeanUtils.isSimpleValueType(type) ;
```
▼ 공식 문서:
https://docs.spring.io/spring-framework/docs/1.2.8/javadoc-api/org/springframework/beans/BeanUtils.html
BeanFactoryUtils
Bean 팩토리의 편리한 조작 메소드, 특히 ListableBeanFactory 인터페이스의 확장 기능을 제공합니다:
- Bean의 수, 이름, 인스턴스를 반환
- 중요한 특징: Bean 팩토리의 중첩된 계층 구조 처리 지원(ListableBeanFactory 인터페이스의 네이티브 메소드는 이 기능을 지원하지 않음)
다음은 사용 예입니다:
```java
ListableBeanFactory beanFactory = ... ;
/**1.지정된 타입 또는 그 서브타입의 단일 Bean을 반환(조상 팩토리를 검색하지 않음)*/
CommonDAO dao = BeanFactoryUtils.beanOfType(beanFactory, CommonDAO.class) ;
/**2.지정된 어노테이션 타입을 가진 모든 Bean의 이름을 가져옴*/
String[] beanNames = BeanFactoryUtils.beanNamesForAnnotationIncludingAncestors(beanFactory, Pack.class) ;
/**3.지정된 타입 또는 그 서브타입의 모든 Bean을 반환*/
Map<String, CommonDAO> beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(beanFactory, CommonDAO.class) ;
```
▼ 공식 문서:
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/factory/BeanFactoryUtils.html
BeanFactoryAnnotationUtils
Spring 특정 어노테이션(Spring의 @Qualifier 어노테이션 등)에 기반하여 Bean을 검색하기 위한 편리한 메소드입니다. 다음은 사용 예입니다:
```java
// 먼저, 다음과 같은 beans를 정의합니다
@Component
@Qualifier("product")
public class ProductDAO implements CommonDAO {
}
@Component
@Qualifier("user")
public class UserDAO implements CommonDAO {
}
ConfigurableListableBeanFactory beanFactory = ... ;
// @Qualifier 어노테이션을 사용하여 이름이 "user"인 Bean을 가져옴
CommonDAO userDAO = BeanFactoryAnnotationUtils.qualifiedBeanOfType(beanFactory, CommonDAO.class, "user") ;
// @Qualifier 어노테이션을 사용하여 이름이 "product"인 Bean을 가져옴
CommonDAO productDAO = BeanFactoryAnnotationUtils.qualifiedBeanOfType(beanFactory, CommonDAO.class, "product") ;
```
▼ 공식 문서:
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/factory/annotation/BeanFactoryAnnotationUtils.html
BeanDefinitionReaderUtils
Bean 정의 리더 구현을 위한 유틸리티 메소드, 주로 내부 사용을 위한 것입니다. 다음은 사용 예입니다:
```java
BeanDefinitionRegistry registry = ... ;
// 1.BeanDefinition에서 beanName 생성
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
.genericBeanDefinition(UserDAO.class)
.getBeanDefinition() ;
String beanName = BeanDefinitionReaderUtils.generateBeanName(beanDefinition, registry) ;
// 2.Bean 등록
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(beanDefinition, beanName) ;
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry) ;
```
▼ 공식 문서:
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/factory/support/BeanDefinitionReaderUtils.html
이 유틸리티들은 spring-aop 패키지에 포함되어 있으며, 매우 유용한 6개의 클래스를 제공합니다.
AopConfigUtils
AOP 자동 프록시 생성자의 등록을 처리하기 위한 유틸리티 클래스입니다. 다음은 사용 예입니다:
```java
BeanDefinitionRegistry registry = ... ;
/**1.이 메소드는 자동으로 InfrastructureAdvisorAutoProxyCreator를 등록합니다*/
// 이 클래스의 등록은 동적 프록시를 위해 사용됩니다
AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry) ;
/**2.이 메소드는 자동으로 AnnotationAwareAspectJAutoProxyCreator를 등록합니다*/
// 마찬가지로, 이 클래스도 동적 프록시를 위해 사용됩니다
AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry) ;
// Spring Boot 컨테이너 시작 시 관련 스타터(spring-boot-starter-aop 등)가 존재하는 경우,
// 위의 메소드를 통해 관련 BeanPostProcessor가 등록됩니다.
```
▼ 공식 문서:
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/aop/config/AopConfigUtils.html
AopProxyUtils
AOP 프록시 팩토리의 유틸리티 메소드입니다. 다음은 사용 예입니다:
```java
/**1.지정된 프록시 클래스 뒤에 있는 싱글톤 타겟 객체(원본 객체)를 가져옴*/
// 이 객체가 Spring에 의해 생성된 프록시 객체인 경우
UserDAO proxy = ... ;
// 이 메소드는 프록시 객체 뒤에 있는 원본 객체를 반환합니다
Object targetObject = AopProxyUtils.getSingletonTarget(proxy) ;
/**2.현재 프록시 객체가 구현하고 있는 인터페이스를 반환합니다(Spring의 규격으로 구현된 인터페이스 제외)*/
Class<?>[] userInterfaces = AopProxyUtils.proxiedUserInterfaces(proxy) ;
```
▼ 공식 문서:
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/aop/framework/AopProxyUtils.html
AutoProxyUtils
자동 프록시를 지원하는 컴포넌트를 위한 유틸리티로, 주로 프레임워크 내부에서 사용됩니다. 다음은 사용 예입니다:
```java
ConfigurableListableBeanFactory beanFactory = ... ;
/**1.지정된 bean에 해당하는 원본 타겟 클래스를 가져옴*/
Class<?> targetClass = AutoProxyUtils.determineTargetClass(beanFactory, "userDAO") ;
/**2.지정된 Bean이 그 타겟 클래스(인터페이스가 아닌)에 기반하여 프록시되어야 하는지 판단*/
// 즉, JDK 프록시인지 CGLIB 프록시인지 판단합니다
AutoProxyUtils.shouldProxyTargetClass(beanFactory, "userDAO") ;
```
▼ 공식 문서:
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/aop/framework/autoproxy/AutoProxyUtils.html
ScopedProxyUtils
스코프가 있는 프록시를 생성하기 위한 유틸리티 클래스로, 일반적으로 ScopedProxyBeanDefinitionDecorator와 ClassPathBeanDefinitionScanner에 의해 사용됩니다. 다음은 사용 예입니다:
```java
try (GenericApplicationContext context = new GenericApplicationContext()) {
BeanDefinition beanDefinition = BeanDefinitionBuilder
.genericBeanDefinition(UserDAO.class)
.getBeanDefinition() ;
BeanDefinitionHolder definition = new BeanDefinitionHolder(beanDefinition, "userDAO") ;
BeanDefinitionHolder scopedProxy = ScopedProxyUtils.createScopedProxy(definition, context, true) ;
context.registerBeanDefinition("userDAO", scopedProxy.getBeanDefinition()) ;
context.refresh();
UserDAO dao = context.getBean(UserDAO.class) ;
System.err.println(dao.getClass()) ;
}
```
위의 코드를 실행하면 다음과 같이 출력됩니다:
```
class com.pack.UserDAO$$SpringCGLIB$$0
```
▼ 공식 문서:
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/aop/scope/ScopedProxyUtils.html
AopUtils
AOP를 지원하기 위한 유틸리티 메소드입니다. 다음은 사용 예입니다:
```java
UserDAO userDAO = ... ;
/**1.bean에 해당하는 원본 타겟 Class를 가져옴*/
AopUtils.getTargetClass(userDAO) ;
/**2.프록시 객체인지 판단(JDK와 CGLIB 모두 포함)*/
AopUtils.isAopProxy(userDAO) ;
/**3.지정된 Advisor(애스펙트)가 지정된 Class 객체에 적용할 수 있는지 판단*/
Advisor advisor = ... ;
AopUtils.canApply(advisor, UserDAO.class) ;
```
▼ 공식 문서:
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/aop/support/AopUtils.html
ClassFilters
ClassFilter를 조합하기 위한 정적 유틸리티 메소드입니다. Spring의 내부에서는 클래스가 프록시 가능한지 판단할 때 ClassFilter를 사용하며, 이 유틸리티 클래스는 여러 ClassFilter를 결합하여 판단합니다(다중 조건 판단). 다음은 사용 예입니다:
```java
ClassFilter f1 = new ClassFilter() {
public boolean matches(Class<?> clazz) {
return clazz.getPackageName().startsWith("com.pack.aop") ;
}
};
ClassFilter f2 = new ClassFilter() {
public boolean matches(Class<?> clazz) {
return clazz.isAssignableFrom(CommonDAO.class) ;
}
};
ClassFilter[] classFilters = new ClassFilter[] {f1, f2} ;
/**1.지정된 '클래스 필터'(ClassFilter)의 세트를 사용하여 클래스를 필터링하고,
클래스가 필터 중 하나(또는 전부)와 일치하는 경우, 그 클래스가 선택됩니다*/
ClassFilter classFilter = ClassFilters.union(classFilters) ;
/**2.지정된 필터 인스턴스의 논리적 부정을 나타내는 클래스 필터를 반환합니다(원래 필터 조건을 만족하지 않는 모든 클래스와 일치)*/
classFilter = ClassFilters.negate(f1) ;
/**3.지정된 ClassFilter 세트가 모두 일치해야 합니다*/
classFilter = ClassFilters.intersection(classFilters) ;
```
▼ 공식 문서:
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/aop/support/ClassFilters.html
이 유틸리티들은 spring-core 패키지에 포함되어 있으며, 매우 유용한 14개의 클래스를 제공합니다.
ReflectUtils
이 유틸리티는 주로 리플렉션을 통해 관련 작업을 수행하기 위해 사용됩니다. 다음은 사용 예입니다:
```java
/**1.인스턴스 생성*/
ReflectUtils.newInstance(UserDAO.class) ;
/**2.Method 객체 검색*/
ReflectUtils.findDeclaredMethod(UserDAO.class, "create", new Class<?>[] {String.class}) ;
/**3.java bean에 해당하는 모든 getter 메소드 가져오기*/
PropertyDescriptor[] getters = ReflectUtils.getBeanGetters(UserDAO.class);
```
▼ 공식 문서:
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/cglib/core/ReflectUtils.html
ResourcePatternUtils
지정된 URL이 ResourcePatternResolver를 통해 로드 가능한 리소스 위치인지 판단하기 위한 유틸리티 클래스입니다. 다음은 사용 예입니다:
```java
String resourceLocation = "classpath:com/pack.properties";
boolean isUrl = ResourcePatternUtils.isUrl(resourceLocation) ;
System.err.println(isUrl) ;
resourceLocation = "com/pack.properties";
isUrl = ResourcePatternUtils.isUrl(resourceLocation) ;
System.err.println(isUrl) ;
resourceLocation = "file:///d:/pack.properties";
isUrl = ResourcePatternUtils.isUrl(resourceLocation) ;
System.err.println(isUrl) ;
resourceLocation = "http://www.pack.com/pack.properties";
isUrl = ResourcePatternUtils.isUrl(resourceLocation) ;
System.err.println(isUrl) ;
```
**출력 결과:**
```
true
false
true
true
```
▼ 공식 문서:
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/io/support/ResourcePatternUtils.html
DigestUtils
다이제스트(해시값)를 계산하기 위해 사용됩니다. 다음은 사용 예입니다:
```java
String hex = DigestUtils.md5DigestAsHex("Spring Boot3 실전 사례 200강".getBytes()) ;
// 출력: e329f65bf350e618cf3a183aba20e362
System.err.println(hex) ;
```
▼ 공식 문서:
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/util/DigestUtils.html
LogFormatUtils
로그 포맷 유틸리티 클래스로, 로그의 길이를 제어합니다. 다음은 사용 예입니다:
```java
/**1.길이 제한 없는 출력*/
String ret = LogFormatUtils.formatValue(new User(1L, "PackXg"), false) ;
System.err.println(ret) ;
/**2.길이 제한 있는 출력*/
ret = LogFormatUtils.formatValue(new User(1L, "PackXg"), 10, true) ;
System.err.println(ret) ;
/**3.커스텀 로그 출력*/
Log logger = LogFactory.getLog(User.class) ;
LogFormatUtils.traceDebug(logger, traceEnabled -> "Spring Boot3 실전 사례 200강");
```
**출력 결과:**
```
User [id=1, name=PackXg]
User [id=1 (truncated)...
```
▼ 공식 문서:
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/log/LogFormatUtils.html
PropertiesLoaderUtils
java.util.Properties를 로드하기 위한 편리한 유틸리티 메소드로, 입력 스트림의 표준 처리를 제공합니다. 다음은 사용 예입니다:
```java
/**1.리소스 로드*/
Properties properties = PropertiesLoaderUtils.loadAllProperties("pack.properties") ;
System.err.println(properties) ;
/**2.리소스 채우기*/
Properties prop = new Properties() ;
Resource resource = new ClassPathResource("pack.properties") ;
PropertiesLoaderUtils.fillProperties(prop, resource) ;
System.err.println(prop) ;
```
**출력 결과:**
```
{pack.app.version=1.0.0, pack.app.title=xxxooo}
{pack.app.version=1.0.0, pack.app.title=xxxooo}
```
▼ 공식 문서:
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/io/support/PropertiesLoaderUtils.html
AnnotatedElementUtils
AnnotatedElement(클래스, 메소드, 필드 등 어노테이션 가능한 요소) 상에서 어노테이션, 메타 어노테이션, 반복 어노테이션을 검색하기 위한 일반적인 유틸리티 메소드입니다. 다음은 사용 예입니다:
```java
/**1.클래스 상의 @Component 어노테이션 검색*/
Set<Component> annotations = AnnotatedElementUtils
.findAllMergedAnnotations(UserDAO.class, Component.class) ;
// 출력: [@org.springframework.stereotype.Component("")]
System.err.println(annotations) ;
/**2.지정된 어노테이션(@Qualifier)의 모든 속성 가져오기*/
MultiValueMap<String,Object> attributes = AnnotatedElementUtils
.getAllAnnotationAttributes(UserDAO.class, Qualifier.class.getName()) ;
// 출력: {value=[user]}
System.err.println(attributes) ;
```
▼ 공식 문서:
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/annotation/AnnotatedElementUtils.html
ClassUtils
java.lang.Class에 관한 다양한 유틸리티 메소드의 컬렉션입니다. 다음은 사용 예입니다:
```java
/**1.클래스 로드*/
Class<?> clazz = ClassUtils.forName("com.pack.utils.zdomain.User", ClassUtilsTest.class.getClassLoader()) ;
System.err.println(clazz) ;
/**2.메소드의 Method 객체 가져오기*/
Method method = ClassUtils.getMethod(clazz, "getId") ;
System.err.println(method) ;
/**3.객체가 구현하고 있는 모든 인터페이스 가져오기*/
Class<?>[] interfaces = ClassUtils.getAllInterfaces(new UserDAO()) ;
System.err.println(Arrays.toString(interfaces)) ;
```
▼ 공식 문서:
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/util/ClassUtils.html
OrderUtils
객체 타입 선언에 기반하여 그 실행 순서를 결정하기 위한 일반적인 유틸리티 클래스로, Spring의 Order 어노테이션과 Jakarta EE의 Priority 어노테이션을 지원합니다. 다음은 사용 예입니다:
```java
/**1.클래스 상의 어노테이션 @Order로 설정된 값 가져오기*/
Integer order = OrderUtils.getOrder(UserDAO.class) ;
System.err.println(order);
/**2.메소드 상의 @Order 값 가져오기*/
order = OrderUtils.getOrder(AppConfig.class.getMethod("userDAO")) ;
System.err.println(order);
```
▼ 공식 문서:
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/annotation/OrderUtils.html
FileCopyUtils
파일과 스트림의 복사를 위한 간단한 유틸리티 메소드의 컬렉션입니다. 모든 복사 메소드는 4096바이트의 블록 크기를 사용하며, 작업 완료 후 영향을 받는 모든 스트림을 자동으로 닫습니다. 다음은 사용 예입니다:
```java
byte[] data = "Spring Boot3 실전 사례 200강".getBytes(StandardCharsets.UTF_8) ;
/**1.데이터를 파일에 쓰기*/
FileCopyUtils.copy(data, new File("f:\\1.txt")) ;
/**2.데이터를 바이트 스트림에 쓰기*/
ByteArrayOutputStream baos = new ByteArrayOutputStream() ;
FileCopyUtils.copy(data, baos) ;
/**3.파일의 내용 읽기*/
byte[] array = FileCopyUtils.copyToByteArray(new File("f:\\1.txt")) ;
System.err.println(new String(array, StandardCharsets.UTF_8)) ;
```
▼ 공식 문서:
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/util/FileCopyUtils.html
FileSystemUtils
파일 시스템을 조작하기 위한 유틸리티 메소드의 컬렉션입니다. 다음은 사용 예입니다:
```java
File src = null ;
File dest = null ;
/**1.파일을 재귀적으로 복사*/
FileSystemUtils.copyRecursively(src, dest) ;
/**2.지정된 파일 삭제—타겟이 디렉토리인 경우, 포함된 모든 중첩된 디렉토리와 파일을 재귀적으로 삭제합니다*/
FileSystemUtils.deleteRecursively(src) ;
```
▼ 공식 문서:
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/util/FileSystemUtils.html
MimeTypeUtils
MIME 타입(미디어 타입)에 관한 다양한 유틸리티 메소드의 컬렉션입니다. 다음은 사용 예입니다:
```java
/**1.문자열을 MimeType 타입으로 파싱*/
MimeType mimeType = MimeTypeUtils.parseMimeType("application/json") ;
System.err.println(mimeType) ;
```
▼ 공식 문서:
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/util/MimeTypeUtils.html
NumberUtils
숫자 변환과 파싱에 관한 다양한 유틸리티 메소드의 컬렉션입니다. 다음은 사용 예입니다:
```java
/**1.문자열을 지정된 타입으로 파싱*/
Double number = NumberUtils.parseNumber("20", Double.class) ;
System.err.println(number) ;
/**2.한 타입에서 지정된 타입으로 변환*/
Number n = 20 ;
Integer ret = NumberUtils.convertNumberToTargetClass(n, Integer.class) ;
System.err.println(ret) ;
```
▼ 공식 문서:
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/util/NumberUtils.html
StreamUtils
스트림(Stream)을 처리하기 위한 간단한 유틸리티 메소드의 컬렉션입니다. 이 클래스의 복사 메소드는 FileCopyUtils의 것과 비슷하지만, 차이점은 영향을 받는 모든 스트림이 작업 완료 후 자동으로 닫히지 않는(열린 상태로 유지) 점입니다. 모든 복사 메소드는 8192바이트(8KB)의 블록 크기를 사용하여 데이터를 전송합니다. 다음은 사용 예입니다:
```java
InputStream in = ... ;
OutputStream out = ... ;
/**1.입력 스트림에서 출력 스트림으로 복사*/
StreamUtils.copy(in, out) ;
/**2.입력 스트림을 바이트 배열로 복사*/
StreamUtils.copyToByteArray(in) ;
```
▼ 공식 문서:
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/util/StreamUtils.html
```java
String s = "" ;
/**1.문자열이 비어 있는지 판단*/
StringUtils.hasLength(s) ;
/**2.문자열에 공백 문자가 존재하는지 판단; 공백 문자의 유무는 Character.isWhitespace로 판단*/
StringUtils.containsWhitespace(s) ;
/**3.컬렉션을 문자열 배열로 변환*/
StringUtils.toStringArray(List.of("a", "b", "c")) ;
```
▼ 공식 문서:
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/util/StringUtils.htmlSpring 내장 유틸리티 클래스는 Bean, AOP, IO, 문자열 처리 등의 일반적인 시나리오를 커버하고 있지만, API 설계, 디버깅, 모킹, 테스트, 문서 관리에는 직접적인 도구가 없습니다.
Apidog의 가치 포인트:
이번에 소개한 Spring의 유틸리티 클래스를 활용하여 여러분의 개발 효율이 향상되기를 바랍니다. 질문이나 의견이 있으시면 댓글로 알려주세요!