Spring

Spring是一个开源框架,它由Rod Johnson创建。
Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。
简单来说,Spring是一个分层的JavaSE/EE full-stack(一站式)轻量级开源框架。

一站式框架:有EE开发的每一层解决方案
WEB层:SpringMVC
Service层:Spring的Bean管理,Spring声明式事务
DAO层:Spring的JDBC模板,Spring的ORM模块

1.方便解耦,简化开发
2.AOP编程的支持
3.声明式事务的支持
4.方便程序的测试
5.方便集成各种优秀框架
6.降低Java EE API的使用难度

Spring4.x整合Hibernate5.x版本

Spring的IOC入门

IOC

控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到象中。

即,将对象的创建权反转给(交给)Spring。

下载

官网:http://spring.io/

Docs:Spring的开发规范和API
libs:Spring的开发的jar和源码
schema:Spring的配置文件的约束

创建WEB工程,引入jar包

beans、context、core、expression
commons.logging、log4j

Spring的底层实现

为了接口和实现类间的解耦,通过工厂+反射+配置文件来完成

将实现类交给Spring管理

在spring的解压路径spring-framework-4.2.4.RELEASE/docs/spring-framework-reference/html/xsd.configuration.html中找:the beans schema
把beans标签内容复制到中applicationContext.xml:使用

applicationContext.xml:
<beans …>

调用:
// 创建Spring的工厂
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("*.xml");
UserDAO userDAO = (UserDAO) applicationContext.getBean(“userDAO”)

applicationContext.close();

IOC和DI

DI:依赖注入,前提必须有IOC的环境,Spring管理这个类的时候将类的依赖的属性注入(设置)进来

实现DI,在bean标签中配置:

Spring的工厂类

BeanFactory

老版本的工厂类
BeanFactory是调用getBeanjf,才会生成类实例

ApplicationContext

新版本的工厂类
ApplicationContext继承了BeanFactory
ApplicationContext加载配置文件时,就会将Spring管理的类都实例化
ApplicationContext有两个实现类
ClassPathXmlApplicationContext
加载类路径下的配置文件
FileSystemXmlApplicationContext
加载文件系统下的配置文件

Spring的配置

XML的提示配置

Bean的相关配置

的id和name的配置

id:使用了约束中的唯一约束,不能出现特殊字符
name:没有使用约束中的唯一约束(实际开发不会重复),可以出现特殊字符
Spring和Struts1框架整合的时候会碰到

Bean的生命周期的配置

setup和destroy是方法名,不是固定的

init-method:Bean被初始化时执行的方法
destroy-method:Bean被销毁时执行的方法(Bean是单例创建)

Bean的作用范围的配置(重点)

scope:Bean的作用范围
singleton:默认,Spring会采用单例模式创建对象
prototype:多例模式(Struts2和Spring整合时一定会用到)
request:应用在web项目中,Spring创建这个类后,将这个类放到request范围中
session:应用在web项目中,Spring创建这个类后,将这个类放到session范围中
globalsession:应用在web项目中,必须在porlet环境下使用。如果没有这个环境,相当于session

Spring的属性注入

三种属性注入方式:构造方法、set方法、接口注入

Spring的属性注入支持构造方法和set方法

构造方法方式的属性注入

需要在类中提供构造方法

set方法方式的属性注入

需要在类中提供set方法



P名称空间的属性注入(Spring2.5之后)

通过引入P名称空间完成属性注入
普通属性 p:属性名=“值”
对象属性 p:属性名-ref=“值”

xmlns:p=“http://www.springframework.org/schema/p”

p:name=“value”

SpEL的属性注入(Spring3.0)

SpEL:Spring Expression Language,Spring的表达式语言

语法:
value="#{SpEL}"
value="#{obj}"
value="#{obj.attr}"
value="#{obj.method()}"

集合属性的注入

list或数组类型:


// 对象类型用

set类型:


// 对象类型用

map类型:




Spring的分模块开发

1.在加载配置文件时,加载多个
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(“1.xml”, “2.xml”);

2.在一个配置文件中引入多个配置文件

Spring的IOC的注解开发

入门

1.引入jar包
在Spring4的版本中,除了引入基本开发包(6个)外,还要引入AOP的包。
2.引入Spring的配置文件
在src下创建applicationcontext.xml
引入约束:使用注解开发引入context约束
3.注解开发的配置
配置组件扫描(哪些包下的类使用IOC注解)
<context:component-scan base-package=""></context:component-scan>
如果Bean类通过xml配置注入,属性注入则通过注解,可以不用配置组件扫描,用下面代替
context:annotation-config/
4.在类上添加注解
@Component(“userDAO”)
上面相当于
5.注解方式设置属性的值
@Value(“wq”)
可以没有set方法
如果有set方法,属性注入的注解添加到set方法上
如果没有set方法,属性注入的注解添加到属性上

IOC注解的详解

@Component:组件

修饰类,将这个类交给Spring管理
衍生了三个注解(目前一样):
1.@Controller,修饰web层类
2.@Service,修饰ervice层类
3.@Repository,修饰DAO层类

属性注入的注解

普通属性 @Value
对象属性 @Autowired

按类型完成的属性注入
为了按名称进行属性注入,在Autowired下面再加一个注解
@Qualifier(value="")

为替代上面两个注解,使用@Resource(name=""),来按照名称完成对象类型的属性注入

Bean的其他注解

1.生命周期要关的注解:
@PostConstruct:初始化方法的注解,相当于init-method
@PreDestroy:销毁方法的注解,相当于destroy-method
2.Bean作用范围的注解:
@Scope:作用范围
singleton、prototype、request、session、globalsession

IOC的XML和注解开发的对比

XML可以适用任何场景,注解有些地方用不了,如这个类不是自己提供时

XML和注解可整合开发
XML管理Bean,注解完成属性注入

Spring的AOP的XML开发

AOP概述

在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

AOP可以进行权限校验、日志记录、性能监控、事务控制

Spring底层AOP的实现原理就是动态代理
1.JDK动态代理:只能对实现了接口的类产生代理
2.Cglib动态代理(类似于javassist第三方代理技术):对没有实现接的类产生代理对象,生成子类对象

JDK动态代理

class JdkProxy implements InvocationHandler{
private UserDao userDao;
public UserDao createProxy() {
return Proxy.newProxyInstance(userDao.getClasss().getClassLoader(), userDao.getClass().getInterfaces(), this);

public Object invoke(object proxy, Method method, Object[] args)
{
	method.invoke();
}

}

cglib动态代理

第三方开源代码生成类库,动态添加类的属性和方法

class CglibProxy implements MethodInterceptor{
private CustomerDao customerDao;
public Customer createProxy() {
// 创建cglib的核心类对象
Enhancer enhancer = new Enhancer();
// 设置父类
enhancer.setSuperclass(customerDao.getClass());
// 设置回调
enhancer.setCallback(this);
// 创建代理对象
return (CustomerDao) enhancer.create();
}
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy){
methodProxy.invokeSuper();
}
}

Spring的AOP开发(AspectJ的XML方式)

Spring的AOP简介

Spring早期对于AOP有自己实现的方式,非常繁锁。AspectJ是一个AOP的框架,后来Spring将其作为自身的AOP开发
传统方式已经弃用,现都是基于AspectJ的AOP开发

AOP开发中的相关术语

Jointpoint:连接点,可以被拦截到的点。
增删改查方法都可以被增强,故都可以称为连接点
Pointcut:切入点,真正被拦截到的点。
在实际开发中,只对save进行了增强,则save称为切入点
Advice:通知或增强
方法层面的增强
现在对save方法进行权限校验,权限校验的方法称为通知
Introduction:引介
类层面的增强
Target:被增强的对象。
Weaving:织入,将通知应用到目标的过程
将权限校验的方法代码应用到对象的save方法上的过程
Proxy:代理对象
Aspect:切面,多个通知和多个切入点的组合

Spring的AOP入门(AspectJ方式)

引入jar包

多引入4个包:
aopalliance:aop联盟包
aspectj.weaver:aspectj开发包,weaver相比tool更小
aop
aspects:spring与aspectj整合的包

引入Spring的配置文件

引入AOP约束:
在xsd-configuration.html中找schema

编写目标类并完成配置

编写测试类

Spring-test是Spring的test包,引入后,可用注解来修饰测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(“classpath:applicationContext.xml”)

测试类注入属性:
@Resource(name="")

编写切面类和配置(即交给Spring管理)

class MyAspectXML{
public void checkPri(){}
}

通过AOP的配置完成对目标类产生代理

aop:config

<aop:pointcut expression=“execution(* 全路径被代理类ProductDaoImpl.save(…))” id=“pointcut1”/>

<aop:aspect ref=“myAspect”>
<aop:before method=“checkPri” pointcut-ref=“pointcut1”/>
</aop:aspect>
</aop:config>

对ProductDaoImpl的save方法配置切面类myAspect(myAspect是id,实际是MyAspectXML)作为代理,myAspect的checkPri方法将对save进行前置增强

Spring中的通知类型

前置通知:在目标方法执行之前进行操作

aop:before/
可以获得切入点信息

checkpri(JoinPoint joinPoint)

后置通知:在目标方法执行之后进行操作

可以获得方法的返回值

<aop:after-returning method=“WriteLog” pointcut-ref="" returning=“result”/>

public void WriteLog(Object result){
}

环绕通知:在目标方法执行之前和之后进行操作

可以阻止目标方法的执行
public object around(ProceedingJoinPoint joinPoint) {
…前
Object obj = joinPoint.proceed();
…后
return obj;
}

<aop:around method=“around” pointcut-ref=""/>

异常抛出通知:在程序出现异常时进行的操作

public void afterThrowing(Throwable ex){
}

<aop:after-throwing method=“afterThrowing” pointcut-ref="" throwing=“ex”/>

最终通知:无论代码是否有异常,总是会执行

public void after(){
}
<aop:after method=“after” pointcut-ref=""/>

引介通知

Spring的切入点表达式写法

基于execution的函数来完成:
[访问修饰符] 方法返回值 包名.类名.方法(参数)

public void com.itheima.spring.CustomerDao.save(…)

:表示任意,上述表达式的任何位置都可以用

com.itheima.spring.CustomerDao+.save(…):表示CustomerDao及其子类
com.itheima.spring….(…):子包下所有类的所有方法

Spring的AOP基于AspectJ的注解开发

在配置文件中开启注解AOP的开发

aop:aspectj-autoproxy/

@Aspect
public class MyAspectAnno {
@Before(value=“execution(* 包名.类名.save(…))”)
public void before(){
}
}

Spring的注解AOP的通知类型

@Before:前置通知

@Before(value=(“execution()”)

@AfterReturning:后置通知

@Before(value=(“execution()”,returning=“result”)

@Around:环绕通知

@Around(value="")

@AfterThrowing:异常抛出通知

@AfterThrowing(value="",throwing=“e”)

@After:最终通知

@After(value="")

切入点注解

@Pointcut(value="")
private void pointcut1(){}

@After(value=“类名.pointcut1()”)

Spring的JDBC模板的使用

Spring的JDBC模板

JDBC:jdbc.core.JdbcTemplate
Hibernate:orm.hibernate3.HibernateTemplate
IBatis:orm.ibatis.SqlMapClientTemplate
JPA:orm.jpa.JpaTemplate

使用入门

引入jar包:基本开发包6个,数据库驱动,Spring的JDBC模板
// 创建连接池
DriveManagerDataSource dataSource = new DriveManagerDataSource();
dataSource.setDriverClassName("");
dataSource.setUrl("");
dataSource.setUsername("");
dataSource.setPassword("");
// 创建JDBC模板
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
jdbcTemplate.update(“insert into accunt values (null,?,?)”…);

将连接池和模板交给Spring管理

引入Spring的配置文件

配置Spring内置的连接池:



<property name=“username value=”"/>

配置Spring的JDBC模板:


使用JDBC的模板

引入spring_aop的jar包

@Resource(name=“jdbcTemplate”)
private JdbcTemplate jdbcTemplate;

使用开源数据库连接池

DBCP

引入jar包:dbcp、pool

jdbc模板的配置一样

C3P0

jar包:c3p0





抽取配置到属性文件

定义一个属性文件:jdbc.properties:
jdbc.driverClass=…

引入属性文件:
1.通过bean引入(较少用)



2.通过context标签引入
<context:property-placeholder location=“classpath:jdbc.properties”/>

使用JDBC模板完成CRUD

jdbcTemplate.queryForObject()

Account account = jdbcTemplate.queryForObject(“select * from account where id = ?”, new MyRowMapper(), 5);

class MyRowMapper implements RowMapper {
public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
Account account = new Account();
account.setId(rs.getInt(“id”));

return account;
}
}

List list = jdbcTemplate.query(“select * from account”, new MyRowMapper());

Spring的事务管理

API

PlatformTransactionManager:平台事务管理器

平台事务管理器:是spring管理事务的真正对象
DataSourceTransactionManager:
底层使用JDBC管理事务
HibernateTransacionManager:
底层使用Hibernate管理事务

TransactionDefinition:事务定义信息

用于定义事务的相关信息,隔离级别、超时信息、传播行为、是否只读

TransactionStatus:事务的状态

用于记录在事务管理过程中事务状态的对象

API的关系

SPING在进行事务管理时,首先平台事务管理器根据事务定义信息进行事务的管理,在事务管理过程中会产生各种状态,记录到事务状态的对象中。

事务的传播行为

提供了7种事务传播行为

用来解决业务层方法相互调用的问题

三类:
1.保证多个操作在同一个事务中
PROPAGATION_REQUIRED
默认值,如果A中有事务,使用A中的事务,如果没有,则创建一个新事务,将操作包含进来
PROPAGATION_SUPPORT
支持事务,如果A中有事务,使用A中的事务,如果没有,则不使用事务
PROPAGATION_MANDATORY
如果A中有事务,使用A中的事务,如果没有,抛出异常
2.保证多个操作不在同一个事务中
PROPAGATION_REQUIRES_NEW
如果A中有事务,将A的事务挂起(暂停),创建新事务,只包含自身的操作,如果没有,创建一个新事务,包含自身操作
PROPAGATION_NOT_SUPPORTED
如果A中有事务,将A事务挂起,不使用事务管理
PROPAGATION_NEVER
如果A中有事务,报异常
3.嵌套式事务
PROPAGATION_NESTED
如果A中有事务,按A事务执行,执行完成后,设置一个保存点,执行B的操作,如果没有异常,执行通过,如果有异常,可以回滚到最初始位置,也可以回滚到保存点

SPRING的事务管理

提供了JdbcDaoSupport,可以让我们自己的DaoImpl继承JdbcDaoSupport,就不需要显示持有JdbcTemplate
且只要注入一个连接池,JdbcDaoSupport就会自动创建JdbcTemplate,这样在配置文件中只要给DAO注入连接池即可,DAO不需要注入jdbc模板了,jdbc模板也不需要注入连接池了

编程式事务(需要手动编码)

1.配置平台事务管理器



2.SPRING提供了事务管理的模板类


3.将事务管理模板注入业务类

4.在业务代码中编写事务管理代码
transactionTemplate.execute(new TransactionCallbackWithoutResult(){
protected void doInTransactionWithoutResult(TransactionStatus arg0){
事务处理代码
}
}

声明式事务管理,通过配置实现 —AOP

1.XML方式
引入AOP开发包
配置事务管理器
配置事务的增强
<tx:advice id=“txAdvice” transaction-manager=“transactionManager”>
tx:attributes
<tx:method name=“save*” propagation=“REQUIRED” isolation=“DEFAULT” read-only=“true”/>
</tx:attributes>
</tx:advice>
AOP的配置
aop:config
<aop:pointcut expression="" id=“pointcut1”/>
<aop:advisor advice-ref=“txAdvice” pointcut-ref=“pointcut1”/>
</aop:config>
2.注解方式
配置事务管理器
开启注解事务
<tx:annotation-driven transaction-manager=“transactionManger”/>
在业务层添加注解
@Transactional

	@Transactional(isolation=Isolation.DEFAULT,propagation=Propagation.REQUIRED)

SSH整合

方式一:无障碍整合

SSH框架回顾

Struts2->Spring->Hibernate

Hibernate:(DAO类中)
class UserDao{
void save(User user){
// 加载核心配置文件hibernate.cfg.xml
Configuration cfg = new Configuration().configure();
// 构建工厂
SessionFactory sf = cfg.buildSessionFactory();
// 获得session
Session session = sf.openSession();
// 开启事务
Transaction tx = session.beginTransaction();
// 执行操作
session.save();
// 事务提交
tx.commit();
session.close();
}
}

Spring:(Service)
class UserSevice {
// 注入UserDao
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void save(User user){
userDao.save(user);
}
}

<bean id=“userService class=”***.UserDao">

Struts2:(web)
class UserAction extends ActionSupport implements ModelDriven{
private User user = new User();
public User getModel() {
return user;
}
public String save() {
ServiceContext sc = ServletActionContext.getServletContext();
WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(sc);
UserService us = wac.getBean(“userService”);
us.save(user);
return saveSuccess;
}
}

SSH整合

创建WEB项目,引入jar包

1.Struts2的jar包:
在struts-2.3.24\apps\struts2-blank\WEB-INF\lib*.jar
struts2-convention-plugin.jar:Struts2的注解开发包
struts2-json-plugin.jar:struts2整合AJAX的开发包
struts2-spring-plugin.jar:struts2整合spring的开发包
2.Hibernate的jar包:
hibernate-release.Final/lib/required/*.jar
MySQL驱动:mysql-connector-java-bin.jar
日志记录
log4j-1.2.16.jar
slf4j-api-1.6.1.jar
slf4j-log4j12-1.7.2.jar
使用c3p0连接池:
c3p0.jar
hibernate-c3p0.Final.jar
mchange-commons.java.jar
Struts2和Hibernate都引入了一个相同的jar包:javassist,需要删除一个
3.Spring的jar包:
IOC的开发:
com.springsource.org.apache.commons.logging.jar
com.springsource.org.apache.log4j.jar
spring-beans.RELEASE.jar
spring-context.RELEASE.jar
spring-core.RELEASE.jar
spring-expression.RELEASE.jar
AOP的开发:
com.springsource.org.aopalliance.jar
com.apringsource.org.aspectj.weaver.RELEASE.jar
spring-app.RELEASE.jar
spring-aspects.RELEASE.jar
JDBC模板的开发:
spring-jdbc.RELEASE.jar
spring-tx.RELEASE.jar
事务管理:
spring-tx.RELEASE.jar
整合web项目的开发:
spring-web.RELEASE.jar
整合单元测试的开发:
spring-test.RELEASE.jar
整合hibernate的开发:
spring-orm.RELEASE.jar

引入配置文件

1.Struts的配置文件:web.xml、struts.xml
web.xml:
struts2的核心 过滤器

struts2
Struts2PrepareAndExecuteFilter

2.Hibernate的配置文件:hibernate.cfg.xml、映射文件
hibernate.cfg.xml:删除与线程绑定的session
3.Spring的配置文件:web.xml、applicationContext.xml、日志记录
web.xml:
spring的核心监听器

org.springframework.web.context.ContextLoaderListener<\listener-class>

加载spring的配置文件路径,默认加载/WEB-INF/appliationContext.xml

contextConfigLocation
classpath:applicationContext.xml

日记记录:Log4j.properties

创建包结构

ssh.dao(子包impl)、ssh.domain、ssh.service(子包impl)、ssh.web.action

创建相关类

Customer.java

class Customer {
}

CustomerAction.java

class CustomerAction extends ActionSupport implements ModelDriven{
// 模型驱动使用的对象
private Customer customer = new Customer();
public Customer getModel() {
return null;
}
// 保存客户的方法
public String save() {
return NONE;
}
}

CustomerService.java

interface CustomerService{
}

CustomerServiceImpl.java

class CustomerServiceImpl implements CustomerService {
}

CustomerDao.java

class interface CustomerDao {
}

CustomerDaoImpl.java

class CustomerDaoImpl implements CustomerDao {
}

引入相关的页面

修改add.jsp

Spring整合Struts2方式一(action由struts2自身创建)

1.编写Action
2.配置Action
在struts.xml中配置action




3.在action中引入service
传统方式:
WebApplicationContext applicationContext = WebApplicationContextUtils.getWebApplicationContext(ServletActionContext.getServletContext());
CustomerService customerService = (CustomerService)applicationContext.getBean(“customerService”);

进行spring和struts2的整合:
	引入struts-spring-plugin.jar
	在插件包中有如下配置:
		<constant name="struts.objectFactory" value="spring" />
		开启了一个常量,这个会引发一系列常量生效
			struts.objectFactory.spring.autoWire = name
				让action按名称自动注入service
			
将service交给spring管理:
	applicationContext.xml:
		<bean id="customerService" class="***.CustomerService">
		</bean>
 	在CustomerAction类中注入CustomerService:
 		private CustomerService customerService;
 		public void setCustomerService(CustomerService customerService) {
 			this.customerService = customerService;
 		}

Spring整合Struts2方式二(action交给spring管理)

1.引入插件包
引入struts-spring-plugin.jar
2.将action交给spring
applicationContext.xml:


3.配置action
struts.xml:

<action name=“customer_*” class=CustomerAction" method="{1}">


4.配置action为多例

5.手动注入service
<bean id=“customerAction” …>

Service调用DAO

1.DAO交给spring管理


2.注入DAO
class CustomerDaoImpl…
{
private CustomerDao customerDao;
public void setCustomerDao(…){…}
}

<bean id="customerService"...>
	<property name="customerDao" ref="customerDao"/>
</bean>

spring整合hibernate框架

1.创建数据库和表
2.编写实体和映射
3.spring和hibernate整合
在spring配置文件中,引入hibernate的配置信息



4.整合后,spring提供了一个hibernate模板类简化hibernate开发
dao类继承HibernateDaoSupport
配置时在dao中直接注入sessionFactory

在dao中使用hibernat模板完成保存操作

配置spring的事务管理

1.配置事务管理器


2.开启注解事务
<tx:annotation-driven transaction-manager=“transactionManager”/>
3.在业务层使用注解
@Transactional

方式二:没有Hibernate配置文件(将其都交给了Spring管理)

将hibernate配置文件的内容交给spring

2.引入外部属性文件
<context:property-placeholder location=“classpath:jdbc.properties”/>
3.配置c3p0连接池
4.注入连接池

5.配置hibernate的属性


***.MySQLDialect
true
true
update


6.设置映射文件


/Customer.hbm.xml

Hibernate模板的使用

Hibernate模板的常用方法

save(Object obj);
update(Object obj);
delete(Object obj);
get(Class c, Serializable id);
load(Class c, Serializable id);
List find(String hql, Object…args);
findByCriteria(DetachedCriteria dc);
findByCriteria(DetachedCriteria dc, int firstResult, int maxResult);
findByNamedQuery(String name, Object…args);
hql

延迟加载问题的解决

spring提供了延迟加载的解决方案
使用的地方:
1.使用load方法查询某一个对象时(不常用)
2.查询到某个对象后,显示其关联对象
常出现no session异常,原因是在业务层查询完后session已经关闭,到了web层再希望显示关联对象时需要延时加载就会报这个异常

解决方案

OpenSessionInViewFilter:提供的过滤器
在web层开启session

在web.xml配置此过滤器:(放在前面)

OpenSessionInViewFilter
***.OpenSessionInViewFilter

OpenSessionInViewFilter *.action
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值