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.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
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue