zoukankan      html  css  js  c++  java
  • 一个Spring Bean从无到有的过程

    有开头没结尾

     经历了java开发变迁史,从早期编写原生的servlet、自研mvc和jdbc工具、和使用开源框架struts、hibernate、jsp、spring、springmvc、freemarker、springboot,到最后前后端分离式开发,一开始开发工具用的是editplus。相对来说,现在开发很好了,框架生态(只有spring生态传承下来了,其他都成了历史)。到spring生态圈终结了,它的体系太过庞大了,席卷了各国,项目产品技术换型的几率很小了。

          一开始做Spring相关开发,写了大量xml配置,到后来换成了注解式开发,虽然能干活,可并不知道它的内在原理和设计理念是什么,随着时间的累计,需要做sping 扩展或集成,就要研究源码级是如何实现的了。我就以一个Spring Bean如何创建开始,不要小看一个Bean,很多码农都说不清它的创建过程。

    简单介绍Spring框架是一个开放源代码的J2EE应用程序框架,由Rod Johnson发起,是针对bean的生命周期进行管理的轻量级容器(lightweight container)。 Spring解决了开发者在J2EE开发中遇到的许多常见的问题,提供了功能强大IOC、AOP及Web MVC等功能。Spring可以单独应用于构筑应用程序,也可以和Struts、Webwork、Tapestry等众多Web框架组合使用,并且可以与 Swing等桌面应用程序AP组合。因此, Spring不仅仅能应用于JEE应用程序之中,也可以应用于桌面应用程序以及小应用程序之中。Spring框架主要由七部分组成,分别是 Spring Core、 Spring AOP、 Spring ORM、 Spring DAO、Spring Context、 Spring Web和 Spring Web MVC。


    就是图片里的Beans,下面开始进入正题,看仔细了,这很重要

    介绍bean之前,说下ioc和context上下文(可以理解为一个宿主环境)

    ioc是Inversion of Control的简称,行内话叫控制反转,早期开发都是需要对象,自己new出来一个,可有了ioc后,我们不需要

    自己new对象了,让spring ioc容器负责对象的创建和管理。一句话概括ioc的作用颠倒了对象的依赖关系,ioc容器管理对象。

    尽量搞明白bean,ioc,context,对以后学spring生态很重要。

    Spring bean 分两种,一种普通bean,一种是工厂型的bean,但处理类型很多

    一, 普通bean(以xml配置为例,现在改用注解的人越来越多了)

    准备好原材料

    spring-bean.xml文件(以演示为例)

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
        
    	<bean id="user" class="spring.model.User">
    		<property name="id" value="1"/>
    		<property name="name" value="dongguangming"/>
    		<property name="age" value="99"/>
    	</bean>
        
    </beans>

          User.java

    public class User {
    
    	private int id;
    	private String name;
    	private int age;
       //set,get略
    }

    测试类

    /**
     * 
     * @author dgm
     * @describe "xml bean"
     * @date 2020年4月16日
     */
    public class XMLConfigurationBeanApp {
    	public static void main(String[] args) {
    		/*DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
    		XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
    		
    		reader.loadBeanDefinitions("conf/spring-bean.xml");*/
    		 ApplicationContext applicationContext = 
    	                new ClassPathXmlApplicationContext("conf/spring-bean.xml");
    		User user1 = applicationContext.getBean("user", User.class);
    		User user2 = (User) applicationContext.getBean("user");
    		System.out.println(user1);
    		System.out.println(user2);
    		System.out.println(user1==user2);
    
    		//由于是个普通的bean,故出现异常,生成环境就不需要这样写了,我是测试两种不同类型的bean
    		System.out.println(applicationContext.getBean("&user"));
    	}
    }

     输出结果


    解析初始化过程,就从这行代码

     ApplicationContext applicationContext = 
    	                new ClassPathXmlApplicationContext("spring-bean.xml");

    说起,看看spring做了多少事

     三大阶段:bean解析,bean实例化,bean初始化,销毁

    1. bean解析定义注册阶段

    早期开发人员都知道,配置文件以xml文件(现在人都喜欢注解解析了)居多,要把xml文件内容解析成java对应的类,简称dom解析,如


    spring也是如此,一开始进行大量的xml文件解析工作,和java对应的类映射好

    文件路径org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException
    
    
    /**
    	 * Actually load bean definitions from the specified XML file.
    	 * @param inputSource the SAX InputSource to read from
    	 * @param resource the resource descriptor for the XML file
    	 * @return the number of bean definitions found
    	 * @throws BeanDefinitionStoreException in case of loading or parsing errors
    	 * @see #doLoadDocument
    	 * @see #registerBeanDefinitions
    	 */
    	protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
    			throws BeanDefinitionStoreException {
    		try {
                //xml文档解析
    			Document doc = doLoadDocument(inputSource, resource);
                //调用注册bean定义
    			return registerBeanDefinitions(doc, resource);
    		}
    }
    
    文件路径org.springframework.beans.factory.xml.XmlBeanDefinitionReader.registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException
    
    public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
    		BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
    		int countBefore = getRegistry().getBeanDefinitionCount();
            //调用bean注册
    		documentReader.registerBeanDefinitions(doc, 
    createReaderContext(resource));
    
    		return getRegistry().getBeanDefinitionCount() - countBefore;
    	}
    
    。。。。。。嵌套很多
    
    文件路径org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate)
    
    /**
    	 * Process the given bean element, parsing the bean definition
    	 * and registering it with the registry.
    	 */
    	protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
    		BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
    		if (bdHolder != null) {
    			bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
    			try {
    				// Register the final decorated instance.
                    //调用注册工具注册
    				BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
    			}
    			catch (BeanDefinitionStoreException ex) {
    				getReaderContext().error("Failed to register bean definition with name '" +
    						bdHolder.getBeanName() + "'", ele, ex);
    			}
    			// Send registration event.
    			getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
    		}
    	}
    
    文件路径org.springframework.beans.factory.support.DefaultListableBeanFactory.registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException
    
    
    @Override
    	public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
    			throws BeanDefinitionStoreException {
    
    		Assert.hasText(beanName, "Bean name must not be empty");
    		Assert.notNull(beanDefinition, "BeanDefinition must not be null");
    
    		if (beanDefinition instanceof AbstractBeanDefinition) {
    			try {
    				((AbstractBeanDefinition) beanDefinition).validate();
    			}
    			catch (BeanDefinitionValidationException ex) {
    				throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
    						"Validation of bean definition failed", ex);
    			}
    		}
    
    		BeanDefinition oldBeanDefinition;
    
    		oldBeanDefinition = this.beanDefinitionMap.get(beanName);
    		if (oldBeanDefinition != null) {
    			if (!isAllowBeanDefinitionOverriding()) {
    				throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
    						"Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
    						"': There is already [" + oldBeanDefinition + "] bound.");
    			}
    			else if (oldBeanDefinition.getRole() < beanDefinition.getRole()) {
    				// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
    				if (this.logger.isWarnEnabled()) {
    					this.logger.warn("Overriding user-defined bean definition for bean '" + beanName +
    							"' with a framework-generated bean definition: replacing [" +
    							oldBeanDefinition + "] with [" + beanDefinition + "]");
    				}
    			}
    			else if (!beanDefinition.equals(oldBeanDefinition)) {
    				if (this.logger.isInfoEnabled()) {
    					this.logger.info("Overriding bean definition for bean '" + beanName +
    							"' with a different definition: replacing [" + oldBeanDefinition +
    							"] with [" + beanDefinition + "]");
    				}
    			}
    			else {
    				if (this.logger.isDebugEnabled()) {
    					this.logger.debug("Overriding bean definition for bean '" + beanName +
    							"' with an equivalent definition: replacing [" + oldBeanDefinition +
    							"] with [" + beanDefinition + "]");
    				}
    			}
                //终于测测成功了,一种数据结构
    			this.beanDefinitionMap.put(beanName, beanDefinition);
    		}
    		else {
    			if (hasBeanCreationStarted()) {
    				// Cannot modify startup-time collection elements anymore (for stable iteration)
    				synchronized (this.beanDefinitionMap) {
                       //同上
    					this.beanDefinitionMap.put(beanName, beanDefinition);
    					List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
    					updatedDefinitions.addAll(this.beanDefinitionNames);
    					updatedDefinitions.add(beanName);
    					this.beanDefinitionNames = updatedDefinitions;
    					if (this.manualSingletonNames.contains(beanName)) {
    						Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
    						updatedSingletons.remove(beanName);
    						this.manualSingletonNames = updatedSingletons;
    					}
    				}
    			}
    			else {
    				// Still in startup registration phase
    				this.beanDefinitionMap.put(beanName, beanDefinition);
    				this.beanDefinitionNames.add(beanName);
    				this.manualSingletonNames.remove(beanName);
    			}
    			this.frozenBeanDefinitionNames = null;
    		}
    
    		if (oldBeanDefinition != null || containsSingleton(beanName)) {
    			resetBeanDefinition(beanName);
    		}
    	}
    
    

     这只是一个bean定义(开发过程时有很多很多个Bean定义注册),很重要做那么多工作就是为了组装成这种数据结构:this.beanDefinitionMap.put(beanName, beanDefinition);它是这么定义的:

    /** Map of bean definition objects, keyed by bean name */
    	private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);

     beanDefinition的部分属性,把它当然一种组装数据的结构就行

    ​ 

     2. Bean实例化和初始化阶段

    调用时

    ClassPathXmlApplicationContext(AbstractApplicationContext).finishBeanFactoryInitialization(ConfigurableListableBeanFactory) line: 861	
    
    {
    //部分代码
    // Register a default embedded value resolver if no bean post-processor
    		// (such as a PropertyPlaceholderConfigurer bean) registered any before:
    		// at this point, primarily for resolution in annotation attribute values.
    		if (!beanFactory.hasEmbeddedValueResolver()) {
    			beanFactory.addEmbeddedValueResolver(new StringValueResolver() {
    				@Override
    				public String resolveStringValue(String strVal) {
    					return getEnvironment().resolvePlaceholders(strVal);
    				}
    			});
    		}
    
    // Instantiate all remaining (non-lazy-init) singletons.
    //这里面东西可多了去了
    		beanFactory.preInstantiateSingletons();
    }

     preInstantiateSingletons核心代码:

    @Override
    	public void preInstantiateSingletons() throws BeansException {
    
    
    		// Iterate over a copy to allow for init methods which in turn register new bean definitions.
    		// While this may not be part of the regular factory bootstrap, it does otherwise work fine.
    		List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
    
    		// Trigger initialization of all non-lazy singleton beans...
    		for (String beanName : beanNames) {
    			RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
    			if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
    				if (isFactoryBean(beanName)) {
                        //工厂型bean(系统自带很多,当然也可以自己写),实现FactoryBean接口或继承AbstractFactoryBean
    					final FactoryBean<?> factory = (FactoryBean<?>) getBean(FACTORY_BEAN_PREFIX + beanName);
    					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 {
                        //普通bean,人为实现,没有实现FactoryBean接口或没有继承AbstractFactoryBean
    					getBean(beanName);
    				}
    			}
    		}
    
    		// Trigger post-initialization callback for all applicable beans...
    		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();
    				}
    			}
    		}
    	}

    0:DefaultListableBeanFactory(AbstractBeanFactory).getBean(String)

    1 DefaultListableBeanFactory(AbstractBeanFactory).doGetBean(String, Class<T>, Object[], boolean) line: 303    
    2 DefaultListableBeanFactory(DefaultSingletonBeanRegistry).getSingleton(String) line: 231    ,第一次getSingleton
    3 Object org.springframework.beans.factory.support.AbstractBeanFactory.createBean(String beanName, RootBeanDefinition mbd, Object[] args) 

    要分叉了:

    4. org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.resolveBeforeInstantiation(String beanName, RootBeanDefinition mbd)

    判断是否有InstantiationAwareBeanPostProcessor,applyBeanPostProcessorsBeforeInstantiation()>postProcessBeforeInstantiation(),

    applyBeanPostProcessorsAfterInitialization()>postProcessAfterInitialization().

    返回代理对象的机会,一旦返回,就没有下面的事了,aop代理由此可生

    如果return ,直接跳到10阶段
    5 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(String beanName, 4 RootBeanDefinition mbd, Object[] args)  很重要
    6 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(String beanName, RootBeanDefinition mbd, Object[] args), 实例化完成,详细请继续看源码,注意实例化策略方式不止一种:构造器,工厂方法,也有机会通过cglib

    cglid代理
    7 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(beanName, mbd, instanceWrapper),,详细请继续看源码,下面是部分

    if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME ||
    				mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) {
    			MutablePropertyValues newPvs = new MutablePropertyValues(pvs);
    
    			// Add property values based on autowire by name if applicable.
    			if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME) {
    				autowireByName(beanName, mbd, bw, newPvs);
    			}
    
    			// Add property values based on autowire by type if applicable.
    			if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) {
    				autowireByType(beanName, mbd, bw, newPvs);
    			}
    
    			pvs = newPvs;
    		}

    此阶段也有机会产生分叉,特别是实现了InstantiationAwareBeanPostProcessor,提前返回,不再进行后续的autowireByName,autowireByType和属性赋值操作,但并不影响初始化操作
    org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(String beanName, Object bean, RootBeanDefinition mbd),初始化完成

    protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) {
    		if (System.getSecurityManager() != null) {
    			AccessController.doPrivileged(new PrivilegedAction<Object>() {
    				@Override
    				public Object run() {
    					invokeAwareMethods(beanName, bean);
    					return null;
    				}
    			}, getAccessControlContext());
    		}
    		else {
                //实现了后缀名Aware接口的调用在这个阶段,通知感应
                //BeanNameAware
                //BeanClassLoaderAware
                //BeanFactoryAware
    			invokeAwareMethods(beanName, bean);
    		}
    
    		Object wrappedBean = bean;
    		if (mbd == null || !mbd.isSynthetic()) {
                //实现BeanPostProcessor接口
    			wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
    		}
    
    		try {
                //调用初始化方法,判断是否实现了InitializingBean,方法名afterPropertiesSet(),自定义初始化方法也在这里
    			invokeInitMethods(beanName, wrappedBean, mbd);
    		}
    		catch (Throwable ex) {
    			throw new BeanCreationException(
    					(mbd != null ? mbd.getResourceDescription() : null),
    					beanName, "Invocation of init method failed", ex);
    		}
    
    		if (mbd == null || !mbd.isSynthetic()) {
                 //实现BeanPostProcessor接口
    			wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
    		}
    		return wrappedBean;
    	}
    

    10 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(String beanName, ObjectFactory<?> singletonFactory),第二次getSingleton,但实现方法不一样

    org.springframework.beans.factory.support.DefaultSingletonBeanRegistry
    
    
    /** Cache of singleton objects: bean name --> bean instance */
    	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
    
    	/** Cache of singleton factories: bean name --> ObjectFactory */
    	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
    
    	/** Cache of early singleton objects: bean name --> bean instance */
    	private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
    
    	/** Set of registered singletons, containing the bean names in registration order */
    	private final Set<String> registeredSingletons = new LinkedHashSet<>(256);

    注意篇幅有限: 列了一些核心方法,其实还有很多,由于代码量超超多,就不贴了

    工厂型bean

    该Bean实现了FactoryBean接口

    public class UserFactoryBean<User> implements FactoryBean<User> {
    
    	private User user;
    	
    	
    	/**
    	 * @return the user
    	 */
    	public User getUser() {
    		return user;
    	}
    
    	
    	/**
    	 * @param user the user to set
    	 */
    	public void setUser(User user) {
    		this.user = user;
    	}
    
    	
    	/* (non-Javadoc)
    	 * @see org.springframework.beans.factory.FactoryBean#getObjectType()
    	 */
    	@Override
    	public Class<?> getObjectType() {
    		return user.getClass();
    	}
    
    
    	/* (non-Javadoc)
    	 * @see org.springframework.beans.factory.FactoryBean#getObject()
    	 */
    	@Override
    	public User getObject() throws Exception {
    		// TODO Auto-generated method stub
    		return user;
    	}
    
    }

    它和上面没有实现FactoryBean接口的处理方式不太一样,大体上相同不再赘述

    /**
    	 * Obtain an object to expose from the given FactoryBean.
    	 * @param factory the FactoryBean instance
    	 * @param beanName the name of the bean
    	 * @param shouldPostProcess whether the bean is subject to post-processing
    	 * @return the object obtained from the FactoryBean
    	 * @throws BeanCreationException if FactoryBean object creation failed
    	 * @see org.springframework.beans.factory.FactoryBean#getObject()
    	 */
    	protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) {
    		if (factory.isSingleton() && containsSingleton(beanName)) {
    			synchronized (getSingletonMutex()) {
    				Object object = this.factoryBeanObjectCache.get(beanName);
    				if (object == null) {
    					object = doGetObjectFromFactoryBean(factory, beanName);
    					// Only post-process and store if not put there already during getObject() call above
    					// (e.g. because of circular reference processing triggered by custom getBean calls)
    					Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
    					if (alreadyThere != null) {
    						object = alreadyThere;
    					}
    					else {
    						if (object != null && shouldPostProcess) {
    							try {
    								object = postProcessObjectFromFactoryBean(object, beanName);
    							}
    							catch (Throwable ex) {
    								throw new BeanCreationException(beanName,
    										"Post-processing of FactoryBean's singleton object failed", ex);
    							}
    						}
    						this.factoryBeanObjectCache.put(beanName, (object != null ? object : NULL_OBJECT));
    					}
    				}
    				return (object != NULL_OBJECT ? object : null);
    			}
    		}
    		else {
    			Object object = doGetObjectFromFactoryBean(factory, beanName);
    			if (object != null && shouldPostProcess) {
    				try {
    					object = postProcessObjectFromFactoryBean(object, beanName);
    				}
    				catch (Throwable ex) {
    					throw new BeanCreationException(beanName, "Post-processing of FactoryBean's object failed", ex);
    				}
    			}
    			return object;
    		}
    	}

    顺序图:


    3.  bean销毁

    三种体现:

    3.1  和初始化InitializingBean对应的DisposableBean接口,

    3.2  自定义方法,destroy-method="destroyXML"

    3.3  和@PostConstruct对应的 @PreDestroy

    销毁顺序如图


    代码就不举例了。

    总结:其实你可以new User(6,"dongguangmming",99),但和spring没关系,Spring很强大,各个阶段都有机会改造参与bean的过程,组合度也很高,****BeanDefinitionRegistryPostProcessor,***BeanPostProcessor,***BeanFactory,****BeanFactoryPostProcessor

    记住Spring bean:解析注册,实例化,初始化,销毁,才能做扩展性开发或集成第三方组件(比如mybatis,dubbo,email,zk,redis等)到spring的生态圈里

    参考:

    0 Spring 框架简介 https://www.ibm.com/developerworks/cn/java/wa-spring1/

    1. spring bean是什么 https://www.awaimai.com/2596.html

    2  what-in-the-world-are-spring-beans https://stackoverflow.com/questions/17193365/what-in-the-world-are-spring-beans

    3. Spring Bean Lifecycle https://www.benchresources.net/spring-bean-lifecycle/

    4. Detailed tutorial on Redis caching in the SpringBoot series  https://laptrinhx.com/detailed-tutorial-on-redis-caching-in-the-springboot-series-3352915639/

    5. Spring Bean creation process http://www.programmersought.com/article/55942589567/

    6. Inversion of Control Containers and the Dependency Injection pattern https://martinfowler.com/articles/injection.html

  • 相关阅读:
    rabbitmq的笔记(四)小版本的升级操作
    rabbitmq的笔记(三)用Python生产和消费
    rabbitmq的笔记(二)基本命令操作
    rabbitmq的笔记(一)安装
    idea自带的maven 配置阿里云中央仓库
    Maven安装与配置
    Win10下Mysql5.7安装教程
    windows10下Mysql5.7安装指南
    连接mysql出现“Unable to load authentication plugin 'caching_sha2_password”错误
    low code平台建设的一些设计和思考
  • 原文地址:https://www.cnblogs.com/dongguangming/p/12767135.html
Copyright © 2011-2022 走看看