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.
-
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. -
Any bean which is accidentally instantiated in this phase and tries to publish an
ApplicationEvent
is in for a nasty shock: theApplicationContext
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)
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 inProxyTransactionManagementConfiguration
and switched on by the user adding@EnableTransactionManagement
. -
MethodSecurityMetadataSourceAdvisor
defined inGlobalMethodSecurityConfiguration
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.
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.
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.
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.
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.