1. Overview
In this quick tutorial, we'll talk about CGLIB proxying in Spring @Configuration classes. Mainly, we'll look at some practical examples, and then examine some development rules - like avoiding final in @Bean methods - for this mechanism to work.
2. Sample Application
Let's first look at our sample application.
Firstly we have PostRepository and its implementation, DefaultPostRepository:
public interface PostRepository {
void save();
}
public class DefaultPostRepository implements PostRepository {
@Override
public void save() {
System.out.println("Saving...");
}
}
Then we have the PostService class and its only implementation, DefaultPostService:
public interface PostService {
void save();
}
public class DefaultPostService implements PostService {
private final String name;
private final PostRepository postRepository;
@Autowired
public DefaultPostService(String name, PostRepository postRepository) {
this.name = name;
this.postRepository = postRepository;
}
@Override
public void save() {
// Do work
}
// Getters...
}
3. What is CGLIB Proxying?
In order to detail the proxying mechanism, we'll first create a @Configuration class to define our beans:
@Configuration
public class ApplicationConfiguration {
@Bean
public DefaultPostRepository postRepository() {
return new DefaultPostRepository();
}
@Bean
public DefaultPostService firstPostService() {
return new DefaultPostService("First", postRepository(), logService());
}
@Bean
public DefaultPostService secondPostService() {
return new DefaultPostService("Second", postRepository(), logService());
}
}
Here, we're defining three beans: postRepository, firstPostService, and secondPostService. Note that when creating a DefaultPostService instance, we're calling the postRepository() method. At first, it seems we would end up with three different PostRepository instances. But on the contrary, we have one PostRepository bean in the container:
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = ApplicationConfiguration.class)
public class ApplicationConfigurationTest {
@Autowired
private ApplicationContext context;
@Test
public void testThatThereIsSinglePostRepository() {
final PostRepository postRepository = context
.getBean("postRepository", PostRepository.class);
final DefaultPostService firstPostService = context
.getBean("firstPostService", DefaultPostService.class);
final DefaultPostService secondPostService = context
.getBean("secondPostService", DefaultPostService.class);
assertThat(firstPostService.getPostRepository()).isEqualTo(postRepository);
assertThat(secondPostService.getPostRepository()).isEqualTo(postRepository);
}
}
Method invocations in a Spring @Configuration class don't follow the regular Java semantics. When we call postRepository() three times, it doesn't create three new PostRepository instances. This is because Spring creates a CGLIB proxy around the @Configuration classes. The calls are intercepted and then Spring checks the container before creating a new bean for us.
So to reiterate, when we invoke postRepository() three times, only the first invocation creates a Spring bean. Other invocations only get the existing PostRepository bean from the container. This is indeed very handy from a developer's perspective.
4. Rules to Work with CGLIB Proxying
Now that we've seen what CGLIB proxying does, next we'll look at some rules related to its internals.
Firstly, CGLIB proxying works with inheritance. To have the inheritance working, we must follow Java inheritance rules:
- @Configuration class shouldn't be final
- @Bean methods shouldn't be final
- @Bean methods shouldn't be private
@Configuration
public /*final*/ class ApplicationConfiguration {
@Bean
/*private*/ public /*final*/ DefaultPostRepository postRepository() {
return new DefaultPostRepository();
}
}
Here, we've commented out the private and final modifiers.
For example, if we add the final modifier to the postRepository() method, Spring throws an exception:
org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: @Bean method 'postRepository' must not be private or final; change the method's modifiers to continue
4.1 Static @Bean Methods
Lastly, we'll look at static @Bean methods.
When we declare a @Bean method as static, Spring doesn't need to initialize its enclosing @Configuration class. This behavior is convenient for some classes like BeanPostProcessors. Since Java doesn't allow overriding static methods, CGLIB proxying doesn't work with static bean definitions.
Let's cover this with an example. We have the LogService class:
public class LogService {
public void log(){
System.out.println("Logging...");
}
}
In the @Configuration class, we'll define it as a static method:
@Bean
public static LogService logService(){
return new LogService();
}
@Bean
public DefaultPostService firstPostService() {
return new DefaultPostService("First", postRepository(), logService());
}
@Bean
public DefaultPostService secondPostService() {
return new DefaultPostService("Second", postRepository(), logService());
}
@Bean
public DefaultPostRepository postRepository() {
return new DefaultPostRepository();
}
Here, we're calling logService() when initializing the DefaultPostService beans - as we did with postRepository().
After this setup, Spring creates three instances of LogService:
@Test
public void testThatThereAreMultipleLogService() {
final LogService logService = context.getBean("logService", LogService.class);
final DefaultPostService firstPostService = context
.getBean("firstPostService", DefaultPostService.class);
final DefaultPostService secondPostService = context
.getBean("secondPostService", DefaultPostService.class);
assertThat(firstPostService.getLogService()).isNotEqualTo(logService);
assertThat(secondPostService.getLogService()).isNotEqualTo(logService);
assertThat(firstPostService.getLogService()).isNotEqualTo(secondPostService.getLogService());
}
5. Disable CGLIB Proxying
Since Spring Framework 5.2, we have the option to disable proxying bean methods. If we set the proxyBeanMethod attribute to false, Spring doesn't intercept the @Bean method calls:
@Configuration(proxyBeanMethods = false)
public class ApplicationConfiguration {
...
}
6. Summary
In this tutorial, we've examined how CGLIB proxying works with Spring @Configuration classes.
We also detailed the rules when defining our Spring beans to comply with the proxying mechanism.
As always, the source code for all examples is available on Github.