Spring概述

​ 我们常说的 Spring 实际上是指 Spring Framework,而 Spring Framework 只是 Spring 家族中的一个分支而已。Spring 是为了解决企业级应用开发的复杂性而创建的。

​ 如果我们想实现某个功能,代码量一般都是固定的,要么全自己写,要么用已有的优秀框架,而Spring不仅已经给我们提供了各种优秀组件,还提供了良好的代码组织逻辑跟业务开发流程规范框架,我们主要学习Spring中以下几点:

  • IOC/DI
  • AOP
  • 声明式事务
  • JdbcTemplate

Spirng组件

​ Spring框架具有很多组件,大约有20多个模块 。我们要记住以下7个核心组件和它的含义。

  1. Spring Core:Spring核心,它是框架最基础的部分,提供IOC和依赖注入DI特性
  2. Spring Context:Spring上下文容器,它是 BeanFactory 功能加强的一个子接口
  3. Spring Web:它提供Web应用开发的支持。
  4. Spring MVC:它针对Web应用中MVC思想的实现。
  5. Spring DAO:提供对JDBC抽象层,简化了JDBC编码,同时,编码更具有健壮性。
  6. Spring ORM:它支持用于流行的ORM框架的整合,比如:Spring + Hibernate、Spring + iBatis、Spring + JDO的整合等。
  7. Spring AOP:即面向切面编程,它提供了与AOP联盟兼容的编程实现。

​ 下图就是maven导入spring后的组件:

IOC

​ IOC(Inversion of Control),中文叫做控制反转

​ Spring提出了一种思想:由Spring来负责控制对象的生命周期和对象间的关系。所有的类都会在Spring容器中登记,告诉Spring这这个类是什么,需要什么,然后Spring会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的Bean。所有的类的创建、销毁都由Spring来控制,也就是说控制对象生存周期的不再是引用它的对象,而是Spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被spring控制,所以这叫控制反转(Inversion of Controller),也可以叫依赖注入 DI(Dependency Injection)。

​ 举个例子:

public class Book {
private Integer id;
private String name;
private Double price;
//省略getter/setter
}
public class User {
private Integer id;
private String name;
private Integer age; public void doSth() {
Book book = new Book();
book.setId(1);
book.setName("故事新编");
book.setPrice((double) 20);
}
}

​ 上面这个例子中,Book对象的控制权在User里面,Book和User高度耦合,如果在其他对象中需要使用 Book 对象,得重新创建,也就是说,对象的创建、初始化、销毁等操作,统统都要开发者自己来完成。

使用 Spring 之后,我们可以将对象的创建、初始化、销毁等操作交给 Spring 容器来管理。就是说,在项目启动时,所有的 Bean 都将自己注册到 Spring 容器中去(如果有必要的话),然后如果其他 Bean 需要使用到这个 Bean ,则不需要自己去 new,而是直接去 Spring 容器去要。

什么是Bean

​ 我们本篇会一直提到Bean,先在前文给Bean做一个大概的介绍。Spring Bean是被实例的,组装的及被Spring 容器管理的Java对象。Spring 容器会自动完成@bean对象的实例化。创建应用对象之间的协作关系的行为称为:装配(wiring),这就是依赖注入的本质。

何为控制

​ 是 bean 的创建、管理的权利,控制 bean 的整个生命周期。

何为反转

​ 把这个权利交给了 Spring 容器,而不是自己去控制,就是反转。由之前的自己主动创建对象,变成现在被动接收别人给我们的对象的过程,这就是反转。

何为依赖

​ 程序运行需要依赖外部的资源,提供程序内对象的所需要的数据、资源。

何为注入

​ 配置文件把资源从外部注入到内部,容器加载了外部的文件、对象、数据,然后把这些资源注入给程序内的对象,维护了程序内外对象之间的依赖关系。

实例1:进一步了解IOC

​ 我们在IDEA创建一个普通Maven项目,然后再pom文件中引入spring-context 依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId>
<artifactId>SpringDemo</artifactId>
<version>1.0-SNAPSHOT</version> <dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
</dependencies> </project>

​ 然后在 resources 目录下创建一个 spring 的配置文件spring.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"> </beans>

​ 文件中我们可以配置bean,把我们的book配置进去

<?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 class="org.javaboy.Book" id="book"/>
</beans>

说明:class 属性表示需要注册的 bean 的全路径,id 则表示 bean 的唯一标记,也开可以 name 属性作为 bean 的标记,在超过 99% 的情况下,id 和 name 其实是一样的。

​ 最后,我们来测试一下,创建一个Main方法来加载这个配置文件,通过 getBean 方法,从容器中去获取对象:

public class Main {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Book book = (Book) ctx.getBean("book");
System.out.println(book);
}
}

​ 打印的结果为:Book{id=null, name='null', price=null}

说明:上面getBean中传入的是我们命名的name或id,这种方式好处是我们给它起了个名,在引入两个或多个相同对象时,有别名不至于混淆。有些教程喜欢在getBean中直接通过Class 去获取一个 Bean,如传入Book.class,如果spring.xml中引入两个book的bean,那么这种做法就会报错,所以我个人不推荐通过Class去获取Bean。

name与id之间的一些注意点

​ 上文中提到了name与id,确实,大部分时候name和id是一样的,也很少在开发中又用name又用id,但是还是要注意一些细节。

  • 配置两个相同的 id 或者 name 都不能通过。
  • 如果既配置了 id ,也配置了 name ,则两个都生效。如果id和name都没有指定,则用类全名作为name,如<bean class="com.stamen.BeanLifeCycleImpl">,则你可以通过getBean("com.stamen.BeanLifeCycleImpl")返回该实例。
  • 如果配置基本类的时候,注解和配置文件都使用的时候,注解和配置文件中 name 相同的时候, 则两个冲突,配置文件生效; 如果配置基本类的时候,注解和配置文件都使用的时候,注解和配置文件中 name 不相同的时候, 则两个不冲突,都能够生效。

属性注入

​ 上一个例子中我们明白了Spirng的IOC可以帮我们管理Bean,将对象的创建、初始化、销毁等操作交给Spring管理,使开发更加方便。此外,上一个例子中我们在spring.xml中配置了Book,最后打印的结果中我们看到Book中的属性并没有值,是null。这一小节就是了解spring是如何注入属性的,有以下几种方式:

  • 构造方法注入
  • set方法注入
  • p命名空间注入
  • 自动装配和@Autowired注解注入
  • 静态工厂注入
  • 实例工厂注入

构造方法注入

  1. 给Book添加一个有参和无参构造方法:

  2. 在xml中注入bean

    <?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 class="com.wms.Demo01.Book" id="book">
    <constructor-arg index="0" value="1"/>
    <constructor-arg index="1" value="宇宙未解之谜"/>
    <constructor-arg index="2" value="39.9"/>
    </bean>
    </beans>

    或者使用name标签,更加清晰

    <bean class="com.wms.Demo01.Book" id="book">
    <constructor-arg name="id" value="1"/>
    <constructor-arg name="name" value="宇宙未解之谜"/>
    <constructor-arg name="price" value="39.9"/>
    </bean>
  3. 说明:

  • constructor-arg --> 指定构造函数的参数
  • index --> 参数的顺序
  • name --> 参数的名称
  • value --> 给参数赋值
  • 注意,你也可以按index2, 0, 1等顺序来给属性注入值,但记得 index 的值和 value 要一一对应,id就要注入id相符的值。

set方法注入

​ set方法注入方式如下:

<bean class="com.wms.Demo01.Book" id="book">
<property name="id" value="1"/>
<property name="name" value="宇宙未解之谜"/>
<property name="price" value="39.9"/>
</bean>

​ 注意这里使用的是property标签,而且name并不是Book这个对象中定义的属性,而是通过get/set方法分析出来的属性名,只是我们规范get/set方法就是get/set + 属性名。

p命名空间注入

​ p 名称空间注入,使用的比较少,它本质上也是调用了 set 方法。

<bean class="com.wms.Demo01.Book" id="book" p:id="1" p:bookName="宇宙未解之谜" p:price="39.9"></bean>

自动装配和@Autowired注解注入

​ 我将这两个放在一起讲,因为要介绍到byName和byTpye的区别。Spring提供了自动装配的功能,简化了我们的配置,自动装配默认是不打开的,常用的方式有两种,byName和byType。方式是在bean标签中加入autowire="byName"autowire="byType"

<bean class="com.wms.Demo01.Book" id="book" autowire="byType"/>

​ 后续我们还会接触到@Autowired注解,@Autowired注解可以实现自动装配,只要在对应的属性上标记该注解,但是@Autowired注解只按照byType注入。这个注解常见于Controller层。如下:

public class UserController {

    @Autowired
private IUserService userService;
}

​ 这里我们主要还是熟悉byName和byType的区别。其实byName根据被注入的名称作为bean名称作为依赖查找,并将对象设置到该属性。byType通过属性的类型查找javabean依赖的对象并为其注入。

byName和byType

  • byName按名称自动装配,当一个bean节点带有 autowire byName的属性时。

    • 将查找其类中所有的set方法名,例如setCat,获得将set去掉并且首字母小写的字符串,即cat。
    • 去spring容器中寻找是否有此字符串名称id的对象。
    • 如果有,就取出注入;如果没有,就报空指针异常。
  • byType按类型自动装配,使用autowire byType首先需要保证:同一类型的对象,在spring容器中唯一。如果不唯一,会报不唯一的异常。

@Autowired、@Qualifier和@Resource

​ 既然讲到了@Autowired,就展开讲一下一些相关的注解。(下面出现的代码来自狂神说的spring教程)

  • @Autowired

    • @Autowired是按类型自动转配的,不支持id匹配。
    • 需要导入 spring-aop的包!
    • @Autowired(required=false) 说明:false,对象可以为null;true,对象必须存对象,不能为null。
  • @Qualifier

    • @Autowired是根据类型自动装配的,加上@Qualifier则可以根据byName的方式自动装配。

    • @Qualifier不能单独使用。

      • 在属性上添加Qualifier注解

      • 示例:

        @Autowired
        @Qualifier(value = "cat2")
        private Cat cat;
        @Autowired
        @Qualifier(value = "dog2")
        private Dog dog;
  • @Resource

    • @Resource如有指定的name属性,先按该属性进行byName方式查找装配;
    • 其次再进行默认的byName方式进行装配;
    • 如果以上都不成功,则按byType的方式自动装配。
    • 都不成功,则报异常。

实体类:

public class User {
//如果允许对象为null,设置required = false,默认为true
@Resource(name = "cat2")
private Cat cat;
@Resource
private Dog dog;
private String str;
}

beans.xml

<bean id="dog" class="com.kuang.pojo.Dog"/>
<bean id="cat1" class="com.kuang.pojo.Cat"/>
<bean id="cat2" class="com.kuang.pojo.Cat"/> <bean id="user" class="com.kuang.pojo.User"/>

测试:结果OK

配置文件2:beans.xml , 删掉cat2

<bean id="dog" class="com.kuang.pojo.Dog"/>
<bean id="cat1" class="com.kuang.pojo.Cat"/>

实体类上只保留注解

@Resource
private Cat cat;
@Resource
private Dog dog;

结果:OK

结论:先进行byName查找,失败;再进行byType查找,成功。

@Autowired与@Resource异同:

1、@Autowired与@Resource都可以用来装配bean。都可以写在字段上,或写在setter方法上。

2、@Autowired默认按类型装配(属于spring规范),默认情况下必须要求依赖对象必须存在,如果要允许null 值,可以设置它的required属性为false,如:@Autowired(required=false) ,如果我们想使用名称装配可以结合@Qualifier注解进行使用

3、@Resource(属于J2EE复返),默认按照名称进行装配,名称可以通过name属性进行指定。如果没有指定name属性,当注解写在字段上时,默认取字段名进行按照名称查找,如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。

静态工厂注入(TODO)

实例工厂注入(TODO)

更多属性注入方式

​ 上述的属性注入方式是比较基础的注入,实际开发中遇到的问题往往更加复杂,比如在开发过程中可能会注入跟多类型的数据,如:

  • 对象
  • 数组
  • Map

对象注入

可以通过 ref 来引用一个对象。

<bean class="com.wms.Demo01.User" id="user">
<property name="cat" ref="cat"/>
</bean>
<bean class="org.javaboy.Cat" id="cat">
<property name="name" value="小白"/>
<property name="color" value="白色"/>
</bean>

数组注入

  • array

    <bean class="com.wms.Demo01.User" id="user">
    <property name="cat" ref="cat"/>
    <property name="favorites">
    <array>
    <value>足球</value>
    <value>篮球</value>
    <value>乒乓球</value>
    </array>
    </property>
    </bean>
    <bean class="com.wms.Demo01.Cat" id="cat">
    <property name="name" value="小白"/>
    <property name="color" value="白色"/>
    </bean>
  • list

    • array 节点,也可以被 list 节点代替。

    • array 或者 list 节点中也可以是对象。

    • 即可以通过 ref 使用外部定义好的 Bean,也可以直接在 list 或者 array 节点中定义 bean。

      <bean class="com.wms.Demo01.User" id="user">
      <property name="cat" ref="cat"/>
      <property name="favorites">
      <list>
      <value>足球</value>
      <value>篮球</value>
      <value>乒乓球</value>
      </list>
      </property>
      <property name="cats">
      <list>
      <ref bean="cat"/>
      <ref bean="cat2"/>
      <bean class="com.wms.Demo01.Cat" id="cat3">
      <property name="name" value="小花"/>
      <property name="color" value="花色"/>
      </bean>
      </list>
      </property>
      </bean>
      <bean class="com.wms.Demo01.Cat" id="cat">
      <property name="name" value="小白"/>
      <property name="color" value="白色"/>
      </bean>
      <bean class="com.wms.Demo01.Cat" id="cat2">
      <property name="name" value="小黑"/>
      <property name="color" value="黑色"/>
      </bean>

Map 注入

<property name="map">
<map>
<entry key="age" value="100"/>
<entry key="name" value="abc"/>
</map>
</property>

Properties 注入

<property name="info">
<props>
<prop key="age">100</prop>
<prop key="name">abc</prop>
</props>
</property>

补充

  • 如果炫技,上面的都可以说。
  • 如果问Spring对象创建方式,要说到构造方法、静态工厂、实例工厂
  • 如果问到Spring注入方式,要说到构造方法、set方法、自动注入、p命名空间

Context

IOC 容器只是提供一个管理对象的空间而已,如何向容器中放入我们需要容器代为管理的对象呢?这就涉及到Spring的应用上下文Context

​ 工作中通过XML配置或注解 将需要管理的Bean跟Bean之间的协作关系配置好,然后利用应用上下文对象Context加载进Spring容器,容器就能为你的程序提供你想要的对象管理服务了。Spring 框架本身就提供了很多个容器的实现。我们在实例1中的Main中出现了ClassPathXmlApplicationContext,就是一种容器,还有很多容器,如下:

  1. AnnotationConfigApplicationContext:从一个或多个基于java的配置类中加载上下文定义,适用于java注解的方式。
  2. ClassPathXmlApplicationContext:从类路径下的一个或多个xml配置文件中加载上下文定义,适用于xml配置的方式。
  3. FileSystemXmlApplicationContext:从文件系统下的一个或多个xml配置文件中加载上下文定义,也就是说系统盘符中加载xml配置文件。
  4. AnnotationConfigWebApplicationContext:专门为web应用准备的,适用于注解方式。
  5. XmlWebApplicationContext:从web应用下的一个或多个xml配置文件加载上下文定义,适用于xml配置方式。

​ 比如ClassPathXmlApplicationContext,来自于我们常提到的ApplicationContext,但如果你点开源码看,就知道ClassPathXmlApplicationContext并不是直接实现ApplicationContext的,而是一层一层的递进,这是为了IOC全面性而考虑。此处的ApplicationContext也是面试中经常出现的问题,经常与BeanFactory一起作比较。

ApplicationContext & BeanFactory区别

BeanFactory接口

  • spring的原始接口,针对原始接口的实现类功能较为单一, 可以理解为 HashMap:它一般只有 get, put 两个功能。

    • Key - bean name
    • Value - bean object
  • BeanFactory接口实现类的容器,特点是每次在获得对象时才会创建对象
  • 优缺点:
    • 优点:应用启动的时候占用资源很少,对资源要求较高的应用,比较有优势;
    • 缺点:运行速度会相对来说慢一些。而且有可能会出现空指针异常的错误,而且通过Bean工厂创建的Bean生命周期会简单一些。

ApplicationContext接口

  • 每次容器启动时就会创建容器中配置的所有对象
  • 它是 BeanFactory 的子类,更好的补充并实现了 BeanFactory.ApplicationContext 多了很多功能,因为它继承了多个接口。 ApplicationContext 的里面有两个具体的实现子类,用来读取配置配件的,上面列举了5个。
  • 优缺点:
    • 优点:所有的Bean在启动的时候都进行了加载,系统运行的速度快;在系统启动的时候,可以发现系统中的配置问题。
    • 缺点:把费时的操作放到系统启动中完成,所有的对象都可以预加载,缺点就是内存占用较大。

BeanFactory & FactoryBean的区别

​ 既然提到了BeanFactory,面试中往往会用FactoryBean来“坑”面试者,这里就讲一下两者的区别。

  • BeanFactory

    • BeanFactory 以 Factory 结尾,表示它是一个工厂类(接口),BeanFacotry 是 Spring 中比较原始的Factory。
    • BeanFactory 无法支持 Spring 的许多插件,如AOP功能、Web应用等。ApplicationContext 接口由BeanFactory接口派生而来,提供了国际化访问、事件传播等多个功能。
    • BeanFactory 是 IOC 容器的核心,负责生产和管理 Bean 对象。
  • FactoryBean
    • FactoryBean 以 Bean 结尾,表示它是一个Bean。
    • FactoryBean 是工厂类接口,用户可以通过实现该接口定制实例化 Bean 的逻辑。FactoryBean 接口对于 Spring 框架来说占用重要的地位,Spring自身就提供了70多个FactoryBean的实现。
    • 当在IOC容器中的Bean实现了 FactoryBean 后,通过getBean(String BeanName)获取到的 Bean 对象并不是 FactoryBean 的实现类对象,而是这个实现类中的 getObject() 方法返回的对象。要想获取FactoryBean的实现类,就要 getBean(String &BeanName),在BeanName之前加上 &

循环依赖

​ 从字面上来理解就是A依赖B的同时B也依赖了A,例如

自动化配置

​ 例如我有一个 UserService,我希望在自动化扫描时,这个类能够自动注册到 Spring 容器中去,那么可以给该类添加一个 @Service,作为一个标记。

​ 和 @Service 注解功能类似的注解,一共有四个:

  • @Component

  • @Repository

  • @Service

  • @Controller

    ​ 这四个中,另外三个都是基于 @Component 做出来的,而且从目前的源码来看,功能也是一致的,那么为什么要搞三个呢?主要是为了在不同的类上面添加时方便。

  • 在 Service 层上,添加注解时,使用 @Service

  • 在 Dao 层,添加注解时,使用 @Repository

  • 在 Controller 层,添加注解时,使用 @Controller

  • 在其他组件上添加注解时,使用 @Component

    ​ 添加完成后,自动化扫描有两种方式,一种就是通过 Java 代码配置自动化扫描,另一种则是通过 xml 文件来配置自动化扫描。

Java 代码配置自动扫描

​ 在项目启动中加载配置类,在配置类中,通过 @ComponentScan 注解指定要扫描的包(如果不指定,默认情况下扫描的是配置类所在的包下载的 Bean 以及配置类所在的包下的子包下的类)

XML 配置自动化扫描

<context:component-scan base-package="org.javaboy.javaconfig"/>

​ 上面这行配置表示扫描 org.javaboy.javaconfig 下的所有 Bean。当然也可以按照类来扫描。

Bean的生命周期和作用域

​ 这两个问题也是面试中常客

Bean的生命周期

​ Spring IOC 初始化跟销毁 Bean 的过程大致分为Bean定义、Bean初始化、Bean的生存期 跟 Bean的销毁4个部分。流程图如下:

浓缩一下:

Bean的生命周期,从Spring容器的创建开始,到Spring容器销毁结束。

  1. ​ 实例化Bean对象
  2. ​ 装配:填充属性
  3. ​ 回调:(可选,如果实现了Aware系列的接口,则会调用回调函数)
  4. ​ 调用预初始化方法(可选,如果实现了BeanPost-Processor的预初始化方法)
  5. ​ 初始化(init-method)
  6. ​ 调用预初始化后置方法(如果实现了BeanPost-Processor的初始化后方法)
  7. ​ 使用bean
  8. ​ 容器关闭
  9. ​ 如果实现了DisposableBean接口,则调用该方法的destory()方法。
  10. ​ 调用自定义的destory方法。

Bean的作用域

​ 在 XML 配置中注册的 Bean,或者用 Java 配置注册的 Bean,如果我多次获取,获取到的对象是否是同一个?

​ 答案是是,因为Spring中Bean默认是单例的,所以多次获取的Bean都是同一个。这里就涉及到Bean的作用域的知识点,

​ 四种常见的 Spring Bean 的作用域:

  • singleton : 唯一 bean 实例,Spring 中的 bean 默认都是单例的。

  • prototype : 每次请求都会创建一个新的 bean 实例。

  • request : 每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP request 内有效。

  • session : 每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP session 内有效。

    怎么更改作用域?

    xml中:

    <bean class="org.javaboy.User" id="user" scope="prototype" />

    注解:

    @Repository
    @Scope("prototype")
    public class UserDao {
    public String hello() {
    return "userdao";
    }
    }

最新文章

  1. HMAC加密的消息摘要码
  2. python logging usage
  3. Quartz 线程处理
  4. Android-调用优酷SDK上传视频
  5. mysql.zip免安装版配置
  6. MSP430 中断优先级
  7. 在Android模拟器中经常出现以下错误 timeout Launch canceled!
  8. 接收Dialog的值
  9. 3.PHP 教程_PHP 语法
  10. dpkg: error processing mysql-server (--configure): dependency problems - leaving unconfigured
  11. jquery实现简单的搜索
  12. 洛谷P1593 因子和
  13. mysql的sql_mode设置
  14. sqlmap的安装
  15. 在python里调用java的py4j的使用方法
  16. vue里的样式添加之行间样式
  17. spring cloud 配置文件application.yml和bootstrap.yml 的定位,区别和联系总算是有一点明白了
  18. 配置React的Babel 6和Webpack 2环境
  19. vue2打包时内存溢出解决方案
  20. c++并发编程之互斥锁(mutex)的使用方法

热门文章

  1. 基于Neptune开发板的键盘蓝牙模块DIY指南
  2. C++ 三消游戏基本实现
  3. IntelliJ IDEA打开Maven项目,Spring boot所有依赖红名,不可用
  4. svn客户端访问失败,错误“请求的名称有效,但是找不到请求的类型”的解决
  5. Java线程的6种状态
  6. Azure Storage 利用 azCopy 复制迁移数据
  7. python分析《三国演义》,谁才是这部书的绝对主角(包含统计指定角色的方法)
  8. Java堆的理解
  9. 并发王者课 - 青铜4:synchronized用法初体验
  10. 【MySQL】MySQL-front等客户端连接MySQL_8.0等失败的解决办法