Skip to content

Commit cfbac20

Browse files
committed
Ensure ErrorControllers work when using AOP
Add a BeanFactoryPostProcessor to set PRESERVE_TARGET_CLASS_ATTRIBUTE to true on all ErrorController bean definitions. Without this attribute AOP advice on @controllers causes ErrorController beans to be created as JDK proxies (since they implement a single valid looking interface) and therefore not get found by Spring MVC. Fixes gh-4236
1 parent ee93307 commit cfbac20

File tree

2 files changed

+73
-2
lines changed

2 files changed

+73
-2
lines changed

spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ErrorMvcAutoConfiguration.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,12 @@
2424
import javax.servlet.http.HttpServletRequest;
2525
import javax.servlet.http.HttpServletResponse;
2626

27+
import org.springframework.aop.framework.autoproxy.AutoProxyUtils;
28+
import org.springframework.beans.BeansException;
2729
import org.springframework.beans.factory.annotation.Autowired;
2830
import org.springframework.beans.factory.annotation.Value;
31+
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
32+
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
2933
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
3034
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
3135
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
@@ -102,6 +106,11 @@ public void customize(ConfigurableEmbeddedServletContainer container) {
102106
new ErrorPage(this.properties.getServletPrefix() + this.errorPath));
103107
}
104108

109+
@Bean
110+
public static PreserveErrorControllerTargetClassPostProcessor preserveErrorControllerTargetClassPostProcessor() {
111+
return new PreserveErrorControllerTargetClassPostProcessor();
112+
}
113+
105114
@Configuration
106115
@ConditionalOnProperty(prefix = "error.whitelabel", name = "enabled", matchIfMissing = true)
107116
@Conditional(ErrorTemplateMissingCondition.class)
@@ -225,4 +234,29 @@ public String resolvePlaceholder(String name) {
225234

226235
}
227236

237+
/**
238+
* {@link BeanFactoryPostProcessor} to ensure that the target class of ErrorController
239+
* MVC beans are preserved when using AOP.
240+
*/
241+
static class PreserveErrorControllerTargetClassPostProcessor
242+
implements BeanFactoryPostProcessor {
243+
244+
@Override
245+
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
246+
throws BeansException {
247+
String[] errorControllerBeans = beanFactory
248+
.getBeanNamesForType(ErrorController.class, false, false);
249+
for (String errorControllerBean : errorControllerBeans) {
250+
try {
251+
beanFactory.getBeanDefinition(errorControllerBean).setAttribute(
252+
AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
253+
}
254+
catch (Throwable ex) {
255+
// Ignore
256+
}
257+
}
258+
}
259+
260+
}
261+
228262
}

spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/BasicErrorControllerDirectMockMvcTests.java

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@
2424

2525
import javax.servlet.ServletException;
2626

27+
import org.aspectj.lang.ProceedingJoinPoint;
28+
import org.aspectj.lang.annotation.Around;
29+
import org.aspectj.lang.annotation.Aspect;
30+
import org.aspectj.lang.annotation.Pointcut;
2731
import org.junit.After;
2832
import org.junit.Rule;
2933
import org.junit.Test;
@@ -34,6 +38,7 @@
3438
import org.springframework.boot.builder.SpringApplicationBuilder;
3539
import org.springframework.boot.test.ApplicationContextTestUtils;
3640
import org.springframework.context.annotation.Configuration;
41+
import org.springframework.context.annotation.EnableAspectJAutoProxy;
3742
import org.springframework.context.annotation.Import;
3843
import org.springframework.http.MediaType;
3944
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@@ -101,11 +106,21 @@ public void errorPageNotAvailableWithWhitelabelDisabled() throws Exception {
101106
setup((ConfigurableWebApplicationContext) new SpringApplication(
102107
WebMvcIncludedConfiguration.class).run("--server.port=0",
103108
"--error.whitelabel.enabled=false"));
104-
105-
thrown.expect(ServletException.class);
109+
this.thrown.expect(ServletException.class);
106110
this.mockMvc.perform(get("/error").accept(MediaType.TEXT_HTML));
107111
}
108112

113+
@Test
114+
public void errorControllerWithAop() throws Exception {
115+
setup((ConfigurableWebApplicationContext) new SpringApplication(
116+
WithAopConfiguration.class).run("--server.port=0"));
117+
MvcResult response = this.mockMvc
118+
.perform(get("/error").accept(MediaType.TEXT_HTML))
119+
.andExpect(status().isOk()).andReturn();
120+
String content = response.getResponse().getContentAsString();
121+
assertTrue("Wrong content: " + content, content.contains("status=999"));
122+
}
123+
109124
@Target(ElementType.TYPE)
110125
@Retention(RetentionPolicy.RUNTIME)
111126
@Documented
@@ -115,6 +130,7 @@ public void errorPageNotAvailableWithWhitelabelDisabled() throws Exception {
115130
HttpMessageConvertersAutoConfiguration.class, ErrorMvcAutoConfiguration.class,
116131
PropertyPlaceholderAutoConfiguration.class })
117132
protected static @interface MinimalWebConfiguration {
133+
118134
}
119135

120136
@Configuration
@@ -127,6 +143,7 @@ protected static class ParentConfiguration {
127143
@MinimalWebConfiguration
128144
@EnableWebMvc
129145
protected static class WebMvcIncludedConfiguration {
146+
130147
// For manual testing
131148
public static void main(String[] args) {
132149
SpringApplication.run(WebMvcIncludedConfiguration.class, args);
@@ -137,6 +154,7 @@ public static void main(String[] args) {
137154
@Configuration
138155
@MinimalWebConfiguration
139156
protected static class VanillaConfiguration {
157+
140158
// For manual testing
141159
public static void main(String[] args) {
142160
SpringApplication.run(VanillaConfiguration.class, args);
@@ -147,11 +165,30 @@ public static void main(String[] args) {
147165
@Configuration
148166
@MinimalWebConfiguration
149167
protected static class ChildConfiguration {
168+
150169
// For manual testing
151170
public static void main(String[] args) {
152171
new SpringApplicationBuilder(ParentConfiguration.class)
153172
.child(ChildConfiguration.class).run(args);
154173
}
174+
175+
}
176+
177+
@Configuration
178+
@EnableAspectJAutoProxy(proxyTargetClass = false)
179+
@MinimalWebConfiguration
180+
@Aspect
181+
protected static class WithAopConfiguration {
182+
183+
@Pointcut("within(@org.springframework.stereotype.Controller *)")
184+
private void controllerPointCut() {
185+
};
186+
187+
@Around("controllerPointCut()")
188+
public Object mvcAdvice(ProceedingJoinPoint pjp) throws Throwable {
189+
return pjp.proceed();
190+
}
191+
155192
}
156193

157194
}

0 commit comments

Comments
 (0)