Skip to content

Instantly share code, notes, and snippets.

@dsyer
Last active September 25, 2019 04:09
Show Gist options
  • Save dsyer/ebeb25d5afbdd9242cd5 to your computer and use it in GitHub Desktop.
Save dsyer/ebeb25d5afbdd9242cd5 to your computer and use it in GitHub Desktop.
Infrastructure Advisors and Spring ApplicationContext lifecycle

Aggressive Bean Instantiation and "Unproxyable" Beans

This project is a minimal setup depending only on spring-context but mimicking closely the mess that you get into with @EnableTransactionManagement (spring-tx) or @EnableGlobalMethodSecurity (spring-security). These annotations lead to registration of an InfrastructureAdvisorAutoProxyCreator which is responsible for locating the Advisor bean that provides the enabled feature. The mess is caused by aggressive bean instantiation in that component which can cause cascades of unsafe bean instantation in the middle of the bean post-processing phase of the ApplicationContext lifecycle (which is very early and quite fragile).

The net result is bad in two ways.

  1. Some beans end up uninitialized, or un-postprocessed, which is a situation that Spring anticipates (since it logs the beans that are screwed up in a BeanPostProcessorChecker at INFO level, this is plain to see for most users), but the side effects can be subtle.

  2. Any bean which is accidentally instantiated in this phase and tries to publish an ApplicationEvent is in for a nasty shock: the ApplicationContext is not ready for them yet and will barf in an ugly and confusing way, saying that it is not yet "refreshed".

Spring does not (possibly can not) have an opinion on a bean-by-bean basis whether or not the early instantiation is a bad thing or is even intended (although it is questionable whether anyone should intend this to happen).

Example logs showing the problem is appearing (note the beans that are mentioned are user beans that have ended up "not eligible for auto-proxying"):

INFO: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@68812517: startup date [Thu Jun 19 12:30:38 BST 2014]; root of context hierarchy
Jun 19, 2014 12:42:35 PM org.springframework.context.support.PostProcessorRegistrationDelegate$BeanPostProcessorChecker postProcessAfterInitialization
INFO: Bean 'demo.DataSourceConfiguration' of type [class demo.DataSourceConfiguration$$EnhancerBySpringCGLIB$$e9a8673a] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
Jun 19, 2014 12:42:36 PM org.springframework.context.support.PostProcessorRegistrationDelegate$BeanPostProcessorChecker postProcessAfterInitialization
INFO: Bean 'dataSource' of type [class demo.DataSource] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
Jun 19, 2014 12:42:37 PM org.springframework.context.support.PostProcessorRegistrationDelegate$BeanPostProcessorChecker postProcessAfterInitialization
INFO: Bean 'application' of type [class demo.Application$$EnhancerBySpringCGLIB$$4a963819] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)

Current Situation

The InfrastructureAdvisorAutoProxyCreator is a BeanPostProcessor with high Order, so relatively low precedence, but still instantiated before any unordered postprocessors. As soon as it is instantiated it starts doing its job, which is to find beans of type Advisor which are marked as @Role(ROLE_INFRASTRUCTURE) (a flag on the BeanDefinition) and use them to create proxies for any other beans that pass through. Whether a bean needs to be proxied of not not can only be determined by instantiating the Advisors, which is why they have a special @Role - you wouldn't want all beans of any type instantiated this early.

Examples of such infrastructure Advisors are:

  • BeanFactoryTransactionAttributeSourceAdvisor defined in ProxyTransactionManagementConfiguration and switched on by the user adding @EnableTransactionManagement.

  • MethodSecurityMetadataSourceAdvisor defined in GlobalMethodSecurityConfiguration and switched on by the user adding @EnableGlobalMethodSecurity.

Problems can arise when one of the Advisor beans depends on a user-defined bean. Users have no interest in the niceties of this process and the authors of the existing Advisors have gone to some lengths to protect them from having to know about it. Unfortunately, with their current implementations, it is not hard for a user to put a big spanner in the works by inadvertently making an Advisor depend on a long string of beans, not all of which are truly infrastructural.

Transaction Management

In the case of @EnableTransactionManagement the Advisor only depends on two other infrastructure beans (a TransactionInterceptor and a TransactionAttributeSource defined in the same config). Often the worst that happens is that those two beans are ineligible for post processing by some of the other BeanPostProcessors (the ones that are instantiated after the InfrastructureAdvisorAutoProxyCreator). Here's the log from such an application starting up (this happens in any application that uses @EnableTransactionManagement):

2014-06-19 13:25:10.901  INFO 4343 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration' of type [class org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration$$EnhancerBySpringCGLIB$$6e7e3662] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2014-06-19 13:25:10.921  INFO 4343 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'transactionAttributeSource' of type [class org.springframework.transaction.annotation.AnnotationTransactionAttributeSource] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2014-06-19 13:25:10.930  INFO 4343 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'transactionInterceptor' of type [class org.springframework.transaction.interceptor.TransactionInterceptor] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2014-06-19 13:25:10.934  INFO 4343 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.transaction.config.internalTransactionAdvisor' of type [class org.springframework.transaction.interceptor.BeanFactoryTransactionAttributeSourceAdvisor] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)

Four beans are showing up (the @Configuration class that defines the others, plus the advisor and its two dependencies) and they are all truly infrastructural, so it's relatively harmless. In particular we note that the PlatformTransactionManager (needed at runtime by the Advisor) is not instantiated this early by default; instead it is looked up lazily later by name as needed.

Things can go wrong though, since the user can inject a PlatformTransactionManager by defining a bean of type TransactionManagementConfigurer. At this point that bean and all its downstream dependencies would be left in limbo. We believe this is relatively rare.

Global Method Security

Things are worse with the Security feature @EnableGlobalMethodSecurity because its Advisor doesn't defer the instantiation of its dependencies. It needs an AuthenticationManager at runtime, and users can put the whole kitchen sink into their AuthenticationManager (e.g. JPA), so practically the whole bean registry can be aggressively instantiated by the user inadvertently (and reasonably) using Spring to configure his authentication requirements.

To see how easy it is to blow up an application, here is a fully functional Spring Boot application that does not start:

@Configuration
@EnableAutoConfiguration
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class Application {

	@Configuration
	@Order(Ordered.HIGHEST_PRECEDENCE + 10)
	protected static class ApplicationSecurity extends GlobalAuthenticationConfigurerAdapter {

		@Autowired
		private DataSource dataSource;

		@Autowired
		private SecurityProperties security;

		@Override
		public void init(AuthenticationManagerBuilder auth) throws Exception {
			User user = security.getUser();
			// @formatter:off
			auth.jdbcAuthentication().dataSource(dataSource)
				.withUser(user.getName())
				.password(user.getPassword())
				.roles(user.getRole().toArray(new String[0]));
			// @formatter:on
		}
	}

	public static void main(String[] args) {
		SpringApplication.run(Application.class, args);
	}
}

together with a schema.sql to initialize the database tables for user data:

create table users (
  username varchar(256),
  password varchar(256),
  enabled boolean
);

create table authorities (
  username varchar(256),
  authority varchar(256)
);

If this code is executed and the app is a web application (Spring MVC and Tomcat on the classpath) it will blow up on startup because the security Advisor triggers the AuthenticationManager to be built, which in turn triggers the DataSource. Normally the DataSource would have been initialized with the schema SQL shown by a BeanPostProcessor, but this doesn't happen because of the early phase at which the beans are all instantiated. Bang!

Here's part of the log:

2014-06-19 13:54:01.723  INFO 4815 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.security.config.annotation.configuration.ObjectPostProcessorConfiguration' of type [class org.springframework.security.config.annotation.configuration.ObjectPostProcessorConfiguration$$EnhancerBySpringCGLIB$$c3bf6094] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2014-06-19 13:54:01.733  INFO 4815 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration' of type [class org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration$$EnhancerBySpringCGLIB$$f62461da] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2014-06-19 13:54:01.754  INFO 4815 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'transactionAttributeSource' of type [class org.springframework.transaction.annotation.AnnotationTransactionAttributeSource] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2014-06-19 13:54:01.763  INFO 4815 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'transactionInterceptor' of type [class org.springframework.transaction.interceptor.TransactionInterceptor] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2014-06-19 13:54:01.768  INFO 4815 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.transaction.config.internalTransactionAdvisor' of type [class org.springframework.transaction.interceptor.BeanFactoryTransactionAttributeSourceAdvisor] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2014-06-19 13:54:01.783  INFO 4815 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'objectPostProcessor' of type [class org.springframework.security.config.annotation.configuration.AutowireBeanFactoryObjectPostProcessor] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2014-06-19 13:54:01.785  INFO 4815 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler@7ba43f6d' of type [class org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2014-06-19 13:54:01.928  INFO 4815 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'spring.datasource.CONFIGURATION_PROPERTIES' of type [class org.springframework.boot.autoconfigure.jdbc.DataSourceProperties] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2014-06-19 13:54:01.930  INFO 4815 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration$NonEmbeddedConfiguration' of type [class org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration$NonEmbeddedConfiguration] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2014-06-19 13:54:02.268  INFO 4815 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'dataSource' of type [class org.apache.tomcat.jdbc.pool.DataSource] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2014-06-19 13:54:02.319  INFO 4815 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration' of type [class org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration$$EnhancerBySpringCGLIB$$974a5d3a] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)

Spring Boot can take steps to mitigate the problems which ensue after this section of logging (already implemented in 1.1.2), but it can't solve the fundamental problem that all those beans are not eligible for post processing.

What Should Happen

Authors of @Role(ROLE_INFRASTRUCTURE) Advisor beans should ensure that only the Pointcut part is instantiated early. The other part is Advice, which will be needed at runtime, and should be instantiated lazily.

@apogrebnyak
Copy link

In retrospect, BeanPostProcessorChecker should produce a WARNING, not INFO message.

I think, in many cases where it is ignored it is hiding a time bomb waiting to strike at the most inopportune moment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment