Spring学习笔记

一 Spring简介

是什么

分层的JavaSE/EE应用全栈轻量级开源框架,以IOC(反转控制)和AOP(面向切面编程)为内核

EJB是Spring的前身

优势
  • 方便解耦,简化开发
  • AOP编程支持
  • 声明式事务支持
  • 方便测试
  • 方便集成各种框架
  • 降低Java EE API使用难度
  • Java源码学习范例
体系结构

在这里插入图片描述

开发步骤
  1. 导入Spring开发的基本包坐标
  2. 编写Dao接口和实现类
  3. 创建Spring核心配置文件
  4. 在Spring核心配置文件配置UserDaoImpl
  5. 使用Spring的API获得Bean实例

二 Spring配置文件

Bean标签基本配置

由Spring创建对象,默认调用无参构造函数

基本属性
  • id:Bean实例在Spring容器唯一标识
  • class:Bean全限定名称
<bean id="userDao" class="dao.impl.UserDaoImpl"></bean>
Bean标签范围配置

scope属性:对象作用范围

在这里插入图片描述

<bean id="userDao" class="dao.impl.UserDaoImpl" scope="singleton"></bean>
scope取值区别

scope=“singleton”:

Bean实例个数:1个

Bean实例化时机:Spring核心文件被加载时

Bean生命周期:

  • 创建:应用加载,创建容器时
  • 运行:容器在,一直活着
  • 销毁:应用卸载,销毁容器时

scope=“prototype”:

Bean实例个数:多个

Bean实例化时机:调用getBean()方法时

Bean生命周期:

  • 创建:使用对象时
  • 运行:对象在使用,一直活着
  • 销毁:对象长时间不用,被垃圾回收
Bean生命周期配置

init-method:指定类的初始化方法

destroy-method:指定类的销毁方法

<bean id="userDao" class="dao.impl.UserDaoImpl" scope="singleton" init-method="init" destroy-method="destroy"></bean>
Bean实例化方式
  • 无参构造

    <bean id="userDao" class="dao.impl.UserDaoImpl"></bean>
    
  • 工厂静态

    <bean id="userDao" class="factory.StaticFactory" factory-method="getUserDao"></bean>
    
  • 工厂实例(需要先配置工厂的bean标签)

    <bean id="factory" class="factory.DynamicFactory"></bean>
    <bean id="userDao" factory-bean="factory" factory-method="getUserDao"></bean>
    
Bean依赖注入

是IOC的具体实现

service层依赖dao层,可以在Spring容器中,将UserDao设置到UserService内部,而不需要另外获取UserDao对象

依赖注入方式
  • 构造方法
public class UserServiceImpl implements UserService {
    private UserDao userDao;

    public UserServiceImpl(UserDao userDao) {
        this.userDao = userDao;
    }

    public UserServiceImpl() {
    }
}
<bean id="userService" class="service.impl.UserServiceImpl">
	<!--name指的是构造方法的参数名,ref指的是bean的id-->
	<constructor-arg name="userDao" ref="userDao"></constructor-arg>
</bean>
  • set方法
public class UserServiceImpl implements UserService {
    private UserDao userDao;

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
}
<bean id="userService" class="service.impl.UserServiceImpl">
	<!--property的name属性是UserServiceImpl的setxxx方法,注意首字母小写,ref指的是bean的id-->
	<property name="userDao" ref="userDao"></property>
</bean>
注入数据类型
  • 普通
public class UserDaoImpl implements UserDao {
    private int age;
    private String name;

    public void setAge(int age) {
        this.age = age;
    }

    public void setName(String name) {
        this.name = name;
    }
}
<bean id="userDao" class="dao.impl.UserDaoImpl">
    <property name="name" value="hhf"></property>
    <property name="age" value="20"></property>
</bean>
  • 引用

看前面的依赖注入方式

  • 集合
public class UserDaoImpl implements UserDao {
    private List<String> list;
    private Map<String, User> map;
    private Properties properties;

    public void setList(List<String> list) {
        this.list = list;
    }

    public void setMap(Map<String, User> map) {
        this.map = map;
    }

    public void setProperties(Properties properties) {
        this.properties = properties;
    }
}
<bean id="userDao" class="dao.impl.UserDaoImpl">
    <property name="list">
        <list>
            <value>aaa</value>
            <value>bbb</value>
        </list>
    </property>
    <property name="map">
        <map>
            <entry key="u1" value-ref="user1"></entry>
        </map>
    </property>
    <property name="properties">
        <props>
            <prop key="p1">v1</prop>
        </props>
    </property>
</bean>
引入其它配置文件

在Spring主配置文件加载其它配置文件

<import resource="applicationContext-dao.xml"></import>

三 Spring相关API

ApplicationContext接口

代表应用上下文,用于获得Spring容器的Bean对象

ApplicationContext实现类
  • ClassPathXmlApplicationContext:从类的根路径下加载配置文件
ApplicationContext app=new ClassPathXmlApplicationContext("applicationContext.xml");
  • FileSystemXmlApplicationContext:从磁盘路径加载配置文件,配置文件可以在磁盘任意位置
ApplicationContext app=new FileSystemXmlApplicationContext("D:\idea_java_project\maven01\spring01\src\main\resources\applicationContext.xml");
  • AnnotationConfigApplicationContext:使用注解配置容器对象

见Spring注解开发-新注解

getBean()方法
(UserService) app.getBean("userService");//参数为bean的id,需要强制类型转换,适用于获取多个相同类不同id的对象
app.getBean(UserService.class);//参数为bean的Class对象,不需要强制类型转换

四 Spring配置数据源(连接池)

数据源作用

提高程序性能

事先实例化数据源,初始化部分连接资源

使用连接资源时从数据源获取

用完将连接资源归还给数据源

数据源开发步骤

导入数据源包、数据库驱动包

创建数据源对象

设置连接数据

获取连接

释放连接

Spring产生数据源

以c3p0为例

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <property name="driverClass" value=""></property>
    <property name="jdbcUrl" value=""></property>
    <property name="user" value=""></property>
    <property name="password" value=""></property>
</bean>
Spring加载properties文件

实现Spring配置与数据源配置的解耦

applicationContext.xml

引入context命名空间

xmlns:context="http://www.springframework.org/schema/context"

引入context约束路径

http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
    <!--加载外部properties文件,location为properties文件位置,如果在resources目录下可以写classpath:xxx.properties-->
    <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>

    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!--${}:花括号的值为properties文件的key-->
        <property name="driverClass" value="${jdbc.driver}"></property>
        <property name="jdbcUrl" value="${jdbc.url}"></property>
        <property name="user" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>

jdbc.properties

jdbc.driver=
jdbc.url=
jdbc.username=
jdbc.password=

五 Spring注解开发

原始注解

替代bean标签配置

在这里插入图片描述

需要在applicationContext.xml配置组件扫描,指定哪个包下的Bean需要进行扫描以便识别使用注解配置的类、字段、方法

!--配置组件扫描,扫描指定包以及它的子包-->
<context:component-scan base-package="dao"></context:component-scan>
@Component("userService")
@Scope("singleton")
public class UserServiceImpl implements UserService {
    @Autowired//按照数据类型从Spring容器进行匹配
    @Qualifier("userDao")//按照id从Spring容器进行匹配,需要结合@Autowired使用
    @Resource(name = "userDao")//相当于@Autowired+@Qualifier
    //使用注解进行依赖注入,不需要写setxxx()方法
    private UserDao userDao;
    
    @Value("${jdbc.driver}")//将jdbc.properties文件的内容注入
    private String driver;
    
    @PostConstruct
    public void init() {
        System.out.println("init");
    }

    @PreDestroy
    public void destroy() {
        System.out.println("destroy");
    }
}
新注解

在这里插入图片描述

@Configuration
@PropertySource("classpath:jdbc.properties")
public class DataSourceConfiguration {
    @Bean("dataSource")
    public DataSource getDataSource() throws PropertyVetoException {
        ComboPooledDataSource dataSource=new ComboPooledDataSource();
        dataSource.setDriverClass("${jdbc.driver}");
        dataSource.setJdbcUrl("${jdbc.url}");
        dataSource.setUser("${jdbc.username}");
        dataSource.setPassword("${jdbc.password}");
        return dataSource;
    }
}
@Configuration
@ComponentScan("dao")
@ComponentScan("service")
@Import({DataSourceConfiguration.class})//可以导入多个配置类
//主配置类
public class SpringConfiguration {}
ApplicationContext app=new AnnotationConfigApplicationContext(SpringConfiguration.class);

六 Spring集成

Spring集成Junit
  1. 导入Spring集成Junit的坐标
  2. 使用@Runwith替换原来的运行期
  3. 使用@ContextConfiguration指定配置文件或配置类
  4. 使用@Autowired注入需要测试的对象
  5. 创建测试方法进行测试
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.0.5.RELEASE</version>
</dependency>
@RunWith(SpringJUnit4ClassRunner.class)
//@ContextConfiguration("classpath:applicationContext.xml")
@ContextConfiguration(classes = {SpringConfiguration.class})
public class SpringJunitTest {

    @Autowired
    private UserService userService;

    @Test
    public void test1() {
        userService.save();
    }
}
Spring集成web环境
ApplicationContext应用上下文获取方式
  • 自定义监听器ContextLoaderListener

在web项目中,用ServletContextListener监听web应用启动,web应用启动时加载Spring配置文件,创建ApplicationContext对象,将它存储到ServletContext域,实现一个web应用只有一个Spring容器

public class ContextLoaderListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        ApplicationContext app=new ClassPathXmlApplicationContext("applicationContext.xml");
        ServletContext servletContext=sce.getServletContext();
        servletContext.setAttribute("app",app);
        System.out.println("spring容器创建完成");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {}
}

web.xml

    <!--配置监听器-->
    <listener>
        <listener-class>com.springmvc01.listener.ContextLoaderListener</listener-class>
    </listener>
public class UserServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletContext servletContext=this.getServletContext();
        ApplicationContext app= (ApplicationContext) servletContext.getAttribute("app");
        UserService userService= (UserService) app.getBean("userService");
        userService.save();
    }
}

优化

将Spring配置文件与监听器解耦,将ApplicationContext对象在ServletContext域的属性与servlet解耦

web.xml

    <!--配置全局初始化参数-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>applicationContext.xml</param-value>
    </context-param>

	<!--配置监听器-->
    <listener>
        <listener-class>com.springmvc01.listener.ContextLoaderListener</listener-class>
    </listener>

ContextLoaderListener.java

@Override
public void contextInitialized(ServletContextEvent sce) {
    ServletContext servletContext=sce.getServletContext();
    //读取web.xml的全局参数
    String contextConfigLocation = servletContext.getInitParameter("contextConfigLocation");

    ApplicationContext app=new ClassPathXmlApplicationContext(contextConfigLocation);
    servletContext.setAttribute("app",app);
    System.out.println("spring容器创建完成");
}

工具类WebApplicationContextUtils.java

public class WebApplicationContextUtils {
    public static ApplicationContext getWebApplicationContext(ServletContext servletContext) {
        return (ApplicationContext) servletContext.getAttribute("app");
    }
}

UserServlet.java

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    ServletContext servletContext=this.getServletContext();
    ApplicationContext app= WebApplicationContextUtils.getWebApplicationContext(servletContext);
    UserService userService= (UserService) app.getBean("userService");
    userService.save();
}
  • 使用Spring提供的获取应用上下文工具

web.xml配置ContextLoaderListener监听器(导入spring-web坐标),使用WebApplicationContextUtils获取ApplicationContext对象

pom.xml

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>5.0.3.RELEASE</version>
</dependency>

web.xml

    <!--配置全局初始化参数-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
    </context-param>
	
	<!--配置监听器-->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

UserServlet.java

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    ServletContext servletContext=this.getServletContext();
    ApplicationContext app = WebApplicationContextUtils.getWebApplicationContext(servletContext);
    UserService userService= (UserService) app.getBean("userService");
    userService.save();
}

七 Spring JdbcTemplate

简介

是Spring框架提供的一个对象,是对原始繁琐的JdbcAPI对象的简单封装

开发步骤
  1. 导入spring-jdbc和spring-tx坐标
  2. 创建数据库表和实体
  3. 创建JdbcTemplate对象
  4. 执行数据库操作(增删改用update()方法,查询用query()方法)
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.0.3.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
    <version>5.0.3.RELEASE</version>
</dependency>
@Test
public void test1() throws PropertyVetoException {
    //创建数据源
    ComboPooledDataSource dataSource=new ComboPooledDataSource();
    dataSource.setDriverClass("");
    dataSource.setJdbcUrl("");
    dataSource.setUser("");
    dataSource.setPassword("");
    //创建模板对象
    JdbcTemplate jdbcTemplate=new JdbcTemplate();
    //设置数据源对象
    jdbcTemplate.setDataSource(dataSource);
    //执行操作
    int row=jdbcTemplate.update("insert into account values(?,?,?)",3,"ccc",30);
    System.out.println(row);
}

Spring产生模板对象

<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<!--数据源对象-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <property name="driverClass" value="${jdbc.driver}"></property>
    <property name="jdbcUrl" value="${jdbc.url}"></property>
    <property name="user" value="${jdbc.username}"></property>
    <property name="password" value="${jdbc.password}"></property>
</bean>

<!--jdbc模板对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource"></property>
</bean>
@Test
public void test2() throws PropertyVetoException {
    ApplicationContext app=new ClassPathXmlApplicationContext("applicationContext.xml");
    JdbcTemplate jdbcTemplate= (JdbcTemplate) app.getBean("jdbcTemplate");
    //执行操作
    int row=jdbcTemplate.update("insert into account values(?,?,?)",5,"ccc",30);
    System.out.println(row);
}
@Test
public void test3() throws PropertyVetoException {
    ApplicationContext app=new ClassPathXmlApplicationContext("applicationContext.xml");
    JdbcTemplate jdbcTemplate= (JdbcTemplate) app.getBean("jdbcTemplate");
    //查询一个
    Account account=jdbcTemplate.queryForObject("select * from account where id=?",new BeanPropertyRowMapper<Account>(Account.class),1);
    System.out.println(account);
    //查询多个
    List<Account> accountList=jdbcTemplate.query("select * from account",new BeanPropertyRowMapper<Account>(Account.class));
    System.out.println(accountList);
    //计数
    Long count=jdbcTemplate.queryForObject("select count(*) from account",Long.class);
    System.out.println(count);
}

八 Spring AOP

简介

面向切面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术,可以对业务逻辑各个部分耦合度降低

作用及优势

作用:在程序运行期间,在不修改源码的情况下对方法进行功能增强

优势:减少重复代码,提高开发效率,便于维护

切面=目标+增强

底层实现

通过Spring提供的动态代理技术实现。在运行期间,Spring通过动态代理动态生成代理对象,代理对象方法执行时进行增强功能的介入,再调用目标对象的方法

动态代理技术
  • JDK代理:基于接口的动态代理
public interface TargetInterface {
    public void save();
}
public class Target implements TargetInterface{
    @Override
    public void save() {
        System.out.println("save running");
    }
}
public class Advice {
    public void before() {
        System.out.println("前置增强");
    }

    public void after() {
        System.out.println("后置增强");
    }
}
public class ProxyTest {
    public static void main(String[] args) {
        //目标对象
        Target target=new Target();
        //增强对象
        Advice advice=new Advice();
        //返回值就是动态生成的代理对象
        TargetInterface proxy= (TargetInterface) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),//目标对象类加载器
                target.getClass().getInterfaces(),//目标对象相同的接口字节码对象数组
                new InvocationHandler() {
                    //调用代理对象的任何方法,实质执行invoke方法
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        advice.before();//前置增强
                        Object invoke = method.invoke(target, args);//执行目标方法
                        advice.after();//后置增强
                        return invoke;
                    }
                }
        );
        //调用代理对象的方法
        proxy.save();
    }
}
  • cglib代理:基于父类的动态代理
public class Target {
    public void save() {
        System.out.println("save running");
    }
}
public class ProxyTest {
    public static void main(String[] args) {
        //目标对象
        Target target=new Target();
        //增强对象
        Advice advice=new Advice();
        //返回值就是动态生成的代理对象
        //1.创建增强器
        Enhancer enhancer=new Enhancer();
        //2.设置父类
        enhancer.setSuperclass(Target.class);
        //3.设置回调
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                advice.before();
                method.invoke(target,args);
                advice.after();
                return null;
            }
        });
        //4.创建代理对象
        Target proxy= (Target) enhancer.create();
        proxy.save();
    }
}
相关概念
  • Target:代理的目标对象
  • Proxy:代理类
  • Joinpoint:连接点,指被拦截到的点,在Spring中指的是可以被增强的方法
  • Pointcut:切入点,被配置可以被增强的方法
  • Advice:通知或增强,拦截到连接点后做的事情
  • Aspect:切面,切入点+通知
  • Weaving:织入,切入点与通知结合的过程
开发步骤
  1. 编写目标类的目标方法
  2. 编写切面类,切面类中有通知
  3. 配置织入关系,即将哪些通知与哪些连接点结合

在Spring中框架会根据目标类是否实现接口决定用哪种动态代理方式

基于XML的开发
步骤
  • 导入AOP相关坐标
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.0.5.RELEASE</version>
</dependency>
<dependency>
	<groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.13</version>
</dependency>
  • 创建目标接口和目标类
public interface TargetInterface {
    public void save();
}
public class Target implements TargetInterface {
    @Override
    public void save() {
        System.out.println("save running");
    }
}
  • 创建切面类
public class MyAspect {
    public void before() {
        System.out.println("前置增强");
    }
}
  • 将目标类和切面类的对象创建权交给Spring
<!--目标对象-->
<bean id="target" class="aop.Target"></bean>

<!--切面对象-->
<bean id="myAspect" class="aop.MyAspect"></bean>
  • 在applicationContext.xml配置织入关系
<!--配置织入,告诉框架哪些切点需要进行哪些增强-->
<aop:config>
    <!--声明切面-->
    <aop:aspect ref="myAspect">
        <!--切点+通知 aop:before是前置增强,method是具体增强逻辑,pointcut是切点表达式-->
        <aop:before method="before" pointcut="execution(public void aop.Target.save())"></aop:before>
    </aop:aspect>
</aop:config>
  • 测试
切点表达式语法

execution([修饰符] 返回值类型 包名.类名.方法名(参数))

修饰符可省略

返回值类型、包名、类名、方法名可以用*代表任意

包名与类名之间一个点代表当前包下面的类,两个点代表当前包以及子包下面的类

参数列表可以用两个点表示任意个数,任意类型

通知类型

在这里插入图片描述

切点表达式抽取
<!--抽取切点表达式-->
<aop:pointcut id="myPointcut" expression="execution(public void aop.Target.save())"/>
<aop:after method="after" pointcut-ref="myPointcut"></aop:after>
基于注解的开发
步骤
  • 创建目标接口和目标类
  • 创建切面类
  • 将目标类和切面类的对象创建权交给Spring
  • 在切面类使用注解配置织入关系
  • 在配置文件开启组件扫描和AOP自动代理
  • 测试
@Component("target")
public class Target implements TargetInterface {
    @Override
    public void save() {
        //int a=1/0;
        System.out.println("save running");
    }
}
@Component("myAspect")
@Aspect//标注切面类
public class MyAspect {
    //配置前置通知
    @Before("execution(public void anno.Target.save())")
    public void before() {
        System.out.println("前置增强");
    }
}
<!--配置组件扫描-->
<context:component-scan base-package="anno"></context:component-scan>

<!--AOP自动代理-->
<aop:aspectj-autoproxy/>
通知类型

在这里插入图片描述

切点表达式抽取

在切面类定义方法,在该方法上用@Pointcut定义切点表达式,在通知注解中引用

//配置前置通知
@Before("MyAspect.pointcut()")
public void before() {
    System.out.println("前置增强");
}

//抽取切点表达式
@Pointcut("execution(public void anno.Target.save())")
public void pointcut() {}

九 事务控制

编程式事务控制
  • PlatformTransactionManager接口

    是Spring的事务管理器,不同Dao层技术有不同实现类

  • TransactionDefinition

    事务的定义信息对象,设置隔离级别、事务传播行为(一个业务方法调用另一个业务方法时保证事务的同一性)

  • TransactionStatus

    事务具体的运行状态

基于XML的声明式事务控制
<!--目标对象 内部方法是切点-->
<bean id="accountService" class="service.impl.AccountServiceImpl">
    <property name="accountDao" ref="accountDao"></property>
</bean>

<!--配置平台事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
	<property name="dataSource" ref="dataSource"></property>
</bean>

<!--通知 事务增强-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
	<!--设置事务的属性信息-->
    <tx:attributes>
        <!--name是被增强方法名 isolation是隔离级别 propagation是传播行为 timeout是超时时间 read-only:是否只读-->
        <!--*代表所有-->
        <tx:method name="*" isolation="DEFAULT" propagation="REQUIRED" timeout="-1" read-only="false"/>
    </tx:attributes>
</tx:advice>

<!--配置事务的AOP织入-->
<aop:config>
	<aop:advisor advice-ref="txAdvice" pointcut="execution(* service.impl.*.*(..))"></aop:advisor>
</aop:config>
基于注解的声明式事务控制
@Service("accountServiceImpl")
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountDao accountDao;

    @Transactional(isolation = Isolation.DEFAULT,propagation = Propagation.REQUIRED)//配置事务通知
    @Override
    public void transfer() {
        accountDao.in("aaa",500);
        int a=1/0;
        accountDao.out("bbb",500);
    }
}
<!--配置平台事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>

<!--配置事务注解驱动-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值