Spring源碼剖析4:其余方式獲取Bean的過程分析

原型Bean加載過程

之前的文章喇完,分析了非懶加載的單例Bean整個加載過程滞时,除了非懶加載的單例Bean之外少欺,Spring中還有一種Bean就是原型(Prototype)的Bean蹂窖,看一下定義方式:

<pre>1 <?xml version="1.0" encoding="UTF-8"?>
2 <beans xmlns="http://www.springframework.org/schema/beans"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xsi:schemaLocation="http://www.springframework.org/schema/beans 5 http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
6
7 <bean id="prototypeBean" class="org.xrq.action.PrototypeBean" scope="prototype" />
8
9 </beans></pre>

原型Bean加載流程總得來說和單例Bean差不多智听,看一下不同之處根欧,在AbstractBeanFactory的doGetBean的方法的這一步:

<pre> 1 else if (mbd.isPrototype()) { 2 // It's a prototype -> create a new instance.
3 Object prototypeInstance = null;
4 try { 5 beforePrototypeCreation(beanName);
6 prototypeInstance = createBean(beanName, mbd, args); 7 }
8 finally { 9 afterPrototypeCreation(beanName); 10 } 11 bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd); 12 }</pre>

第6行createBean是一樣的怜珍,原型Bean實例化的主要區(qū)別就在于第6行,它是直接創(chuàng)建bean的凤粗,而單例bean我們再對比一下:

<pre> 1 if (mbd.isSingleton()) { 2 sharedInstance = getSingleton(beanName, new ObjectFactory() { 3 public Object getObject() throws BeansException { 4 try { 5 return createBean(beanName, mbd, args); 6 }
7 catch (BeansException ex) { 8 // Explicitly remove instance from singleton cache: It might have been put there 9 // eagerly by the creation process, to allow for circular reference resolution. 10 // Also remove any beans that received a temporary reference to the bean.
11 destroySingleton(beanName); 12 throw ex; 13 } 14 } 15 }); 16 bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); 17 }</pre>

它優(yōu)先會嘗試getSington酥泛,即先嘗試從singletonObjects中獲取一下bean是否存在,如果存在直接返回singletonObjects中的bean對象嫌拣。

接著柔袁,我們看到原型bean創(chuàng)建和單例bean創(chuàng)建的區(qū)別還在于第5行和第9行,先看第5行的代碼:

<pre> 1 protected void beforePrototypeCreation(String beanName) { 2 Object curVal = this.prototypesCurrentlyInCreation.get();
3 if (curVal == null) {
4 this.prototypesCurrentlyInCreation.set(beanName);
5 }
6 else if (curVal instanceof String) { 7 Set<String> beanNameSet = new HashSet<String>(2);
8 beanNameSet.add((String) curVal);
9 beanNameSet.add(beanName); 10 this.prototypesCurrentlyInCreation.set(beanNameSet); 11 } 12 else { 13 Set<String> beanNameSet = (Set<String>) curVal; 14 beanNameSet.add(beanName); 15 } 16 }</pre>

這段主要是說bean在創(chuàng)建前要把當前beanName設(shè)置到ThreadLocal中去异逐,其目的是保證多線程不會同時創(chuàng)建同一個bean捶索。接著看第9行的代碼實現(xiàn),即bean創(chuàng)建之后做了什么:

<pre> 1 protected void afterPrototypeCreation(String beanName) { 2 Object curVal = this.prototypesCurrentlyInCreation.get();
3 if (curVal instanceof String) { 4 this.prototypesCurrentlyInCreation.remove();
5 }
6 else if (curVal instanceof Set) { 7 Set<String> beanNameSet = (Set<String>) curVal;
8 beanNameSet.remove(beanName);
9 if (beanNameSet.isEmpty()) { 10 this.prototypesCurrentlyInCreation.remove(); 11 } 12 } 13 }</pre>

很好理解灰瞻,就是把當前bean移除一下腥例,這樣其它線程就可以創(chuàng)建bean了。第11行的代碼不看了酝润,意思是如果bean是FactoryBean的實現(xiàn)類的話燎竖,調(diào)用getObject()方法獲取真正的對象。

byName源碼實現(xiàn)

Spring有為開發(fā)者提供Autowire(自動裝配)的功能要销,自動裝配最常用的就是byName和byType這兩種屬性构回。由于自動裝配是為了解決對象注入導致的<property>過多的問題,因此很容易找到byName與byType的Spring源碼實現(xiàn)應該在屬性注入這一塊疏咐,定位到屬性注入的代碼AbstractAutowireCapableBeanFactory的populateBean方法纤掸,直接截取重點:

<pre> 1 if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME ||
2 mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) { 3 MutablePropertyValues newPvs = new MutablePropertyValues(pvs); 4
5 // Add property values based on autowire by name if applicable.
6 if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME) { 7 autowireByName(beanName, mbd, bw, newPvs);
8 }
9
10 // Add property values based on autowire by type if applicable.
11 if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) { 12 autowireByType(beanName, mbd, bw, newPvs); 13 } 14
15 pvs = newPvs; 16 }</pre>

看到第6行第8行判斷是否byName形式,是就執(zhí)行byName自動裝配代碼浑塞;第11行第13行判斷是否byType形式借跪,是就執(zhí)行byType自動裝配代碼。那么首先看一下第7行的byName代碼實現(xiàn):

<pre> 1 protected void autowireByName( 2 String beanName, AbstractBeanDefinition mbd, BeanWrapper bw, MutablePropertyValues pvs) {
3
4 String[] propertyNames = unsatisfiedNonSimpleProperties(mbd, bw); 5 for (String propertyName : propertyNames) { 6 if (containsBean(propertyName)) { 7 Object bean = getBean(propertyName); 8 pvs.add(propertyName, bean);
9 registerDependentBean(propertyName, beanName); 10 if (logger.isDebugEnabled()) { 11 logger.debug("Added autowiring by name from bean name '" + beanName +
12 "' via property '" + propertyName + "' to bean named '" + propertyName + "'"); 13 } 14 } 15 else { 16 if (logger.isTraceEnabled()) { 17 logger.trace("Not autowiring property '" + propertyName + "' of bean '" + beanName +
18 "' by name: no matching bean found"); 19 } 20 } 21 } 22 }</pre>

篇幅問題缩举,代碼不一層層跟了,邏輯梳理一下:

  • 第4行,找到Bean中不是簡單屬性的屬性仅孩,這句話有點繞托猩,意思就是找到屬性是對象類型的屬性,但也不是所有的對象類型都會被找到辽慕,比如CharSequence類型京腥、Number類型、Date類型溅蛉、URL類型公浪、URI類型、Locale類型船侧、Class類型就會忽略欠气,具體可見BeanUtils的isSimpleProperty方法
  • 第5行~第7行,遍歷所有被找到的屬性镜撩,如果bean定義中包含了屬性名预柒,那么先實例化該屬性名對應的bean
  • 第9行registerDependentBean,注冊一下當前bean的依賴bean袁梗,用于在某個bean被銷毀前先將其依賴的bean銷毀

其余代碼都是一些打日志的宜鸯,沒什么好說的。

byType源碼實現(xiàn)

上面說了byName的源碼實現(xiàn)遮怜,接下來看一下byType源碼實現(xiàn):

<pre> 1 protected void autowireByType( 2 String beanName, AbstractBeanDefinition mbd, BeanWrapper bw, MutablePropertyValues pvs) {
3
4 TypeConverter converter = getCustomTypeConverter(); 5 if (converter == null) {
6 converter = bw; 7 }
8
9 Set<String> autowiredBeanNames = new LinkedHashSet<String>(4); 10 String[] propertyNames = unsatisfiedNonSimpleProperties(mbd, bw); 11 for (String propertyName : propertyNames) { 12 try { 13 PropertyDescriptor pd = bw.getPropertyDescriptor(propertyName); 14 // Don't try autowiring by type for type Object: never makes sense, 15 // even if it technically is a unsatisfied, non-simple property.
16 if (!Object.class.equals(pd.getPropertyType())) { 17 MethodParameter methodParam = BeanUtils.getWriteMethodParameter(pd); 18 // Do not allow eager init for type matching in case of a prioritized post-processor.
19 boolean eager = !PriorityOrdered.class.isAssignableFrom(bw.getWrappedClass()); 20 DependencyDescriptor desc = new AutowireByTypeDependencyDescriptor(methodParam, eager); 21 Object autowiredArgument = resolveDependency(desc, beanName, autowiredBeanNames, converter); 22 if (autowiredArgument != null) { 23 pvs.add(propertyName, autowiredArgument); 24 } 25 for (String autowiredBeanName : autowiredBeanNames) { 26 registerDependentBean(autowiredBeanName, beanName); 27 if (logger.isDebugEnabled()) { 28 logger.debug("Autowiring by type from bean name '" + beanName + "' via property '" +
29 propertyName + "' to bean named '" + autowiredBeanName + "'"); 30 } 31 } 32 autowiredBeanNames.clear(); 33 } 34 } 35 catch (BeansException ex) { 36 throw new UnsatisfiedDependencyException(mbd.getResourceDescription(), beanName, propertyName, ex); 37 } 38 } 39 }</pre>

前面一樣淋袖,到第10行都是找到Bean中屬性是對象類型的屬性。

接著就是遍歷一下PropertyName锯梁,獲取PropertyName對應的屬性描述即碗,注意一下16行的判斷及其對應的注釋:不要嘗試自動裝配Object類型,這沒有任何意義涝桅,即使從技術(shù)角度看它是一個非簡單的對象屬性拜姿。

第18行~第20行跳過(沒有太明白是干什么的),byType實現(xiàn)的源碼主要在第21行的方法resolveDependency中冯遂,這個方法是AbstractAutowireCapableBeanFactory類的實現(xiàn)類DefaultListableBeanFactory中的方法:

<pre> 1 public Object resolveDependency(DependencyDescriptor descriptor, String beanName, 2 Set<String> autowiredBeanNames, TypeConverter typeConverter) throws BeansException { 3
4 descriptor.initParameterNameDiscovery(getParameterNameDiscoverer());
5 if (descriptor.getDependencyType().equals(ObjectFactory.class)) {
6 return new DependencyObjectFactory(descriptor, beanName); 7 }
8 else if (descriptor.getDependencyType().equals(javaxInjectProviderClass)) { 9 return new DependencyProviderFactory().createDependencyProvider(descriptor, beanName); 10 } 11 else { 12 return doResolveDependency(descriptor, descriptor.getDependencyType(), beanName, autowiredBeanNames, typeConverter); 13 } 14 }</pre>

這里判斷一下要自動裝配的屬性是ObjectFactory.class還是javaxInjectProviderClass還是其他的蕊肥,我們裝配的是其他的,看一下12行的代碼實現(xiàn):

<pre> 1 protected Object doResolveDependency(DependencyDescriptor descriptor, Class<?> type, String beanName, 2 Set<String> autowiredBeanNames, TypeConverter typeConverter) throws BeansException { 3
4 Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor); 5 if (value != null) {
6 if (value instanceof String) { 7 String strVal = resolveEmbeddedValue((String) value); 8 BeanDefinition bd = (beanName != null && containsBean(beanName) ? getMergedBeanDefinition(beanName) : null);
9 value = evaluateBeanDefinitionString(strVal, bd); 10 } 11 TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter()); 12 return converter.convertIfNecessary(value, type); 13 } 14
15 if (type.isArray()) { 16 ... 17 } 18 else if (Collection.class.isAssignableFrom(type) && type.isInterface()) { 19 ... 20 } 21 else if (Map.class.isAssignableFrom(type) && type.isInterface()) { 22 ... 23 } 24 else { 25 Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor); 26 if (matchingBeans.isEmpty()) { 27 if (descriptor.isRequired()) { 28 raiseNoSuchBeanDefinitionException(type, "", descriptor); 29 } 30 return null; 31 } 32 if (matchingBeans.size() > 1) { 33 String primaryBeanName = determinePrimaryCandidate(matchingBeans, descriptor); 34 if (primaryBeanName == null) { 35 throw new NoSuchBeanDefinitionException(type, "expected single matching bean but found " +
36 matchingBeans.size() + ": " + matchingBeans.keySet()); 37 } 38 if (autowiredBeanNames != null) { 39 autowiredBeanNames.add(primaryBeanName); 40 } 41 return matchingBeans.get(primaryBeanName); 42 } 43 // We have exactly one match.
44 Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next(); 45 if (autowiredBeanNames != null) { 46 autowiredBeanNames.add(entry.getKey()); 47 } 48 return entry.getValue(); 49 } 50 }</pre>

第四行結(jié)果是null不看了蛤肌,為了簡化代碼Array裝配壁却、Collection裝配、Map裝配的代碼都略去了裸准,重點看一下普通屬性的裝配展东。首先是第25行獲取一下自動裝配的候選者:

<pre> 1 protected Map<String, Object> findAutowireCandidates( 2 String beanName, Class requiredType, DependencyDescriptor descriptor) {
3
4 String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors( 5 this, requiredType, true, descriptor.isEager());
6 Map<String, Object> result = new LinkedHashMap<String, Object>(candidateNames.length);
7 for (Class autowiringType : this.resolvableDependencies.keySet()) {
8 if (autowiringType.isAssignableFrom(requiredType)) { 9 Object autowiringValue = this.resolvableDependencies.get(autowiringType); 10 autowiringValue = AutowireUtils.resolveAutowiringValue(autowiringValue, requiredType); 11 if (requiredType.isInstance(autowiringValue)) { 12 result.put(ObjectUtils.identityToString(autowiringValue), autowiringValue); 13 break; 14 } 15 } 16 } 17 for (String candidateName : candidateNames) { 18 if (!candidateName.equals(beanName) && isAutowireCandidate(candidateName, descriptor)) { 19 result.put(candidateName, getBean(candidateName)); 20 } 21 } 22 return result; 23 }</pre>

代碼邏輯整理一下:

  • 首先獲取候選者bean名稱,通過DefaultListableBeanFactory的getBeanNamesForType方法炒俱,即找一下所有的Bean定義中指定Type的實現(xiàn)類或者子類
  • 接著第7行~第16行的判斷要自動裝配的類型是不是要自動裝配的糾正類型盐肃,這個在【Spring源碼分析】非懶加載的單例Bean初始化前后的一些操作一文講PrepareBeanFactory方法的時候有講過爪膊,如果要自動裝配的類型是糾正類型,比如是一個ResourceLoader砸王,那么就會為該類型生成一個代理實例推盛,具體可以看一下第10行的AutowireUtils.resolveAutowiringValue方法的實現(xiàn)
  • 正常來說都是執(zhí)行的第17行~第21行的代碼,逐個判斷查找一下beanName對應的BeanDefinition谦铃,判斷一下是不是自動裝配候選者耘成,默認都是的,如果<bean>的autowire-candidate屬性設(shè)置為false就不是

這樣驹闰,拿到所有待裝配對象的實現(xiàn)類或者子類的候選者瘪菌,組成一個Map,Key為beanName嘹朗,Value為具體的Bean师妙。接著回看獲取Bean之后的邏輯:

<pre> 1 Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor); 2 if (matchingBeans.isEmpty()) { 3 if (descriptor.isRequired()) { 4 raiseNoSuchBeanDefinitionException(type, "", descriptor);
5 }
6 return null;
7 }
8 if (matchingBeans.size() > 1) {
9 String primaryBeanName = determinePrimaryCandidate(matchingBeans, descriptor); 10 if (primaryBeanName == null) { 11 throw new NoSuchBeanDefinitionException(type, "expected single matching bean but found " +
12 matchingBeans.size() + ": " + matchingBeans.keySet()); 13 } 14 if (autowiredBeanNames != null) { 15 autowiredBeanNames.add(primaryBeanName); 16 } 17 return matchingBeans.get(primaryBeanName); 18 } 19 // We have exactly one match.
20 Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next(); 21 if (autowiredBeanNames != null) { 22 autowiredBeanNames.add(entry.getKey()); 23 } 24 ... 25 }</pre>

整理一下邏輯:

  • 如果拿到的Map是空的且屬性必須注入淋肾,拋異常
  • 如果拿到的Map中有多個候選對象条获,判斷其中是否有<bean>中屬性配置為"primary=true"的,有就拿執(zhí)行第13行~第15行的代碼粉私,沒有就第8行的方法返回null惫谤,拋異常壁顶,這個異常的描述相信Spring用的比較多的應該比較熟悉
  • 如果拿到的Map中只有一個候選對象,直接拿到那個

通過這樣一整個流程溜歪,實現(xiàn)了byType自動裝配若专,byType自動裝配流程比較長,中間細節(jié)比較多蝴猪,還需要多看看才能弄明白调衰。

最后注意一點,即所有待注入的PropertyName-->PropertyValue映射拿到之后都只是放在MutablePropertyValues中自阱,最后由AbstractPropertyAccessor類的setPropertyValues方法遍歷并進行逐一注入嚎莉。

通過FactoryBean獲取Bean實例源碼實現(xiàn)

我們知道可以通過實現(xiàn)FactoryBean接口,重寫getObject()方法實現(xiàn)個性化定制Bean的過程沛豌,這部分我們就來看一下Spring源碼是如何實現(xiàn)通過FactoryBean獲取Bean實例的。代碼直接定位到AbstractBeanFactory的doGetBean方法創(chuàng)建單例Bean這部分:

<pre> 1 // Create bean instance.
2 if (mbd.isSingleton()) { 3 sharedInstance = getSingleton(beanName, new ObjectFactory() { 4 public Object getObject() throws BeansException { 5 try { 6 return createBean(beanName, mbd, args); 7 }
8 catch (BeansException ex) { 9 // Explicitly remove instance from singleton cache: It might have been put there 10 // eagerly by the creation process, to allow for circular reference resolution. 11 // Also remove any beans that received a temporary reference to the bean.
12 destroySingleton(beanName); 13 throw ex; 14 } 15 } 16 }); 17 bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); 18 }</pre>

FactoryBean首先是個Bean且被實例化出來成為一個對象之后才能調(diào)用getObject()方法加派,因此還是會執(zhí)行第3行~第16行的代碼叫确,這段代碼之前分析過了就不說了吓歇。之后執(zhí)行第17行的方法:

<pre> 1 protected Object getObjectForBeanInstance( 2 Object beanInstance, String name, String beanName, RootBeanDefinition mbd) {
3
4 // Don't let calling code try to dereference the factory if the bean isn't a factory.
5 if (BeanFactoryUtils.isFactoryDereference(name) && !(beanInstance instanceof FactoryBean)) { 6 throw new BeanIsNotAFactoryException(transformedBeanName(name), beanInstance.getClass()); 7 }
8
9 // Now we have the bean instance, which may be a normal bean or a FactoryBean. 10 // If it's a FactoryBean, we use it to create a bean instance, unless the 11 // caller actually wants a reference to the factory.
12 if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) { 13 return beanInstance; 14 } 15
16 Object object = null; 17 if (mbd == null) { 18 object = getCachedObjectForFactoryBean(beanName); 19 } 20 if (object == null) { 21 // Return bean instance from factory.
22 FactoryBean factory = (FactoryBean) beanInstance; 23 // Caches object obtained from FactoryBean if it is a singleton.
24 if (mbd == null && containsBeanDefinition(beanName)) { 25 mbd = getMergedLocalBeanDefinition(beanName); 26 } 27 boolean synthetic = (mbd != null && mbd.isSynthetic()); 28 object = getObjectFromFactoryBean(factory, beanName, !synthetic); 29 } 30 return object; 31 }</pre>

首先第5行~第7行判斷一下是否beanName以"&"開頭并且不是FactoryBean的實現(xiàn)類,不滿足則拋異常,因為beanName以"&"開頭是FactoryBean的實現(xiàn)類bean定義的一個特征姿现。

接著判斷第12行~第14行吮蛹,如果:

  • bean不是FactoryBean的實現(xiàn)類
  • beanName以"&"開頭

這兩種情況,都直接把生成的bean對象返回出去枫笛,不會執(zhí)行余下的流程。

最后流程走到第16行~第30行吠冤,最終調(diào)用getObject()方法實現(xiàn)個性化定制bean浑彰,先執(zhí)行第28行的方法:

<pre> 1 protected Object getObjectFromFactoryBean(FactoryBean factory, String beanName, boolean shouldPostProcess) { 2 if (factory.isSingleton() && containsSingleton(beanName)) { 3 synchronized (getSingletonMutex()) { 4 Object object = this.factoryBeanObjectCache.get(beanName);
5 if (object == null) {
6 object = doGetObjectFromFactoryBean(factory, beanName, shouldPostProcess); 7 this.factoryBeanObjectCache.put(beanName, (object != null ? object : NULL_OBJECT)); 8 }
9 return (object != NULL_OBJECT ? object : null); 10 } 11 } 12 else { 13 return doGetObjectFromFactoryBean(factory, beanName, shouldPostProcess); 14 } 15 }</pre>

第1行第11行的代碼與第12行第13行的代碼最終都是一樣的,調(diào)用了如下一段:

<pre> 1 private Object doGetObjectFromFactoryBean( 2 final FactoryBean factory, final String beanName, final boolean shouldPostProcess) 3 throws BeanCreationException { 4
5 Object object;
6 try { 7 if (System.getSecurityManager() != null) {
8 AccessControlContext acc = getAccessControlContext(); 9 try { 10 object = AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() { 11 public Object run() throws Exception { 12 return factory.getObject(); 13 } 14 }, acc); 15 } 16 catch (PrivilegedActionException pae) { 17 throw pae.getException(); 18 } 19 } 20 else { 21 object = factory.getObject(); 22 } 23 } 24 catch (FactoryBeanNotInitializedException ex) { 25 throw new BeanCurrentlyInCreationException(beanName, ex.toString()); 26 } 27 catch (Throwable ex) { 28 throw new BeanCreationException(beanName, "FactoryBean threw exception on object creation", ex); 29 } 30
31 // Do not accept a null value for a FactoryBean that's not fully 32 // initialized yet: Many FactoryBeans just return null then.
33 if (object == null && isSingletonCurrentlyInCreation(beanName)) { 34 throw new BeanCurrentlyInCreationException( 35 beanName, "FactoryBean which is currently in creation returned null from getObject"); 36 } 37
38 if (object != null && shouldPostProcess) { 39 try { 40 object = postProcessObjectFromFactoryBean(object, beanName); 41 } 42 catch (Throwable ex) { 43 throw new BeanCreationException(beanName, "Post-processing of the FactoryBean's object failed", ex); 44 } 45 } 46
47 return object; 48 }</pre>

第12行和第21行的代碼拯辙,都一樣郭变,最終調(diào)用getObject()方法獲取對象⊙谋#回過頭去看之前的getObjectFromFactoryBean方法诉濒,雖然if...else...邏輯最終都是調(diào)用了以上的方法,但是區(qū)別在于:

  • 如果FactoryBean接口實現(xiàn)類的isSington方法返回的是true夕春,那么每次調(diào)用getObject方法的時候會優(yōu)先嘗試從FactoryBean對象緩存中取目標對象未荒,有就直接拿,沒有就創(chuàng)建并放入FactoryBean對象緩存及志,這樣保證了每次單例的FactoryBean調(diào)用getObject()方法后最終拿到的目標對象一定是單例的片排,即在內(nèi)存中都是同一份
  • 如果FactoryBean接口實現(xiàn)類的isSington方法返回的是false,那么每次調(diào)用getObject方法的時候都會新創(chuàng)建一個目標對象

微信公眾號【黃小斜】作者是螞蟻金服 JAVA 工程師速侈,專注于 JAVA
后端技術(shù)棧:SpringBoot率寡、SSM全家桶、MySQL倚搬、分布式勇劣、中間件、微服務潭枣,同時也懂點投資理財比默,堅持學習和寫作,相信終身學習的力量盆犁!關(guān)注公眾號后回復”架構(gòu)師“即可領(lǐng)取
Java基礎(chǔ)命咐、進階、項目和架構(gòu)師等免費學習資料谐岁,更有數(shù)據(jù)庫醋奠、分布式、微服務等熱門技術(shù)學習視頻伊佃,內(nèi)容豐富窜司,兼顧原理和實踐,另外也將贈送作者原創(chuàng)的Java學習指南航揉、Java程序員面試指南等干貨資源

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末塞祈,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子帅涂,更是在濱河造成了極大的恐慌议薪,老刑警劉巖尤蛮,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異斯议,居然都是意外死亡产捞,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進店門哼御,熙熙樓的掌柜王于貴愁眉苦臉地迎上來坯临,“玉大人,你說我怎么就攤上這事恋昼∧虺叮” “怎么了?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵焰雕,是天一觀的道長。 經(jīng)常有香客問我芳杏,道長矩屁,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任爵赵,我火速辦了婚禮吝秕,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘空幻。我一直安慰自己烁峭,他們只是感情好,可當我...
    茶點故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布秕铛。 她就那樣靜靜地躺著约郁,像睡著了一般。 火紅的嫁衣襯著肌膚如雪但两。 梳的紋絲不亂的頭發(fā)上鬓梅,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天,我揣著相機與錄音谨湘,去河邊找鬼绽快。 笑死,一個胖子當著我的面吹牛紧阔,可吹牛的內(nèi)容都是我干的坊罢。 我是一名探鬼主播,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼擅耽,長吁一口氣:“原來是場噩夢啊……” “哼活孩!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起乖仇,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤诱鞠,失蹤者是張志新(化名)和其女友劉穎挎挖,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體航夺,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡蕉朵,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了阳掐。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片始衅。...
    茶點故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖缭保,靈堂內(nèi)的尸體忽然破棺而出汛闸,到底是詐尸還是另有隱情,我是刑警寧澤艺骂,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布诸老,位于F島的核電站,受9級特大地震影響钳恕,放射性物質(zhì)發(fā)生泄漏别伏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一忧额、第九天 我趴在偏房一處隱蔽的房頂上張望厘肮。 院中可真熱鬧,春花似錦睦番、人聲如沸类茂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽巩检。三九已至,卻和暖如春示启,著一層夾襖步出監(jiān)牢的瞬間碴巾,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工丑搔, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留厦瓢,地道東北人。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓啤月,卻偏偏與公主長得像煮仇,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子谎仲,可洞房花燭夜當晚...
    茶點故事閱讀 44,864評論 2 354

推薦閱讀更多精彩內(nèi)容

  • org.springframework.beans: org.springframework.beans.fact...
    過河卒sc閱讀 590評論 1 1
  • 1. 詳解Spring 中如何控制2個bean中的初始化順序 ??開發(fā)過程中有這樣一個場景浙垫,2個 bean 初始化...
    未名枯草閱讀 1,301評論 0 1
  • 作者: 一字馬胡 轉(zhuǎn)載標志 【2017-12-29】 更新日志 日期更新內(nèi)容備注2017-12-29創(chuàng)建分析文檔...
    一字馬胡閱讀 12,089評論 2 32
  • 一個詩人每天少不了讀一些詩,詩人可以保持不寫詩,但不能沒有詩夹姥。 現(xiàn)如今是一個快節(jié)奏的世界杉武,我們習慣了快餐閱讀,看紙...
    書新一閱讀 323評論 4 4
  • 雨落吳山青望眼辙售,汀蘭飲露梗纖纖轻抱。 玉階扶蕊鶯眠柳,古寺?lián)硖傺嗾砗煛?千載伍公冤自訴旦部,萬年曹婦恨誰潛祈搜? 蒼苔幾點東風...
    幽小窗閱讀 612評論 31 39