关于Spring的学习笔记

1、Spring【学习笔记】

1.1、简介

  • Spring : 春天

  • 2002,首次推出了Spring框架的雏形:interface21框架!

  • Spring框架即以interface21框架为基础经过重新设计,并不断丰富其内涵,于2004年3月24日,发布了1.0正式版。

  • Rod Johnson ,Spring Framework创始人,著名作者。

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

  • SSH : struts+spring+hibernate

  • SSM : Spring+SpringMVC+MyBatis

官网 : http://spring.io/

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

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

<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.3.13</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.3.13</version>
</dependency>

1.2、优点

  • Spring 是一个开源的免费的框架(容器)
  • Spring是一个轻量级的,非入侵式的框架
  • 控制反转(IOC),面向切面编程(AOP)
  • 支持事务的处理,对框架整合的支持

总结:Spring就是一个轻量级的控制反转(IOC)和面向切面编程(AOP)的框架

1.3、组成

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2NQxECrN-1639486123669)(D:\相册\ma\spring.png)]

1.4、扩展

  • Spring Boot
    • 一个快速开发的脚手架
    • 基于Spring Boot可以快速的开发单个微服务
    • 约定大于配置
  • Spring Clou
    • Spring Cloud是基于Spring Boot 实现的
  • Spring弊端发展太久之后,配置十分繁琐。

2、IOC理论

1.UserDao接口

public interface UserDao {
    void getUser();
}

2.UserDaoImpl实现类

public class UserDaoImpl implements UserDao{
    public void getUser(){
        System.out.println("默认获取用户的数据");
    }
}

3.UserService业务接口

public interface UserService {
    void getUser();
}

4.UserServiceImpl业务实现类

public class UserServicempl implements UserService{

    private UserDao userDao  = new UserDaoImpl() ;

    public void getUser() {
        userDao.getUser();
    }
}

5.测试一下

public static void main (String[] args){

//      用户实际调用的是业务层,dao层他们不需要接触
        UserService userService = new UserServicempl();
        userService.getUser();
    }

在之前的代码中,如果实现类多了,我们需要根据用户的需求去修改原代码,每次都需要去service实现类里面修改对应的实现 ,为了解决这个问题,我们可以在需要用到他的地方 , 不去实现它 , 而是留出一个接口 , 利用set , 代码如下:

使用Set接口实现

 private UserDao userDao ;//原本是 private UserDao userDao  = new UserDaoImpl() ;
 
//创建set方法,利用set进行动态实现值的注入
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

测试一下

public static void main (String[] args){

//      用户实际调用的是业务层,dao层他们不需要接触
        UserService userService = new UserServicempl();
        ((UserServicempl)userService).setUserDao(new UserDaoOracleImpl());
        userService.getUser();
    }
  • 之前,程序是主动创建对象,控制权在程序员手上
  • 使用了set注入后,程序不再具有主动性,而是变成被动的接受对象,主动权在用户手上

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

IOC本质

**控制反转IoC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IoC的一种方法,**也有人认为DI只是IoC的另一种说法。没有IoC的程序中 , 我们使用面向对象编程 , 对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方,个人认为所谓控制反转就是:获得依赖对象的方式反转了。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xzc0OjAv-1639486123671)(D:\相册\ma\QQ截图20211204102814.png)]

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Dyy6t0NN-1639486123672)(D:\相册\ma\QQ截图20211204103111.png)]

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

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

3、HelloSpring

  1. 导入Spring相关jar包
 <!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.3.13</version>
  </dependency>
  1. 编写一个hello实体类
public class Hello {
     private String str;

    public String getStr() {
        return str;
    }

    public void setStr(String str) {
        this.str = str;
    }

    @Override
    public String toString() {
        return "Hello{" +
                "str='" + str + '\'' +
                '}';
    }
  1. 编写spring文件命名为beans.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
类型 变量名 = new 类型();
Hello hello = new Hello();
id = 变量名
class = new 的对象
property 相对于给对象中的属性设置一个值
-->
   <bean id="hello" class="com.zhang.po.Hello">
       <property name="str" value="Spring"/>
   </bean>
</beans>
  1. 测试一下
public static void main(String[] args) {
        //获取spring的上下文对象
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        //我们的对象现在都在spring中管理,我们如果要使用,直接在里面拿就可以
        Hello hello = (Hello) context.getBean("hello");
        System.out.println(hello.toString());
    }
  • hello对象是由spring创建的
  • hello对象的属性是由spring容器设置的

控制反转

控制:传统应用程序的对象是由程序本身控制创建的,使用spring,对象是由spring来创建的。

反转:程序本身不创建对象,而变成被动的接收

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

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

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

4、IOC创建对象的方式

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

1.1导入Spring相关jar包

 <!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.3.13</version>
  </dependency>

1.2编写一个user类

public class User {
    private String name;

    public User(){
        System.out.println("User的无参构造");
    }
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    public void show(){
        System.out.println("name"+name) ;
    }

1.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="user" class="com.zhang.po.User">
        <property name="name" value="张三"/>
    </bean>

</beans>

1.4测试一下

 public static void main(String[] args){
//        User user = new User();
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        User user = (User) context.getBean("user");
        user.show();
    }

2.有参构造创建对象

2.1导入Spring相关jar包

<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.3.13</version>
  </dependency>

2.2编写一个user类

package com.zhang.po;

public class User {
    private String name;

    public User(String name){
        this.name = name ;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    public void show(){
        System.out.println("name"+name) ;
    }
}

2.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--  无参构造对象
<bean id="user" class="com.zhang.po.User">
        <property name="name" value="张三"/>
</bean>
 -->
<!--    下标赋值
    <bean id="user" class="com.zhang.po.User">
        <constructor-arg index="0" value="里斯"/>
    </bean>
-->
<!--    通过类型,不建议使用
    <bean id="user" class="com.zhang.po.User">
        <constructor-arg type="java.lang.String" value="lisi"/>
    </bean>
    -->
<!--    直接通过参数名来设置-->
    <bean id="user" class="com.zhang.po.User">
        <constructor-arg name="name" value="zhang"/>
    </bean>

</beans>

2.4测试一下

public static void main(String[] args){
//        User user = new User();
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        User user = (User) context.getBean("user");
        user.show();
    }

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

5、Spring配置

5.1、别名

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

5.2 、Bean的配置

<!--    
id:bean的唯一标识符
class:bean对象所对应的全限定名称:包名 + 类型
name:也是别名,name可以同时取多个别名
-->
    <bean id="user" class="com.zhang.po.User">
        <constructor-arg name="name" value="zhang"/>
    </bean>

5.3、import

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

假设,项目是多人开发的,这些人负责不同的类的开发,不同的类需要注册在不同的bean中,可以利用import将所有的beans.xml文件合并为一个总的,使用时直接使用总的配置就可以。

 <import resource="beans.xml"/>
 <import resource="beans1.xml"/>

6、DI依赖注入

6.1、构造器注入

前面有

6.2、Set方式注入【重点】

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

【环境搭建】

1.复杂类型

public class Address {
    private String address;

    public String getAddress() {
        return address;
    }

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

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

2.真实测试对象

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 wife ;
    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 getWife() {
        return wife;
    }

    public void setWife(String wife) {
        this.wife = wife;
    }

    public Properties getInfo() {
        return info;
    }

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

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

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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="address" class="com.zhang.po.Address">
        <property name="address" value="西安"/>
    </bean>
    <bean id="student" class="com.zhang.po.Student">
        <!--   普通值注入,value-->
          <property name="name" value="zhang"/>
        <!--   bean注入, ref   -->
        <property name="address" ref="address"/>
        <!--    数组注入-->
        <property name="books">
            <array>
                <value>红楼梦</value>
                <value>水浒传</value>
                <value>三国演义</value>
            </array>
        </property>
        <!--    list注入-->
        <property name="hobbys">
            <list>
                <value>听歌</value>
                <value>看书</value>
                <value>敲代码</value>
            </list>
        </property>
        <!--    map注入-->
        <property name="card">
            <map>
                <entry key="省份证" value="12345667"/>
                <entry key="学号" value="90867687"/>
            </map>
        </property>
        <!--    map注入-->
        <property name="games">
            <set>
                <value>王者</value>
                <value>英雄联盟</value>
            </set>
        </property>
        <!--    null注入-->
        <property name="wife">
            <null/>
        </property>
        <!--    Properties注入-->
        <property name="info">
            <props>
                <prop key="driver">4563245</prop>
                <prop key="url"></prop>
                <prop key="username">root</prop>
                <prop key="password">12345</prop>
            </props>
        </property>
    </bean>

</beans>

4.测试一下

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.toString());
    }

6.3、其他方式

p命名空间与c命名空间

1.测试对象

package com.zhang.po;

public class User {
    private String name;
    private int age;

    public User() {
    }

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

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

2.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:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
   <!--    p命名空间注入,可以直接注入属性的值:property-->
   <bean id="user" class="com.zhang.po.User" p:name="张三" p:age="18"/>
   <!--    c命名空间注入,通过构造器注入:construct-args-->
   <bean id="user2" class="com.zhang.po.User" c:name="历史" c:age="20"/>

</beans>

3.测试一下

import com.zhang.po.Student;
import com.zhang.po.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest01 {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("userbeans.xml");
//        User user = (User) context.getBean("user");
        User user = (User) context.getBean("user2");
        System.out.println(user.toString());

    }
}

注意点:p命名空间和c命名空间不能直接使用,需要导入xml约束!

 xmlns:p="http://www.springframework.org/schema/p"
 xmlns:c="http://www.springframework.org/schema/c"

6.4、bean的作用域

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VhMMwm6a-1639486123674)(D:\相册\ma\bean的作用域.png)]

1.单例模式(Spring默认机制)

<bean id="accountService" class="com.something.DefaultAccountService"/>

<!-- the following is equivalent, though redundant (singleton scope is the default) -->
<bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>

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

<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>

3.其余的request,session,application.这些只能在web开发中使用到

4.生命周期

single (单例)对象:一个应用只有一个对象的实例。作用范围:整个应用,基于spring容器的生命周期。例如:SqlSessionFactory对应一个数据库环境。

生命周期:
对象创建:创建spring容器时,对象就被创建。
对象活着:只要容器在,对象一直活着。
对象销毁:容器被销毁,对象被销毁。

prototype(原型)对象: 每次访问对象,都会重新创建一个新的对象实例。例如:SqlSession
生命周期:
对象创建:当使用对象时,创建新的对象。
对象活着:只要对象在使用中,就活着。
对象销毁:当对象长时间不使用时,被java垃圾回收器进行回收。

7、Bean的自动装配

  • 自动装配是Spring满足bean依赖一种方式
  • Spring会在上下文中自动寻找,并自动给bean装配属性

在Spring中有三种装配的方式

1.在xml中显示的配置

2.在Java中显示的配置

3.隐式的自动装配bean【重要】

7.1、测试

环境搭建:一个人有两个宠物

7.2、ByName自动装配

 <!--
    byName:会自动在容器的上下文中查找,和自己对象set方法后面的值对应的 beanid
    -->
    <bean id="people" class="com.zhang.po.People" autowire="byName">
        <property name="name" value="张三"/>
    </bean>

7.3、ByType自动装配

 <!--
       byType:会自动在容器的上下文中查找,和自己对象属性类型相同的 bean ,必须保证这个类型全局唯一
       -->
    <bean id="people" class="com.zhang.po.People" autowire="byType">
        <property name="name" value="张三"/>
    </bean>

小结:

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

7.4、使用注解实现自动装配

基于注解的配置的引入提出了一个问题,即这种方法是否比 XML“更好”。

要使用注解须知:

1.导入约束 : context

2.配置注解的支持 : context:annotation-config/ 【重要】

<?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>

@Autowired

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

使用Autowired我们可以不用编写set方法了,前提是你这个自动装置的属性在IOC(Spring)容器中存在,且符合名字"byType",有多个类型的时候就用byName,应该先用byType,然后都存在再按照byName

扩展 :

@Nullable  字段标记了这个注解,说明这个字段可以为null;
public @interface Autowired {
    boolean required() default true;
}

测试代码 :

public class People {
    @Autowired
    private Cat cat;
    @Autowired
    private Dog dog;
    private String name;

如果 @Autowired自动装配的环境比较复杂,自动装配无法通过一个 @Autowired完成的时候,我们可以使用@Qualifier(value = “xxx”)去配置 @Autowired的使用,指定一个唯一的bean对象注入

public class People {
    @Autowired
    private Cat cat;
    @Autowired
    @Qualifier(value = "dog11")
    private Dog dog;
    private String name;

@Resource 默认按byName自动注入 但是@Resource有两个属性是比较重要的,分别是name和type;

如果使用name属性,则使用byName的自动注入策略; 而使用type属性时则使用byType自动注入策略;

public class People {
    @Resource
    private Cat cat;
    @Resource

    private Dog dog;
    private String name;

小结:

@Resource和@Autowired的区别:

  • 都是用自动装置的,都可以放在属性字段上
  • @Autowired 应该先用byType,然后都存在再按照byName
  • @Resource 默认按byName自动注入
  • 执行顺序不同

8、使用注解开发

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qcDIr5hs-1639486123676)(D:\相册\ma\QQ截图20211205232945.png)]

使用注解需要导入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:component-scan base-package="com.zhang"/>
    <context:annotation-config/>
</beans>
@Component 组件,放在类上,说明这个类被spring管理了,就是bean

1.bean

2.属性如何注入

package com.zhang.po;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
//等价于<bean id="user" class="com.zhang.po.User"/>
//@Component 组件
@Component
//@Scope("prototype")原型
@Scope("singleton")
public class User {

    public String name ;
    //    相当于 <property name="name" value="zhang"/>
    @Value("zhang")
    public void setName(String name) {
        this.name = name;
    }
}

3.衍生的注解

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

  • dao【@Repository】
  • service【@Service】
  • controller【@Controller】

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

4.自动装配置

  • @Resource
  • @Autowired
  • @Nullable
  • @Component

5.作用域

package com.zhang.po;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
//等价于<bean id="user" class="com.zhang.po.User"/>
//@Component 组件
@Component
//@Scope("prototype")原型
@Scope("singleton")
public class User {

    public String name ;
    //    相当于 <property name="name" value="zhang"/>
    @Value("zhang")
    public void setName(String name) {
        this.name = name;
    }
}

6.小结

xml与注解:

  • xml更加万能,适用于任何场合,维护简单方便
  • 注解不是自己的类使用不了,维护相对复杂

xml和注解最佳实践:

  • xml用来管理bean
  • 注解只是负责完成属性的注入
  • 我们在使用的过程中必须让注解生效,需要开启注解的支持
<!--指定要扫描的包,这个包的注解就会生效-->
    <context:component-scan base-package="com.zhang"/>
    <context:annotation-config/>

9、使用Java的方式配置spring

我们现在要完全不使用Spring的xml配置了,全权交给Java来做

JavaConfig是Spring的一个子项目,在Spring4之后,它成为了一个核心功能

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IC47MFee-1639486123677)(D:\相册\ma\56.png)]

1.实体类

//这个注解说明这个类被Spring接管了,注册到了容器中
@Component
public class User {
    private String name;

    public String getName() {
        return name;
    }
//    属性注入值
@Value("zhang")
    public void setName(String name) {
        this.name = name;
    }

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

2.配置文件

//@Configuration也会Spring容器托管,注册到容器中,因为它本来就是一个@Component
//@Configuration代表这是一个配置类,就和beans.xml
@Configuration
@ComponentScan("com.zhang.po")
@Import(ZhangConfig2.class)
public class ZhangConfig {
//    注册一个bean,就相当于一个bean标签
//    方法名就相当于bean标签中的id属性
//    返回值就相当于bean标签中的class属性
    @Bean
    public User getUser(){

        return new User();//就是返回要注入到bean的对象
    }
}
@Configuration
public class ZhangConfig2 {

}

3.测试类

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

这种纯Java的配置方式,在springboot中随处可见

10、代理模式

为什么要学习代理模式,因为这就是SpringAOP的底层 【SpringAOP和SpringMVC】

代理模式的分类

  • 静态代理
  • 动态代理

1.静态代理

角色分析:

  • 抽象角色:一般会使用接口或抽象类来解决
  • 真实角色:被代理的角色
  • 代理角色:代理真实角色,代理真实角色后,我们一般会做一些附属操作
  • 客户:访问代理对象的人

代码步骤:

1.接口

package com.zhang.demo01;
//租房
public interface Rent {
    public void rent();
}

2.真实角色

package com.zhang.demo01;
//房东
public class Host implements Rent{

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

3.代理角色

package com.zhang.demo01;

public class Proxy implements Rent{
    private Host host;

    public Proxy(){
    }

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

    public void rent() {
        host.rent();
        seeHouse();
        hetong();
        fare();
    }
//    看房
    public void seeHouse(){
        System.out.println("中介带你看房");
    }
    //    收中介费
    public void fare(){
        System.out.println("收中介费");
    }
    //    签租赁合同
    public void hetong(){
        System.out.println("签租赁合同");
    }
}

4.客户端访问代理角色

package com.zhang.demo01;

public class Client {
    public static void main(String[] args) {
//        房东要租房子
        Host host = new Host();
//        host.rent();
//        代理,中介帮房东租房子,但是代理角色一般会有一些附属操作
        Proxy proxy = new Proxy(host);
//        你不用面对房东,直接找中介租房子即可
        proxy.rent();
    }
}

代理模式的好处:

  • 可以使真实角色的操作更加纯粹,不用去关注一些公共的业务
  • 公共业务就交给代理角色,实现了业务的分工
  • 公共业务发生扩展的时候,方便集中管理

缺点:

  • 一个真实角色就会产生一个代理角色,代码量会翻倍—开发效率会变低

2.加深理解

关于 AOP[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Hte8xJTK-1639486123678)(D:\相册\ma\QQ截图20211208153503.png)]

1.接口

package com.zhang.demo02;

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

2.真实角色

package com.zhang.demo02;

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

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

    }

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

    }

    @Override
    public void query() {
        System.out.println("查询了一个用户");

    }

}

3.代理角色

package com.zhang.demo02;

public class UserServiceProxy implements UserService{
    private UserServiceImpl userService;

    public void setUserService(UserServiceImpl userService) {
        this.userService = userService;
    }

    @Override
    public void add() {
        log("add");
        userService.add();
    }

    @Override
    public void delete() {
        log("delete");
        userService.add();

    }

    @Override
    public void update() {
        log("update");
        userService.add();

    }

    @Override
    public void query() {
        log("query");
        userService.add();

    }
//    日志方法
    public void log(String msg){
        System.out.println("使用了" +msg+ "方法");
    }
}

4.客户端访问角色

package com.zhang.demo02;

public class Client {
    public static void main(String[] args) {
        UserServiceImpl userService = new UserServiceImpl();
//        userService.add();
        UserServiceProxy proxy = new UserServiceProxy();
        proxy.setUserService(userService);

        proxy.add();
    }
}

3.动态代理

  • 动态代理和静态代理角色一样
  • 动态代理的代理类是动态生成的,不是我们直接写好的
  • 动态代理分为两大类:基于接口的动态代理,基于类的动态代理
    • 基于接口 — JDK动态代理
    • 基于类 :cglib
    • java字节码实现:javassist

需要了解 :Proxy :代理 类,InvocationHandler :调用处理程序接口

动态代理的好处:

  • 可以使真实角色的操作更加纯粹,不用去关注一些公共的业务
  • 公共业务就交给代理角色,实现了业务的分工
  • 公共业务发生扩展的时候,方便集中管理
  • 一个动态代理类代理的是一个接口,一般就对应的一类业务
  • 一个动态代理类可以代理多个类,只要是实现了同一个接口即可

11、AOP

1.什么是AOP

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

2.Aop在Spring中的作用

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

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

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

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

  • 目标(Target):被通知对象

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

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

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

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4xfbPOJr-1639486123680)(D:\相册\ma\QQ截图20211209181159.png)]

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

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rFLRHN2s-1639486123681)(D:\相册\ma\QQ截图20211209181225.png)]

即AOP在不改变原有代码的情况下,去增加新的功能。

3、使用Spring实现AOP

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

<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.4</version>
</dependency>

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

  1. 在service包下,定义UserService业务接口和UserServiceImpl实现类
package com.zhang.service;

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

package com.zhang.service;

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

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

    @Override
    public void update() {
        System.out.println("更新了一个用户");
    }

    @Override
    public void select() {
        System.out.println("查询了一个用户");
    }
}

2.在log包下,定义我们的增强类,一个Log前置增强和一个AfterLog后置增强类

package com.zhang.log;

import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;

public class log implements MethodBeforeAdvice {
//method:要执行的目标对象的方法
//args:参数
//target:目标对象
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println(target.getClass().getName()+"的"+method.getName()+"被执行了");
    }
}

package com.zhang.log;

import org.springframework.aop.AfterAdvice;
import org.springframework.aop.AfterReturningAdvice;

import java.lang.reflect.Method;

public class AfterLog implements AfterReturningAdvice {
//    returnValue:返回值
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("执行了"+method.getName()+"方法,返回结果为:"+returnValue);
    }
}

3.最后去spring的文件中注册 , 并实现aop切入实现 , 注意导入约束,配置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"
       xmlns:aop="http://www.springframework.org/schema/aop"
       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">
<!--   注册bean-->
    <bean id="userService" class="com.zhang.service.UserServiceImpl"/>
    <bean id="log" class="com.zhang.log.log"/>
    <bean id="afterlog" class="com.zhang.log.AfterLog"/>

<!--    方式一:使用原生Spring API接口-->
<!--    配置aop,需要导入aop的约束-->
    <aop:config>
<!--      切入点: expression:表达式,execution(要执行的位置!xxpublic修饰词,返回值,类名,方法名,参数) -->
        <aop:pointcut id="pointcut" expression="execution(* com.zhang.service.UserServiceImpl.*(..))"/>

<!--        执行环绕增加-->
        <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
        <aop:advisor advice-ref="afterlog" pointcut-ref="pointcut"/>
    </aop:config>
</beans>

4.测试

import com.zhang.service.UserService;
import com.zhang.service.UserServiceImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//        动态代理代理的是接口【注意点】
        UserService userService = (UserService) context.getBean("userService");
        userService.add();
    }
}

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

  1. 在diy包下定义自己的DiyPointCut切入类
package com.zhang.diy;

public class DiyPointCut {
    public void before(){
        System.out.println("======方法执行前======");
    }
    public void after(){
        System.out.println("======方法执行后======");
    }
}

2.在spring中配置文件

<!--    方式二:自定义类-->
    <bean id="diy" class="com.zhang.diy.DiyPointCut"/>
    <aop:config>
<!--        自定义切面,ref要引用的类-->
        <aop:aspect ref="diy">
<!--            切入点-->
            <aop:pointcut id="point" expression="execution(* com.zhang.service.UserServiceImpl.*(..))"/>
<!--            通知-->
            <aop:before method="before" pointcut-ref="point"/>
            <aop:after method="after" pointcut-ref="point"/>
        </aop:aspect>
    </aop:config>

3.测试

import com.zhang.service.UserService;
import com.zhang.service.UserServiceImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//        动态代理代理的是接口【注意点】
        UserService userService = (UserService) context.getBean("userService");
        userService.add();
    }
}

方法三:使用注解实现

1.在diy包下定义注解实现的AnnotationPointCut增强类

package com.zhang.diy;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

//方式三 :使用注解方式实现AOP
@Aspect //标注这个类是一个切面
public class AnnotationPointCut {
    @Before("execution(* com.zhang.service.UserServiceImpl.*(..))")
    public void before(){
        System.out.println("======方法执行前======");
    }
    @After("execution(* com.zhang.service.UserServiceImpl.*(..))")
    public void after(){
        System.out.println("======方法执行后======");
    }
//    在环绕增强中,我们可以给定一个参数,代表我们要获取切入的点
    @Around("execution(* com.zhang.service.UserServiceImpl.*(..))")
    public void around(ProceedingJoinPoint jp)throws Throwable{
        System.out.println("环绕前");
        Object proceed = jp.proceed();//执行方法
        System.out.println("环绕后");
    }
}

2.在Spring配置文件中,注册bean,并增加支持注解的配置。

<!--    方式三-->
    <bean id="annotationPointCut" class="com.zhang.diy.AnnotationPointCut"/>
<!--    开启注解支持    JDK(默认 proxy-target-class="false")   cglib(proxy-target-class="true")-->
    <aop:aspectj-autoproxy />

3.测试

import com.zhang.service.UserService;
import com.zhang.service.UserServiceImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//        动态代理代理的是接口【注意点】
        UserService userService = (UserService) context.getBean("userService");
        userService.add();
    }
}

12、整合Mybatis

步骤:

1.导入相关jar包

  • junit
  • mybatis
  • mysql数据库
  • spring相关的
  • aop织入
  • mybatis-spring
<dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.1</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.22</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.3.13</version>
        </dependency>
<!--        Spring操作数据库的话,还需要一个spring-jdbc-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.2.5.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>1.3.1</version>
        </dependency>
    </dependencies>

2.编写配置文件

3.测试

1. 回忆mybatis

1.编写实体类

package com.zhang.po;

import lombok.Data;

@Data
public class UserInfo {
    private int uid;
    private String uname;
    private String upassword;
    private int did;
}

2.编写核心配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>


    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

    <typeAliases>
        <package name="com.zhang.po" />
    </typeAliases>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <!--            配置数据源:创建Connection对象-->
            <dataSource type="POOLED">
                <!--                diver:驱动的内容-->
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <!--                连接数据库的url-->
                <property name="url" value="jdbc:mysql://localhost:3306/food?serverTimezone=UTC"/>
                <!--                用户名-->
                <property name="username" value="root"/>
                <!--                密码-->
                <property name="password" value="263283"/>
            </dataSource>
        </environment>
    </environments>


    <mappers>
        <mapper resource="com/zhang/dao/UserInfoMapper .xml"/>
    </mappers>
</configuration>

3.编写接口

package com.zhang.dao;

import com.zhang.po.UserInfo;

import java.util.List;

public interface UserInfoMapper{
    public List<UserInfo> selectUserInfo();
}

4.编写Mapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zhang.dao.UserInfoMapper">

    <select id="selectUserInfo" resultType="UserInfo">
        select * from userinfo
    </select>

</mapper>

5.测试

import com.zhang.dao.UserInfoMapper;
import com.zhang.po.UserInfo;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

public class MyTest {
    @Test
    public void test() throws IOException {
        String resources = "mybatis-config.xml";
        InputStream in = Resources.getResourceAsStream(resources);

        SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(in);
        SqlSession sqlSession = sessionFactory.openSession(true);

        UserInfoMapper mapper = sqlSession.getMapper(UserInfoMapper.class);
        List<UserInfo> userInfos = mapper.selectUserInfo();

        for (UserInfo user : userInfos){
            System.out.println(user);
        }


    }
}

2.Mybatis-Spring

实现整合一:

1.编写数据源配置

<!--    DataSource:使用Spring的数据源替换mybatis的配置  c3p0 dbcp  druid
        这里使用Spring提供的JDBC
-->
    <bean id="datasource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/food?serverTimezone=UTC"/>
        <property name="username" value="root"/>
        <property name="password" value="263283"/>
    </bean>

2.sqlSessionFactory

<!--   sqlSessionFactory-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="datasource"/>
<!--        绑定mybatis配置文件-->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <property name="mapperLocations" value="classpath:com/zhang/dao/* .xml"/>
    </bean>

3.sqlSessionTemplate

<!--    SqlSessionTemplate:就是我们使用的sqlSession-->
    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<!--        只能使用构造器注入,因为sqlSessionFactory没有set方法-->
        <constructor-arg index="0" ref="sqlSessionFactory"/>
    </bean>

4.需要给接口加实现类【】

package com.zhang.dao;

import com.zhang.po.UserInfo;
import org.mybatis.spring.SqlSessionTemplate;

import java.util.List;

public class UserInfoMapperImpl implements UserInfoMapper{
//    在原来我们所有的操作,都使用sqlSession来操作;现在都使用SqlSessionTemplate
    private SqlSessionTemplate sqlSession;

    public void setSqlSession(SqlSessionTemplate sqlSession) {
        this.sqlSession = sqlSession;
    }

    @Override
    public List<UserInfo> selectUserInfo() {
        UserInfoMapper mapper = sqlSession.getMapper(UserInfoMapper.class);
        return  mapper.selectUserInfo();
    }
}

5.将自己写的实现类,注入到Spring中

 <bean id="userInfoMapper" class="com.zhang.dao.UserInfoMapperImpl">
        <property name="sqlSession" ref="sqlSession"/>
    </bean>

6.测试使用

public class MyTest {
    @Test
    public void test() throws IOException {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-dao.xml");

        UserInfoMapper userInfoMapper = context.getBean("userInfoMapper",UserInfoMapper.class);
        for (UserInfo userInfo : userInfoMapper.selectUserInfo()) {
            System.out.println(userInfo);
        }
    }
}

实现整合二:

mybatis-spring1.2.3版以上的才有这个,官方文档截图:

dao继承Support类 , 直接利用 getSqlSession() 获得 , 然后直接注入SqlSessionFactory . 比起整合方式一 , 不需要管理SqlSessionTemplate , 而且对事务的支持更加友好 . 可跟踪源码查看。

1.实现类

package com.zhang.dao;

import com.zhang.po.UserInfo;
import org.mybatis.spring.support.SqlSessionDaoSupport;

import java.util.List;

public class UserInfoMapperImpl2 extends SqlSessionDaoSupport implements UserInfoMapper {
    @Override
    public List<UserInfo> selectUserInfo() {
        return getSqlSession().getMapper(UserInfoMapper.class).selectUserInfo();
    }
}

2.注入到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"
       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">
    <import resource="spring-dao.xml"/>


    <bean id="userInfoMapper" class="com.zhang.dao.UserInfoMapperImpl">
        <property name="sqlSession" ref="sqlSession"/>
    </bean>

    <bean id="userInfoMapper2" class="com.zhang.dao.UserInfoMapperImpl2">
        <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
    </bean>

</beans>

3.测试

import com.zhang.dao.UserInfoMapper;
import com.zhang.po.UserInfo;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.io.IOException;


public class MyTest {
    @Test
    public void test() throws IOException {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

        UserInfoMapper userInfoMapper = context.getBean("userInfoMapper2",UserInfoMapper.class);
        for (UserInfo userInfo : userInfoMapper.selectUserInfo()) {
            System.out.println(userInfo);
        }
    }
}

13、声明式事务

1、回顾事务

  • 把一组业务当成一个业务来做;要么都成功,要么都失败
  • 事务在项目开发中,十分重要,涉及到数据的一致性问题,不能马虎
  • 确保完整性和一致性

事务的ACID原则

  • 原子性
  • 一致性
  • 隔离性
    • 多个业务可能操作同一资源,防止数据损坏
  • 持久性
    • 事物一旦提交,无论系统发生什么问题,结果都不会再被影响,被持久化的写到存储器中

2、spring中的事务管理

  • 声明式事务:AOP
  • 编程式事务:需要在代码中,进行事务的管理
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值