2021-08-13

Spring

  • spring的理念:使现有的技术更加容易,本身是一个大杂烩,整合了现有的技术框架!

官网:https://spring.io/projects/spring-framework#overview

下载地址:https://repo.spring.io/libs-release-local/org/springframework/spring/

GitHub:https://github.com/spring-projects/spring-framework

1 Spring优缺点

1、Spring是一个开源免费的框架 , 容器 .

2、Spring是一个轻量级的框架 , 非侵入式的 .

3、控制反转 IOC , 面向切面 Aop

4、对事物的支持 , 对框架的支持

一句话概括:

Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器(框架)。

缺点

  • 发展太久以后,违背了原来的理念!配置十分繁琐,人称:“配置地狱”

    Spring的优良特性:

    • 非侵入式

    • 依赖注入(DI),控制反转最经典的实现

    • 面向切面编程

    • 容器 :spring是一个容器,因为它包含并且管理应用对象的生命周期

    • 组件化:spring实现了使用简单的组件配置组合成一个复杂的应用。在spring中可以使用XML饿Java注解组合这些对象

      (把具有一些功能的类称为组件)

2 spring 所需依赖

  <!-- Spring依赖 -->
    <!-- 1.Spring核心依赖 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>4.3.7.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-beans</artifactId>
        <version>4.3.7.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>4.3.7.RELEASE</version>
    </dependency>
<!-- 2.Spring dao依赖 -->
<!-- spring-jdbc包括了一些如jdbcTemplate的工具类 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>4.3.7.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-tx</artifactId>
        <version>4.3.7.RELEASE</version>
    </dependency>
    <!-- 3.Spring web依赖 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
        <version>4.3.7.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>4.3.7.RELEASE</version>
    </dependency>
    <!-- 4.Spring test依赖:方便做单元测试和集成测试 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>4.3.7.RELEASE</version>
  </dependency>

3 组成

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YzSH568N-1628824238474)(file:///C:\Users\mi\Documents\Tencent Files\3359514063\Image\C2C\1B14056E8FD04545036AC3C9DA0796B5.jpg)]

组成 Spring 框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现。每个模块的功能如下:

  • 核心容器:核心容器提供 Spring 框架的基本功能。核心容器的主要组件是 BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转(IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。

  • Spring 上下文:Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。

  • Spring AOP:通过配置管理特性,Spring AOP 模块直接将面向切面的编程功能 , 集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理任何支持 AOP的对象。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖组件,就可以将声明性事务管理集成到应用程序中。

  • Spring DAO:JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。

  • Spring ORM:Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。

  • Spring Web 模块:Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring 框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。

  • Spring MVC 框架:MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。

4 扩展

Spring Boot与Spring Cloud

  • Spring Boot

    • 一个快速开发脚手架

    • 基于Spring Boot可以快速开发单个微服务

    • 约定大于配置

  • Spring Boot Spring 的一套,可以基于Spring Boot 快速开发单个微服务;

  • Spring Cloud

    • Spring Cloud是基于Spring Boot实现的
    • Spring Cloud关注全局的服务治理框架;
  • Spring Boot使用了约束优于配置的理念,很多集成方案已经帮你选择好了,能不配置就不配置 , Spring Cloud很大的一部分是基于Spring Boot来实现,Spring Boot可以离开Spring Cloud独立使用开发项目,但是Spring Cloud离不开Spring Boot,属于依赖的关系。

  • SpringBoot在SpringClound中起到了承上启下的作用,如果你要学习SpringCloud必须要学习SpringBoot。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yDzdVcQk-1628824238479)(file:///C:\Users\mi\Documents\Tencent Files\3359514063\Image\C2C\23DB4EE3EE6791F521A017DD248E0CE3.jpg)]

因为现在大多数公司都在使用SpringBoot进行快速开发,学习SpringBoot的前提,需要完全掌握Spring,SpringMVC!

IOC理论推导

原来写一个业务:

  • UserDao接口
  • UserDaoImpl实现类
  • UserService业务接口
  • UserServiceImpl业务接口实现类

在我们之前的业务中,用户的需求可能会影响我们的原代码,我们根据用户的需求去修改原代码!如果程序代码量十分大,修改一次的成本十分昂贵!

我们使用一个set接口实现,已经发生了革命性的变化

package com.luckily.service;

import com.luckily.dao.UserDao;
import com.luckily.dao.UserDaoImpl;

public class UserServiceImpl implements UserService{
    private UserDao userDao;
    //利用set实现动态注入值
    public void setUserDao(UserDao userDao){
        this.userDao = userDao;
    }
    public void getUser() {
        userDao.getUser();
    }
}

  • 之前程序是主动创建对象!控制权在程序员手上。
  • 使用了set注入后,程序不在具有主动性,而是变成了被动的接受对象!

这种思想,从本质上解决了问题,我们程序员不用再去管理对象的创建了。系统的耦合性大大的降低了,可以更加专注的在业务的实现上!这就是IOC原型!

1 IOC 本质

控制反转IoC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IoC的一种方法,也有人认为DI只是IoC的另一种说法。没有IoC的程序中 , 我们使用面向对象编程 , 对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方,个人认为所谓控制反转就是:获得依赖对象的方式反转了。

img

IOC是Spring框架的核心内容,使用多种方式完美的实现了IOC,可以使用XML配置,也可以使用注解,新版本的Spring也可以零配置实现IOC。

Spring容器在初始化时先读取配置文件,根据配置文件或元数据创建与组织对象存入容器中,程序使用时再从IOC容器中取出需要的对象。

img

采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。

控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入(Dependency Injection,DI)。

小狂神温馨提示

明白IOC的思想,是理解Spring的核心技巧

Spring提供的IOC的两种实现方式:

  • BeanFactory:IOC的基本实现,是Spring内部的使用的接口
    • 加载配置文件的时候不会去创建对象,在使用的时候才会去创建对象
  • ApplicationContext:BeanFactory接口的子接口,提供更多更强大的功能,一般开大人员使用
    • 加载配置文件的时候就会创建对象

IOC操作的bean管理:

什么是bean管理?

  • bean管理指的是两个操作
    • Spring创建对象
    • Spring注入属性

2 编写hello Spring

1 先编写一个hello实体类
package com.luckily.pojo;

public class Hello {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Hello{" +
                "name='" + name + '\'' +
                '}';
    }
}

2 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">

<!--    使用spring来创建对象,在Spring这些都称为Bean-->

<!--
java  Hello hello = new Hello();

      bean = 对象   new Hello();
      id 等价于变量名
      class = new 的对象
      property 相当于给对象中的属性设置一个值
      -->
    <bean id = "hello" class="com.luckily.pojo.Hello">
        <property name="name" value="Spring"></property>
    </bean>
</beans>

3 测试类
import com.luckily.pojo.Hello;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
    public static void main(String[] args) {
//    获取spring的上下问对象!
       ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
       //我们的对象现在都在spring中管理了,我们要使用,直接取里面去出来就行了
        Hello hello = (Hello)context.getBean("hello");
    }
}

4 思考

Hello 对象是谁创建的?

  • hello对象是由Spring创建的

Hello对象的属性是怎么设置的?

  • hello对象的属性是由Spring容器设置的

这个过程就叫做控制反转:

  • 控制:谁来控制对象的创建,传统的应用程序对象用该由程序员创建,使用Spring 后,对象是由Sring来创建的
  • 反转:程序本身不创建对象,而变成被动的接受对象

依赖注入:就是利用set的方法来进行注入

IOC是一种编程思想,由主动的编程,变成被动的接受

到现在我们彻底的不需要改程序了,要实现不同的操作,只需要在XML配置文件中进行修改, 所谓的IOC,就是:对象有Spring来创建,管理,装配!

3 IOC 创建对象的方式

1 .使用无参构造创建对象,默认!

Set方法注入

2 .使用有参构造创建对象。

1 下标赋值
<!--   第一种方式  需要 -->
    <bean id = "user" class="com.luckily.pojo.User">
       <constructor-arg index="0" value="haha"></constructor-arg>
    </bean>


2 类型
<!--    
第二种方式 通过类型创建,不建议使用 
如果两个参数都是Spring 就不可以了
-->
    <bean id = "user" class="com.luckily.pojo.User">
        <constructor-arg type="java.lang.String" value="luckily"></constructor-arg>
    </bean>
3 直接通过参数名
<!--第三种    -->
    <bean id = "user" class="com.luckily.pojo.User">
        <constructor-arg name="name" value="hahahaha"></constructor-arg>
    </bean>
4 总结

在配置文件加载的时候,容器中管理的对象就已经初始化了!

spring配置

1 别名

<alias name="user" alias="user2"/>

添加了别名,也可以使用别名来使用这个对象

2 Bean的配置

<!--bean就是java对象,由Spring创建和管理-->

<!--
    id:bean 的唯一标识,也就是相当于我们学的对象名
    class : bean 对象所对应的权限定名 也就是 包名+类名
    name : 也是别名,而且name可以同时去多个别名



   id 是bean的标识符,要唯一,如果没有配置id,name就是默认标识符
   如果配置id,又配置了name,那么name是别名
   name可以设置多个别名,可以用逗号,分号,空格隔开
   如果不配置id和name,可以根据applicationContext.getBean(.class)获取对象;

class是bean的全限定名=包名+类名
-->
<bean id="hello" name="hello2 h2,h3,h4" class="com.kuang.pojo.Hello">
   <property name="name" value="Spring"/>
</bean>

3 import

一般用于团队开发使用,可以将多个配置文件导入合并一个

假设现在项目有多个人开发,这几个人负责不同的类开发,不同的类需要注册在不同的bean中,我们可以利用import将所有人的beans.xml 合并为一个总的!

使用的时候使用总的就可以了

<import resource="{path}/beans.xml"/>

依赖注入(DI)

1 构造器注入

2 Set方式注入【重点】

  • 依赖注入: Set注入!
    • 依赖:bean对象的创建依赖于容器
    • 注入:bean对象中的所有属性,由容器来注入!

【环境搭建】

  • 复杂类型
  • 真实测试对象

1 创建两个实体类

package com.luckily.pojo;

import java.util.*;

public class Student {
    private String name;
    private Address address;
    private String[] books;
    private List<String> hobbys;
    private Map<String,String> card;
    private Set<String> games;
    private String friend;
    private Properties info;    //配置类

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

    public String[] getBooks() {
        return books;
    }

    public void setBooks(String[] books) {
        this.books = books;
    }

    public List<String> getHobbys() {
        return hobbys;
    }

    public void setHobbys(List<String> hobbys) {
        this.hobbys = hobbys;
    }

    public Map<String, String> getCard() {
        return card;
    }

    public void setCard(Map<String, String> card) {
        this.card = card;
    }

    public Set<String> getGames() {
        return games;
    }

    public void setGames(Set<String> games) {
        this.games = games;
    }

    public String getFriend() {
        return friend;
    }

    public void setFriend(String friend) {
        this.friend = friend;
    }

    public Properties getInfo() {
        return info;
    }

    public void setInfo(Properties info) {
        this.info = info;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", address=" + address +
                ", books=" + Arrays.toString(books) +
                ", hobbys=" + hobbys +
                ", card=" + card +
                ", games=" + games +
                ", friend='" + friend + '\'' +
                ", info=" + info +
                '}';
    }
}


package com.luckily.pojo;

public class Address {
    private String address;

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}


2 配置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">
<!--    普通值注入直接使用value-->
 <bean id = "student" class="com.luckily.pojo.Student">
     <property name="name" value="haha"></property>
 </bean>
</beans>

3 测试类

import com.luckily.pojo.Student;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        Student student =(Student) context.getBean("student");
        System.out.println(student.getName());
        System.out.println(student.getAddress());
    }
}

<?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">
<!--    普通值注入直接使用value-->
    <bean id = "address" class="com.luckily.pojo.Address">
        <property name="address" value="开封"/>
    </bean>
 <bean id = "student" class="com.luckily.pojo.Student">
     <property name="name" value="haha"></property>

<!--    第二种 Bean注入 ref-->
     <property name="address" ref="address"/>

<!--     数组注入-->
     <property name="books">
         <array>
             <value>红楼梦</value>
             <value>水浒传</value>
             <value>西游记</value>
             <value>三国演义</value>
         </array>
     </property>

<!--     list注入-->
     <property name="hobbys">
         <list>
             <value>听歌</value>
             <value>看书</value>
             <value>追剧</value>
             <value></value>
         </list>
     </property>


<!--     Map-->
     <property name="card">
         <map>
             <entry key="身份证" value="123456"></entry>
             <entry key="银行卡" value="1239765"></entry>
         </map>
     </property>


<!--     set-->
     <property name="games">
         <set>
             <value>lol</value>
             <value>coc</value>
             <value>vov</value>
         </set>
     </property>



<!--     null注入-->
     <property name="friend">
         <null/>
     </property>


<!--     properties-->
     <property name="info">
         <props>
             <prop key="学号">20180341114</prop>
             <prop key="姓名">核桃仁</prop>
         </props>
     </property>
 </bean>
</beans>

3 拓展方式注入

P命名空间注入
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       
       
       xmlns:p="http://www.springframework.org/schema/p"
       
       
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- p命名空间注入,可以直接注入属性的值:property-->
    <bean id="user" class="com.luckily.pojo.User" p:name="haha" p:age="18"></bean>
</beans>

P命名空间用的是无参构造,c命名空间通过有参构造

P命名空间用的是setter,c命名空间通过构造器

c命名空间的注入
 导入约束 : xmlns:c="http://www.springframework.org/schema/c"
 <!--C(构造: Constructor)命名空间 , 属性依然要设置set方法-->
 <bean id="user" class="com.kuang.pojo.User" c:name="狂神" c:age="18"/>

4 bean的作用域

scope作用域

img

1 Singleton

当一个bean的作用域为Singleton,那么Spring IoC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。Singleton是单例类型,就是在创建起容器时就同时自动创建了一个bean的对象,不管你是否使用,他都存在了,每次获取到的对象都是同一个对象。注意,Singleton作用域是Spring中的缺省作用域。要在XML中将bean定义成singleton,可以这样配置:

 <bean id="ServiceImpl" class="cn.csdn.service.ServiceImpl" scope="singleton">

单例模式也就是只new一次对象,之后getBean的都直接获取第一次new的对象

2 prototype

当一个bean的作用域为Prototype,表示一个bean定义对应多个对象实例。Prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法)时都会创建一个新的bean实例。Prototype是原型类型,它在我们创建容器的时候并没有实例化,而是当我们获取bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。根据经验,对有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域。在XML中将bean定义成prototype,可以这样配置:

 <bean id="account" class="com.foo.DefaultAccount" scope="prototype"/>  
  或者
 <bean id="account" class="com.foo.DefaultAccount" singleton="false"/>

原型模式:每次从容器中中get的时候,都会产生一个新对象!

3 request

当一个bean的作用域为Request,表示在一次HTTP请求中,一个bean定义对应一个实例;即每个HTTP请求都会有各自的bean实例,它们依据某个bean定义创建而成。该作用域仅在基于web的Spring ApplicationContext情形下有效。考虑下面bean定义:

 <bean id="loginAction" class=cn.csdn.LoginAction" scope="request"/>
4 session

当一个bean的作用域为Session,表示在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。考虑下面bean定义:

<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>

bean的生命周期

  • 通过构造器创建bean的实例(无参构造)
  • 为bean的属性设置值和对其他bean进行引用(调用set方法)
  • 把bean实例传递给bean后置处理器的方法postProcessBefterInitialzation
  • 调用bean的初始化方法(需要进行配置初始化值得方法)
  • 把bean实例传递给bean后置处理器的方法postProcessAfterInitialzation
  • bean可以是使用了(对象获取到了)
  • 当容器关闭的时候,调用bean的销毁的

Bean的自动装配

  • 自动装配是spring满足bean依赖的一种方式!
  • spring 会在上下问中自动

在spring中有三种装配方式

  • 在xml中显示的装置
  • 在java中显示的装置
  • 隐式的自动装配bean(重要)

1 测试

环境搭建,一个让你有两个宠物,一个猫,一个狗

2 byName自动装配

autowire byName (按名称自动装配)

由于在手动配置xml过程中,常常发生字母缺漏和大小写等错误,而无法对其进行检查,使得开发效率降低。

采用自动装配将避免这些错误,并且使配置简单化。

测试:

  • 修改bean配置,增加一个属性 autowire=“byName”

  • 再次测试,结果依旧成功输出!

  • 我们将 cat 的bean id修改为 catXXX

  • 再次测试, 执行时报空指针java.lang.NullPointerException。因为按byName规则找不对应set方法,真正的setCat就没执行,对象就没有初始化,所以调用时就会报空指针错误。

小结:

当一个bean节点带有 autowire byName的属性时。

  1. 将查找其类中所有的set方法名,例如setCat,获得将set去掉并且首字母小写的字符串,即cat。
  2. 去spring容器中寻找是否有此字符串名称id的对象。
  3. 如果有,就取出注入;如果没有,就报空指针异常。
<?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">

    <bean id="dog" class="com.luckily.pojo.Dog"/>
    <bean id="cat" class="com.luckily.pojo.Cat"/>

    <bean id="people" class="com.luckily.pojo.People" autowire="byName">
        <property name="name" value="haha"/>
    </bean>
</beans>

3 byType

autowire byType (按类型自动装配)

必须保证类型全局唯一

使用autowire byType首先需要保证:同一类型的对象,在spring容器中唯一。如果不唯一,会报不唯一的异常。

4 小结

  • byname的时候,需要保证所有的bean的id唯一,并且这个bean需要和自动注入的属性的set方法的值一致!
  • bytype的时候,需要保证所有的bean的class唯一,并且这个bean需要和自动注入的属性的类型一致!

外部属性文件

  • 引入druid的依赖
  • 配置连接池

5 使用注解实现自动装配

使用注解须知:

  • 导入约束
  • 配置注解的支持
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context ="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
    <context:annotation-config/>
   
</beans>
1 @Autowired【常用】

直接在属性上即可!也可以在set方式上使用

使用@Autowired 我们可以不用编写set方法了,前提是你这个自动装配的属性在IOC(spring)容器中存在,且符合名字byname!

@Autowired

@Autowired(required=false) 说明:false,对象可以为null;true,对象必须存对象,不能为null。

名字可以不用默认的类名

<bean id="dog11" class="com.luckily.pojo.Dog"/>
    <bean id="cat11" class="com.luckily.pojo.Cat"/>
这样也可以
@Nullable    字段标记了这个注解,说明这个字段可以为空
2 @Qualifier
  • @Autowired是根据类型自动装配的,加上@Qualifier则可以根据byName的方式自动装配
  • @Qualifier不能单独使用。
@Autowired
    @Qualifier(value = "cat111")
    private Cat cat;
    @Autowired
    @Qualifier(value = "dog111")
    private Dog dog;
<bean id="dog11" class="com.luckily.pojo.Dog"/>
    <bean id="dog111" class="com.luckily.pojo.Dog"/>
    <bean id="cat11" class="com.luckily.pojo.Cat"/>
    <bean id="cat111" class="com.luckily.pojo.Cat"/>

    <bean id="people" class="com.luckily.pojo.People"/>
3 @Resource
    @Resource(name = "cat2")也可以指定名字
    private Cat cat;
    @Resource
    private Dog dog;

会先通过名字查找,有一个符合就可以了,名字查找不成功会再通过类型查找

4 小结

@Resource和@Autowired的区别:

  • 都是用来自动装配的,都可以放在属性的字段上
  • @Autowired通过bytype的方式实现,而且要求这个对象必须存在
  • @Resource默认通过byname的方式实现,找不到名字通过bytype实现,两个方式都找不到就报错
  • 执行顺序不同

使用注解开发

在spring4 之后,使用注解开发必须要保证aop的包导入了

   <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.2.0.RELEASE</version>
    </dependency>

测试需要导入的依赖

 <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
   </dependency>

使用注解需要导入context约束,增加注解的支持

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context ="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
    <context:annotation-config/>
</beans>
  • bean

  • 属性如何注入

    package com.luckily.pojo;
    
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Component;
    //@Component 组件   使用注解方式创建对象@Component没有给值,默认就是类名第一个首字母小写
    @Component
    public class User {
        @Value("luckily")
        public String name;
    }
    
    

    如果有set方法也可以吧@Value(“luckily”)加载set方法上

    此时的配置文件为

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context ="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd">
    <!--    指定要扫描的包,这个包下的注解就会生效-->
        <context:component-scan base-package="com.luckily.pojo"/>
    <!--    注解驱动。-->
        <context:annotation-config/>
    </beans>
    
  • 衍生的注解

    @Component有几个衍生注解,我们在web开发中,会按照mvc三层架构分层!

    • dao【@Repository】

    • service【@Service】

    • controller【@Controller】

      这四个注解功能都是一样的,都是代表将某个类注册到spring中,装配Bean

  • 自动装配置

    @Autowired :自动装配通过类型。名字
       如果@Autowired不能唯一自动装配上属性,则需要通过@Qualifier(value="XXX");
     @Nullable 字段标记这个注解,说明这个字段可以为null
     @Resource 自动装配通过名字,类型。 
    
  • 作用域

    @Scope
    
  • 小结

    xml与注解

    • xml更加万能,适用于一切场合,维护方便
    • 注解不是自己的类实现不了,维护相对复杂!

    xml与注解的最佳实现

    • xml负责用来管理bean

    • 注解只负责完成属性的注入;

    • 我们在使用的过程中,只需要注意一个问题:必须让注解生效,就需要开启注解的支持也就是两句话

      <!--    指定要扫描的包,这个包下的注解就会生效-->
          <context:component-scan base-package="com.luckily.pojo"/>
      <!--    注解驱动。-->
          <context:annotation-config/>
      

使用Java的方式配置spring

完全不使用xml配置

JavaConfig 原来是 Spring 的一个子项目,它通过 Java 类的方式提供 Bean 的定义信息,在 Spring4 的版本, JavaConfig 已正式成为 Spring4 的核心功能 。

1 配置类

package com.luckily.config;

import com.luckily.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration//这个也会被Spring容器托管,注册到容器中,因为它本来就是一个@Component,
// @Configuration代表这是一个配置类,就和我们之前的beans.xml是一样的2
@ComponentScan("com.luckily.pojo")//扫描包
@Import(Config1.class)//相当于把两个配置类引入到一个配置类中
public class Config {
    //注册一个bean就相当于我们之前紫萼的一个标签
    //这个方法的名字,就相当于bean标签中的id属性
    //这个方法的返回值,就相当于bean标签中的class属性
    @Bean
    public User getUser(){
        return new User();//就是要返回要注入bean中的对象
    }

}

2 实体类

package com.luckily.pojo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class User {
    public String name;

    public String getName() {
        return name;
    }
    @Value("hha")
    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                '}';
    }
}

3 测试类

import com.luckily.config.Config;
import com.luckily.pojo.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MyTest {
    public static void main(String[] args) {
        //如果完全使用了配置类的方式去做,我们就只能通过AnnotationConfig上下文来获取容器,通过配置类的class对象加载!
        ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
        User getUser = context.getBean("getUser", User.class);
        System.out.println(getUser.name);
    }
}

代理模式

为什么学习代码模式?因为这就是SpringAOP的底层!

代理模式的分类

  • 静态代理
  • 动态代理

1 静态代理

角色分析:

  • 抽象角色:一般会使用接口或者抽象类来解决

  • 真实角色:被代理的角色

  • 代理角色:代理真是角色,代理真实角色后,我们一般会做一些附属操作

  • 客户:访问代理对象的人!

1 接口
package com.luckily.demo01;

public interface Rent {
    public void rent();
}

2 真实角色
package com.luckily.demo01;

public class Host implements Rent{

    public void rent() {
        System.out.println("房东要出租房子");
    }
}

3 代理角色
package com.luckily.demo01;

public class Proxy implements Rent{
    private Host host;

    public Proxy(Host host) {
        this.host = host;
    }

    public Proxy() {
    }

    public void rent() {
        host.rent();
    }
    public void seehouse(){
        System.out.println("代理带你看房");
    }
}

4 客户端访问代理角色
package com.luckily.demo01;

public class Client {
    public static void main(String[] args) {
        Host host = new Host();
        //代理
        Proxy proxy = new Proxy(host);
        proxy.rent();
    }

}

分析:在这个过程中,你直接接触的就是中介,就如同现实生活中的样子,你看不到房东,但是你依旧租到了房东的房子通过代理,这就是所谓的代理模式,程序源自于生活,所以学编程的人,一般能够更加抽象的看待生活中发生的事情。

静态代理的好处:

  • 可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .
  • 公共的业务由代理来完成 . 实现了业务的分工 ,
  • 公共业务发生扩展时变得更加集中和方便 .

缺点 :

  • 类多了 , 多了代理类 , 工作量变大了 . 开发效率降低 .

我们想要静态代理的好处,又不想要静态代理的缺点,所以 , 就有了动态代理 !

2 动态代理

  • 动态代理的角色和静态代理的一样 .
  • 动态代理的代理类是动态生成的 ,不需要我们自己写. 静态代理的代理类是我们提前写好的
  • 动态代理分为两类 : 一类是基于接口动态代理 , 一类是基于类的动态代理
    • 基于接口的动态代理----JDK动态代理
    • 基于类的动态代理–cglib
    • 现在用的比较多的是 javasist 来生成动态代理 . 百度一下javasist
    • 我们这里使用JDK的原生代码来实现,其余的道理都是一样的!

JDK的动态代理需要了解两个类

核心 : InvocationHandler【调用处理程序 】 和 Proxy 【代理】

package com.luckily.demo04;

import com.luckily.demo01.Rent;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

//等会我们会用这个类,自动生成代理类!
public class ProxyInvocationHandler implements InvocationHandler {
   //被代理的接口
    private Object target;

    public void setTarget(Object target) {
        this.target = target;
    }

    //生成得到代理类
    public Object getProxy(){
       return  Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(),this);
    }

    //处理代理实例,并返回结果
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //动态代理的本质,就是使用反射机制
        log(method.getName());
        Object invoke = method.invoke(target, args);


        return invoke;
    }
  public void log(String msg){
      System.out.println("执行了"+msg+"方法");
  }

}

package com.luckily.demo04;

import com.luckily.demo02.UserService;
import com.luckily.demo02.UserServiceImpl;

public class Client {
    public static void main(String[] args) {
        //真实角色
        UserServiceImpl userService = new UserServiceImpl();

        //代理角色,不存在
        ProxyInvocationHandler pih = new ProxyInvocationHandler();

        pih.setTarget(userService);//设置要代理的对象

        //动态生成代理类
        UserService proxy = (UserService)pih.getProxy();
        proxy.add();
    }




}

package com.luckily.demo02;

public interface UserService {
    public void add();
    public void delete();
    public void update();
    public void query();
}



实现类
 package com.luckily.demo02;

public class UserServiceImpl implements UserService {
    public void add() {
        System.out.println("增加了一个用户");
    }

    public void delete() {
        System.out.println("删除了一个用户");
    }

    public void update() {
        System.out.println("修改了一个用户");
    }

    public void query() {
        System.out.println("查询用户");
    }
}

动态代理的好处:

  • 可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .
  • 公共的业务由代理来完成 . 实现了业务的分工 ,
  • 公共业务发生扩展时变得更加集中和方便 .
  • 一个动态代理类代理的是一个接口,一般就是对应的一类业务
  • 一个动态代理类可以代理多个类只要是实现了同一个接口即可

AOP

1 什么是AOP

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JQzCdzVv-1628824238488)(C:\Users\mi\AppData\Roaming\Typora\typora-user-images\image-20210715150752728.png)]

2 AOP在Spring中的作用

提供声明式事务;允许用户自定义切面

以下名词需要了解下:

  • 横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志 , 安全 , 缓存 , 事务等等 …

  • 切面(ASPECT):横切关注点 被模块化 的特殊对象。即,它是一个类。 log

    把通知应用到切入点的过程,就是切面,切面是动作

  • 通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。 log中的方法

    实际增强逻辑的部分,称为通知

    • 通知有五中类型
    • 前置通知
    • 后置通知
    • 环绕通知
    • 异常通知
    • 最终通知
  • 目标(Target):被通知对象。

  • 代理(Proxy):向目标对象应用通知之后创建的对象。 生成的代理类

  • 切入点(PointCut):切面通知 执行的 “地点”的定义。 在哪里执行

    实际被增强的方法

  • 连接点(JointPoint):与切入点匹配的执行点

    类里面哪些方法可以被增强,这些方法称为连接点

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MLdlauTC-1628824238489)(C:\Users\mi\AppData\Roaming\Typora\typora-user-images\image-20210715150930463.png)]

SpringAOP中,通过Advice定义横切逻辑,Spring中支持5种类型的Advice:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wNO9orcC-1628824238489)(C:\Users\mi\AppData\Roaming\Typora\typora-user-images\image-20210715151146157.png)]

3 使用Spring 实现AOP

Spring框架一般基于aspectj实现针对AOP的相关操作

什么是aspectj?

  • aspectj不是Spring的组成部分,是 独立的AOP框架,一般把aspectj和Spring框架一起使用,进行AOP的一些相关操作

切入点表达式:

  • 切入点表达式作用:知道对哪个类里面的哪个方法进行增强

  • 语法结构:

    expression([权限修饰符] [返回类型] [类全路径] [方法名称] ([参数列表]))

    <!--        <aop:pointcut id="pointcut" expression="execution(* com.luckily.service.UserServiceImpl.*(..))"/>-->
                                  
    

4 AOP操作(aspectj注解)

1 创建类,在类里面定义方法
package com.luckily.aop;
//1,创建一个类  被增强的类
public class User {
    public void add(){
        System.out.println("add方法执行了");
    }
}

2 创建增强类(编写增强逻辑)
package com.luckily.aop;
//2 增强的类
public class UserProxy {
    //前置通知方法
    public void before(){
        System.out.println("before");
    }
}

3 进行通知的配置
  • 在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"
       xmlns:aop ="http://www.springframework.org/schema/aop"
       xmlns:context ="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">
       <context:component-scan base-package="com.luckily.aop"></context:component-scan>

</beans>
  • 使用注解创建User和UserProxy对象

    package com.luckily.aop;
    
    import org.springframework.stereotype.Component;
    
    //1,创建一个类  被增强的类
    @Component //创建对象
    public class User {
        public void add(){
            System.out.println("add方法执行了");
        }
    }
    
    
    
    
    package com.luckily.aop;
    
    import org.springframework.stereotype.Component;
    
    //2 增强的类
    @Component //创建对象
    public class UserProxy {
        //前置通知方法
        public void before(){
            System.out.println("before");
        }
    }
    
    
  • 在增强的类上面添加注解@Aspect

    package com.luckily.aop;
    
    import org.aspectj.lang.annotation.Aspect;
    import org.springframework.stereotype.Component;
    
    //2 增强的类
    @Component
    @Aspect//表示生成代理对象
    public class UserProxy {
        //前置通知方法
        public void before(){
            System.out.println("before");
        }
    }
    
    
  • 在Spring配置文件中开启生成代理对象

    <!--       开启Aspect生成对象-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    
  • 配置不同类型的通知

    • 在增强类的里面,在作为通知的上面添加通知类型的注解,使用切入点进行配置
package com.luckily.aop;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

//2 增强的类
@Component
@Aspect
public class UserProxy {
    //前置通知方法  @Before注解就表示前置通知,在增强方法的前面执行   
    @Before(value = "execution(* com.luckily.aop.User.add(..))")
    public void before(){
        System.out.println("before");
    }
}

5 @Order(数字)

数字越小优先级越高

【重点】使用AOP织入,需要导入一个依赖包!

   <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.5</version>
    </dependency>

方式一: 使用Spring的API接口【主要是spring接口实现】

代码见spring09-AOP

方式二: 使用自定义类来实现AOP【主要是切面

方式三:使用注解实现AOP

默认是JDK(proxy-target-class=“false” )默认为false

如果把 proxy-target-class=“true” 则是cglib实现

整合Mybatis

步骤:

  • 导入相关Jar包
    • junit
    • mybatis
    • mysql
    • spring相关的
    • aop织入
    • mybatis-spring
<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.11</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.2</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.1.9.RELEASE</version>
    </dependency>
<!--    spring操作数据库还需要-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.1.9.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.5</version>
    </dependency>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>2.0.2</version>
    </dependency>
</dependencies>
   <build>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>
        </resources>
    </build>

1 回忆Mybatis

  • 编写实体类
  • 编写核心配置文件
  • 编写接口
  • 编写Mapper.xml
  • 测试

JDBCTemplate

1 什么是JDBCTemplate?

  • Spring框架对JDBC进行了封装,使用JDBCTemplate方便实现对数据库的封装

准备工作

  • 引入相关的依赖

  • 配置数据库连接池,在xml文件中

  • 配置JDBCTemplate对象,注入DataSource

    JDBCTemplate对象
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource"></property>
    </bean>
    
  • 创建service类,创建dao,在dao中注入JDBCTemplate对象

2 JDBCTemplate操作数据库(添加)

  • 对应数据库建实体类
  • 编写Service,dao

事务操作

1 注解声明式事务管理

  • 在spring配置文件配置事务管理器
<!--创建事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--    注入数据源-->
    <property name="dataSource" ref="dataSource"/>
</bean>
  • 在spring配置文件,开启事务注解

    • 在spring的命名空间中引入tx

      <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:aop="http://www.springframework.org/schema/aop"
             xmlns:tx="http://www.springframework.org/schema/tx"
             xmlns:context="http://www.springframework.org/schema/context"
             xsi:schemaLocation="http://www.springframework.org/schema/beans
              https://www.springframework.org/schema/beans/spring-beans.xsd
              http://www.springframework.org/schema/aop
              https://www.springframework.org/schema/aop/spring-aop.xsd
              http://www.springframework.org/schema/context
              https://www.springframework.org/schema/context/spring-context.xsd
              http://www.springframework.org/schema/tx
              https://www.springframework.org/schema/tx/spring-tx.xsd">
      
<!--开启事务注解-->
<tx:annotation-driven transaction-manager="transactionManager"/>
    
    
  • 在service的类上面,或者里面的方法上,加上事务的注解
@Transactional//事务的注解
//添加到类上面,证明所有的方法都开启了注解
@Transactional的一些参数配置
  • propagation :事务的传播行为
    • 多第方法直接进行调用,这个过程中事务是如何进行管理的
    • 多事务进行相互电泳的时候,该怎么处理就叫传播行为

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YCh6lAnl-1628824238490)(C:\Users\mi\AppData\Roaming\Typora\typora-user-images\image-20210804154011201.png)]

  • isolation:隔离级别

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qTVRgU9P-1628824238491)(C:\Users\mi\AppData\Roaming\Typora\typora-user-images\image-20210804155412956.png)]

  • timeout :超时时间

  • readOnly :是否只读

    • 默认false 可以增,删,改,查
    • 若为true只可以查
  • rollbackFor:回滚

  • norollbackFor:不回滚

    • 设置出现有写异常可以不进行回滚

事务操作(XML方式)

  • 在spring配置文件中进行配置

    • 配置事务管理器

      <!--创建事务管理器-->
      <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
      <!--    注入数据源-->
          <property name="dataSource" ref="dataSource"/>
      </bean>
      
    • 配置通知

      <!--配置通知-->
          <tx:advice>
              <tx:attributes>
      <!--            指定在那种规则的犯法上面添加事务-->
                  <tx:method name="方法名"/>
              </tx:attributes>
          </tx:advice>
      
    • 配置切入点和切面

Spring5 框架的新功能

整个Spring5框架的代码基于Java8兼容java9,许多不建议使用的类和方法在代码库中删除了

Spring5 框架自带了通用的日志封装

  • Spring5 已经移除了Log4jConfigListener ,官方建议使用Log4j2

  • Spring5整合Log4j2

    • 怎么整合Log4j2

    • 引入相关的依赖

      log4j-api

      log4j-core

      log4j-slf4j-impl

      slf4j-api

    • 创建log4j2.xml的配置文件

      文件内容是固定的,网上百度

spring5框架核心容器支持@Nullable注解

  • @Nullable注解可以使用在方法上面,属性上面
Spring-Webflux
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值