'2017/10/19'에 해당되는 글 6건

  1. 2017.10.19 Introduction to Spring Converters and Formatters
  2. 2017.10.19 Formatter
  3. 2017.10.19 Convertor
  4. 2017.10.19 WebMvcConfigurerAdapter
  5. 2017.10.19 Spring support for @Controller given by <context:component-scan /> vs <mvc:annotation-driven>
  6. 2017.10.19 Difference between <context:annotation-config> vs <context:component-scan>

Introduction to Spring Converters and Formatters

FRAMEWORK/SPRING 2017. 10. 19. 17:13

Introduction

In this article, we will provide introductory details on Spring Converters and Formatters. Converter components are used for converting one type to another type and also to provide a cleaner separation by forcing to place all such conversion related code in one single place. Spring already supports built-in converters for the commonly used types and the framework is extensible enough for writing custom converters as well. Spring Formatters come into picture to format the data according to the display where it is rendered.
Examples may include formatting date/timestamp values according to locales etc. The first section of this article deals with Converters whereas the rest deals with Formatters and plenty of code samples are given at appropriate places for better illustration. This article assumes that readers has the sufficient knowledge on Spring Framework and its workflow. If you are beginner looking for basic concepts on Spring Framework, please read Introduction to Spring Framework and Introduction to Spring Web Flow (SWF). The following section provides the list of populate articles in the Spring Framework.
also read:

Built-in Converters

In this section, we will look into the series of Built-in Converters in Spring. It’s always worthwhile to see the exhaustive list of pre-built converters before even thinking of writing a custom converter that suits for a particular business need. Converters in Spring are available as services and typically client will make use of converter services while working with the conversion process.

package net.javabeat.articles.spring.converter.builtin;

import java.util.List;

import org.springframework.core.convert.support.ConversionServiceFactory;
import org.springframework.core.convert.support.GenericConversionService;

public class BuiltInForStringTypeTest {

	public static void main(String[] args) {

		GenericConversionService conversionService = ConversionServiceFactory.createDefaultConversionService();

		testToArray(conversionService);
		testToCollection(conversionService);
		testToBoolean(conversionService);
		testToCharacter(conversionService);
		testToNumber(conversionService);
		testToEnum(conversionService);
	}

	private static void testToArray(GenericConversionService conversionService){

		String[] stringArray = conversionService.convert("One,Two,Three", String[].class);
		for (String element : stringArray){
			System.out.println("Element is " + element);
		}
	}

	private static void testToCollection(GenericConversionService conversionService){

		@SuppressWarnings("unchecked")
		List listOfStrings = conversionService.convert("One,Two,Three", List.class);
		for (String element : listOfStrings){
			System.out.println("Element is " + element);
		}
	}

	private static void testToBoolean(GenericConversionService conversionService){

		Boolean data = null;

		data = conversionService.convert("true", Boolean.class);
		System.out.println("Boolean value is " + data);

		data = conversionService.convert("no", Boolean.class);
		System.out.println("Boolean value is " + data);
	}

	private static void testToCharacter(GenericConversionService conversionService){

		Character data = null;

		data = conversionService.convert("A", Character.class);
		System.out.println("Character value is " + data);

		data = conversionService.convert("Exception", Character.class);
		System.out.println("Character value is " + data);
	}

	private static void testToNumber(GenericConversionService conversionService){

		Integer intData = conversionService.convert("124", Integer.class);
		System.out.println("Integer value is " + intData);

		Float floatData = conversionService.convert("215f", Float.class);
		System.out.println("Float value is " + floatData);
	}

	private static void testToEnum(GenericConversionService conversionService){

		TaskStatus taskStatus = conversionService.convert("PENDING", TaskStatus.class);
		System.out.println("Task Status is " + taskStatus);
	}

}

Go through the above code listing which illustrates the concept of converters. In the above sample code, an attempt is made to convert a string object to various different types such as Array, Collection, Boolean, Character, Number and Enumeration. The example is pretty straightforward, though it is worthwhile to provide description on what each test method is doing. It is essential to the client to have an instance of ConversionService before working out with converts and the same is obtained through ConversionServiceFactory.
For converting a string to an array (a string array), the string has to be comma-delimited, though there is currently no support for the client to pass the delimiter. The same thing holds good for converting a string to a Collection type. String objects can even be converted to Boolean objects. The valid string values for a corresponding ‘true’ Boolean object are ‘true’, ‘on’, ‘yes’ and ‘1’, whereas for a ‘falsify’ a Boolean object, the allowed values are ‘false’, ‘off’, ‘no’ and ‘0’. For converting a string to a character, the string holding the character must be of length one, else an ‘IllegalArgumentException’ will be thrown at the run-time.

package net.javabeat.articles.spring.converter.builtin;

public enum TaskStatus {

	STARTED,

	COMPLETED,

	PENDING
}

Similarly, while converting a string to an enumeration, the appropriate valueOf() method will be called on the Enum object. In the above example, an attempt is made to convert the task status to a TaskStatus enumeration object.

Converting Array types to Collection and String

In the following example, we will see how to use Converters for converting an Array type to other different types such as Collection, String and to a Generic object.

package net.javabeat.articles.spring.converter.builtin;

import java.util.List;

import net.javabeat.articles.spring.converter.custom.Article;
import net.javabeat.articles.spring.converter.custom.StringToArticleConverter;

import org.springframework.core.convert.support.ConversionServiceFactory;
import org.springframework.core.convert.support.GenericConversionService;

public class BuiltInForArrayTypeTest {

	public static void main(String[] args) {

		GenericConversionService conversionService = ConversionServiceFactory.createDefaultConversionService();

		testToCollection(conversionService);
		testToString(conversionService);
		testToObject(conversionService);
	}

	private static void testToCollection (GenericConversionService conversionService){

		@SuppressWarnings("unchecked")
		List listOfColors = conversionService.convert(
			new String[]{"Red","Blue","Green"}, List.class);
		for (String color : listOfColors){
			System.out.println("Color is " + color);
		}
	}

	private static void testToString(GenericConversionService conversionService){

		String colors = conversionService.convert(
			new String[]{"Red","Blue","Green"}, String.class);
		System.out.println("Colors is " + colors);
	}

	private static void testToObject(GenericConversionService conversionService){

		conversionService.addConverter(new StringToArticleConverter());

		Article article = conversionService.convert(
			new String[]{"Introduction to Google Guice,Google Guice"}, Article.class);
		System.out.println("Article name is " + article.getName());
		System.out.println("Article category is " + article.getCategory());
	}

}

In the above example, we have converted the array of string (representing colors) to a list of string objects by calling the convert() defined on the ConversionService object. The example also illustrates how a conversion can happen from a string array type to a string type. Note that the resultant string will have the comma delimiter strings from the string array. The method ‘testToObject()’ introduces a custom Domain object called ‘Article’ which will be explained later.

DO YOU REALLY KNOW YOUR MOBILES?

How many times does the average person unlock their phone a day?

In 1973, Martin Cooper made the first mobile phone call to whom?

In Japan, 90% of phones have which feature?

99% of all mobile malware is targeted at which mobile users?

Exposure to cell phone radiation right before sleep has been known to cause:

Nomofobia is a psychological condition where a person:

In Malaysia, it is legal to do what via text message?

In 1983, the first cell phones were sold in the U.S. for how much each?

What is the fate of 100,000 mobile phones a year in Britain?

At 250 million gadgets sold, the best-selling gadget in history is:

 
15
  • 20

  • 110

  • 55

  • 201

  • Veronica Lake

  • His mother

  • A rival telecommunications company

  • The president of Russia

  • Eyeball tracking

  • Two screens

  • Projector

  • Waterproof

  • iOS

  • Windows

  • Blackberry

  • Android

  • Insomnia

  • Forgetfulness

  • Incomplete digestion

  • Depression

  • Is afraid of being without their phone

  • Is repulsed by touch screens

  • Fears new technology

  • Misplaces their phone daily

  • Divorce your partner

  • Confess guilty to a crime

  • Buy a house

  • Declare bankruptcy

  • $62,000

  • $3,095

  • $25

  • $400

  • Stolen by family members

  • Overheated in parked cars

  • Broken by toddlers

  • Dropped down the toilet

  • 3.4 Samsung Galaxy C

  • iPhone 6

  • Nokia 1100

  • Android 1.6. Donut

START NEXT QUIZ
You Scored A Fair
 
5/10
CHALLENGE

NEXT QUIZ STARTS IN:

 
10
Advertisement
package net.javabeat.articles.spring.converter.builtin;

import java.util.Arrays;
import java.util.List;

import net.javabeat.articles.spring.converter.custom.Article;
import net.javabeat.articles.spring.converter.custom.ArticleToStringConverter;

import org.springframework.core.convert.support.ConversionServiceFactory;
import org.springframework.core.convert.support.GenericConversionService;

public class BuiltInForCollectionTypeTest {

	public static void main(String[] args) {

		GenericConversionService conversionService = ConversionServiceFactory.createDefaultConversionService();

		testToArray(conversionService);
		testToString(conversionService);
		testToObject(conversionService);
	}

	private static void testToArray(GenericConversionService conversionService){

		List languages = Arrays.asList("C", "C++", "Java");
		String[] stringArray = conversionService.convert(languages, String[].class);
		for (String language : stringArray){
			System.out.println("Language is " + language);
		}
	}

	private static void testToString(GenericConversionService conversionService){

		List languages = Arrays.asList("C", "C++", "Java");
		String languagesAsString = conversionService.convert(languages, String.class);
		System.out.println("All languages -->" + languagesAsString);
	}

	private static void testToObject(GenericConversionService conversionService){

		conversionService.addConverter(new ArticleToStringConverter());

		Article articleObject = new Article("Introduction to Google Guice", "Google Guice");
		String articleAsString = conversionService.convert(
			new Article[]{articleObject}, String.class);
		System.out.println("Article -->" + articleAsString);
	}
}

The above example shows how to convert Collection types to Array, string and Object type. Note that the test methods follow the very similar pattern as shown in the previous example.

Custom Converters using Spring Converters and Formatters

So far we have seen how to make use of Spring Converters for performing conversions between the basic and the very often used data types in the Java programming language. However that may not often suffice and there will be always a necessity to do conversions on user-defined objects. As always Spring’s framework is extensible and in this section, we will illustrate the usage of Custom converters for converting user-defined or custom objects.

package net.javabeat.articles.spring.converter.custom;

public class Article {

	private String name;
	private String category;

	public Article(String name, String category){
		this.name = name;
		this.category = category;
	}

	public String getName() {
		return name;
	}

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

	public String getCategory() {
		return category;
	}

	public void setCategory(String category) {
		this.category = category;
	}
}

The user-defined class we will be using for illustrating the concept as well as throughout this article is ‘Article’. As you can see in the above definition, the structure of ‘Article’ class is very simple; it has two properties, the ‘name’ and the ‘category’.

package net.javabeat.articles.spring.converter.custom;

import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.Converter;

public class ArticleToStringConverter implements Converter<Article, String>{

	@Override
	public String convert(Article article) {

		if (article == null){
			throw new ConversionFailedException(TypeDescriptor.valueOf(Article.class),
				TypeDescriptor.valueOf(String.class), article, null);
		}

		StringBuilder builder = new StringBuilder();
		builder.append(article.getName());
		builder.append("-");
		builder.append(article.getCategory());
		return builder.toString();
	}

}

In this section, we will see how to convert a given ‘Article’ object to a ‘String’ object by writing a custom converter. As you can see in the above listing, for writing any custom converter, the interface Converter needs to be implemented and the method convert() has to be overridden. This interface uses Java Generics to achieve maximum compile-time safety and that is obvious in the declaration of the interface itself. The interface accepts the source and the target types as type parameters and the method convert() accepts the same source parameter and returns the target parameter. In our example case, the source parameter will be of type ‘Article’ and the target parameter will be of ‘String’. In the implementation of the convert() method, after performing suitable null-conditional checks, we return an instance of string object after concatenating the various properties of the article.
Before seeing the usage of this Custom converter, we will also write the reverse converter class which tries to convert a string object to an Article object. Note the following class definition below. Here the source type is ‘String’ and the target type is ‘Article’.

package net.javabeat.articles.spring.converter.custom;

import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.Converter;

public class StringToArticleConverter implements Converter<String, Article>{

	@Override
	public Article convert(String articleAsString) {

		if (articleAsString == null){
			throw new ConversionFailedException(TypeDescriptor.valueOf(String.class),
				TypeDescriptor.valueOf(String.class), articleAsString, null);
		}

		String[] tempArray = articleAsString.split(",");
		Article article = new Article(tempArray[0], tempArray[1]);
		return article;
	}

}

In the above sample, it is expected that the name and the category properties of the article object are concatenated as a string with comma as a delimiter. Hence appropriate parsing is done within the implementation of convert() method and an appropriate instance of Article object is constructed from the string.

package net.javabeat.articles.spring.converter.custom;

import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.support.GenericConversionService;

public class CustomConverterTest {

	public static void main(String[] args) {

//		test1();
		test2();
		test3();
	}

	private static void test1(){

		GenericConversionService conversionService = new GenericConversionService();

		Converter<Article, String> customConverter = new ArticleToStringConverter();
		conversionService.addConverter(customConverter);

		String result = customConverter.convert(null);
		System.out.println("Result is " + result);
	}

	private static void test2(){

		GenericConversionService conversionService = new GenericConversionService();

		Converter<Article, String> customConverter = new ArticleToStringConverter();
		conversionService.addConverter(customConverter);

		Article article = new Article("Introduction to Spring Converters", "Core Spring");
		String result = conversionService.convert(article, String.class);
		System.out.println("Result is '" + result + "'");
	}

	private static void test3(){

		GenericConversionService conversionService = new GenericConversionService();

		Converter<String, Article> customConverter = new StringToArticleConverter();
		conversionService.addConverter(customConverter);

		String articleAsString = new String(
			"Introduction to Spring Converters,Core Spring");
		Article result = conversionService.convert(articleAsString, Article.class);
		System.out.println("Article name is " + result.getName());
		System.out.println("Article category is " + result.getCategory());
	}
}

It is necessary to make any Custom Converters visible to the Spring’s Conversion framework by appropriately registering them. Registration of a custom converter to the Converter registry can be achieved by calling the method addConverter() defined on the ConversionService object. In the ‘test2()’ method, we have registered the ‘ArticleToStringConverter’ and have called the method ‘convert()’ defined on ConversionService by passing in an article object. Similarly in the method test3(), the converter ‘StringToArticleConverter’ is registered and a similar attempt is made to convert the String object to an Article object.

Converter Factory

In this section, we will see the usage of Converter Factories which follow the Factory Design pattern for creating Converter objects. Converter Factory provides a centralized place for creating converter objects. This prevents the client from directly getting exposed to a series of custom converter classes in an application.

package net.javabeat.articles.spring.converter.factory;

import net.javabeat.articles.spring.converter.custom.Article;
import net.javabeat.articles.spring.converter.custom.StringToArticleConverter;

import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConverterFactory;

public class StringToArticleConverterFactory implements ConverterFactory<String, Article>{

	@SuppressWarnings("unchecked")
	@Override
	public  Converter<String, T> getConverter(Class arg0) {
		return (Converter<String, T>) new StringToArticleConverter();
	}
}

Have a look at the above sample where we create a factory for ‘StringToArticle’ converter. Note that any converter factory class must implement ‘ConvertFactory’ interface and the method getConverter() has to overridden that will create and return a suitable converter object. Similarly, in the below listing, a factory is created for wrapping the implementation for ‘StringToArticleConverter’.

package net.javabeat.articles.spring.converter.factory;

import net.javabeat.articles.spring.converter.custom.Article;
import net.javabeat.articles.spring.converter.custom.ArticleToStringConverter;

import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConverterFactory;

public class ArticleToStringConverterFactory implements ConverterFactory<Article, String>{

	@SuppressWarnings("unchecked")
	@Override
	public  Converter<Article, T> getConverter(Class arg0) {
		return (Converter<Article, T>)new ArticleToStringConverter();
	}

}

When it comes to the client usage, we register the custom converter factory implementations by calling the method ‘addCustomFactory()’ defined on the ConversionService object. Have a glance over the following piece of client code.

package net.javabeat.articles.spring.converter.factory;

import net.javabeat.articles.spring.converter.custom.Article;

import org.springframework.core.convert.support.GenericConversionService;

public class FactoryTest {

	public static void main(String[] args) {

		GenericConversionService conversionService = new GenericConversionService();

		conversionService.addConverterFactory(new ArticleToStringConverterFactory());
		conversionService.addConverterFactory(new StringToArticleConverterFactory());

		String articleAsString = "Java Programming,Java";
		Article article = conversionService.convert(articleAsString, Article.class);
		System.out.println("Article name is " + article.getName());
		System.out.println("Article category is " + article.getCategory());

		articleAsString = conversionService.convert(article, String.class);
		System.out.println("Article as string is '" + articleAsString + "'");
	}
}

The custom converter factory implementations ‘ArticleToStringConversionFactory’ and ‘StringToArticleConversionFactory’ are appropriately registered by calling the method addConversionFactory(). Note that when this method is called, the converter instances returned from the converter factory will be registered and maintained in the Converter registry.

Built-in Formatters in Spring Converters and Formatters

Similar to built-in components available for Converter components, there are number of built-in Formatter components for formatting date, timestamp and numeric data.

package net.javabeat.articles.spring.formatter.builtin;

import java.util.Date;
import java.util.Locale;

import org.springframework.format.Formatter;
import org.springframework.format.datetime.DateFormatter;
import org.springframework.format.number.NumberFormatter;

public class FormatterTest {

	public static void main(String[] args) throws Exception{

		testDateFormatter();
		testNumberFormatter();
	}

	private static void testDateFormatter(){

		Formatter dateFormatter = new DateFormatter();
		String dateAsString = dateFormatter.print(new Date(), Locale.CHINA);
		System.out.println("Date as string in chinese locale is " + dateAsString);
	}

	private static void testNumberFormatter() throws Exception{

		NumberFormatter doubleFormatter = new NumberFormatter();
		doubleFormatter.setPattern("#####.###");
		String number = doubleFormatter.print(new Double(12325.1144d), Locale.ITALIAN);
		System.out.println("Number is " + number);
	}
}

Have a look at the above sample code. Formatters in Spring are modeled through the Formatter interface which in turn extends the Printer and the Parser interface. The interface Printer encapsulates the behavior of displaying the data on a particular locale whereas the interface Parser is responsible for parsing the data for the given locale. Implementations are free to ignore the locale if they want to. The above example illustrates the usage of Date and Number Formatters.

Custom Formatters

It is always possible to write Custom formatters especially for parsing and formatting user-defined objects. For illustration purposes, we will consider the user-defined Domain object CreditCardNumber in this example.

package net.javabeat.articles.spring.formatter.custom;

public class CreditCardNumber {

	private int firstFourDigits;
	private int secondFourDigits;
	private int thirdFourDigits;
	private int fourthFourDigits;

	public CreditCardNumber(){}

	public CreditCardNumber(int firstFourDigits, int secondFourDigits,
		int thirdFourDigits, int fourthFourDigits){
		this.firstFourDigits = firstFourDigits;
		this.secondFourDigits = secondFourDigits;
		this.thirdFourDigits = thirdFourDigits;
		this.firstFourDigits = firstFourDigits;
	}

	public int getFirstFourDigits() {
		return firstFourDigits;
	}
	public void setFirstFourDigits(int firstFourDigits) {
		this.firstFourDigits = firstFourDigits;
	}
	public int getSecondFourDigits() {
		return secondFourDigits;
	}
	public void setSecondFourDigits(int secondFourDigits) {
		this.secondFourDigits = secondFourDigits;
	}
	public int getThirdFourDigits() {
		return thirdFourDigits;
	}
	public void setThirdFourDigits(int thirdFourDigits) {
		this.thirdFourDigits = thirdFourDigits;
	}
	public int getFourthFourDigits() {
		return fourthFourDigits;
	}
	public void setFourthFourDigits(int fourthFourDigits) {
		this.fourthFourDigits = fourthFourDigits;
	}

}

The listing for the model class CreditCardNumber is shown below. As one can see, the structure is pretty simple as it holds four integer properties representing the four ‘four digits number that appear in a credit card.
For writing a custom formatter, we need to define the behavior of parsing and printing (displaying) the domain objects. Parsing of an object is encapsulated in the Parserinterface and one needs to implement this interface to write a custom parser. Again, the Parser interface supports compile-time safety by defining the generic type T.

package net.javabeat.articles.spring.formatter.custom;

import java.text.ParseException;
import java.util.Locale;

import org.springframework.format.Parser;

public class CreditCardNumberParser implements Parser{

	@Override
	public CreditCardNumber parse(String ccNumber, Locale locale) throws ParseException {

		String digitsArray[] = ccNumber.split("-");
		if (digitsArray == null || digitsArray.length != 4){
			throw new org.springframework.expression.ParseException(-1, "Invalid format");
		}

		CreditCardNumber ccNumberObject = new CreditCardNumber();
		ccNumberObject.setFirstFourDigits(Integer.parseInt(digitsArray[0]));
		ccNumberObject.setSecondFourDigits(Integer.parseInt(digitsArray[1]));
		ccNumberObject.setThirdFourDigits(Integer.parseInt(digitsArray[2]));
		ccNumberObject.setFourthFourDigits(Integer.parseInt(digitsArray[3]));

		return ccNumberObject;
	}

}

Note that the method parse() has to be overridden for parsing the credit card number which will be passed as a string as designated by the first parameter. Note that the parse() method also accepts a Locale object as the second parameter. It is up to the implementation whether to consider or to ignore the ‘Locale’ parameter. For simplicity, in our example, we have ignored this parameter.
It is expected that the credit card number is passed as a string with the delimiter being ‘-‘ between every set of four digits. The implementation checks if the format of the credit card number is correct, if not, it throws a ParseException thereby aborting the parse operation. Once the incoming string proves to be of correct format, an instance of Credit Card Number class is created and the delimited values obtained from the string is appropriately set to the object and returned.

package net.javabeat.articles.spring.formatter.custom;

import java.util.Locale;

import org.springframework.format.Printer;

public class CreditCardNumberPrinter implements Printer{

	@Override
	public String print(CreditCardNumber ccNumberObject, Locale locale) {

		return
				ccNumberObject.getFirstFourDigits() + "-"
			+	ccNumberObject.getSecondFourDigits() + "-"
			+ 	ccNumberObject.getThirdFourDigits() + "-"
			+ 	ccNumberObject.getFirstFourDigits();
	}

}

Displaying the data is encapsulated through the interface Printer and a custom displayable class for displaying an object is expected to implement this interface and thereby providing the functionality in the print() method. Note that the first parameter passed to print() is the object itself and the second parameter is the Locale object. Again, it is up to the implementation to consider or to ignore the Locale parameter. Within the implementation of the print() method we have constructed a suitable string from the CreditCardNumber object.

package net.javabeat.articles.spring.formatter.custom;

import java.text.ParseException;
import java.util.Locale;

import org.springframework.format.Formatter;
import org.springframework.format.Parser;
import org.springframework.format.Printer;

public class CreditCardNumberFormatter implements Formatter{

	private Parser parser;
	private Printer printer;

	public CreditCardNumberFormatter(Parser parser,
		Printer printer){
		this.parser = parser;
		this.printer = printer;
	}

	@Override
	public String print(CreditCardNumber ccNumberObject, Locale locale) {
		return printer.print(ccNumberObject, locale);
	}

	@Override
	public CreditCardNumber parse(String ccNumber, Locale locale) throws ParseException {
		return parser.parse(ccNumber, locale);
	}
}

Let us have a look at the implementation of Formatter class for formatting the credit card number. Formatter interface defines the abstract methods parse() and print() whose return types are Parser and Printer respectively. Because we have already provided the implementation of Parser and Printer, the methods parse() and print() redirects the control to the parse() and print() methods present in CreditCardNumberParser and CreditCardNumberPrinter respectively.

package net.javabeat.articles.spring.formatter.custom;

import org.springframework.format.support.FormattingConversionService;

public class Client {

	public static void main(String[] args) {

		FormattingConversionService service = new FormattingConversionService();

		CreditCardNumberParser parser = new CreditCardNumberParser();
		CreditCardNumberPrinter printer = new CreditCardNumberPrinter();
		service.addFormatterForFieldType(CreditCardNumber.class, printer, parser);

//		CreditCardNumberFormatter formatter = new CreditCardNumberFormatter(
//			parser, printer);
//		service.addFormatterForFieldType(CreditCardNumber.class, formatter);

		test1(service);
		test2(service);
	}

	private static void test1(FormattingConversionService service){

		String ccNumber = "1111-2222-3333-4444";
		CreditCardNumber ccNumberObject = service.convert(ccNumber, CreditCardNumber.class);

		System.out.println(ccNumberObject.getFirstFourDigits());
		System.out.println(ccNumberObject.getSecondFourDigits());
		System.out.println(ccNumberObject.getThirdFourDigits());
		System.out.println(ccNumberObject.getFourthFourDigits());
	}

	private static void test2(FormattingConversionService service){

		CreditCardNumber ccNumberObject = new CreditCardNumber(
			1111, 2222, 3333, 4444);

		String ccNumber = service.convert(ccNumberObject, String.class);
		System.out.println("CC Number is " + ccNumber);
	}

}

In the client program, we have to register the customized formatter implementation of Credit Card Number object by method addFormatterForFieldType() defined on the FormattingConversionService object. Note that the service ‘FormattingConversionService’ can be used to parse and print data as well as can be used for registering customized formatters. The method addFormatterForFieldType() accepts the class type for which the formatting has to be applied, printer object and the parser object as its arguments. An overloaded version of the same method is available which takes the class type and the formatter object directly.

Conclusion

This article started with explaining the needs to have converter components and went on to explaining the various built-in converters available in Spring framework. Code samples were provided to illustrate about writing custom converters also. As a closure to converters, the need for Converter Factories was also discussed in detail. The final section of the article explained the needs for formatters and discussed the various built-in formatters available in Spring. It also provided assistance in writing custom formatters with examples.
also read:

 

출처 - http://javabeat.net/introduction-to-spring-converters-and-formatters/

'FRAMEWORK > SPRING' 카테고리의 다른 글

클래스 리터럴, 타입 토큰, 수퍼 타입 토큰  (0) 2017.10.20
Super type token  (0) 2017.10.20
Formatter  (0) 2017.10.19
Convertor  (0) 2017.10.19
WebMvcConfigurerAdapter  (0) 2017.10.19
:

Formatter

FRAMEWORK/SPRING 2017. 10. 19. 17:12

Spring MVC - Creating a new Formatter

[Updated: Nov 19, 2016, Created: Feb 26, 2016]

In this example we are going to define our own formatter using spring Formatter SPI. Spring Formatter interface extends Printer and Parser interfaces:

 package org.springframework.format;
 public interface Formatter<T> extends Printer<T>, Parser<T> {
 }

We have to implement print() and parse() methods of Printer and Parser respectively.



we are going to demonstrate how to format an object of Address instance in Customer object.

Creating Backing object classes

public class Customer {
    private Long id;
    private String name;
    private Address address;

   //getters and setters
}

public class Address {
    private String street;
    private String city;
    private String county;
    private String zipCode;

    //getters and setters
}



Creating our Formatter

import org.springframework.format.Formatter;
import java.text.ParseException;
import java.util.Locale;

public class AddressFormatter implements Formatter<Address> {
    private Style style = Style.FULL;

    public void setStyle (Style style) {
        this.style = style;
    }

    @Override
    public Address parse (String text, Locale locale) throws ParseException {
        if (text != null) {
            String[] parts = text.split(",");
            if (style == Style.FULL && parts.length == 4) {
                Address address = new Address();
                address.setStreet(parts[0].trim());
                address.setCity(parts[1].trim());
                address.setZipCode(parts[2].trim());
                address.setCounty(parts[3].trim());
                return address;
            } else if (style == Style.REGION && parts.length == 3) {
                Address address = new Address();
                address.setCity(parts[0].trim());
                address.setZipCode(parts[1].trim());
                address.setCounty(parts[4].trim());
                return address;
            }
        }
        return null;
    }

    @Override
    public String print (Address a, Locale l) {
        if (a == null) {
            return "";
        }
        switch (style) {
            case FULL:
                return String.format(l, "%s, %s, %s, %s", a.getStreet(), a.getCity(),
                                     a.getZipCode(), a.getCounty());
            case REGION:
                return String.format(l, "%s, %s, %s", a.getCity(), a.getZipCode(),
                                     a.getCounty());
        }
        return a.toString();
    }

    public enum Style {
        FULL,
        REGION
    }
}



Registering The Formatter

import org.springframework.context.annotation.Configuration;
 import org.springframework.format.FormatterRegistry;
 import org.springframework.web.servlet.config.annotation.EnableWebMvc;
 import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

 @EnableWebMvc
 @Configuration
 public class MyWebConfig extends WebMvcConfigurerAdapter {

     @Override
   public void addFormatters (FormatterRegistry registry) {
   AddressFormatter addressFormatter = new AddressFormatter();
   addressFormatter.setStyle(AddressFormatter.Style.REGION);
   registry.addFormatter(addressFormatter);
   }
 }

Alternatively we could have used @InitBinder approach, if we want to customize AddressFormatter per request basis.




Creating Controller

@Controller
@RequestMapping("/customers")
public class CustomerController {
    @Autowired
    private CustomerDataService customerDataService;

    @RequestMapping(method = RequestMethod.GET)
    private String handleRequest (Model model) {
        model.addAttribute("customerList", customerDataService.getAllUsers());
        return "customers";
    }
}




customer.jsp

<%@ page language="java"
    contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@taglib uri="http://www.springframework.org/tags" prefix="spring" %>
<html>
<body>
<h3>Customer List </h3>
<table style="width:100%">
<c:forEach var="customer" items="${customerList}" varStatus="status">
<tr>
<td>
    <spring:eval expression="customer.id" />
</td>
<td>
    <spring:eval expression="customer.name" />
</td>
<td>
   <spring:eval expression="customer.address" />
</td>
</tr>
</c:forEach>
</table>
</body>
</html>

출처 - http://www.logicbig.com/tutorials/spring-framework/spring-web-mvc/spring-define-formatter/

:

Convertor

FRAMEWORK/SPRING 2017. 10. 19. 17:12

13.Data Binding and Type Conversion

Spring에서 타입 변환이 발생하는 영역은 크게 2가지이다. 하나는 Bean 정의 XML에서 <property />를 이용해 설정한 값을 실제 Bean 객체의 Property에 바인딩 시킬 때인데, XML에 String으로 정의한 값을 해당 Property의 타입으로 변환해서 셋팅해야한다.

예를 들어, Movie 클래스가 다음과 같이 정의되어 있고,

public class Movie {
    String id;
    String name;
    int ticketPrice;
}

'movie' Bean을 아래와 같이 정의했다고 하면,

<bean id="movie" class="sample.Movie">
    <property name="name" value="Avatar"/>
    <property name="ticketPrice" value="7500"/>
</bean>

'name'이라는 Property는 같은 String 타입이기 때문에 문제가 없지만, 'ticketPrice'의 경우 String으로 작성된 '7500'값을 int 타입의 7500으로 변환하여 바인딩 해야한다.

타입 변환이 발생하는 다른 한가지 경우는, 아래 코드 예와 같이 HTTP Request 파라미터로 들어온 사용자 입력 값들을 'Movie'라는 Model 객체에 바인딩시킬 때이다. 여기서도 마찬가지로 문자열로 표현된 값을 특정 타입으로 변환하는 과정이 필요하다.

@RequestMapping("/movies/new", method=RequestMethod.POST)
public String create(@ModelAttribute Movie movie, BindingResult results) {

    this.movieService.create(movie);
    status.setComplete();

    return "redirect:/movies";
}

또한 단순히 타입의 변환이 아니라, 사용자가 보는 View에서 값에 "$45.22"와 같은 특정 Format이 적용되어 변환되어야 하는 경우도 종종 있다.

이 장에서는 이러한 타입 변환을 위해서 Spring에서 지원하고 있는 기술들에 대해서 자세히 알아보도록 하겠다.

13.1.PropertyEditor

Spring에서는 위에서 언급한 타입 변환을 위해서 기본적으로 JavaBeans 표준에서 제공하는 PropertyEditor를 사용해왔다. PropertyEditor는 String과 특정 타입 객체 간의 변환 로직을 구현할 수 있는 인터페이스이다.

13.1.1.Implementing Custom Editor

타입 변환시 호출되는 PropertyEditor의 메소드는 setValue()/getValue(), setAsText()/getAsText() 4가지 이다. PropertyEditorSupport를 상속받아서 setAsText()/getAsText() 메소드만 오버라이드하면 특정 타입 변환을 위한 PropertyEditor를 구현할 수 있다.

Spring에서 제공하고 있는 CustomBooleanEditor 코드를 조금 살펴보면, 아래와 같이 setAsText() 메소드에는 String값을 받아서 boolean값으로 변환하여 setValue() 해주는 로직이 구현되어 있고, getAsText() 메소드에는 getValue() 호출해서 가져온 값을 String으로 변환하여 리턴하는 로직이 구현되어 있다.

@Override
public void setAsText(String text) throws IllegalArgumentException {
    String input = (text != null ? text.trim() : null);
    if (this.allowEmpty && !StringUtils.hasLength(input)) {
        setValue(null);
    } else if (this.trueString != null && input.equalsIgnoreCase(this.trueString)) {
        setValue(Boolean.TRUE);
    } else if (this.falseString != null && input.equalsIgnoreCase(this.falseString)) {
        setValue(Boolean.FALSE);
    // 중략
    } else {
        throw new IllegalArgumentException("Invalid boolean value [" + text + "]");
    }
}

@Override
public String getAsText() {
    if (Boolean.TRUE.equals(getValue())) {
        return (this.trueString != null ? this.trueString : VALUE_TRUE);
    } else if (Boolean.FALSE.equals(getValue())) {
        return (this.falseString != null ? this.falseString : VALUE_FALSE);
    } else {
        return "";
    }
}

13.1.2.Default PropertyEditors

위에서 본 CustomBooleanEditor와 같이 Spring에서는 기본 타입에 대해서 이미 구현해놓은 여러가지 Build-in PropertyEditor들을 제공한다. Built-in PropertyEditor들은 모두 org.springframework.beans.propertyeditors 패키지 하위에 존재한다.

ClassEditor, FileEditor, InputStreamEditor, LocaleEditor, PropertiesEditor 등의 Built-in PropertyEditor들의 이름에서 볼 수 있듯이 Built-in PropertyEditor들은 변환할 타입에 'Editor'라는 이름을 붙인 클래스들이다. CustomNumberEditorr와 같이 사용자가 Customizing이 가능한 PropertyEditor에는 'Custom'이라는 접두어가 붙기도 한다. 이들은 모두 디폴트로 등록되어 내부적으로 사용되지만, CustomDateEditor와 StringTrimmerEditor는 디폴트로 등록되지 않기 때문에, 사용이 필요한 경우에는 반드시 직접 코드에서 등록해 주어야 한다.

13.1.3.Register Custom Editor

기본적으로 Spring에서는 Built-in PropertyEditor들을 미리 등록해놓고 사용하고 있다. 이외에 추가로 Custom Editor 등록이 필요한 경우 따로 등록을 해주어야 하는데, 이 장에서는 Custom PropertyEditor를 어떻게 등록할 수 있는 지에 대해서 알아보도록 하겠다. Spring MVC에서 사용자가 추가로 개발한 Custom PropertyEditor를 등록하는 방법에는 아래와 같이 3가지가 있다.

  • 개별 컨트롤러에 적용

    Controller에서 @InitBinder annotation을 이용하여 PropertyEditor 등록하는 메소드 정의

    @InitBinder
    public void initBinder(WebDataBinder binder) { 
        DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
        binder.registerCustomEditor(Date.class, new CustomDateEditor(df, false));
    }
  • 전체 컨트롤러에 적용

    어플리케이션 전반에서 많이 사용되는 Custom PropertyEditor의 경우 WebBindingInitializer 이용

    1. WebBindingInitializer를 구현한 클래스 생성

      public class ClinicBindingInitializer implements WebBindingInitializer {
          @Autowired
          private Clinic clinic;
      
          public void initBinder(WebDataBinder binder, WebRequest request) {
              SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
              dateFormat.setLenient(false);
              binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
              binder.registerCustomEditor(String.class, new StringTrimmerEditor(false));
              binder.registerCustomEditor(PetType.class, new PetTypeEditor(this.clinic));
          }
      }
    2. AnnotationMethodHandlerAdapter에 webBindingInitializer 속성을 이용해서 설정

      <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
          <property name="webBindingInitializer">
              <bean class="org.springframework.samples.petclinic.web.ClinicBindingInitializer" />
          </property>
      </bean>
  • 여러 개의 PropertyEditor를 여러 컨트롤러에 적용

    다수의 컨트롤러에서 자주 사용되는 여러 개의 Custom PropertyEditor 셋트로 관리할 경우 PropertyEditorRegistrar 이용

    1. PropertyEditorRegistrars를 구현한 클래스 생성

      package com.foo.editors.spring;
      					
      public final class CustomPropertyEditorRegistrar implements PropertyEditorRegistrar {
          public void registerCustomEditors(PropertyEditorRegistry registry) {
      
              // 새로운 PropertyEditor 인스턴스 생성
              registry.registerCustomEditor(ExoticType.class, new ExoticTypeEditor());
      
              // 필요한 Custom PropertyEditor들 추가
          }
      }
    2. 구현한 Custom PropertyEditorRegistrar를 Bean으로 등록

      <bean id="customPropertyEditorRegistrar" class="com.foo.editors.spring.CustomPropertyEditorRegistrar"/>
    3. @InitBinder를 이용하여 Controller에서 사용

      @Inject
      private final PropertyEditorRegistrar customPropertyEditorRegistrar;
      					
      @InitBinder
      public void initBinder(WebDataBinder binder) { 
          this.customPropertyEditorRegistrar.registerCustomEditors(binder);
      }

13.1.4.PropertyEditor의 단점

PropertyEditor는 기본적으로 String과 특정 타입 간의 변환을 지원한다. PropertyEditor는 변환 과정 중에, 변환하려고 하는 Object나 String을 PropertyEditor 객체에 잠깐 저장하였다가 변환하기 때문에, 여러 Thread에서 동시에 사용하는 경우, 변환 도중에 가지고 있던 값이 변경되어 엉뚱한 변환 값을 전달할 수도 있다. 이런 이유에서 PropertyEditor는 Thread-Safe하지 않기 때문에, Sington Bean으로 사용하지 못하고 위에서 봤던 예제 코드에서 처럼 항상 'new'를 통해서 새로 생성해야 한다.

13.2.Spring 3 Type Conversion

앞서 언급했듯이 JavaBeans의 표준인 PropertyEditor에는 몇가지 단점이 존재한다. 또한 Spring 내부적으로도 한쪽이 String으로 제한된 타입 변환이 아니라 좀 더 일반적인 타입 변환이 요구되기 시작했다. 그래서 Spring 3에서는 PropertyEditor의 단점을 극복하고 내부적으로 타입 변환이 일어나는 모든 곳에서 사용할 수 있는 범용적인 Type Conversion System을 내놓았다. 이와 관련된 클래스들은 모두 org.springframework.core.convert 패키지 하위에 존재한다. 이 장에서는 Spring 3에서 소개한 Type Conversion 서비스의 사용방법에 대해서 자세히 알아보도록 하겠다.

13.2.1.Implementing Conveter

Spring 3에서는 Converter 구현을 위해서 다음과 같이 여러가지 API를 제공하고 있다.

  • Converter

    Spring 3 Type Conversion 시스템에서 타입 변환을 실제 담당하는 객체는 Converter이다. Converter를 작성하려면 Spring에서 제공하는 org.springframework.core.convert.converter.Converter<S, T> 인터페이스를 구현하면 된다. Generics를 이용해서 Converter를 정의하므로 Run-time Type-Safety를 보장해준다.

    package org.springframework.core.convert.converter;
    				
    public interface Converter<S, T> {
        T convert(S source);    
    }

    Converter 인터페이스에서 구현해야 할 메소드는 convert() 메소드 하나이다. 즉 PropertyEditor와는 달리 단방향 타입 변환만 제공한다. 'S'에는 변환 전인 Source 타입을 명시하고, 'T'에는 변환 할 Target 타입을 명시한다. Converter 객체가 변환과 관련된 상태 값을 저장하지 않기 때문에 Converter를 Singlton Bean으로 등록하여 Multi-thread 환경에서도 안전하게 사용할 수 있다.

    다음은 Converter를 구현한 예제 코드이다.

    final class StringToInteger implements Converter<String, Integer> {
    
        public Integer convert(String source) {
            return Integer.valueOf(source);
        }   
    }
  • ConverterFactory

    클래스 계층으로 묶을 수 있는 java.lang.Number나 java.lang.Enum과 같은 타입 변환 로직을 한 곳에서 관리하고자 하는 경우, 아래의 ConverterFactory 인터페이스의 구현클래스를 작성하면 된다..

    package org.springframework.core.convert.converter;
    				
    public interface ConverterFactory<S, R> {
        <T extends R> Converter<S, T> getConverter(Class<T> targetType);
    }
    여기서 'S'에는 변환 전인 Source 타입을 명시하고, 'R'에는 변환할 클래스들의 상위 베이스 클래스를 명시한다. 그리고 getConverter() 메소드를 구현하는데, 이 때, 'T'는 'R'의 하위 클래스 타입이 될 것이다.

    다음은 ConverterFactory의 구현클래스 예이다. (Spring에서 제공하는 StringToNumberConverterFactory이다.)

    final class StringToNumberConverterFactory implements ConverterFactory<String, Number> {
    
        public <T extends Number> Converter<String, T> getConverter(Class<T> targetType) {
            return new StringToNumber<T>(targetType);
        }
    
        private static final class StringToNumber<T extends Number> implements Converter<String, T> {
    
            private final Class<T> targetType;
    
            public StringToNumber(Class<T> targetType) {
                this.targetType = targetType;
            }
    
            public T convert(String source) {
                if (source.length() == 0) {
                    return null;
                }
                return NumberUtils.parseNumber(source, this.targetType);
            }
        }
    }
  • GenericConverter

    또한, 두 가지 이상의 타입 변환을 수행하는 Converter를 개발하고자 하는 경우에는 GenericConverter 인터페이스를 구현하면 된다. 여러개의 Source/Target 타입을 지정할 수 있고, Source나 Target 객체의 Field Context(Field에 적용된 Annotation이나 Generics 등을 포함한 Field와 관련된 모든 정보)를 사용할 수 있기 때문에 유연한 Converter이긴 하지만, 그만큼 구현하기가 어렵고 복잡하다. 일반적으로 Converter나 ConverterFactory만으로 커버할 수 있는 기본적인 변환에는 사용하지 않는 것이 좋다.

    package org.springframework.core.convert.converter;
    					
    public interface GenericConverter {
    
        public Set<ConvertiblePair> getConvertibleTypes();
        
        Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
    }
    실제 GenericConverter 구현 모습을 보고 싶다면, Spring에서 제공하는 Built-in Converter 중 하나인 org.springframework.core.convert.support.ArrayToCollectionConverter 코드에서 확인할 수 있다.

  • ConditionalGenericConverter

    만약 어떤 조건을 만족하는 경우에만 변환을 수행하는 Converter를 개발할 경우는 ConditionalGenericConverter 인터페이스 구현클래스를 작성한다. 참조할 수 있는 구현 예는 Spring의 org.springframework.core.convert.support.IdToEntityConverter 이다.

13.2.2.Default Converter

Spring에서는 Converter도 PropertyEditor처럼 기본적인 타입들에 대해서 이미 구현해놓은 Built-in Converter들을 제공한다. Built-in Converter들은 모두 org.springframework.core.convert.support 패키지 하위에 존재한다.

13.2.3.Register Converter

사용자 필요에 의해서 추가로 개발한 Custom Converter들을 사용하려면 Converter도 역시 PropertyEditor처럼 등록이 필요하다. 한가지 다른 점은 각각의 Converter를 개별적으로 등록하는 것이 아니라, 모든 Converter를 가지고 변환 작업을 처리하는 ConversionService를 Bean으로 등록한 후, ConversionService Bean을 필요한 곳에서 Inject 받아서 사용한다는 것이다.

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);
}

실제 Run-time시에 Converter들의 변환 로직은 이 ConversionService에 의해서 실행된다. 기본적으로 Spring에서 사용되는 ConversionService 구현 클래스는 GenericConversionService이다. 대부분의 ConversionService 구현 클래스는 Converter 등록 기능을 가지고 있는 ConverterRegistry도 구현하고 있다.

  • ConversionService Bean 정의 시 'converters' 속성 이용

    ConversionService 구현클래스인 GenericConversionService는 ConversionServiceFactoryBean을 이용해서 Bean으로 등록할 수 있다. ConversionServiceFactoryBean이 가진 'converters' 속성을 이용하면 Custom Converter를 추가할 수도 있다.

    다음은 ConversionServiceFactoryBean을 사용하여 ConversionService를 Bean으로 정의한 모습이다.

    <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
        <!-- 추가할 Custom Converter를 설정 -->
        <property name="converters">
            <list>
                <bean class="org.anyframe.sample.moviefinder.StringToFilmRatingConverter" />
                <bean class="org.anyframe.sample.moviefinder.FilmRatingToStringConverter" />
            </list>
        </property>					
    </bean>
    ConversionServiceFactoryBean은 ConversionServiceFactory 클래스를 이용해서 디폴트 Converter들을 GenericConversionService에 등록하고, 'converters' 속성을 통해 추가된 Converter들을 등록한다.

    'conversionService'이라는 Bean 이름은 Spring에게 양보!

    Spring 3에서는 타입 변환을 위해 Run-time 시에 사용되는 ConversionService Bean을 'conversionService'라는 이름으로 찾는다. 따라서 다른 용도의 Bean을 'conversionService'라는 이름으로 등록해서는 안된다.

13.2.4.ConversionService 사용하기

앞서 PropertyEditor는 매번 new 키워드를 이용해서 매번 인스턴스를 새로 생성해야만 했기 때문에 개별 컨트롤러 적용방법과 전체 컨트롤러 적용방법이 달랐었지만, Converter의 경우는 모든 Converter들을 가지고 있는 ConversionService를 Singleton Bean으로 등록해서 사용하기 때문에 아래와 같이 개별 컨트롤러에서 사용하는 것과, WebBindingInitializer 구현클래스를 이용해서 전체 컨트롤러에서 적용하는 것이 차이가 없다.

@Inject
private ConversionService conversionService;

@InitBinder
public void initBinder(WebDataBinder binder) {
    binder.setConversionService(this.conversionService);
}

따라서 WebBindingInitializer를 구현한 클래스를 이용하여 하나의 설정으로 등록하는 것이 편리하다. Spring에서는 WebBindingInitializer를 직접 구현하지 않고 선언적인 설정만으로도 WebDataBinder의 설정을 초기화할 수 있게 해주는 ConfigurableWebBindingInitializer를 제공한다.

아래와 같이 설정하기만 하면 Custom Converter들이 추가된 ConversionService가 타입 변환 시에 사용될 것이다.

<!-- AnnotationMethodHandlerAdapter에 webBindingInitializer DI -->
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
    <property name="webBindingInitializer" ref="webBindingInitializer" />
</bean>

<!-- 사용자가 변경한 conversionService를 WebBindingInitializer 구현체에 DI -->
<bean id="webBindingInitializer" class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">
    <property name="conversionService" ref="conversionService" />
</bean>

<!-- Custom Converter들을 추가한 conversionService Bean 정의 -->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <list>
            <bean class="org.anyframe.sample.moviefinder.StringToFilmRatingConverter" />
        	<bean class="org.anyframe.sample.moviefinder.FilmRatingToStringConverter" />
        </list>
    </property>
</bean>

위와 같은 복잡한 설정을 쉽고 간편하게 할 수 있도록 Spring 3에서는 mvc 네임스페이스를 제공한다.

<mvc:annotation-driven>에 대한 자세한 내용은 본 매뉴얼 Spring MVC >> Configuration에서 Configuration Simplification 내용을 참고하기 바란다.

13.3.Spring 3 Formatting

지금까지 설명한 Conversion System은 Spring에서 범용적인 사용을 목적으로 만들어졌다. Spring 컨테이너에서 Bean의 Property 값을 셋팅할 때, Controller에서 데이터를 바인딩할 때는 물론이고 SpEL에서 데이터 바인딩 시에도 이 Conversion System을 사용한다.

Conversion System은 하나의 타입에서 다른 타입으로의 변환 로직을 구현할 수 있는 일관성있는 API를 제공한다. 그러나 실제로 사용자 UI가 존재하는 어플리케이션에서는 단순한 타입 변환만이 아니라, 날짜나 통화 표현같이 특정 Format을 객체의 값에 적용하여 String으로 변환해야 하는 경우가 종종 있다. 범용적인 용도로 만들어진 Converter에는 이러한 Formatting에 대한 처리 방법이 명시되어있지 않다.

그래서 Spring 3에서는 다음과 같은 Formatter API를 제공한다.

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;
}

13.3.1.Implementing Formatter

Formatter를 개발하기 위해서는 위의 Formatter 인터페이스를 구현하여야 한다. print() 메소드에서 format을 적용하여 출력하는 로직을 구현하고, parse() 메소드에는 format이 적용된 String 값을 분석해서 객체 인스턴스로 변환하는 로직을 구현하면 된다. 위의 인터페이스 정의에서 볼 수 있듯이, Locale 정보도 함께 넘겨주기 때문에 Localization 적용도 쉽게 처리할 수 있다.

다음은 구현된 Formatter 예제 코드이다.

public final class DateFormatter implements Formtter<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;
    }
}

13.3.2.Default Formatter

Spring에서는 편의를 위해서 Formatter 역시 기본적인 Built-in Formatter를 제공하고 있다.

  • DateFormatter

    Spring은 기본적으로 java.text.DateFormat을 가지고 java.util.Date 객체의 formatting 처리를 하는 DateFormatter를 제공한다. (org.springframework.format.datetime 패키지) 또한 Spring에서는 강력한 Date/Time 관련 기능을 지원하는 Joda Time Library를 이용한 formatting도 제공한다.(org.springframework.format.datetime.joda 패키지) 클래스패스상에 Joda Time Library가 존재한다면 디폴트로 동작한다.

  • NumberFormatter

    Spring에서는 java.text.NumberFormat을 사용한 java.lang.Number 객체의 formatting처리를 위해서 NumberFormatter, CurrencyFormatter, PercentFormatter를 제공하고 있다.(org.springframework.format.number 패키지)

일반적으로는 위의 Formatter를 직접 사용하기 보다는 아래에서 살펴볼 Annotation 기반 Formatting 처리 방법, 특히 Spring에서 기본적으로 제공하는 Formatting 관련 Annotation 들을 주로 사용하게 될 것이다.

13.3.3.Annotation 기반 Formatting

다음 섹션에서 살펴보겠지만, 구현된 Formatter는 특정 타입의 변환 시에 사용되도록 등록할 수도 있지만, 특정 Annotation이 적용된 필드의 타입 변환 시에 사용되도록 등록할 수도 있다.

  • Implementation

    Formatting 관련 Annotation을 정의하고 그 Annotation이 적용된 필드의 타입 변환에는 연결되어 있는 특정 Formatter가 사용되도록 하려면 필드에 사용할 Annotation과 AnnotationFormatterFacotry 구현체를 만들어야 한다.

    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);
    }
    'A'에는 연결할 Annotation을 명시하고, getFieldTypes()은 해당 Annotation을 적용할 수 있는 필드 타입을 리턴하도록 구현하고, getPrinter()/getParser()는 각각 사용될 Printer와 Parser를 리턴하도록 구현한다.

    실제로 Spring에서 제공하고 있는 @NumberFormat의 경우 Annotation과 AnnotationFormatterFacotry가 어떻게 구현되어 있는지 살펴보자.

    다음은 @NumberFormat Annotation 구현 코드이다.

    @Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface NumberFormat {
    
        Style style() default Style.NUMBER;
    
        String pattern() default "";
    
        public enum Style {
            NUMBER,
            CURRENCY,
            PERCENT
        }
    }

    그리고 다음 코드는 @NumberFormat이 적용된 필드에 어떤 Formatter가 사용되어야 하는지 연결한 AnnotationFormatterFacotry 구현체이다.

    public final class NumberFormatAnnotationFormatterFactory implements AnnotationFormatterFactory<NumberFormat> {
    
        public Set<Class<?>> getFieldTypes() {
            return new HashSet<Class<?>>(asList(new Class<?>[] { 
                Short.class, Integer.class, Long.class, Float.class, Double.class, BigDecimal.class, BigInteger.class }));
        }
        
        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 NumberFormatter(annotation.pattern());
            } else {
                Style style = annotation.style();
                if (style == Style.PERCENT) {
                    return new PercentFormatter();
                } else if (style == Style.CURRENCY) {
                    return new CurrencyFormatter();
                } else {
                    return new NumberFormatter();
                }
            }
        }
    }

    이렇게 구현한 Formatter가 실제 Run-time 타입 변환 시에 사용되려면 반드시 등록과정을 거쳐야 한다. Formatter 등록에 대해서는 다음 섹션에서 자세히 알아보도록 하자.

  • Default annotations

    Spring에서 제공하는 Format 관련 Annotation은 아래와 같이 2가지가 있다.

    • @DateTimeFormat : java.util.Date, java.util.Calendar, java.util.Long, Joda Time 타입(LocalDate, LocalTime, LocalDateTime, DateTime)의 필드 formatting에 사용 가능

      public class Movie {
          // 중략
          @DateTimeFormat(pattern="yyyy-MM-dd")
          private Date releaseDate;
      }
      위와 같이 필드에 @DateTimeFormat을 적용하기만 하면 @DateTimeFormat에 연결된 Formatter에 의해서 Formatting이 처리된다.

      사용 가능한 속성은 다음과 같다.

      NameDescription
      style

      'S'-Short, 'M'-Medium, 'L'-Long, 'F'-Full 4가지 문자를 날짜에 한글자, 시간에 한글자를 사용해서 두 개의 문자로 만들어 지정. 날짜나 시간을 생략하고자 하는 경우 '-'를 사용 (예: 'S-'). 디폴트 값은 'SS'. Locale 정보를 기반으로 적절한 표현 형식을 적용해 줌

      isoISO 표준을 사용하고자 하는 경우, @DateTimeFormat(iso=ISO.DATE)와 같이 지정. ISO.DATE, ISO.DATE_TIME, ISO.TIME, ISO.NONE 사용가능, Locale 정보를 기반으로 적절한 표현 형식을 적용해 줌
      patternLocale과 상관없이 임의의 패턴을 사용하고자 하는 경우, ‘yyyy/mm/dd h:mm:ss a’등의 패턴을 지정

    • @NumberFormat : java.lang.Number 타입의 필드 formatting에 사용 가능

      public class Movie {
          // 중략
          @NumberFormat(pattern = "#,##0")
          private int ticketPrice;
      }
      위와 같이 필드에 @NumberFormat을 적용하기만 하면 @NumberFormat에 연결된 Formatter에 의해서 Formatting이 처리된다. java.lang.Number 하위의 클래스인 Byte, Double, Float, Integer, Long, Short, BigInteger, BigDecimal 변환에도 사용할 수 있다.

      사용 가능한 속성은 다음과 같다.

      NameDescription
      style

      NUMBER, CURRENCY, PERCENT 중 선택 가능. Locale 정보를 기반으로 적절한 표현 형식을 적용해 줌

      patternLocale과 상관없이 임의의 패턴을 사용하고자 하는 경우, ‘#,##0’등의 패턴을 지정

13.3.4.Register Formatter

Converter 영역에서, 등록된 Converter들을 가지고 실제 Run-time시에 타입 변환을 처리하는 역할을 담당하는 것이 GenericConversionService라면, Formatter에서 GenericConversionService와 같은 역할을 담당하는 것은 FormattingConversionService이다. FormattingConversionService는 GenericConversionService를 상속받고 있다.

위에서 살펴본 과정을 통해서 구현한 Formatter를 등록하는 방법은 Converter 등록과는 달리 불편하다. 설정으로 등록할 수 있는 방법은 아직 제공하고 있지 않고, FormattingConversionService를 초기화해주는 FormattingConversionServiceFactoryBean을 상속받은 클래스를 만들어서, installFormatters() 메소드를 오버라이드하여 Custom Formatter를 추가해야한다.

public class CustomFormattingConversionServiceFactoryBean extends FormattingConversionServiceFactoryBean {

    @Override
    protected void installFormatters(FormatterRegistry registry) {
        super.installFormatters(registry);
        
        // 필드 타입과 Formatter를 연결하여 등록하는 경우
        registry.addFormatterForFieldType(FilmRatings.class, new FilmRatingsFormatter());
        
        // Annotation과 Formatter를 연결하여 등록하는 경우
        registry.addFormatterForFieldAnnotation(new FilmRatingsFormatAnnotationFormatterFactory());
    }
}
위 코드에서 FormatterRegistry가 Formatter 등록과 관련된 메소드를 제공하는 것을 확인할 수 있다.

이렇게 확장한 FormattingConversionServiceFactoryBean를 아래와 같이 Bean으로 등록하고, Converter에서처럼 ConfigurableWebBindingInitializer를 이용하여 컨트롤러에서 사용할 수 있도록 설정할 수도 있고,

<bean id="conversionService" class="org.anyframe.sample.format.CustomFormattingConversionServiceFactoryBean" />
아래와 같이 mvc 네임스페이스의 <mvc:annotation-driven>를 이용하면 간편하게 설정할 수도 있다.
<mvc:annotation-driven conversion-service="conversionService" />
    
<bean id="conversionService" class="org.anyframe.sample.format.CustomFormattingConversionServiceFactoryBean" />

<mvc:annotation-driven>만 설정해주면 기본적으로 제공하는 Built-in Converter와 Built-in Formatter, 그리고 Formatting관련 Annotation인 @DateTimeFormat, @NumberFormat을 사용할 수 있다.

PropertyEditor와 Spring 3 Converter 간의 실행 순서

타입변환이 필요한 경우 기본적으로 ConversionService가 등록되지 않으면 Spring은 PropertyEditor를 기반으로 타입 변환을 수행한다. ConversionService가 등록된 경우라고 하더라도 Custom PropertyEditor가 등록된 경우는 Custom PropertyEditor가 우선적으로 적용된다. Even when ConversionService has been registered, Custom PropertyEditor takes priority when Custom PropertyEditor is registered.

* 우선 순위

  1. Custom PropertyEditor

  2. Converter

  3. Default PropertyEditor

출처 - http://dev.anyframejava.org/docs/anyframe/plugin/essential/core/1.0.0/reference/html/ch13.html

:

WebMvcConfigurerAdapter

FRAMEWORK/SPRING 2017. 10. 19. 16:25

spring mvc의 xml과 java config를 비교해서 한번 살펴보겠다.

EnableWebMvc

@Configuration
@EnableWebMvc
public class WebConfig {
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <mvc:annotation-driven/>

</beans>

xml의 은 @EnableWebMvc과 같은 설정이다. 위의 설정은 해주는게 많다. messageConverter, formatting, validating 기타 등등 아주 여러가지를 해주는 어노테이션 및 xml 설정이다. 나머지는 인터넷에서 쉽게 찾을 수 있으니 참고만 하길 바란다.

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

}

우리는 WebMvcConfigurerAdapter을 상속받아서 커스터마이징을 할 수 있다.

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

  @Override
  public void addFormatters(FormatterRegistry registry) {
  }
}

Conversion and Formatting

<mvc:annotation-driven conversion-service="conversionService"/>

<bean id="conversionService"
      class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
    <property name="converters">
        <set>
            <bean class="org.example.MyConverter"/>
        </set>
    </property>
    <property name="formatters">
        <set>
            <bean class="org.example.MyFormatter"/>
            <bean class="org.example.MyAnnotationFormatterFactory"/>
        </set>
    </property>
    <property name="formatterRegistrars">
        <set>
            <bean class="org.example.MyFormatterRegistrar"/>
        </set>
    </property>
</bean>

위의 xml과 javaconfig는 똑같은 설정이다. 간단하게 addFormatters 메서드를 재정의하면 된다.

Validation

@Override
public Validator getValidator() {
  return null;
}
<mvc:annotation-driven validator="globalValidator"/>

마찬가지로 getValidator를 재정의하면 위의 xml과 같은 설정이 된다.

Interceptors

@Override
public void addInterceptors(InterceptorRegistry registry) {
  registry.addInterceptor(new LocaleInterceptor());
  registry.addInterceptor(new ThemeInterceptor()).addPathPatterns("/**").excludePathPatterns("/admin/**");
  registry.addInterceptor(new SecurityInterceptor()).addPathPatterns("/secure/*");
}
<mvc:interceptors>
    <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"/>
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <mvc:exclude-mapping path="/admin/**"/>
        <bean class="org.springframework.web.servlet.theme.ThemeChangeInterceptor"/>
    </mvc:interceptor>
    <mvc:interceptor>
        <mvc:mapping path="/secure/*"/>
        <bean class="org.example.SecurityInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

위의 설정은 인터셉터이다. addInterceptors를 재정의해서 등록하면 된다.

Content Negotiation

@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
  configurer.mediaType("json", MediaType.APPLICATION_JSON);
}
<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager"/>

<bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
    <property name="mediaTypes">
        <value>
            json=application/json
            xml=application/xml
        </value>
    </property>
</bean>

위의 xml설정은 java config의 configureContentNegotiation 재정의해서 등록하면 된다.

View Controllers

@Override
public void addViewControllers(ViewControllerRegistry registry) {
  registry.addViewController("/").setViewName("home");
}
<mvc:view-controller path="/" view-name="home"/>

static한 html을 바로 사용하기 좋은 설정이다. addViewControllers만 재정의해서 등록하면 된다.

Serving of Resources

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
  registry.addResourceHandler("/resources/**").addResourceLocations("/public-resources/");
}
<mvc:resources mapping="/resources/**" location="/public-resources/"/>

리소스들은 등록한다 addResourceHandlers만 재정의하면 된다.

<mvc:resources mapping="/resources/**" location="/public-resources/" cache-period="31556926"/>

캐쉬 설정도 가능하다.

Falling Back On the “Default” Servlet To Serve Resources

@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
  configurer.enable();
}
<mvc:default-servlet-handler/>

위는 같은 설정이다.

@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
  configurer.enable("myCustomDefaultServlet");
}
<mvc:default-servlet-handler default-servlet-name="myCustomDefaultServlet"/>

Path Matching

@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
  configurer
    .setUseSuffixPatternMatch(true)
    .setUseTrailingSlashMatch(false)
    .setUseRegisteredSuffixPatternMatch(true)
    .setPathMatcher(antPathMatcher())
    .setUrlPathHelper(urlPathHelper());
}

@Bean
public UrlPathHelper urlPathHelper() {
  //...
}

@Bean
public PathMatcher antPathMatcher() {
  //...
}
<mvc:annotation-driven>
    <mvc:path-matching
        suffix-pattern="true"
        trailing-slash="false"
        registered-suffixes-only="true"
        path-helper="pathHelper"
        path-matcher="pathMatcher"/>
</mvc:annotation-driven>

<bean id="pathHelper" class="org.example.app.MyPathHelper"/>
<bean id="pathMatcher" class="org.example.app.MyPathMatcher"/>

configurePathMatch만 재정의 하면 된다.

Message Converters

@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
  Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder()
    .indentOutput(true)
    .dateFormat(new SimpleDateFormat("yyyy-MM-dd"))
    .modulesToInstall(new ParameterNamesModule());
  converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
  converters.add(new MappingJackson2XmlHttpMessageConverter(builder.xml().build()));
}
<mvc:annotation-driven>
    <mvc:message-converters>
        <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
            <property name="objectMapper" ref="objectMapper"/>
        </bean>
        <bean class="org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter">
            <property name="objectMapper" ref="xmlMapper"/>
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>

<bean id="objectMapper" class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean"
      p:indentOutput="true"
      p:simpleDateFormat="yyyy-MM-dd"
      p:modulesToInstall="com.fasterxml.jackson.module.paramnames.ParameterNamesModule"/>

<bean id="xmlMapper" parent="objectMapper" p:createXmlMapper="true"/>

위의 설정도 같은 설정이다.

솔직히 거의다 아는 내용이지만 아예 모르거나 긴가민가 하는 부분도 몇개 있다. 다음에는 저 설정이 어떤 역할을 하는지 알아보자!

출처 : http://docs.spring.io/spring/docs/4.3.0.RC2/spring-framework-reference/htmlsingle/#mvc-config-

:

Spring support for @Controller given by <context:component-scan /> vs <mvc:annotation-driven>

FRAMEWORK/SPRING 2017. 10. 19. 10:52

Both elements serve an entirely different purpose.

<context:component-scan /> is, as the name implies, for component scanning. It by default scans for all beans with the @Component annotation (or "sub"annotations like @Controller@Serviceetc.). It will only register instances of those classes in the application context as beans. That is all.

<mvc:annotation-driven /> is for bootstrapping Spring MVC and it registers, amongst others, a RequestMappingHandlerMapping and RequestMappingHandlerAdapter. The first links requests to a certain method (the @RequestMapping annotation on methods in a @Controller annotated class). The last knows how to execute methods annotated with @RequestMaping.

Now <mvc:annotation-driven /> does nothing for scanning or detecting @Controllers if there are none in the application context then no request mappings are made. Now you have several ways of registering those beans in the application context and one of them is the aforementioned <context:component-scan />.

Basically a @Controller without <mvc:annotation-driven /> is, well, pretty useless as it does nothing but take up memory. It will not be bound to incoming requests, it just hangs around in the application context. It is just another bean like all other beans and nothing special is being done to it. (Recent, but deprecated, versions of Spring register the DefaultAnnotationHandlerMappingwhich processes the @Controller, this is however deprecated).


출처 - https://stackoverflow.com/questions/20551217/spring-support-for-controller-given-by-contextcomponent-scan-vs-mvcannot

'FRAMEWORK > SPRING' 카테고리의 다른 글

Convertor  (0) 2017.10.19
WebMvcConfigurerAdapter  (0) 2017.10.19
Difference between <context:annotation-config> vs <context:component-scan>  (0) 2017.10.19
스프링 시큐리티(SPRING SECURITY)  (0) 2016.10.05
파일 업로드  (0) 2015.01.07
:

Difference between <context:annotation-config> vs <context:component-scan>

FRAMEWORK/SPRING 2017. 10. 19. 10:47

<context:annotation-config> is used to activate annotations in beans already registered in the application context (no matter if they were defined with XML or by package scanning).

<context:component-scan> can also do what <context:annotation-config> does but <context:component-scan> also scans packages to find and register beans within the application context.

I'll use some examples to show the differences/similarities.

Lets start with a basic setup of three beans of type AB and C, with B and C being injected into A.

package com.xxx;
public class B {
  public B() {
    System.out.println("creating bean B: " + this);
  }
}

package com.xxx;
public class C {
  public C() {
    System.out.println("creating bean C: " + this);
  }
}

package com.yyy;
import com.xxx.B;
import com.xxx.C;
public class A { 
  private B bbb;
  private C ccc;
  public A() {
    System.out.println("creating bean A: " + this);
  }
  public void setBbb(B bbb) {
    System.out.println("setting A.bbb with " + bbb);
    this.bbb = bbb;
  }
  public void setCcc(C ccc) {
    System.out.println("setting A.ccc with " + ccc);
    this.ccc = ccc; 
  }
}

With the following XML configuration :

<bean id="bBean" class="com.xxx.B" />
<bean id="cBean" class="com.xxx.C" />
<bean id="aBean" class="com.yyy.A">
  <property name="bbb" ref="bBean" />
  <property name="ccc" ref="cBean" />
</bean>

Loading the context produces the following output:

creating bean B: com.xxx.B@c2ff5
creating bean C: com.xxx.C@1e8a1f6
creating bean A: com.yyy.A@1e152c5
setting A.bbb with com.xxx.B@c2ff5
setting A.ccc with com.xxx.C@1e8a1f6

OK, this is the expected output. But this is "old style" Spring. Now we have annotations so lets use those to simplify the XML.

First, lets autowire the bbb and ccc properties on bean A like so:

package com.yyy;
import org.springframework.beans.factory.annotation.Autowired;
import com.xxx.B;
import com.xxx.C;
public class A { 
  private B bbb;
  private C ccc;
  public A() {
    System.out.println("creating bean A: " + this);
  }
  @Autowired
  public void setBbb(B bbb) {
    System.out.println("setting A.bbb with " + bbb);
    this.bbb = bbb;
  }
  @Autowired
  public void setCcc(C ccc) {
    System.out.println("setting A.ccc with " + ccc);
    this.ccc = ccc;
  }
}

This allows me to remove the following rows from the XML:

<property name="bbb" ref="bBean" />
<property name="ccc" ref="cBean" />

My XML is now simplified to this:

<bean id="bBean" class="com.xxx.B" />
<bean id="cBean" class="com.xxx.C" />
<bean id="aBean" class="com.yyy.A" />

When I load the context I get the following output:

creating bean B: com.xxx.B@5e5a50
creating bean C: com.xxx.C@54a328
creating bean A: com.yyy.A@a3d4cf

OK, this is wrong! What happened? Why aren't my properties autowired?

Well, annotations are a nice feature but by themselves they do nothing whatsoever. They just annotate stuff. You need a processing tool to find the annotations and do something with them.

<context:annotation-config> to the rescue. This activates the actions for the annotations that it finds on the beans defined in the same application context where itself is defined.

If I change my XML to this:

<context:annotation-config />
<bean id="bBean" class="com.xxx.B" />
<bean id="cBean" class="com.xxx.C" />
<bean id="aBean" class="com.yyy.A" />

when I load the application context I get the proper result:

creating bean B: com.xxx.B@15663a2
creating bean C: com.xxx.C@cd5f8b
creating bean A: com.yyy.A@157aa53
setting A.bbb with com.xxx.B@15663a2
setting A.ccc with com.xxx.C@cd5f8b

OK, this is nice, but I've removed two rows from the XML and added one. That's not a very big difference. The idea with annotations is that it's supposed to remove the XML.

So let's remove the XML definitions and replace them all with annotations:

package com.xxx;
import org.springframework.stereotype.Component;
@Component
public class B {
  public B() {
    System.out.println("creating bean B: " + this);
  }
}

package com.xxx;
import org.springframework.stereotype.Component;
@Component
public class C {
  public C() {
    System.out.println("creating bean C: " + this);
  }
}

package com.yyy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.xxx.B;
import com.xxx.C;
@Component
public class A { 
  private B bbb;
  private C ccc;
  public A() {
    System.out.println("creating bean A: " + this);
  }
  @Autowired
  public void setBbb(B bbb) {
    System.out.println("setting A.bbb with " + bbb);
    this.bbb = bbb;
  }
  @Autowired
  public void setCcc(C ccc) {
    System.out.println("setting A.ccc with " + ccc);
    this.ccc = ccc;
  }
}

While in the XML we only keep this:

<context:annotation-config />

We load the context and the result is... Nothing. No beans are created, no beans are autowired. Nothing!

That's because, as I said in the first paragraph, the <context:annotation-config /> only works on beans registered within the application context. Because I removed the XML configuration for thethree beans there is no bean created and <context:annotation-config /> has no "targets" to work on.

But that won't be a problem for <context:component-scan> which can scan a package for "targets" to work on. Let's change the content of the XML config into the following entry:

<context:component-scan base-package="com.xxx" />

When I load the context I get the following output:

creating bean B: com.xxx.B@1be0f0a
creating bean C: com.xxx.C@80d1ff

Hmmmm... something is missing. Why?

If you look closelly at the classes, class A has package com.yyy but I've specified in the <context:component-scan> to use package com.xxx so this completely missed my A class and only picked up B and C which are on the com.xxx package.

To fix this, I add this other package also:

<context:component-scan base-package="com.xxx,com.yyy" />

and now we get the expected result:

creating bean B: com.xxx.B@cd5f8b
creating bean C: com.xxx.C@15ac3c9
creating bean A: com.yyy.A@ec4a87
setting A.bbb with com.xxx.B@cd5f8b
setting A.ccc with com.xxx.C@15ac3c9

And that's it! Now you don't have XML definitions anymore, you have annotations.

As a final example, keeping the annotated classes AB and C and adding the following to the XML, what will we get after loading the context?

<context:component-scan base-package="com.xxx" />
<bean id="aBean" class="com.yyy.A" />

We still get the correct result:

creating bean B: com.xxx.B@157aa53
creating bean C: com.xxx.C@ec4a87
creating bean A: com.yyy.A@1d64c37
setting A.bbb with com.xxx.B@157aa53
setting A.ccc with com.xxx.C@ec4a87

Even if the bean for class A isn't obtained by scanning, the processing tools are still applied by <context:component-scan> on all beans registered in the application context, even for A which was manually registered in the XML.

But what if we have the following XML, will we get duplicated beans because we've specified both <context:annotation-config /> and <context:component-scan>?

<context:annotation-config />
<context:component-scan base-package="com.xxx" />
<bean id="aBean" class="com.yyy.A" />

No, no duplications, We again get the expected result:

creating bean B: com.xxx.B@157aa53
creating bean C: com.xxx.C@ec4a87
creating bean A: com.yyy.A@1d64c37
setting A.bbb with com.xxx.B@157aa53
setting A.ccc with com.xxx.C@ec4a87

That's because both tags register the same processing tools (<context:annotation-config /> can be omitted if <context:component-scan> is specified) but Spring takes care of running them onlyonce.

Even if you register the processing tools yourself multiple times, Spring will still make sure they do their magic only once; this XML:

<context:annotation-config />
<context:component-scan base-package="com.xxx" />
<bean id="aBean" class="com.yyy.A" />
<bean id="bla" class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor" />
<bean id="bla1" class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor" />
<bean id="bla2" class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor" />
<bean id="bla3" class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor" />

will still generate the following result:

creating bean B: com.xxx.B@157aa53
creating bean C: com.xxx.C@ec4a87
creating bean A: com.yyy.A@25d2b2
setting A.bbb with com.xxx.B@157aa53
setting A.ccc with com.xxx.C@ec4a87

OK, that about raps it up.


출처 - https://stackoverflow.com/questions/7414794/difference-between-contextannotation-config-vs-contextcomponent-scan

: