Spring5

Spring

1、Spring

1.1、简介

  • Spring:给软件行业带来了春天!
  • 2002年,首次推出了spring框架的雏形:interface21框架
  • Spring框架即以interface21框架为基础,经过重新设计,并不断丰富其内涵,于2004年3月24日,发布了1.0正式版。
  • 作者:Rod Johnson 音乐学的博士
  • Spring理念:使现有的技术更加容易使用,本身是一个大杂烩,整合了现有的技术框架。

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

官方下载地址:https://docs.spring.io/spring/docs/4.3.9.RELEASE/spring-framework-reference/

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

导包:

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

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


1.2、优点

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

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

1.3、组成

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ajbjAqDj-1580535659596)(/Users/mac/Library/Application Support/typora-user-images/image-20200129150702648.png)]

1.4、拓展

在Spring的官网有这个介绍:现代化的Java开发!说白了就是基于Spring的开发!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pcbpcUBK-1580535659598)(/Users/mac/Library/Application Support/typora-user-images/image-20200129150936413.png)]

  • Spring Boot
    • 一个快速开发的脚手架
    • 基于SpringBoot可以快速开发单个微服务
    • 约定大于配置!
  • Spring Cloud
    • Spring Cloud是基于Spring Boot实现的

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

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

2、IOC理论推导

  1. UserDao接口
  2. UserDdoImpl实现类
  3. UserService业务接口
  4. UserServiceImpl业务实现类

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

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

 private UserDao userDao;

    //利用set进行动态实现值得注入!
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
  • 之前程序是主动创建对象!控制权在程序员手上!
  • 使用了set注入后,程序不再具有主动性!而是变成了被动的接收对象!

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1ZLwvYcJ-1580535659599)(/Users/mac/Library/Application Support/typora-user-images/image-20200129161803957.png)]

IOC本质

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h164wMF8-1580535659599)(/Users/mac/Library/Application Support/typora-user-images/image-20200129162106393.png)]

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

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

3、Hello Spring

  • Hello 对象是谁创建的 ? 【 hello 对象是由Spring创建的 】
  • Hello 对象的属性是怎么设置的 ? 【hello 对象的属性是由Spring容器设置的 】

这个过程就叫控制反转 :

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

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

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

可以通过newClassPathXmlApplicationContext去浏览一下底层源码 .

OK , 到了现在 , 我们彻底不用再程序中去改动了 , 要实现不同的操作 , 只需要在xml配置文件中进行修改 , 所谓的IoC,一句话搞定 : 对象由Spring 来创建 , 管理 , 装配 !

4、IOC创建对象的方式

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

  2. 假设我们要使用有参构造创建对象》

    1. 下标赋值

      <!--      第一种:下标赋值-->
           <bean id="user" class="com.yuan.pojo.User">
               <constructor-arg index="0" value="狂神说Java"/>
           </bean>
      
      
    2. 类型赋值

      <!--    第二种方式:通过类型创建,不建议使用-->
          <bean id="user" class="com.yuan.pojo.User">
              <constructor-arg type="java.lang.String" value="你帅哥哥"/>
          </bean>
      
    3. 参数名

      <!--    第三种:直接通过参数名设置-->
          <bean id="user" class="com.yuan.pojo.User">
              <constructor-arg name="name" value="你帅哥哥"/>
          </bean>
      

    5、Spring配置

    5.1、别名

    <!--    别名:如果添加了别名,我们也可以使用别名获取到这个对象-->
        <alias name="user" alias="ssdsdsads" />
    

    5.2、Bean的配置

    <!--
           id:bean的唯一标识符,也就是相当于我们学过的对象名
           class:bean对象所对应的全限定名: 包名+类型
           name : 也是别名,而且name可以取多个别名
           -->
        <bean id="user" class="com.yuan.pojo.User" name="user2,user3,user4">
            <property name="name" value="Speing"/>
        </bean>
    

    5.3、import

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

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

  • 张三
  • 李四
  • 王五
  • ApplicationContext.xml
 <import resource="bean.xml"/>
    <import resource="bean2.xml"/>
    <import resource="bean3.xml"/>

使用的时候,直接使用总的配置就可以了。

6、依赖注入(DI)

6.1、构造器注入

前面已经说过了

6.2、Set方式注入【重点】

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

【环境搭建】

  1. 复杂类型

    package com.yuan.pojo;
    
    public class Address {
        private String address;
    
        public String getAddress() {
            return address;
        }
    
        public void setAddress(String address) {
            this.address = address;
        }
    }
    
    
  2. 真实测试对象

    public class Student {
        private String name;
        private Address address;
        private String[] books;
        private List<String> hobbies;
        private Map<String,String> card;
        private Set<String> games;
        private String wife;
        private Properties info;
    
  3. 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">
    
    </beans>
    
  4. 测试类

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

    完善注入信息:

    <?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.yuan.pojo.Address"/>
    
        <bean id="student" class="com.yuan.pojo.Student">
    <!--        第一种:普通值注入-->
            <property name="name" value="你帅哥哥"/>
    <!--        第二种:bean注入  : ref  -->
            <property name="address" ref="address"/>
    
    <!--        数组注入: ref-->
            <property name="books">
                <array>
                    <value>红楼梦</value>
                    <value>西游记</value>
                    <value>水浒传</value>
                    <value>三国演义</value>
                </array>
            </property>
    <!--        List-->
            <property name="hobbies">
                <list>
                    <value>听歌</value>
                    <value>敲代码</value>
                    <value>看电影</value>
                </list>
            </property>
    
    <!--        Map-->
            <property name="card">
                <map>
                    <entry key="身份证" value="154654199011231546"/>
                    <entry key="银行卡" value="6228454521147569985"/>
                    <entry key="" value=""/>
                </map>
            </property>
    
    <!--        Set-->
            <property name="games">
                <set>
                    <value>LOL</value>
                    <value>DNF</value>
                    <value>CF</value>
                </set>
            </property>
    
    <!--        null-->
            <property name="wife">
                <null/>
            </property>
    
    <!--        Properties-->
            <property name="info">
                <props>
                    <prop key="学号">180451236</prop>
                    <prop key="性别">男性</prop>
                    <prop key="姓名">小明</prop>
                    <prop key="username">root</prop>
                    <prop key="password">123456</prop>
                </props>
            </property>
    
    
    
        </bean>
    
    
    </beans>
    

6.3、拓展方式注入

我们可以使用p命名空间和c命名空间进行注入!

官方解释:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m6gObbq6-1580535659600)(/Users/mac/Library/Application Support/typora-user-images/image-20200129232504534.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c0sXkF6W-1580535659600)(/Users/mac/Library/Application Support/typora-user-images/image-20200129232552883.png)]

<?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.yuan.pojo.User" p:name="你帅哥哥" p:age="18" />

    c命名空间注入,通过构造器注入 : construct-args
    <bean id="user2" class="com.yuan.pojo.User" c:age="18" c:name="你帅哥哥"/>

</beans>

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

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

6.4、bean的作用域

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jf3O5c2W-1580535659601)(/Users/mac/Library/Application Support/typora-user-images/image-20200130143300467.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开发中使用到;

7、bean的自动装配

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

在Spring中有三种装配的方式:

  1. 在xml中显示的配置
  2. 在java中显示配置
  3. 隐式的自动装配bean【重要】

7.1、测试

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

7.2、ByName自动装配

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

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

7.3、ByType自动装配

 <bean  class="com.yuan.pojo.Cat"/>
    <bean  class="com.yuan.pojo.Dog"/>

<!--    byType:会自动在容器上下文中查找,和自己对象属性类型相同的bean!-->
    <bean id="people" class="com.yuan.pojo.People" autowire="byType">
        <property name="name" value="你帅哥哥"/>

    </bean>

小结:

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

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

jdk1.5支持的注解,Spring2.5就支持注解了!

The introduction of annotation-based configuration raised the question of whether this approach is “better” than XML.

要使用注解须知:

  1. 导入约束

    context约束:

     xmlns:context="http://www.springframework.org/schema/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
            https://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            https://www.springframework.org/schema/context/spring-context.xsd">
    
        <context:annotation-config/>
    
    </beans>
    

@Autowired

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

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

科普:

@Nullable  字段标记了这个注解,说明这个字段可以为null;

测试代码:

public class People {
    //如果显示定义了Autowired的required属性,说明这个属性可以为null,否则不能为空!
    @Autowired(required = false)
    private Cat cat;
    @Autowired
    private Dog dog;
    private String name;

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

public class People {

    @Autowired
    @Qualifier(value = "cat111")
    private Cat cat;
    @Autowired
    private Dog dog;

    private String name;

@Resource注解

public class People {
    @Resource(name = "cat2")
    private Cat cat;
    @Resource
    private Dog dog;

    private String name;

小结:

@Resource和@Autowired的区别:

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

8、使用注解开发

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eD8eZDzd-1580535659601)(/Users/mac/Library/Application Support/typora-user-images/image-20200130172855364.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"
       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/context
        https://www.springframework.org/schema/context/spring-context.xsd
         http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/context/spring-aop.xsd">


    <!--    开启注解的支持-->
    <context:annotation-config/>

    </bean>


  1. bean 【@Component组件】

    //等价于   <bean id="user" class="com.yuan.pojo.User"/>
    //@Component组件
    @Component
    public class User {
        public String name= "你帅哥哥";
    }
    
  2. 属性如何注入 【 @Value(“xxx”)】

public class User {
//    等价于  <bean id="user" class="com.yuan.pojo.User">
//        <property name="name" value="你帅哥哥"/>
//    </bean>
    @Value("你帅哥哥")
    public String name;
}
  1. 衍生的注解

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

  • dao 【@Repository】

  • service 【@Service】

  • controller 【@Controller】

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

  1. 自动装配
  • @Autowired : 自动装配 通过先类型,后名字
    • 如果@Autowired不能唯一自动装配上属性,则需要通过@Qualifier(value = “xxx”)配合使用
  • @Nullable 字段标记了这个注解,说明这个字段可以为null;
  • @Resource : 自动装配 通过先名字,后类型
  • @Component : 组件,放在类上,说明这个类被Spring管理了,就是bean!
  1. 作用域
@Scope("xxx")     singleton、prototype.....
  1. 小结

xml与注解

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

xml与注解最佳实践:

  • xml用来管理bean

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

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

     <!--    开启注解的支持-->
        <context:annotation-config/>
    <!--    指定要扫描的包,这个包下的注解就会生效-->
        <context:component-scan base-package="com.yuan.pojo"/>
    

注解说明:

  • @Autowired : 自动装配 通过先类型,后名字
    • 如果@Autowired不能唯一自动装配上属性,则需要通过@Qualifier(value = “xxx”)配合使用
  • @Nullable 字段标记了这个注解,说明这个字段可以为null;
  • @Resource : 自动装配 通过先名字,后类型
  • @Component : 组件,放在类上,说明这个类被Spring管理了,就是bean!
  • @Value(“xxx”) 属性的注入
  • @Component 有几个衍生注解
    • dao 【@Repository】
    • service 【@Service】
    • controller 【@Controller】
  • @Scope(“xxx”) : 作用域: singleton、prototype…

9、使用java的方式配置Spring

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

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

实体类:

package com.yuan.pojo;

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

@Component
public class User {
    private String name;

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

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

配置文件:

package com.yuan.config;


import com.yuan.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration  //这个也会被Spring容器托管,注册到容器中,因为他本来就是一个@Component 
                 //  @Configuration代表这是一个配置类,就和我们之前看到的beans.xml是一样的

public class Myconfig {
    
    //注册一个bean ,就相当于我们之前写的一个bean标签  
    // 这个方法的名字,就相当于bean标签中的id属性
    //这个方法的返回值,就相当于bean标签中的class属性
    @Bean
    public User getUser(){
        return new User();   //就是返回要注入到bean的对象
    }
}

测试类:

public class MyTest {

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

这种纯java的配置方式,在SpringBoot中随处可见!

10、代理模式

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

代理模式分类:

  • 静态代理
  • 动态代理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PdCn8ovL-1580535659601)(/Users/mac/Library/Application Support/typora-user-images/image-20200130205014816.png)]

10.1、静态代理

角色分析:

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

代码步骤:

  1. 接口

    //租房
    public interface Rent {
        public void rent();
    }
    
    
  2. 真实角色

    //房东
    public class Host implements Rent {
        public void rent() {
            System.out.println("房东要出租房子!");
        }
    }
    
  3. 代理角色

    public class Proxy implements Rent {
        private Host host;
    
        public Proxy() {
        }
    
        public Proxy(Host host) {
            this.host = host;
        }
    
        public void rent() {
        seeHouse();
        host.rent();
        hetong();
        fare();
        }
    
        //看房
        public void seeHouse(){
            System.out.println("中介带你看房!");
        }
        //签合同
        public void hetong(){
            System.out.println("签租赁合同!");
        }
        //收中介费
        public void fare(){
            System.out.println("收中介费!");
        }
    }
    
  4. 客户端访问代理角色

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

代理模式的好处:

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

缺点:

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

10.2、加深理解

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FHWR49iN-1580535659602)(/Users/mac/Library/Application Support/typora-user-images/image-20200130214447830.png)]

  1. 接口

    package com.yuan.demo02;
    
    public interface UserService {
        public void add();
        public void delete();
        public void update();
        public void query();
    
    
    }
    
    
  2. 真实角色

    //真实对象那个
    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("查询了一个用户!");
        }
    }
    
    
  3. 代理角色

    public class UserServiceProxy implements UserService {
    
        private UserServiceImpl userService;
    
        public void setUserService(UserServiceImpl userService) {
            this.userService = userService;
        }
    
        public void add() {
            log("add");
        userService.add();
        }
    
        public void delete() {
            log("delete");
        userService.delete();
        }
    
        public void update() {
            log("update");
        userService.update();
        }
    
        public void query() {
            log("query");
        userService.query();
        }
        //日志方法
        public void log(String msg){
            System.out.println("使用了"+msg+"方法!");
        }
    }
    
  4. 客户端访问代理角色

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

10.3、动态代理【重点】

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

需要了解两个类:Proxy :代理; InvocationHandler:调动处理程序

动态代理的好处:

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v98PZ7Rn-1580535659602)(/Users/mac/Library/Application Support/typora-user-images/image-20200131131359326.png)]

Rent

//租房
public interface Rent {
    public void rent();
}

ProxyInvocationHandler

//我们会用这个类,自动生成代理类
public class ProxyInvocationHandler implements InvocationHandler {

    //被代理的接口
    private Rent rent;

    public void setRent(Rent rent) {
        this.rent = rent;
    }

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




    //处理代理实例,并返回结果
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        //动态代理的本质,就是使用反射机制实现!
        seeHouse();
        fare();
        Object result = method.invoke(rent, args);
        return result;
    }
    public void seeHouse(){
        System.out.println("中介带你看房子!");
    }
    public void fare(){
        System.out.println("收中介费!");
    }
}

Host

//房东
public class Host implements Rent {
    public void rent() {
        System.out.println("房东要出租房子!");
    }
}

Client

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

        //代理角色 : 现在没有
        ProxyInvocationHandler pih = new ProxyInvocationHandler();
        //通过调用程序处理角色来处理我们要调用的接口对象
        pih.setRent(host);

        Rent proxy = (Rent) pih.getProxy();//这里的proxy就是动态生成的,我们并没有写

        proxy.rent();
    }
}

11、AOP

11.1、什么是AOP

在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-btQaVDNq-1580535659602)(/Users/mac/Downloads/IMG_8DAFFC7FF154-1.jpeg)]

11.2、AOP在Spring中的作用

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

  • 横切关注点:跨越应用程序多个模块的方法或功能。即是:与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志,安全,缓存,事务等等…
  • 切面(ASPECT):横切关注点被模块化的特殊对象。即:它是一个类;
  • 通知(Advice): 切面必须要完成的工作。即:它是类中的一个方法;
  • 目标(Target):被通知的对象;
  • 代理(Proxy):向目标对象应用通知之后创建的对象;
  • 切入点(PointCut):切面通知执行的“地点”的定义;
  • 连接点(JointPoint):与切入点匹配的执行点;
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9fwSCI9w-1580535659603)(/Users/mac/Downloads/IMG_39DD92868246-1.jpeg)]

SpringAOP中,通过Advice定义横切逻辑,Spring中支持5种类型的Advice:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LWlmRqGA-1580535659603)(/Users/mac/Downloads/IMG_328A39F832A2-1.jpeg)]

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

11.3、使用Spring实现AOP

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

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

如:

接口:

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

实体类:

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 select() {
        System.out.println("查询了一个用户!");
    }
}

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

要切入的类:

Log:

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

    }
}

AfterLog:

public class AfterLog implements AfterReturningAdvice {

    //Object o:  返回值
    public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
        System.out.println("执行了"+method.getName()+"返回结果为"+o);
    }
}

applicationContext.xml中配置:记得导入AOP约束

<!--        方式一:使用原生的Spring API接口-->
<!--    配置aop:需要导入aop的约束-->
    <aop:config>
<!--        切入点 :aop:pointcut  表达式:expression : execution(要执行的位置! * * * * *)-->
        <aop:pointcut id="pointcut" expression="execution(* com.yuan.service.UserServiceImpl.*(..))"/>

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

测试:

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

        userService.add();

    }
}

  • 方式二:自定义来实现AOP【主要是切面定义】

要切入的类:

DiyPointCut:

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

applicationContext.xml中配置:记得导入AOP约束

<!--     方式二:自定义类-->
            <bean id="diy" class="com.yuan.diy.DiyPointCut"/>

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

测试:

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

        userService.add();

    }
}

  • 方式三:使用注解实现

要切入的类:

AnnotationPointCut:

//方式三:使用注解方式实现AOP
@Aspect   //标注这个类是一个切面
public class AnnotationPointCut {
    @Before("execution(* com.yuan.service.UserServiceImpl.*(..))")
    public void before(){
        System.out.println("方法执行前!!!");
    }
    @After("execution(* com.yuan.service.UserServiceImpl.*(..))")
    public void after(){
        System.out.println("方法执行后!!!");
    }
    @Around("execution(* com.yuan.service.UserServiceImpl.*(..))")
    public void around(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("环绕执行前!!!");

        Object proceed = jp.proceed();

        System.out.println("环绕执行后!!!");

    }
}

applicationContext.xml中配置:记得开启注解支持

<!--        方式三-->
        <bean id="annotationPointCut" class="com.yuan.diy.AnnotationPointCut"/>
<!--    开启注解支持    -->
    <aop:aspectj-autoproxy/>

测试:

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

        userService.add();

    }
}

结果:注意输出顺序

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0aWFNVDu-1580535659604)(/Users/mac/Library/Application Support/typora-user-images/image-20200131162903318.png)]

12、整合Mybatis

步骤:

  1. 导入相关的jar包

    • junit
    • mybatis
    • mysql数据库
    • spring相关的
    • aop织入
    • mybatis-spring 【新】
     <dependency>
                <groupId>org.junit.jupiter</groupId>
                <artifactId>junit-jupiter-api</artifactId>
                <version>5.6.0</version>
                <scope>test</scope>
            </dependency>
    
    
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>8.0.18</version>
            </dependency>
    
    
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis</artifactId>
                <version>3.5.3</version>
            </dependency>
    
    
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-webmvc</artifactId>
                <version>5.2.3.RELEASE</version>
            </dependency>
    
    <!--        Spring操作数据库还需要一个spring-jdbc包-->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-jdbc</artifactId>
                <version>5.2.3.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.3</version>
            </dependency>
    
    
  2. 编写配置文件

  3. 测试

12.1、回忆Mybatis

  1. 编写实体类
  2. 编写核心配置文件
  3. 编写接口
  4. 编写Mapper.xml
  5. 测试

12.2、Mybatis-spring

  1. 编写数据源
  2. sqlSessionFactory
  3. sqlSessionTemplate
  4. 需要给接口加实现类
  5. 将自己写的实现类,注入到Spring中
  6. 测试使用

第一步:导入相关架包(使用maven构建项目)

<dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.6.0</version>
            <scope>test</scope>
        </dependency>


        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.18</version>
        </dependency>


        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.3</version>
        </dependency>


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

<!--        Spring操作数据库还需要一个spring-jdbc包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.2.3.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.3</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.10</version>
            <scope>provided</scope>
        </dependency>

第二步:配置mybatis的配置文件mybatis-config.xml(在maven项目的resource下,该文件和spring整合其实可以不用创建的,数据源在spring中配置就行,mapper映射文件也可以在spring配置文件中配置Spring:整合Mybatis

实现mybatis的配置文件

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

   //一下代码都可以在spring中配置
    <typeAliases>
        <package name="com.kuang.pojo"/>//别名如果只是到某一个文件,默认别名为类名的开头字母小写User-user
    </typeAliases>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf8"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <package name="com.kuang.dao"/>使用该方法,映射xxxMapper.xml文件必须和接口在同一个目录下,并且名字必须一样
    或者使用<class>标签具体到某一个类,
    </mappers>
</configuration>
复制代码

接口对用=应的映射文件

<?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.kuang.dao.UserMapper">

    <select id="selectUser" resultType="User">
      select * from user
    </select>

</mapper>

测试:

@Test
public void selectUser() throws IOException {

    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    SqlSession sqlSession = sqlSessionFactory.openSession();

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);

    List<User> userList = mapper.selectUser();
    for (User user: userList){
        System.out.println(user);
    }

    sqlSession.close();
}

整合

版本要求:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pT735qfV-1580535659604)(/Users/mac/Library/Application Support/typora-user-images/image-20200131223709712.png)]

如果使用 Maven 作为构建工具,仅需要在 pom.xml 中加入以下代码即可:

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>2.0.2</version>
</dependency>

要和 Spring 一起使用 MyBatis,需要在 Spring 应用上下文中定义至少两样东西:一个 SqlSessionFactory 和至少一个数据映射器类。

在 MyBatis-Spring 中,可使用 SqlSessionFactoryBean来创建 SqlSessionFactory。 要配置这个工厂 bean,只需要把下面代码放在 Spring 的 XML 配置文件中:

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="dataSource" ref="dataSource" />
</bean>

注意:SqlSessionFactory 需要一个 DataSource(数据源)。 这可以是任意的 DataSource,只需要和配置其它 Spring 数据库连接一样配置它就可以了。

在基础的 MyBatis 用法中,是通过 SqlSessionFactoryBuilder 来创建 SqlSessionFactory 的。 而在 MyBatis-Spring 中,则使用 SqlSessionFactoryBean 来创建。

在 MyBatis 中,你可以使用 SqlSessionFactory 来创建 SqlSession。一旦你获得一个 session 之后,你可以使用它来执行映射了的语句,提交或回滚连接,最后,当不再需要它的时候,你可以关闭 session。

SqlSessionFactory 有一个唯一的必要属性:用于 JDBC 的 DataSource。这可以是任意的 DataSource 对象,它的配置方法和其它 Spring 数据库连接是一样的。

一个常用的属性是 configLocation,它用来指定 MyBatis 的 XML 配置文件路径。它在需要修改 MyBatis 的基础配置非常有用。通常,基础配置指的是 元素。

需要注意的是,这个配置文件并不需要是一个完整的 MyBatis 配置。确切地说,任何环境配置(),数据源()和 MyBatis 的事务管理器(``)都会被忽略SqlSessionFactoryBean 会创建它自有的 MyBatis 环境配置(Environment),并按要求设置自定义环境的值。

SqlSessionTemplate 是 MyBatis-Spring 的核心。作为 SqlSession 的一个实现,这意味着可以使用它无缝代替你代码中已经在使用的 SqlSession

模板可以参与到 Spring 的事务管理中,并且由于其是线程安全的,可以供多个映射器类使用,你应该总是SqlSessionTemplate 来替换 MyBatis 默认的 DefaultSqlSession 实现。在同一应用程序中的不同类之间混杂使用可能会引起数据一致性的问题。

可以使用 SqlSessionFactory 作为构造方法的参数来创建 SqlSessionTemplate 对象。

<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
  <constructor-arg index="0" ref="sqlSessionFactory" />
</bean>

现在,这个 bean 就可以直接注入到你的 DAO bean 中了。你需要在你的 bean 中添加一个 SqlSession 属性,就像下面这样:

public class UserDaoImpl implements UserDao {

  private SqlSession sqlSession;

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

  public User getUser(String userId) {
    return sqlSession.getMapper...;
  }
}

按下面这样,注入 SqlSessionTemplate

<bean id="userDao" class="org.mybatis.spring.sample.dao.UserDaoImpl">
  <property name="sqlSession" ref="sqlSession" />
</bean>

整合实现一

  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">
  1. 配置数据源替换mybaits的数据源
<!--配置数据源:数据源有非常多,可以使用第三方的,也可使使用Spring的-->
<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/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf8"/>
    <property name="username" value="root"/>
    <property name="password" value="123456"/>
</bean>
  1. 配置SqlSessionFactory,关联MyBatis
<!--配置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/kuang/dao/*.xml"/>
</bean>
  1. 注册sqlSessionTemplate,关联sqlSessionFactory;
<!--注册sqlSessionTemplate , 关联sqlSessionFactory-->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
    <!--利用构造器注入-->
    <constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
  1. 增加Dao接口的实现类;私有化sqlSessionTemplate
public class UserDaoImpl implements UserMapper {

    //sqlSession不用我们自己创建了,Spring来管理
    private SqlSessionTemplate sqlSession;

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

    public List<User> selectUser() {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        return mapper.selectUser();
    }
    
}
  1. 注册bean实现
<bean id="userDao" class="com.kuang.dao.UserDaoImpl">
    <property name="sqlSession" ref="sqlSession"/>
</bean>
  1. 测试
@Test
    public void test2(){
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        UserMapper mapper = (UserMapper) context.getBean("userDao");
        List<User> user = mapper.selectUser();
        System.out.println(user);
    }

结果成功输出!现在我们的Mybatis配置文件的状态!发现都可以被Spring整合!

<?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>
    <typeAliases>
        <package name="com.kuang.pojo"/>
    </typeAliases>
</configuration>

整合实现二

mybatis-spring1.2.3版以上的才有这个 .

官方文档截图 :

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lKzWmqDZ-1580535659604)(/Users/mac/Library/Application Support/typora-user-images/image-20200131224326439.png)]

测试:

  1. 将我们上面写的UserDaoImpl修改一下
public class UserDaoImpl extends SqlSessionDaoSupport implements UserMapper {
    public List<User> selectUser() {
        UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
        return mapper.selectUser();
    }
}
  1. 修改bean的配置
<bean id="userDao" class="com.kuang.dao.UserDaoImpl">
    <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
  1. 测试
@Test
public void test2(){
    ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
    UserMapper mapper = (UserMapper) context.getBean("userDao");
    List<User> user = mapper.selectUser();
    System.out.println(user);
}

总结 : 整合到spring中以后可以完全不要mybatis的配置文件,除了这些方式可以实现整合之外,我们还可以使用注解来实现,这个等我们后面学习SpringBoot的时候还会测试整合!

13、声明式事务

1. 回顾事务

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

事务的ACID原则:

  • A:原子性

  • C:一致性

  • I:隔离性

    • 多个业务可能操作一个资源,防止数据损坏
  • D:持久性

    • 事务一旦提交,无论系统发生什么问题,结果都不会被影响,被持久化的写到存储器中!

2、声明式事务

1、理解Spring Framework的声明式事务实现
    告诉你简单的为你的类注释上@Transactional的注释, 为配置加上@EnableTransactionManagement 是不够充分的, 除非你理解了他们全部是如何工作的.

    从概念上来讲, 在事务型代理上调用一个方法看起来像这样…

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AI7OYa7f-1580535659605)(/Users/mac/Library/Application Support/typora-user-images/image-20200131232634455.png)]

2、声明式事务实现的例子
//我们想使之支持事务的服务层接口
 
package x.y.service;
 
public interface FooService {
 
    Foo getFoo(String fooName);
 
    Foo getFoo(String fooName, String barName);
 
    void insertFoo(Foo foo);
 
    void updateFoo(Foo foo);
 
}


//上面接口的一个实现
 
package x.y.service;
 
public class DefaultFooService implements FooService {
 
    public Foo getFoo(String fooName) {
        throw new UnsupportedOperationException();
    }
 
    public Foo getFoo(String fooName, String barName) {
        throw new UnsupportedOperationException();
    }
 
    public void insertFoo(Foo foo) {
        throw new UnsupportedOperationException();
    }
 
    public void updateFoo(Foo foo) {
        throw new UnsupportedOperationException();
    }
 
}


来让我们假设, FooService接口的前两个方法getFoo(String)和getFoo(String, String)必须在只读 类型语义的事务上下文中执行, 并且其他的方法insertFoo(Foo)和updateFoo(Foo)必须在可读可写类型 语义的事务上下文环境中执行. 下面的配置的详细解释将在接下来的段落中进行.

<!-- 来自文件 'context.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"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">
 
    <!-- 这是我们希望使之支持事务的服务层对象 -->
    <bean id="fooService" class="x.y.service.DefaultFooService"/>
 
    <!-- 事务化配置(请看下面的<aop:advisor/>) -->
    <tx:advice id="txAdvice" transaction-manager="txManager">
        <!-- 事务语义... -->
        <tx:attributes>
            <!-- 所有用'get'开头的方法都是只读的 -->
            <tx:method name="get*" read-only="true"/>
            <!-- 其他的方法使用默认的事务配置(看下面) -->
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>
 
    <!-- 使得上面的事务配置对FooService接口的所有操作有效 -->
    <aop:config>
        <aop:pointcut id="fooServiceOperation" expression="execution(* x.y.service.FooService.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/>
    </aop:config>
 
    <!-- 不要忘了DataSource -->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
        <property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/>
        <property name="username" value="scott"/>
        <property name="password" value="tiger"/>
    </bean>
 
    <!-- 同样的, 也不要忘了PlatformTransactionManager -->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
 
    <!-- 关于其他的<bean/>的定义 -->
 
</beans>


检查前面的配置. 你想让一个服务层对象, 就是fooService这个bean, 支持事务. 应用的关于事务语义的封装 是定义在tx:advice/的. 那tx:advice/的定义的意思就是"… 所有以’get’开头的方法都运行 在只读的事务语义中, 并且其他的所有方法都运行在默认的事务语义中". tx:advice/标签的 transaction-manager属性就是用来设置用来驱动事务的beanPlatformTransactionManager的名称, 在这里就是txManager这个bean.

aop:config/的定义确保了由txAdvice这个bean定义的事务配置在程序合适的切入点运行. 首先需要定义 一个切入点来匹配FooService( fooServiceOperation)这个接口定义的任何操作. 然后用一个顾问(advisor) 将切入点与txAdvice关联起来. 这样做的结果就是使用txAdvice定义的配置会在fooServiceOperation 上面工作起来.

让整个服务层都是事务型的是一个通常的需求. 要这么做的最好方式就是简单的修改切入点表达式, 使之能够匹配 服务层所有的操作. 就像下面这样:

<aop:config>
    <aop:pointcut id="fooServiceMethods" expression="execution(* x.y.service.*.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceMethods"/>
</aop:config>


3、回滚一个声明式事务

让Spring Framework事务的基础构件知道事务需要进行回滚的推荐做法是在正在执行的代码的当前上下文中抛出 Exception. Spring Framework事务的基础构件将会在调用栈中出现未处理的Exception的时候将其全部 捕获, 然后会进行测定是否需要将事务进行回滚.

在默认配置中, Spring Framework的事务基础构件只会在运行期、未检查的异常时才会标记事务回滚;也就 是说, 当抛出的异常是RuntimeException或者其子类的实例时(Error也同样)默认都是标记为回滚. 事务的方法中抛出检查的异常时在默认情况下不会标记为回滚.

你可以自己配置哪些Exception的类型是需要标记为回滚的, 这包括了检查的异常. 下面的XML代码片段展示了 你需要怎样配置标记检查的、程序自定义的Exception为需要回滚异常.

<tx:advice id="txAdvice" transaction-manager="txManager">
    <tx:attributes>
    <tx:method name="get*" read-only="true" rollback-for="NoProductInStockException"/>
    <tx:method name="*"/>
    </tx:attributes>
</tx:advice>


如果你需要在某一些异常抛出的时候不进行回滚, 你一样可以配置不回滚规则. 下面的例子就告诉 Spring Framework的事务基础构件提交所进行的事务即使出现了未处理的InstrumentNotFoundException.

<tx:advice id="txAdvice">
    <tx:attributes>
    <tx:method name="updateStock" no-rollback-for="InstrumentNotFoundException"/>
    <tx:method name="*"/>
    </tx:attributes>
</tx:advice>


当Spring Framework的事务基础构件捕获了一条被多个参考配置确定是否需要回滚的异常时, 那一条最精确 的将生效.所以在下面的配置中, 除了InstrumentNotFoundException的所有异常都将被标记为回滚.

<tx:advice id="txAdvice">
    <tx:attributes>
    <tx:method name="*" rollback-for="Throwable" no-rollback-for="InstrumentNotFoundException"/>
    </tx:attributes>
</tx:advice>


4、为不同的bean配置不同的事务

考虑这样一个场景, 你在服务层有大量的对象, 并且你想对它们每一个都应用完全不同的事务配置. 你完成 这个事情是使用了不同的pointcut和advice-ref属性的值来定义了不同的aop:advisor/元素.

作为一个出发点, 首先假设你服务层所有的类都定义在根包x.y.service中. 为了让在这个包(或者他的子包) 中定义的所有以Service结尾的类的所有实例都具有默认事务配置, 你将会进行如下配置:

<?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"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">
 
    <aop:config>
 
        <aop:pointcut id="serviceOperation"
                expression="execution(* x.y.service..*Service.*(..))"/>
 
        <aop:advisor pointcut-ref="serviceOperation" advice-ref="txAdvice"/>
 
    </aop:config>
 
    <!-- 这两个bean将支持事务... -->
    <bean id="fooService" class="x.y.service.DefaultFooService"/>
    <bean id="barService" class="x.y.service.extras.SimpleBarService"/>
 
    <!-- ... 而这两个bean将不支持 -->
    <bean id="anotherService" class="org.xyz.SomeService"/> <!-- (not in the right package) -->
    <bean id="barManager" class="x.y.service.SimpleBarManager"/> <!-- (doesn't end in 'Service') -->
 
    <tx:advice id="txAdvice">
        <tx:attributes>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>
 
    <!-- 省略其他如PlatformTransactionManager的事务基础构件的配置... -->
 
</beans>


下面的例子展示了怎样配置两个不一样的bean使用两个完全不同的事务配置.

<?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"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">
 
    <aop:config>
 
        <aop:pointcut id="defaultServiceOperation"
                expression="execution(* x.y.service.*Service.*(..))"/>
 
        <aop:pointcut id="noTxServiceOperation"
                expression="execution(* x.y.service.ddl.DefaultDdlManager.*(..))"/>
 
        <aop:advisor pointcut-ref="defaultServiceOperation" advice-ref="defaultTxAdvice"/>
 
        <aop:advisor pointcut-ref="noTxServiceOperation" advice-ref="noTxAdvice"/>
 
    </aop:config>
 
    <!-- 这个bean是事务型的(查看'defaultServiceOperation'切入点) -->
    <bean id="fooService" class="x.y.service.DefaultFooService"/>
 
    <!-- 这个bean也是事务型的, 但是它拥有完全不一样的事务配置 -->
    <bean id="anotherFooService" class="x.y.service.ddl.DefaultDdlManager"/>
 
    <tx:advice id="defaultTxAdvice">
        <tx:attributes>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>
 
    <tx:advice id="noTxAdvice">
        <tx:attributes>
            <tx:method name="*" propagation="NEVER"/>
        </tx:attributes>
    </tx:advice>
 
    <!-- 省略其他如PlatformTransactionManager的事务基础构件的配置... -->
 
</beans>


5、tx:advice/ 设置

tx:advice/标签指定的各种设置. tx:advice/标签默认的设置是:

传播行为设置是REQUIRED.
隔离等级是DEFAULT.
事务是可读可写.
事务超时是使用系统底层组件的默认值, 在不支持超时的时候没有超时.
任何的RuntimeException均触发回滚, 并且检查的Exception不会.
你可以修改默认的设置; tx:advice/和tx:attributes/标签所需要的tx:method/标签的属性都 整理在下面了:

Table 11.1. tx:method/设置
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Af95qQSF-1580535659605)(/Users/mac/Library/Application Support/typora-user-images/image-20200131233605924.png)]

6、@Transactional 的使用

作为使用基于XML配置声明式事务配置方法的补充, 你可以使用一种基于注解的方法. 直接在Java代码中声明事务 语义声明使得声明更加靠近生效的代码. 这不存在过度危险的耦合, 因为不管怎么说开发代码的就意味着这样 被事务化地使用.

@Transactional注解所提供的易用性将使用后面文本中的例子进行说明. 参考下面声明的类:

// 我们想要支持事务的服务类
@Transactional
public class DefaultFooService implements FooService {
 
    Foo getFoo(String fooName);
 
    Foo getFoo(String fooName, String barName);
 
    void insertFoo(Foo foo);
 
    void updateFoo(Foo foo);
}


当在Spring IoC容器中定义上面的POJO时, 这个bean的实例就仅仅需要在XML配置添加行就可以添加 事务了:

<!-- 来自文件context.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"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">
 
    <!-- 这就是我们想要使之支持事务的对象 -->
    <bean id="fooService" class="x.y.service.DefaultFooService"/>
 
    <!-- 使使用注解配置的事务行为生效 -->
    <tx:annotation-driven transaction-manager="txManager"/><!-- 仍然需要一个PlatformTransactionManager -->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- (这个需要的对象是在其他地方定义的) -->
        <property name="dataSource" ref="dataSource"/>
    </bean>
 
    <!-- 其他<bean/>的定义 -->
 
</beans>


方法可见性和@Transactional

    当使用代理时, 你应该只给public可见性的方法添加@Transactional注解. 如果你给protected, private或者包访问的方法添加了@Transactional注解, 不会产生错误, 但是添加了注解的方法并没有真正的 配置了事务. 

    你可以把@Transactional注解添加在接口定义、接口中的方法定义、类定义、或者一个类中public方法的 前面. 然而, 仅仅有@Transactional注解的存在还不足以使事务的行为生效. @Transactional注解仅仅是 一个用来让某些运行期@Transactional-发现的基础构件来发现的元数据, 并且这些发现还会使用这个元数据 来配置bean的事务行为. 在前面的例子中, 元素`<tx:annotation-driven/>`开启了事务行为.

    Spring建议你只为具体类(以及具体类的方法)添加@Transactional注解, 而不要给接口添加注解. 你当然 也可以给接口(或者接口中的方法)添加注解, 但是这只会在你期望的使用的代理时基于接口的时候工作. Java中的 注解不会从接口继承的事实意味着如果你是使用的基于类的代理( proxy-target-class="true")或者基于 编织的方面( mode="aspectj"), 那么关于事务的设置不会被代理或者编织的基础设施注册, 并且对象就不会 被事务型的代理包装, 而这当然是不好的.

Table 11.2. 基于注解的事务设置
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ew6eHBnJ-1580535659605)(/Users/mac/Library/Application Support/typora-user-images/image-20200131233718874.png)]

在决定方法的事务设置时, 最精确的配置优先. 在下面的例子中, DefaultFooService是一个在类级别使用只读 事务设置的类, 但是在同一个类的updateFoo(Foo)方法上的@Transactional注解优先于在类级别的事务设置.

@Transactional(readOnly = true)
public class DefaultFooService implements FooService {
 
    public Foo getFoo(String fooName) {
        // do something
    }
 
    // 该方法的设置更优先
    @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
    public void updateFoo(Foo foo) {
        // do something
    }
}


@Transactional 设置

@Transactional注解是一个用来定义一个接口、类或者方法必须具备事务化语义的元数据; 例如, “在调用 该方法时挂起所有已经存在的事务,开始一个新的只读事务”. 下面是@Transactional注解的默认设置:

传播设置是PROPAGATION_REQUIRED.
隔离等级是ISOLATION_DEFAULT.
事务是可读可写的.
事务超时是使用底层事务系统的默认值, 或者在不支持时没有.
任何的RuntimeException触发回滚, 并且所有的检查的Exception不触发.
这些默认设置都是可以修改的; @Transactional注解的各种属性都整理在下面的表格中了:

Table 11.3. @Transactional
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QLv2pcVE-1580535659606)(/Users/mac/Library/Application Support/typora-user-images/image-20200131233808768.png)]

7、事务传播性

请记住本部分并不是介绍事务的传播性本身; 尽管它比Spring 中的事务传播性更加详细.

在Spring的受管事务中, 存在物理逻辑事务的差别, 以及还有传播性的设置是怎样在这种差别上生效的.

需要 Required

Figure 11.2.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oP1gW0P4-1580535659606)(/Users/mac/Library/Application Support/typora-user-images/image-20200131233835615.png)]

PROPAGATION_REQUIRED

当传播属性设置为PROPAGATION_REQUIRED时, 将会为设置应用到的每一个方法创建一个逻辑上的事务 作用域. 这每一个单独的逻辑事务作用域可以单独的确定回滚状态, 在逻辑上独立于事务范围的外部事务范围. 当然, 考虑到标准的PROPAGATION_REQUIRED的行为, 所有的这些作用域都将会映射到相同的物理事务上. 因此, 在内部事务作用域中作的事务回滚标记确实会影响到外部事物实际上提交的可能性(这和你所期待的一样).

然而, 在内部事务作用域中标记了回滚, 外部事物决定它自己不回滚的情况下, 这样的回滚(由内部事务作用域 静默触发)就不是期待的了. 一个对应的UnexpectedRollbackException将会在在那里抛出. 这是一个异常 行为, 所以事务的调用者将不可能会在事务其实没有提交的时候被误导为假设提交了. 所以对于内部事务作用域 (在外部调用者没有发觉时)静默的标记了回滚的情况下, 外部调用者调用了提交. 那么外部调用者需要收到一个 UnexpectedRollbackException来清楚的知道需要用一个回滚来取而代之(提交).需要新的 RequiresNew

Figure 11.3.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CO9j4ACG-1580535659606)(/Users/mac/Library/Application Support/typora-user-images/image-20200131233909123.png)]

PROPAGATION_REQUIRES_NEW

相比较于PROPAGATION_REQUIRED, PROPAGATION_REQUIRES_NEW对每一个受影响的事务作用域都使用完全 独立的事务. 这样, 物理上的事务就不同了并且可以独立的提交或者回滚, 外部事物不会影响到内部事务的回滚 状态.

嵌套 Nested

PROPAGATION_NESTED对多个可以回滚到的保存点使用了一个单独的底层事务. 这种局部化的回滚允许一个 内部事务触发一个针对它的作用域的回滚, 尽管一些操作已经回滚了, 但外部事物还是可以继续物理上的事务. 这个设置通常都和JDBC的保存点对应, 所以只会在JDBC的资源的事务上有作用. 请查看Spring的DataSourceTransactionManager.

8、通知事务操作

假设你想要同时执行事务型的和一些基本的分析通知. 你怎样在tx:annotation-driven/的上下文 中体现?

当你执行updateFoo(Foo)方法时, 你期望看到下面的动作:

配置了分析通知的切面启动.
事务通知执行.
被添加了通知的对象的方法执行.
提交事务.
分析切面报告整个事务方法执行的准确时间.
这里是上面讨论的简单分析切面的代码

package x.y;
 
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.util.StopWatch;
import org.springframework.core.Ordered;
 
public class SimpleProfiler implements Ordered {
 
    private int order;
 
    // 允许我们对通知排序
    public int getOrder() {
        return this.order;
    }
 
    public void setOrder(int order) {
        this.order = order;
    }
 
    // 这个方法是关于通知
    public Object profile(ProceedingJoinPoint call) throws Throwable {
        Object returnValue;
        StopWatch clock = new StopWatch(getClass().getName());
        try {
            clock.start(call.toShortString());
            returnValue = call.proceed();
        } finally {
            clock.stop();
            System.out.println(clock.prettyPrint());
        }
        return returnValue;
    }
}


<?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"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">
 
    <bean id="fooService" class="x.y.service.DefaultFooService"/>
 
    <!-- 这是切面 -->
    <bean id="profiler" class="x.y.SimpleProfiler">
        <!-- 在事务通知之前执行(更低的排序) -->
        <property name="order" __value="1"__/>
    </bean>
 
    <tx:annotation-driven transaction-manager="txManager" __order="200"__/>
 
    <aop:config>
            <!-- 这个通知将会在事务通知执行时执行 -->
            <aop:aspect id="profilingAspect" ref="profiler">
                <aop:pointcut id="serviceMethodWithReturnValue"
                        expression="execution(!void x.y..*Service.*(..))"/>
                <aop:around method="profile" pointcut-ref="serviceMethodWithReturnValue"/>
            </aop:aspect>
    </aop:config>
 
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
        <property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/>
        <property name="username" value="scott"/>
        <property name="password" value="tiger"/>
    </bean>
 
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
 
</beans>


**【代理模式、整合Mybatis、事务】**需强化!!!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值