diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ErrorMvcAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ErrorMvcAutoConfiguration.java index e917276499a..062a55a8259 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ErrorMvcAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ErrorMvcAutoConfiguration.java @@ -24,8 +24,12 @@ import javax.servlet.Servlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.springframework.aop.framework.autoproxy.AutoProxyUtils; +import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionOutcome; @@ -102,6 +106,11 @@ public class ErrorMvcAutoConfiguration new ErrorPage(this.properties.getServletPrefix() + this.errorPath)); } + @Bean + public static PreserveErrorControllerTargetClassPostProcessor preserveErrorControllerTargetClassPostProcessor() { + return new PreserveErrorControllerTargetClassPostProcessor(); + } + @Configuration @ConditionalOnProperty(prefix = "error.whitelabel", name = "enabled", matchIfMissing = true) @Conditional(ErrorTemplateMissingCondition.class) @@ -225,4 +234,29 @@ public class ErrorMvcAutoConfiguration } + /** + * {@link BeanFactoryPostProcessor} to ensure that the target class of ErrorController + * MVC beans are preserved when using AOP. + */ + static class PreserveErrorControllerTargetClassPostProcessor + implements BeanFactoryPostProcessor { + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) + throws BeansException { + String[] errorControllerBeans = beanFactory + .getBeanNamesForType(ErrorController.class, false, false); + for (String errorControllerBean : errorControllerBeans) { + try { + beanFactory.getBeanDefinition(errorControllerBean).setAttribute( + AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE); + } + catch (Throwable ex) { + // Ignore + } + } + } + + } + } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/BasicErrorControllerDirectMockMvcTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/BasicErrorControllerDirectMockMvcTests.java index 264f4b4e7b0..a4fd5323ea7 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/BasicErrorControllerDirectMockMvcTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/BasicErrorControllerDirectMockMvcTests.java @@ -24,6 +24,10 @@ import java.lang.annotation.Target; import javax.servlet.ServletException; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; import org.junit.After; import org.junit.Rule; import org.junit.Test; @@ -34,6 +38,7 @@ import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfigurati import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.test.ApplicationContextTestUtils; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; import org.springframework.context.annotation.Import; import org.springframework.http.MediaType; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -101,11 +106,21 @@ public class BasicErrorControllerDirectMockMvcTests { setup((ConfigurableWebApplicationContext) new SpringApplication( WebMvcIncludedConfiguration.class).run("--server.port=0", "--error.whitelabel.enabled=false")); - - thrown.expect(ServletException.class); + this.thrown.expect(ServletException.class); this.mockMvc.perform(get("/error").accept(MediaType.TEXT_HTML)); } + @Test + public void errorControllerWithAop() throws Exception { + setup((ConfigurableWebApplicationContext) new SpringApplication( + WithAopConfiguration.class).run("--server.port=0")); + MvcResult response = this.mockMvc + .perform(get("/error").accept(MediaType.TEXT_HTML)) + .andExpect(status().isOk()).andReturn(); + String content = response.getResponse().getContentAsString(); + assertTrue("Wrong content: " + content, content.contains("status=999")); + } + @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @@ -115,6 +130,7 @@ public class BasicErrorControllerDirectMockMvcTests { HttpMessageConvertersAutoConfiguration.class, ErrorMvcAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class }) protected static @interface MinimalWebConfiguration { + } @Configuration @@ -127,6 +143,7 @@ public class BasicErrorControllerDirectMockMvcTests { @MinimalWebConfiguration @EnableWebMvc protected static class WebMvcIncludedConfiguration { + // For manual testing public static void main(String[] args) { SpringApplication.run(WebMvcIncludedConfiguration.class, args); @@ -137,6 +154,7 @@ public class BasicErrorControllerDirectMockMvcTests { @Configuration @MinimalWebConfiguration protected static class VanillaConfiguration { + // For manual testing public static void main(String[] args) { SpringApplication.run(VanillaConfiguration.class, args); @@ -147,11 +165,30 @@ public class BasicErrorControllerDirectMockMvcTests { @Configuration @MinimalWebConfiguration protected static class ChildConfiguration { + // For manual testing public static void main(String[] args) { new SpringApplicationBuilder(ParentConfiguration.class) .child(ChildConfiguration.class).run(args); } + + } + + @Configuration + @EnableAspectJAutoProxy(proxyTargetClass = false) + @MinimalWebConfiguration + @Aspect + protected static class WithAopConfiguration { + + @Pointcut("within(@org.springframework.stereotype.Controller *)") + private void controllerPointCut() { + }; + + @Around("controllerPointCut()") + public Object mvcAdvice(ProceedingJoinPoint pjp) throws Throwable { + return pjp.proceed(); + } + } }