spring5-学习总结

整体内容

spring框架的概述
spring中基于xml的IOC配置和基于注释的IOC
spring中基于xml和注解的AOP配置
spring中的jdbcTemplate和Spring事务控制
在这里插入图片描述

官网

http://spring.io/
当前使用spring5.0
官方文档:https://docs.spring.io/

概述

spring是分层的javase/ee应用full-stack轻量级开源框架,以IOC反转控制和AOP面向切面编程为核心,提供了展现spring mvc 和持久层spring jdbc一级业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的java ee企业应用开源框架。

spring是轻量级的控制反转和面向切面的容器

优势

方便解耦,简化开发
AOP编程支持 面向切面编程
声名式事务的支持
方便程序测试
方便集成各种优秀框架
降低javaee api使用
java源码是经典学习范例

两大核心

IOC反转控制 和 AOP面向切面编程
IOC和AOP都是为了降低程序间的依赖关系

spring体系结构

在这里插入图片描述
CoreContainer:核心容器,是spring的一个核心部分,就是IOC核心

耦合与解耦

耦合是程序间的依赖关系,包括类之间的依赖,方法间的依赖
解耦就是降低程序间的依赖关系,如编译期间不依赖,运行时才依赖
解耦的思路:
(1)读取配置文件来获取要创建对象的全限定类名
(2)使用反射来创建对象,避免使用new创建对象

简单示例 JDBC连接数据库项目

一、创建项目
选择MAVEN 和自己的SDK路径
在这里插入图片描述
输入项目目录、项目名
在这里插入图片描述
在这里插入图片描述
二、选择自动加载依赖
在这里插入图片描述
遇到问题:
1、IDEA Unable to import maven project: See logs for details
解决方法:打开日志Help>>show Log in Explorer,发现里面有N多报错,百度发现是IDE和maven版本不匹配,Maven更改本地默认仓库时遇到的问题,当前IDE版本2017.2.5,当前maven 3.6.3,降级maven版本
maven官网:http://maven.apache.org/
在这里插入图片描述
maven下载网站:https://archive.apache.org/dist/maven/maven-3/
在这里插入图片描述
修改maven配置
在这里插入图片描述
三、添加依赖
在这里插入图片描述
四、src/main/java下新建class,com.yxx.Jdbc
在这里插入图片描述
在这里插入图片描述

使用工厂模式解耦

1、通过读取配置文件来获取要创建对象的全限定类名
2、使用反射方式来创建对象,而避免使用new
项目结构
在这里插入图片描述
BeanFactory.java

package com.yxx.factory;

import java.io.InputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Properties;
import java.util.Map;

/**
 * 创建Bean对象的工厂
 * Bean:可重用的组件
 * JavaBean:非实体类,用java编写的可重用组件
 */
public class BeanFactory {
//    定义一个Properties对象
    private static Properties props;
//    定义一个map用于存放我们要创建的对象
    private static Map<String,Object> beans;
//    使用静态代码块为Properties对象进行赋值,单例模式
    static{
        try {
            props = new Properties();
//            获取properties文件的流对象,调用构造函数创建对象
            InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
            props.load(in);
            beans = new HashMap<String, Object>();
//              取出配置文件中所有的key
            Enumeration keys = props.keys();
//            遍历枚举
            while (keys.hasMoreElements()){
                String key = keys.nextElement().toString();
                String beanPath = props.getProperty(key);
                Object value = Class.forName(beanPath).newInstance();
                beans.put(key,value);
            }
        } catch (Exception e) {
            throw new ExceptionInInitializerError("加载配置文件失败");
        }
    }

    public static Object getBean(String beanName){
        return beans.get(beanName);
    }
}

IAccountDao.java

package com.yxx.dao;
/**
 * 账户的持有层接口
 */
public interface IAccountDao {
    /**
     * 模拟保存账户
     */
    void saveAccount();
}

AccountDaoImpl.java

package com.yxx.dao.impl;
import com.yxx.dao.IAccountDao;
/**
 * 账号实体实现类
 */
public class AccountDaoImpl implements IAccountDao{
    public void saveAccount(){
        System.out.println("保存账号信息");
    }
}

IAccountService.java

package com.yxx.service;

/**
 * 账户业务层接口
 */
public interface IAccountService {
    /**
     * 模拟保存账户
     */
    void saveAccount();
}

AccountServiceImpl.java

package com.yxx.service.impl;

import com.yxx.dao.IAccountDao;
import com.yxx.dao.impl.AccountDaoImpl;
import com.yxx.factory.BeanFactory;
import com.yxx.service.IAccountService;

/**
 * 账号的业务层实现
 */
public class AccountServiceImpl implements IAccountService {
    private IAccountDao accountDao = (IAccountDao)BeanFactory.getBean("accountDao");
    public void saveAccount() {
        accountDao.saveAccount();
    }
}

bean.properties

accountDao = com.yxx.dao.impl.AccountDaoImpl
accountService = com.yxx.service.impl.AccountServiceImpl

Client.java

package com.yxx.service;

import com.yxx.factory.BeanFactory;

public class Client {
    public static void main(String[] args) {
        IAccountService as = (AccountServiceImpl) BeanFactory.getBean("accountService");
        as.saveAccount();
    }
}

IOC 控制反转

把new对象的权利交给框架,它包括依赖注入和依赖查找
在这里插入图片描述
IOC的作用就是削减计算机程序的耦合
可以通过工程模式实现,spring框架中已经集成

实现原理:代理模式,工厂模式,解析xml
配置bean和注入bean
配置bean:@Controller @Service @Repository
注入bean:@Autowired @Resource

使用spring中的IOC

spring ioc是一个map ,map的key是id string类型,map的value是object类型
一、添加spring依赖
在这里插入图片描述

  <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.7.RELEASE</version>
        </dependency>
    </dependencies>

核心容器依赖关系图
二、添加配置文件
在这里插入图片描述

<?xml version="1.0" encoding="UTF-8" ?>



三、把对象的创建交给spring管理
在这里插入图片描述

<bean id="accountService" class="com.yxx.service.impl.AccountServiceImpl"></bean>
<bean id="accountDemo" class="com.yxx.dao.impl.AccountDaoImpl"></bean>

四、获取spring的核心容器ioc,并根据id获取对象
在这里插入图片描述

    /**
     *获取spring的核心容器ioc,并根据id获取对象
     */
    public static void main(String[] args) {
//        获取核心容器对象
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//        根据id获取bean对象
        IAccountService as = (IAccountService)ac.getBean("accountService");
        as.saveAccount();
    }

根据id获取bean对象,两种方法

方法一:
IAccountService as = (IAccountService)ac.getBean("accountService"); 
方法二:
IAccountDao ad = ac.getBean("accountDao",IAccountDao.class);

ApplicationContext常用的三个实现类

ClassPathXmlApplicationContext; 加载classpath下的配置文件,配置文件必须在classpath下
FileSystemXmlApplicationContext; 加载磁盘任意路径下的配置文件,必须有文件操作权限
AnnotationConfigApplicationContext; 通过读取注解创建容器

ApplicationContext和BeanFactory的区别

ApplicationContext 单例对象适用
在构建核心容器时立即加载,只要读取完配置文件,就立即创建对象。
BeanFactory 多例对象适用
在构建核心容器时延迟加载,在使用id获取对象时才创建对象

spring创建bean对象的三种方式

方法一:
使用默认构造函数创建对象,类中如果没有默认构造函数则创建失败
在bean.xml在添加

<bean id="accountDemo" class="com.yxx.dao.impl.AccountDaoImpl"></bean>

方法二:
使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器)
在bean.xml在添加

<bean id="instanceFactory" class="com.yxx.factory.InstanceFactory"></bean>
<bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>

方法三:
使用工厂中的静态方法创建对象(使用某个类中的静态方法创建对象,并存入容器)

<bean id="staticFactory" class="com.yxx.factory.StaticFactory" factory-method="getAccountService"></bean>

bean的作用范围

bean标签使用scope属性指定bean的使用范围,
取值:
singleton 单例(默认),
prototype 多例,
request 作用于web应用的请求范围,
session 作用于web应用的会话范围,
global-session 作用于集群环境的会话范围(全局会话范围),当不是集群环境时就是session范围

bean的生命周期

一、
单例bean对象:
和ApplicationContext容器的生命周期一样,容器创建就创建bean对象,容器销毁bean对象也销毁
多例bean对象:
当使用对象时spring为我们创建对象;当对象长时间不用,且没有别的对象使用时,由java垃圾回收机制回收。

spring依赖注入

一、spring框架维护依赖关系,依赖关系的维护就是依赖注入
二、能注入的数据有三种类型
1、基本类型和string
2、其他bean类型
3、复杂类型和集合类型
三、依赖注入的方式有三种
1、使用构造函数提供
2、使用set方法提供
3、使用注解提供

使用构造函数注入数据

在这里插入图片描述

//要求必须有默认的构造函数,否则注入失败
<bean id="accountDemo" class="com.yxx.dao.impl.AccountDaoImpl"></bean>

构造函数有参数时,使用bean的constructor-arg属性注入数据,一般不用

<!--把对象的创建交给spring管理-->
    <!--constructor-arg属性
     type 用于指定要注入的数据的数据类型,该数据类型是构造函数中某个或者某些参数的类型
     index 用于注定要注入的数据给构造函数中指定索引位置的参数赋值,索引位置从0开始
     name 用于指定给构造函数中给指定名称的参数赋值 常用
     value 给参数赋值
     ref 引用关联的bean对象
    -->
    <bean id="accountService" class="com.yxx.service.impl.AccountServiceImpl">
        <constructor-arg name="name" value="test"></constructor-arg>
        <constructor-arg name="age" value="18"></constructor-arg>
        <constructor-arg name="birthday" ref="now"></constructor-arg>
    </bean>
    <bean id="now" class="java.util.Date"></bean>

使用set方法注入数据

<!--
set方法注入
涉及的标签:property
name 指定注入时所调用的set方法名称
value 给参数赋值
ref 引用关联的bean对象
-->
<bean id="accountService2" class="com.yxx.service.impl.AccountServiceImpl2">
    <property name="name" value="test"></property>
    <property name="age" value="12"></property>
    <property name="birthday" ref="now"></property>
</bean>
<bean id="now" class="java.util.Date"></bean>

在这里插入图片描述
在这里插入图片描述
优势:创建对象时没有明确的限制,可以直接使用默认构造函数
弊端:
1、如果某个成员必须有值,则获取对象时有可能set方法么有执行
2、必须有默认构造函数

复杂类型数据注入

<bean id="accountService3" class="com.yxx.service.impl.AccountServiceImpl3">
        <property name="myStrs">
            <array>
                <value>AAA</value>
                <value>bbb</value>
            </array>
        </property>
        <property name="myList">
        <list>
            <value>12</value>
            <value>15</value>
        </list>
        </property>
        <property name="myMap">
            <map>
                <entry key="a" value="a"></entry>
                <entry key="b" value="b"></entry>
            </map>
        </property>
        <property name="myProps">
            <props>
<prop key="C">
    ccc
</prop>
            </props>
        </property>
    </bean>

在这里插入图片描述

IOC注解

实现和bean.xml配置文件同样的功能

IOC注解分类

一、创建对象
二、注入数据
三、改变作用范围
四、和生命周期相关

@Component 创建对象

一、同作用相同
使用@Component把当前类注入到Spring框架中,
bean的id默认是当前类名首字母小写,也可以使用属性value指定bean的id

二、实现
1、bean.xml中导入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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">
</beans>

在这里插入图片描述
2、添加context:component-scan,使扫描配置目录下有@Component注解的类
<context:component-scan base-package=“com.yxx”></context:component-scan>
3、在实现类上添加@Component注解
在这里插入图片描述

package com.yxx.service;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Client {
    /**
     *获取spring的核心容器ioc,并根据id获取对象
     */
    public static void main(String[] args) {
//        获取核心容器对象
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        IAccountService as = (IAccountService)ac.getBean("accountServiceImpl");
        as.saveAccount();
    }
}

由Component衍生出的三个注解

Controller 用于表现层
Service 用于业务层
Repository 用于持久层
这三个注解和Component的功能一模一样,他们是spring框架为我们提供的明确的上层使用注解

@Autowired注入其他bean类型的数据

一、作用:自动按照类型注入,只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就可以注入成功;
注入位置:可以是变量上,也可以是方法上
使用注解注入时,set方法不是必须的
在这里插入图片描述
二、如果容器中有两个bean对象和注入的变量类型匹配,先按照类型找打匹配的所有bean对象,然后按照变量的名称查找名称一样的bean
在这里插入图片描述
三、只能注入其他bean类型的数据,不支持基本类型和String类型的注入
四、集合类型的注入只能通过xml实现

@Qualifier 注入其他bean类型的数据

一、按照类型注入的基础上再按照名称注入,它在给类成员注入时不能单独使用,但是在给定方法参数注入时可以使用
属性value可以指定bean的id name
二、在给类成员注入时必须配置@Autowired使用
在这里插入图片描述
三、只能注入其他bean类型的数据,不支持基本类型和String类型的注入

@Resource注入其他bean类型的数据

一、不需要像@Autowired和@Qualifier一样麻烦,可直接指定bean的 id name

@Service
public class AccountServiceImpl implements IAccountService {
   @Resource(name = "accountDaoImpl")
    private IAccountDao accountDao;
    public void saveAccount() {
        accountDao.saveAccount();
    }
}

二、只能注入其他bean类型的数据,不支持基本类型和String类型的注入

@value 注入string和基本类型的数据

value用于指定数据的值,可以使用spring的SpEL,也就是spring的el表达式

@Scope 改变bean对象的作用范围

value属性可以指定范围的取值,常用取值singletion单例 prototype多例,,默认是单例的

@PreDestory 指定bean对象销毁方法

@PostConstruct 指定bean对象初始化方法

@Configuration指定当前类是配置类

一、用于替换bean.xml中的配置

package com.bjhl.config;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SpringConfiguration {
}

二、当配置类作为AnnotationConfigApplicationContext对象创建的参数时,@Configuration可以不写

@ComponentScan(“com.yxx”) 指定spring在创建容器时要扫描的包

一、作用同:<context:component-scan base-package=“com.yxx”></context:component-scan>
二、属性valus和basePackages作用一样

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("com.bjhl")
public class SpringConfiguration {
}

@Bean

一、作用:用于把当前方法的返回值作为bean对象存入spring的IOC容器中
二、属性
name:用于指定bean的id,默认是当前方法的名称
三、当使用注解配置方法,如果方法有参数,spring会到容器中查找有没可用的bean对象,查找的方式和Autowired相同

@Configuration
@ComponentScan("com.bjhl")
public class SpringConfiguration {
    @Bean(name="runner")
    public QueryRunner createQueryRunner(DataSource dataSource){
        return new QueryRunner(dataSource);
    }
    @Bean(name="dataSource")
    public DataSource createDataSource(){
    	ComboPooledDataSource ds =null;
        try {      
        	ds = new ComboPooledDataSource();
            ds.setDriverClass("com.mysql.jdbc.Driver");
            ds.setJdbcUrl("jdbc:mysql://localhost:3306/mysql");
            ds.setUser("root");
            ds.setPassword("root");
        }catch(Exception e){
            throw new RuntimeException(e);
        }
        return ds;
    }
}

功能等同于:
在这里插入图片描述

AnnotationConfigApplicationContext

要完全取消bean.xml,获取ioc容器中的bean对象时使用AnnotationConfigApplicationContext,参数是被注解的class类

package com.yxx.service.impl;

import com.yxx.config.SpringConfiguration;
import com.yxx.domain.Account;
import com.yxx.service.IAccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import java.util.List;

public class clen {
    public static void main(String[] args) {
        ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
        IAccountService as = (IAccountService) ac.getBean("accountServiceImpl");
        List<Account> la = as.findAllAccount();
        System.out.println(la);
    }
}

@Scope 设置bean为单例或者多例模式

    @Bean(name="runner")
    @Scope("prototype")
    public QueryRunner createQueryRunner(DataSource dataSource){
        return new QueryRunner(dataSource);
    }

@Import

一、作用:导入其它的配置类
二、属性:value:用于指定其它配置类的字节码
三、有import的配置类为父配置类,被导入的是子配置类
四、配置类之间的关系
1、并列关系
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class, JdbcConfig.class);
2、父子关系
使用@import

@PropertySource

作用:用于指定properties文件的路径
属性 value:指定文件的名称和路径
关键字classpath 表示类路径下
使用
1、在resources下添加properties文件,文件中填写配置信息
在这里插入图片描述
2、获取properties值

package com.yxx.config;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.commons.dbutils.QueryRunner;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.*;

import javax.sql.DataSource;

/**
 * 配置类,实现与bean.xml相同的功能
 */
@Configuration
@ComponentScan("com.yxx")
@PropertySource("classpath:config.properties")
public class SpringConfiguration {
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.user}")
    private String user;
    @Value("${jdbc.password}")
    private String password;

    @Bean(name="runner")
    @Scope("prototype")
    public QueryRunner createQueryRunner(DataSource dataSource){
        return new QueryRunner(dataSource);
    }

    @Bean(name="dataSource")
    public DataSource CreateDataSource(){
        ComboPooledDataSource ds = new ComboPooledDataSource();
        try {
            ds.setDriverClass(driver);
            ds.setJdbcUrl(url);
            ds.setUser(user);
            ds.setPassword(password);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return ds;
    }
}

Spring整合junit配置

一、导入spring整合junit的jar包
在这里插入图片描述
使用spring5.*版本时,要求junit的jar必须是4.12及以上
在这里插入图片描述
二、使用Junit提供的注解将Junit的main方法替换成Spring提供的@Runwith,会自动读取配置文件或者注解

三、告知spring的运行器,spring的IOC创建是基于xml还是基于注解的,并说明位置
@ContextConfiguration
属性:location:指定xml文件的位置,加上classpath关键字,表示在类路径下
classes:指定注解类所在的位置
四、自动注入@Autowired
在这里插入图片描述

事务

当有多个操作需要同时执行,可以使用事务控制多个操作同时成功或者同时失败时。可使用ThreadLocal对象把Connection和当前线程绑定,从而使当前线程中只有一个能控制事务的对象。
ConnectionUtils.java

package com.yxx.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.sql.Connection;

/**
 * 连接的工具类,用于从数据源中获取一个连接,并且实现和线程的绑定
 */
@Component
public class ConnectionUtils {
    private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
    @Autowired
    private DataSource dataSource;
//    获取当前线程上的连接

    public Connection getConnection(){
        try {
//      先从ThreadLocal上获取
        Connection conn = tl.get();
//        判断当前线程上是否有连接
        if (conn==null){
//            从数据源中获取一个连接,并存入到ThreadLocal
                conn = dataSource.getConnection();
                tl.set(conn);
        }
        return conn;
        }catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }
//    把连接和线程解绑
    public void removeConnection(){
        tl.remove();
    }
}

TransactionManager.java

package com.yxx.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.sql.SQLException;

/**
 * 和事务管理相关的工具类,包括开启事务、提交事务、回滚事务、释放连接
 */
@Component
public class TransactionManager {
    @Autowired
    private ConnectionUtils connectionUtils;
//  开启事务
    public void beginTransaction(){
        try {
            connectionUtils.getConnection().setAutoCommit(false);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    //  提交事务
    public void commitTransaction(){
        try {
            connectionUtils.getConnection().commit();
        } catch (SQLException e) {
            e.printStackTrace();
        }

    }
    //  回滚事务
    public void rollbackTransaction(){
        try {
            connectionUtils.getConnection().rollback();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    //  释放连接
    public void releaseTransaction(){
        try {
            connectionUtils.getConnection().close();
            connectionUtils.removeConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

业务层,多个操作同时成功或者同时失败

public void transfar(){
    try {
        tm.beginTransaction();
        List<Account> la = findAllAccount();
        List<Account> lb = findAllAccount();
        tm.commitTransaction();
        System.out.println("完整事务");
    }catch (Exception e){
        tm.rollbackTransaction();
   }finally{
        tm.releaseTransaction();
    }
}

动态代理

特点:随用随创建,随用随加载
作用:不修改源码的基础上对方法增强
分类:基于接口的动态代理;基于子类的动态代理

基于接口的动态代理

一、涉及类:Proxy
二、创建代理对象:使用Proxy类中的newProxyInstance方法
三、创建代理对象的要求:被代理类最少实现一个接口,如果没有则不能使用
四、方法参数:
ClassLoader,类加载器,它是用于加载代理对象字节码的,和被代理对象使用相同的类加载器,
Class[] ,字节码数组,用于让代理对象和被代理对象有相同的方法
InvocationHandler 用于提供增强的代码,一般是写该接口的一个实现类,通常情况下都是匿名内部类,但不是必须的。
五、实例
1、IProducer.java

package com.yxx.service;
public interface IProducer {
    void saleProduct(float money);
    void afterProduct(float money);
}

2、ProducerImpl.java

package com.yxx.service.impl;

import com.yxx.service.IProducer;
import org.springframework.stereotype.Service;

@Service
public class ProducerImpl implements IProducer{
    public void saleProduct(float money) {
        System.out.println("销售产品,并拿到钱"+money);
    }

    public void afterProduct(float money) {
        System.out.println("提供售后服务,并拿到钱"+money);
    }
}

3、基于接口的动态代理的实现

package com.yxx.service.impl;

import com.yxx.config.SpringConfiguration;
import com.yxx.proxy.IProducer;
import com.yxx.service.IAccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

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

public class client {
    public static void main(String[] args) {
        ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
        final IProducer producer=(IProducer)ac.getBean("producerImpl");
        IProducer proxyProducer=(IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),
                producer.getClass().getInterfaces(),
                new InvocationHandler() {
                    /**
                     * 执行被代理对象的任何接口方法都会经过该方法
                     * @param proxy  代理对象的引用
                     * @param method 当前执行的方法
                     * @param args 当前执行方法所需的参数
                     * @return 和被代理对象方法有相同的返回值
                     * @throws Throwable
                     */
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //提供增强的代码
                        Object returnValue=null;
//                        获取方法执行的参数
                        Float money = (Float)args[0];
                        if("saleProduct".equals(method.getName())){
                            returnValue=method.invoke(producer, money*0.8f);
                        }
                        return returnValue;
                    }
                });
        proxyProducer.saleProduct(1000);
    }
}

基于子类的动态代理

代理普通的java类
涉及类:Enhancer
提供者:第三方cglib插件
创建代理对象:使用Enhancer类中的create方法
创建代理对象的要求:被代理类不能是final类型
方法参数:
Class: 指定被代理对象的字节码
CallBack: 用于提供增强的代码,一般是写该接口的一个实现类,通常情况下都是匿名内部类,但不是必须的。我们一般写的是该CallBack的子接口实现类MethodInterceptor
一、添加依赖

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>
<dependency>
        <groupId>asm</groupId>
        <artifactId>asm</artifactId>
        <version>3.3.1</version>
</dependency>  

二、Produ.java

package com.yxx.service.impl;

import org.springframework.stereotype.Service;

@Service
public class Produ {
    public void saleProduct(float money) {
        System.out.println("销售产品,并拿到钱"+money);
    }

    public void afterProduct(float money) {
        System.out.println("提供售后服务,并拿到钱"+money);
    }
}

三、基于子类动态代理实现

    package com.yxx.cglib;

import com.yxx.config.SpringConfiguration;
import com.yxx.service.impl.Produ;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import java.lang.reflect.Method;

public class client {
    public static void main(String[] args) {
        ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
        final Produ produ =(Produ)ac.getBean("produ");
        Produ cglibProdu = (Produ)Enhancer.create(produ.getClass(), new MethodInterceptor() {
            /**
             * 执行被代理对象的任何方法都会经过该方法
             * @param proxy
             * @param args
             * @param method
             * @param methodProxy  当前执行方法的代理对象
             * @return
             * @throws Throwable
             */
            public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//                提供增强的代码
                        Object returnValue=null;
//                        获取方法执行的参数
                        Float money = (Float)args[0];
                        if("saleProduct".equals(method.getName())){
                            returnValue=method.invoke(produ, money*0.8f);
                        }
                        return returnValue;
            }
        });
        cglibProdu.saleProduct(1000);
    }
}

使用动态代理实现事务控制

package com.yxx.factory;

import com.yxx.service.IAccountService;
import com.yxx.utils.TransactionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * 用于创建service的代理对象工程
 */

public class BeanFactory {
    @Autowired
    private  IAccountService accountService;
    @Autowired
    TransactionManager tm;
    /**
     * 获取Service的代理对象
     * @return
     */
    @Bean(name = "accountServiceProxy")
    public IAccountService getAccountService() {
        accountService=(IAccountService)Proxy.newProxyInstance(accountService.getClass().getClassLoader(),
                accountService.getClass().getInterfaces(),
                new InvocationHandler() {
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        Object rtValue=null;
                        try {
                            tm.beginTransaction();
                            System.out.println("执行事务");
                            rtValue = method.invoke(accountService, args);
                            tm.commitTransaction();
                            return rtValue;
                        }catch (Exception e){
                            tm.rollbackTransaction();
                        }finally{
                            tm.releaseTransaction();
                        }
                        return null;
                    }
                });
        return accountService;
    }
}

AOP概念

一、面向切面编程,通过预编译方式和运行动态代理实现程序功能的统一维护的一种技术
二、作用:在程序运行期间,不修改源码对已有方法进行增强
减少重复代码
提高开发效率
维护方便
三、Joinpoint连接点 :所谓连接点是指那些被拦截的点,在spring中这些点指的是方法,因为spring只支持方法类型的连接点
四、Pointout切入点:是指我们要对哪些Joinpoint进行拦截的定义;就是要被增强的方法
五、Advice 通知/增强,是指拦截到Joinpoint后要哪些事情
通知类型包括前置通知、后置通知、异常通知、最终通知和环绕通知
六、Introduction引介:是一种特殊的通知,在不修改类代码的情况下,Introduction可以在运行期动态为类添加一些方法或者Field
七、Target目标对象,指代理的目标对象,被代理对象
八、Weaving 植入 ,指把增强应用到目标对象来创建新的代理对象的过程
pring采用动态代理植入

九、Proxy 代理,一个类被AOP植入增强后,就产生一个结果代理类
十、Aspect 切面,是切入点和通知的集合

AOP的实现方式

使用动态代理技术
spring中的AOP是通过配置的方式实现动态代理

使用spring AOP要明确的内容

一、我们需要做的:
1、编写核心代码
2、公共代码提取出来,制作成通知;
3、在配置文件中声明切入点和通知之间的关系,即切面
二、运行阶段
spring框架监控切入点方法的执行,一旦监控到切入点方法被执行,使用代理机制,动态创建目标对象的代理对象,根据通知类型,在代理对象的对应位置,将通知对应的功能植入,完成完整的代码逻辑运行。

spring基于XML的AOP配置步骤

bean.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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--一、配置spring的IOC,把service对象配置进来,-->
    <bean id="accountServiceImpl" class="com.itheima.service.impl.AccountServiceImpl"></bean>
<!--二、配置sping中基于xml的AOP-->
    <!--1、通知bean交给spring管理-->
    <!--2、使用aop:config标签表明开始AOP配置-->
    <!--3、使用aop:aspect标签表明配置切面
    id属性:给切面提供一个唯一标识
    ref属性:指定通知类bean的id
    -->
    <!--4、在aop:aspect标签的内部使用对应标签中配置通知的类型
    apo:before  表示配置前置通知,method属性用于指定Logger类中的哪个方法是前置通知,pointcut属性用于指定切入点表达式,
    该表达式的含义指的是对业务层中哪些方法加强

    切入点表达式写法:
        关键字:execution(表达式)
        表达式:访问修饰符  返回值  包名.包名...类名.方法名(参数列表)
        标准的表达式写法:public void com.itheima.service.impl.AccountServiceImpl.saveAccount()
    -->
    <!--配置Logger-->
    <bean id="logger" class="com.itheima.utils.Logger"></bean>
    <!--配置AOP-->
    <aop:config>
        <!--配置切面-->
        <aop:aspect id="logAdvice" ref="logger">
            <!--配置通知的类型,并且建立通知方法和切入点的关系-->
            <aop:before method="printLog" pointcut="execution(public void com.itheima.service.impl.AccountServiceImpl.saveAccount())"></aop:before>
        </aop:aspect>
    </aop:config>
</beans>

切入点表达式写法

一、表达式:访问修饰符 返回值 包名.包名…类名.方法名(参数列表)如:public void com.itheima.service.impl.AccountServiceImpl.saveAccount()
1、 访问修饰符可以省略;
2、返回值用表示任意返回值;
3、包名可以使用
…表示当前包及其子包;
4、类名和方法名都可以使用*实现通配;
5、参数列表:
(1)可以直接写数据类型,基本类型直接写名称int;
(2)引用类型写包名.类名 java.lang.String;
(3)可以用通配符表示任意类型,但是必须有参数;
(4)可以使用…表示有或者无参数

二、全通配写法:* .*(…)

三、实际开发中切入点表达式的通用写法:切换到业务层实现类下的所有方法 * com.itheima.service.impl..(…)

四、配置切入点表达式
1、aop:pointcut标签写在aop:aspect内部,则只能由当前切面使用

<aop:config>
        <!--配置切面-->
        <aop:aspect id="logAdvice" ref="logger">
         <aop:pointcut id="pit" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut>
        <aop:before method="beforePrintLog" pointcut-ref="pit"></aop:before>
        </aop:aspect>
    </aop:config>

2、aop:pointcut标签写在aop:aspect外部,则可由多个切面使用
aop:pointcut必须写在aop:aspect前面

<aop:config>
	 <aop:pointcut id="pit" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut>
    <!--配置切面-->
    <aop:aspect id="logAdvice" ref="logger">
    	<aop:before method="beforePrintLog" pointcut-ref="pit"></aop:before>
    </aop:aspect>
</aop:config>

通用的通知类型

bean.xml

   <!--配置AOP-->
    <aop:config>
        <!--配置切面-->
        <aop:aspect id="logAdvice" ref="logger">
            <!--配置通知的类型,并且建立通知方法和切入点的关系-->
            <!--一、前置通知 -->
            <aop:before method="beforePrintLog" pointcut="execution(* com.itheima.service.impl.*.*(..))"></aop:before>
            <!-- 二、后置通知-->
            <aop:after-returning method="afterPrintLog" pointcut="execution(* com.itheima.service.impl.*.*(..))"></aop:after-returning>
            <!-- 三、异常通知-->
            <aop:after-throwing method="throwPrintLog" pointcut="execution(* com.itheima.service.impl.*.*(..))"></aop:after-throwing>
            <!-- 四、最终通知-->
            <aop:after method="laserPrintLog" pointcut="execution(* com.itheima.service.impl.*.*(..))"></aop:after>
        </aop:aspect>
    </aop:config>

五、环绕通知
pring框架为我们提供了一个接口ProceedingJoinPoint,该接口有一个方法proceed(),此方法就相当于明确调用切入点方法,该接口可以作为环绕通知的方法参数。在程序执行时,spring框架为我们提供该接口的实现类供我们使用。

/**  * 环绕通知  */ 
public Object aroundPringLog(ProceedingJoinPoint pjp){
    Object rtValue=null;
    try {
        Object[] args = pjp.getArgs();//得到方法执行所需的参数
        System.out.println("前置通知");
        rtValue = pjp.proceed(args);//明确调用切入点方法
        System.out.println("后置通知");
    }catch (Throwable t){
        System.out.println("异常通知");
    }finally {
        System.out.println("最终通知");
    }
    return rtValue; }

Spring基于注解的AOP

bean.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:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">
<!--配置sping创建容器要扫描的包-->
<context:component-scan base-package="com.itheima"></context:component-scan>
    <!--配置Spring开启注解AOP的支持,在配置文件类中标注@EnableAspectJAutoProxy-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

在这里插入图片描述
Logger.java

package com.itheima.utils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * 用于记录日志的公共类,里面记录了公共的代码
 */
@Component
@Aspect //表示当前类是一个切面类
public class Logger {
//    配置切面表达式
    @Pointcut("execution(* com.itheima.service.impl.*.*(..))")
    private void ptl(){}
    /**
     * 前置通知
     */
    @Before("ptl()")
    public void beforePrintLog(){
        System.out.println("前置通知");
    }
    /**
     * 后置通知
     */
    @AfterReturning("ptl()")
    public void afterPrintLog(){
        System.out.println("后置通知");
    }
    /**
     * 异常通知
     */
    @AfterThrowing("ptl()")
    public void throwPrintLog(){
        System.out.println("前置通知");
    }
    /**
     * 最后通知
     */
    @After("ptl()")
    public void laserPrintLog(){
        System.out.println("最后通知");
    }
}

AccountServiceImpl.java

package com.itheima.service.impl;
import com.itheima.service.IAccountService;
import org.springframework.stereotype.Service;

/**
 * 账号的业务层实现类
 */
@Service
public class AccountServiceImpl implements IAccountService{
    public void saveAccount() {
        System.out.println("保存");
    }

    public void updateAccount(int i) {
        System.out.println("更新");
    }

    public int deleteAccount() {
        System.out.println("删除");
        return 0;
    }
}

Spring中的JdbcTemplate

在这里插入图片描述
在这里插入图片描述

JdbcTemplate的简单使用

package com.itheima.jdbctemplate;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;

/**
 * Jdbc Template的基本用法
 */
public class JdbcTemplateDemo1 {
    public static void main(String[] args) {
//        准备数据源,spring的内置数据源
        DriverManagerDataSource ds = new DriverManagerDataSource();
        ds.setDriverClassName("com.mysql.jdbc.Driver");
        ds.setUrl("jdbc:mysql://localhost:3306/");
        ds.setUsername("root");
        ds.setPassword("root");
//        1、创建JdbcTemplate对象
        JdbcTemplate jt = new JdbcTemplate();
        jt.setDataSource(ds);
//        2、执行操作
        jt.execute("select * from mysql.user");
    }
}

JdbcTemplate在spring ioc中的使用

JdbcTemplateDemo2.java

package com.itheima.jdbctemplate;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;

public class JdbcTemplateDemo2 {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        JdbcTemplate jt = ac.getBean("jdbcTemplate", JdbcTemplate.class);
        jt.execute("select * from mysql.user");
    }
}

bean.xml

<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!--配置数据源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root"></property>
    </bean>

JdbcTemplate的CRUD操作

package com.itheima.jdbctemplate;

import com.itheima.domain.AccountRowMapper;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import com.itheima.domain.Account;
import java.util.List;

public class JdbcTemplateDemo2 {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        JdbcTemplate jt = ac.getBean("jdbcTemplate", JdbcTemplate.class);
        //  插入
        jt.update("insert into mysql.account(name,money) values(?,?)","eee",222);
        //  更新
        jt.update("update account set money=? where name=?",12,"eee");
        //  删除
        jt.update("delete from account where id=?",12);
        // 查询所有
        List<Account> accounts = jt.query("select * from account", new BeanPropertyRowMapper<Account>(Account.class));
        for(Account account:accounts){
            System.out.println(account);
        }
        // 查询一个
        List<Account> account = jt.query("select * from account", new BeanPropertyRowMapper<Account>(Account.class));
        System.out.println(account.isEmpty()?"没有内容":account.get(0));
        // 查询返回一行一列(使用聚合函数,但不加group by)
        Integer count = jt.queryForObject("select count(*) from account where money >?",Integer.class,1000);
        System.out.println(count);
    }
}

JdbcTemplate在DAO中的使用

在这里插入图片描述

public class JdbcTemplateDemo3 {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        IAccountDao ad= ac.getBean("accountDaoImpl", IAccountDao.class);
        ad.findAccountById(1);
    }
}

spring中事务控制的一组API

在这里插入图片描述
二、PlatformTransactionManager是spring的事务管理器接口,它里面提供了我们常用的操作事务的方法
1、获取事务状态信息
TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
2、提交事务
void commit(TransactionStatus var1) throws TransactionException;
3、回滚事务
void rollback(TransactionStatus var1) throws TransactionException;

三、PlatformTransactionManager的实现类常用的有
DataSourceTransactionManager 使用spring JDBC或iBatis进行持久化数据时使用
HibernateTransactionManager 使用Hibernate进行持久化数据时使用

四、TransactionDefinition
1、int getIsolationLevel(); 获取事务隔离级
在这里插入图片描述
2、 int getPropagationBehavior(); 获取事务传播行为
在这里插入图片描述
REQUIRED:增删改的传播行为
SUPPOPTS:查询的传播行为
3、int getTimeout(); 获取事务超时时间
默认是-1,没有超时时间限制
如果设置了超时时间,以秒为单位进行设置
4、boolean isReadOnly(); 获取事务是否只读
建议查询时设置为只读
5、@Nullable
String getName(); 获取事务对象名称

五、TransactionStatus 提供事务具体的运行状态
1、boolean isNewTransaction();获取事务是否是新的事务
2、boolean hasSavepoint();获取是否存在存储点
3、void setRollbackOnly();设置事务回滚
4、boolean isRollbackOnly();获取事务是否回滚
5、void flush();刷新事务
6、boolean isCompleted();获取是否事务完成

spring基于XML的声明式事务控制

bean.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:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       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
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd">

    <!--配置sping创建容器要扫描的包-->
    <context:component-scan base-package="com.itheima"></context:component-scan>
    <!--配置事务的管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!--配置数据源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root"></property>
    </bean>

    <!--配置事务的通知,id给事务通知起唯一的标志,transaction-manager给事务通知提供一个事务管理器引用-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <!--配置事务的属性
        isolation 指定事务的隔离级别,默认使用数据库的隔离级别
        propagation 指定事务的传播行为,默认值是REQUIRED,表示一定会有事务,增删改的选择;查询可选择SUPPORTS
        read-only="" 指定事务是否只读,默认是false,只有查询可设置为true
        rollback-for="" 指定一个异常,当产生该异常时事务回滚,产生其它异常时事务不回滚。默认表示任何异常都回滚。
        no-rollback-for="" 和rollback-for相反,但是默认表示任何异常都回滚。
        timeout="" 指定超时时间
        -->
        <tx:attributes>
            <tx:method name="transfer" propagation="REQUIRED"/>
            <tx:method name="find*" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>
    <!--配置AOP-->
    <aop:config>
        <!--配置AOP中的通用切入点表达式-->
        <aop:pointcut id="ptl" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut>
        <!--建立切入点表达式和事务通知的对应关系-->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="ptl"></aop:advisor>
    </aop:config>
</beans>

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:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       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
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd">

    <!--配置sping创建容器要扫描的包-->
    <context:component-scan base-package="com.itheima"></context:component-scan>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!--配置数据源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root"></property>
    </bean>

    <!--配置事务的管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!--开启spring对事务注解的支持-->
    <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
    <!--在需要事务支持的类或者方法上使用@Transactional-->
</beans>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值