mirror of
https://github.com/spring-projects/spring-boot.git
synced 2024-09-03 04:26:12 +08:00
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
This commit is contained in:
parent
ee93307402
commit
cfbac20807
@ -24,8 +24,12 @@ import javax.servlet.Servlet;
|
|||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
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.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
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.AutoConfigureBefore;
|
||||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
|
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
|
||||||
@ -102,6 +106,11 @@ public class ErrorMvcAutoConfiguration
|
|||||||
new ErrorPage(this.properties.getServletPrefix() + this.errorPath));
|
new ErrorPage(this.properties.getServletPrefix() + this.errorPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public static PreserveErrorControllerTargetClassPostProcessor preserveErrorControllerTargetClassPostProcessor() {
|
||||||
|
return new PreserveErrorControllerTargetClassPostProcessor();
|
||||||
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@ConditionalOnProperty(prefix = "error.whitelabel", name = "enabled", matchIfMissing = true)
|
@ConditionalOnProperty(prefix = "error.whitelabel", name = "enabled", matchIfMissing = true)
|
||||||
@Conditional(ErrorTemplateMissingCondition.class)
|
@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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,10 @@ import java.lang.annotation.Target;
|
|||||||
|
|
||||||
import javax.servlet.ServletException;
|
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.After;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@ -34,6 +38,7 @@ import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfigurati
|
|||||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||||
import org.springframework.boot.test.ApplicationContextTestUtils;
|
import org.springframework.boot.test.ApplicationContextTestUtils;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.EnableAspectJAutoProxy;
|
||||||
import org.springframework.context.annotation.Import;
|
import org.springframework.context.annotation.Import;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||||
@ -101,11 +106,21 @@ public class BasicErrorControllerDirectMockMvcTests {
|
|||||||
setup((ConfigurableWebApplicationContext) new SpringApplication(
|
setup((ConfigurableWebApplicationContext) new SpringApplication(
|
||||||
WebMvcIncludedConfiguration.class).run("--server.port=0",
|
WebMvcIncludedConfiguration.class).run("--server.port=0",
|
||||||
"--error.whitelabel.enabled=false"));
|
"--error.whitelabel.enabled=false"));
|
||||||
|
this.thrown.expect(ServletException.class);
|
||||||
thrown.expect(ServletException.class);
|
|
||||||
this.mockMvc.perform(get("/error").accept(MediaType.TEXT_HTML));
|
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)
|
@Target(ElementType.TYPE)
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Documented
|
@Documented
|
||||||
@ -115,6 +130,7 @@ public class BasicErrorControllerDirectMockMvcTests {
|
|||||||
HttpMessageConvertersAutoConfiguration.class, ErrorMvcAutoConfiguration.class,
|
HttpMessageConvertersAutoConfiguration.class, ErrorMvcAutoConfiguration.class,
|
||||||
PropertyPlaceholderAutoConfiguration.class })
|
PropertyPlaceholderAutoConfiguration.class })
|
||||||
protected static @interface MinimalWebConfiguration {
|
protected static @interface MinimalWebConfiguration {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@ -127,6 +143,7 @@ public class BasicErrorControllerDirectMockMvcTests {
|
|||||||
@MinimalWebConfiguration
|
@MinimalWebConfiguration
|
||||||
@EnableWebMvc
|
@EnableWebMvc
|
||||||
protected static class WebMvcIncludedConfiguration {
|
protected static class WebMvcIncludedConfiguration {
|
||||||
|
|
||||||
// For manual testing
|
// For manual testing
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
SpringApplication.run(WebMvcIncludedConfiguration.class, args);
|
SpringApplication.run(WebMvcIncludedConfiguration.class, args);
|
||||||
@ -137,6 +154,7 @@ public class BasicErrorControllerDirectMockMvcTests {
|
|||||||
@Configuration
|
@Configuration
|
||||||
@MinimalWebConfiguration
|
@MinimalWebConfiguration
|
||||||
protected static class VanillaConfiguration {
|
protected static class VanillaConfiguration {
|
||||||
|
|
||||||
// For manual testing
|
// For manual testing
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
SpringApplication.run(VanillaConfiguration.class, args);
|
SpringApplication.run(VanillaConfiguration.class, args);
|
||||||
@ -147,11 +165,30 @@ public class BasicErrorControllerDirectMockMvcTests {
|
|||||||
@Configuration
|
@Configuration
|
||||||
@MinimalWebConfiguration
|
@MinimalWebConfiguration
|
||||||
protected static class ChildConfiguration {
|
protected static class ChildConfiguration {
|
||||||
|
|
||||||
// For manual testing
|
// For manual testing
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
new SpringApplicationBuilder(ParentConfiguration.class)
|
new SpringApplicationBuilder(ParentConfiguration.class)
|
||||||
.child(ChildConfiguration.class).run(args);
|
.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();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user