diff --git a/org.springframework.context/src/main/java/org/springframework/format/FormatterRegistry.java b/org.springframework.context/src/main/java/org/springframework/format/FormatterRegistry.java index a31cfa17ebf..f5f15a41403 100644 --- a/org.springframework.context/src/main/java/org/springframework/format/FormatterRegistry.java +++ b/org.springframework.context/src/main/java/org/springframework/format/FormatterRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2011 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,7 +30,7 @@ import org.springframework.core.convert.converter.ConverterRegistry; public interface FormatterRegistry extends ConverterRegistry { /** - * Adds a Formatter to format fields of a specific type. + * Adds a Formatter to format fields of the given type. *

On print, if the Formatter's type T is declared and fieldType is not assignable to T, * a coersion to T will be attempted before delegating to formatter to print a field value. * On parse, if the parsed object returned by formatter is not assignable to the runtime field type, @@ -40,6 +40,14 @@ public interface FormatterRegistry extends ConverterRegistry { */ void addFormatterForFieldType(Class fieldType, Formatter formatter); + /** + * Adds a Formatter to format fields of a specific type. + * The field type is implied by the parameterized Formatter instance. + * @param formatter the formatter to add + * @see #addFormatterForFieldType(Class, Formatter) + */ + void addFormatter(Formatter formatter); + /** * Adds a Printer/Parser pair to format fields of a specific type. * The formatter will delegate to the specified printer for printing diff --git a/org.springframework.context/src/main/java/org/springframework/format/support/FormattingConversionService.java b/org.springframework.context/src/main/java/org/springframework/format/support/FormattingConversionService.java index 5749c249c11..279cc5b811a 100644 --- a/org.springframework.context/src/main/java/org/springframework/format/support/FormattingConversionService.java +++ b/org.springframework.context/src/main/java/org/springframework/format/support/FormattingConversionService.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2011 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -68,12 +68,21 @@ public class FormattingConversionService extends GenericConversionService addConverter(new ParserConverter(fieldType, formatter, this)); } + public void addFormatter(Formatter formatter) { + final Class fieldType = GenericTypeResolver.resolveTypeArgument(formatter.getClass(), Formatter.class); + if (fieldType == null) { + throw new IllegalArgumentException("Unable to extract parameterized field type argument from Formatter [" + + formatter.getClass().getName() + "]; does the formatter parameterize the generic type?"); + } + addFormatterForFieldType(fieldType, formatter); + } + public void addFormatterForFieldType(Class fieldType, Printer printer, Parser parser) { addConverter(new PrinterConverter(fieldType, printer, this)); addConverter(new ParserConverter(fieldType, parser, this)); } - @SuppressWarnings("unchecked") + @SuppressWarnings({ "unchecked", "rawtypes" }) public void addFormatterForFieldAnnotation(final AnnotationFormatterFactory annotationFormatterFactory) { final Class annotationType = (Class) GenericTypeResolver.resolveTypeArgument(annotationFormatterFactory.getClass(), AnnotationFormatterFactory.class); @@ -174,7 +183,7 @@ public class FormattingConversionService extends GenericConversionService private TypeDescriptor printerObjectType; - @SuppressWarnings("unchecked") + @SuppressWarnings("rawtypes") private Printer printer; private ConversionService conversionService; diff --git a/org.springframework.context/src/main/java/org/springframework/format/support/FormattingConversionServiceFactoryBean.java b/org.springframework.context/src/main/java/org/springframework/format/support/FormattingConversionServiceFactoryBean.java index 8c40588422f..713b39eec45 100644 --- a/org.springframework.context/src/main/java/org/springframework/format/support/FormattingConversionServiceFactoryBean.java +++ b/org.springframework.context/src/main/java/org/springframework/format/support/FormattingConversionServiceFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2011 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,6 +27,7 @@ import org.springframework.beans.factory.InitializingBean; import org.springframework.context.EmbeddedValueResolverAware; import org.springframework.core.convert.support.ConversionServiceFactory; import org.springframework.format.AnnotationFormatterFactory; +import org.springframework.format.Formatter; import org.springframework.format.FormatterRegistry; import org.springframework.format.Parser; import org.springframework.format.Printer; @@ -38,10 +39,8 @@ import org.springframework.util.StringValueResolver; /** * A factory for a {@link FormattingConversionService} that installs default - * formatters for common types such as numbers and datetimes. - * - *

Subclasses may override {@link #installFormatters(FormatterRegistry)} - * to register custom formatters. + * and custom converters and formatters for common types such as numbers + * and datetimes . * * @author Keith Donald * @author Juergen Hoeller @@ -55,21 +54,31 @@ public class FormattingConversionServiceFactoryBean private Set converters; + private Set formatters; + private StringValueResolver embeddedValueResolver; private FormattingConversionService conversionService; - /** - * Configure the set of custom converter objects that should be added: - * implementing {@link org.springframework.core.convert.converter.Converter}, - * {@link org.springframework.core.convert.converter.ConverterFactory}, - * or {@link org.springframework.core.convert.converter.GenericConverter}. + * Configure the set of custom converter objects that should be added. + * @param converters instances of + * {@link org.springframework.core.convert.converter.Converter}, + * {@link org.springframework.core.convert.converter.ConverterFactory} or + * {@link org.springframework.core.convert.converter.GenericConverter}. */ public void setConverters(Set converters) { this.converters = converters; } + /** + * Configure the set of custom formatter objects that should be added. + * @param formatters instances of {@link Formatter} or {@link AnnotationFormatterFactory}. + */ + public void setFormatters(Set formatters) { + this.formatters = formatters; + } + public void setEmbeddedValueResolver(StringValueResolver embeddedValueResolver) { this.embeddedValueResolver = embeddedValueResolver; } @@ -112,6 +121,18 @@ public class FormattingConversionServiceFactoryBean else { registry.addFormatterForFieldAnnotation(new NoJodaDateTimeFormatAnnotationFormatterFactory()); } + if (this.formatters != null) { + for (Object formatter : this.formatters) { + if (formatter instanceof Formatter) { + this.conversionService.addFormatter((Formatter) formatter); + } else if (formatter instanceof AnnotationFormatterFactory) { + this.conversionService.addFormatterForFieldAnnotation((AnnotationFormatterFactory) formatter); + } else { + throw new IllegalArgumentException( + "Custom formatters must be implementations of Formatter or AnnotationFormatterFactory"); + } + } + } } diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java index c5ec33f6cb0..fb972fc94eb 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java @@ -16,9 +16,8 @@ package org.springframework.web.servlet.config; -import org.w3c.dom.Element; - import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.parsing.BeanComponentDefinition; import org.springframework.beans.factory.parsing.CompositeComponentDefinition; @@ -39,6 +38,7 @@ import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConvert import org.springframework.http.converter.xml.SourceHttpMessageConverter; import org.springframework.http.converter.xml.XmlAwareFormHttpMessageConverter; import org.springframework.util.ClassUtils; +import org.springframework.util.xml.DomUtils; import org.springframework.validation.Validator; import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; import org.springframework.web.bind.support.ConfigurableWebBindingInitializer; @@ -49,6 +49,7 @@ import org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExc import org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping; import org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver; import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver; +import org.w3c.dom.Element; /** * {@link BeanDefinitionParser} that parses the {@code annotation-driven} element to configure a Spring MVC web @@ -75,6 +76,7 @@ import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolv * @author Keith Donald * @author Juergen Hoeller * @author Arjen Poutsma + * @author Rossen Stoyanchev * @since 3.0 */ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser { @@ -107,14 +109,16 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser { RuntimeBeanReference conversionService = getConversionService(element, source, parserContext); RuntimeBeanReference validator = getValidator(element, source, parserContext); + RuntimeBeanReference messageCodesResolver = getMessageCodesResolver(element, source, parserContext); RootBeanDefinition bindingDef = new RootBeanDefinition(ConfigurableWebBindingInitializer.class); bindingDef.setSource(source); bindingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); bindingDef.getPropertyValues().add("conversionService", conversionService); bindingDef.getPropertyValues().add("validator", validator); + bindingDef.getPropertyValues().add("messageCodesResolver", messageCodesResolver); - ManagedList messageConverters = getMessageConverters(source); + ManagedList messageConverters = getMessageConverters(element, source, parserContext); RootBeanDefinition annAdapterDef = new RootBeanDefinition(AnnotationMethodHandlerAdapter.class); annAdapterDef.setSource(source); @@ -166,10 +170,10 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser { return null; } - private RuntimeBeanReference getConversionService(Element element, Object source, ParserContext parserContext) { + RuntimeBeanReference conversionServiceRef; if (element.hasAttribute("conversion-service")) { - return new RuntimeBeanReference(element.getAttribute("conversion-service")); + conversionServiceRef = new RuntimeBeanReference(element.getAttribute("conversion-service")); } else { RootBeanDefinition conversionDef = new RootBeanDefinition(FormattingConversionServiceFactoryBean.class); @@ -177,8 +181,21 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser { conversionDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); String conversionName = parserContext.getReaderContext().registerWithGeneratedName(conversionDef); parserContext.registerComponent(new BeanComponentDefinition(conversionDef, conversionName)); - return new RuntimeBeanReference(conversionName); + conversionServiceRef = new RuntimeBeanReference(conversionName); } + Element formattersElement = DomUtils.getChildElementByTagName(element, "formatters"); + if (formattersElement != null) { + ManagedList formatters = new ManagedList(); + formatters.setSource(source); + for (Element formatter : DomUtils.getChildElementsByTagName(formattersElement, "bean")) { + BeanDefinitionHolder beanDef = parserContext.getDelegate().parseBeanDefinitionElement(formatter); + beanDef = parserContext.getDelegate().decorateBeanDefinitionIfRequired(formatter, beanDef); + formatters.add(beanDef); + } + BeanDefinition beanDef = parserContext.getRegistry().getBeanDefinition(conversionServiceRef.getBeanName()); + beanDef.getPropertyValues().add("formatters", formatters); + } + return conversionServiceRef; } private RuntimeBeanReference getValidator(Element element, Object source, ParserContext parserContext) { @@ -198,29 +215,51 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser { } } - private ManagedList getMessageConverters(Object source) { - ManagedList messageConverters = new ManagedList(); - messageConverters.setSource(source); - messageConverters.add(createConverterBeanDefinition(ByteArrayHttpMessageConverter.class, source)); + private RuntimeBeanReference getMessageCodesResolver(Element element, Object source, ParserContext parserContext) { + if (element.hasAttribute("message-codes-resolver")) { + return new RuntimeBeanReference(element.getAttribute("message-codes-resolver")); + } else { + return null; + } + } - RootBeanDefinition stringConverterDef = createConverterBeanDefinition(StringHttpMessageConverter.class, source); - stringConverterDef.getPropertyValues().add("writeAcceptCharset", false); - messageConverters.add(stringConverterDef); + private ManagedList getMessageConverters(Element element, Object source, ParserContext parserContext) { + Element convertersElement = DomUtils.getChildElementByTagName(element, "message-converters"); + if (convertersElement != null) { + ManagedList messageConverters = new ManagedList(); + messageConverters.setSource(source); + for (Element converter : DomUtils.getChildElementsByTagName(convertersElement, "bean")) { + BeanDefinitionHolder beanDef = parserContext.getDelegate().parseBeanDefinitionElement(converter); + beanDef = parserContext.getDelegate().decorateBeanDefinitionIfRequired(converter, beanDef); + messageConverters.add(beanDef); + } + return messageConverters; + } else { + ManagedList messageConverters = new ManagedList(); + messageConverters.setSource(source); + messageConverters.add(createConverterBeanDefinition(ByteArrayHttpMessageConverter.class, source)); - messageConverters.add(createConverterBeanDefinition(ResourceHttpMessageConverter.class, source)); - messageConverters.add(createConverterBeanDefinition(SourceHttpMessageConverter.class, source)); - messageConverters.add(createConverterBeanDefinition(XmlAwareFormHttpMessageConverter.class, source)); - if (jaxb2Present) { - messageConverters.add(createConverterBeanDefinition(Jaxb2RootElementHttpMessageConverter.class, source)); + RootBeanDefinition stringConverterDef = createConverterBeanDefinition(StringHttpMessageConverter.class, + source); + stringConverterDef.getPropertyValues().add("writeAcceptCharset", false); + messageConverters.add(stringConverterDef); + + messageConverters.add(createConverterBeanDefinition(ResourceHttpMessageConverter.class, source)); + messageConverters.add(createConverterBeanDefinition(SourceHttpMessageConverter.class, source)); + messageConverters.add(createConverterBeanDefinition(XmlAwareFormHttpMessageConverter.class, source)); + if (jaxb2Present) { + messageConverters + .add(createConverterBeanDefinition(Jaxb2RootElementHttpMessageConverter.class, source)); + } + if (jacksonPresent) { + messageConverters.add(createConverterBeanDefinition(MappingJacksonHttpMessageConverter.class, source)); + } + if (romePresent) { + messageConverters.add(createConverterBeanDefinition(AtomFeedHttpMessageConverter.class, source)); + messageConverters.add(createConverterBeanDefinition(RssChannelHttpMessageConverter.class, source)); + } + return messageConverters; } - if (jacksonPresent) { - messageConverters.add(createConverterBeanDefinition(MappingJacksonHttpMessageConverter.class, source)); - } - if (romePresent) { - messageConverters.add(createConverterBeanDefinition(AtomFeedHttpMessageConverter.class, source)); - messageConverters.add(createConverterBeanDefinition(RssChannelHttpMessageConverter.class, source)); - } - return messageConverters; } private RootBeanDefinition createConverterBeanDefinition(Class converterClass, diff --git a/org.springframework.web.servlet/src/main/resources/org/springframework/web/servlet/config/spring-mvc-3.1.xsd b/org.springframework.web.servlet/src/main/resources/org/springframework/web/servlet/config/spring-mvc-3.1.xsd index 02c6e7ebad7..c65528db118 100644 --- a/org.springframework.web.servlet/src/main/resources/org/springframework/web/servlet/config/spring-mvc-3.1.xsd +++ b/org.springframework.web.servlet/src/main/resources/org/springframework/web/servlet/config/spring-mvc-3.1.xsd @@ -1,6 +1,7 @@ @@ -16,6 +17,47 @@ ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -161,11 +217,11 @@ - - + - + ]]> + diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParserTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParserTests.java new file mode 100644 index 00000000000..dfc9accb5d5 --- /dev/null +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParserTests.java @@ -0,0 +1,225 @@ +/* + * Copyright 2002-2011 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.web.servlet.config; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.text.ParseException; +import java.util.HashSet; +import java.util.Locale; +import java.util.Set; + +import org.junit.BeforeClass; +import org.junit.Test; +import org.springframework.beans.DirectFieldAccessor; +import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; +import org.springframework.core.MethodParameter; +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.core.io.ClassPathResource; +import org.springframework.format.AnnotationFormatterFactory; +import org.springframework.format.Formatter; +import org.springframework.format.Parser; +import org.springframework.format.Printer; +import org.springframework.format.support.FormattingConversionService; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.ResourceHttpMessageConverter; +import org.springframework.http.converter.StringHttpMessageConverter; +import org.springframework.validation.MessageCodesResolver; +import org.springframework.web.bind.support.ConfigurableWebBindingInitializer; +import org.springframework.web.bind.support.WebArgumentResolver; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.context.support.GenericWebApplicationContext; +import org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter; +import org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver; + +/** + * Test fixture for the configuration in mvc-config-annotation-driven.xml. + * @author Rossen Stoyanchev + */ +public class AnnotationDrivenBeanDefinitionParserTests { + + private static GenericWebApplicationContext appContext; + + @BeforeClass + public static void setup() { + appContext = new GenericWebApplicationContext(); + XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(appContext); + reader.loadBeanDefinitions(new ClassPathResource("mvc-config-annotation-driven.xml", + AnnotationDrivenBeanDefinitionParserTests.class)); + appContext.refresh(); + } + + @Test + public void testMessageCodesResolver() { + AnnotationMethodHandlerAdapter adapter = appContext.getBean(AnnotationMethodHandlerAdapter.class); + assertNotNull(adapter); + Object initializer = new DirectFieldAccessor(adapter).getPropertyValue("webBindingInitializer"); + assertNotNull(initializer); + MessageCodesResolver resolver = ((ConfigurableWebBindingInitializer) initializer).getMessageCodesResolver(); + assertNotNull(resolver); + assertEquals(TestMessageCodesResolver.class, resolver.getClass()); + } + + @Test + public void testMessageConverters() { + verifyMessageConverters(appContext.getBean(AnnotationMethodHandlerAdapter.class)); + verifyMessageConverters(appContext.getBean(AnnotationMethodHandlerExceptionResolver.class)); + } + + @Test + public void testFormatters() throws Exception { + FormattingConversionService conversionService = appContext.getBean(FormattingConversionService.class); + assertNotNull(conversionService); + + TestBean testBean = conversionService.convert("5", TestBean.class); + assertEquals(TestBeanFormatter.class.getSimpleName() + " should have been used.", 5, testBean.getField()); + assertEquals("5", conversionService.convert(testBean, String.class)); + + TypeDescriptor intTypeDescriptor = new TypeDescriptor(TestBean.class.getDeclaredField("anotherField")); + Object actual = conversionService.convert(">>5<<", TypeDescriptor.valueOf(String.class), intTypeDescriptor); + assertEquals(TestBeanAnnotationFormatterFactory.class.getSimpleName() + " should have been used", 5, actual); + actual = conversionService.convert(5, intTypeDescriptor, TypeDescriptor.valueOf(String.class)); + assertEquals(">>5<<", actual); + } + + private void verifyMessageConverters(Object bean) { + assertNotNull(bean); + Object converters = new DirectFieldAccessor(bean).getPropertyValue("messageConverters"); + assertNotNull(converters); + assertTrue(converters instanceof HttpMessageConverter[]); + assertEquals(2, ((HttpMessageConverter[]) converters).length); + assertTrue(((HttpMessageConverter[]) converters)[0] instanceof StringHttpMessageConverter); + assertTrue(((HttpMessageConverter[]) converters)[1] instanceof ResourceHttpMessageConverter); + } + + private static class TestMessageCodesResolver implements MessageCodesResolver { + + public String[] resolveMessageCodes(String errorCode, String objectName) { + throw new IllegalStateException("Not expected to be invoked"); + } + + @SuppressWarnings("rawtypes") + public String[] resolveMessageCodes(String errorCode, String objectName, String field, Class fieldType) { + throw new IllegalStateException("Not expected to be invoked"); + } + + } + + @SuppressWarnings("unused") + private static class TestWebArgumentResolver implements WebArgumentResolver { + + public Object resolveArgument(MethodParameter methodParameter, NativeWebRequest webRequest) throws Exception { + throw new IllegalStateException("Not expected to be invoked"); + } + + } + + @SuppressWarnings("unused") + private static class AnotherTestWebArgumentResolver implements WebArgumentResolver { + + public Object resolveArgument(MethodParameter methodParameter, NativeWebRequest webRequest) throws Exception { + throw new IllegalStateException("Not expected to be invoked"); + } + + } + + private static class TestBeanFormatter implements Formatter { + + public String print(TestBean object, Locale locale) { + return String.valueOf(object.getField()); + } + + public TestBean parse(String text, Locale locale) throws ParseException { + TestBean object = new TestBean(); + object.setField(Integer.parseInt(text)); + return object; + } + + } + + private static class TestBeanAnnotationFormatterFactory implements AnnotationFormatterFactory { + + private final Set> fieldTypes = new HashSet>(1); + + @SuppressWarnings("unused") + public TestBeanAnnotationFormatterFactory() { + fieldTypes.add(Integer.class); + } + + public Set> getFieldTypes() { + return fieldTypes; + } + + public Printer getPrinter(SpecialIntFormat annotation, Class fieldType) { + return new Printer() { + public String print(Integer object, Locale locale) { + return ">>" + object.toString() + "<<"; + } + }; + } + + public Parser getParser(SpecialIntFormat annotation, Class fieldType) { + return new Parser() { + public Integer parse(String text, Locale locale) throws ParseException { + if (!text.startsWith(">>") || !text.endsWith("<<") || (text.length() < 5)) { + throw new ParseException(text + " is not in the expected format '>>intValue<<'", 0); + } + return Integer.parseInt(text.substring(2,3)); + } + }; + } + + } + + private static class TestBean { + + private int field; + + @SpecialIntFormat + private int anotherField; + + public int getField() { + return field; + } + + public void setField(int field) { + this.field = field; + } + + @SuppressWarnings("unused") + public int getAnotherField() { + return anotherField; + } + + @SuppressWarnings("unused") + public void setAnotherField(int anotherField) { + this.anotherField = anotherField; + } + + } + + @Target({ ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER }) + @Retention(RetentionPolicy.RUNTIME) + private @interface SpecialIntFormat { + } + +} diff --git a/org.springframework.web.servlet/src/test/resources/org/springframework/web/servlet/config/mvc-config-annotation-driven.xml b/org.springframework.web.servlet/src/test/resources/org/springframework/web/servlet/config/mvc-config-annotation-driven.xml new file mode 100644 index 00000000000..fc7e450b24c --- /dev/null +++ b/org.springframework.web.servlet/src/test/resources/org/springframework/web/servlet/config/mvc-config-annotation-driven.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + +