Spring框架入门

目录

Spring框架概述

1.Spring框架组成

2.使用spring有什么好处

3.为什么使用spring框架

4.Spring快速入门

4.1Spring IoC底层实现原理

4.2下载Spring的开发包

4.3创建web项目,引入Spring的开发包

4.4创建接口和实现类

4.5将实现类交给Spring管理

4.6编写测试类

5.Spring IoC和DI的区别

6.Spring的工厂类

6.1Spring工厂类的结构图

6.2BeanFactory:老版本的工厂类

6.3ApplicationContext:新版本的工厂类


Spring框架概述

官方文档介绍https://spring.io/

Why Spring?

Spring makes programming Java quicker, easier, and safer for everybody. Spring’s focus on speed, simplicity, and productivity has made it the world's most popular Java framework.

Web Applications

Spring makes building web applications fast and hassle-free. By removing much of the boilerplate code and configuration associated with web development, you get a modern web programming model that streamlines the development of server-side HTML applications, REST APIs, and bidirectional, event-based systems.

好像还是没有看懂到底是什么东西,我们来详细说明一下:

Spring框架是一个于2003年兴起的轻量级的Java开发框架,由Rod Johnson在其著作Expert One-On-One J2EE Development and Design中阐述的部分理念和原型衍生而来,它是为了解决企业应用开发的复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许使用者选择使用哪一个组件,同时为J2EE应用程序开发提供集成的框架。Spring使用基本的JavaBean来完成以前只可能由EJB完成的事情,是针对bean的生命周期进行管理的轻量级容器。Spring可以单独应用于构筑应用程序,也可以和Struts、Webwork、Tapestry等众多Web框架组合使用,并且可以与 Swing等桌面应用程序AP组合。因此, Spring不仅仅能应用于JEE应用程序之中,也可以应用于桌面应用程序以及小应用程序之中。Spring框架主要由七部分组成,分别是 Spring Core、 Spring AOP、 Spring ORM、 Spring DAO、Spring Context、 Spring Web和 Spring Web MVC。Spring的核心是控制反转(IoC)和面向切面(AOP)。简单来说,Spring是一个分层的JavaSE/EEfull-stack(一站式)轻量级开源框架。

Spring核心组件

1)IoC(Inversion of Control)控制反转

对象创建责任的反转,在spring中BeanFacotory是IoC容器的核心接口,负责实例化,定位,配置应用程序中的对象及建立这些对象间的依赖。XmlBeanFacotory实现BeanFactory接口,通过获取xml配置文件数据,组成应用对象及对象间的依赖关系。

spring中有三种注入方式,一种是set注入,一种是接口注入,另一种是构造方法注入。

2)AOP面向切面编程

AOP就是纵向的编程,如下图所示,业务1和业务2都需要一个共同的操作,与其往每个业务中都添加同样的代码,不如写一遍代码,让两个业务共同使用这段代码。

Spring中面向切面变成的实现有两种方式,一种是动态代理,一种是CGLIB,动态代理必须要提供接口,而CGLIB实现是有继承。

为什么说Spring是一个一站式的轻量级开源框架呢?EE开发可分成三层架构,针对JavaEE的三层结构,每一层Spring都提供了不同的解决技术。

在这里插入图片描述

1.Spring框架组成

Spring框架有七个模块组成组成,这7个模块(或组件)均可以单独存在,也可以与其它一个或多个模块联合使用。

Spring架构图,如下:

 

  • Spring core:提供Spring框架的基本功能。核心容器的主要组件是beanfactory,它是工厂模式的实现。beanfactory使用控制反转(ioc)模式将应用程序的配置和依赖性规范与实际的应用代码程序分开。
  • Spring aop:通过配置管理特性,Springaop模块直接面向方面的编程功能集成到了Spring框架中,所以可以很容易的使Spring框架管理的任何对象支持aop。Springaop模块为基于Spring的应用程序中的对象提供了事务管理服务。通过使用Springaop,不用依赖于ejb组件,就可以将声明性事务管理集成到应用程序中。
  • Spring orm:Spring框架集成了若干orm框架,从而提供了orm的对象关系工具,其中包括jdo、hibernate、ibatis和toplink。所有这些都遵从Spring的通用事务和dao异常层结构。
  • Spring dao:jdbcdao抽象层提供了有意义的异常层次的结构,可用该结构来管理异常处理和不同数据供应商抛出的异常错误信息。异常层次结构简化了错误处理,并且大大的降低了需要编写的异常代码数量(例如,打开和关系连接)。Springdao的面向jdbc的异常遵从通用的dao异常层结构。
  • Spring web:web上下文模块建立在上下文模块(context)的基础之上,为基于web服务的应用程序提供了上下文的服务。所以Spring框架支持jakartastruts的集成。web模块还简化了处理多部分请求及将请求参数绑定到域对象的工作。
  • Spring context:Spring上下文是一个配置文件,向Spring框架提供上下文信息。Spring上下文包括企业服务,例如jndi、ejb、电子邮件、国际化校验和调度功能。
  • Spring mvc:Spring的mvc框架是一个全功能的构建web应用程序的mvc实现。通过策略接口,mvc框架变成为高度可配置的,mvc容纳的大量视图技术,包括jsp、velocity、tiles、itext和pol。

2.使用spring有什么好处

  • spring能有效地组织你的中间层对象,无论你是否选择使用了ejb。如果你仅仅使用了struts或其他的包含了j2ee特有apis的framework,你会发现spring关注了遗留下的问题。
  • spring能消除在许多工程上对singleton的过多使用。根据我的经验,这是一个主要的问题,它减少了系统的可测试性和面向对象特性。
  • spring能消除使用各种各样格式的属性定制文件的需要,在整个应用和工程中,可通过一种一致的方法来进行配置。曾经感到迷惑,一个特定类要查找迷幻般的属性关键字或系统属性,为此不得不读javadoc乃至源编码吗?有了spring,你可很简单地看到类的javabean属性。倒置控制的使用(在下面讨论)帮助完成这种简化。
  • spring能通过接口而不是类促进好的编程习惯,减少编程代价到几乎为零。
  • spring被设计为让使用它创建的应用尽可能少的依赖于他的apis。在spring应用中的大多数业务对象没有依赖于spring。◆使用spring构建的应用程序易于单元测试。
  • spring能使ejb的使用成为一个实现选择,而不是应用架构的必然选择。你能选择用pojos或localejbs来实现业务接口,却不会影响调用代码。
  • spring帮助你解决许多问题而无需使用ejb。spring能提供一种ejb的替换物,它们适于许多web应用。例如,spring能使用aop提供声明性事务而不通过使用ejb容器,如果你仅仅需要与单个的数据库打交道,甚至不需要jta实现。
  • spring为数据存取提供了一致的框架,不论是使用jdbc或o/rmapping产品(如hibernate)。
  • spring确实使你能通过最简单可行的解决办法解决你的问题。这些特性是有很大价值的。

总结起来,spring有如下优点:

  • 低侵入式设计,代码污染极低
  • 独立于各种应用服务器,可以真正实现writeonce,runanywhere的承诺
  • spring的di机制降低了业务对象替换的复杂性
  • spring并不完全依赖于spring,开发者可自由选用spring框架的部分或全部

 

3.为什么使用spring框架

在不使用spring框架之前,我们的service层中要使用dao层的对象,不得不在service层中new一个对象。如下:

//dao层对象
public class UserDao{
   publicvoid insert(User user){}
}
 
//service层对象
public classUserService{
   publicvoid insert(User user){
       UserDaouserdao = new UserDao();
       userdao.insert(user);
   }
}

存在的问题:层与层之间的依赖。

使用框架后:

//dao层对象
public class UserDao{
    publicvoid insert(User user){}
}


 
//service层对象
public classUserService{
   privateUserDao userdao;
 
   publicUserDao getUserdao() {
      returnuserdao;
   }
   publicvoid setUserdao(UserDao userdao) {
      this.userdao= userdao;
   }
 
   publicvoid insert(User user){
      userdao.insert(user);
   }
 
}

service层要用dao层对象需要配置到xml配置文件中,至于对象是怎么创建的,关系是怎么组合的都交给了spring框架去实现。

 

4.Spring快速入门

需要说明的是,本系列入门Spring的教程使用的版本是spring-framework-4.2.4.RELEASE。

4.1Spring IoC底层实现原理

IoC即Inversion of Control,反应过来就是控制反转。啥是控制反转啊?控制反转指的就是将对象的创建权反转给(交给)了Spring,其作用是实现了程序的解耦合。也可这样解释:获取对象的方式变了,对象创建的控制权不是"使用者",而是"框架"或者"容器"。用更通俗的话来说,IoC就是指对象的创建,并不是在代码中用new操作new出来的,而是通过Spring进行配置创建的。

这里先给出结论:Spring的IoC的底层实现原理是工厂设计模式+反射+XML配置文件。 就拿持久层(也即dao层,data access object,数据访问对象)的开发来说,官方推荐做法是先创建一个接口,然后再创建接口对应的实现类。所以,这里,我会以dao层的开发为例来证明Spring的IoC的底层实现原理就是工厂设计模式+反射+XML配置文件。
首先,创建一个Userdao接口。

public interface UserDao {
	public void add();
}

然后,再创建Userdao接口的一个实现类(UserDaoImpl.java)。

public class UserDaoImpl implements UserDao {
    public void add() {
	    balabala......
    }
}

接着,我们在service层中调用dao层,核心代码如下:

// 接口 实例变量 = new 实现类
UserDao dao = new UserDaoImpl();
dao.add();

这时我们便可发现一个缺点:service层和dao层耦合度太高了,即接口和实现类有耦合(它俩之间的联系过于紧密),一旦切换底层实现类,那么就需要修改源代码,这真的不是一个好的程序设计,好的程序设计应当满足OCP原则(也即开闭原则),即在尽量不修改程序源代码的基础上对程序进行扩展。说到这里,我就不得不稍微讲一下面向对象设计的七大原则了,它不必强记,重在理解。
在这里插入图片描述
出现的这个问题该如何解决呢?解决方法是使用工厂设计模式进行解耦合操作。所以,我们需要创建一个工厂类,在工厂类中提供一个方法,返回实现类的对象。

public class BeanFactory {
    // 提供返回实现类对象的方法
    public static UserDao getUserDao() {
        return new UserDaoImpl();
    }
}

这样,在service层中调用dao层的核心代码就变为了下面的样子。

UserDao dao = BeanFactory.getUserDao();
dao.add();

如若这样做,会发现又产生了一个缺点:现在接口和实现类之间是没有耦合了,但是service层和工厂类耦合了。如果真正想实现程序之间的解耦合,那么就需要使用到工厂设计模式+反射+XML配置文件了。所以,我们这里提供一个XML配置文件,并且该配置文件中有如下配置信息。

<bean id="userDao" class="com.meimeixia.dao.impl.UserDaoImpl" />

然后再来创建一个工厂类,在工厂类中提供一个返回实现类对象的方法,但并不是直接new实现类,而是使用SAX解析配置文件,根据标签bean中的id属性值得到对应的class属性值,使用反射创建实现类对象。

public class BeanFactory {
    public static Object getBean(String id) {
        // 1.使用SAX解析得到配置文件内容
        // 直接根据id值userDao得到class属性值
        String classvalue = "class属性值";
        // 2.使用反射得到对象
        Class clazz = Class.forName(classvalue);
        UserDaoImpl userDaoImpl = (UserDaoImpl)lazz.newInstance();
        return userDaoImpl;
    }
}

以上就是Spring的IoC的底层实现原理。

4.2下载Spring的开发包

Spring的官网是https://spring.io/,Spring的开发包的下载地址是https://repo.spring.io/libs-release-local/org/springframework/spring/,上面说过,我下载的是spring-framework-4.2.4.RELEASE这个版本的Spring。解压缩之后,可发现Spring开发包的目录结构如下。
在这里插入图片描述

4.3创建web项目,引入Spring的开发包

首先创建一个动态web项目,例如spring_demo01,然后导入Spring框架相关依赖jar包,要导入哪些jar包呢?这是一个问题。
在这里插入图片描述
由于我们只是初次入门Spring,所以也只是使用到了Spring的基本功能,从上图红框框中的部分可知,我们需要使用到下面的这4个jar包。
在这里插入图片描述
除此之外,还要导入Spring支持的日志jar包,也就是下面两个jar包。
在这里插入图片描述
关于以上两个jar包,我稍微做一下解释,com.springsource.org.apache.commons.logging-1.1.1.jar它里面都是一些接口,有接口,那当然要有实现类了,恰好,com.springsource.org.apache.log4j-1.2.15.jar里面就是那些接口的实现类。使用Log4j,我们可以查看到当前运行程序中对象创建的过程,也可以看到更详细的信息,Log4j适合使用在程序调试中。
导入完日志相关的jar包之后,我们还要导入日志记录文件,即在src目录下引入Log4j的配置文件(log4j.properties),该文件内容如下:

### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.err
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

### direct messages to file mylog.log ###
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.File=c\:mylog.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

### set log levels - for more verbose logging change 'info' to 'debug' ###
# error warn info debug trace
log4j.rootLogger= info, stdout

对以上日志记录文件,我会粗略讲解一下。
在这里插入图片描述
从上图可知,log4j.rootLogger就是用于设置日志的输出级别,那么日志的输出级别有几种呢?有如下5种。
在这里插入图片描述

4.4创建接口和实现类

首先,在src目录下创建一个com.meimeixia.spring.demo01包,并在该包下创建一个名为UserDao的接口。

package com.meimeixia.spring.demo01;

/**
 * 用户管理的dao层的接口
 * @author liayun
 *
 */
public interface UserDao {

	public void save();
	
}

然后,在com.meimeixia.spring.demo01包下创建UserDao接口的一个实现类——UserDaoImpl.java。

package com.meimeixia.spring.demo01;

/**
 * 用户管理的dao层接口的实现类
 * @author liayun
 *
 */
public class UserDaoImpl implements UserDao {

	@Override
	public void save() {
		System.out.println("UserDaoImpl中的save方法执行了......");
	}
	
}

4.5将实现类交给Spring管理

首先,我们需要创建Spring的配置文件,Spring配置文件的名称和位置没有固定要求,一般建议把该文件放到src目录下面,名称可随便写,官方建议写成applicationContext.xml。然后我们还需要在配置文件中引入约束,Spring学习阶段的约束是schema约束。那么问题来了,这个约束又该怎么写呢?可参考docs\spring-framework-reference\html目录下的xsd-configuration.html文件,在其内容最后面找到如下内容。
在这里插入图片描述
将其复制黏贴到配置文件applicationContext.xml中,这样applicationContext.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>

最后,咱还要将实现类交给Spring来管理,即在配置文件中配置对象的创建。

<?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">
        
	<!-- Spring入门的配置 -->
	<bean id="userDao" class="com.meimeixia.spring.demo01.UserDaoImpl"></bean>
	
</beans>

4.6编写测试类

我们要在Spring中写代码来实现获取applicationContext.xml文件中配置的对象,这段代码不要求大家重点掌握,只是用在测试中而已。这段代码主要用来解析Spring配置文件得到对象,但这个过程不需要我们写代码来实现了,Spring已经封装了一个对象来帮我们进行了这些操作,这个对象叫ApplicationContext,它就能实现这个功能。于是,我们需要在com.meimeixia.spring.demo01包下创建一个SpringDemo01的单元测试类。

package com.meimeixia.spring.demo01;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

/**
 * Spring的入门
 * @author liayun
 *
 */
public class SpringDemo01 {
	
	/*
	 * 传统方式的调用
	 */
	@Test
	public void demo01() {
		UserDao userDao = new UserDaoImpl();
		userDao.save();
	}
	
	/*
	 * Spring的方式的调用
	 */
	@Test
	public void demo02() {
		//先要创建Spring的工厂
		ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");// classpath就是类路径,src目录下的文件最终要编译到类路径下
		UserDao userDao = (UserDao) applicationContext.getBean("userDao");
		userDao.save();
	}

}

然后,运行以上demo02单元测试方法,Eclipse控制台就会打印如下内容。
在这里插入图片描述

5.Spring IoC和DI的区别

IoC上面我已经讲过了,它指的就是将对象的创建权反转给(交给)了Spring。那DI又是什么鬼呢?DI,即Dependency Injection,翻译过来就是依赖注入,它指的就是Spring在管理某个类的时候会将该类依赖的属性注入(设置)进来,也就是说在创建对象的过程中,向类里面的属性中设置值。注意:依赖注入不能单独存在,须在控制反转基础之上完成,用更通俗点的话来说,就是注入类里面的属性值,不能直接注入,须创建类的对象再完成注入。
你还记得在面向对象设计的时候,类和类之间有几种关系吗?有3种,它们分别是:

  • 依赖,由于下图中B类的方法用到了A类,所以此时就可以说B类依赖于A类;
    在这里插入图片描述
  • 继承(is a),这种关系我们应该是见的要吐了;
    在这里插入图片描述
  • 聚合(has a),它有松散和紧密之分。例如,球队得有一个守门员,即使这个球队没有了这个守门员,它也还是一个球队,所以它是松散的;人得有一个脑袋,此时它就是紧密的。

说了这么多,咱就通过一个案例来看看DI在程序的体现。现在,我们想要给UserDaoImpl这个实现类里面的某一个属性(例如String类型的name)设置值,该咋怎呢?首先,将UserDaoImpl这个实现类修改成下面这个样子。

package com.meimeixia.spring.demo01;

/**
 * 用户管理的dao层接口的实现类
 * @author liayun
 *
 */
public class UserDaoImpl implements UserDao {
	
	private String name;
	
	//提供set方法
	public void setName(String name) {
		this.name = name;
	}

	@Override
	public void save() {
		System.out.println("UserDaoImpl中的save方法执行了......" + name);
	}
	
}

如果使用传统的方式来为UserDaoImpl实现类的name属性设置值,那么SpringDemo01单元测试类中的demo01方法就应该写成下面这个样子。

package com.meimeixia.spring.demo01;

import org.junit.Test;

/**
 * Spring的入门
 * @author liayun
 *
 */
public class SpringDemo01 {
	
	/*
	 * 传统方式的调用
	 */
	@Test
	public void demo01() {
		/*
		 * 我想给这个类里面的某一个属性设置值,挺麻烦的!
		 * 
		 * 1. 不能面向接口编程了
		 * 2. 你还得手动调用set方法,也得去改变程序的源代码
		 */
		UserDaoImpl userDao = new UserDaoImpl();
		userDao.setName("李二");
		userDao.save();
	}

}

这样写,就有两个缺点,一是不能面向接口编程了,二是咱还得手动调用对象的set方法,这必然就涉及到要改变程序的源代码了,这是我们不能接受的!
如果使用了依赖注入,即在applicationContext.xml文件中为配置好的UserDaoImpl实现类的name属性注入一个值,那么情况就完全不同了,以上两个缺点也就不复存在了。

<?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">
        
	<!-- Spring入门的配置 -->
	<bean id="userDao" class="com.meimeixia.spring.demo01.UserDaoImpl">
		<!-- DI:依赖注入 -->
		<property name="name" value="李二" />
	</bean>
	
</beans>

此时,SpringDemo01单元测试类中的demo02方法依然不变,如下。

package com.meimeixia.spring.demo01;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

/**
 * Spring的入门
 * @author liayun
 *
 */
public class SpringDemo01 {
	
	/*
	 * 传统方式的调用
	 */
	@Test
	public void demo01() {		
		/*
		 * 我想给这个类里面的某一个属性设置值,挺麻烦的!
		 * 
		 * 1. 不能面向接口编程了
		 * 2. 你还得手动调用set方法,也得去改变程序的源代码
		 */
		UserDaoImpl userDao = new UserDaoImpl();
		userDao.setName("李二");
		userDao.save();
	}
	
	/*
	 * Spring的方式的调用
	 */
	@Test
	public void demo02() {
		//先要创建Spring的工厂
		ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
		UserDao userDao = (UserDao) applicationContext.getBean("userDao");
		userDao.save();
	}

}

运行以上demo02单元测试方法,Eclipse控制台就会打印出如下内容。
在这里插入图片描述

6.Spring的工厂类

6.1Spring工厂类的结构图

在这里插入图片描述
从上图可以看出ApplicationContext(接口)继承自BeanFactory(接口)。

6.2BeanFactory:老版本的工厂类

BeanFactory是老版本的工厂类,稍微了解一下就好,这个类在实际开发中我们并不需要用到。需要说明的一点是,它只有在调用getBean方法的时候,才会生成Spring所管理的类的实例。

6.3ApplicationContext:新版本的工厂类

ApplicationContext是新版本的工厂类,它在加载配置文件的时候,就会将Spring所管理的类都实例化。ApplicationContext这个接口有两个实现类,如下图所示。
在这里插入图片描述
ClassPathXmlApplicationContext这个实现类前面用过了,下面咱就来用一下FileSystemXmlApplicationContext这个实现类。首先,拷贝一份applicationContext.xml文件到C盘下,其内容做一点点修改。
在这里插入图片描述
然后,在SpringDemo01单元测试类中编写如下一个demo03方法。

package com.meimeixia.spring.demo01;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

/**
 * Spring的入门
 * @author liayun
 *
 */
public class SpringDemo01 {
	
	/*
	 * 传统方式的调用
	 */
	@Test
	public void demo01() {	
		/*
		 * 我想给这个类里面的某一个属性设置值,挺麻烦的!
		 * 
		 * 1. 不能面向接口编程了
		 * 2. 你还得手动调用set方法,也得去改变程序的源代码
		 */
		UserDaoImpl userDao = new UserDaoImpl();
		userDao.setName("李二");
		userDao.save();
	}
	
	/*
	 * Spring的方式的调用
	 */
	@Test
	public void demo02() {
		//先要创建Spring的工厂
		ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
		UserDao userDao = (UserDao) applicationContext.getBean("userDao");
		userDao.save();
	}
	
	/*
	 * 加载磁盘上的配置文件
	 */
	@Test
	public void demo03() {
		//先要创建Spring的工厂
		ApplicationContext applicationContext = new FileSystemXmlApplicationContext("C:\\applicationContext.xml");
		UserDao userDao = (UserDao) applicationContext.getBean("userDao");
		userDao.save();
	}

}

最后,运行以上demo03单元测试方法,Eclipse控制台就会打印出如下内容。
在这里插入图片描述

 

参考文章:

1.Spring入门第一讲——Spring框架的快速入门

2.spring框架学习(一)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

慕城南风

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

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

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

打赏作者

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

抵扣说明:

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

余额充值