preface
Sometimes, when you don't understand the way Spring manages Bean, you may encounter various problems, such as calling a method of Bean object in the container, such as:
@Service public class MyServiceImpl { @Resource private UserServiceImpl userService; public MyServiceImpl() { userService.method(); } }
Then the following exception will be thrown:
Constructor threw exception; nested exception is java.lang.NullPointerException
1, Case demonstration
Both Service classes are injected into the Bean object container of Spring through the @ Service annotation. We hope that the MyServiceImpl object can call the init method of UserServiceImpl during initialization, so we write it as follows.
@Service public class MyServiceImpl { @Resource private UserServiceImpl userService; public MyServiceImpl() { userService.init(); } } @Service public class UserServiceImpl { public void init() { System.out.println("UserServiceImpl method"); } }
Finally, you will find that null pointer exceptions will be thrown when the project starts.
2, Problem analysis
1. The main process of spring loading Bean objects
The reason why we make such a mistake is entirely because we don't understand the process of initializing Bean objects in Spring. Then, we might as well briefly analyze its key process first.
The process of Spring loading Bean objects can be roughly divided into three parts:
- Scan all Bean objects that need to be managed by Spring.
- Instantiate and initialize Bean objects.
- Confirm whether the proxy object needs to be generated for the Bean object.
It can be confirmed that steps 1 and 3 are not related to our problem this time, so we focus on the second step.
The key method of instantiating and initializing Bean objects is doCreateBean. It can be seen that the process is mainly divided into three steps: instantiation, attribute injection and initialization.
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] throws BeanCreationException { // ... Omit non critical code if (instanceWrapper == null) { // instantiation instanceWrapper = createBeanInstance(beanName, mbd, args); } // ... Omit non critical code Object exposedObject = bean; try { // Dependency injection of bean s populateBean(beanName, mbd, instanceWrapper); // Initialize bean object exposedObject = initializeBean(beanName, exposedObject, mbd); } // ... Omit non critical code }
Obviously, the problem with null pointers is that properties are not injected when the object is instantiated.
2. Problem solving
Now that the root cause of the problem has been found, the modification is very simple. There are many ways to realize our needs.
2.1. Construction method
The simplest way is to use constructor injection, because if it is constructor injection, Spring will instantiate the parameters in the constructor in advance, that is, the instantiation of UserServiceImpl is before MyServiceImpl.
@Service public class MyServiceImpl { //@Resource //private UserServiceImpl userService; public MyServiceImpl(UserServiceImpl userService) { userService.method(); } }
2.2. Use @ PostConstruct annotation
@Service public class MyServiceImpl { @Resource private UserServiceImpl userService; @PostConstruct public void init() { userService.method(); } }
This annotation is called when the Bean object is initialized
protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) { // ... Omit non critical code Object wrappedBean = bean; if (mbd == null || !mbd.isSynthetic()) { // Call with @ PostConstruct annotation wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); } try { invokeInitMethods(beanName, wrappedBean, mbd); } // ... Omit non critical code }
@Override public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName) throws BeansException { Object result = existingBean; for (BeanPostProcessor processor : getBeanPostProcessors()) { Object current = processor.postProcessBeforeInitialization(result, beanName); if (current == null) { return result; } result = current; } return result; }
Call the postProcessBeforeInitialization method of InitDestroyAnnotationBeanPostProcessor
@Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass()); try { metadata.invokeInitMethods(bean, beanName); } // ... Omit non critical code }
private LifecycleMetadata findLifecycleMetadata(Class<?> clazz) { if (this.lifecycleMetadataCache == null) { // Happens after deserialization, during destruction... return buildLifecycleMetadata(clazz); } // ... Omit non critical code }
Where initAnnotationType and destroyAnnotationType correspond to @ PostConstruct and @ PreDestroy annotations respectively
private LifecycleMetadata buildLifecycleMetadata(final Class<?> clazz) { if (!AnnotationUtils.isCandidateClass(clazz, Arrays.asList(this.initAnnotationType, this.destroyAnnotationType))) { return this.emptyLifecycleMetadata; } List<LifecycleElement> initMethods = new ArrayList<>(); List<LifecycleElement> destroyMethods = new ArrayList<>(); Class<?> targetClass = clazz; do { final List<LifecycleElement> currInitMethods = new ArrayList<>(); final List<LifecycleElement> currDestroyMethods = new ArrayList<>(); ReflectionUtils.doWithLocalMethods(targetClass, method -> { if (this.initAnnotationType != null && method.isAnnotationPresent(this.initAnnotationType)) { LifecycleElement element = new LifecycleElement(method); currInitMethods.add(element); if (logger.isTraceEnabled()) { logger.trace("Found init method on class [" + clazz.getName() + "]: " + method); } } if (this.destroyAnnotationType != null && method.isAnnotationPresent(this.destroyAnnotationType)) { currDestroyMethods.add(new LifecycleElement(method)); if (logger.isTraceEnabled()) { logger.trace("Found destroy method on class [" + clazz.getName() + "]: " + method); } } }); initMethods.addAll(0, currInitMethods); destroyMethods.addAll(currDestroyMethods); targetClass = targetClass.getSuperclass(); } while (targetClass != null && targetClass != Object.class); return (initMethods.isEmpty() && destroyMethods.isEmpty() ? this.emptyLifecycleMetadata : new LifecycleMetadata(clazz, initMethods, destroyMethods)); }
2.3. Implement InitializingBean interface
@Service public class MyServiceImpl implements InitializingBean { @Resource private UserServiceImpl userService; @Override public void afterPropertiesSet() { userService.method(); } }
In addition to calling the @ PostConstruct annotated method, the initializeBean method also completes the afterpropertieset method call of the InitializingBean interface
protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) { // ... Omit non critical code Object wrappedBean = bean; if (mbd == null || !mbd.isSynthetic()) { // Call with @ PostConstruct annotation wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); } try { // Call the afterpropertieset method that implements the InitializingBean interface invokeInitMethods(beanName, wrappedBean, mbd); } // ... Omit non critical code }
protected void invokeInitMethods(String beanName, final Object bean, @Nullable RootBeanDefinition mbd) throws Throwable { boolean isInitializingBean = (bean instanceof InitializingBean); if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) { if (logger.isTraceEnabled()) { logger.trace("Invoking afterPropertiesSet() on bean with name '" + beanName + "'"); } if (System.getSecurityManager() != null) { try { AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> { ((InitializingBean) bean).afterPropertiesSet(); return null; }, getAccessControlContext()); } catch (PrivilegedActionException pae) { throw pae.getException(); } } else { ((InitializingBean) bean).afterPropertiesSet(); } } // ... Omit non critical code
2.4. Implement ApplicationContextAware interface
@Service public class MyServiceImpl implements ApplicationContextAware { @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { UserServiceImpl userService = (UserServiceImpl) applicationContext.getBean("userServiceImpl"); userService.method(); } }
In fact, the setApplicationContext method in the ApplicationContextAware interface is also called when the Bean object is initialized. The call entry is still the method of postProcessBeforeInitialization, but it is in the ApplicationContextAwareProcessor.
@Override @Nullable public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if (!(bean instanceof EnvironmentAware || bean instanceof EmbeddedValueResolverAware || bean instanceof ResourceLoaderAware || bean instanceof ApplicationEventPublisherAware || bean instanceof MessageSourceAware || bean instanceof ApplicationContextAware)){ return bean; } AccessControlContext acc = null; if (System.getSecurityManager() != null) { acc = this.applicationContext.getBeanFactory().getAccessControlContext(); } if (acc != null) { AccessController.doPrivileged((PrivilegedAction<Object>) () -> { invokeAwareInterfaces(bean); return null; }, acc); } else { // Call the corresponding interface method invokeAwareInterfaces(bean); } return bean; }
private void invokeAwareInterfaces(Object bean) { if (bean instanceof EnvironmentAware) { ((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment()); } if (bean instanceof EmbeddedValueResolverAware) { ((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver); } if (bean instanceof ResourceLoaderAware) { ((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext); } if (bean instanceof ApplicationEventPublisherAware) { ((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext); } if (bean instanceof MessageSourceAware) { ((MessageSourceAware) bean).setMessageSource(this.applicationContext); } if (bean instanceof ApplicationContextAware) { ((ApplicationContextAware) bean).setApplicationContext(this.applicationContext); } }
summary
For the initial problem, we used four different ways to solve and realize the final business requirements. Obviously, these solutions can only be found after you analyze the source code. On the one hand, we have to admire the rich extension points left by Spring design, on the other hand, it also shows the importance of learning the source code.