Note that it takes a long, long time to read this article.
This article is about Spring IOC Source Parsing (Part I) In the sequel, the previous article introduced how to start Spring using XML, and then tracked the creation of Bean Factory containers, the parsing of configuration files, the registration of beans, and so on.
12. finishBeanFactoryInitialization()
Super-long warning ahead...
We mentioned just now that beans have not been initialized yet. This method is responsible for initializing all singleton bean s that are not lazily loaded.
It's started.
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) { if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) && beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) { beanFactory.setConversionService( beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)); } if (!beanFactory.hasEmbeddedValueResolver()) { beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal)); } //Initialize LoadTime Weaver Aware-type Bean s first String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false); for (String weaverAwareName : weaverAwareNames) { getBean(weaverAwareName); } //Stop using temporary class loaders for type matching beanFactory.setTempClassLoader(null); //Freeze all bean definitions, that is, registered bean definitions will not be modified or reprocessed beanFactory.freezeConfiguration(); //Initialization beanFactory.preInstantiateSingletons(); }
Look down at the meaning of the code that is not explained above.
conversionService
The most practical scenario for this type of bean is to use it when converting the parameters passed from the front end to the parameters format on the back-end controller method.
For example, the front end needs to pass a String, and the back end can do this when it uses Date acceptance.
public class StringToDateConverter implements Converter<String, Date> { @Override public Date convert(String date) { try { return dateFormat.parse(date); } catch (Exception e) { e.printStackTrace(); System.out.println("Date conversion failed!"); return null; } } }
Get another bean
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean"> <property name="converters"> <list> <bean class="cn.shiyujun.utils.StringToDateConverter"/> </list> </property></bean>
EmbeddedValueResolver
Using Embedded Value Resolver, it is easy to read the properties of configuration files.
@Component public class PropertiesUtil implements EmbeddedValueResolverAware { private StringValueResolver resolver; @Override public void setEmbeddedValueResolver(StringValueResolver resolver) { this.resolver = resolver; } /** * Just pass in the attribute name when you get the attribute. */ public String getPropertiesValue(String key) { StringBuilder name = new StringBuilder("${").append(key).append("}"); return resolver.resolveStringValue(name.toString()); } }
Initialization
Knock on the blackboard, the point is...
Here we analyze the beanFactory.preInstantiateSingletons() method
public void preInstantiateSingletons() throws BeansException { if (this.logger.isDebugEnabled()) { this.logger.debug("Pre-instantiating singletons in " + this); } // This. bean Definition Names saves all beanNames List<String> beanNames = new ArrayList<String>(this.beanDefinitionNames); for (String beanName : beanNames) { // Merge the configuration in the parent Bean with the parent attribute in the idea <Bean id=""class="""parent="/>. RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName); // Not abstract, singular, and not lazy to load if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) { // Processing FactoryBean if (isFactoryBean(beanName)) { //Add the "&" sign before the bean Name final FactoryBean<?> factory = (FactoryBean<?>) getBean(FACTORY_BEAN_PREFIX + beanName); // Determine whether the current FactoryBean is an implementation of SmartFactoryBean boolean isEagerInit; if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) { isEagerInit = AccessController.doPrivileged(new PrivilegedAction<Boolean>() { @Override public Boolean run() { return ((SmartFactoryBean<?>) factory).isEagerInit(); } }, getAccessControlContext()); } else { isEagerInit = (factory instanceof SmartFactoryBean && ((SmartFactoryBean<?>) factory).isEagerInit()); } if (isEagerInit) { getBean(beanName); } } else { // Initialization is not a direct use of FactoryBean getBean(beanName); } } } // If the bean implements the Smart Initializing Singleton interface, it is called back here. for (String beanName : beanNames) { Object singletonInstance = getSingleton(beanName); if (singletonInstance instanceof SmartInitializingSingleton) { final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance; if (System.getSecurityManager() != null) { AccessController.doPrivileged(new PrivilegedAction<Object>() { @Override public Object run() { smartSingleton.afterSingletonsInstantiated(); return null; } }, getAccessControlContext()); } else { smartSingleton.afterSingletonsInstantiated(); } } } }
As you can see, whether it's FactoryBean or not, getBean(beanName) is finally called, so keep looking at this method.
@Override public Object getBean(String name) throws BeansException { return doGetBean(name, null, null, false); } protected <T> T doGetBean( final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly) throws BeansException { // Getting the Bean Name handles two situations, one is FactoryBean (with'&'in front of it), and the other is that it can get the Bean by alias, so here it is converted to the most orthodox Bean Name. //The main logic is to remove aliases if they are FactoryBean s and not post codes after getting real names from aliases. final String beanName = transformedBeanName(name); //Final Return Value Object bean; // Check if initialized Object sharedInstance = getSingleton(beanName); //If it has been initialized and no args parameter is passed, it means get, take it out and return it directly. if (sharedInstance != null && args == null) { if (logger.isDebugEnabled()) { if (isSingletonCurrentlyInCreation(beanName)) { logger.debug("..."); } else { logger.debug("Returning cached instance of singleton bean '" + beanName + "'"); } } // Here, if it's a normal Bean, return it directly, and if it's a FactoryBean, return the instance object it created. bean = getObjectForBeanInstance(sharedInstance, name, beanName, null); } else { // If this bean of prototype type type type exists if (isPrototypeCurrentlyInCreation(beanName)) { throw new BeanCurrentlyInCreationException(beanName); } // If the current BeanDefinition does not exist and has a parent BeanFactory BeanFactory parentBeanFactory = getParentBeanFactory(); if (parentBeanFactory != null && !containsBeanDefinition(beanName)) { String nameToLookup = originalBeanName(name); // Returns the query result of the parent container if (args != null) { return (T) parentBeanFactory.getBean(nameToLookup, args); } else { return parentBeanFactory.getBean(nameToLookup, requiredType); } } if (!typeCheckOnly) { // TypeeCheckOnly is false, putting the current bean Name into an alreadyCreated Set of Sets. markBeanAsCreated(beanName); } /* * This is the time to create bean s */ try { final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); checkMergedBeanDefinition(mbd, beanName, args); // Initialize all dependent beans first, dependencies defined in dependent-on String[] dependsOn = mbd.getDependsOn(); if (dependsOn != null) { for (String dep : dependsOn) { // Check for cyclic dependencies if (isDependent(beanName, dep)) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'"); } // Register dependencies registerDependentBean(dep, beanName); // Initialize dependencies first getBean(dep); } } // If it is singular if (mbd.isSingleton()) { sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() { @Override public Object getObject() throws BeansException { try { // Perform the creation of beans, as follows return createBean(beanName, mbd, args); } catch (BeansException ex) { destroySingleton(beanName); throw ex; } } }); bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); } // If it is prototype else if (mbd.isPrototype()) { Object prototypeInstance = null; try { beforePrototypeCreation(beanName); // Execute Create Bean prototypeInstance = createBean(beanName, mbd, args); } finally { afterPrototypeCreation(beanName); } bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd); } // If it's not singleton and prototype, then it's custom scope, such as session type in Web project, which is implemented by the application of custom scope. else { String scopeName = mbd.getScope(); final Scope scope = this.scopes.get(scopeName); if (scope == null) { throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'"); } try { Object scopedInstance = scope.get(beanName, new ObjectFactory<Object>() { @Override public Object getObject() throws BeansException { beforePrototypeCreation(beanName); try { // Execute Create Bean return createBean(beanName, mbd, args); } finally { afterPrototypeCreation(beanName); } } }); bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd); } catch (IllegalStateException ex) { throw new BeanCreationException(beanName, "Scope '" + scopeName + "' is not active for the current thread; consider " + "defining a scoped proxy for this bean if you intend to refer to it from a singleton", ex); } } } catch (BeansException ex) { cleanupAfterBeanCreationFailure(beanName); throw ex; } } //Check the type of bean if (requiredType != null && bean != null && !requiredType.isInstance(bean)) { try { return getTypeConverter().convertIfNecessary(bean, requiredType); } catch (TypeMismatchException ex) { if (logger.isDebugEnabled()) { logger.debug("Failed to convert bean '" + name + "' to required type '" + ClassUtils.getQualifiedName(requiredType) + "'", ex); } throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass()); } } return (T) bean; }
Looking at the above method, we know that Spring itself only defines two kinds of Scopes, and we also know how several Scopes of Spring MVC are implemented.
Then we find that at first we will judge whether the bean exists or not, and if it exists, it will return directly. If it doesn't exist, go ahead and look at the createBean method
protected Object createBean(String beanName, RootBeanDefinition mbd, Object[] args) throws BeanCreationException { if (logger.isDebugEnabled()) { logger.debug("Creating instance of bean '" + beanName + "'"); } RootBeanDefinition mbdToUse = mbd; // Ensure that Class in BeanDefinition is loaded Class<?> resolvedClass = resolveBeanClass(mbd, beanName); if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) { mbdToUse = new RootBeanDefinition(mbd); mbdToUse.setBeanClass(resolvedClass); } // Prepare method overrides if <lookup-method/> and <replaced-method/> are defined in the bean try { mbdToUse.prepareMethodOverrides(); } catch (BeanDefinitionValidationException ex) { throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(), beanName, "Validation of method overrides failed", ex); } try { // Return directly if there is an agent Object bean = resolveBeforeInstantiation(beanName, mbdToUse); if (bean != null) { return bean; } } catch (Throwable ex) { throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName, "BeanPostProcessor before instantiation of bean failed", ex); } // Create bean s Object beanInstance = doCreateBean(beanName, mbdToUse, args); if (logger.isDebugEnabled()) { logger.debug("Finished creating instance of bean '" + beanName + "'"); } return beanInstance; } protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) throws BeanCreationException { BeanWrapper instanceWrapper = null; if (mbd.isSingleton()) { //If it is. factoryBean, it is deleted from the cache instanceWrapper = this.factoryBeanInstanceCache.remove(beanName); } if (instanceWrapper == null) { // Instantiate Bean, which is the end point. Here's how instanceWrapper = createBeanInstance(beanName, mbd, args); } //bean instance final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null); //bean type Class<?> beanType = (instanceWrapper != null ? instanceWrapper.getWrappedClass() : null); mbd.resolvedTargetType = beanType; synchronized (mbd.postProcessingLock) { if (!mbd.postProcessed) { try { // Loop calls implement the postProcessMergedBeanDefinition method of the MergedBeanDefinitionPostProcessor interface // Spring has several default implementations of this interface, one of which you are most familiar with is the operation of the @Autowire annotation applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName); } catch (Throwable ex) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Post-processing of merged bean definition failed", ex); } mbd.postProcessed = true; } } // Solving the problem of cyclic dependence boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { if (logger.isDebugEnabled()) { logger.debug("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references"); } //When creating A, A depends on B, which allows for cyclic dependency by (8) putting A as an ObjectFactory in a singleton factory for early exposure, where B needs to refer to A, but A is creating, taking ObjectFactory from the singleton factory. addSingletonFactory(beanName, new ObjectFactory<Object>() { @Override public Object getObject() throws BeansException { return getEarlyBeanReference(beanName, mbd, bean); } }); } Object exposedObject = bean; try { // It's important to be responsible for attribute assembly. Here's how it works populateBean(beanName, mbd, instanceWrapper); if (exposedObject != null) { // Here are various callbacks after processing bean s are initialized, such as init-method, InitializingBean interface, BeanPostProcessor interface. exposedObject = initializeBean(beanName, exposedObject, mbd); } } catch (Throwable ex) { if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) { throw (BeanCreationException) ex; } else { throw new BeanCreationException( mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex); } } //Similarly, if there is a cyclic dependency if (earlySingletonExposure) { Object earlySingletonReference = getSingleton(beanName, false); if (earlySingletonReference != null) { if (exposedObject == bean) { exposedObject = earlySingletonReference; } else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) { String[] dependentBeans = getDependentBeans(beanName); Set<String> actualDependentBeans = new LinkedHashSet<String>(dependentBeans.length); for (String dependentBean : dependentBeans) { if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) { actualDependentBeans.add(dependentBean); } } if (!actualDependentBeans.isEmpty()) { throw new BeanCurrentlyInCreationException(beanName, "Bean with name '" + beanName + "' has been injected into other beans [" + StringUtils.collectionToCommaDelimitedString(actualDependentBeans) + "] in its raw version as part of a circular reference, but has eventually been " + "wrapped. This means that said other beans do not use the final version of the " + "bean. This is often the result of over-eager type matching - consider using " + "'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example."); } } } } // Register bean s in the corresponding Scope try { registerDisposableBeanIfNecessary(beanName, bean, mbd); } catch (BeanDefinitionValidationException ex) { throw new BeanCreationException( mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex); } return exposedObject; }
The first initialized bean s back here, do you think it's over? No, there are several other important points.
Create bean instance createBeanInstance ()
protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, Object[] args) { // Ensure that the class is loaded Class<?> beanClass = resolveBeanClass(mbd, beanName); // Access privileges of validation classes if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) && !mbd.isNonPublicAccessAllowed()) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Bean class isn't public, and non-public access not allowed: " + beanClass.getName()); } if (mbd.getFactoryMethodName() != null) { // Instance by Factory Method return instantiateUsingFactoryMethod(beanName, mbd, args); } //Is it the first time? boolean resolved = false; //Whether to use constructor injection or not boolean autowireNecessary = false; if (args == null) { synchronized (mbd.constructorArgumentLock) { if (mbd.resolvedConstructorOrFactoryMethod != null) { resolved = true; autowireNecessary = mbd.constructorArgumentsResolved; } } } if (resolved) { if (autowireNecessary) { return autowireConstructor(beanName, mbd, null, null); } else { // non-parameter constructor return instantiateBean(beanName, mbd); } } // Judging whether to use a parametric constructor Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName); if (ctors != null || mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_CONSTRUCTOR || mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) { // Constructor Dependency Injection return autowireConstructor(beanName, mbd, ctors, args); } // Calling a parametric constructor return instantiateBean(beanName, mbd); }
Take a look at an unqualified structure.
protected BeanWrapper instantiateBean(final String beanName, final RootBeanDefinition mbd) { try { Object beanInstance; final BeanFactory parent = this; if (System.getSecurityManager() != null) { beanInstance = AccessController.doPrivileged(new PrivilegedAction<Object>() { @Override public Object run() { return getInstantiationStrategy().instantiate(mbd, beanName, parent); } }, getAccessControlContext()); } else { // Realization of concrete instantiation, look down beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, parent); } BeanWrapper bw = new BeanWrapperImpl(beanInstance); initBeanWrapper(bw); return bw; } catch (Throwable ex) { throw new BeanCreationException( mbd.getResourceDescription(), beanName, "Instantiation of bean failed", ex); } } public Object instantiate(RootBeanDefinition bd, String beanName, BeanFactory owner) { // If there is no method override, use java reflection to instantiate, otherwise use CGLIB. if (bd.getMethodOverrides().isEmpty()) { Constructor<?> constructorToUse; synchronized (bd.constructorArgumentLock) { constructorToUse = (Constructor<?>) bd.resolvedConstructorOrFactoryMethod; if (constructorToUse == null) { final Class<?> clazz = bd.getBeanClass(); if (clazz.isInterface()) { throw new BeanInstantiationException(clazz, "Specified class is an interface"); } try { if (System.getSecurityManager() != null) { constructorToUse = AccessController.doPrivileged(new PrivilegedExceptionAction<Constructor<?>>() { @Override public Constructor<?> run() throws Exception { return clazz.getDeclaredConstructor((Class[]) null); } }); } else { constructorToUse = clazz.getDeclaredConstructor((Class[]) null); } bd.resolvedConstructorOrFactoryMethod = constructorToUse; } catch (Throwable ex) { throw new BeanInstantiationException(clazz, "No default constructor found", ex); } } } // Instance by Constructing Method return BeanUtils.instantiateClass(constructorToUse); } else { // Existence method override, the use of CGLIB to complete instantiation, need to rely on CGLIB to generate subclasses, here does not expand return instantiateWithMethodInjection(bd, beanName, owner); } }
bean attribute injection populateBean ()
protected void populateBean(String beanName, RootBeanDefinition mbd, BeanWrapper bw) { // All properties of bean s PropertyValues pvs = mbd.getPropertyValues(); if (bw == null) { if (!pvs.isEmpty()) { throw new BeanCreationException( mbd.getResourceDescription(), beanName, "Cannot apply property values to null instance"); } else { return; } } boolean continueWithPropertyPopulation = true; if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof InstantiationAwareBeanPostProcessor) { InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp; // If false is returned, the representative does not need to do any subsequent attribute settings or go through other BeanPostProcessor processing. if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) { continueWithPropertyPopulation = false; break; } } } } if (!continueWithPropertyPopulation) { return; } if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME || mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) { MutablePropertyValues newPvs = new MutablePropertyValues(pvs); // Find all attribute values by name, and initialize the dependent bean if it is a bean dependency. Record dependencies if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME) { autowireByName(beanName, mbd, bw, newPvs); } // Assemble by type. Complicated if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) { autowireByType(beanName, mbd, bw, newPvs); } pvs = newPvs; } boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors(); boolean needsDepCheck = (mbd.getDependencyCheck() != RootBeanDefinition.DEPENDENCY_CHECK_NONE); if (hasInstAwareBpps || needsDepCheck) { PropertyDescriptor[] filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching); if (hasInstAwareBpps) { for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof InstantiationAwareBeanPostProcessor) { InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp; // Here's a BeanPostProcessor I mentioned above that handles @Autowire correctly // It sets values for all attributes marked with @Autowired and @Value annotations pvs = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName); if (pvs == null) { return; } } } } if (needsDepCheck) { checkDependencies(beanName, mbd, filteredPds, pvs); } } // Setting the property value of the bean instance applyPropertyValues(beanName, mbd, bw, pvs); }
getBean is done.
13. finishRefresh()
protected void finishRefresh() { //As you can see by name, clean up the resource cache used in the previous series of operations clearResourceCaches(); // Initialize Lifecycle Processor initLifecycleProcessor(); // The internal implementation of this method is to start all bean s that implement the Lifecycle interface. getLifecycleProcessor().onRefresh(); //Publish ContextRefreshedEvent events publishEvent(new ContextRefreshedEvent(this)); // Check whether spring.liveBeansView.mbeanDomain exists, and if so, create an MBeanServer LiveBeansView.registerApplicationContext(this); }
14. resetCommonCaches()
The last step is to clear the cache
15. refresh() summary
It takes so long to sort out the details of the whole refresh() method, and here's a paste of the muddy refresh() method you just saw.
public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Record container startup time, mark "startup" status, and check environment variables prepareRefresh(); // Initialize BeanFactory container, register BeanDefinition ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Setting up the class loader for BeanFactory, adding several BeanPostProcessor s, and manually registering several special bean s prepareBeanFactory(beanFactory); try { // Extension Point postProcessBeanFactory(beanFactory); // Call the postProcessBeanFactory method of BeanFactoryPostProcessor implementation classes invokeBeanFactoryPostProcessors(beanFactory); // Implementing Class for Registering BeanPostProcessor registerBeanPostProcessors(beanFactory); // Initialize MessageSource initMessageSource(); // Initialize Event Broadcaster initApplicationEventMulticaster(); // Extension Point onRefresh(); // Registered Event Listener registerListeners(); // Initialize all singleton beans finishBeanFactoryInitialization(beanFactory); // Broadcasting events finishRefresh(); } catch (BeansException ex) { if (logger.isWarnEnabled()) { logger.warn("Exception encountered during context initialization - " + "cancelling refresh attempt: " + ex); } // Destroy initialized beans destroyBeans(); // Setting the'active'state cancelRefresh(ex); throw ex; } finally { // Clear Cache resetCommonCaches(); } } }
summary
This is the end of this article. In view of the simplicity and understandability of XML, this paper introduces the start-up process of Spring IOC based on XML, explains the creation of Bean container and the initialization process of Bean in a slightly deeper way. This is the first time that the author has read the source code of the open source framework. If there are any mistakes in the article, please take the trouble to point out.
Given the current popularity of Spring Boot and Spring Cloud, the next article will analyze Spring IOC from the annotation-based Perspective
<h4 style="color:red">recommended reading </h4>
- Summary of Spring Cloud Learning Series
- Why do we have to ask redis in front-line interviews?
- Summary of Essential Knowledge for Multithread Interview
- Java Collection Source Analysis Summary - JDK 1.8
- Quick Search of Common Commands in Linux-Summary
- Summary of JVM Series Articles
- Summary of MySQL Series Articles
- Summary of RabbitMQ Series Articles
<h4 style="color:red"> All blog articles are published in the public number "Java Learning Record". Please keep them.
You can get 2000G Java Learning Resources </h4> by scanning the code and paying attention to the public number.