面试题总结103-150


Spring Boot/Spring Cloud
104. 什么是 spring boot?
    Spring Boot 是所有基于 Spring 开发的项目的起点。Spring Boot 的设计是为了让你尽可能快的跑起来 
    Spring 应用程序并且尽可能减少你的配置文件。简单来说就是SpringBoot其实不是什么新的框架,
    它默认配置了很多框架的使用方式,就像maven整合了所有的jar包,
    spring boot整合了所有的框架(不知道这样比喻是否合适)。
    SpringBoot四个主要特性
        1、SpringBoot Starter:他将常用的依赖分组进行了整合,将其合并到一个依赖中,
        这样就可以一次性添加到项目的Maven或Gradle构建中;
        2、自动配置:SpringBoot的自动配置特性利用了Spring4对条件化配置的支持,
        合理地推测应用所需的bean并自动化配置他们;
        3、命令行接口:(Command-line-interface, CLI):SpringBoot的CLI发挥了Groovy编程语言的优势,
        并结合自动配置进一步简化Spring应用的开发;
        4、Actuatir:它为SpringBoot应用的所有特性构建一个小型的应用程序。
        但首先,我们快速了解每项特性,更好的体验他们如何简化Spring编程模型。
    Spring Boot是在Spring的基础上面搭设的框架,目的是为了简化Spring项目的搭设和开发过程。不存在冲突的问题。
    它提供了如下特性如果有需求则可以引入
    自动配置 Spring-boot-starter 开箱即用依赖模块
    简化统一配置文件
    监控管理actuator
    内嵌了如Tomcat,Jetty,所有的依赖都打到一个jar包里面,可以直接java -jar 运行
        
105. 为什么要用 spring boot?
    Spring Boot解决的问题:
    (1) Spring Boot使编码变简单
    (2) Spring Boot使配置变简单
    (3) Spring Boot使部署变简单
    (4) Spring Boot使监控变简单
    (5) 解决了Spring的不足
    Spring Boot的主要特性:
    (1)遵循“习惯优于配置”的原则,使用Spring Boot只需要很少的配置,大部分的时候我们直接使用默认的配置即可;?
    (2)项目快速搭建,可以无需配置的自动整合第三方的框架;?
    (3)可以完全不使用XML配置文件,只需要自动配置和Java Config;?
    (4)内嵌Servlet容器,降低了对环境的要求,可以使用命令直接执行项目,应用可用jar包执行:java -jar;?
    (5)提供了starter POM, 能够非常方便的进行包管理, 很大程度上减少了jar hell或者dependency hell;?
    (6)运行中应用状态的监控;?
    (7)对主流开发框架的无配置集成;?
    (8)与云计算的天然继承;
    Spring Boot的核心功能:
    (1)独立运行的Spring项目
    Spring Boot可以以jar包的形式进行独立的运行,使用:java -jar xx.jar?
    就可以成功的运行项目,或者在应用项目的主程序中运行main函数即可;
    (2)内嵌的Servlet容器
    内嵌容器,使得我们可以执行运行项目的主程序main函数,是想项目的快速运行;
    主程序代码SpringbootDemoApplication.java
        package com.ceshi.demo;
        import org.springframework.boot.SpringApplication;
        import org.springframework.boot.autoconfigure.SpringBootApplication;
        @SpringBootApplication
        public class SpringbootDemoApplication {
            public static void main(String[] args) {
                SpringApplication.run(SpringbootDemoApplication.class, args);
            }
        }
    (3)提供starter简化Manen配置
    Spring Boot提供了一系列的starter pom用来简化我们的Maven依赖
    (4)自动配置Spring
    Spring Boot会根据我们项目中类路径的jar包/类,为jar包的类进行自动配置Bean,
    这样一来就大大的简化了我们的配置。
    当然,这只是Spring考虑到的大多数的使用场景,在一些特殊情况,我们还需要自定义自动配置;
    (5)应用监控
    Spring Boot提供了基于http、ssh、telnet对运行时的项目进行监控
    (6)无代码生成和XML配置
    Spring Boot神奇的地方不是借助于代码生成来实现的,
    而是通过条件注解的方式来实现的,这也是Spring 4.x的新特性。

106. spring boot 核心配置文件是什么?
    用过 Spring Boot 的都知道在 Spring Boot 中有以下两种配置文件
    bootstrap (.yml 或者 .properties)
    application (.yml 或者 .properties)
    因此,对比 application 配置文件,bootstrap 配置文件具有以下几个特性。
    

107. spring boot 配置文件有哪几种类型?它们有什么区别?
    boostrap 由父 ApplicationContext 加载,比 applicaton 优先加载
    boostrap 里面的属性不能被覆盖
    bootstrap/ application 的应用场景
    application 配置文件这个容易理解,主要用于 Spring Boot 项目的自动化配置。
    bootstrap 配置文件有以下几个应用场景。
    使用 Spring Cloud Config 配置中心时,
    这时需要在 bootstrap 配置文件中添加连接到配置中心的配置属性来加载外部配置中心的配置信息;
    一些固定的不能被覆盖的属性
    一些加密/解密的场景;

108. spring boot 有哪些方式可以实现热部署?
    1、模板热部署
        在SpringBoot中,模板引擎的页面默认是开启缓存的,如果修改了页面的内容,则刷新页面是得不到修改后的页面的,
        因此我们可以在application.properties中关闭模版引擎的缓存,如下:
        Thymeleaf的配置:
        spring.thymeleaf.cache=false
        FreeMarker的配置:
        spring.freemarker.cache=false  
        Groovy的配置:
        spring.groovy.template.cache=false
        Velocity的配置:
    spring.velocity.cache=false
    2、使用调试模式Debug实现热部署
        此种方式为最简单最快速的一种热部署方式,运行系统时使用Debug模式,
        无需装任何插件即可,但是无发对配置文件,
        方法名称改变,增加类及方法进行热部署,使用范围有限。
    3、spring-boot-devtools
        在Spring Boot 项目中添加 spring-boot-devtools依赖即可实现页面和代码的热部署。如下:
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>
        此种方式的特点是作用范围广,系统的任何变动包括配置文件修改、方法名称变化都能覆盖,但是后遗症也非常明显,
        它是采用文件变化后重启的策略来实现了,主要是节省了我们手动点击重启的时间,提高了实效性,在体验上回稍差。
        spring-boot-devtools 默认关闭了模版缓存,如果使用这种方式不用单独配置关闭模版缓存。
    4、Spring Loaded
        此种方式与Debug模式类似,适用范围有限,但是不依赖于Debug模式启动,通过Spring Loaded库文件启动,
        即可在正常模式下进行实时热部署。此种需要在 run confrgration 中进行配置。
    5、JRebel
        Jrebel是Java开发最好的热部署工具,对Spring Boot 提供了极佳的支持,
        JRebel为收费软件,试用期14天。,可直接通过插件安装。

109. jpa 和 hibernate 有什么区别?
    Hibernate是JPA规范的一个具体实现
    hibernate有JPA没有的特性 
    hibernate 的效率更快
    JPA 有更好的移植性,通用性

110. 什么是 spring cloud?
    Spring Cloud是一个微服务框架,相比Dubbo等RPC框架, 
    Spring Cloud提供的全套的分布式系统解决方案。 
      Spring Cloud对微服务基础框架Netflix的多个开源组件进行了封装,
      同时又实现了和云端平台以及和Spring Boot开发框架的集成。 
      Spring Cloud为微服务架构开发涉及的配置管理,服务治理,熔断机制,
      智能路由,微代理,控制总线,一次性token,
      全局一致性锁,leader选举,分布式session,集群状态管理等操作提供了一种简单的开发方式。
      Spring Cloud 为开发者提供了快速构建分布式系统的工具,
      开发者可以快速的启动服务或构建应用、同时能够快速和云平台资源进行对接。   

111. spring cloud 断路器的作用是什么?
    当一个服务调用另一个服务由于网络原因或者自身原因出现问题时?
    调用者就会等待被调用者的响应?当更多的服务请求到这些资源时
    导致更多的请求等待?这样就会发生连锁效应(雪崩效应)?
    断路器就是解决这一问题
    断路器有完全打开状态
    一定时间内?达到一定的次数无法调用?并且多次检测没有恢复的迹象?
    断路器完全打开,那么下次请求就不会请求到该服务
    半开
        短时间内?有恢复迹象?断路器会将部分请求发给该服务?当能正常调用时?断路器关闭
    关闭
        当服务一直处于正常状态?能正常调用?断路器关闭

112. spring cloud 的核心组件有哪些?
    服务发现——Netflix Eureka
    客服端负载均衡——Netflix Ribbon
    断路器——Netflix Hystrix
    服务网关——Netflix Zuul
    分布式配置——Spring Cloud Config

Hibernate
113. 为什么要使用 hibernate?
    Hibernate对JDBC访问数据库的代码做了封装,大大简化了数据访问层繁琐的重复性代码
    Hibernate是一来个基于jdbc的主流持久化框架,是一个优秀的orm实现,
    它很大程度的简化了dao层编码工作源
    Hibernate使用java的反射机制,而不是字节码增强程序类实现透明性
    Hibernate的性能非常好,因为它是一个轻量级框架。映射的灵活性很出色。
    它支持很多关系型数据库,从一对一到多对多的各种复杂关系
    *Hibernate本身性能并不是很好,存在很多优化手段 (一级缓存、zhidao二级缓存、查询缓存、抓取策略)

114. 什么是 ORM 框架?
    ORM 全称是 Object/Relation Mapping,即对象/关系数据库映射。
    可以讲ORM理解成一种规范,它概述了这类框架的基本特征,
    完成面相对象的编程语言到关系数据库的映射。
    ORM可以当成是应用程序和数据的桥梁。

115. hibernate 中如何在控制台查看打印的 SQL 语句?
    spring.jpa.properties.hibernate.show_sql=true         //控制台是否打印
    spring.jpa.properties.hibernate.format_sql=true        //格式化sql语句
    spring.jpa.properties.hibernate.use_sql_comments=true //指出是什么操作生成了该语句

116. hibernate 有几种查询方式?
    hql查询,sql查询,条件查询
    HQL:  Hibernate Query Language. 面向对象的写法:
        Query query = session.createQuery("from Customer where name = ?");
        query.setParameter(0, "苍老师");
        Query.list();
    QBC:  Query By Criteria.(条件查询)
        Criteria criteria = session.createCriteria(Customer.class);
        criteria.add(Restrictions.eq("name", "花姐"));
        List<Customer> list = criteria.list();
    SQL:
        SQLQuery query = session.createSQLQuery("select * from customer");
        List<Object[]> list = query.list();
        SQLQuery query = session.createSQLQuery("select * from customer");
        query.addEntity(Customer.class);
        List<Customer> list = query.list();
    Hql: 具体分类
    1、 属性查询 2、 参数查询、命名参数查询 3、 关联查询 4、 分页查询 5、 统计函数
    HQL和SQL的区别
    HQL是面向对象查询操作的,SQL是结构化查询语言 是面向数据库表结构的

117. hibernate 实体类可以被定义为 final 吗?
    是的,你可以将Hibernate的实体类定义为final类,但这种做法并不好。
    因为Hibernate会使用代理模式在延迟关联的情况下提高性能,如果你把实体类定义成final类之后,
    因为Java不允许对final类进行扩展,所以Hibernate就无法再使用代理了,如此一来就限制了使用可以提升性能的手段。
    不过,如果你的持久化类实现了一个接口而且在该接口中声明了所有定义于实体类中的所有public的方法的话,
    你就能够避免出现前面所说的不利后果。

118. 在 hibernate 中使用 Integer 和 int 做映射有什么区别?
    hibernate的PO类中 经常会用到int 型得变量 这个时候如果使用基本类型的变量(int ) 
    如果数据库中对应的存储数据是 null 时使用PO类进行获取数据 
    会出现类型转换异常 如果使用的是对象类型(Integer)这个时候则不会报错。

119. hibernate 是如何工作的?
    1.通过Configuration对象读取并解析配置文件
    2.读取并解析映射信息,创建SessionFactory对象
    3.打开session
    4.创建事务Transaction
    5.持久化操作,对对象进行CRUD操作
    6.提交事务
    7.关闭session和SessionFactory对象

120. get()和 load()的区别?
    load()没有使用对象的其他属性的时候,没有SQL  延迟加载
    get() :没有使用对象的其他属性的时候,也生成了SQL  立即加载

121. 说一下 hibernate 的缓存机制?
    一、why(为什么要用 Hibernate缓存?)
    Hibernate是一个持久层框架,经常访问物理数据库。
    为了降低应用程序对物理数据源访问的频次,从而提高应用程序的运行性能。
    缓存内的数据是对物理数据源中的数据的复制,应用程序在运行时从缓存读写数据,
    在特定的时刻或事件会同步缓存和物理数据源的数据。
    为了提供访问速度,把磁盘或数据库访问变成内存访问。
    二、what(Hibernate缓存原理是怎样的?)
    Hibernate缓存包括两大类:Hibernate一级缓存和 Hibernate二级缓存。
    1.Hibernate一级缓存又称为“Session的缓存”。Session缓存内置不能被卸载,
    Session的缓存是事务范围的缓存(Session对象的生命周期通常对应一个数据库事务或者一个应用事务)。
    一级缓存中,持久化类的每个实例都具有唯一的 OID。
    2.Hibernate二级缓存又称为“SessionFactory的缓存”。由于 ?SessionFactory 对象的生命周期和应用程序的整个过程对应,
    因此 Hibernate ?二级缓存是进程范围或者集群范围的缓存,有可能出现并发问题,因此需要采用适当的并发访问策略,
    该策略为被缓存的数据提供了事务隔离级别。第二级缓存是可选的,是一个可配置的插件,默认下 SessionFactory不会启用这个插件。
    Hibernate提供了 ?org.hibernate.cache.CacheProvider接口,它充当缓存插件与 Hibernate之间的适配器。

    总结一下:
    Hibernate中的缓存分一级缓存和二级缓存。
    一级缓存就是 ?Session 级别的缓存,在事务范围内有效是,内置的不能被卸载。
    二级缓存是 SesionFactory级别的缓存,
    从应用启动到应用结束有效。是可选的,默认没有二级缓存,需要手动开启。
    保存数据库后,缓存在内存中保存一份,如果更新了数据库就要同步更新。
    什么样的数据适合存放到第二级缓存中?
    1)很少被修改的数据 ??帖子的最后回复时间
    2)经常被查询的数据 ??电商的地点
    2) ?不是很重要的数据,允许出现偶尔并发的数据
    3) ?不会被并发访问的数据
    4) ?常量数据
    扩展:hibernate的二级缓存默认是不支持分布式缓存的。使用 ?memcahe,redis等中央缓存来代替二级缓存。

122. hibernate 对象有哪些状态?
    瞬时态(Transient):创建Session后,由 new 操作符创建,且尚未与Hibernate Session 关联的对象被认定为瞬时的。
    瞬时对象不会被持久化到数据库中,也不会被赋予持久化标识。 如果瞬时对象在程序中没有被引用,它会被垃圾回收器销毁。
    持久态(Persistent):持久的实例可能是刚被保存的,或刚被加载的,无论哪一种,按定义,它存在于相关联的Session作用范围内。
    Hibernate会检测到处于持久状态的对象的任何改动,在当前操作单元(unit of work)执行完毕时将对象数据(state)与数据库同步,
    开发者不需要手动执行UPDATE。
    脱管态(Detached):也叫游离态,与持久对象关联的Session被关闭后,对象就变为脱管的。脱管态不能直接持久化,需要重新保存。
    删除态(DELETED):调用Session的delete方法之后,对象就变成删除态,此时Session中仍然保存有对象的信息,
    对删除态对象的引用依然有效,对象可继续被修改。删除态对象如果重新关联到某个新的 Session 上(也就是执行持久化操作),
    会再次转变为持久的(在DELETED其间的改动将被持久化到数据库)。


123. 在 hibernate 中 getCurrentSession 和 openSession 的区别是什么?
    1 getCurrentSession创建的session会和绑定到当前线程,而openSession不会。
    2 getCurrentSession创建的线程会在事务回滚或事物提交后自动关闭,
    而openSession必须手动关闭(调用session的close()方法)
    这里getCurrentSession本地事务(本地事务:jdbc)时要在配置文件里进行如下设置
    * 如果使用的是本地事务(jdbc事务)
    <property name="hibernate.current_session_context_class">thread</property>
    * 如果使用的是全局事务(jta事务)
    <property name="hibernate.current_session_context_class">jta</property>
    (这个不能忘记,我在测试中如果没有上面在hibernate.cfg.xml中配置,使用getCurrentSession()时,
    会出错([color=darkred]org.hibernate.HibernateException: No CurrentSessionContext configured![/color])
    在 SessionFactory 启动的时候, Hibernate 会根据配置创建相应的 CurrentSessionContext ,
    在 getCurrentSession() 被调用的时候,实际被执行的方法是 CurrentSessionContext.currentSession() 。
    在 currentSession() 执行时,如果当前 Session 为空, currentSession 会调用 SessionFactory 的 openSession 。
    所以 getCurrentSession() 对于 Java EE 来说是更好的获取 Session 的方法。

124. hibernate 实体类必须要有无参构造函数吗?为什么?
    首先答案是肯定的。
    原因
        Hibernate框架会调用这个默认构造方法来构造实例对象,即Class类的newInstance方法 ,
        这个方法就是通过调用默认构造方法来创建实例对象的 。
        当查询的时候返回的实体类是一个对象实例,是Hibernate动态通过反射生成的。
        反射的Class.forName(“className”).newInstance()
        需要对应的类提供一个无参构造方法,必须有个无参的构造方法将对象创建出来,单从Hibernate的角度讲
        他是通过反射创建实体对象的 所以没有默认构造方法是不行的,另外Hibernate也可以通过有参的构造方法创建对象。
    提醒一点
        如果你没有提供任何构造方法,虚拟机会自动提供默认构造方法(无参构造器),
        但是如果你提供了其他有参数的构造方法的话,
        虚拟机就不再为你提供默认构造方法,这时必须手动把无参构造器写在代码里,否则new Xxxx()是会报错的,
        所以默认的构造方法不是必须的,只在有多个构造方法时才是必须的,这里“必须”指的是“必须手动写出来”。

MyBatis
SpringMVC与Struts2区别与比较总结:
    1、Struts2是类级别的拦截, 一个类对应一个request上下文,SpringMVC是方法级别的拦截,
    一个方法对应一个request上下文,而方法同时又跟一个url对应,所以说从架构本身上SpringMVC就容易实现restful url,
    而struts2的架构实现起来要费劲,因为Struts2中Action的一个方法可以对应一个url,而其类属性却被所有方法共享,
    这也就无法用注解或其他方式标识其所属方法了。
    2、由上边原因,SpringMVC的方法之间基本上独立的,独享request response数据,请求数据通过参数获取,
    处理结果通过ModelMap交回给框架,方法之间不共享变量,而Struts2搞的就比较乱,虽然方法之间也是独立的,
    但其所有Action变量是共享的,这不会影响程序运行,却给我们编码 读程序时带来麻烦,每次来了请求就创建一个Action,
    一个Action对象对应一个request上下文。
    3、由于Struts2需要针对每个request进行封装,把request,session等servlet生命周期的变量封装成一个一个Map,
    供给每个Action使用,并保证线程安全,所以在原则上,是比较耗费内存的。
    4、?拦截器实现机制上,Struts2有以自己的interceptor机制,SpringMVC用的是独立的AOP方式,
    这样导致Struts2的配置文件量还是比SpringMVC大。
    5、SpringMVC的入口是servlet,而Struts2是filter(这里要指出,filter和servlet是不同的。
    以前认为filter是servlet的一种特殊),这就导致了二者的机制不同,这里就牵涉到servlet和filter的区别了。
    6、SpringMVC集成了Ajax,使用非常方便,只需一个注解@ResponseBody就可以实现,然后直接返回响应文本即可,
    而Struts2拦截器集成了Ajax,在Action中处理时一般必须安装插件或者自己写代码集成进去,使用起来也相对不方便。
    7、SpringMVC验证支持JSR303,处理起来相对更加灵活方便,而Struts2验证比较繁琐,感觉太烦乱。
    8、Spring MVC和Spring是无缝的。从这个项目的管理和安全上也比Struts2高
    (当然Struts2也可以通过不同的目录结构和相关配置做到SpringMVC一样的效果,但是需要xml配置的地方不少)。
    9、 设计思想上,Struts2更加符合OOP的编程思想, SpringMVC就比较谨慎,在servlet上扩展。
    10、SpringMVC开发效率和性能高于Struts2。
    11、SpringMVC可以认为已经100%零配置。
    
    
125. MyBatis 中 #{}和 ${}的区别是什么?
    (1)
    1)#{} 为参数占位符 ?,即sql 预编译
    2)${} 为字符串替换,即 sql 拼接
    (2)
    1)#{}:动态解析 -> 预编译 -> 执行
    2)${}:动态解析 -> 编译 -> 执行
    (3)
    1)#{} 的变量替换是在DBMS 中
    2)${} 的变量替换是在 DBMS 外
    (4)
    1)变量替换后,#{} 对应的变量自动加上单引号 ''
    2)变量替换后,${} 对应的变量不会加上单引号 ''
    (5)
    1)#{} 能防止sql 注入
    2)${} 不能防止sql 注入
    5、#{} 和 ${} 在使用中的技巧和建议
    (1)不论是单个参数,还是多个参数,一律都建议使用注解@Param("")
    (2)能用 #{} 的地方就用 #{},不用或少用 ${}
    (3)表名作参数时,必须用 ${}。如:select * from ${tableName}
    (4)order by 时,必须用 ${}。如:select * from t_user order by ${columnName}
    (5)使用 ${} 时,要注意何时加或不加单引号,即 ${} 和 '${}'

126. MyBatis 有几种分页方式?
    数组分页
    原理:进行数据库查询操作时,获取到数据库中所有满足条件的记录,保存在应用的临时数组中,
    再通过List的subList方法,获取到满足条件的所有记录。
    Sql语句分页
    实现:通过sql语句实现分页也是非常简单的,
    只是需要改变我们查询的语句就能实现了,即在sql语句后面添加limit分页语句。
    RowBounds实现分页
    原理:通过RowBounds实现分页和通过数组方式分页原理差不多,都是一次获取所有符合条件的数据,
    然后在内存中对大数据进行操作,实现分页效果。只是数组分页需要我们自己去实现分页逻辑,这里更加简化而已。

125. MyBatis 中 #{}和 ${}的区别是什么?
    \#{}是预编译处理,${}是字符替换。
    在使用?#{}时,MyBatis 会将 SQL 中的?#{}替换成“?”,配合 PreparedStatement 的 set 方法赋值,
    这样可以有效的防止 SQL 注入,保证程序的运行安全。
    
126. MyBatis 有几种分页方式?
    分页方式:逻辑分页和物理分页。
    逻辑分页:?使用 MyBatis 自带的 RowBounds 进行分页,它是一次性查询很多数据,然后在数据中再进行检索。
    物理分页:?自己手写 SQL 分页或使用分页插件 PageHelper,去数据库查询指定条数的分页数据的形式。
    
127. RowBounds 是一次性查询全部结果吗?为什么?
    RowBounds 表面是在“所有”数据中检索数据,其实并非是一次性查询出所有数据,因为 MyBatis 是对 jdbc 的封装,
    在 jdbc 驱动中有一个 Fetch Size 的配置,它规定了每次最多从数据库查询多少条数据,假如你要查询更多数据,
    它会在你执行 next()的时候,去查询更多的数据。就好比你去自动取款机取 10000 元,但取款机每次最多能取 2500 元,
    所以你要取 4 次才能把钱取完。只是对于 jdbc 来说,
    当你调用 next()的时候会自动帮你完成查询工作。这样做的好处可以有效的防止内存溢出。
    
128. MyBatis 逻辑分页和物理分页的区别是什么?
    逻辑分页是一次性查询很多数据,然后再在结果中检索分页的数据。
    这样做弊端是需要消耗大量的内存、有内存溢出的风险、对数据库压力较大。
    物理分页是从数据库查询指定条数的数据,弥补了一次性全部查出的所有数据的种种缺点,
    比如需要大量的内存,对数据库查询压力较大等问题。
    
129. MyBatis 是否支持延迟加载?延迟加载的原理是什么?
    MyBatis 支持延迟加载,设置 lazyLoadingEnabled=true 即可。
    延迟加载的原理的是调用的时候触发加载,而不是在初始化的时候就加载信息。比如调用 a. getB(). getName(),
    这个时候发现 a. getB() 的值为 null,此时会单独触发事先保存好的关联 B 对象的 SQL,先查询出来 B,
    然后再调用 a. setB(b),而这时候再调用 a. getB(). getName() 就有值了,这就是延迟加载的基本原理。
    
130. 说一下 MyBatis 的一级缓存和二级缓存?
    一级缓存:基于 PerpetualCache 的 HashMap 本地缓存,它的声明周期是和 SQLSession 一致的,
    有多个 SQLSession 或者分布式的环境中数据库操作,可能会出现脏数据。当 Session flush 或 close 之后,
    该 Session 中的所有 Cache 就将清空,默认一级缓存是开启的。
    二级缓存:也是基于 PerpetualCache 的 HashMap 本地缓存,不同在于其存储作用域为 Mapper 级别的,
    如果多个SQLSession之间需要共享缓存,则需要使用到二级缓存,并且二级缓存可自定义存储源,如 Ehcache。
    默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现 Serializable 序列化接口(可用来保存对象的状态)。
    开启二级缓存数据查询流程:二级缓存 -> 一级缓存 -> 数据库。
    缓存更新机制:当某一个作用域(一级缓存 Session/二级缓存 Mapper)进行了C/U/D 操作后,
    默认该作用域下所有 select 中的缓存将被 clear。
    
131. MyBatis 和 hibernate 的区别有哪些?
    灵活性:MyBatis 更加灵活,自己可以写 SQL 语句,使用起来比较方便。
    可移植性:MyBatis 有很多自己写的 SQL,因为每个数据库的 SQL 可以不相同,所以可移植性比较差。
    学习和使用门槛:MyBatis 入门比较简单,使用门槛也更低。
    二级缓存:hibernate 拥有更好的二级缓存,它的二级缓存可以自行更换为第三方的二级缓存。
    
132. MyBatis 有哪些执行器(Executor)?
    Mybatis有三种基本的Executor执行器:SimpleExecutor、ReuseExecutor、BatchExecutor。
    SimpleExecutor:每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。
    ReuseExecutor:执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,
    不关闭Statement对象,而是放置于Map内,供下一次使用。简言之,就是重复使用Statement对象。
    BatchExecutor:执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),
    等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,
    等待逐一执行executeBatch()批处理。与JDBC批处理相同。
    作用范围:Executor的这些特点,都严格限制在SqlSession生命周期范围内。
    Mybatis中如何指定使用哪一种Executor执行器?
    答:在Mybatis配置文件中,可以指定默认的ExecutorType执行器类型,
    也可以手动给DefaultSqlSessionFactory的创建SqlSession的方法传递ExecutorType类型参数。

133. MyBatis 分页插件的实现原理是什么?
    1、PageHelper:
    据说在PageHelper之前的mybatis都是逻辑上的分页(这里也是据说,因为没有经历过这个年代。。。
    刚培训出来就接触的是PageHelper);PageHelper出来之后彻底实现了mybatis的物理分页
    2、实现原理: 就是在StatementHandler之前进行拦截,对MappedStatement进行一系列的操作(大致就是拼上分页sql)
    3、StatementHandler:StatementHandler的默认实现类是RoutingStatementHandler,因此拦截的实际对象是它。
    RoutingStatementHandler的主要功能是分发,这个类只是根据MappedStatement的配置,
    生成一个对应的StatementHandler(delegate),然后所有的实现都由delegate完成。(这一段是网上参考的)
    4、PageHelper: PageHelper这个类之前是一个拦截器,但是我们可以看到在现在使用的版本中它并未实现Interceptor,
    不知道是那个版本之后,拦截的任务就交给了PageInterceptor这个类,PageHelper应该只是PageInterceptor的一个属性Dialect,
    而且Dialect的默认值就是PageHelper

134. MyBatis 如何编写一个自定义插件?
    实现原理:
    MyBatis 自定义插件针对 MyBatis 四大对象(Executor,StatementHandler,ParamentHandler,ResultSetHandler)进行拦截。
    Executor? :拦截内部执行器,它负责调用StatementHandler 操作数据库,
    并把结果集通过 ResultSetHandler 进行自动映射,另外它还处理了二级缓存的操作。
    StatementHandler? :拦截 SQL 语法构建的处理,它是MyBatis 直接和数据库执行 SQL脚本的对象,
    另外它也实现了 MyBatis一级缓存
    ParamenterHandler? :拦截参数的处理。
    ResultSetHandler? :拦截结果集的处理。
    实现关键:
    MyBatis 插件要实现 Interceptor 接口,接口包含的方法,如下:
    public interface Interceptor {
        Object intrceptor(Invocation invocation) throws Throwable;
        Object plugin(Object target);
        void setProperties(Properties properties);
    }
    setProperties() 方法是在 MyBatis 进行配置插件的时候可以配置自定义相关属性,即:接口实现对象的参数配置。
    plugin() 方法是插件用于封装目标对象的,通过该方法我们可以返回目标对象本身,也可以返回一个它的代理,
    可以决定是否要进行拦截,进而决定要返回一个什么样的目标对象。
    interceptor()? 方法就是要进行拦截的时候要执行的方法。

RabbitMQ
135. RabbitMQ 的使用场景有哪些?
    场景1:单发送单接收
    使用场景:简单的发送与接收,没有特别的处理。
    场景2:单发送多接收
    使用场景:一个发送端,多个接收端,如分布式的任务派发。为了保证消息发送的可靠性,不丢失消息,使消息持久化了。
    同时为了防止接收端在处理消息时down掉,只有在消息处理完成后才发送ack消息。
    场景3:Publish/Subscribe
    使用场景:发布、订阅模式,发送端发送广播消息,多个接收端接收。
    场景4:Routing (按路线发送接收)
    使用场景:发送端按routing key发送消息,不同的接收端按不同的routing key接收消息。
    场景5:Topics (按topic发送接收)
    使用场景:发送端不只按固定的routing key发送消息,而是按字符串“匹配”发送,接收端同样如此。

136. RabbitMQ 有哪些重要的角色?
    生产者:消息的创建者,负责创建和推送数据到消息服务器

    消费者:消息的接收方,用于处理数据和确认消息

    代理:就是RabbitMQ本身,用于扮演快递的角色,本身并不生产消息
    1.AMQP,即Advanced Message Queuing Protocol,高级消息队列协议,
    是应用层协议的一个开放标准,为面向消息的中间件设计。
        消息中间件主要用于组件之间的解耦,消息的发送者无需知道消息使用者的存在,反之亦然。 
        AMQP的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。 
        RabbitMQ是一个开源的AMQP实现,服务器端用Erlang语言编写,支持多种客户端,
        如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等,支持AJAX。
        用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。
        下面将重点介绍RabbitMQ中的一些基础概念,
        了解了这些概念,是使用好RabbitMQ的基础。
    2.在实际应用中,可能会发生消费者收到Queue中的消息,但没有处理完成就宕机(或出现其他意外)的情况,
        这种情况下就可能会导致消息丢失。为了避免这种情况发生,
        我们可以要求消费者在消费完消息后发送一个回执给RabbitMQ,
        RabbitMQ收到消息回执(Message acknowledgment)后才将该消息从Queue中移除;
        如果RabbitMQ没有收到回执并检测到消费者的RabbitMQ连接断开,
        则RabbitMQ会将该消息发送给其他消费者(如果存在多个消费者)进行处理。
        这里不存在timeout概念,一个消费者处理消息时间再长也不会导致该消息被发送给其他消费者,
        除非它的RabbitMQ连接断开。
        这里会产生另外一个问题,如果我们的开发人员在处理完业务逻辑后,
        忘记发送回执给RabbitMQ,这将会导致严重的bug——Queue中堆积的消息会越来越多。
    3.生产者在将消息发送给Exchange的时候,一般会指定一个routing key,来指定这个消息的路由规则,
        而这个routing key需要与Exchange Type及binding key联合使用才能最终生效。 
        在Exchange Type与binding key固定的情况下(在正常使用时一般这些内容都是固定配置好的),
        我们的生产者就可以在发送消息给Exchange时,
        通过指定routing key来决定消息流向哪里。 RabbitMQ为routing key设定的长度限制为255 bytes。
        在绑定(Binding)Exchange与Queue的同时,一般会指定一个binding key;
        消费者将消息发送给Exchange时,一般会指定一个routing key;
        当binding key与routing key相匹配时,消息将会被路由到对应的Queue中。
        这个将在Exchange Types章节会列举实际的例子加以说明。
        在绑定多个Queue到同一个Exchange的时候,这些Binding允许使用相同的binding key。
        binding key 并不是在所有情况下都生效,
        它依赖于Exchange Type,比如fanout类型的Exchange就会无视binding key,
        而是将消息路由到所有绑定到该Exchange的Queue。
    4.我们以routingKey=”error”发送消息到Exchange,则消息会路由到Queue1
    (amqp.gen-S9b…,这是由RabbitMQ自动生成的Queue名称)
        和Queue2(amqp.gen-Agl…);如果我们以routingKey=”info”
        或routingKey=”warning”来发送消息,则消息只会路由到Queue2。
        如果我们以其他routingKey发送消息,则消息不会路由到这两个Queue中。
    5.headers类型的Exchange不依赖于routing key与binding key的匹配规则来路由消息,
    而是根据发送的消息内容中的headers属性进行匹配。 
        在绑定Queue与Exchange时指定一组键值对;当消息发送到Exchange时,
        RabbitMQ会取到该消息的headers(也是一个键值对的形式),
        对比其中的键值对是否完全匹配Queue与Exchange绑定时指定的键值对;
        如果完全匹配则消息会路由到该Queue,否则不会路由到该Queue。
        
137. RabbitMQ 有哪些重要的组件?
    ConnectionFactory(连接管理器):应用程序与RabbitMQ之间建立连接的管理器
    Channel(信道):消息推送使用的通道
    Exchange(交换器):用于接受、分配消息
    Queue(队列):用于存储生产者的消息
    RoutingKey(路由键):用于把生产者的数据分配到交换器上
    BindKey(绑定键):用于把交换器的消息绑定到队列上
    
138. RabbitMQ 中 vhost 的作用是什么?
    vhost本质上是一个mini版的RabbitMQ服务器,拥有自己的队列、绑定、交换器和权限控制;

    vhost通过在各个实例间提供逻辑上分离,允许你为不同应用程序安全保密地运行数据;

    vhost是AMQP概念的基础,必须在连接时进行指定,RabbitMQ包含了默认vhost:“/”;

    当在RabbitMQ中创建一个用户时,用户通常会被指派给至少一个vhost,
    并且只能访问被指派vhost内的队列、交换器和绑定,vhost之间是绝对隔离的。

    vhost可以理解为虚拟broker,即mini-RabbitMQ server,
    其内部均含有独立的queue、bind、exchange等,最重要的是拥有独立的权限系统,
    可以做到vhost范围内的用户控制。当然,从RabbitMQ全局角度,
    vhost可以作为不同权限隔离的手段(一个典型的例子,不同的应用可以跑在不同的vhost中)。

139. RabbitMQ 的消息是怎么发送的?


140. RabbitMQ 怎么保证消息的稳定性?
    publisher confirms(发布方确认):
    确保producer到broker节点消息的可靠性。如可能发生消息到了broker但是还没有投递到queue,broker突然宕机这种情况;

    message持久化:
    将message存储到硬盘,如果是未持久化的消息会存在内存中,broker宕机后重启内存中的消息会丢失

    acknowledgement(consumer确认):
    该机制能够保证,只有consumer成功消费了消息,才将其从queue中移除。

141. RabbitMQ 怎么避免消息丢失?
    RabbitMQ一般情况很少丢失,但是不能排除意外,为了保证我们自己系统高可用,我们必须作出更好完善措施,保证系统的稳定性。
    下面来介绍下,如何保证消息的绝对不丢失的问题,下面分享的绝对干货,都是在知名互联网产品的产线中使用。
    1.消息持久化
    2.ACK确认机制
    3.设置集群镜像模式
    4.消息补偿机制
    第一种:消息持久化
        RabbitMQ 的消息默认存放在内存上面,如果不特别声明设置,消息不会持久化保存到硬盘上面的,
        如果节点重启或者意外crash掉,消息就会丢失。
        所以就要对消息进行持久化处理。如何持久化,下面具体说明下:
        要想做到消息持久化,必须满足以下三个条件,缺一不可。
        1) Exchange 设置持久化
        2)Queue 设置持久化
        3)Message持久化发送:发送消息设置发送模式deliveryMode=2,代表持久化消息
    第二种:ACK确认机制
        多个消费者同时收取消息,比如消息接收到一半的时候,一个消费者死掉了(逻辑复杂时间太长,
        超时了或者消费被停机或者网络断开链接),如何保证消息不丢?
        这个使用就要使用Message acknowledgment 机制,就是消费端消费完成要通知服务端,服务端才把消息从内存删除。
        这样就解决了,及时一个消费者出了问题,没有同步消息给服务端,还有其他的消费端去消费,保证了消息不丢的case。
    第三种:设置集群镜像模式
        我们先来介绍下RabbitMQ三种部署模式:
        1)单节点模式:最简单的情况,非集群模式,节点挂了,消息就不能用了。业务可能瘫痪,只能等待。
        2)普通模式:默认的集群模式,某个节点挂了,该节点上的消息不能用,有影响的业务瘫痪,
        只能等待节点恢复重启可用(必须持久化消息情况下)。
        3)镜像模式:把需要的队列做成镜像队列,存在于多个节点,属于RabbitMQ的HA方案
        为什么设置镜像模式集群,因为队列的内容仅仅存在某一个节点上面,不会存在所有节点上面,
        所有节点仅仅存放消息结构和元数据。下面自己画了一张图介绍普通集群丢失消息情况:
        如果想解决上面途中问题,保证消息不丢失,需要采用HA 镜像模式队列。
        下面介绍下三种HA策略模式:
            1)同步至所有的
            2)同步最多N个机器
            3)只同步至符合指定名称的nodes
            命令处理HA策略模版:rabbitmqctl set_policy [-p Vhost] Name Pattern Definition [Priority]
            1)为每个以“rock.wechat”开头的队列设置所有节点的镜像,并且设置为自动同步模式
            rabbitmqctl set_policy ha-all "^rock.wechat" '{"ha-mode":"all","ha-sync-mode":"automatic"}'
            rabbitmqctl set_policy -p rock ha-all "^rock.wechat" '{"ha-mode":"all","ha-sync-mode":"automatic"}'
            2)为每个以“rock.wechat.”开头的队列设置两个节点的镜像,并且设置为自动同步模式
            rabbitmqctl set_policy -p rock ha-exacly "^rock.wechat" \
            '{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}'
            3)为每个以“node.”开头的队列分配指定的节点做镜像
            rabbitmqctl set_policy ha-nodes "^nodes\." \
            '{"ha-mode":"nodes","ha-params":["rabbit@nodeA", "rabbit@nodeB"]}'
        但是:HA 镜像队列有一个很大的缺点就是:   系统的吞吐量会有所下降
    第四种:消息补偿机制
        为什么还要消息补偿机制呢?难道消息还会丢失,没错,系统是在一个复杂的环境,不要想的太简单了,
        虽然以上的三种方案,基本可以保证消息的高可用不丢失的问题,
        但是作为有追求的程序员来讲,要绝对保证我的系统的稳定性,有一种危机意识。
        比如:持久化的消息,保存到硬盘过程中,当前队列节点挂了,存储节点硬盘又坏了,消息丢了,怎么办?
        产线网络环境太复杂,所以不知数太多,消息补偿机制需要建立在消息要写入DB日志,发送日志,
        接受日志,两者的状态必须记录。
        然后根据DB日志记录check 消息发送消费是否成功,不成功,进行消息补偿措施,重新发送消息处理。


142. 要保证消息持久化成功的条件有哪些?
    声明队列必须设置持久化 durable 设置为 true.
    消息推送投递模式必须设置持久化,deliveryMode 设置为 2(持久)。
    消息已经到达持久化交换器。
    消息已经到达持久化队列。
    以上四个条件都满足才能保证消息持久化成功。

143. RabbitMQ 持久化有什么缺点?
    持久化的缺地就是降低了服务器的吞吐量,因为使用的是磁盘而非内存存储,从而降低了吞吐量。
    可尽量使用 ssd 硬盘来缓解吞吐量的问题。

144. RabbitMQ 有几种广播类型?
    direct(默认方式):最基础最简单的模式,发送方把消息发送给订阅方,如果有多个订阅者,
    默认采取轮询的方式进行消息发送。
    headers:与 direct 类似,只是性能很差,此类型几乎用不到。
    fanout:分发模式,把消费分发给所有订阅者。
    topic:匹配订阅模式,使用正则匹配到消息队列,能匹配到的都能接收到。

145. RabbitMQ 怎么实现延迟消息队列?
    延迟队列的实现有两种方式:
    通过消息过期后进入死信交换器,再由交换器转发到延迟消费队列,实现延迟功能;
    使用 RabbitMQ-delayed-message-exchange 插件实现延迟功能。

146. RabbitMQ 集群有什么用?


147. RabbitMQ 节点的类型有哪些?
    磁盘节点:消息会存储到磁盘。
    内存节点:消息都存储在内存中,重启服务器消息丢失,性能高于磁盘类型。

148. RabbitMQ 集群搭建需要注意哪些问题?
    各节点之间使用“–link”连接,此属性不能忽略。
    各节点使用的 erlang cookie 值必须相同,此值相当于“秘钥”的功能,用于各节点的认证。
    整个集群中必须包含一个磁盘节点。

149. RabbitMQ 每个节点是其他节点的完整拷贝吗?为什么?
    不是,原因有以下两个:
    存储空间的考虑:如果每个节点都拥有所有队列的完全拷贝,这样新增节点不但没有新增存储空间,
    反而增加了更多的冗余数据;
    性能的考虑:如果每条消息都需要完整拷贝到每一个集群节点,
    那新增节点并没有提升处理消息的能力,最多是保持和单节点相同的性能甚至是更糟。

150. RabbitMQ 集群中唯一一个磁盘节点崩溃了会发生什么情况?
    如果唯一磁盘的磁盘节点崩溃了,
    不能进行以下操作:
    不能创建队列
    不能创建交换器
    不能创建绑定
    不能添加用户
    不能更改权限
    不能添加和删除集群节点
    唯一磁盘节点崩溃了,集群是可以保持运行的,但你不能更改任何东西。

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值