知识总结-常用框架相关(摘录+转载)

Spring框架是Java开发中最流行的选择,以其轻量级、强大的控制反转和面向切面编程能力著称,广泛应用于超过80%的Java项目。本文详细解析Spring的七大核心模块,包括核心容器、AOP、JDBC抽象、ORM、Web模块和MVC,以及事务管理、微服务框架Dubbo与Spring Cloud的对比。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Spring

Spring 是为了简化开发复杂度而诞生,其简单、强大、可测试、耦合性低是目前java最流行的开发框架,广泛应用于java开发中,几乎超过百分之八十的java项目都与spring有关。

Spring是一个轻量级的控制反转(IOC和DI)和面向切面(AOP)容器框架。轻量:完整包总大小不到1M。控制反转:原本由人工手动管理的bean由spring统一管理。面向切面:使用动态代理设计模式,将功能横向切入,极大的减少代码量和维护成本。容器:Spring管理者对象的配置和生命周期。框架:Spring将通用的配置组装,而开发者只要专注于功能开发即可。

Spring分为7大核心模块:核心容器、应用上下文、AOP、JDBC抽象和DAO模块、对象/关系映射、Web模块、MVC模块。

核心容器:Spring最核心模块,它通过一个BeanFactory来管理所有Bean,BeanFactory是工厂模式的一种实现,它使用控制反转将配置和依赖说明从代码中分离。

bean装配过程:

1. 实例化;  
2. 设置属性值;  
3. 如果实现了BeanNameAware接口,调用setBeanName设置Bean的ID或者Name;  
4. 如果实现BeanFactoryAware接口,调用setBeanFactory 设置BeanFactory;  
5. 如果实现ApplicationContextAware,调用setApplicationContext设置ApplicationContext  
6. 调用BeanPostProcessor的预先初始化方法;  
7. 调用InitializingBean的afterPropertiesSet()方法;  
8. 调用定制init-method方法;  
9. 调用BeanPostProcessor的后初始化方法;  

Spring容器关闭过程:

1. 调用DisposableBean的destroy(); 
2. 调用定制的destroy-method方法;

多个Bean的先后顺序

  • 优先加载BeanPostProcessor的实现Bean
  • 按Bean文件和Bean的定义顺序按bean的装载顺序(即使加载多个spring文件时存在id覆盖)
  • “设置属性值”(第2步)时,遇到ref,则在“实例化”(第1步)之后先加载ref的id对应的bean
  • AbstractFactoryBean的子类,在第6步之后,会调用createInstance方法,之后会调用getObjectType方法
  • BeanFactoryUtils类也会改变Bean的加载顺序

应用上下文

这个模块拓展了BeanFactory,增加了对国际化消息、事务传播和验证的支持。

Spring主要有以下三种类型的应用上下文:

ClassPathXmlApplicationContext(org.springframework.context.support.ClassPathXmlApplicationContext)——从类路径下的XML配置文件中加载上下文定义,把应用上下文定义文件当作类资源。

FileSystemXmlApplicationContext(org.springframework.context.support.FileSystemXmlApplicationContext)——读取文件系统下的XML配置文件并加载上下文定义。

XmlWebApplicationContext(org.springframework.web.context.support.XmlWebApplicationContext)——读取Web应用下的XML配置文件并装载上下文定义。

使用FileSystemXmlApplicationContext和使用ClassPathXmlApplicationContext的区别在于:FileSystemXmlApplicationContext在指定的文件系统路径下查找.xml文件;而ClassPathXmlApplicationContext是在所有的类路径(包含JAR文件)下查找.xml文件。

通过现有的应用上下文引用,你可以调用应用上下文的getBean()方法从Spring容器中获取Bean。

AOP

提供了面向切面支持,是实现切面编程的基础,是面向对象编程的补充和完善。AOP将公共部分分离并封装到一个可重用模块,以减少重复代码,降低代码耦合度。AOP核心概念:横切关注点、切面、连接点、切入点、通知、目标对象、织入、引入。AOP代理由IOC负责生成、管理,可以自由调用容器中的Bean。默认使用java动态代理实现,当代理类不是代理接口时会切换为CGLIB来实现。使用步骤:定义业务组件-定义切入接口点-定义增强处理。AOP常用于实现日志打印、JDBC管理、多数据源管理、公共校验等等功能。

JDBC抽象和DAO模块

提供了对Hibernate、JDBC等DAO层的支持。可以保证数据库操作的整洁,并避免因错误关闭数据源连接而导致的问题,还利用AOP提供了统一事务支持。它提供了一个DataSource对象来记录数据源的内容,并在此基础上提供了JdbcTemplate来对数据库操作进行统一处理。在项目中一般使用数据源池来对DataSource进行管理避免由于无限制创建数据库连接而造成资源浪费或崩溃宕机问题。目前常用阿里巴巴的DuridDataSource进行管理。

对象/关系映射模块

就是Java对象 Object 和关系型数据库 Relationship 之间的映射Mapping, 即ORM。开发者可以像操作java对象一样操作数据库,比起直接使用sql更易于理解。ORM实现原理很简单,就是通过反射将对应的对象bean与数据库表字段一一对应,当进行Query时直接将数据塞到对应的字段中,当进行UPDATE/INSERT时直接将对象解析到。

Web模块

用于整合Web框架,例如Struts、JSF等,也可以用于特定服务的开发绑定。

MVC模块

Spring MVC是一种基于Java,实现了Web MVC设计模式,请求驱动类型的轻量级Web框架,即使用了MVC架构模式的思想,将Web层进行职责解耦。基于请求驱动指的就是使用请求-响应模型,框架的目的就是帮助我们简化开发,SpringMVC也是要简化我们日常Web开发。对程序进行了很好的分层,是当前最优秀的MVC框架。M-Model业务层、V-View视图层、C-Controller控制层。访问流程:

(1)用户发送请求——>DispatcherServlet,前端控制器收到请求后自己不进行处理,而是委托给其他的解析器进行处理,作为统一访问点,进行全局的流程控制;

(2)DispatcherServlet——>HandlerMapping,处理器映射器将会把请求映射为HandlerExecutionChain对象(包含一个Handler处理器对象、多个HandlerInterceptor拦截器)对象;

(3)DispatcherServlet——>HandlerAdapter,处理器适配器将会把处理器包装为适配器,从而支持多种类型的处理器,即适配器设计模式的应用,从而很容易支持很多类型的处理器;

(4)HandlerAdapter——>调用处理器相应功能处理方法,并返回一个ModelAndView对象(包含模型数据、逻辑视图名);

(5)ModelAndView对象(Model部分是业务对象返回的模型数据,View部分为逻辑视图名)——> ViewResolver, 视图解析器将把逻辑视图名解析为具体的View;

(6)View——>渲染,View会根据传进来的Model模型数据进行渲染,此处的Model实际是一个Map数据结构;

(7)返回控制权给DispatcherServlet,由DispatcherServlet返回响应给用户,到此一个流程结束。

Spring是一个优秀的框架,它每一个模块都可以单独使用并可以与其他框架无缝衔接,它的源码很有学习价值。

Spring事务

事务是一系列的动作,它们综合在一起才是一个完整的工作单元,这些动作必须全部完成,如果有一个失败的话,那么事务就会回滚到最开始的状态,仿佛什么都没发生过一样。 事务有四个特性:ACID

  • 原子性(Atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。
  • 一致性(Consistency):一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏。
  • 隔离性(Isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。
  • 持久性(Durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中。

spring事务管理接口展示:

这里写图片描述

Spring并不直接管理事务,而是提供了多种事务管理器,他们将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现。

Spring事务管理器的接口是org.springframework.transaction.PlatformTransactionManager,通过这个接口,Spring为各个平台如JDBC、Hibernate等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。如果应用程序中直接使用JDBC来进行持久化,DataSourceTransactionManager会处理事务边界。实际上,DataSourceTransactionManager是通过调用java.sql.Connection来管理事务,而后者是通过DataSource获取到的。通过调用连接的commit()方法来提交事务,同样,事务失败则通过调用rollback()方法进行回滚。如果应用程序的持久化是通过Hibernate实习的,那么要使用HibernateTransactionManager。sessionFactory属性需要装配一个Hibernate的session工厂,HibernateTransactionManager的实现细节是它将事务管理的职责委托给org.hibernate.Transaction对象,而后者是从Hibernate Session中获取到的。当事务成功完成时,HibernateTransactionManager将会调用Transaction对象的commit()方法,反之,将会调用rollback()方法。Hibernate多年来一直是事实上的Java持久化标准,但是现在Java持久化API作为真正的Java持久化标准进入大家的视野。如果使用JPA的话,需要使用Spring的JpaTransactionManager来处理事务。JpaTransactionManager只需要装配一个JPA实体管理工厂(javax.persistence.EntityManagerFactory接口的任意实现)。JpaTransactionManager将与由工厂所产生的JPA EntityManager合作来构建事务。如果没有使用以上所述的事务管理,或者是跨越了多个事务管理源(比如两个或者是多个不同的数据源),要使用JtaTransactionManager。JtaTransactionManager将事务管理的责任委托给javax.transaction.UserTransaction和javax.transaction.TransactionManager对象,其中事务成功完成通过UserTransaction.commit()方法提交,事务失败通过UserTransaction.rollback()方法回滚。

事务的传播行为

Spring定义了七种传播行为:

传播行为含义
PROPAGATION_REQUIRED表示当前方法必须运行在事务中。如果当前事务存在,方法将会在该事务中运行。否则,会启动一个新的事务
PROPAGATION_SUPPORTS表示当前方法不需要事务上下文,但是如果存在当前事务的话,那么该方法会在这个事务中运行
PROPAGATION_MANDATORY表示该方法必须在事务中运行,如果当前事务不存在,则会抛出一个异常
PROPAGATION_REQUIRED_NEW表示当前方法必须运行在它自己的事务中。一个新的事务将被启动。如果存在当前事务,在该方法执行期间,当前事务会被挂起。如果使用JTATransactionManager的话,则需要访问TransactionManager
PROPAGATION_NOT_SUPPORTED表示该方法不应该运行在事务中。如果存在当前事务,在该方法运行期间,当前事务将被挂起。如果使用JTATransactionManager的话,则需要访问TransactionManager
PROPAGATION_NEVER表示当前方法不应该运行在事务上下文中。如果当前正有一个事务在运行,则会抛出异常
PROPAGATION_NESTED表示如果当前已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌套的事务可以独立于当前事务进行单独地提交或回滚。如果当前事务不存在,那么其行为与PROPAGATION_REQUIRED一样。注意各厂商对这种传播行为的支持是有所差异的。可以参考资源管理器的文档来确认它们是否支持嵌套事务

事务的隔离级别

隔离级别含义
ISOLATION_DEFAULT使用后端数据库默认的隔离级别
ISOLATION_READ_UNCOMMITTED最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
ISOLATION_READ_COMMITTED允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
ISOLATION_REPEATABLE_READ对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生
ISOLATION_SERIALIZABLE最高的隔离级别,完全服从ACID的隔离级别,确保阻止脏读、不可重复读以及幻读,也是最慢的事务隔离级别,因为它通常是通过完全锁定事务相关的数据库表来实现的
  • 脏读(Dirty reads)——脏读发生在一个事务读取了另一个事务改写但尚未提交的数据时。如果改写在稍后被回滚了,那么第一个事务获取的数据就是无效的。
  • 不可重复读(Nonrepeatable read)——不可重复读发生在一个事务执行相同的查询两次或两次以上,但是每次都得到不同的数据时。这通常是因为另一个并发事务在两次查询期间进行了更新。
  • 幻读(Phantom read)——幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录。

目前spring事务可以通过xml配置或注解来使用。

Mybatis/Ibatis

一款操作数据库框架,严格意义上讲不能算是ORM框架,Mybatis是Ibatis的3.X版本,在3.X之前是Ibatis,3.X以后是MyBatis。MyBatis功能要比Ibatis功能更强大。相比较而言Mybatis实现了接口绑定,使用起来更方便。对对象关系映射进行改进效率更高。

Hibernate

Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,它将POJO与数据库表建立映射关系,是一个全自动的orm框架,hibernate可以自动生成SQL语句,自动执行,使得Java程序员可以随心所欲的使用对象编程思维来操纵数据库。 Hibernate可以应用在任何使用JDBC的场合,既可以在Java的客户端程序使用,也可以在Servlet/JSP的Web应用中使用,最具革命意义的是,Hibernate可以在应用EJB的JaveEE架构中取代CMP,完成数据持久化的重任。

两款框架相比较而言,Mybatis更自由所有sql都需要开发人员自己写,Hibernate封装性更好直接把sql也封装了。Mybatis所有脚本都维护在xml文件中,相对而言学习成本更低,优化起来简单方便,但需要移植数据库是要繁琐一些,维护成本要更高。Hibernate将sql也进行了封装,优化起来较为困难,相对而言学习成本要高的多,许多高级功能要深入学习后才可以熟练使用,但换数据库会方便得多,且后期维护成本也比较低。

Struts

一款优秀的MVC框架,但目前基本看不到它的身影,逐渐被spring所取代。

Dubbo与SpringCloud

dubbo与cloud都是优秀的微服务框架,dubbo由阿里巴巴研发,并广泛应用于电子商务公司,其以高效、稳定著称。cloud是spring旗下又一优秀的框架,其稳定、简单、功能丰富已逐渐成为中小型互联网公司首选微服务框架。

dubbo是一个RPC远程调用服务,其核心部分包含:远程通讯: 提供对多种基于长连接的NIO框架抽象封装,包括多种线程模型,序列化,以及“请求-响应”模式的信息交换方式。集群容错: 提供基于接口方法的透明远程过程调用,包括多协议支持,以及软负载均衡,失败容错,地址路由,动态配置等集群支持。自动发现: 基于注册中心目录服务,使服务消费方能动态的查找服务提供方,使地址透明,使服务提供方可以平滑增加或减少机器。dubbo可以让程序像本地调用方法一样调用远程服务器方法,并且没有任何的api侵入,它也可以作为软负载均衡工具代替F5这种硬件负载均衡,并提供服务自动注册与发现,不再需要写死服务提供方地址,注册中心基于接口名查询服务提供者的IP地址,并且能够平滑添加或删除服务提供者。它可以与spring完美集成,没有任何入侵,并可以通过注解使用,简单方便。使用dubbo必须要用zk。

Dubbo框架设计一共划分了10个层:

服务接口层(Service):该层是与实际业务逻辑相关的,根据服务提供方和服务消费方的业务设计对应的接口和实现。

配置层(Config):对外配置接口,以ServiceConfig和ReferenceConfig为中心,可以直接new配置类,也可以通过spring解析配置生成配置类。

服务代理层(Proxy):服务接口透明代理,生成服务的客户端Stub和服务器端Skeleton,以ServiceProxy为中心,扩展接口为ProxyFactory。

服务注册层(Registry):封装服务地址的注册与发现,以服务URL为中心,扩展接口为RegistryFactory、Registry和RegistryService。可能没有服务注册中心,此时服务提供方直接暴露服务。

集群层(Cluster):封装多个提供者的路由及负载均衡,并桥接注册中心,以Invoker为中心,扩展接口为Cluster、Directory、Router和LoadBalance。将多个服务提供方组合为一个服务提供方,实现对服务消费方来透明,只需要与一个服务提供方进行交互。

监控层(Monitor):RPC调用次数和调用时间监控,以Statistics为中心,扩展接口为MonitorFactory、Monitor和MonitorService。

远程调用层(Protocol):封装RPC调用,以Invocation和Result为中心,扩展接口为Protocol、Invoker和Exporter。Protocol是服务域,它是Invoker暴露和引用的主功能入口,它负责Invoker的生命周期管理。Invoker是实体域,它是Dubbo的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可执行体,可向它发起invoke调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。

信息交换层(Exchange):封装请求响应模式,同步转异步,以Request和Response为中心,扩展接口为Exchanger、ExchangeChannel、ExchangeClient和ExchangeServer。

网络传输层(Transport):抽象mina和netty为统一接口,以Message为中心,扩展接口为Channel、Transporter、Client、Server和Codec。

数据序列化层(Serialize):可复用的一些工具,扩展接口为Serialization、 ObjectInput、ObjectOutput和ThreadPool。

dubbo通过二进制序列化的方式传递实例,在反序列化成方法最后得到返回结果。

springcloud是spring大家族中的一员,是一套完整的微服务框架集合,它基于springboot,总的来说是一个容器将微服务框架集成进来,继而简化开发者工作量,可以说是微服务全家桶。springcloud服务调用方式为RESTFUl,通过json传递数据,它的服务及其完善,要什么有什么,使用起来比dubbo简单得多。springboot是一个spring全新的框架,依照约定大于配置的原则对spring的配置进行了极大程度的简化,使用者不需要定义大量的xml文件,在springboot中对绝大多数配置设置了默认值,开发者几乎不需要任何其他操作即可轻松上手,目前springboot在互联网公司使用及其广泛。

下面几张图展示了官网上提供的 Spring Boot 所集成的所有类库:

这里写图片描述

这里写图片描述

这里写图片描述

Spring Boot 官方推荐使用 Maven 或 Gradle 来构建项目。

Spring Boot 整个应用程序只有一个配置文件,那就是 .properties 或 .yml 文件。但是,在前面的示例代码中,我们并没有看到该配置文件,那是因为 Spring Boot 对每个配置项都有默认值。当然,我们也可以添加配置文件,用以覆盖其默认值,这里以 .properties 文件为例,首先在 resources 下新建一个名为 application.properties(注意:文件名必须是 application)的文件,键入内容为:

server.port=8081
server.servlet.context-path=/api
并且启动 main 方法,这时程序请求地址则变成了:http://localhost:8081/api/hello。

Spring Boot 支持 properties 和 yaml 两种格式的文件,文件名分别对应 application.properties 和 application.yml,下面贴出 yaml 文件格式供大家参考:

server:
    port: 8080
    servlet:
        context-path: /api
可以看出 properties 是以逗号隔开,而 yaml 则换行+ tab 隔开,这里需要注意的是冒号后面必须空格,否则会报错。yaml 文件格式更清晰,更易读。

常用注解
我们知道,Spring Boot 主要采用注解的方式,在上一篇的入门实例中,我们也用到了一些注解。

本文,我将详细介绍在实际项目中常用的注解。

@SpringBootApplication

我们可以注意到 Spring Boot 支持 main 方法启动,在我们需要启动的主类中加入此注解,告诉 Spring Boot,这个类是程序的入口。如:

@SpringBootApplication
public class Application {
 
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
如果不加这个注解,程序是无法启动的。

我们查看下 SpringBootApplication 的源码,源码如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
        @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
 
    /**
     * Exclude specific auto-configuration classes such that they will never be applied.
     * @return the classes to exclude
     */
    @AliasFor(annotation = EnableAutoConfiguration.class, attribute = "exclude")
    Class<?>[] exclude() default {};
 
    /**
     * Exclude specific auto-configuration class names such that they will never be
     * applied.
     * @return the class names to exclude
     * @since 1.3.0
     */
    @AliasFor(annotation = EnableAutoConfiguration.class, attribute = "excludeName")
    String[] excludeName() default {};
 
    /**
     * Base packages to scan for annotated components. Use {@link #scanBasePackageClasses}
     * for a type-safe alternative to String-based package names.
     * @return base packages to scan
     * @since 1.3.0
     */
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
    String[] scanBasePackages() default {};
 
    /**
     * Type-safe alternative to {@link #scanBasePackages} for specifying the packages to
     * scan for annotated components. The package of each class specified will be scanned.
     * <p>
     * Consider creating a special no-op marker class or interface in each package that
     * serves no purpose other than being referenced by this attribute.
     * @return base packages to scan
     * @since 1.3.0
     */
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
    Class<?>[] scanBasePackageClasses() default {};
 
}
在这个注解类上有3个注解,如下:

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
        @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
因此,我们可以用这三个注解代替 SpringBootApplication,如:

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
public class Application {
 
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
其中,SpringBootConfiguration 表示 Spring Boot 的配置注解,EnableAutoConfiguration 表示自动配置,ComponentScan 表示 Spring Boot 扫描 Bean 的规则,比如扫描哪些包。

@Configuration

加入了这个注解的类被认为是 Spring Boot 的配置类,我们知道可以在 application.yml 设置一些配置,也可以通过代码设置配置。

如果我们要通过代码设置配置,就必须在这个类上标注 Configuration 注解。如下代码:

@Configuration
public class WebConfig extends WebMvcConfigurationSupport{
 
    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        super.addInterceptors(registry);
        registry.addInterceptor(new ApiInterceptor());
    }
}
不过 Spring Boot 官方推荐 Spring Boot 项目用 SpringBootConfiguration 来代替 Configuration。

@Bean

这个注解是方法级别上的注解,主要添加在 @Configuration 或 @SpringBootConfiguration 注解的类,有时也可以添加在 @Component 注解的类。它的作用是定义一个Bean。

请看下面代码:

   @Bean
    public ApiInterceptor interceptor(){
        return new ApiInterceptor();
    }
那么,我们可以在 ApiInterceptor 里面注入其他 Bean,也可以在其他 Bean 注入这个类。

@Value

通常情况下,我们需要定义一些全局变量,都会想到的方法是定义一个 public static 变量,在需要时调用,是否有其他更好的方案呢?答案是肯定的。下面请看代码:

    @Value("${server.port}")
    String port;
    @RequestMapping("/hello")
    public String home(String name) {
        return "hi "+name+",i am from port:" +port;
    }
其中,server.port 就是我们在 application.yml 里面定义的属性,我们可以自定义任意属性名,通过 @Value 注解就可以将其取出来。

它的好处不言而喻:

定义在配置文件里,变量发生变化,无需修改代码。
变量交给Spring来管理,性能更好。

Dubbo 只是实现了服务治理,而 Spring Cloud 实现了微服务架构的方方面面,服务治理只是其中的一个方面。下面通过一张图对其进行比较:

这里写图片描述

springcloud与dubbo都是优秀的微服务解决方案,dubbo很多组件需要自己集成开发,cloud则不需要,相比起来cloud更简单方便,但是dubbo性能要优于cloud,至于用哪个还是需要根据具体情况决定。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jet-W

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值