http://www.javashuo.com/article/p-uvudtich-bm.htmlhtml
在了解一个东西的原理以前,咱们得初步的懂得如何使用它。因此,本文先从@PostConstruct注解如何简单的使用开始。java
简单起见,咱们准备一个springboot项目快速启动。项目目录结构以下:spring
下面咱们在cn.lay.postconstruct目录下建立一个类,并添加一个@PostConstruct的方法,如缓存
最后,咱们执行PostConstructApplication的main方法,启动项目。在控制台里,咱们会看到springboot
到这里,咱们能够知道@PostConstruct注解的用途了。当一个class被注解为一个Bean,那么class上被@PostConstruct注解的方法将会在程序启动的时候执行。app
知道了如何使用@PostConstruct之后,咱们会产生疑问。为何@PostConstruct注解的方法会在程序启动的时候执行呢?后续的内容将为你解开疑惑。ide
在关注@PostConstruct原理以前,咱们不得不先回顾一下spring中一个Bean是如何被建立的,这将有助于咱们理清脉络。工具
配置Bean一般采用xml配置或者@Component、@Service、@Controller等注解配置,这是咱们很熟悉的。这意味着Bean的建立过程第一步是配置Beanpost
配置Bean -->
不管是xml配置,仍是注解配置,都会执行解析处理,处理后的结果会变成BeanDefinition这样的对象,存储在Bean容器里面。咱们能够把BeanDefinition理解为Bean的元数据。ui
因此,第二步就是将配置解析成Bean的元数据
配置Bean --> 解析为Bean的元数据 -->
到这里,还只是Bean元数据,并非咱们最熟悉的Bean。因此,第三步就会根据Bean的元数据来建立Bean了。
这里注意了,触发某个Bean的建立,就是从Bean容器中第一次获取Bean的时候,也就是BeanFactory的getBean()方法。而不是解析了Bean元数据后就立刻建立为Bean。
配置Bean --> 解析为Bean的元数据 --> 根据Bean的元数据生成Bean
这样,咱们就大致明白了一个Bean的建立过程。生成的Bean将会存放在Bean容器当中,或者咱们称呼其为Bean工厂。
前面,咱们了解了Bean的建立过程。而@PostConstruct方法将在最后生成Bean的时候被调用。getBean方法是一个Bean生成的入口,为此,咱们找到BeanFactory的getBean方法。
BeanFactory是一个抽象接口,它抽象了一个Bean工厂或者Bean容器。BeanDefinition和Bean实例都存放在BeanFactory中。
那么,咱们根据继承关系,向下找到AbstractAutowireCapableBeanFactory这个抽象类,该类间接实现了BeanFactory,并跟进其中一个getBean方法
再跟进doGetBean,doGetBean方法很长,咱们作一些删减。关注一下核心内容
protected <T> T doGetBean( final String name, @Nullable final Class<T> requiredType, @Nullable final Object[] args, boolean typeCheckOnly ) throws BeansException { final String beanName = transformedBeanName(name); Object bean; Object sharedInstance = getSingleton(beanName); if (sharedInstance != null && args == null) { // ... } else { try { // ... // 建立Bean实例 if (mbd.isSingleton()) { sharedInstance = getSingleton(beanName, () -> { try { return createBean(beanName, mbd, args); } catch (BeansException ex) { // ... } }); bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); } else if (mbd.isPrototype()) { // ... } else { // ... } } catch (BeansException ex) { cleanupAfterBeanCreationFailure(beanName); throw ex; } } // ... return (T) bean; }
这里以建立单例Bean为例,咱们注意到createBean方法将会建立一个Bean实例,因此createBean方法包含了建立一个Bean的核心逻辑。
再跟进createBean方法
protected Object createBean( String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException { // ... try { Object beanInstance = doCreateBean(beanName, mbdToUse, args); // ... return beanInstance; } catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) { // ... } catch (Throwable ex) { // ... } }
createBean委托给了doCreateBean处理
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException { BeanWrapper instanceWrapper = null; // ... // 建立Bean的实例对象 if (instanceWrapper == null) { instanceWrapper = createBeanInstance(beanName, mbd, args); } // ... // 初始化一个Bean Object exposedObject = bean; try { // 处理Bean的注入 populateBean(beanName, mbd, instanceWrapper); // 处理Bean的初始化操做 exposedObject = initializeBean(beanName, exposedObject, mbd); } catch (Throwable ex) { // ... } // ... return exposedObject; }
到这里,咱们能够知道。BeanFactory的getBean()方法将会去建立Bean,在doCreateBean方法的建立逻辑中主要包含了三个核心逻辑:
1)建立一个Bean的实例对象,createBeanInstance方法执行
2)处理Bean之间的依赖注入,好比@Autowired注解注入的Bean。因此,populateBean方法将会先去处理注入的Bean,所以对于相互注入的Bean来讲不用担忧Bean的生成前后顺序问题。
3)Bean实例生成,相互注入之后。还须要对Bean进行一些初始化操做。好比咱们@PostConstruct注解注释的方法,将再初始化的时候被解析并调用。固然还有一些Aware接口,@Schedule注解啥的也会作相应的处理。
咱们继续跟进初始化过程,进入initializeBean方法
protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) { // ... Object wrappedBean = bean; if (mbd == null || !mbd.isSynthetic()) { // 初始化前置处理 wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); } try { // 调用初始化方法 invokeInitMethods(beanName, wrappedBean, mbd); } catch (Throwable ex) { // ... } if (mbd == null || !mbd.isSynthetic()) { // 初始化后置处理 wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); } return wrappedBean; }
这里主要包含了初始化的前置、后置处理,之后初始化方法的调用。@PostConstruct注解将在applyBeanPostProcessorsBeforeInitialization这个前置处理
咱们跟进applyBeanPostProcessorsBeforeInitialization前置方法
@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; }
咱们注意到,这里遍历了在spring启动过程当中被注册的BeanPostProcessor接口,并调用其前置方法。
BeanPostProcessor接口被称做Bean的后置处理器接口,也就是说当一个Bean生成之后,会针对生成的Bean作一些处理。好比咱们注解了@PostConstruct注解的Bean将会被其中一个BeanPostProcessor处理。或者一些@Schedule之类注解的Bean也会被处理,等一些所谓的后置处理操做。
到这里呢,咱们意识到,原来@PostConstruct注解是会被一个专门的BeanPostProcessor接口的具体实现类来处理的。
咱们找到该实现类:InitDestroyAnnotationBeanPostProcessor,根据名字咱们就大致能够知道这个后置处理器是用于处理Bean的初始化方法注解和Bean的销毁方法注解的。这里,咱们只关注@PostConstruct初始化注解相关的
咱们跟进InitDestroyAnnotationBeanPostProcessor的postProcessBeanInitialization方法
@Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { // 元数据解析 LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass()); try { // 触发初始化方法 metadata.invokeInitMethods(bean, beanName); } catch (InvocationTargetException ex) { throw new BeanCreationException(beanName, "Invocation of init method failed", ex.getTargetException()); } catch (Throwable ex) { throw new BeanCreationException(beanName, "Failed to invoke init method", ex); } return bean; }
findLifecycleMetadata方法将会解析元数据,因此@PostConstruct注解的初始化方法也会在这里被找到。
invokeInitMethods方法将会触发上一步被找到的方法。
其实,到这里咱们大致均可以猜想这两个方法的逻辑了。就是经过反射将Method给找出来,而后再经过反射去调用这些method方法。
跟进findLifecycleMetadata方法看看初始化方法的查找过程吧
private LifecycleMetadata findLifecycleMetadata(Class<?> clazz) { // ... LifecycleMetadata metadata = this.lifecycleMetadataCache.get(clazz); if (metadata == null) { synchronized (this.lifecycleMetadataCache) { metadata = this.lifecycleMetadataCache.get(clazz); if (metadata == null) { // 构建元数据 metadata = buildLifecycleMetadata(clazz); this.lifecycleMetadataCache.put(clazz, metadata); } return metadata; } } return metadata; }
这里作了一个双重校验来控制缓存,咱们更关心的是buildLifecycleMetadata这个构建方法
跟进方法,简单起见这里删除了destroy相关的部分
private LifecycleMetadata buildLifecycleMetadata(final Class<?> clazz) { List<LifecycleElement> initMethods = new ArrayList<>(); Class<?> targetClass = clazz; do { final List<LifecycleElement> currInitMethods = new ArrayList<>();// 变量类中的方法Method对象 ReflectionUtils.doWithLocalMethods(targetClass, method -> { // 判断是否被@PostConstruct注解注释 if (this.initAnnotationType != null && method.isAnnotationPresent(this.initAnnotationType)) { LifecycleElement element = new LifecycleElement(method); currInitMethods.add(element); } // ... }); // 添加到集合中,后续调用 initMethods.addAll(0, currInitMethods); // ... targetClass = targetClass.getSuperclass(); } while (targetClass != null && targetClass != Object.class); return new LifecycleMetadata(clazz, initMethods, destroyMethods); }
能够看到,doWithLocalMethods这个工具方法将会从class中获取方法的反射对象。然后判断该方法是否被被initAnnotationType指定的注释注解。最后,添加到initMethods集合当中供后续反射调用。
这里还向父类进行了递归处理,直到Object类为止。
咱们看看initAnnotationType是一个什么注解
public CommonAnnotationBeanPostProcessor() { setOrder(Ordered.LOWEST_PRECEDENCE - 3); setInitAnnotationType(PostConstruct.class); setDestroyAnnotationType(PreDestroy.class); ignoreResourceType("javax.xml.ws.WebServiceContext"); }
能够看到,@PostConstruct注解被设置为了initAnnotationType的值。值得注意的是,这是在CommonAnnotationBeanPostProcessor这个后置处理器的构造方法中执行的。
而CommonAnnotationBeanPostProcessor和InitDestroyAnnotationBeanPostProcessor的关系是继承关系,前者继承了后者。咱们能够断点看看getBeanPostProcessors方法
到这里,咱们能够知道。跟咱们前面的猜想同样,解析过程是经过反射来获取@PostConstruct注解的方法,并放到一个List集合里面去。
下面,咱们再简单看看这些Method被调用的过程吧。
回到InitDestroyAnnotationBeanPostProcessor的postProcessBeforeInitialization方法
@Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass()); try { metadata.invokeInitMethods(bean, beanName); } catch (InvocationTargetException ex) { throw new BeanCreationException(beanName, "Invocation of init method failed", ex.getTargetException()); } catch (Throwable ex) { throw new BeanCreationException(beanName, "Failed to invoke init method", ex); } return bean; }
此次咱们关注invokeInitMethods方法
public void invokeInitMethods(Object target, String beanName) throws Throwable { Collection<LifecycleElement> checkedInitMethods = this.checkedInitMethods; Collection<LifecycleElement> initMethodsToIterate = (checkedInitMethods != null ? checkedInitMethods : this.initMethods); if (!initMethodsToIterate.isEmpty()) { for (LifecycleElement element : initMethodsToIterate) { if (logger.isTraceEnabled()) { logger.trace("Invoking init method on bean '" + beanName + "': " + element.getMethod()); } // 调用 element.invoke(target); } } }
跟进invoke,单纯地invoke了method,是咱们很熟悉的反射调用
public void invoke(Object target) throws Throwable { ReflectionUtils.makeAccessible(this.method); this.method.invoke(target, (Object[]) null); }
至此,本文就结束了。作一个简单的总结,本文内容包含三块:1)如何使用@PostConstruct;2)Bean建立过程简介;3)@PostConstruct的原理分析。
咱们提出了一个问题:为何@PostConstruct注解的方法会在启动的时候执行呢?
到这里你们应该可以知道答案了,spring的Bean在建立的时候会进行初始化,而初始化过程会解析出@PostConstruct注解的方法,并反射调用该方法。从而,在启动的时候该方法被执行了。
还有一个小点要注意,spring中的Bean默认是不会lazy-init的,因此在启动过程就会调用getBean方法。若是不但愿该Bean在启动过程就调用,那么将lazy-init设置为true,它就会在程序第一次使用的时候进行初始化。