【愚公系列】2023年10月 Java教学课程 147-Spring框架的简介和基本使用

在这里插入图片描述

🏆 作者简介,愚公搬代码
🏆《头衔》:华为云特约编辑,华为云云享专家,华为开发者专家,华为产品云测专家,CSDN博客专家,阿里云专家博主,阿里云签约作者,腾讯云优秀博主,腾讯云内容共创官,掘金优秀博主,51CTO博客专家等。
🏆《近期荣誉》:2022年CSDN博客之星TOP2,2022年华为云十佳博主等。
🏆《博客内容》:.NET、Java、Python、Go、Node、前端、IOS、Android、鸿蒙、Linux、物联网、网络安全、大数据、人工智能、U3D游戏、小程序等相关领域知识。
🏆🎉欢迎 👍点赞✍评论⭐收藏


🚀一、Spring简介

🔎1.什么是框架

框架是指在软件开发中,为了实现特定功能而提供的一种基础架构或设计模式。框架通常包含了一些通用的模块或库,开发人员可以使用这些模块或库来快速搭建应用,节省开发时间、减少开发成本。框架提供了一些规范化的开发方式,使开发人员更容易理解和使用。常见的框架有Web开发框架、数据库框架、GUI(图形用户界面)框架、游戏引擎框架等。

🔎2.框架的作用

框架是一种软件开发工具,它提供了一套基础结构和组件,以便开发人员可以更快速、更简单地构建应用程序。框架的主要作用如下:

  1. 提高开发效率:框架提供了可重用的代码和组件,开发人员可以更快速地构建应用程序。

  2. 抽象出底层细节:框架可以屏蔽掉底层的技术细节,使开发人员更专注于业务逻辑的开发。

  3. 统一编程风格:框架规定了一套编程规范和风格,使得开发人员之间的代码更易于理解和维护。

  4. 提高应用程序的稳定性和安全性:框架通常包含了对应用程序中常见问题的解决方案,从而提高了应用程序的稳定性和安全性。

  5. 降低开发成本:框架提供了许多预先构建好的组件和功能,减少了开发人员的工作量,从而降低了开发成本。

在这里插入图片描述

🔎3.Spring是什么

Spring是一个Java框架,它提供可用于开发企业级应用程序的各种功能和特性,如依赖注入、面向切面编程、事务管理、Web开发、数据访问等。它的目标是简化Java开发,提高生产力和代码质量。Spring框架由一个核心容器以及众多的扩展模块组成,可以根据应用程序的需要选择合适的模块进行定制化开发。Spring框架已成为Java开发中广泛使用的框架之一。

官网:https://spring.io

🦋3.1 Spring家族

Spring家族是指一系列开源的Java开发框架,以Spring Framework为核心,包括Spring Boot、Spring Cloud、Spring Data、Spring Security等。这些框架提供了一系列的功能和工具,能够帮助开发人员快速构建Java应用程序,提高开发效率和质量。其中,Spring Framework主要提供了IoC容器、AOP、数据访问和Web开发等方面的支持,被广泛应用于企业级应用开发。Spring Boot则是一款快速构建可扩展的生产级Spring应用的框架,通过自动化配置、嵌入式Web服务器和健康检查等功能,简化了应用程序的开发和部署。Spring Cloud则提供了一组工具,用于构建分布式系统中的服务发现、负载均衡、配置管理、断路器等功能。Spring Data则是一个面向数据访问层的框架,提供了对不同类型的数据源的访问和操作支持。Spring Security则提供了身份验证、授权和安全性等方面的支持,能够帮助应用程序保障安全性。
在这里插入图片描述

🦋3.2 Spring发展史

Spring是一个开源的Java应用程序框架,它的发展历史可以分为以下几个阶段:

  • Spring 1.x:2002年,Rod Johnson首次发布了Spring框架,它的目标是简化企业应用程序的开发。Spring 1.x版本提供了依赖注入、AOP、JDBC等基本功能。
  • Spring 2.x:2006年,Spring 2.0发布,引入了新的特性如Spring MVC、Java 5注解和Java EE
    5集成。此时Spring开始成为企业级Java应用程序开发的事实标准。
  • Spring 3.x:2009年,Spring 3.0发布,引入了Java 6和Java EE 6的支持,增强了对注解的支持,引入了Spring Expression Language(SpEL)等新特性。
  • Spring 4.x:2013年,Spring 4.0发布,引入了对Java 8和Java EE 7的支持,增强了REST和WebSocket的支持,改进了测试和日志等方面。
  • Spring 5.x:2017年,Spring 5.0发布,引入了对Java 9和Reactive编程的支持,改进了WebFlux框架,提高了性能和可扩展性。

在这里插入图片描述
在这里插入图片描述

🔎4.Spring的体系结构

Spring是一个面向企业级应用的开源框架,它的体系结构可以从以下几个方面来介绍:

  1. 核心容器(Core Container):Spring的核心容器由四个模块组成,即Spring Core、Spring Beans、Spring Context和Spring Expression Language(SpEL)。Spring Core提供了框架最基础的功能和API支持。Spring Beans是一个面向对象编程的框架,主要负责对象的创建、管理、依赖注入和周期管理等。Spring Context是一个中心接口,它提供了访问Bean容器、消息源和Spring AOP等功能。SpEL是一种表达式语言,支持在运行时通过表达式访问对象、调用方法、进行算数运算等。

  2. AOP(Aspect Oriented Programming):Spring的AOP模块提供了面向切面编程的支持。通过AOP可以将应用程序的业务逻辑和通用的横切关注点(如日志、安全等)进行分离,从而提高了应用程序的可重用性、可扩展性和可维护性等。

  3. 数据访问与集成(Data Access/Integration):Spring的数据访问与集成模块提供了对各种数据持久化技术的支持,如JDBC、ORM、JPA、Hibernate、MyBatis等。另外,Spring还提供了集成其他开源框架(如Apache Camel、Apache Kafka、RabbitMQ等)的功能支持。

  4. Web(Spring Web):Spring提供了SpringMVC和Spring WebFlux两个Web框架。SpringMVC是一个基于Servlet的MVC框架,提供了处理请求、渲染视图等功能;Spring WebFlux是一个响应式Web框架,支持异步和非阻塞的编程模型。

  5. 测试(Testing):Spring提供了用于单元测试和集成测试的模块,如Spring Test和Spring Boot Test。这些模块可以帮助开发人员快速编写和运行测试用例,从而确保应用程序的质量和可靠性。

在这里插入图片描述

在这里插入图片描述

Spring框架的体系结构是非常庞大和复杂的,但其模块化的设计使得开发者可以根据自己的需求选择相应的模块来使用。同时,Spring框架的灵活性和可扩展性也为开发者提供了很多便利。

🔎5.Spring Framework路线

在这里插入图片描述

🚀二、IoC简介

🔎1.优质程序代码的制作原则

  • 可读性高:程序代码应该易于阅读和理解,包括适当的注释,良好的命名和格式化。
  • 可维护性高:程序代码应该易于维护,包括易于调试,易于修改和易于扩展。
  • 可重用性高:程序代码应该易于重用,包括可重用的函数、类和模块。
  • 健壮性高:程序代码应该具有健壮性,能够处理各种异常情况和错误。
  • 性能高:程序代码应该具有高性能,能够快速响应用户的请求和处理大量数据。
  • 安全性高:程序代码应该具有高安全性,能够保证用户的隐私和数据的安全。
  • 可移植性高:程序代码应该易于移植,能够在不同的平台和环境下运行。

🔎2.耦合与内聚

耦合和内聚是软件工程中两个非常重要的概念,它们描述了模块之间的关系和模块内部的结构。

  • 耦合:指的是模块之间的依赖关系,也就是一个模块对另一个模块的调用和使用。耦合分为紧耦合和松耦合两种。紧耦合表示两个模块之间的依赖程度很高,一个模块的改变会影响到另一个模块,导致系统的稳定性和可扩展性下降;松耦合则表示两个模块之间的依赖程度较低,一个模块的改变不会影响到另一个模块,系统的稳定性和可扩展性较高。
  • 内聚:指的是模块内部的结构,也就是一个模块内部各个元素之间的联系和关系。内聚分为强内聚和弱内聚两种。强内聚表示一个模块内部各个元素之间的联系和关系很强,具有明确的功能和目的,易于维护和修改;弱内聚则表示一个模块内部各个元素之间的联系和关系较弱,功能不够明确,难以维护和修改。

在这里插入图片描述

在软件设计中,应该尽量使模块之间的耦合度低、内聚度高,以便提高系统的稳定性、可扩展性和可维护性。

🔎3.工厂模式发展史

工厂模式是一种软件设计模式,旨在通过将对象的创建委托给一个专门的类来解决对象创建的问题。它的历史可以追溯到20世纪60年代,当时软件工程师开始探索如何有效地创建和组织大型软件系统。

在早期,工厂模式被广泛用于创建和管理对象的实例。然而,这种方法存在一些问题,包括代码重复和维护成本高。为了解决这些问题,软件工程师开始探索其他方法,如单例模式和抽象工厂模式。

在90年代,工厂模式开始得到更广泛的应用,尤其是在面向对象编程语言和框架中。此外,随着互联网和分布式系统的发展,工厂模式成为更加重要的设计模式,因为它可以帮助开发人员有效地处理大量的对象实例。

今天,工厂模式仍然是面向对象编程的重要组成部分,被广泛应用于各种编程语言和框架中。同时,它也在不断发展和演变,以满足不断变化的软件开发需求。

第一阶段:
在这里插入图片描述
第二阶段:
在这里插入图片描述
第三阶段:
在这里插入图片描述

🔎4.Spring发展历程

在这里插入图片描述

🔎5.IoC

  • IoC(Inversion Of Control)控制反转,Spring反向控制应用程序所需要使用的外部资源

  • Spring控制的资源全部放置在Spring容器中,该容器称为IoC容器

在这里插入图片描述

🚀三、入门案例

🔎1.案例环境说明

  • 模拟三层架构中表现层调用业务层功能

    • 表现层:UserApp模拟UserServlet(使用main方法模拟)

    • 业务层:UserService

🔎2.IoC入门案例制作步骤

🦋2.1 IoC入门案例制作步骤-1

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.1.9.RELEASE</version>
</dependency>

🦋2.2 IoC入门案例制作步骤-2

public interface UserService {
	//业务方法  
	void save();
}

🦋2.3 IoC入门案例制作步骤-3

public class UserServiceImpl implements UserService {
    public void save() {
        System.out.println("user service running...");
    }
}

🦋2.4 IoC入门案例制作步骤-4

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- 1.创建spring控制的资源-->
    <bean id="userService" class="com.itheima.service.impl.UserServiceImpl"/>
</beans>

🦋2.5 IoC入门案例制作步骤-5

public class UserApp {
    public static void main(String[] args) {
        //2.加载配置文件
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        //3.获取资源
        UserService userService = (UserService) ctx.getBean("userService");
        userService.save();
    }
}

在这里插入图片描述

🚀四、IoC配置(XML格式)

🔎1.Bean的配置

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

🔎2.Bean作用范围

在这里插入图片描述

scope的取值不仅仅只有singleton和prototype,还有request、session、application、 websocket ,表示创建出的对象放置在web容器(tomcat)对应的位置。比如:request表示保存到request域中。
在这里插入图片描述
在这里插入图片描述

🔎3.bean生命周期

🦋3.1 Bean生命周期控制

  • 提供生命周期控制方法
public class BookDaoImpl implements BookDao {
    public void save() {
        System.out.println("book dao save ...");
    }
    //表示bean初始化对应的操作
    public void init(){
        System.out.println("init...");
    }
    //表示bean销毁前对应的操作
    public void destory(){
        System.out.println("destory...");
    }
}
  • applicationContext.xml配置
<!--init-method:设置bean初始化生命周期回调函数,此处填写init方法名-->
<!--destroy-method:设置bean销毁生命周期回调函数,仅适用于单例对象,此处填写destory方法名-->
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl" init-method="init" destroy-method="destory"/>
  • 测试类
public class AppForLifeCycle {
    public static void main( String[] args ) {
        //此处需要使用实现类类型,接口类型没有close方法
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        BookDao bookDao = (BookDao) ctx.getBean("bookDao");
        bookDao.save();
        //关闭容器,执行销毁的方法
        ctx.close();
    }
}

🦋3.2 Bean销毁时机

  • 容器关闭前触发bean的销毁
  • 关闭容器方式:
    • 手工关闭容器
      调用容器的close()操作
    • 注册关闭钩子(类似于注册一个事件),在虚拟机退出前先关闭容器再退出虚拟机
      调用容器的registerShutdownHook()操作
public class AppForLifeCycle {
    public static void main( String[] args ) {
        //此处需要使用实现类类型,接口类型没有close方法
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

        BookDao bookDao = (BookDao) ctx.getBean("bookDao");
        bookDao.save();
        //注册关闭钩子函数,在虚拟机退出之前回调此函数,关闭容器
        ctx.registerShutdownHook();
        //关闭容器
        //ctx.close();
    }
}

🔎4.Bean的实例化

package com.itheima.service;

import com.itheima.service.impl.UserServiceImpl;

public class UserServiceFactory {
    public static UserService getService(){
        System.out.println("factory create object...");
        return new UserServiceImpl();
    }
}

1、factory-bean

  • 名称:factory-bean

  • 类型:属性

  • 归属:bean标签

  • 作用:定义bean对象创建方式,使用静态工厂的形式创建bean,兼容早期遗留系统的升级工作

  • 格式:

    <bean id="userService4" class="com.itheima.service.UserServiceFactory" factory-method="getService"/>
    
  • 取值:工厂bean中用于获取对象的静态方法名

  • 注意事项:

    • class属性必须配置成静态工厂的类名

2、factory-bean,factory-method

  • 名称:factory-bean,factory-method

  • 类型:属性

  • 归属:bean标签

  • 作用:定义bean对象创建方式,使用实例工厂的形式创建bean,兼容早期遗留系统的升级工作

  • 格式:

    <!--实例工厂对应的bean-->
    <bean id="factoryBean" class="com.itheima.service.UserServiceFactory"/>
    <!--实例工厂创建bean,依赖工厂对象对应的bean-->
    <bean id="userService5" factory-bean="factoryBean" factory-method="getService" />
    
  • 取值:工厂bean中用于获取对象的实例方法名

  • 注意事项:

    • 使用实例工厂创建bean首先需要将实例工厂配置bean,交由spring进行管理

    • factory-bean是实例工厂的beanId

🔎5.DI

  • IoC(Inversion Of Control)控制翻转,Spring反向控制应用程序所需要使用的外部资源

  • DI(Dependency Injection)依赖注入,应用程序运行依赖的资源由Spring为其提供,资源进入应用程序的方式称为注入

在这里插入图片描述

IoC是一种编程思想,DI是实现IoC的一种方式。在使用IoC容器时,DI是其中最基本的功能,也是实现IoC的核心。因此,IoC和DI是密不可分的。

🦋5.1 set注入(主流)

1、引用类型
在这里插入图片描述
2、简单类型
在这里插入图片描述

🦋5.2 构造器注入

1、引用类型
在这里插入图片描述

2、简单类型

  1. 给BookDao添加整数类型的构造方法

    /**
     * 书籍DAO的实现类
     */
    public class BookDaoImpl implements BookDao {
    
        //成员变量
        private int connectionNumber;
    
        //无参的构造方法
        public BookDaoImpl() {
        }
    
        //1个参数的构造方法
        public BookDaoImpl(int connectionNumber) {
            this.connectionNumber = connectionNumber;
        }
    
        @Override
        public String toString() {
            return "BookDaoImpl{" +
                    "connectionNumber=" + connectionNumber +
                    '}';
        }
    
    }
    
  2. 使用配置通过构造器给BookDao注入属性值

    <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
        <constructor-arg name="connectionNumber" value="10"/>
    </bean>
    

🦋5.3 参数适配

  1. 按类型的方式匹配
  2. 按位置的方式匹配
☀️5.3.1 步骤
  1. 添加新的成员变量:String databaseName

  2. 创建2个参数的构造方法

  3. 生成toString方法

    package com.itheima.dao.impl;
    
    import com.itheima.dao.BookDao;
    
    /**
     * 书籍DAO的实现类
     */
    public class BookDaoImpl implements BookDao {
    
        //成员变量
        private int connectionNumber;
        private String databaseName;
    
        //无参的构造方法
        public BookDaoImpl() {
        }
    
        //1个参数的构造方法
        public BookDaoImpl(int connectionNumber) {
            this.connectionNumber = connectionNumber;
        }
    
        //2个参数的构造方法
        public BookDaoImpl(int connectionNumber, String databaseName) {
            this.connectionNumber = connectionNumber;
            this.databaseName = databaseName;
        }
    
        @Override
        public String toString() {
            return "BookDaoImpl{" +
                    "connectionNumber=" + connectionNumber +
                    ", databaseName='" + databaseName + '\'' +
                    '}';
        }
    }
    
  4. 分别使用类型匹配和位置匹配的方式注入值

在这里插入图片描述

🦋5.4 自动装配

☀️5.4.1 自动装配概念
  • IoC容器根据bean所依赖的资源在容器中自动查找并注入到bean中的过程称为自动装配
  • 自动装配方式
    • 按名称
    • 按构造方法
    • 不启用自动装配
☀️5.4.2 自动装配类型
🌈5.4.2.1 依赖自动装配

配置中使用bean标签autowire属性设置自动装配的类型

<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl" autowire="byType"/>
🌈5.4.2.2 依赖自动装配特征
  1. 自动装配用于引用类型依赖注入,不能对简单类型进行操作
  2. 使用按类型装配时(byType)必须保障容器中相同类型的bean唯一,推荐使用
  3. 使用按名称装配时(byName)必须保障容器中具有指定名称的bean,不推荐使用
  4. 自动装配优先级低于setter注入与构造器注入,同时出现时自动装配配置失效
<bean id="bookDao1" class="com.itheima.dao.impl.BookDaoImpl">
    <constructor-arg type="int" value="10"/>
    <constructor-arg type="java.lang.String" value="mysql"/>
</bean>

<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
    <constructor-arg type="int" value="20"/>
    <constructor-arg type="java.lang.String" value="oracle"/>
</bean>

<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl" autowire="byName">
</bean>

🔎8.集合类型数据注入

🦋8.1 注入数组类型数据

<property name="array">
    <array>
        <value>100</value>
        <value>200</value>
        <value>300</value>
    </array>
</property>

🦋8.2 注入List类型数据

<property name="list">
    <list>
        <value>itcast</value>
        <value>itheima</value>
        <value>boxuegu</value>
        <value>chuanzhihui</value>
    </list>
</property>

🦋8.3 注入Set类型数据

<property name="set">
    <set>
        <value>itcast</value>
        <value>itheima</value>
        <value>boxuegu</value>
        <value>boxuegu</value>
    </set>
</property>

🦋8.4 注入Map类型数据

<property name="map">
    <map>
        <entry key="country" value="china"/>
        <entry key="province" value="henan"/>
        <entry key="city" value="kaifeng"/>
    </map>
</property>

🦋8.5 注入Properties类型数据

<property name="properties">
    <props>
        <prop key="country">china</prop>
        <prop key="province">henan</prop>
        <prop key="city">kaifeng</prop>
    </props>
</property>

说明:property标签表示setter方式注入,构造方式注入constructor-arg标签内部也可以写<array>、<list>、<set>、<map>、<props>标签

🔎9.使用p命名空间简化配置

一个完整的使用p命名空间简化Bean配置的例子如下:

首先,我们定义一个UserDao接口和它的实现类UserDaoImpl:

public interface UserDao {
    void save(User user);
}
public class UserDaoImpl implements UserDao {
    public void save(User user) {
        System.out.println("Saving user: " + user);
    }
}

然后,我们定义一个UserService类,它依赖于UserDao和EmailService:

public class UserService {
    private UserDao userDao;
    private EmailService emailService;
    private String defaultRole;
    
    public UserService(UserDao userDao, EmailService emailService, String defaultRole) {
        this.userDao = userDao;
        this.emailService = emailService;
        this.defaultRole = defaultRole;
    }
    
    public void register(User user) {
        userDao.save(user);
        String message = "Welcome to our site! Your role is " + defaultRole;
        emailService.send(user.getEmail(), "Registration Successful", message);
    }
}

最后,我们使用p命名空间来简化Bean的配置:

<bean id="userDao" class="com.example.UserDaoImpl" />

<bean id="emailService" class="com.example.EmailServiceImpl" />

<bean id="userService" class="com.example.UserService"
      p:userDao-ref="userDao"
      p:emailService-ref="emailService"
      p:defaultRole="user" />

在这个例子中,我们定义了三个Bean对象:userDao、emailService和userService。其中,userService使用了p命名空间来简化属性注入的配置。它依赖于userDao和emailService,并设置了defaultRole属性。

这样,我们就可以通过Spring容器来获取userService对象,并调用它的register方法来完成用户注册,而不需要手动注入userDao和emailService。使用p命名空间可以让我们更轻松、快速地配置Bean对象。

🔎10.SpEL

我们可以使用SpEL(Spring表达式语言)来访问对象的属性、方法和其他元素。

依赖配置:

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-expression</artifactId>
  <version>5.1.9.RELEASE</version>
</dependency>
  1. 创建两个Java类:User和Address,其中User类包含一个Address对象。
public class User {
    private String name;
    private Address address;

    // getter and setter methods
    // ...
}

public class Address {
    private String city;
    private String state;

    // getter and setter methods
    // ...
}
  1. 在Spring的配置文件中定义两个Bean:user和address,并使用SpEL包含引用类型和值类型来设置属性值。
<bean id="address" class="com.example.Address">
    <property name="city" value="New York" />
    <property name="state" value="NY" />
</bean>

<bean id="user" class="com.example.User">
    <property name="name" value="John" />
    <property name="address" value="#{address}" />
</bean>

在这个案例中,我们使用SpEL来设置User对象的name属性的值为"John",并使用SpEL包含引用类型来设置User对象的address属性的值为address Bean。

  1. 在一个Java类中获取User Bean,并访问User对象的属性和Address对象的属性。
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
 
public class MainApp {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        User user = (User)context.getBean("user");
        
        System.out.println("Name: " + user.getName());
        System.out.println("City: " + user.getAddress().getCity());
        System.out.println("State: " + user.getAddress().getState());
    }
}

在这个案例中,我们使用ApplicationContext来获取user Bean,并使用SpEL包含引用类型来访问User对象的address属性的值。运行该程序,输出结果如下:

Name: John
City: New York
State: NY

🚀感谢:给读者的一封信

亲爱的读者,

我在这篇文章中投入了大量的心血和时间,希望为您提供有价值的内容。这篇文章包含了深入的研究和个人经验,我相信这些信息对您非常有帮助。

如果您觉得这篇文章对您有所帮助,我诚恳地请求您考虑赞赏1元钱的支持。这个金额不会对您的财务状况造成负担,但它会对我继续创作高质量的内容产生积极的影响。

我之所以写这篇文章,是因为我热爱分享有用的知识和见解。您的支持将帮助我继续这个使命,也鼓励我花更多的时间和精力创作更多有价值的内容。

如果您愿意支持我的创作,请扫描下面二维码,您的支持将不胜感激。同时,如果您有任何反馈或建议,也欢迎与我分享。

在这里插入图片描述

再次感谢您的阅读和支持!

最诚挚的问候, “愚公搬代码”

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

愚公搬代码

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

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

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

打赏作者

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

抵扣说明:

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

余额充值