while(1) work();
반응형

유효성 검사, 데이터 바인딩 및 타입 변환

유효성 검사를 비즈니스 로직으로 간주하는 데에는 장단점이 있으며, Spring은 유효성 검사 및 데이터 바인딩을 위한 설계를 제공합니다. 특히 유효성 검사는 웹 계층에 묶여 있지 않아야 하고 지역화가 쉬워야 하며 사용 가능한 모든 유효성 검사기를 플러그인할 수 있어야 합니다. 이러한 우려를 고려하여 Spring은 애플리케이션의 모든 계층에서 기본적이면서도 유용하게 사용할 수 있는 유효성 검사기 계약을 제공합니다.

데이터 바인딩은 사용자 입력을 애플리케이션의 도메인 모델(또는 사용자 입력을 처리하는 데 사용하는 모든 객체)에 동적으로 바인딩할 수 있도록 하는 데 유용합니다. Spring은 이를 위해 적절한 이름의 DataBinder를 제공합니다. 유효성 검사기와데이터 바인더는 유효성 검사 패키지를 구성하며, 주로 웹 계층에서 사용되지만 이에 국한되지 않습니다.

BeanWrapper는 Spring 프레임워크의 기본 개념이며 많은 곳에서 사용됩니다. 그러나 BeanWrapper를 직접 사용할 필요는 없습니다. 그러나 이 문서는 참조 문서이므로 몇 가지 설명이 필요할 것 같습니다. 이 장에서 BeanWrapper를 설명하는 이유는 데이터를 객체에 바인딩하려고 할 때 주로 사용하게 될 가능성이 높기 때문입니다.

Spring의 DataBinder와 하위 수준의 BeanWrapper는 모두 PropertyEditorSupport구현을 사용하여 속성 값을 구문 분석하고 형식을 지정합니다. PropertyEditor 및PropertyEditorSupport 유형은 JavaBeans 사양의 일부이며 이 장에서도 설명합니다. Spring의 core.convert 패키지는 일반적인 유형 변환 기능과 UI 필드 값의 형식을 지정하기 위한 상위 수준 형식 패키지를 제공합니다. 이러한 패키지를 PropertyEditorSupport구현에 대한 더 간단한 대안으로 사용할 수 있습니다. 이 장에서도 이 패키지에 대해 설명합니다.

Spring은 설정 인프라와 Spring의 자체 유효성 검사기 계약에 대한 어댑터를 통해 Java Bean 유효성 검사를 지원합니다. 애플리케이션은 자바 빈 유효성 검사에 설명된 대로 전역적으로 한 번 Bean 유효성 검사를 활성화하고 모든 유효성 검사 요구사항에 대해 독점적으로 사용할 수 있습니다. 웹 계층에서 애플리케이션은 데이터 바인더 구성에 설명된 대로 데이터 바인더별로 컨트롤러 로컬 Spring유효성 검사기 인스턴스를 추가로 등록할 수 있으며, 이는 사용자 정의 유효성 검사 로직을 연결하는 데 유용할 수 있습니다.

 

Spring의 유효성 검사기 인터페이스를 사용한 유효성 검사

Spring에는 객체의 유효성을 검사하는 데 사용할 수 있는 유효성 검사기 인터페이스가 있습니다.유효성 검사기 인터페이스는 오류 객체를 사용하여 작동하므로 유효성을 검사하는 동안 유효성 검사기가 오류 객체에 유효성 검사 실패를 보고할 수 있습니다.

다음 작은 데이터 객체의 예를 살펴봅시다:

public class Person {

	private String name;
	private int age;

	// the usual getters and setters...
}

다음 예제는 org.springframework.validation.Validator 인터페이스의 다음 두 메서드를 구현하여 Person 클래스에 대한 유효성 검사 동작을 제공합니다:

  • supports(Class): 이 유효성 검사기가 제공된 클래스의 인스턴스를 유효성 검사할 수 있습니까?
  • validate(Object, org.springframework.validation.Errors): 지정된 객체의 유효성을 검사하고, 유효성 검사 에러가 있는 경우, 지정된 Errors 객체에 등록합니다.

특히 Spring 프레임워크에서 제공하는ValidationUtils 헬퍼 클래스를 알고 있다면 유효성 검사기를 구현하는 것은 매우 간단합니다. 다음 예제는 Person 인스턴스에 대한 유효성 검사기를 구현합니다:

public class PersonValidator implements Validator {

	/**
	 * This Validator validates only Person instances
	 */
	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.darn.old");
		}
	}
}

이름 속성이 null이거나 빈 문자열인 경우 ValidationUtils 클래스의 정적 rejectIfEmpty(..) 메서드를 사용하여 거부합니다. 앞서 표시된 예제 외에 어떤 기능을 제공하는지 보려면ValidationUtils 자바독을 살펴보세요.

리치 객체에서 중첩된 각 객체의 유효성을 검사하기 위해 단일 유효성 검사기 클래스를 구현할 수도 있지만, 자체 유효성 검사기 구현에서 각 중첩된 객체 클래스에 대한 유효성 검사 로직을 캡슐화하는 것이 더 나을 수 있습니다. "리치" 객체의 간단한 예로 두 개의 문자열속성(이름과 두 번째 이름)과 복잡한 주소 객체로 구성된 Customer를 들 수 있습니다. Address 객체는 Customer 객체와 독립적으로 사용할 수 있으므로 별도의 AddressValidator가구현되어 있습니다. 복사 및 붙여넣기를 사용하지 않고 CustomerValidator가 AddressValidator 클래스에 포함된 로직을 재사용하려면 다음 예제에서 보는 바와 같이 CustomerValidator 내에 종속성 주입 또는 인스턴스화할 수 있습니다:

public class CustomerValidator implements Validator {

	private final Validator addressValidator;

	public CustomerValidator(Validator addressValidator) {
		if (addressValidator == null) {
			throw new IllegalArgumentException("The supplied [Validator] is " +
				"required and must not be null.");
		}
		if (!addressValidator.supports(Address.class)) {
			throw new IllegalArgumentException("The supplied [Validator] must " +
				"support the validation of [Address] instances.");
		}
		this.addressValidator = addressValidator;
	}

	/**
	 * This Validator validates Customer instances, and any subclasses of Customer too
	 */
	public boolean supports(Class clazz) {
		return Customer.class.isAssignableFrom(clazz);
	}

	public void validate(Object target, Errors errors) {
		ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "field.required");
		ValidationUtils.rejectIfEmptyOrWhitespace(errors, "surname", "field.required");
		Customer customer = (Customer) target;
		try {
			errors.pushNestedPath("address");
			ValidationUtils.invokeValidator(this.addressValidator, customer.getAddress(), errors);
		} finally {
			errors.popNestedPath();
		}
	}
}

유효성 검사 오류는 유효성 검사기에 전달된 Errors 객체에 보고됩니다. Spring Web MVC의 경우 <spring:bind/> 태그를 사용하여 오류 메시지를 검사할 수 있지만 Errors 객체를 직접 검사할 수도 있습니다. 제공하는 메서드에 대한 자세한 내용은 자바독에서 확인할 수 있습니다.

바인딩 프로세스를 포함하지 않고 주어진 객체의 즉각적인 유효성 검사를 위해 유효성 검사기가 로컬로 호출될 수도 있습니다. 6.1부터 기본적으로 사용할 수 있는 새로운Validator.validateObject(Object) 메서드를 통해 이 작업이 단순화되었으며, 검사할 수 있는 간단한 ´Errors´ 표현을 반환합니다(일반적으로 hasErrors()또는 오류 요약 메시지를 예외로 전환하는 새로운 failOnError 메서드를 호출합니다(예: validator.validateObject(myObject).failOnError(IllegalArgumentException::new)).

 

Data Binding

데이터 바인딩은 사용자 입력이 속성 경로를 키로 사용하는 맵인 대상 객체에 JavaBeans 규칙에 따라 사용자 입력을 바인딩하는 데 유용합니다. 이를 지원하는 기본 클래스는DataBinder이며, 사용자 입력을 바인딩하는 두 가지 방법을 제공합니다:

  • 생성자바인딩 - 사용자 입력을 공용 데이터 생성자에 바인딩하여 사용자 입력에서 생성자 인수 값을 조회합니다.
  • 속성 바 인딩 - 사용자 입력을 세터에 바인딩하여 사용자 입력의 키를 대상 개체 구조의 속성과 일치시킵니다.

생성자 바인딩과 속성 바인딩을 모두 적용하거나 하나만 적용할 수 있습니다.

Constructor Binding

생성자 바인딩을 사용하려면

  1. 대상 객체로 null을 사용하여 DataBinder를 생성합니다.
  2. TargetType을 대상 클래스로 설정합니다.
  3. 생성자를 호출합니다.

대상 클래스에는 인수가 있는 단일 공개 생성자 또는 단일 비공개 생성자가 있어야 합니다. 생성자가 여러 개 있는 경우 기본 생성자가 있으면 기본 생성자가 사용됩니다.

기본적으로 생성자 매개변수 이름은 인수 값을 조회하는 데 사용되지만 NameResolver를 구성할 수 있습니다. Spring MVC와 WebFlux는 모두 생성자 매개 변수의 @BindParam 어노테이션을 통해 바인딩할 값의 이름을 사용자 정의할 수 있도록 합니다.

사용자 입력을 변환하기 위해 필요에 따라유형 변환이 적용됩니다. 생성자 매개 변수가 객체인 경우 동일한 방식으로 재귀적으로 구성되지만 중첩된 속성 경로를 통해 구성됩니다. 즉, 생성자 바인딩은 대상 객체와 그 객체에 포함된 모든 객체를 모두 생성합니다.

바인딩 및 변환 오류는 데이터 바인더의 BindingResult에 반영됩니다. 타겟이 성공적으로 생성되면 생성 호출 후 생성된 인스턴스로 타겟이 설정됩니다.

Property Binding with BeanWrapper

Org.springframework.beans 패키지는 JavaBeans 표준을 준수합니다. JavaBean은 기본 무인수 생성자가 있는 클래스이며 명명 규칙을 따르는데, 예를 들어 bingoMadness라는 속성은 설정자 메서드 setBingoMadness(..)와 게터 메서드 getBingoMadness()를 가지게 됩니다. JavaBeans 및 사양에 대한 자세한 내용은javabeans를 참조하세요.

Bean 패키지에서 매우 중요한 클래스 중 하나는 BeanWrapper 인터페이스와 해당 구현(BeanWrapperImpl)입니다. 자바독에서 인용한바와 같이BeanWrapper는 속성 값을 개별적으로 또는 일괄적으로 설정 및 가져오고, 속성 설명자를 가져오고, 속성을 쿼리하여 읽기 가능한지 또는 쓰기 가능한지 확인하는 기능을 제공합니다. 또한 빈래퍼는 중첩 프로퍼티를 지원하므로 하위 프로퍼티의 프로퍼티를 무제한으로 설정할 수 있습니다.BeanWrapper는 또한 대상 클래스에서 지원 코드를 추가할 필요 없이 표준 JavaBeans PropertyChangeListeners및 VetoableChangeListeners를 추가하는 기능을 지원합니다. 마지막으로 BeanWrapper는 색인된 속성 설정을 지원합니다. 일반적으로 애플리케이션 코드에서 직접 사용하지 않고DataBinder와 BeanFactory에서 Be anWrapper를 사용하는 경우가 많습니다.

BeanWrapper의 작동 방식은 이름에서 알 수 있듯이 빈을 래핑하여 해당 빈에서 속성 설정 및 검색과 같은 작업을 수행합니다.

기본 및 중첩 속성 설정 및 가져오기

프로퍼티 설정 및 가져오기는 BeanWrapper의 오버로드된 메서드 변형인 setPropertyValue 및getPropertyValue를 통해 수행됩니다. 자세한 내용은 해당 자바독을 참조하세요. 아래 표는 이러한 규칙의 몇 가지 예를 보여줍니다:

표 1. 프로퍼티의 예표현식설명
name GetName() 또는 isName() 및 setName(...) 메서드에 해당하는 속성 이름을 나타냅니다.
account.name (예를 들어) getAccount().setName( ) 또는 getAccount().getName() 메서드에 대응하는 속성 계정의 중첩된 속성 이름을 나타냅니다.
account[2] 인덱싱된 속성 계정의 세 번째 요소를 나타냅니다. 인덱싱된 속성은 배열, 목록 또는 기타 자연 정렬된 컬렉션 유형일 수 있습니다.
계정[회사명] 계정 맵속성의 회사명 키로 인덱싱된 맵 항목의 값을 나타냅니다.

( BeanWrapper로 직접 작업하지 않을 계획이라면 이 다음 섹션은 크게 중요하지 않습니다. DataBinder와 BeanFactory및 그 기본 구현만 사용하는 경우 PropertyEditors 섹션으로 건너뛰면 됩니다

다음 두 예제 클래스는 BeanWrapper를 사용하여 프로퍼티를 가져오고 설정합니다:

public class Company {

	private String name;
	private Employee managingDirector;

	public String getName() {
		return this.name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Employee getManagingDirector() {
		return this.managingDirector;
	}

	public void setManagingDirector(Employee managingDirector) {
		this.managingDirector = managingDirector;
	}
}
public class Employee {

	private String name;

	private float salary;

	public String getName() {
		return this.name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public float getSalary() {
		return salary;
	}

	public void setSalary(float salary) {
		this.salary = salary;
	}
}

다음 코드 조각은 인스턴스화된 회사및 직원의일부 속성을 검색하고 조작하는 방법에 대한 몇 가지 예를 보여줍니다:

BeanWrapper company = new BeanWrapperImpl(new Company());
// setting the company name..
company.setPropertyValue("name", "Some Company Inc.");
// ... can also be done like this:
PropertyValue value = new PropertyValue("name", "Some Company Inc.");
company.setPropertyValue(value);

// ok, let's create the director and tie it to the company:
BeanWrapper jim = new BeanWrapperImpl(new Employee());
jim.setPropertyValue("name", "Jim Stravinsky");
company.setPropertyValue("managingDirector", jim.getWrappedInstance());

// retrieving the salary of the managingDirector through the company
Float salary = (Float) company.getPropertyValue("managingDirector.salary");

PropertyEditor's

Spring은 PropertyEditor라는 개념을 사용하여객체와 문자열 간의 변환을 수행합니다. 프로퍼티를 객체 자체와 다른 방식으로 표현하는 것이 편리할 수 있습니다. 예를 들어, 날짜는사람이 읽을 수 있는 방식(예: '2007-14-09')으로 표현할 수 있지만, 사람이 읽을 수 있는 형식을 원래 날짜로 다시 변환하거나 사람이 읽을 수 있는 형식으로 입력한 날짜를 다시 날짜 객체로 변환할 수 있습니다. 이 동작은java.beans.PropertyEditor 유형의 사용자 지정 편집기를 등록하여 수행할 수 있습니다. BeanWrapper에 사용자 정의 편집기를 등록하거나 (이전 장에서 언급했듯이) 특정 IoC 컨테이너에 등록하면 속성을 원하는 유형으로 변환하는 방법에 대한 지식을 얻을 수 있습니다.PropertyEditor에 대한 자세한 내용은 오라클의 java.beans 패키지의 javadoc을 참조하세요.

Spring에서 속성 편집이 사용되는 몇 가지 예제입니다:

  • 빈에 대한 속성 설정은 PropertyEditor 구현을 사용하여 수행됩니다. XML 파일에서 선언한 일부 빈의 속성 값으로 String을 사용하는 경우 Spring(해당 속성의 설정자에 Class매개 변수가 있는 경우)은 ClassEditor를 사용하여 매개 변수를 Class 객체로 해결하려고 시도합니다.
  • Spring의 MVC 프레임워크에서 HTTP 요청 매개변수를 구문 분석하는 것은CommandController의 모든 하위 클래스에서 수동으로 바인딩할 수 있는 모든 종류의 PropertyEditor 구현을 사용하여 수행됩니다.

Spring에는 작업을 쉽게 하기 위한 여러 가지 기본 제공 PropertyEditor 구현이 있습니다. 이러한 구현은 모두 org.springframework.beans.propertyeditors패키지에 있습니다. 다음 표에 표시된 바와 같이 대부분(전부는 아님)은 기본적으로BeanWrapperImpl에 등록되어 있습니다. 속성 편집기를 어떤 방식으로든 구성할 수 있는 경우에도 기본값을 재정의하기 위해 자신만의 변형을 등록할 수 있습니다. 다음 표는 Spring이 제공하는 다양한 PropertyEditor 구현에 대해 설명합니다:

표 2. 기본 제공 PropertyEditor 구현클래스설명
바이트 배열 프로퍼티 에디터 바이트 배열용 에디터. 문자열을 해당 바이트 표현으로 변환합니다. 기본적으로 BeanWrapperImpl에 의해 등록됩니다.
ClassEditor 클래스를 나타내는 문자열을 실제 클래스로 또는 그 반대로 구문 분석합니다. 클래스를 찾을 수 없는 경우 IllegalArgumentException이 발생합니다. 기본적으로 BeanWrapperImpl에 의해 등록됩니다.
CustomBooleanEditor 부울 프로퍼티를 위한 사용자 정의 가능한 프로퍼티 편집기. 기본적으로 BeanWrapperImpl에 의해 등록되지만, 커스텀 에디터로 커스텀 인스턴스를 등록하여 재정의할 수 있습니다.
커스텀 컬렉션 에디터 소스 컬렉션을 지정된 대상 컬렉션 유형으로 변환하는 컬렉션용 프로퍼티 편집기입니다.
CustomDateEditor Java.util.Date에 대한 사용자 지정 가능한 속성 편집기로, 사용자 지정 DateFormat을 지원합니다. 기본적으로 등록되지 않습니다. 필요에 따라 사용자가 적절한 형식으로 등록해야 합니다.
커스텀넘버에디터 정수, 긴, 실수 또는 두 배  같은 숫자 하위 클래스에 대한 사용자 정의 가능한 속성 편집기. 기본적으로 BeanWrapperImpl에 의해 등록되지만 사용자 지정 편집기로 사용자 지정 인스턴스를 등록하여 재정의할 수 있습니다.
FileEditor 문자열을 java.io.File 객체로 변환합니다. 기본적으로 BeanWrapperImpl에 의해 등록됩니다.
입력 스트림 편집기 입력스트림 프로퍼티를 문자열로 직접 설정할 수 있도록 문자열을 받아 (중간 ResourceEditor와 Resource를 통해) 입력 스트림을 생성할 수 있는 단방향 프로퍼티 편집기입니다. 기본 사용법은 입력 스트림을 닫지 않는다는 점에 유의하세요. 기본적으로 BeanWrapperImpl에 의해 등록됩니다.
LocaleEditor 문자열을 Locale 객체로 또는 그 반대로 해석할 수 있습니다(문자열 형식은 [ language]_[country]_[variant]로, Locale의 toString() 메서드와 동일). 또한 밑줄 대신 공백을 구분 기호로 사용할 수 있습니다. 기본적으로 BeanWrapperImpl에 의해 등록됩니다.
PatternEditor 문자열을 java.util.regex.Pattern 객체로 또는 그 반대로 해석할 수 있습니다.
PropertiesEditor 문자열( java.util.Properties 클래스의 javadoc에 정의된 형식으로 형식이 지정됨)을 Properties 객체로 변환할 수 있습니다. 기본적으로 BeanWrapperImpl에 의해 등록됩니다.
StringTrimmerEditor 문자열을 다듬는 속성 편집기. 선택적으로 빈 문자열을  값으로 변환할 수 있습니다. 기본적으로 등록되지 않으며 사용자가 등록해야 합니다.
URLEditor URL의 문자열 표현을 실제 URL 객체로 해석할 수 있습니다. 기본적으로 BeanWrapperImpl에 의해 등록됩니다.

Spring은 java.beans.PropertyEditorManager를 사용하여 필요할 수 있는 속성 편집기에 대한 검색 경로를 설정합니다. 검색 경로에는 Font, Color 및 대부분의 기본 유형과 같은 유형에 대한 PropertyEditor 구현이 포함된 sun.bean.editors도 포함됩니다. 또한 표준 JavaBeans 인프라는 처리하는 클래스와 동일한 패키지에 있고 해당 클래스와 이름이 같으며 Editor가 추가된 경우 명시적으로 등록할 필요 없이 자동으로 PropertyEditor 클래스를 검색합니다(사용자가 명시적으로 등록하지 않아도 됨). 예를 들어 다음과 같은 클래스 및 패키지 구조가 있을 수 있는데, 이 경우 SomethingEditor 클래스가 Something 유형 프로퍼티에 대한 PropertyEditor로 인식되어 사용될 수 있습니다.

com chank pop Something SomethingEditor // Something 클래스에 대한 PropertyEditor

여기에서도 표준 BeanInfo JavaBeans 메커니즘을 사용할 수 있습니다(여기에 어느 정도 설명되어 있음). 다음 예제는 BeanInfo 메커니즘을 사용하여 하나 이상의PropertyEditor 인스턴스를 연관된 클래스의 프로퍼티에 명시적으로 등록하는 예제입니다:

com chank pop Something SomethingBeanInfo // Something 클래스에 대한 BeanInfo

참조된 SomethingBeanInfo 클래스에 대한 다음 Java 소스 코드는 CustomNumberEditor를 Something 클래스의 나이 프로퍼티와 연관시킵니다:

public class SomethingBeanInfo extends SimpleBeanInfo {

	public PropertyDescriptor[] getPropertyDescriptors() {
		try {
			final PropertyEditor numberPE = new CustomNumberEditor(Integer.class, true);
			PropertyDescriptor ageDescriptor = new PropertyDescriptor("age", Something.class) {
				@Override
				public PropertyEditor createPropertyEditor(Object bean) {
					return numberPE;
				}
			};
			return new PropertyDescriptor[] { ageDescriptor };
		}
		catch (IntrospectionException ex) {
			throw new Error(ex.toString());
		}
	}
}

커스텀 프로퍼티 편집기의

빈 프로퍼티를 문자열 값으로 설정할 때 Spring IoC 컨테이너는 궁극적으로 표준 JavaBeans PropertyEditor 구현을 사용하여 이러한 문자열을 프로퍼티의 복합 유형으로 변환합니다. Spring은 여러 사용자 정의 PropertyEditor 구현을 사전 등록합니다(예: 문자열로 표현된 클래스 이름을 클래스 객체로 변환하기 위해). 또한 Java의 표준 JavaBeans PropertyEditor 조회 메커니즘을 사용하면 클래스에 대한 PropertyEditor의이름을 적절하게 지정하고 지원하는 클래스와 동일한 패키지에 배치하여 자동으로 찾을 수 있습니다.

다른 커스텀 프로퍼티에디터를 등록해야 하는 경우 몇 가지 메커니즘을 사용할 수 있습니다. 일반적으로 편리하지 않거나 권장되지 않는 가장 수동적인 접근 방식은 BeanFactory 참조가 있다고 가정하고ConfigurableBeanFactory 인터페이스의 registerCustomEditor() 메서드를 사용하는 것입니다. 또 다른 (약간 더 편리한) 메커니즘은 CustomEditorConfigurer라는 특수 빈 팩토리 포스트프로세서를 사용하는 것입니다. BeanFactory 구현에서 빈 팩토리 포스트 프로세서를 사용할 수 있지만, CustomEditorConfigurer에는 중첩된 속성 설정이 있으므로 다른 빈과 유사한 방식으로 배포할 수 있고 자동으로 감지 및 적용될 수 있는ApplicationContext와 함께 사용할 것을 강력히 권장합니다.

모든 빈 팩토리와 애플리케이션 컨텍스트는 BeanWrapper를 사용하여 속성 변환을 처리함으로써 여러 내장 속성 편집기를 자동으로 사용한다는 점에 유의하세요. BeanWrapper가등록하는 표준 속성 편집기는 이전 섹션에 나열되어 있습니다. 또한 ApplicationContexts는특정 애플리케이션 컨텍스트 유형에 적합한 방식으로 리소스 조회를 처리하기 위해 편집기를 재정의하거나 추가할 수도 있습니다.

문자열로 표현된 속성 값을 실제 복잡한 속성 유형으로 변환하는 데는 표준 JavaBeans PropertyEditor 인스턴스가 사용됩니다. 빈 팩토리 포스트프로세서인CustomEditorConfigurer를 사용하여 ApplicationContext에 추가 PropertyEditor 인스턴스에 대한 지원을 편리하게 추가할 수 있습니다.

ExoticType이라는 사용자 클래스와 ExoticType을 프로퍼티로 설정해야 하는 DependsOnExoticType이라는 다른 클래스를 정의하는 다음 예제를 살펴봅시다:

package example;

public class ExoticType {

	private String name;

	public ExoticType(String name) {
		this.name = name;
	}
}

public class DependsOnExoticType {

	private ExoticType type;

	public void setType(ExoticType type) {
		this.type = type;
	}
}

모든 것이 제대로 설정되면 유형 속성을 문자열로 할당할 수 있고, PropertyEditor가 실제ExoticType 인스턴스로 변환할 수 있기를 원합니다. 다음 빈 정의는 이 관계를 설정하는 방법을 보여줍니다:

<bean id="sample" class="example.DependsOnExoticType">
	<property name="type" value="aNameForExoticType"/>
</bean>

PropertyEditor 구현은 다음과 유사하게 보일 수 있습니다:

package example;

import java.beans.PropertyEditorSupport;

// converts string representation to ExoticType object
public class ExoticTypeEditor extends PropertyEditorSupport {

	public void setAsText(String text) {
		setValue(new ExoticType(text.toUpperCase()));
	}
}

마지막으로, 다음 예제는 CustomEditorConfigurer를 사용하여 새 PropertyEditor를ApplicationContext에 등록한 다음 필요에 따라 사용할 수 있도록 하는 방법을 보여줍니다:

<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
	<property name="customEditors">
		<map>
			<entry key="example.ExoticType" value="example.ExoticTypeEditor"/>
		</map>
	</property>
</bean>

PropertyEditorRegistrar

Spring 컨테이너에 프로퍼티 에디터를 등록하는 또 다른 메커니즘은 PropertyEditorRegistrar를 생성하여 사용하는 것입니다. 이 인터페이스는 여러 다른 상황에서 동일한 속성 편집기 집합을 사용해야 할 때 특히 유용합니다. 해당 등록기를 작성하여 각 경우에 재사용할 수 있습니다.PropertyEditorRegistrar 인스턴스는 Spring BeanWrapper(및 DataBinder)에서 구현되는PropertyEditorRegistry라는 인터페이스와 함께 작동합니다. PropertyEditorRegistrar 인스턴스는 setPropertyEditorRegistrars(..)라는 프로퍼티를 노출하는 CustomEditorConfigurer (여기에 설명됨)와 함께 사용할 때 특히 편리합니다. 이러한 방식으로 CustomEditorConfigurer에 추가된 PropertyEditorRegistrar 인스턴스는 DataBinder 및 Spring MVC 컨트롤러와 쉽게 공유할 수 있습니다. 또한 사용자 정의 편집기에서 동기화할 필요가 없습니다: PropertyEditorRegistrar는 각 빈 생성 시도에 대해 새로운 PropertyEditor인스턴스를 생성할 것으로 예상됩니다.

다음 예제는 자신만의 PropertyEditorRegistrar 구현을 만드는 방법을 보여줍니다:

package com.foo.editors.spring;

public final class CustomPropertyEditorRegistrar implements PropertyEditorRegistrar {

	public void registerCustomEditors(PropertyEditorRegistry registry) {

		// it is expected that new PropertyEditor instances are created
		registry.registerCustomEditor(ExoticType.class, new ExoticTypeEditor());

		// you could register as many custom property editors as are required here...
	}
}

PropertyEditorRegistrar 구현의 예는 org.springframework.beans.support.ResourceEditorRegistrar를 참조하세요.RegisterCustomEditors(..) 메서드의 구현에서 각 속성 편집기의 새 인스턴스를 생성하는 방법에 주목하세요.

다음 예제는 CustomEditorConfigurer를 구성하고 여기에 CustomPropertyEditorRegistrar의 인스턴스를 주입하는 방법을 보여줍니다:

<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
	<property name="propertyEditorRegistrars">
		<list>
			<ref bean="customPropertyEditorRegistrar"/>
		</list>
	</property>
</bean>

<bean id="customPropertyEditorRegistrar"
	class="com.foo.editors.spring.CustomPropertyEditorRegistrar"/>

마지막으로 (그리고 이 장의 초점에서 약간 벗어났지만) Spring의 MVC 웹 프레임워크를 사용하는 사람들에게는 데이터 바인딩 웹 컨트롤러와 함께 PropertyEditorRegistrar를 사용하는 것이 매우 편리할 수 있습니다. 다음 예제에서는 @InitBinder 메서드 구현에 PropertyEditorRegistrar를 사용합니다:

@Controller
public class RegisterUserController {

	private final PropertyEditorRegistrar customPropertyEditorRegistrar;

	RegisterUserController(PropertyEditorRegistrar propertyEditorRegistrar) {
		this.customPropertyEditorRegistrar = propertyEditorRegistrar;
	}

	@InitBinder
	void initBinder(WebDataBinder binder) {
		this.customPropertyEditorRegistrar.registerCustomEditors(binder);
	}

	// other methods related to registering a User
}

이러한 스타일의 PropertyEditor 등록은 간결한 코드를 만들 수 있으며( @InitBinder 메서드의 구현은 한 줄에 불과합니다), 일반적인 PropertyEditor등록 코드를 클래스에 캡슐화한 다음 필요한 만큼 많은 컨트롤러에서 공유할 수 있습니다.

 

오류 메시지로 코드 해석하기

데이터 바인딩과 유효성 검사에 대해 다뤘습니다. 이 섹션에서는 유효성 검사 오류에 해당하는 메시지를 출력하는 방법을 다룹니다. 이전 섹션에 표시된 예제에서는 이름  연령 필드를 거부했습니다.메시지소스를 사용하여 오류 메시지를 출력하려면 해당 필드(이 경우 '이름' 및 '나이')를 거부할 때 제공한 오류 코드를 사용하여 출력할 수 있습니다. 직접 또는 ValidationUtils 클래스 등을 사용하여 간접적으로 Errors 인터페이스의 rejectValue 또는 다른 거부 메서드 중 하나를 호출하면 기본 구현은 전달한 코드뿐만 아니라 여러 가지 추가 오류 코드도 등록합니다. 메시지 코드 해결자는 Errors 인터페이스가 어떤 오류 코드를 등록할지 결정합니다. 기본적으로DefaultMessageCodesResolver가 사용되는데, 예를 들어 사용자가 제공한 코드가 포함된 메시지를 등록할 뿐만 아니라 거부 메서드에 전달한 필드 이름이 포함된 메시지도 등록합니다. 따라서 rejectValue("age", "too.darn.old")를 사용하여 필드를 거부하는 경우 Spring은 너무 오래된 코드와는 별도로 너무 오래된 나이와너무 오래된 나이.int도 등록합니다(전자는 필드 이름, 후자는 필드 유형 포함). 이는 개발자가 오류 메시지를 타겟팅할 때 편의를 돕기 위한 것입니다.

MessageCodesResolver와 기본 전략에 대한 자세한 내용은 각각MessageCodesResolver DefaultMessageCodesResolver의 javadoc에서 확인할 수 있습니다.

 

Spring Type Conversion

Core.convert 패키지는 일반적인 타입 변환 시스템을 제공합니다. 이 시스템은 타입 변환 로직을 구현하는 SPI와 런타임에 타입 변환을 수행하는 API를 정의합니다. Spring 컨테이너 내에서 이 시스템을PropertyEditor 구현의 대안으로 사용하여 외부화된 빈 속성 값 문자열을 필요한 속성 유형으로 변환할 수 있습니다. 또한 유형 변환이 필요한 애플리케이션의 어느 곳에서나 공용 API를 사용할 수 있습니다.

Converter SPI

유형 변환 로직을 구현하는 SPI는 다음 인터페이스 정의에서 볼 수 있듯이 간단하고 강력하게 유형화되어 있습니다:

package org.springframework.core.convert.converter;

public interface Converter<S, T> {

	T convert(S source);
}

자신만의 변환기를 만들려면 Converter 인터페이스를 구현하고 변환 대상 유형으로 S를, 변환 대상 유형으로 T를 파라미터화하세요. 또한, 위임 배열 또는 컬렉션 변환기가 등록되어 있는 경우( DefaultConversionService가 기본적으로 수행) S의 컬렉션 또는 배열을 T의 배열 또는 컬렉션으로 변환해야 하는 경우 이러한 변환기를 투명하게 적용할 수 있습니다.

Convert(S)를 호출할 때마다 소스 인자가 null이 아닌 것이 보장됩니다. 변환이 실패하면변환기는 확인되지 않은 예외를 던질 수 있습니다. 특히, 유효하지 않은 소스 값을 보고하기 위해IllegalArgumentException을 던져야 합니다. Converter 구현이 스레드에 안전한지 주의하세요.

편의를 위해 core.convert.support 패키지에는 몇 가지 변환기 구현이 제공됩니다. 여기에는 문자열에서 숫자로의 변환기 및 기타 일반적인 유형이 포함됩니다. 다음 목록은 일반적인 Converter 구현인 StringToInteger 클래스를 보여줍니다:

package org.springframework.core.convert.support;

final class StringToInteger implements Converter<String, Integer> {

	public Integer convert(String source) {
		return Integer.valueOf(source);
	}
}

Using ConverterFactory

전체 클래스 계층 구조에 대한 변환 로직을 중앙 집중화해야 하는 경우(예: String에서 Enum 객체로 변환할 때), 다음 예제에서 볼 수 있듯이ConverterFactory를 구현할 수 있습니다:

package org.springframework.core.convert.converter;

public interface ConverterFactory<S, R> {

	<T extends R> Converter<S, T> getConverter(Class<T> targetType);
}

S를 변환하는 유형으로, R을 변환할 수 있는 클래스 범위를 정의하는 기본 유형으로 매개변수화합니다. 그런 다음 getConverter(Class<T>)를 구현합니다. 여기서 T는 R의 서브클래스입니다.

StringToEnumConverterFactory를 예로 들어보겠습니다:

package org.springframework.core.convert.support;

final class StringToEnumConverterFactory implements ConverterFactory<String, Enum> {

	public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) {
		return new StringToEnumConverter(targetType);
	}

	private final class StringToEnumConverter<T extends Enum> implements Converter<String, T> {

		private Class<T> enumType;

		public StringToEnumConverter(Class<T> enumType) {
			this.enumType = enumType;
		}

		public T convert(String source) {
			return (T) Enum.valueOf(this.enumType, source.trim());
		}
	}
}

Using GenericConverter

정교한 Converter 구현이 필요한 경우GenericConverter 인터페이스를 사용하는 것이 좋습니다. Converter보다 더 유연하지만 덜 강력한 서명을 가진 GenericConverter는 여러 소스 및 대상 유형 간의 변환을 지원합니다. 또한 GenericConverter는 변환 로직을 구현할 때 사용할 수 있는 소스 및 대상 필드 컨텍스트를 제공합니다. 이러한 컨텍스트를 사용하면 필드 어노테이션이나 필드 서명에 선언된 일반 정보에 따라 유형 변환을 수행할 수 있습니다. 다음 목록은 GenericConverter의 인터페이스 정의를 보여줍니다:

package org.springframework.core.convert.converter;

public interface GenericConverter {

	public Set<ConvertiblePair> getConvertibleTypes();

	Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}

GenericConverter를 구현하려면 getConvertibleTypes() 가 지원되는 소스→대상 유형 쌍을 반환하도록 합니다. 그런 다음 변환 로직을 포함하도록 convert(Object, TypeDescriptor, TypeDescriptor) 를 구현하세요. 소스 타입 설명 자는 변환할 값을 담고 있는 소스 필드에 대한 액세스를 제공합니다. 대상 TypeDescriptor는변환된 값을 설정할 대상 필드에 대한 액세스를 제공합니다.

GenericConverter의 좋은 예는 Java 배열과 컬렉션을 변환하는 변환기입니다. 이러한 ArrayToCollectionConverter는 대상 컬렉션 유형을 선언하는 필드를 내재화하여 컬렉션의 요소 유형을 확인합니다. 이렇게 하면 대상 필드에 컬렉션이 설정되기 전에 소스 배열의 각 요소를 컬렉션 요소 유형으로 변환할 수 있습니다.

  GenericConverter는 보다 복잡한 SPI 인터페이스이므로 필요한 경우에만 사용해야 합니다. 기본적인 유형 변환이 필요한 경우에는 Converter 또는 ConverterFactory를 사용하세요

조건부 일반 컨버터 사용

특정 조건이 참일 때만 Converter를 실행하고 싶을 때가 있습니다. 예를 들어, 대상 필드에 특정 어노테이션이 있는 경우에만 Converter를 실행하거나 대상 클래스에 특정 메서드(예: 정적 valueOf 메서드)가 정의된 경우에만 Converter를 실행하고 싶을 수 있습니다. 이러한 사용자 정의 일치 조건을 정의할 수 있는 것은 GenericConverter와ConditionalConverter 인터페이스의 결합인ConditionalGenericConverter입니다:

public interface ConditionalConverter {

	boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
}

public interface ConditionalGenericConverter extends GenericConverter, ConditionalConverter {
}

영구 엔티티 식별자와 엔티티 참조를 변환하는 IdToEntityConverter가 Cond itionalGenericConverter의 좋은 예입니다. 이러한 IdToEntityConverter는대상 엔티티 유형이 정적 찾기 메서드(예:findAccount(Long))를 선언하는 경우에만 일치할 수 있습니다.일치 메서드 구현에서 이러한 파인더 메서드 검사를 수행할 수 있습니다(유형 설명자, 유형 설명자).

The ConversionService API

변환 서비스는 런타임에 유형 변환 로직을 실행하기 위한 통합 API를 정의합니다. 변환기는 종종 다음과 같은 외관 인터페이스 뒤에서 실행됩니다:

package org.springframework.core.convert;

public interface ConversionService {

	boolean canConvert(Class<?> sourceType, Class<?> targetType);

	<T> T convert(Object source, Class<T> targetType);

	boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType);

	Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}

대부분의 ConversionService 구현은 변환기 등록을 위한 SPI를 제공하는 ConverterRegistry도 구현합니다. 내부적으로 ConversionService구현은 등록된 변환기에 위임하여 유형 변환 로직을 수행합니다.

강력한 ConversionService 구현은 core.convert.support패키지에 제공됩니다. GenericConversionService는 대부분의 환경에서 사용하기에 적합한 범용 구현입니다. ConversionServiceFactory는 일반적인 ConversionService 구성을 생성하기 위한 편리한 팩토리를 제공합니다.

Configuring a ConversionService

변환 서비스는 애플리케이션 시작 시 인스턴스화된 다음 여러 스레드 간에 공유되도록 설계된 상태 비저장 객체입니다. Spring 애플리케이션에서는 일반적으로 각 Spring 컨테이너(또는 ApplicationContext)에 대해 ConversionService 인스턴스를 구성합니다. Spring은 프레임워크에서 유형 변환을 수행해야 할 때마다 해당 ConversionService를 가져와 사용합니다. 이변환 서비스를 빈에 주입하여 직접 호출할 수도 있습니다.

  Spring에 등록된 ConversionService가 없는 경우 원래 PropertyEditor 기반 시스템이 사용됩니다

Spring에 기본 ConversionService를 등록하려면 다음 빈 정의에 conversionService라는 ID를 추가하세요:

<bean id="conversionService"
	class="org.springframework.context.support.ConversionServiceFactoryBean"/>

기본 ConversionService는 문자열, 숫자, 열거형, 컬렉션, 지도 및 기타 일반적인 유형 간에 변환할 수 있습니다. 사용자 정의 변환기로 기본 변환기를 보완하거나 재정의하려면 converters 속성을 설정하세요. 속성 값은 Converter, ConverterFactory 또는 GenericConverter 인터페이스 중 하나를 구현할 수 있습니다.

<bean id="conversionService"
		class="org.springframework.context.support.ConversionServiceFactoryBean">
	<property name="converters">
		<set>
			<bean class="example.MyCustomConverter"/>
		</set>
	</property>
</bean>

또한 Spring MVC 애플리케이션 내에서 ConversionService를 사용하는 것이 일반적입니다. Spring MVC 장에서변환 및 서식 지정을 참조하세요.

특정 상황에서는 변환 중에 서식을 적용하고 싶을 수도 있습니다. FormattingConversionServiceFactoryBean 사용에 대한 자세한 내용은 FormatterRegistry SPI를 참조하세요.

Using a ConversionService Programmatically

프로그래밍 방식으로 변환 서비스 인스턴스로 작업하려면 다른 빈과 마찬가지로 변환 서비스 인스턴스에 대한 참조를 삽입하면 됩니다. 다음 예제는 그 방법을 보여줍니다:

@Service
public class MyService {

	private final ConversionService conversionService;

	public MyService(ConversionService conversionService) {
		this.conversionService = conversionService;
	}

	public void doIt() {
		this.conversionService.convert(...)
	}
}

대부분의 사용 사례에서 대상 유형을 지정하는 convert 메서드를 사용할 수 있지만 매개변수화된 요소의 컬렉션과 같은 더 복잡한 유형에서는 작동하지 않습니다. 예를 들어, 프로그래밍 방식으로 List of Integer를 List of String으로 변환하려면 소스 및 대상 유형에 대한 공식적인 정의를 제공해야 합니다.

다행히도 다음 예제에서 볼 수 있듯이 TypeDescriptor는 이를 간단하게 수행할 수 있는 다양한 옵션을 제공합니다:

DefaultConversionService cs = new DefaultConversionService();

List<Integer> input = ...
cs.convert(input,
	TypeDescriptor.forObject(input), // List<Integer> type descriptor
	TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(String.class)));

DefaultConversionService는 대부분의 환경에 적합한 변환기를 자동으로 등록합니다. 여기에는 컬렉션 변환기, 스칼라 변환기, 기본 객체-스트링 변환기 등이 포함됩니다. DefaultConversionService 클래스의 정적 addDefaultConverters메서드를 사용하여 모든 ConverterRegistry에 동일한 변환기를 등록할 수 있습니다.

값 유형에 대한 변환기는 배열과 컬렉션에 재사용되므로 표준 컬렉션 처리가 적절하다고 가정할 때 S의 컬렉션에서 T의컬렉션으로 변환하기 위해 특정 변환기를 만들 필요가 없습니다.

 

Spring Field Formatting

이전 섹션에서 설명한 것처럼 core.convert는 범용 타입 변환 시스템입니다. 이것은 한 유형에서 다른 유형으로의 변환 로직을 구현하기 위한 강력한 타입의 Converter SPI뿐만 아니라 통합된 ConversionService API를 제공합니다. Spring 컨테이너는 이 시스템을 사용하여 빈 속성 값을 바인딩합니다. 또한, Spring 표현식 언어(SpEL)와 DataBinder는 모두 이 시스템을 사용하여 필드 값을 바인딩합니다. 예를 들어, SpEL이 expression.setValue(Object bean, Object value) 시도를 완료하기 위해 Short를 Long으로 강제로 변환해야 하는 경우 core.convert시스템이 강제를 수행합니다.

이제 웹 또는 데스크톱 애플리케이션과 같은 일반적인 클라이언트 환경의 유형 변환 요구 사항을 고려해 보겠습니다. 이러한 환경에서는 일반적으로 클라이언트 포스트백 프로세스를 지원하기 위해 문자열에서변환하고, 뷰 렌더링 프로세스를 지원하기 위해 다시 문자열로 변환합니다. 또한 문자열 값을 지역화해야 하는 경우가 많습니다. 보다 일반적인 core.convert Converter SPI는 이러한 형식 지정 요구 사항을 직접 처리하지 않습니다. 이를 직접 처리하기 위해 Spring은 클라이언트 환경을 위한 PropertyEditor 구현에 대한 간단하고 강력한 대안을 제공하는 편리한 Formatter SPI를 제공합니다.

일반적으로 범용 유형 변환 로직을 구현해야 하는 경우(예: java.util.Date와 Long 간 변환) Converter SPI를 사용할 수 있습니다. 웹 애플리케이션과 같은 클라이언트 환경에서 작업하고 지역화된 필드 값을 구문 분석하고 인쇄해야 하는 경우 Formatter SPI를 사용할 수 있습니다. ConversionService는두 SPI에 대해 통합된 유형 변환 API를 제공합니다.

The Formatter SPI

필드 서식 지정 로직을 구현하는 Formatter SPI는 간단하고 강력하게 입력됩니다. 다음 목록은 포매터 인터페이스 정의를 보여줍니다:

package org.springframework.format;

public interface Formatter<T> extends Printer<T>, Parser<T> {
}

포맷터는 프린터  파서 빌딩 블록 인터페이스에서 확장됩니다. 다음 목록은 이 두 인터페이스의 정의를 보여줍니다:

public interface Printer<T> {

	String print(T fieldValue, Locale locale);
}
import java.text.ParseException;

public interface Parser<T> {

	T parse(String clientValue, Locale locale) throws ParseException;
}

자신만의 포맷터를 만들려면 앞서 표시된 포맷터 인터페이스를 구현합니다. 포맷하려는 객체 유형(예:java.util.Date)으로 T를 매개변수화합니다. Print() 연산을 구현하여 클라이언트 로캘에 표시할 T의 인스턴스를 인쇄합니다. 클라이언트 로캘에서 반환된 형식화된 표현에서T의 인스턴스를 구문 분석하려면 parse() 연산을 구현합니다. 구문 분석 시도가 실패하면 포매터는 ParseException 또는 IllegalArgumentException을 던져야 합니다. 포매터 구현이 스레드에 안전한지 주의하세요.

포맷 하위 패키지는 편의를 위해 몇 가지 포매터 구현을 제공합니다. 숫자 패키지는 java.text.NumberFormat을 사용하는 숫자 객체의 포맷을 지정하는 NumberStyleFormatter, CurrencyStyleFormatter 및PercentStyleFormatter를 제공합니다. 날짜 시간 패키지는 java. util. Date 객체의 포맷을 지정하는 DateFormatter를 제공합니다.

다음 DateFormatter는 포매터 구현의 예시입니다:

package org.springframework.format.datetime;

public final class DateFormatter implements Formatter<Date> {

	private String pattern;

	public DateFormatter(String pattern) {
		this.pattern = pattern;
	}

	public String print(Date date, Locale locale) {
		if (date == null) {
			return "";
		}
		return getDateFormat(locale).format(date);
	}

	public Date parse(String formatted, Locale locale) throws ParseException {
		if (formatted.length() == 0) {
			return null;
		}
		return getDateFormat(locale).parse(formatted);
	}

	protected DateFormat getDateFormat(Locale locale) {
		DateFormat dateFormat = new SimpleDateFormat(this.pattern, locale);
		dateFormat.setLenient(false);
		return dateFormat;
	}
}

Spring 팀은 커뮤니티 중심의 포매터 기여를 환영합니다. 기여하려면깃허브 이슈를 참조하세요.

Annotation-driven Formatting

필드 서식은 필드 유형 또는 어노테이션별로 구성할 수 있습니다. 어노테이션을 포맷터에 바인딩하려면 AnnotationFormatterFactory를 구현하세요. 다음 목록은 AnnotationFormatterFactory 인터페이스의 정의를 보여줍니다:

package org.springframework.format;

public interface AnnotationFormatterFactory<A extends Annotation> {

	Set<Class<?>> getFieldTypes();

	Printer<?> getPrinter(A annotation, Class<?> fieldType);

	Parser<?> getParser(A annotation, Class<?> fieldType);
}

구현을 만들려면 다음과 같이 하세요:

  1. A를 서식 지정 로직을 연결하려는 필드 annotationType으로 매개 변수화합니다(예: org.springframework.format.annotation.DateTimeFormat).
  2. GetFieldTypes()가 어노테이션을 사용할 수 있는 필드 유형을 반환하도록 합니다.
  3. GetPrinter()가 주석이 달린 필드의 값을 인쇄할 프린터를 반환하도록 합니다.
  4. GetParser() 가 어노테이션 필드의 클라이언트 값을 구문 분석할 수 있는 파서를 반환하도록 합니다.

다음 예제 AnnotationFormatterFactory 구현은 숫자 스타일이나 패턴을 지정할 수 있도록 @NumberFormat어노테이션을 포맷터에 바인딩합니다:

public final class NumberFormatAnnotationFormatterFactory
		implements AnnotationFormatterFactory<NumberFormat> {

	private static final Set<Class<?>> FIELD_TYPES = Set.of(Short.class,
			Integer.class, Long.class, Float.class, Double.class,
			BigDecimal.class, BigInteger.class);

	public Set<Class<?>> getFieldTypes() {
		return FIELD_TYPES;
	}

	public Printer<Number> getPrinter(NumberFormat annotation, Class<?> fieldType) {
		return configureFormatterFrom(annotation, fieldType);
	}

	public Parser<Number> getParser(NumberFormat annotation, Class<?> fieldType) {
		return configureFormatterFrom(annotation, fieldType);
	}

	private Formatter<Number> configureFormatterFrom(NumberFormat annotation, Class<?> fieldType) {
		if (!annotation.pattern().isEmpty()) {
			return new NumberStyleFormatter(annotation.pattern());
		}
		// else
		return switch(annotation.style()) {
			case Style.PERCENT -> new PercentStyleFormatter();
			case Style.CURRENCY -> new CurrencyStyleFormatter();
			default -> new NumberStyleFormatter();
		};
	}
}

다음 예제와 같이 @NumberFormat으로 필드에 주석을 달아 서식 지정을 트리거할 수 있습니다:

public class MyModel {

	@NumberFormat(style=Style.CURRENCY)
	private BigDecimal decimal;
}

서식 어노테이션 API

이식 가능한 형식 어노테이션 API는 org.springframework.format.annotation패키지에 존재합니다. Double 및Long과 같은 숫자 필드의 서식을 지정하려면 @NumberFormat을 사용하고, java.util.Date, java.util.Calendar, Long(밀리초 타임스탬프의 경우) 및 JSR-310 java.time의 서식을 지정하려면 @DateTimeFormat을 사용할 수 있습니다.

다음 예에서는 @DateTimeFormat을 사용하여 java.util.Date의 서식을 ISO 날짜(yyyy-MM-dd)로 지정합니다:

public class MyModel {

	@DateTimeFormat(iso=ISO.DATE)
	private Date date;
}

The FormatterRegistry SPI

포맷터 레지스트리는 포맷터와 변환기를 등록하기 위한 SPI입니다.포맷팅컨버전서비스는 대부분의 환경에 적합한 포맷터 레지스트리의 구현입니다. 이 변형을 프로그래밍 방식으로 또는 선언적으로 구성할 수 있습니다(예: FormattingConversionServiceFactoryBean 사용). 이 구현은 ConversionService도 구현하므로 Spring의 DataBinder 및 Spring 표현식 언어(SpEL)와 함께 사용하도록 직접 구성할 수 있습니다.

다음 목록은 FormatterRegistry SPI를 보여줍니다:

package org.springframework.format;

public interface FormatterRegistry extends ConverterRegistry {

	void addPrinter(Printer<?> printer);

	void addParser(Parser<?> parser);

	void addFormatter(Formatter<?> formatter);

	void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter);

	void addFormatterForFieldType(Class<?> fieldType, Printer<?> printer, Parser<?> parser);

	void addFormatterForFieldAnnotation(AnnotationFormatterFactory<? extends Annotation> annotationFormatterFactory);
}

앞의 목록과 같이 필드 유형별 또는 어노테이션별로 포맷터를 등록할 수 있습니다.

FormatterRegistry SPI를 사용하면 여러 컨트롤러에서 이러한 구성을 복제하는 대신 중앙에서 서식 지정 규칙을 구성할 수 있습니다. 예를 들어 모든 날짜 필드의 서식을 특정 방식으로 지정하거나 특정 어노테이션이 있는 필드의 서식을 특정 방식으로 지정할 수 있습니다. 공유 FormatterRegistry를 사용하면 이러한 규칙을 한 번 정의하면 서식을 지정해야 할 때마다 규칙이 적용됩니다.

The FormatterRegistrar SPI

FormatterRegistrar는 포맷터 레지스트리를 통해 포맷터와 변환기를 등록하기 위한 SPI입니다. 다음 목록은 인터페이스 정의를 보여줍니다:

package org.springframework.format;

public interface FormatterRegistrar {

	void registerFormatters(FormatterRegistry registry);
}

FormatterRegistrar는 날짜 서식과 같이 지정된 서식 범주에 대해 여러 관련 변환기와 포맷터를 등록할 때 유용합니다. 또한 선언적 등록이 불충분한 경우(예: 포맷터를 자체 <T>와 다른 특정 필드 유형으로 색인해야 하거나 프린터/파서 쌍을 등록할 때)에도 유용할 수 있습니다. 다음 섹션에서는 변환기 및 포맷터 등록에 대한 자세한 정보를 제공합니다.

Configuring Formatting in Spring MVC

Spring MVC 장의 변환 및 서식 지 정을 참조하세요.

 

전역 날짜 및 시간 형식 구성하기

기본적으로 @DateTimeFormat으로 주석을 달지 않은 날짜 및 시간 필드는 DateFormat.SHORT 스타일을 사용하여 문자열에서 변환됩니다. 원하는 경우 고유한 전역 형식을 정의하여 이를 변경할 수 있습니다.

이렇게 하려면 Spring이 기본 포맷터를 등록하지 않도록 하세요. 대신 다음을 사용하여 포맷터를 수동으로 등록하십시오:

  • org.springframework.format.datetime.standard.DateTimeFormatterRegistrar
  • org.springframework.format.datetime.DateFormatterRegistrar

예를 들어, 다음 Java 구성은 전역 yyyyMMdd 형식을 등록합니다:

@Configuration
public class AppConfig {

	@Bean
	public FormattingConversionService conversionService() {

		// Use the DefaultFormattingConversionService but do not register defaults
		DefaultFormattingConversionService conversionService =
			new DefaultFormattingConversionService(false);

		// Ensure @NumberFormat is still supported
		conversionService.addFormatterForFieldAnnotation(
			new NumberFormatAnnotationFormatterFactory());

		// Register JSR-310 date conversion with a specific global format
		DateTimeFormatterRegistrar dateTimeRegistrar = new DateTimeFormatterRegistrar();
		dateTimeRegistrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyyMMdd"));
		dateTimeRegistrar.registerFormatters(conversionService);

		// Register date conversion with a specific global format
		DateFormatterRegistrar dateRegistrar = new DateFormatterRegistrar();
		dateRegistrar.setFormatter(new DateFormatter("yyyyMMdd"));
		dateRegistrar.registerFormatters(conversionService);

		return conversionService;
	}
}

XML 기반 구성을 선호하는 경우FormattingConversionServiceFactoryBean을 사용할 수 있습니다. 다음 예에서는 그 방법을 보여 줍니다:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="
		http://www.springframework.org/schema/beans
		https://www.springframework.org/schema/beans/spring-beans.xsd">

	<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
		<property name="registerDefaultFormatters" value="false" />
		<property name="formatters">
			<set>
				<bean class="org.springframework.format.number.NumberFormatAnnotationFormatterFactory" />
			</set>
		</property>
		<property name="formatterRegistrars">
			<set>
				<bean class="org.springframework.format.datetime.standard.DateTimeFormatterRegistrar">
					<property name="dateFormatter">
						<bean class="org.springframework.format.datetime.standard.DateTimeFormatterFactoryBean">
							<property name="pattern" value="yyyyMMdd"/>
						</bean>
					</property>
				</bean>
			</set>
		</property>
	</bean>
</beans>

웹 애플리케이션에서 날짜 및 시간 형식을 구성할 때는 추가로 고려해야 할 사항이 있습니다.WebMVC 변환 및 서식 지정 또는WebFlux 변환 및 서식 지정을 참조하세요.

 

Java Bean Validation

Spring 프레임워크는Java Bean 유효성 검사 API를 지원합니다.

Overview of Bean Validation

Bean 유효성 검사는 Java 애플리케이션을 위한 제약 조건 선언 및 메타데이터를 통해 일반적인 유효성 검사 방법을 제공합니다. 이를 사용하려면 선언적 유효성 검사 제약 조건으로 도메인 모델 속성에 주석을 달면 런타임에 의해 적용됩니다. 기본 제공 제약 조건이 있으며 사용자 지정 제약 조건을 정의할 수도 있습니다.

다음 예제는 두 개의 속성이 있는 간단한 PersonForm 모델을 보여줍니다:

public class PersonForm {
	private String name;
	private int age;
}

Bean 유효성 검사를 사용하면 다음 예제와 같이 제약 조건을 선언할 수 있습니다:

public class PersonForm {

	@NotNull
	@Size(max=64)
	private String name;

	@Min(0)
	private int age;
}

그러면 Bean 유효성 검사 유효성 검사기가 선언된 제약 조건을 기반으로 이 클래스의 인스턴스를 유효성 검사합니다. API에 대한 일반적인 정보는 Bean 유효 성 검사를 참조하세요. 특정 제약 조건에 대해서는 최대 절전 모드 유효성 검사기 설명서를 참조하세요. Spring 빈으로 빈 유효성 검사 공급자를 설정하는 방법을 알아보려면 계속 읽어보세요.

Configuring a Bean Validation Provider

Spring은 Bean 유효성 검사 공급자를 Spring 빈으로 부트스트랩하는 것을 포함하여 Bean 유효성 검사 API에 대한 완전한 지원을 제공합니다. 이를 통해 애플리케이션에서 유효성 검사가 필요한 곳이면 어디든jakarta.validation.ValidatorFactory 또는 jakarta.validation.Validator를 삽입할 수 있습니다.

다음 예제에서 볼 수 있듯이 LocalValidatorFactoryBean을 사용하여 기본 유효성 검사기를 Spring 빈으로 구성할 수 있습니다:

import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;

@Configuration
public class AppConfig {

	@Bean
	public LocalValidatorFactoryBean validator() {
		return new LocalValidatorFactoryBean();
	}
}

앞의 예제의 기본 구성은 기본 부트스트랩 메커니즘을 사용하여 초기화하도록 빈 유효성 검사를 트리거합니다. 최대 절전 모드 유효성 검사기와 같은 빈 유효성 검사 공급자는 클래스 경로에 존재할 것으로 예상되며 자동으로 감지됩니다.

자카르타 유효성 검사기 주입

LocalValidatorFactoryBean은 jakarta.validation.ValidatorFactory와jakarta.validation.Validator를 모두 구현하므로 다음 예제에서 보듯이 Bean 유효성 검사 API로 직접 작업하려는 경우 후자에 대한 참조를 주입하여 유효성 검사 로직을 적용할 수 있습니다:

import jakarta.validation.Validator;

@Service
public class MyService {

	@Autowired
	private Validator validator;
}

Spring 유효성 검사기 주입

Jakarta.validation.Validator를 구현하는 것 외에도 LocalValidatorFactoryBean은 org.springframework.validation.Validator에도 적응하므로 빈에 Spring Validation API가 필요한 경우 후자에 대한 참조를 주입할 수 있습니다.

예를 들어

import org.springframework.validation.Validator;

@Service
public class MyService {

	@Autowired
	private Validator validator;
}

Org.springframework.validation.Validator로 사용되는 경우, LocalValidatorFactoryBean은기본 jakarta.validation.Validator를 호출한 다음ContraintViolations를 FieldErrors에맞게 조정하고 이를 validate 메서드로 전달된 Errors 객체에 등록합니다.

사용자 지정 제약 조건 구성

각 빈 유효성 검사 제약 조건은 두 부분으로 구성됩니다:

  • 제약 조건과 그 구성 가능한 속성을 선언하는 @Constraint 어노테이션.
  • 제약 조건의 동작을 구현하는 jakarta.validation.ConstraintValidator 인터페이스의 구현.

선언을 구현과 연결하기 위해 각 @Constraint 어노테이션은 해당 ConstraintValidator 구현 클래스를 참조합니다. 런타임에 도메인 모델에서 제약 조건 어노테이션이 발견되면ConstraintValidatorFactory가 참조된 구현을 인스턴스화합니다.

기본적으로 로컬 유효성 검사기 팩토리 빈은 스프링을 사용하여 유효성 검사기 인스턴스를 생성하는 스프링 유효성 검사기 팩토리를구성합니다. 이렇게 하면 사용자 정의ConstraintValidator가 다른 Spring 빈과 마찬가지로 종속성 주입의 이점을 누릴 수 있습니다.

다음 예제는 종속성 주입을 위해 Spring을 사용하는 사용자 정의 @Constraint 선언과 연결된ConstraintValidator 구현을 보여줍니다:

@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy=MyConstraintValidator.class)
public @interface MyConstraint {
}
import jakarta.validation.ConstraintValidator;

public class MyConstraintValidator implements ConstraintValidator {

	@Autowired;
	private Foo aDependency;

	// ...
}

앞의 예제에서 볼 수 있듯이, ConstraintValidator 구현은 다른 Spring 빈과 마찬가지로@Autowired 종속성을 가질 수 있습니다.

Spring-driven Method Validation

메서드 유효성 검사의 메서드 유효성 검사 기능을 MethodValidationPostProcessor 빈 정의를 통해 Spring 컨텍스트에 통합할 수 있습니다:

import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;

@Configuration
public class AppConfig {

	@Bean
	public static MethodValidationPostProcessor validationPostProcessor() {
		return new MethodValidationPostProcessor();
	}
}

Spring 기반 메서드 유효성 검사를 사용하려면 대상 클래스에 Spring의 @Validated 주석을 추가해야 하며, 이 주석은 선택적으로 사용할 유효성 검사 그룹을 선언할 수도 있습니다. 최대 절전 모드 유효성 검사기 및 Bean 유효성 검사 공급자를 사용한 설정에 대한 자세한 내용은MethodValidationPostProcessor를참조하세요.

 
메서드 유효성 검사는 인터페이스의 메서드에 대한 JDK 동적 프록시 또는 CGLIB 프록시인 대상 클래스 주변의 AOP 프록시에 의존합니다. 프록시 사용에는 특정 제한 사항이 있으며, 그 중 일부는AOP 프록시 이해에 설명되어 있습니다. 또한 항상 프록시된 클래스에서 메서드와 접근자를 사용해야 하며 직접 필드 액세스는 작동하지 않습니다.

Spring MVC와 WebFlux는 동일한 기본 메서드 유효성 검사를 기본적으로 지원하지만 AOP가 필요하지 않습니다. 따라서 이 섹션의 나머지 부분을 확인하고 Spring MVC유효성 검사 및오류 응답 섹션과 WebFlux유효성 검사 및오류 응답 섹션도 참조하세요.

메서드 유효성 검사 예외

기본적으로 jakarta.validation.Validator가 반환한ConstraintViolation집합과 함께 jakarta.validation .ConstraintViolationException이 발생합니다. 대안으로, MessageSourceResolvable 오류에 맞게 조정된 ConstraintViolations와함께 MethodValidationException이 대신 발생하도록 할 수 있습니다. 활성화하려면 다음 플래그를 설정하세요:

import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;

@Configuration
public class AppConfig {

	@Bean
	public static MethodValidationPostProcessor validationPostProcessor() {
		MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
		processor.setAdaptConstraintViolations(true);
		return processor;
	}
}

메서드 매개변수별로 오류를 그룹화하는 ParameterValidationResults목록을 포함하며, 각각은 MethodParameter, 인수 값 및ConstraintViolations에서조정된 MessageSourceResolvable 오류 목록을 노출합니다. 필드 및 프로퍼티에 대한 계단식 위반이 있는 @Valid 메서드 매개 변수의 경우, ParameterValidationResult는 org.springframework.validation.Errors를 구현하고 유효성 검사 오류를FieldErrors로노출하는 ParameterErrors입니다.

유효성 검사 오류 사용자 정의

로캘 및 언어별 리소스 번들과 함께 구성된 MessageSource를 통해 사용자에게 표시할 오류 메시지로 변환된 MessageSourceResolvable 오류를 사용자 정의할 수 있습니다. 이 섹션에서는 설명을 위한 예제를 제공합니다.

다음 클래스 선언이 주어집니다:

record Person(@Size(min = 1, max = 10) String name) {
}

@Validated
public class MyService {

	void addStudent(@Valid Person person, @Max(2) int degrees) {
		// ...
	}
}

Person.name()  컨스트레인트 위반은 다음과 같은 필드 오류에 적용됩니다:

  • 오류 코드 "Size.student.name", "Size.name", "Size.java.lang.String"  "Size"
  • 메시지 인수 "name", 10  1 (필드 이름 및 제약 조건 속성)
  • 기본 메시지 "크기는 1에서 10 사이여야 합니다."

기본 메시지를 사용자 지정하려면 위의 오류 코드 및 메시지 인수를 사용하여메시지 소스리소스 번들에 속성을 추가할 수 있습니다. 또한 메시지 인수 "name " 자체는 오류 코드"student.name " 및 "name "이 있는 MessagreSourceResolvable이며 사용자 지정할 수도 있습니다. 예를 들어

속성
Size.student.name=Please, provide a {0} that is between {2} and {1} characters long
student.name=username

Degrees 메서드 매개 변수에 대한 ConstraintViolation은 다음과 같이MessageSourceResolvable에 적용됩니다:

  • 오류 코드 "Max.myService#addStudent.degrees", "Max.degrees", "Max .int", "Max"
  • 메시지 인수 "degrees2 및 2(필드 이름 및 제약 조건 속성)
  • 기본 메시지 "2보다 작거나 같아야 함"

위의 기본 메시지를 사용자 지정하려면 다음과 같은 속성을 추가하면 됩니다:

속성
Max.degrees=You cannot provide more than {1} {0}

추가 구성 옵션

대부분의 경우 기본 LocalValidatorFactoryBean 구성으로 충분합니다. 메시지 보간에서 트래버스 해상도에 이르기까지 다양한 Bean 유효성 검사 구성을 위한 여러 가지 구성 옵션이 있습니다. 이러한 옵션에 대한 자세한 내용은LocalValidatorFactoryBean자바독을 참조하세요.

Configuring a DataBinder

유효성 검사기를 사용하여 데이터 바인더 인스턴스를 구성할 수 있습니다. 구성이 완료되면 binder.validate()를 호출하여 유효성 검사기를 호출할 수 있습니다. 모든 유효성 검사 오류는 바인더의 BindingResult에 자동으로 추가됩니다.

다음 예제는 프로그래밍 방식으로 DataBinder를 사용하여 대상 객체에 바인딩한 후 유효성 검사 로직을 호출하는 방법을 보여줍니다:

Foo target = new Foo();
DataBinder binder = new DataBinder(target);
binder.setValidator(new FooValidator());

// bind to the target object
binder.bind(propertyValues);

// validate the target object
binder.validate();

// get BindingResult that includes any validation errors
BindingResult results = binder.getBindingResult();

데이터바인더를 여러 유효성 검사기 인스턴스로 구성할 수도 있습니다. 데이터바인더.addValidators  데이터바인더.replaceValidators를 통해 여러 유효성 검사기 인스턴스로 구성할 수도 있습니다. 이는 전역으로 구성된 빈 유효성 검사를 DataBinder 인스턴스에 로컬로 구성된 Spring 유효성 검사기와 결합할 때 유용합니다.Spring MVC 유효성 검사 구성을 참조하세요.

Spring MVC 3 Validation

Spring MVC 장의 유효성 검사를 참조하세요.


반응형
profile

while(1) work();

@유호건

❤️댓글은 언제나 힘이 됩니다❤️ 궁금한 점이나 잘못된 내용이 있다면 댓글로 남겨주세요.

검색 태그