Skip to content

Commit dded51f

Browse files
committed
Merge branch '5.2.x'
2 parents ba65cef + 96da1ff commit dded51f

File tree

3 files changed

+210
-21
lines changed

3 files changed

+210
-21
lines changed

spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
import org.springframework.beans.factory.SmartFactoryBean;
3838
import org.springframework.core.OrderComparator;
3939
import org.springframework.core.ResolvableType;
40-
import org.springframework.core.annotation.AnnotationUtils;
40+
import org.springframework.core.annotation.AnnotatedElementUtils;
4141
import org.springframework.lang.Nullable;
4242
import org.springframework.util.Assert;
4343
import org.springframework.util.ObjectUtils;
@@ -450,7 +450,7 @@ public <A extends Annotation> A findAnnotationOnBean(String beanName, Class<A> a
450450
throws NoSuchBeanDefinitionException {
451451

452452
Class<?> beanType = getType(beanName);
453-
return (beanType != null ? AnnotationUtils.findAnnotation(beanType, annotationType) : null);
453+
return (beanType != null ? AnnotatedElementUtils.findMergedAnnotation(beanType, annotationType) : null);
454454
}
455455

456456
}

spring-beans/src/test/java/org/springframework/beans/factory/BeanFactoryUtilsTests.java

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.beans.factory;
1818

19+
import java.lang.annotation.Retention;
20+
import java.lang.annotation.RetentionPolicy;
1921
import java.util.Arrays;
2022
import java.util.List;
2123
import java.util.Map;
@@ -33,6 +35,7 @@
3335
import org.springframework.beans.testfixture.beans.TestBean;
3436
import org.springframework.beans.testfixture.beans.factory.DummyFactory;
3537
import org.springframework.cglib.proxy.NoOp;
38+
import org.springframework.core.annotation.AliasFor;
3639
import org.springframework.core.io.Resource;
3740
import org.springframework.util.ObjectUtils;
3841

@@ -324,6 +327,33 @@ public void testIntDependencies() {
324327
assertThat(Arrays.equals(new String[] { "buffer" }, deps)).isTrue();
325328
}
326329

330+
@Test
331+
public void findAnnotationOnBean() {
332+
this.listableBeanFactory.registerSingleton("controllerAdvice", new ControllerAdviceClass());
333+
this.listableBeanFactory.registerSingleton("restControllerAdvice", new RestControllerAdviceClass());
334+
testFindAnnotationOnBean(this.listableBeanFactory);
335+
}
336+
337+
@Test // gh-25520
338+
public void findAnnotationOnBeanWithStaticFactory() {
339+
StaticListableBeanFactory lbf = new StaticListableBeanFactory();
340+
lbf.addBean("controllerAdvice", new ControllerAdviceClass());
341+
lbf.addBean("restControllerAdvice", new RestControllerAdviceClass());
342+
testFindAnnotationOnBean(lbf);
343+
}
344+
345+
private void testFindAnnotationOnBean(ListableBeanFactory lbf) {
346+
assertControllerAdvice(lbf, "controllerAdvice");
347+
assertControllerAdvice(lbf, "restControllerAdvice");
348+
}
349+
350+
private void assertControllerAdvice(ListableBeanFactory lbf, String beanName) {
351+
ControllerAdvice controllerAdvice = lbf.findAnnotationOnBean(beanName, ControllerAdvice.class);
352+
assertThat(controllerAdvice).isNotNull();
353+
assertThat(controllerAdvice.value()).isEqualTo("com.example");
354+
assertThat(controllerAdvice.basePackage()).isEqualTo("com.example");
355+
}
356+
327357
@Test
328358
public void isSingletonAndIsPrototypeWithStaticFactory() {
329359
StaticListableBeanFactory lbf = new StaticListableBeanFactory();
@@ -393,6 +423,35 @@ public void isSingletonAndIsPrototypeWithStaticFactory() {
393423
}
394424

395425

426+
@Retention(RetentionPolicy.RUNTIME)
427+
@interface ControllerAdvice {
428+
429+
@AliasFor("basePackage")
430+
String value() default "";
431+
432+
@AliasFor("value")
433+
String basePackage() default "";
434+
}
435+
436+
@Retention(RetentionPolicy.RUNTIME)
437+
@ControllerAdvice
438+
@interface RestControllerAdvice {
439+
440+
@AliasFor(annotation = ControllerAdvice.class)
441+
String value() default "";
442+
443+
@AliasFor(annotation = ControllerAdvice.class)
444+
String basePackage() default "";
445+
}
446+
447+
@ControllerAdvice("com.example")
448+
static class ControllerAdviceClass {
449+
}
450+
451+
@RestControllerAdvice("com.example")
452+
static class RestControllerAdviceClass {
453+
}
454+
396455
static class TestBeanSmartFactoryBean implements SmartFactoryBean<TestBean> {
397456

398457
private final TestBean testBean = new TestBean("enigma", 42);
Lines changed: 149 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,56 +16,68 @@
1616

1717
package org.springframework.test.web.servlet.samples.standalone;
1818

19+
import org.junit.jupiter.api.Nested;
1920
import org.junit.jupiter.api.Test;
2021

22+
import org.springframework.core.Ordered;
23+
import org.springframework.core.annotation.Order;
24+
import org.springframework.http.MediaType;
2125
import org.springframework.stereotype.Controller;
2226
import org.springframework.web.bind.annotation.ControllerAdvice;
2327
import org.springframework.web.bind.annotation.ExceptionHandler;
2428
import org.springframework.web.bind.annotation.GetMapping;
2529
import org.springframework.web.bind.annotation.PathVariable;
30+
import org.springframework.web.bind.annotation.RestController;
31+
import org.springframework.web.bind.annotation.RestControllerAdvice;
2632

2733
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
2834
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.forwardedUrl;
35+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
2936
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
3037
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup;
3138

3239
/**
33-
* Exception handling via {@code @ExceptionHandler} method.
40+
* Exception handling via {@code @ExceptionHandler} methods.
3441
*
3542
* @author Rossen Stoyanchev
43+
* @author Sam Brannen
3644
*/
37-
public class ExceptionHandlerTests {
45+
class ExceptionHandlerTests {
3846

39-
@Test
40-
public void testExceptionHandlerMethod() throws Exception {
41-
standaloneSetup(new PersonController()).build()
42-
.perform(get("/person/Clyde"))
47+
@Nested
48+
class MvcTests {
49+
50+
@Test
51+
void localExceptionHandlerMethod() throws Exception {
52+
standaloneSetup(new PersonController()).build()
53+
.perform(get("/person/Clyde"))
4354
.andExpect(status().isOk())
4455
.andExpect(forwardedUrl("errorView"));
45-
}
56+
}
4657

47-
@Test
48-
public void testGlobalExceptionHandlerMethod() throws Exception {
49-
standaloneSetup(new PersonController()).setControllerAdvice(new GlobalExceptionHandler()).build()
58+
@Test
59+
void globalExceptionHandlerMethod() throws Exception {
60+
standaloneSetup(new PersonController()).setControllerAdvice(new GlobalExceptionHandler()).build()
5061
.perform(get("/person/Bonnie"))
5162
.andExpect(status().isOk())
5263
.andExpect(forwardedUrl("globalErrorView"));
53-
}
64+
}
5465

55-
@Test
56-
public void testGlobalExceptionHandlerMethodUsingClassArgument() throws Exception {
57-
standaloneSetup(PersonController.class).setControllerAdvice(GlobalExceptionHandler.class).build()
66+
@Test
67+
void globalExceptionHandlerMethodUsingClassArgument() throws Exception {
68+
standaloneSetup(PersonController.class).setControllerAdvice(GlobalExceptionHandler.class).build()
5869
.perform(get("/person/Bonnie"))
5970
.andExpect(status().isOk())
6071
.andExpect(forwardedUrl("globalErrorView"));
72+
}
6173
}
6274

6375

6476
@Controller
6577
private static class PersonController {
6678

6779
@GetMapping("/person/{name}")
68-
public String show(@PathVariable String name) {
80+
String show(@PathVariable String name) {
6981
if (name.equals("Clyde")) {
7082
throw new IllegalArgumentException("simulated exception");
7183
}
@@ -76,20 +88,138 @@ else if (name.equals("Bonnie")) {
7688
}
7789

7890
@ExceptionHandler
79-
public String handleException(IllegalArgumentException exception) {
91+
String handleException(IllegalArgumentException exception) {
8092
return "errorView";
8193
}
8294
}
8395

84-
8596
@ControllerAdvice
8697
private static class GlobalExceptionHandler {
8798

8899
@ExceptionHandler
89-
public String handleException(IllegalStateException exception) {
100+
String handleException(IllegalStateException exception) {
90101
return "globalErrorView";
91102
}
103+
}
104+
105+
106+
@Nested
107+
class RestTests {
108+
109+
@Test
110+
void noException() throws Exception {
111+
standaloneSetup(RestPersonController.class)
112+
.setControllerAdvice(RestGlobalExceptionHandler.class, RestPersonControllerExceptionHandler.class).build()
113+
.perform(get("/person/Yoda").accept(MediaType.APPLICATION_JSON))
114+
.andExpect(status().isOk())
115+
.andExpect(jsonPath("$.name").value("Yoda"));
116+
}
117+
118+
@Test
119+
void localExceptionHandlerMethod() throws Exception {
120+
standaloneSetup(RestPersonController.class)
121+
.setControllerAdvice(RestGlobalExceptionHandler.class, RestPersonControllerExceptionHandler.class).build()
122+
.perform(get("/person/Luke").accept(MediaType.APPLICATION_JSON))
123+
.andExpect(status().isOk())
124+
.andExpect(jsonPath("$.error").value("local - IllegalArgumentException"));
125+
}
126+
127+
@Test
128+
void globalExceptionHandlerMethod() throws Exception {
129+
standaloneSetup(RestPersonController.class)
130+
.setControllerAdvice(RestGlobalExceptionHandler.class).build()
131+
.perform(get("/person/Leia").accept(MediaType.APPLICATION_JSON))
132+
.andExpect(status().isOk())
133+
.andExpect(jsonPath("$.error").value("global - IllegalStateException"));
134+
}
135+
136+
@Test
137+
void globalRestPersonControllerExceptionHandlerTakesPrecedenceOverGlobalExceptionHandler() throws Exception {
138+
standaloneSetup(RestPersonController.class)
139+
.setControllerAdvice(RestGlobalExceptionHandler.class, RestPersonControllerExceptionHandler.class).build()
140+
.perform(get("/person/Leia").accept(MediaType.APPLICATION_JSON))
141+
.andExpect(status().isOk())
142+
.andExpect(jsonPath("$.error").value("globalPersonController - IllegalStateException"));
143+
}
144+
145+
@Test // gh-25520
146+
void noHandlerFound() throws Exception {
147+
standaloneSetup(RestPersonController.class)
148+
.setControllerAdvice(RestGlobalExceptionHandler.class, RestPersonControllerExceptionHandler.class)
149+
.addDispatcherServletCustomizer(dispatcherServlet -> dispatcherServlet.setThrowExceptionIfNoHandlerFound(true))
150+
.build()
151+
.perform(get("/bogus").accept(MediaType.APPLICATION_JSON))
152+
.andExpect(status().isOk())
153+
.andExpect(jsonPath("$.error").value("global - NoHandlerFoundException"));
154+
}
155+
}
156+
157+
158+
@RestController
159+
private static class RestPersonController {
160+
161+
@GetMapping("/person/{name}")
162+
Person get(@PathVariable String name) {
163+
switch (name) {
164+
case "Luke":
165+
throw new IllegalArgumentException();
166+
case "Leia":
167+
throw new IllegalStateException();
168+
default:
169+
return new Person("Yoda");
170+
}
171+
}
172+
173+
@ExceptionHandler
174+
Error handleException(IllegalArgumentException exception) {
175+
return new Error("local - " + exception.getClass().getSimpleName());
176+
}
177+
}
178+
179+
@RestControllerAdvice(assignableTypes = RestPersonController.class)
180+
@Order(Ordered.HIGHEST_PRECEDENCE)
181+
private static class RestPersonControllerExceptionHandler {
92182

183+
@ExceptionHandler
184+
Error handleException(Throwable exception) {
185+
return new Error("globalPersonController - " + exception.getClass().getSimpleName());
186+
}
187+
}
188+
189+
@RestControllerAdvice
190+
@Order(Ordered.LOWEST_PRECEDENCE)
191+
private static class RestGlobalExceptionHandler {
192+
193+
@ExceptionHandler
194+
Error handleException(Throwable exception) {
195+
return new Error( "global - " + exception.getClass().getSimpleName());
196+
}
197+
}
198+
199+
static class Person {
200+
201+
private final String name;
202+
203+
Person(String name) {
204+
this.name = name;
205+
}
206+
207+
public String getName() {
208+
return name;
209+
}
210+
}
211+
212+
static class Error {
213+
214+
private final String error;
215+
216+
Error(String error) {
217+
this.error = error;
218+
}
219+
220+
public String getError() {
221+
return error;
222+
}
93223
}
94224

95225
}

0 commit comments

Comments
 (0)