Skip to content

Commit 4fd27b1

Browse files
odrotbohmrstoyanchev
authored andcommitted
Introduced MvcUriComponentsBuilder to create URIs pointing to controller methods.
MvcUriComponentsBuilder allows creating URIs that point to Spring MVC controller methods annotated with @RequestMapping. It builds them by exposing a mock method invocation API similar to Mockito, records the method invocations and thus builds up the URI by inspecting the mapping annotations and the parameters handed into the method invocations. Introduced a new SPI UriComponentsContributor that should be implemented by HandlerMethodArgumentResolvers that actually contribute path segments or query parameters to a URI. While the newly introduced MvcUriComponentsBuilder looks up those UriComponentsContributor instances from the MVC configuration. The MvcUriComponentsBuilderFactory (name to be discussed - MvcUris maybe?) prevents the multiple lookups by keeping the UriComponentsBuilder instances in an instance variable. So an instance of the factory could be exposed as Spring bean or through a HandlerMethodArgumentResolver to be injected into Controller methods. Issue: SPR-10665, SPR-8826
1 parent 92a48b7 commit 4fd27b1

File tree

18 files changed

+2036
-1
lines changed

18 files changed

+2036
-1
lines changed

build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -640,6 +640,7 @@ project("spring-webmvc") {
640640

641641
dependencies {
642642
compile(project(":spring-core"))
643+
compile(files(project(":spring-core").objenesisRepackJar))
643644
compile(project(":spring-expression"))
644645
compile(project(":spring-beans"))
645646
compile(project(":spring-web"))
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/*
2+
* Copyright 2012-2013 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.core;
18+
19+
import java.lang.annotation.Annotation;
20+
import java.lang.reflect.AnnotatedElement;
21+
import java.lang.reflect.Method;
22+
23+
import org.springframework.core.annotation.AnnotationUtils;
24+
import org.springframework.util.Assert;
25+
26+
/**
27+
* Simply helper to reference a dedicated attribute of an {@link Annotation}.
28+
*
29+
* @author Oliver Gierke
30+
*/
31+
public class AnnotationAttribute {
32+
33+
private final Class<? extends Annotation> annotationType;
34+
35+
private final String attributeName;
36+
37+
/**
38+
* Creates a new {@link AnnotationAttribute} to the {@code value} attribute of the
39+
* given {@link Annotation} type.
40+
*
41+
* @param annotationType must not be {@literal null}.
42+
*/
43+
public AnnotationAttribute(Class<? extends Annotation> annotationType) {
44+
this(annotationType, null);
45+
}
46+
47+
/**
48+
* Creates a new {@link AnnotationAttribute} for the given {@link Annotation} type and
49+
* annotation attribute name.
50+
*
51+
* @param annotationType must not be {@literal null}.
52+
* @param attributeName can be {@literal null}, defaults to {@code value}.
53+
*/
54+
public AnnotationAttribute(Class<? extends Annotation> annotationType,
55+
String attributeName) {
56+
57+
Assert.notNull(annotationType);
58+
59+
this.annotationType = annotationType;
60+
this.attributeName = attributeName;
61+
}
62+
63+
/**
64+
* Returns the annotation type.
65+
*
66+
* @return the annotationType
67+
*/
68+
public Class<? extends Annotation> getAnnotationType() {
69+
return annotationType;
70+
}
71+
72+
/**
73+
* Reads the {@link Annotation} attribute's value from the given
74+
* {@link MethodParameter}.
75+
*
76+
* @param parameter must not be {@literal null}.
77+
* @return
78+
*/
79+
public Object getValueFrom(MethodParameter parameter) {
80+
81+
Assert.notNull(parameter, "MethodParameter must not be null!");
82+
Annotation annotation = parameter.getParameterAnnotation(annotationType);
83+
return annotation == null ? null : getValueFrom(annotation);
84+
}
85+
86+
/**
87+
* Reads the {@link Annotation} attribute's value from the given type.
88+
*
89+
* @param type must not be {@literal null}.
90+
* @return
91+
*/
92+
public Object findValueOn(Class<?> type) {
93+
94+
Assert.notNull(type, "Type must not be null!");
95+
Annotation annotation = AnnotationUtils.findAnnotation(type, annotationType);
96+
return annotation == null ? null : getValueFrom(annotation);
97+
}
98+
99+
public Object findValueOn(Method method) {
100+
101+
Assert.notNull(method, "Method must nor be null!");
102+
Annotation annotation = AnnotationUtils.findAnnotation(method, annotationType);
103+
return annotation == null ? null : getValueFrom(annotation);
104+
}
105+
106+
public Object getValueFrom(AnnotatedElement element) {
107+
108+
Assert.notNull(element, "Annotated element must not be null!");
109+
Annotation annotation = AnnotationUtils.getAnnotation(element, annotationType);
110+
return annotation == null ? null : getValueFrom(annotation);
111+
}
112+
113+
/**
114+
* Returns the {@link Annotation} attribute's value from the given {@link Annotation}.
115+
*
116+
* @param annotation must not be {@literal null}.
117+
* @return
118+
*/
119+
public Object getValueFrom(Annotation annotation) {
120+
121+
Assert.notNull(annotation, "Annotation must not be null!");
122+
return attributeName == null ? AnnotationUtils.getValue(annotation)
123+
: AnnotationUtils.getValue(annotation, attributeName);
124+
}
125+
}
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
/*
2+
* Copyright 2012-2013 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.core;
18+
19+
import java.lang.annotation.Annotation;
20+
import java.lang.reflect.Method;
21+
import java.util.ArrayList;
22+
import java.util.List;
23+
24+
import org.springframework.util.Assert;
25+
26+
/**
27+
* Value object to represent {@link MethodParameters} to allow to easily find the ones
28+
* with a given annotation.
29+
*
30+
* @author Oliver Gierke
31+
*/
32+
public class MethodParameters {
33+
34+
private static final ParameterNameDiscoverer DISCOVERER = new LocalVariableTableParameterNameDiscoverer();
35+
36+
private final List<MethodParameter> parameters;
37+
38+
/**
39+
* Creates a new {@link MethodParameters} from the given {@link Method}.
40+
*
41+
* @param method must not be {@literal null}.
42+
*/
43+
public MethodParameters(Method method) {
44+
this(method, null);
45+
}
46+
47+
/**
48+
* Creates a new {@link MethodParameters} for the given {@link Method} and
49+
* {@link AnnotationAttribute}. If the latter is given, method parameter names will be
50+
* looked up from the annotation attribute if present.
51+
*
52+
* @param method must not be {@literal null}.
53+
* @param namingAnnotation can be {@literal null}.
54+
*/
55+
public MethodParameters(Method method, AnnotationAttribute namingAnnotation) {
56+
57+
Assert.notNull(method);
58+
this.parameters = new ArrayList<MethodParameter>();
59+
60+
for (int i = 0; i < method.getParameterTypes().length; i++) {
61+
62+
MethodParameter parameter = new AnnotationNamingMethodParameter(method, i,
63+
namingAnnotation);
64+
parameter.initParameterNameDiscovery(DISCOVERER);
65+
parameters.add(parameter);
66+
}
67+
}
68+
69+
/**
70+
* Returns all {@link MethodParameter}s.
71+
*
72+
* @return
73+
*/
74+
public List<MethodParameter> getParameters() {
75+
return parameters;
76+
}
77+
78+
/**
79+
* Returns the {@link MethodParameter} with the given name or {@literal null} if none
80+
* found.
81+
*
82+
* @param name must not be {@literal null} or empty.
83+
* @return
84+
*/
85+
public MethodParameter getParameter(String name) {
86+
87+
Assert.hasText(name, "Parameter name must not be null!");
88+
89+
for (MethodParameter parameter : parameters) {
90+
if (name.equals(parameter.getParameterName())) {
91+
return parameter;
92+
}
93+
}
94+
95+
return null;
96+
}
97+
98+
/**
99+
* Returns all {@link MethodParameter}s annotated with the given annotation type.
100+
*
101+
* @param annotation must not be {@literal null}.
102+
* @return
103+
*/
104+
public List<MethodParameter> getParametersWith(Class<? extends Annotation> annotation) {
105+
106+
Assert.notNull(annotation);
107+
List<MethodParameter> result = new ArrayList<MethodParameter>();
108+
109+
for (MethodParameter parameter : getParameters()) {
110+
if (parameter.hasParameterAnnotation(annotation)) {
111+
result.add(parameter);
112+
}
113+
}
114+
115+
return result;
116+
}
117+
118+
/**
119+
* Custom {@link MethodParameter} extension that will favor the name configured in the
120+
* {@link AnnotationAttribute} if set over discovering it.
121+
*
122+
* @author Oliver Gierke
123+
*/
124+
private static class AnnotationNamingMethodParameter extends MethodParameter {
125+
126+
private final AnnotationAttribute attribute;
127+
128+
private String name;
129+
130+
/**
131+
* Creates a new {@link AnnotationNamingMethodParameter} for the given
132+
* {@link Method}'s parameter with the given index.
133+
*
134+
* @param method must not be {@literal null}.
135+
* @param parameterIndex
136+
* @param attribute can be {@literal null}
137+
*/
138+
public AnnotationNamingMethodParameter(Method method, int parameterIndex,
139+
AnnotationAttribute attribute) {
140+
141+
super(method, parameterIndex);
142+
this.attribute = attribute;
143+
144+
}
145+
146+
/*
147+
* (non-Javadoc)
148+
*
149+
* @see org.springframework.core.MethodParameter#getParameterName()
150+
*/
151+
@Override
152+
public String getParameterName() {
153+
154+
if (name != null) {
155+
return name;
156+
}
157+
158+
if (attribute != null) {
159+
Object foundName = attribute.getValueFrom(this);
160+
if (foundName != null) {
161+
name = foundName.toString();
162+
return name;
163+
}
164+
}
165+
166+
name = super.getParameterName();
167+
return name;
168+
}
169+
}
170+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright 2002-2013 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.core;
18+
19+
import java.lang.annotation.Retention;
20+
import java.lang.annotation.RetentionPolicy;
21+
22+
import org.junit.Test;
23+
24+
import static org.hamcrest.CoreMatchers.*;
25+
import static org.junit.Assert.*;
26+
27+
/**
28+
* Unit tests for {@link AnnotationAttribute}.
29+
*
30+
* @author Oliver Gierke
31+
*/
32+
public class AnnotationAttributeUnitTests {
33+
34+
AnnotationAttribute valueAttribute = new AnnotationAttribute(MyAnnotation.class);
35+
36+
AnnotationAttribute nonValueAttribute = new AnnotationAttribute(MyAnnotation.class,
37+
"nonValue");
38+
39+
@Test
40+
public void readsAttributesFromType() {
41+
42+
assertThat(valueAttribute.findValueOn(Sample.class), is((Object) "foo"));
43+
assertThat(nonValueAttribute.findValueOn(Sample.class), is((Object) "bar"));
44+
}
45+
46+
@Test
47+
public void findsAttributesFromSubType() {
48+
assertThat(valueAttribute.findValueOn(SampleSub.class), is((Object) "foo"));
49+
}
50+
51+
@Test
52+
public void doesNotGetValueFromSubTyp() {
53+
assertThat(valueAttribute.getValueFrom(SampleSub.class), is(nullValue()));
54+
}
55+
56+
@Retention(RetentionPolicy.RUNTIME)
57+
public static @interface MyAnnotation {
58+
59+
String value() default "";
60+
61+
String nonValue() default "";
62+
}
63+
64+
@MyAnnotation(value = "foo", nonValue = "bar")
65+
static class Sample {
66+
67+
}
68+
69+
static class SampleSub extends Sample {
70+
71+
}
72+
}

0 commit comments

Comments
 (0)