diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/spr/ControllerAdviceIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/spr/ControllerAdviceIntegrationTests.java new file mode 100644 index 00000000000..982e5e13642 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/spr/ControllerAdviceIntegrationTests.java @@ -0,0 +1,135 @@ +/* + * Copyright 2002-2019 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 + * + * https://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.test.web.servlet.samples.spr; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.stereotype.Controller; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.annotation.RequestScope; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; + +import static org.junit.Assert.assertEquals; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.forwardedUrl; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup; + +/** + * Integration tests for {@link ControllerAdvice @ControllerAdvice}. + * + *

Introduced in conjunction with + * gh-24017. + * + * @author Sam Brannen + * @since 5.1.12 + */ +@RunWith(SpringRunner.class) +@WebAppConfiguration +public class ControllerAdviceIntegrationTests { + + @Autowired + WebApplicationContext wac; + + MockMvc mockMvc; + + @Before + public void setUpMockMvc() { + this.mockMvc = webAppContextSetup(wac).build(); + } + + @Test + public void controllerAdviceIsAppliedOnlyOnce() throws Exception { + assertEquals(0, SingletonControllerAdvice.counter.get()); + assertEquals(0, RequestScopedControllerAdvice.counter.get()); + + this.mockMvc.perform(get("/test"))// + .andExpect(status().isOk())// + .andExpect(forwardedUrl("singleton:1;request-scoped:1")); + + assertEquals(1, SingletonControllerAdvice.counter.get()); + assertEquals(1, RequestScopedControllerAdvice.counter.get()); + } + + @Configuration + @EnableWebMvc + static class Config { + + @Bean + TestController testController() { + return new TestController(); + } + + @Bean + SingletonControllerAdvice singletonControllerAdvice() { + return new SingletonControllerAdvice(); + } + + @Bean + @RequestScope + RequestScopedControllerAdvice requestScopedControllerAdvice() { + return new RequestScopedControllerAdvice(); + } + } + + @ControllerAdvice + static class SingletonControllerAdvice { + + static final AtomicInteger counter = new AtomicInteger(); + + @ModelAttribute + void initModel(Model model) { + model.addAttribute("singleton", counter.incrementAndGet()); + } + } + + @ControllerAdvice + static class RequestScopedControllerAdvice { + + static final AtomicInteger counter = new AtomicInteger(); + + @ModelAttribute + void initModel(Model model) { + model.addAttribute("request-scoped", counter.incrementAndGet()); + } + } + + @Controller + static class TestController { + + @GetMapping("/test") + String get(Model model) { + return "singleton:" + model.asMap().get("singleton") + ";request-scoped:" + + model.asMap().get("request-scoped"); + } + } + +} diff --git a/spring-web/src/main/java/org/springframework/web/method/ControllerAdviceBean.java b/spring-web/src/main/java/org/springframework/web/method/ControllerAdviceBean.java index af12b15d5be..ddf67af093b 100644 --- a/spring-web/src/main/java/org/springframework/web/method/ControllerAdviceBean.java +++ b/spring-web/src/main/java/org/springframework/web/method/ControllerAdviceBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -20,6 +20,7 @@ import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; +import org.springframework.aop.scope.ScopedProxyUtils; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.context.ApplicationContext; @@ -42,6 +43,7 @@ import org.springframework.web.bind.annotation.ControllerAdvice; * @author Rossen Stoyanchev * @author Brian Clozel * @author Juergen Hoeller + * @author Sam Brannen * @since 3.2 */ public class ControllerAdviceBean implements Ordered { @@ -187,6 +189,7 @@ public class ControllerAdviceBean implements Ordered { */ public static List findAnnotatedBeans(ApplicationContext context) { return Arrays.stream(BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context, Object.class)) + .filter(name -> !ScopedProxyUtils.isScopedTarget(name)) .filter(name -> context.findAnnotationOnBean(name, ControllerAdvice.class) != null) .map(name -> new ControllerAdviceBean(name, context)) .collect(Collectors.toList()); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/annotation/RequestScopedControllerAdviceIntegrationTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/annotation/RequestScopedControllerAdviceIntegrationTests.java index e75a84ec3a5..062ed71dde4 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/annotation/RequestScopedControllerAdviceIntegrationTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/annotation/RequestScopedControllerAdviceIntegrationTests.java @@ -48,17 +48,12 @@ public class RequestScopedControllerAdviceIntegrationTests { context.register(Config.class); context.refresh(); - // Until gh-24017 is fixed, we expect the RequestScopedControllerAdvice to show up twice. List adviceBeans = ControllerAdviceBean.findAnnotatedBeans(context); - assertEquals(2, adviceBeans.size()); + assertEquals(1, adviceBeans.size()); - ControllerAdviceBean adviceBean1 = adviceBeans.get(0); - assertEquals(RequestScopedControllerAdvice.class, adviceBean1.getBeanType()); - assertEquals(42, adviceBean1.getOrder()); - - ControllerAdviceBean adviceBean2 = adviceBeans.get(1); - assertEquals(RequestScopedControllerAdvice.class, adviceBean2.getBeanType()); - assertEquals(42, adviceBean2.getOrder()); + ControllerAdviceBean adviceBean = adviceBeans.get(0); + assertEquals(RequestScopedControllerAdvice.class, adviceBean.getBeanType()); + assertEquals(42, adviceBean.getOrder()); context.close(); }