zoukankan      html  css  js  c++  java
  • 超详细的阿里字节Spring面试技术点总结(建议收藏)

    前言

    Spring作为现在最流行Java开发技术,其内部源码设计非常优秀。

    Spring这个词对于Java开发者想必不会陌生,可能你每天都在使用Spring,享受着Spring生态提供的服务。现在很多互联网公司都把Spring作为招聘面试其中最重要的知识点之一来考核。

    毫不夸张的说,Java程序员想要进一线大厂,Spring是必须要掌握的。

    文末有福利~


    做程序员难,做一个2020年的程序员更难,随着IT人员越来越多,我们的竞争压力也越来越大,想要在茫茫人海中脱颖而出,其实考察的就是我们技术栈的广度和深度。

    一般Spring面试的哲学三问,是什么?为什么?怎么用?

    Spring底层到底要看什么?以下是总结的spring核心知识点,给大家分享一下,希望可以对你掌握Spring有所帮助。

    先分享一个Spring知识点思维导图给大家

    一、Spring框架功能整体介绍

    Sring Core Container
    模块作用:Core 和 Beans模块是框架的基础部分,提供 IoC (反转控制)和依赖注入特性。 这里的基础概念是 BeanFactory,它提供对Factory模式的经典实现来消除对程序性单例模式的需要,并真正地允许你从程序逻辑中分离出依赖关系和配置

    1. Core

    主要包含 Spring 框架基本的核心工具类, Spring 的其他组件都要用到这个包里的类, Core模块是其他组件的基本核心。

    2. Beans (BeanFacotry的作用)

    它包含访问配置文件、创建和管理 bean 以及进行 Inversion of Control | Dependency
    Injection ( IoC/DI )操作相关的所有类

    3.Context

    处理BeanFactory,还是ApplicationContext的作用。
    模块构建于 Core 和 Beans 模块基础之上,提供了一种类似JNDI注册器的框架式的对象访问方法。Context 模块继承了Beans的特性,为Spring核心提供了大量扩展,添加了对国际化(例如资源绑定)、事件传播、资源加载和对 Context 的透明创 建的支持。Context 模块同时也支持J2EE的一些特性,ApplicationContext接口是Context模块的关键。
    本质区别:(使用BeanFacotry的bean是延时加载的,ApplicationContext是非延时加载的)

    4.Expression Language

    模块提供了强大的表达式语言,用于在运行时查询和操纵对象。它是JSP 2.1规范中定义的 unifed expression language 的扩展。该语言支持设置/获取属性的值,属性的分配,方法的调用,访问数组上下文( accessiong the context of arrays )、容器和索引器、逻辑和算术运算符、命名变量以及从Spring的IoC容器中根据名称检索对象。它也支持list 投影、选择和一般的list聚合。
     

    二、Spring IOC容器底层注解使用

    xml配置文件的形式 VS 配置类的形式

    1.基于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的信息 -->
    <bean id="car" class="com.demo.compent.Car"></bean> 
    </beans>
    去容器中读取Bean
    public static void main( String[] args ) 
    { 
    ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml"); 
    System.out.println(ctx.getBean("person")); 
    } 

    2.基于读取配置类的形式定义Bean信息

    @Configuration 
    public class MainConfig { 
    @Bean 
    public Person person(){ 
    return new Person(); 
    } 
    } 
    去容器中读取Bean的信息(传入配置类)
    public static void main( String[] args ) 
    { 
    AnnotationConfigApplicationContext ctx=new AnnotationConfigApplicationContext(MainConfig.class); 
    System.out.println(ctx.getBean("person")); 
    } 
     

    三、Spring Ioc容器源码解析

     IOC 容器启动的核心流程

     

    i0:>org.springframework.context.support.AbstractApplicationContext#refresh

    IOC容器刷新流程

     

    i1>org.springframework.context.support.AbstractApplicationContext#prepareRefresh

     

    准备刷新容器上下文prepareRefresh();

     

    i2> ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

     

    初始化容器

     

    i3>org.springframework.context.support.AbstractApplicationContext#prepareBeanFactory

     

    对IOC容器注册系统默认组件

     

    i4>org.springframework.context.support.AbstractApplicationContext#postProcessBeanFactory

    子类覆盖做额外处理

     

    i5>org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcess

    调用bean工程的后置处理器

     

    i6>org.springframework.context.support.AbstractApplicationContext#registerBeanPostProcessors

    注册bean的后置处理器

     

    i7>org.springframework.context.support.AbstractApplicationContext#initMessageSource

     

    初始化国际化资源

     

    i8>org.springframework.context.support.AbstractApplicationContext#initApplicationEventMulticaster

    注册事件多播器

     

    i9>org.springframework.context.support.AbstractApplicationContext#onRefresh

     

    留给子类实现,springboot就是从这里启动

     

    i10>org.springframework.context.support.AbstractApplicationContext#registerListeners

     

    把事件监听器注册到多播器上

     

    i11>org.springframework.context.support.AbstractApplicationContext#finishBeanFactoryInitialization

    创建bean实例

     

    i12:org.springframework.context.support.AbstractApplicationContext#finishRefresh

     

    容器刷新完成,eureka就是从这里启动

     

     
     

    四、Spring 是如何解决循环依赖的

    什么是循环依赖?
    所谓的循环依赖就是A依赖B,B依赖A,或者是A依赖B,B依赖C,C依赖A

    1.代码实例:

    //getter/setter
    public class InstanceA {
     private InstanceB instanceB;
    }
    public class InstanceB {
    private InstanceA instanceA;
    }
    
    <bean id="instanceA" class="com.tuling.circulardependencies.InstanceA">
    <property name="instanceB" ref="intanceB"></property>
    </bean>
    <bean id="intanceB" class="com.tuling.circulardependencies.InstanceB">
    <property name="instanceA" ref="instanceA"></property>
    </bean>
    
    

    2.可能存在的问题:

    IOC容器在创建Bean的时候,按照顺序,先去实例化instanceA。然后突然发现我的instanceA是依赖我 的instanceB的;
    那么IOC容器接着去实例化intanceB,那么在intanceB的时候发现依赖instanceA。若容器不处理的话,那么IOC将无限的执行上述流程,直到内存异常程序奔溃.

    3.解决方案:

    当然,Spring是不会让这种情况发生的。在容器发现 beanB 依赖于 beanA 时,容器会获取 beanA对象的一个早期的引用(early reference),并把这个早期引用注入到 beanB 中,让 beanB 先完成实例化。beanB 完成实例化,beanA 就可以获取到 beanB 的引用,beanA 随之完成实例化。这里大家可能不知道“早期引用”是什么意思,这里先别着急...
     

    五、Spring Aop源码分析

    AOP核心概念

    1.横切关注点(对哪些方法进行切入)

    对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点

    2.切面(aspect,把原来糅杂在业务逻辑代码中的非业务代码抽取出来,把功能 相同的放在一个类中形成一个切面)

    类是对物体特征的抽象,切面就是对横切关注点的抽象

    3.连接点(joinpoint)(需要切入的点)

    被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指 的就是被拦截到的方法,实际上连接点还可以是字段或者构造器

    4.切入点(pointcut)

    对连接点进行拦截的定义

    5.通知(advice)

    所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异 常、最终、环绕通知五类

    6.目标对象

    代理的目标对象

    7.织入(weave)

    将切面应用到目标对象并导致代理对象创建的过程

    8.引入(introduction)

    在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段

    简单案例

     

     

     

    public interface Calculate {	/**	 * 加法	 * @param numA	 * @param numB	 * @return	 */	int add(int numA,int numB); 	/**	 * 减法	 * @param numA	 * @param numB	 * @return	 */	int reduce(int numA,int numB); 	/**	 * 除法	 * @param numA	 * @param numB	 * @return	 */	int div(int numA,int numB); 	/**	 * 乘法	 * @param numA	 * @param numB	 * @return	 */	int multi(int numA,int numB);}

     

    ==========实现类

     

     

     

     

    public class TulingCalculate implements Calculate { 	public int add(int numA, int numB) { 		return numA+numB;	} 	public int reduce(int numA, int numB) {		return numA-numB;	} 	public int div(int numA, int numB) {		return numA/numB;	} 	public int multi(int numA, int numB) {		return numA*numB;	}}

     

    =========切面类

     

     

     

     

     

     

    @Aspectpublic class TulingLogAspect { 	@Pointcut("execution(* com.tuling.TulingCalculate.*(..))")	public void pointCut(){}; 	@Before(value = "pointCut()")	public void methodBefore(JoinPoint joinPoint){		String methodName = joinPoint.getSignature().getName(); System.out.println("执行目标方法【"+methodName+"】之前执行<前置通知>,入参"+ Arrays.asList(joinPoint.getArgs()));	} 	@After(value = "pointCut()")	public void methodAfter(JoinPoint joinPoint) {		String methodName = joinPoint.getSignature().getName(); System.out.println("执行目标方法【"+methodName+"】之前执行<后置通知>,入参"+Arrays.asList(joinPoint.getArgs()));	} 	@AfterReturning(value = "pointCut()")	public void methodReturning(JoinPoint joinPoint ) {		String methodName = joinPoint.getSignature().getName(); System.out.println("执行目标方法【"+methodName+"】之前执行<返回通知>,入参"+Arrays.asList(joinPoint.getArgs()));	} 	@AfterThrowing(value = "pointCut()")	public void methodAfterThrowing(JoinPoint joinPoint) {		String methodName = joinPoint.getSignature().getName(); System.out.println("执行目标方法【"+methodName+"】之前执行<异常通知>,入参"+Arrays.asList(joinPoint.getArgs()));	}}

     

    ==========配置类

     

     

     

     

     

     

    @Configuration@EnableAspectJAutoProxy public class MainConfig { 	@Bean	public Calculate calculate() {		return new TulingCalculate();	} 	@Bean	public TulingLogAspect tulingLogAspect() {		return new TulingLogAspect();	}}

     

     

    六、Spring 事务源码解析

    事务概念解析

    1.什么是事物?

    事务是逻辑上的一组执行单元,要么都执行,要么都不执行.

    2. 事物的特性(ACID)

    什么是ACID?

    ACID是指数据库管理系统DBMS中事务所具有四个特性
    eg:在数据库系统中,一个事务由一系列的数据库操作组成一个完整的逻辑过程,比如银行转账,从原账户扣除金额,目标账户增加金额

    (1)atomicity【原子性】

    原子性表现为操作不能被分割,那么这两个操作要么同时完成,要么就全部不完成,若事务出错了,那么事务就会回滚,好像什么都没有发生过

    (2)Consistency【一致性】

    一致性也比较容易理解,也就是说数据库要一直处于一致的状态,事务开始前是一个一致状态,事务结束后是另一个一致状态,事务将数据库从一个一致状态转移到另一个一致状态

    (3)Isolation【隔离性】

    所谓的独立性就是指并发的事务之间不会互相影响,如果一个事务要访问的数据正在被另外一个事务修改,只要另外一个事务还未提交,它所访问的数据就不受未提交事务的影响。换句话说,一个事务的影响在该事务提交前对其它事务是不可见的

    (4)Durability【持久性】

    若事务已经提交了,那么就回在数据库中永久的保存下来

    篇幅有限,其他内容就不在这里一一展示了,这份Spring核心知识点一共176页PDF文档

    关注公众号:程序员追风,回复 008 获取这份Spring核心知识点总结

    最后

    欢迎大家一起交流,喜欢文章记得关注我点个赞,感谢支持!

     

    前言

    Spring作为现在最流行Java开发技术,其内部源码设计非常优秀。

    Spring这个词对于Java开发者想必不会陌生,可能你每天都在使用Spring,享受着Spring生态提供的服务。现在很多互联网公司都把Spring作为招聘面试其中最重要的知识点之一来考核。

    毫不夸张的说,Java程序员想要进一线大厂,Spring是必须要掌握的。

    文末有福利~


    做程序员难,做一个2020年的程序员更难,随着IT人员越来越多,我们的竞争压力也越来越大,想要在茫茫人海中脱颖而出,其实考察的就是我们技术栈的广度和深度。

    一般Spring面试的哲学三问,是什么?为什么?怎么用?

    Spring底层到底要看什么?以下是总结的spring核心知识点,给大家分享一下,希望可以对你掌握Spring有所帮助。

    先分享一个Spring知识点思维导图给大家

    一、Spring框架功能整体介绍

    Sring Core Container
    模块作用:Core 和 Beans模块是框架的基础部分,提供 IoC (反转控制)和依赖注入特性。 这里的基础概念是 BeanFactory,它提供对Factory模式的经典实现来消除对程序性单例模式的需要,并真正地允许你从程序逻辑中分离出依赖关系和配置

    1. Core

    主要包含 Spring 框架基本的核心工具类, Spring 的其他组件都要用到这个包里的类, Core模块是其他组件的基本核心。

    2. Beans (BeanFacotry的作用)

    它包含访问配置文件、创建和管理 bean 以及进行 Inversion of Control | Dependency
    Injection ( IoC/DI )操作相关的所有类

    3.Context

    处理BeanFactory,还是ApplicationContext的作用。
    模块构建于 Core 和 Beans 模块基础之上,提供了一种类似JNDI注册器的框架式的对象访问方法。Context 模块继承了Beans的特性,为Spring核心提供了大量扩展,添加了对国际化(例如资源绑定)、事件传播、资源加载和对 Context 的透明创 建的支持。Context 模块同时也支持J2EE的一些特性,ApplicationContext接口是Context模块的关键。
    本质区别:(使用BeanFacotry的bean是延时加载的,ApplicationContext是非延时加载的)

    4.Expression Language

    模块提供了强大的表达式语言,用于在运行时查询和操纵对象。它是JSP 2.1规范中定义的 unifed expression language 的扩展。该语言支持设置/获取属性的值,属性的分配,方法的调用,访问数组上下文( accessiong the context of arrays )、容器和索引器、逻辑和算术运算符、命名变量以及从Spring的IoC容器中根据名称检索对象。它也支持list 投影、选择和一般的list聚合。
     

    二、Spring IOC容器底层注解使用

    xml配置文件的形式 VS 配置类的形式

    1.基于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的信息 -->
    <bean id="car" class="com.demo.compent.Car"></bean> 
    </beans>
    去容器中读取Bean
    public static void main( String[] args ) 
    { 
    ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml"); 
    System.out.println(ctx.getBean("person")); 
    } 

    2.基于读取配置类的形式定义Bean信息

    @Configuration 
    public class MainConfig { 
    @Bean 
    public Person person(){ 
    return new Person(); 
    } 
    } 
    去容器中读取Bean的信息(传入配置类)
    public static void main( String[] args ) 
    { 
    AnnotationConfigApplicationContext ctx=new AnnotationConfigApplicationContext(MainConfig.class); 
    System.out.println(ctx.getBean("person")); 
    } 
     

    三、Spring Ioc容器源码解析

     IOC 容器启动的核心流程

     

    i0:>org.springframework.context.support.AbstractApplicationContext#refresh

    IOC容器刷新流程

     

    i1>org.springframework.context.support.AbstractApplicationContext#prepareRefresh

     

    准备刷新容器上下文prepareRefresh();

     

    i2> ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

     

    初始化容器

     

    i3>org.springframework.context.support.AbstractApplicationContext#prepareBeanFactory

     

    对IOC容器注册系统默认组件

     

    i4>org.springframework.context.support.AbstractApplicationContext#postProcessBeanFactory

    子类覆盖做额外处理

     

    i5>org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcess

    调用bean工程的后置处理器

     

    i6>org.springframework.context.support.AbstractApplicationContext#registerBeanPostProcessors

    注册bean的后置处理器

     

    i7>org.springframework.context.support.AbstractApplicationContext#initMessageSource

     

    初始化国际化资源

     

    i8>org.springframework.context.support.AbstractApplicationContext#initApplicationEventMulticaster

    注册事件多播器

     

    i9>org.springframework.context.support.AbstractApplicationContext#onRefresh

     

    留给子类实现,springboot就是从这里启动

     

    i10>org.springframework.context.support.AbstractApplicationContext#registerListeners

     

    把事件监听器注册到多播器上

     

    i11>org.springframework.context.support.AbstractApplicationContext#finishBeanFactoryInitialization

    创建bean实例

     

    i12:org.springframework.context.support.AbstractApplicationContext#finishRefresh

     

    容器刷新完成,eureka就是从这里启动

     

     
     

    四、Spring 是如何解决循环依赖的

    什么是循环依赖?
    所谓的循环依赖就是A依赖B,B依赖A,或者是A依赖B,B依赖C,C依赖A

    1.代码实例:

    //getter/setter
    public class InstanceA {
     private InstanceB instanceB;
    }
    public class InstanceB {
    private InstanceA instanceA;
    }
    
    <bean id="instanceA" class="com.tuling.circulardependencies.InstanceA">
    <property name="instanceB" ref="intanceB"></property>
    </bean>
    <bean id="intanceB" class="com.tuling.circulardependencies.InstanceB">
    <property name="instanceA" ref="instanceA"></property>
    </bean>
    
    

    2.可能存在的问题:

    IOC容器在创建Bean的时候,按照顺序,先去实例化instanceA。然后突然发现我的instanceA是依赖我 的instanceB的;
    那么IOC容器接着去实例化intanceB,那么在intanceB的时候发现依赖instanceA。若容器不处理的话,那么IOC将无限的执行上述流程,直到内存异常程序奔溃.

    3.解决方案:

    当然,Spring是不会让这种情况发生的。在容器发现 beanB 依赖于 beanA 时,容器会获取 beanA对象的一个早期的引用(early reference),并把这个早期引用注入到 beanB 中,让 beanB 先完成实例化。beanB 完成实例化,beanA 就可以获取到 beanB 的引用,beanA 随之完成实例化。这里大家可能不知道“早期引用”是什么意思,这里先别着急...
     

    五、Spring Aop源码分析

    AOP核心概念

    1.横切关注点(对哪些方法进行切入)

    对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点

    2.切面(aspect,把原来糅杂在业务逻辑代码中的非业务代码抽取出来,把功能 相同的放在一个类中形成一个切面)

    类是对物体特征的抽象,切面就是对横切关注点的抽象

    3.连接点(joinpoint)(需要切入的点)

    被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指 的就是被拦截到的方法,实际上连接点还可以是字段或者构造器

    4.切入点(pointcut)

    对连接点进行拦截的定义

    5.通知(advice)

    所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异 常、最终、环绕通知五类

    6.目标对象

    代理的目标对象

    7.织入(weave)

    将切面应用到目标对象并导致代理对象创建的过程

    8.引入(introduction)

    在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段

    简单案例

     

     

     

    public interface Calculate {	/**	 * 加法	 * @param numA	 * @param numB	 * @return	 */	int add(int numA,int numB); 	/**	 * 减法	 * @param numA	 * @param numB	 * @return	 */	int reduce(int numA,int numB); 	/**	 * 除法	 * @param numA	 * @param numB	 * @return	 */	int div(int numA,int numB); 	/**	 * 乘法	 * @param numA	 * @param numB	 * @return	 */	int multi(int numA,int numB);}

     

    ==========实现类

     

     

     

     

    public class TulingCalculate implements Calculate { 	public int add(int numA, int numB) { 		return numA+numB;	} 	public int reduce(int numA, int numB) {		return numA-numB;	} 	public int div(int numA, int numB) {		return numA/numB;	} 	public int multi(int numA, int numB) {		return numA*numB;	}}

     

    =========切面类

     

     

     

     

     

     

    @Aspectpublic class TulingLogAspect { 	@Pointcut("execution(* com.tuling.TulingCalculate.*(..))")	public void pointCut(){}; 	@Before(value = "pointCut()")	public void methodBefore(JoinPoint joinPoint){		String methodName = joinPoint.getSignature().getName(); System.out.println("执行目标方法【"+methodName+"】之前执行<前置通知>,入参"+ Arrays.asList(joinPoint.getArgs()));	} 	@After(value = "pointCut()")	public void methodAfter(JoinPoint joinPoint) {		String methodName = joinPoint.getSignature().getName(); System.out.println("执行目标方法【"+methodName+"】之前执行<后置通知>,入参"+Arrays.asList(joinPoint.getArgs()));	} 	@AfterReturning(value = "pointCut()")	public void methodReturning(JoinPoint joinPoint ) {		String methodName = joinPoint.getSignature().getName(); System.out.println("执行目标方法【"+methodName+"】之前执行<返回通知>,入参"+Arrays.asList(joinPoint.getArgs()));	} 	@AfterThrowing(value = "pointCut()")	public void methodAfterThrowing(JoinPoint joinPoint) {		String methodName = joinPoint.getSignature().getName(); System.out.println("执行目标方法【"+methodName+"】之前执行<异常通知>,入参"+Arrays.asList(joinPoint.getArgs()));	}}

     

    ==========配置类

     

     

     

     

     

     

    @Configuration@EnableAspectJAutoProxy public class MainConfig { 	@Bean	public Calculate calculate() {		return new TulingCalculate();	} 	@Bean	public TulingLogAspect tulingLogAspect() {		return new TulingLogAspect();	}}

     

     

    六、Spring 事务源码解析

    事务概念解析

    1.什么是事物?

    事务是逻辑上的一组执行单元,要么都执行,要么都不执行.

    2. 事物的特性(ACID)

    什么是ACID?

    ACID是指数据库管理系统DBMS中事务所具有四个特性
    eg:在数据库系统中,一个事务由一系列的数据库操作组成一个完整的逻辑过程,比如银行转账,从原账户扣除金额,目标账户增加金额

    (1)atomicity【原子性】

    原子性表现为操作不能被分割,那么这两个操作要么同时完成,要么就全部不完成,若事务出错了,那么事务就会回滚,好像什么都没有发生过

    (2)Consistency【一致性】

    一致性也比较容易理解,也就是说数据库要一直处于一致的状态,事务开始前是一个一致状态,事务结束后是另一个一致状态,事务将数据库从一个一致状态转移到另一个一致状态

    (3)Isolation【隔离性】

    所谓的独立性就是指并发的事务之间不会互相影响,如果一个事务要访问的数据正在被另外一个事务修改,只要另外一个事务还未提交,它所访问的数据就不受未提交事务的影响。换句话说,一个事务的影响在该事务提交前对其它事务是不可见的

    (4)Durability【持久性】

    若事务已经提交了,那么就回在数据库中永久的保存下来

    篇幅有限,其他内容就不在这里一一展示了,这份Spring核心知识点一共176页PDF文档

    关注公众号:程序员追风,回复 008 获取这份Spring核心知识点总结

    最后

    欢迎大家一起交流,喜欢文章记得关注我点个赞,感谢支持!

     

  • 相关阅读:
    POJ3480 John 博弈论 anti-nim anti-SG
    POJ2068 Nim 博弈论 dp
    POJ 1740 A New Stone Game 又是博弈论配对找规律orz 博弈论 规律
    Python复习之下划线的含义
    django 模板语法和三种返回方式
    Python自动化之一对多
    Python自动化之django的ORM
    Python自动化之django的ORM操作——Python源码
    django orm字段和参数
    Python自动化之django视图
  • 原文地址:https://www.cnblogs.com/zhuifeng523/p/13541817.html
Copyright © 2011-2022 走看看