Spring框架的基本使用

一、前言

Spring框架基础部分内容过于很长,分为几篇文章进行巩固学习记录。

1、Spring是什么

Spring框架是由于软件开发的复杂性而创建的。Spring使用的是基本的JavaBean来完成以前只可能由EJB完成的事情。然而,Spring的用途不仅仅限于服务器端的开发。从简单性、可测试性和松耦合性角度而言,绝大部分Java应用都可以从Spring中受益。
目 的:解决企业应用开发的复杂性
功 能:使用基本的JavaBean代替EJB
范 围:任何Java应用
Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。

2、Spring优势

1 方便解耦,简化开发
Spring通过容器,将对象的创建从代码中剥离出来,交给Spring控制,避免直接编码造成模块之间的耦合度高,用户也不必自己编码处理对象的单例和多例控制,主要关注接口功能即可,不用关注具体使用哪个实现类和实现细节问题
2 AOP切面编程
AOP切面编程是程序设计的一种概念,Spring对该概念实现的比较好,通过切面编程我们可以在不修改原有代码的情况下实现功能的增加,通常用于 事务控制,日志记录,性能检测,权限控制等等
3 声明式事务
事务的控制可以托管给Spring,我们通过注解或者配置文件声明事务的处理方式即可,不用我们自己去编码处理
4 整合JUNIT,方便测试
spring整合JUNIT单元测试,对于项目的功能都可以进行轻松快速的测试,便于我们调试程序
5方便整合各种优秀的框架
SSM> Spring+SpringMVC +MyBatis
SSH> Spring+Hibernate +Strust
各种其他框架
6 丰富的功能封装
spring对JAVAEE(JDBC ,JAVAMail,)都进行了一系列的封装,简化我们对于API的使用,提高程序的开发效率
7 规范的源码学习样本
spring的源码设计巧妙,结构清晰,大量使用了设计模式,是java代码规范编写的典范,也是高级程序员面试中经常会问到的源码

3、Spring的体系结构

在这里插入图片描述
1.Data Access/Integration(数据访问/集成)

  • 数据访问/集成层包括 JDBC、ORM、OXM、JMS 和 Transactions 模块,具体介绍如下。
    • JDBC 模块:提供了一个 JDBC 的抽象层,大幅度减少了在开发过程中对数据库操作的编码。
    • ORM 模块:对流行的对象关系映射 API,包括 JPA、JDO、Hibernate 和 iBatis 提供了的集成层。
    • OXM 模块:提供了一个支持对象/XML 映射的抽象层实现,如 JAXB、Castor、XMLBeans、JiBX 和 XStream。
    • JMS 模块:指 Java 消息服务,包含的功能为生产和消费的信息。
    • Transactions 事务模块:支持编程和声明式事务管理实现特殊接口类,并为所有的 POJO。

2.Web 模块

  • Spring 的 Web 层包括 Web、Servlet、Struts 和 Portlet 组件,具体介绍如下。
    • Web 模块:提供了基本的 Web 开发集成特性,例如多文件上传功能、使用的 Servlet 监听器的 IoC 容器初始化以及 Web 应用上下文。
    • Servlet模块:包括 Spring 模型—视图—控制器(MVC)实现 Web 应用程序。
    • Struts 模块:包含支持类内的 Spring 应用程序,集成了经典的 Struts Web 层。
    • Portlet 模块:提供了在 Portlet 环境中使用 MV C实现,类似 Web-Servlet 模块的功能。

3.Core Container(核心容器)

  • Spring 的核心容器是其他模块建立的基础,由 Beans 模块、Core 核心模块、Context 上下文模块和 Expression Language 表达式语言模块组成,具体介绍如下。
    • Beans 模块:提供了 BeanFactory,是工厂模式的经典实现,Spring 将管理对象称为 Bean。
    • Core 核心模块:提供了 Spring 框架的基本组成部分,包括 IoC 和 DI 功能。
    • Context 上下文模块:建立在核心和 Beans 模块的基础之上,它是访问定义和配置任何对象的媒介。ApplicationContext 接口是上下文模块的焦点。
    • Expression Language 模块:是运行时查询和操作对象图的强大的表达式语言。
  1. 其他模块
  • Spring的其他模块还有 AOP、Aspects、Instrumentation 以及 Test 模块,具体介绍如下。
    • AOP 模块:提供了面向切面编程实现,允许定义方法拦截器和切入点,将代码按照功能进行分离,以降低耦合性。
    • Aspects 模块:提供与 AspectJ 的集成,是一个功能强大且成熟的面向切面编程(AOP)框架。
    • Instrumentation 模块:提供了类工具的支持和类加载器的实现,可以在特定的应用服务器中使用。
    • Test 模块:支持 Spring 组件,使用 JUnit 或 TestNG 框架的测试。

二、Spring IOC基本使用

1、IOC概念

简单的说就是,创建对象的权利,或者是控制的位置,由JAVA代码转移到spring容器,由spring的容器控制对象的创建,就是控制反转,spring创建对象时,会读取配置文件中的信息,然后使用反射给我们创建好对象之后在容器中存储起来,当我们需要某个对象时,通过id获取对象即可,不需要我们自己去new.
一句话:创建对象交给容器
基本测试代码:
创建一个Maven项目,然后导入spring-beans的架包,在导入该架包后,四个依赖也会自动导入到项目中
spring-context 上下文,容器
spring-beans 创建对象
spring-core 核心jar
spring-expression 表达式jar

 <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-beans</artifactId>
    <version>5.3.5</version>
 </dependency>

依赖传递关系图如下
在这里插入图片描述
为了方便测试,我们导入Junit测试依赖

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

然后创建实现类

package com.xiaohui.dao;

public interface EmpDao {
    int addEmp();
}

package com.xiaohui.dao.impl;

import com.xiaohui.dao.EmpDao;

public class EmpDaoImpl implements EmpDao {
    @Override
    public int addEmp() {
        System.out.println("addEmp()");
        return 0;
    }
}

在resources目录下new一个Spring的xml文件出来,<bean id="empDao" class="com.xiaohui.dao.impl.EmpDaoImpl"></bean>复制到里面

public class Test01 {
    @Test
    public void testAddEmp(){
        //获取一个容器对象,并读取spring.xml文件,实例化配置文件中的配置的对象后,自动放入容器
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        //通过容器的ID来获取对象的对象
        EmpDao empDao = applicationContext.getBean("empDao",EmpDao.class);
        //调用方法测试
        empDao.addEmp();
    }
}

2、IOC容器的实现原理

  • 1、XML解析技术读取配置文件
<bean id="empDao" class="com.msb.dao.impl.EmpDaoImpl"></bean>

将上面的信息读取进入程序 对象的ID ,一个是对象的类的全路径名

  • 2、反射技术实例化对象,放到容器中
    获得类的字节码
    Class clazz =Class.forName(“com.msb.dao.impl.EmpDaoImpl”)
    通过字节码实例化对象
    Object obj = clazz.newInstance();
    将对象放到一个map集合中
    map.put(“empDao”,obj)
  • 3、工厂模式返回Bean对象 getBean方法
     public Object getBean(String name){
            Object obj =map.get(name);
            return obj;
     }   
  • IOC接口
    • BeanFactory 接口: IOC容器基本功能接口,是spring内部使用的接口,我们在处理业务时一般不直接使用该接口
    • ApplicationContext 接口: BeanFactory的子接口,提供更多更强大的功能,研发人员一般使用的接口
      在这里插入图片描述

三、XML方式实现DI

  • spring中的Bean的管理:
    • Bean(汉译咖啡豆). 又称JAVABean.其实就是JAVA程序程序中的一个个对象,所以Bean的管理其实就是spring对于JAVA程序中的对象的管理
  • 管理的内容是什么
    • 1、对象的创建 IOC
      • IOC 叫做控制反转,就是Spring给我们创建对象,然后我们直接用,不用自己NEW
      • IOC处理的是对象如何创建的问题
    • 2、属性的赋值 DI
      • DI Dependency Injection,即“依赖注入” 就是创建属性时给对象属性赋值
        对象功能的实现往往要依赖属性的值,那么给对象属性赋值就可以说成是依赖注入
      • 由于对象属性不仅仅是基本数据类型,还可能是其他类,或者引用类型
        那么依赖注入将会把更多的对象之间的关系整理到一起,可以行程一个庞大的依赖关系
        DI处理的是对象的属性赋值和互相依赖的关系

1、IOC创建对象

通过无参构造方法构造对象

<bean id="user1" class="com.xiaohui.bean.User"></bean>

bean 标签的常见属性

  • id 对象的id
  • class 类的全路径名
  • name 和id类似,一般不用
  • scope 控制对象单例多例和使用范围
    • singleton作用域(scope 默认值), Spring IOC容器中只会存在一个共享的bean实例
    • prototype作用域部署的bean,每一次获取都会产生一个新的bean实例,相当与一个new的操作
    • request表示该针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP request内有效
    • session作用域表示该针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP session内有效
    • global session作用域类似于标准的HTTP Session作用域,不过它仅仅在基于portlet的web应用中才有意义
  • lazy-init 懒加载 调用getBean的时候再去实例化对象

2、DI给对象属性赋值

a、通过set方法给对象属性赋值

 <!--property 就是在使用set方法实现依赖注入-->
    <bean id="user1" class="com.xiaohui.bean.User">
        <property name="userid" value="1"></property>
        <property name="username" value="张三"></property>
        <property name="password" value="abcdefg"></property>
    </bean>

b、通过有参构造给对象属性赋值

<!--
    constructor-arg 就是在使用构造方法实现依赖注入
    constructor-arg 的个数必须和某个构造方法的参数个数向对应
    name指的是参数名
    index指的是参数的索引
    value指的是参数值
    -->
    <bean id="user2" class="com.xiaohui.bean.User">
        <constructor-arg name="userid" value="20"></constructor-arg>
        <constructor-arg name="username" value="阿武"></constructor-arg>
        <constructor-arg name="password" value="123456789"></constructor-arg>
    </bean>
    <bean id="user3" class="com.xiaohui.bean.User">
        <constructor-arg index="0"  value="30"></constructor-arg>
        <constructor-arg index="1"  value="阿萨"></constructor-arg>
        <constructor-arg index="2"  value="987654321"></constructor-arg>
    </bean>

c、通过p名称空间和c名称空间给对象属性赋值

添加约束

<?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="user4" class="com.msb.bean.User" p:userid="4" p:username="小东" p:password="111111" ></bean>
    <!--c名称空间,就是对constructor-arg的简化-->
    <bean id="user5" class="com.msb.bean.User" c:userid="5" c:username="小西" c:password="222222" ></bean>

d、注入空值和特殊符号

 <bean id="user1" class="com.msb.bean.User">
        <!--null值-->
        <property name="userid">
            <null></null>
        </property>
        <!--特殊符号 转译字符 < &lt;  >&gt;  & &amp;     -->
        <property name="username" value="&amp;xiaoming&lt;&gt;"></property>
        <!-- 特殊符号  <![CDATA[内容]]>  -->
        <property name="password">
            <value><![CDATA[&<123456>]]></value>
        </property>
    </bean>

e、关于bean引用

创建两个实体类

package com.msb.bean;
import java.util.Date;

public class Mouse {
    private String name;
    private Date birthdate;
    @Override
    public String toString() {
        return "Mouse{" +
                "name='" + name + '\'' +
                ", birthdate=" + birthdate +
                '}';
    }
    public Mouse() {
    }
    public Mouse(String name, Date birthdate) {
        this.name = name;
        this.birthdate = birthdate;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Date getBirthdate() {
        return birthdate;
    }
    public void setBirthdate(Date birthdate) {
        this.birthdate = birthdate;
    }
}
package com.msb.bean;

public class Cat {
    private String name;
    private Mouse mouse1;
    @Override
    public String toString() {
        return "Cat{" +
                "name='" + name + '\'' +
                ", mouse1=" + mouse1 +
                '}';
    }
    public Cat() {
    }
    public Cat(String name, Mouse mouse1) {
        this.name = name;
        this.mouse1 = mouse1;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Mouse getMouse1() {
        return mouse1;
    }
    public void setMouse1(Mouse mouse1) {
        this.mouse1 = mouse1;
    }
}

applicationContext3.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">

    <!--告诉容器准备一个Date对象-->
    <bean id="date1" class="java.util.Date"></bean>

    <bean id="mouse" class="com.xiaohui.bean.Mouse">
        <property name="name" value="Jerry"></property>
        <!--bean引用引用外部bean-->
        <property name="birthdate" ref="date1"></property>
    </bean>

    <bean id="cat1" class="com.xiaohui.bean.Cat">
        <property name="name" value="Tom"></property>
        <!--bean引用引用外部bean-->
        <property name="mouse1" ref="mouse"></property>
    </bean>

    <bean id="cat2" class="com.xiaohui.bean.Cat">
        <property name="name" value="Tom2"></property>
        <!--引用内部bean-->
        <property name="mouse1">
            <bean class="com.xiaohui.bean.Mouse">
                <property name="name" value="Jerry2"></property>
                <property name="birthdate">
                    <bean class="java.util.Date"></bean>
                </property>
            </bean>
        </property>
    </bean>

    <bean id="mouse2" class="com.xiaohui.bean.Mouse"></bean>
    <bean id="cat3" class="com.xiaohui.bean.Cat">
        <property name="name" value="Tom3"></property>
        <!--级联引入bean-->
        <property name="mouse1" ref="mouse2"></property>
        <!--用反射调用该类的get***()方法,获取对象后再进行赋值-->
        <property name="mouse1.name" value="Jerry3"></property>
        <property name="mouse1.birthdate">
            <bean class="java.util.Date"></bean>
        </property>
    </bean>

</beans>

测试代码:

    @Test
    public void testCat(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext3.xml");
        Cat cat = applicationContext.getBean("cat1", Cat.class);
        System.out.println(cat.toString());

        Cat cat2 = applicationContext.getBean("cat2", Cat.class);
        System.out.println(cat2.toString());

        Cat cat3 = applicationContext.getBean("cat3", Cat.class);
        System.out.println(cat3.toString());

    }

结果:

Cat{name='Tom', mouse1=Mouse{name='Jerry', birthdate=Tue May 11 16:54:10 CST 2021}}
Cat{name='Tom2', mouse1=Mouse{name='Jerry2', birthdate=Tue May 11 16:54:10 CST 2021}}
Cat{name='Tom3', mouse1=Mouse{name='Jerry3', birthdate=Tue May 11 16:54:10 CST 2021}}

f、关于集合注入

<?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"
       xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/util
       http://www.springframework.org/schema/util/spring-util.xsd">

    <!--定义公共集合-->
    <util:list id="outerbookList">
        <!--声明多个Book对象-->
        <bean id="b1" class="com.xiaohui.bean.Book" p:bname="JAVA" p:author="马士兵"></bean>
        <bean id="b2" class="com.xiaohui.bean.Book" p:bname="Go" p:author="马士兵"></bean>
        <bean id="b3" class="com.xiaohui.bean.Book" p:bname="JVM" p:author="马士兵"></bean>
    </util:list>

    <bean id="student1" class="com.xiaohui.bean.Student">
        <!--数组注入  第一种-->
        <!--<property name="books" value="1,2,3"></property>-->
        <!--数组注入  第二种-->
        <property name="books">
            <array>
                <value>java</value>
                <value>Mysql</value>
                <value>Spring</value>
            </array>
        </property>

        <!--set集合注入-->
        <property name="bookSet">
            <set>
                <value>java</value>
                <value>Mysql</value>
                <value>Spring</value>
            </set>
        </property>

        <!--list集合注入-->
        <property name="bookList">
            <list>
                <value>java</value>
                <value>Mysql</value>
                <value>Spring</value>
            </list>
        </property>

        <!--Map集合注入-->
        <property name="bookMap">
            <map>
                <entry key="java" value="aaa"></entry>
                <entry key="mysql" value="bbb"></entry>
                <entry key="spring" value="ccc"></entry>
            </map>
        </property>

        <!--List对象注入  第一种方式-->
        <!--<property name="bookList2" ref="outerbookList"></property>-->
        <!--第二种-->
        <property name="bookList2">
            <list>
                <bean class="com.xiaohui.bean.Book">
                    <property name="bname" value="java"></property>
                    <property name="author" value="aaa"></property>
                </bean>
                <bean class="com.xiaohui.bean.Book">
                    <property name="bname" value="mysql"></property>
                    <property name="author" value="bbb"></property>
                </bean>
                <bean class="com.xiaohui.bean.Book">
                    <property name="bname" value="spring"></property>
                    <property name="author" value="ccc"></property>
                </bean>
                <ref bean="book1"></ref>
            </list>
        </property>
    </bean>

    <bean name="book1" class="com.xiaohui.bean.Book">
        <property name="bname" value="Java"></property>
        <property name="author" value="ddd"></property>
    </bean>


</beans>

测试代码:

    @Test
    public void testStudent(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext4.xml");
        Student student = applicationContext.getBean("student1", Student.class);
        System.out.println(student.getBooks());
        System.out.println(student.getBookList());
        System.out.println(student.getBookSet());
        System.out.println(student.getBookMap());
        System.out.println(student.getBookList2());

    }

结果:

[java, Mysql, Spring]
[java, Mysql, Spring]
[java, Mysql, Spring]
{java=aaa, mysql=bbb, spring=ccc}
[Book{bname='java', author='aaa'}, Book{bname='mysql', author='bbb'}, Book{bname='spring', author='ccc'}, Book{bname='Java', author='ddd'}]

g、工厂方式获取bean

特点 : bean标签中定义的class类不是返回的对象的类,而是生产该对象的工厂
工厂模式:GOF 设计模式
spring中给我们定义好了一个工厂接口,可以生产对象的接口,我们可以通过工厂来获取bean
定义工厂对象 实现 FactoryBean接口

package com.xiaohui.bean;

import org.springframework.beans.factory.FactoryBean;

public class BookFactroy implements FactoryBean<Book> {
    @Override
    public Book getObject() throws Exception {
        Book book = new Book();
        book.setBname("java");
        book.setAuthor("程序员");
        return book;
    }

    @Override
    public Class<?> getObjectType() {
        return null;
    }
}

配置文件

<bean id="bookFactroy" class="com.xiaohui.bean.BookFactroy"></bean>

测试代码

package com.xiaohui.test;

import com.xiaohui.bean.*;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.Arrays;

public class Test02 {

    @Test
    public void testUser(){
        ApplicationContext applicationContext =
                new ClassPathXmlApplicationContext("applicationContext5.xml");
        //工厂返回的对象是Book对象,所以这里要使用Book类来进行接收
        Book book = applicationContext.getBean("bookFactroy", Book.class);
        System.out.println(book);
    }

}

结果:

Book{bname='java', author='程序员'}

四、Bean的生命周期

SpringBean的生命周期参考链接:Spring Bean生命周期详解

  • bean从创建到销毁经历的各个阶段以及每个阶段所调用的方法
    • 1 通过构造器创建bean实例 (执行构造器)
    • 2 为bean属性赋值 (执行set方法)
    • 3 初始化bean (调用bean的初始化方法,需要配置指定调用的方法)
    • 4 bean的获取 (容器对象 getBean方法)
    • 5 容器关闭销毁bean (调用销毁方法,需要配置指定调用的方法)

测试生命周期
准备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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

     <bean id="user" class="com.xiaohui.bean.User" init-method="initUser" destroy-method="destoryUser">
          <property name="username" value="张三"></property>
     </bean>

</beans>

实体类

package com.xiaohui.bean;

 **/
public class User {
    private Integer userid;
    private String username;
    private String password;

    public User() {
        System.out.println("第一步:User构造");
    }

    public void setUsername(String username) {
        System.out.println("第二步:User set方法");
        this.username = username;
    }

    public void initUser(){
        System.out.println("第三步:User初始化");
    }

    public void destoryUser(){
        System.out.println("第五步:User 销毁");
    }

    public Integer getUserid() {
        return userid;
    }

    public void setUserid(Integer userid) {
        this.userid = userid;
    }

    public String getUsername() {
        return username;
    }



    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public User(Integer userid, String username, String password) {
        System.out.println("allArgConstructor");
        this.userid = userid;
        this.username = username;
        this.password = password;
    }

    @Override
    public String toString() {
        return "User{" +
                "userid=" + userid +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

测试代码:
	@Test
    public void testUser(){
        ClassPathXmlApplicationContext applicationContext =
                new ClassPathXmlApplicationContext("applicationContext6.xml");
        User user = applicationContext.getBean("user", User.class);
        System.out.println("第四步:User对象从容器中获取");
        System.out.println(user);
        //关闭容器
        applicationContext.close();
    }

结果:

第一步:User构造
第二步:User set方法
第三步:User初始化
第四步:User对象从容器中获取
User{userid=null, username='张三', password='null'}
第五步:User 销毁
  • 关于后置处理器
    • 1 通过构造器创建bean实例(执行构造器)
    • 2 为bean属性赋值(执行set方法)
    • 3 把bean实例传递给bean的后置处理器的方法
    • 4 初始化bean(调用bean的初始化方法,需要配置指定调用的方法)
    • 5 把bean实例传递给bean的后置处理器的方法
    • 6 bean的获取(容器对象 getBean方法)
    • 7 容器关闭销毁bean(调用销毁方法,需要配置指定调用的方法)

1、创建后置处理器 实现 BeanPostProcesser 重写两个方法

package com.xiaohui.beanProcesser;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

public class MyBeanProcesser implements BeanPostProcessor {
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        //Object bean      实例化的bean
        //String beanName  bean的id
        System.out.println("bean:初始化方法之前");
        return bean;// 这里必须return bean
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("bean:初始化方法之后");
        return bean;// 这里必须return bean
    }
}

2 配置后置处理器,对容器中的所有bean添加后置处理器的生命周期

<bean id="myBeanProcesser" class="com.xiaohui.beanProcesser.MyBeanProcesser"></bean>

结果:

第一步:User构造
第二步:User set方法
bean:初始化方法之后
第三步:User初始化
bean:初始化方法之前
第四步:User对象从容器中获取
User{userid=null, username='张三', password='null'}
第五步:User 销毁
  • BeanPostProcessor接口作用:
    • 如果我们想在Spring容器中完成bean实例化、配置以及其他初始化方法前后要添加一些自己逻辑处理。我们需要定义一个或多个BeanPostProcessor接口实现类,然后注册到Spring IoC容器中。

1、接口中的两个方法都要将传入的bean返回,而不能返回null,如果返回的是null那么我们通过getBean方法将得不到目标。
2、ApplicationContext会自动检测在配置文件中实现了BeanPostProcessor接口的所有bean,并把它们注册为后置处理器,然后在容器创建bean的适当时候调用它,因此部署一个后置处理器同部署其他的bean并没有什么区别。而使用BeanFactory实现的时候,bean 后置处理器必须通过代码显式地去注册,在IoC容器继承体系中的ConfigurableBeanFactory接口中定义了注册方法

五、自动装配

通过property标签可以手动指定给属性进行注入
我们也可以通过自动转配,完成属性的自动注入,就是自动装配,可以简化DI的配置

package com.xiaohui.bean;

public class Dept {
}

package com.xiaohui.bean;

public class Emp {
    private Dept dept;
    @Override
    public String toString() {
        return "Emp{" +
                "dept=" + dept +
                '}';
    }
    public Dept getDept() {
        return dept;
    }
    public void setDept(Dept dept) {
        this.dept = dept;
    }
    public Emp() {
    }
    public Emp(Dept dept) {
        this.dept = dept;
    }
}

配置文件

<?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">
    <bean id="dept" class="com.msb.bean.Dept"></bean>
    <!--
    autowire 属性控制自动将容器中的对象注入到当前对象的属性上
    byName 根据目标id值和属性值注入,要保证当前对象的属性值和目标对象的id值一致
    byType 根据类型注入,要保证相同类型的目标对象在容器中只有一个实例
    -->
    <bean id="emp" class="com.msb.bean.Emp" autowire="byName"></bean>
</beans>

测试代码

@Test
    public void testUser(){
        ApplicationContext applicationContext =
                new ClassPathXmlApplicationContext("applicationContext7.xml");
        Emp emp = applicationContext.getBean("emp", Emp.class);
        System.out.println(emp);
    }

结果:

Emp{dept=com.xiaohui.bean.Dept@13c9d689}

六、使用外部属性配置文件

1 导入Druid依赖和mysql-connector依赖

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.23</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.21</version>
        </dependency>

2 在resources目录下准备一个jdbc.properties属性配置文件

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1/mybatis?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
jdbc.username=root
jdbc.password=root

applicationContext中添加context名称空间 并读取属性配置文件,配置druid数据源将属性配置文件中的信息注入到连接池中

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

     <context:property-placeholder location="jdbc.properties"></context:property-placeholder>

     <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
          <property name="username" value="${jdbc.username}"></property>
          <property name="password" value="${jdbc.password}"></property>
          <property name="url" value="${jdbc.url}"></property>
          <property name="driverClassName" value="${jdbc.driver}"></property>
          <property name="initialSize" value="1"></property>
     </bean>

</beans>

3 Debug测试代码

    @Test
    public void testUser() throws SQLException {
        ApplicationContext applicationContext =
                new ClassPathXmlApplicationContext("applicationContext8.xml");
        DruidDataSource druidDataSource = applicationContext.getBean("druidDataSource", DruidDataSource.class);
        System.out.println(druidDataSource);
    }

在这里插入图片描述

七、注解方式管理bean

1、注解方式创建对象IOC

  • @Component 放在类上,用于标记,告诉spring当前类需要由容器实例化bean并放入容器中
    • 该注解有三个子注解
      • @Controller 用于实例化controller层bean
      • @Service 用于实例化service层bean
      • @Repository 用于实例化持久层bean
    • 当不确定是哪一层,就用Component
    • 这几个注解互相混用其实也可以,但是不推荐
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">
    <!--添加注解扫描,扫描指定的包,将包中的所有有注解的类实例化
        base-package 后面放要扫描的包
        如果有多个包需要扫描,可以使用逗号隔开  com.xiaohui.bean,com.xiaohui.service
        或者可以写上一层包路径  com.xiaohui
        可以通过注解指定bean的id@Component("user1")
        如果不指定,则id默认是 类名首字母小写
        -->
    <context:component-scan base-package="com.xiaohui.bean"></context:component-scan>

</beans>

实体类以及测试代码

package com.xiaohui.bean;

import org.springframework.stereotype.Component;

@Component
public class User {
}

    @Test
    public void testGetBean(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        User user = applicationContext.getBean("user", User.class);
        System.out.println(user);
    }

结果:

com.xiaohui.bean.User@41fecb8b

组件扫描配置注解识别
use-default-filters=“false”
默认值为true 代表使用默认的扫描过滤器
默认的扫描过滤器会识别并包含 @Component @Controller @Service @Repository 四个注解
不使用默认的filter,使用我们自己的filter

    <!--控制只扫描Controller注解-->
    <context:component-scan base-package="com.xiaohui.bean" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

    <!--控制不扫描Controller注解-->
    <context:component-scan base-package="com.xiaohui.bean" use-default-filters="true">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

测试代码:

//@Component
@Controller
//@Service
//@Repository
public class User {
}

    @Test
    public void testGetBean(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        User user1 = applicationContext.getBean("user", User.class);
        System.out.println(user1);
    }

只扫描Controller注解时,结果为 com.xiaohui.bean.User@5ace1ed4
不扫描Controller注解,结果为 org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'user' available

2、注解方式依赖注入DI

@Autowired 根据属性数据类型自动装配
@Qualifier 根据属性名称注入依赖
@Resources 可以根据类型,也可以根据名称注入
@Value 注入普通数据类型(8+String)
项目结构图
在这里插入图片描述
接口以及实现类

public interface UserDao {
    void add();
}

@Repository
public class UserDaoImplA implements UserDao {
    @Override
    public void add() {
        System.out.println("UserDaoImplA  add...");
    }
}

@Repository
public class UserDaoImplB implements UserDao {
    @Override
    public void add() {
        System.out.println("UserDaoImplB  add...");
    }
}

public interface UserService {
    void add();
}

applicationContext2.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:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">

    <context:property-placeholder location="classpath:aaa.properties"></context:property-placeholder>
    <context:component-scan base-package="com.xiaohui"></context:component-scan>

</beans>

aaa.properties

sname=小明
sage=20
sgender=男

使用外部文件读取内容时,中文乱码进行设置相关配置
在这里插入图片描述

  • @Autowired(常用)
    • 根据类型到容器中去寻找对应的对象,找到后给当前属性赋值
    • 不需要依赖 set方法
    • 属性类型可以是接口,会自动匹配对应的实现类对象
    • @Autowired配合 @Qualifier,可以通过名称指定注入的对象
    • @Resource 如果不配置name 那么就是根据类型注入
    • @Resource(name=“userDaoImplB”) 配置name,就是根据名称注入
  • @Resource 是JDK中javax包的注解
  • @Autowired 和 @Qualifier 是spring中的注解
  • @Value 可以个普通属性赋值
  • @Value 可以使用${}这种表达式获取系统的变量值
    • 或者是.properties属性配置文件中的值
package com.xiaohui.service.impl;

import com.xiaohui.dao.UserDao;
import com.xiaohui.dao.impl.UserDaoImplA;
import com.xiaohui.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
public class UserServiceImpl implements UserService {
//    @Autowired
//    @Qualifier("userDaoImplB")
//    private UserDao userDao;

    @Resource(name="userDaoImplA")
    private UserDao userDao;
    
    //普通属性赋值(可以使用外部文件也可以直接赋值)
    @Value("${sname}")
    private String sname;
    @Value("${sgender}")
    private String sgender;
    @Value("${sage}")
    private Integer sage;
    
    @Override
    public void add() {
        System.out.println("UserServiceImpl add...");
        System.out.println(sname);
        System.out.println(sgender);
        System.out.println(sage);
        userDao.add();
    }
}

使用@Autowired和@Qualifier的测试结果和@Resource的测试结果是一样的

UserServiceImpl add...
小明
男
20
UserDaoImplA  add...

3、配置类方式实现IOC和DI(了解)

通过一个config目录下的配置类添加@ComponentScan和@PropertySource进行扫描包和外部文件的目的
创建配置类,替代XML配置文件

package com.xiaohui.config;

import org.springframework.context.annotation.ComponentScan;

@ComponentScan(basePackages = "com.xiaohui")
@PropertySource("classpath:aaa.properties")
public class SpringConfig {
}

测试代码:

    @Test
    public void testGetBean2(){
        ApplicationContext applicationContext =
                new AnnotationConfigApplicationContext(SpringConfig.class);
        UserServiceImpl userServiceImpl = applicationContext.getBean("userServiceImpl", UserServiceImpl.class);
        userServiceImpl.add();
    }

结果:

UserServiceImpl add...
小明
男
20
UserDaoImplB  add...

八、代理模式

代理模式是通过代理对象访问目标对象,这样可以在目标对象基础上增强额外的功能,如添加权限,访问控制和审计等功能。

1、静态代理

静态代理中代理类与被代理类都需要实现同一个接口,这就说明我们的一个静态代理类只能代理一个类,并且还要事先知道我们要代理哪个类才能写代理类,如果我们有其他类还想使用代理那就必须再写一个代理类。然而在实际开发中我们是可能是有非常多的类是需要被代理的,并且事先我们可能并不知道我们要代理哪个类。所以如果继续使用静态代理反而会增加许多的工作量,并且效率低下,代码复用率也不好。

package com.xiaohui.test;

public class Test1 {
    public static void main(String[] args) {
        Person person =new Person("张三");
        Court court=new Lawyer(person);
        court.doCourt();
    }
}
// 接口
interface Court{
    void doCourt();
}
// 代理类
class Lawyer implements Court{
    private Person person;
    public Lawyer(Person person) {
        this.person = person;
    }
    @Override
    public void doCourt() {
        System.out.println("律师取证:视频证明张三当时正在旅游,不在案发现场");
        System.out.println("律师总结:张三不可能去杀人");
        person.doCourt();
    }
}
// 被代理的类
class Person implements Court{
    private String name;
    public Person(String name) {
        this.name = name;
    }
    @Override
    public void doCourt() {
        System.out.println(name+"说:我没有杀人");
    }
}

使用代理技术 获得代理对象 代替张三 增强打官司的方法

2、动态代理

动态代理可以针对于一些不特定的类或者一些不特定的方法进行代理,我们可以在程序运行时动态的变化代理的规则,代理类在程序运行时才创建的代理模式成为动态代理。这种情况下,代理类并不是在Java代码中定义好的,而是在程序运行时根据我们的在Java代码中的“指示”动态生成的
Proxy 动态代理 JDK动态代理 面向接口
cglib 动态代理 第三方动态代理 面向父类

a、Proxy 动态代理 JDK动态代理

通过Proxy动态代理获得一个代理对象,在代理对象中,对某个方法进行增强

  • 使用proxy 动态代理的前提
    • 面向接口
    • 1、必须有接口和实现类
    • 2、增强接口中定义的方法
    • 3、只能读取接口中方法的上注解
package com.xiaohui.testProxy;

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

public class Test02 {
    public static void main(String[] args) {
        Dinner dinner = new Person("张三");

        //通过Proxy动态代理获得一个代理对象,在代理对象中,对某个方法进行增强
        // ClassLoader loader,被代理的对象的类加载器
        ClassLoader classLoader = dinner.getClass().getClassLoader();
        // Class<?>[] interfaces,被代理对象所实现的所有接口
        Class[] interaces= dinner.getClass().getInterfaces();
        // InvocationHandler h,执行处理器对象,专门用于定义增强的规则
        InvocationHandler invocationHandler = new InvocationHandler(){
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//                Object proxy 代理对象
//                Method method 被代理的方法
//                Object[] args 被代理方法运行时的实参
                Object res = null;
                if(method.getName().equals("eat")){
                    System.out.println("饭前先洗手");
                    //让原有的eat方法去运行
                    res = method.invoke(dinner,args);
                    System.out.println("饭后洗碗");
                }else{
                    //让原有的drink方法去运行
                    res = method.invoke(dinner,args);
                }
                return res;
            }
        };

        Dinner dinnerProxy =(Dinner) Proxy.newProxyInstance(classLoader,interaces,invocationHandler);
        dinnerProxy.eat("包子");
//        dinnerProxy.drink();
    }
}

interface Dinner{
    void eat(String foodName);
    void drink();
}

class Person implements Dinner{
    private String name;
    public Person(String name) {
        this.name = name;
    }

    @Override
    public void eat(String foodName) {
        System.out.println(name+"正在吃"+foodName);
    }

    @Override
    public void drink() {
        System.out.println(name+"正在喝茶");
    }
}

class Student implements Dinner {
    private String name;

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

    @Override
    public void eat(String foodName) {
        System.out.println(name + "正在食堂吃" + foodName);
    }

    @Override
    public void drink() {
        System.out.println(name + "正在喝可乐");
    }
}

总结

  • 1、在不修改原有代码的 或者没有办法修改原有代码的情况下 增强对象功能 使用代理对象 代替原来的对象去完成功能
    进而达到拓展功能的目的
  • 2、JDK Proxy 动态代理面向接口的动态代理 一定要有接口和实现类的存在 代理对象增强的是实现类 在实现接口的方法重写的方法
    • 生成的代理对象只能转换成 接口的不能转换成 被代理类
    • 代理对象只能增强接口中定义的方法 实现类中其他和接口无关的方法是无法增强的
    • 代理对象只能读取到接口中方法上的注解 不能读取到实现类方法上的注解

b、CGLIB动态代理

1、面向父类的,和接口没有直接关系
2、不仅仅可以增强接口中定义的方法,还可以增强一个类其他的方法
3、可以读取父类中方法上的所有注解

package com.xiaohui.testCglib;

import org.junit.Test;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * @ClassName Test01
 **/
public class Test01 {
    @Test
    public void testCglib() {
        //获取一个Person对象
        Person person = new Person();
        //1、获得一个Enhancer对象
        Enhancer enhancer = new Enhancer();
        //2、设置父类字节码
        enhancer.setSuperclass(person.getClass());
        //3、获取MethodInterceptor对象,用于定义增强规则
        MethodInterceptor methodInterceptor = new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                /*
                Object o, 生成后的代理对象  personProxy
                Method method,  父类中原来要执行的方法 Person >>> eat()
                Object[] objects,  方法在调用时传入的实参数组
                MethodProxy methodProxy 子类中重写父类的方法 personProxy >>> eat()
                */
                Object res = null;
                if (method.getName().equals("eat")) {
                    System.out.println("饭前请洗手");
                    res = methodProxy.invokeSuper(o, objects);
                    System.out.println("饭后请洗碗");
                } else {
                    res = methodProxy.invokeSuper(o, objects);
                }
                return res;
            }
        };
        //4、回调设置methodInterceptor
        enhancer.setCallback(methodInterceptor);
        //5、获取代理对象
        Person person1 = (Person) enhancer.create();
        //6、使用代理对象完成功能
        person1.eat("包子");
    }
}

class Person {
    public Person() {
    }

    public void eat(String foodName) {
        System.out.println("张三正在吃" + foodName);
    }
}

class Son extends Person{
    public Son() {
        super();
    }

    @Override
    public void eat(String foodName) {
        System.out.println("饭前请洗手");
        super.eat(foodName);
        System.out.println("饭后请洗碗");
    }
}

九、Spring AOP基本使用

1、AOP概念

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

AOP切面编程一般可以帮助我们在不修改现有代码的情况下,对程序的功能进行拓展,往往用于实现 日志处理,权限控制,性能检测,事务控制等
AOP实现的原理就是动态代理,在有接口的情况下,使用JDK动态代理,在没有接口的情况下使用cglib动态代理

2、AOP中的术语辨析

  • 1、连接点 Joint point:
    • 类里面那些可以被增强的方法,这些方法称之为连接点
    • 表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point
  • 2、切入点 Pointcut:
    • 实际被增强的方法,称之为切入点
    • 表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方
  • 3、通知 Advice:
    • 实际增强的逻辑部分称为通知 (增加的功能)
    • Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。
    • 通知类型: 1 前置通知 2 后置通知 3 环绕通知 4 异常通知 5 最终通知
  • 4、目标对象 Target:
    • 被增强功能的对象(被代理的对象)
    • 织入 Advice 的目标对象
  • 5、切面Aspect:
    • 表现为功能相关的一些advice方法放在一起声明成的一个Java类
    • Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。
  • 6、织入 Weaving:
    • 创建代理对象并实现功能增强的声明并运行过程
      将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程

3、AOP注解方式实现(掌握)

项目框架如下图
在这里插入图片描述
导入相关的依赖包

    <packaging>jar</packaging>
    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.5</version>
        </dependency>
        <!--spring切面包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.3.5</version>
        </dependency>
        <!--aop联盟包-->
        <dependency>
            <groupId>aopalliance</groupId>
            <artifactId>aopalliance</artifactId>
            <version>1.0</version>
        </dependency>
        <!--Apache Commons日志包-->
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.2</version>
        </dependency>
        <!--德鲁伊连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.10</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.21</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/junit/junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.1</version>
            <scope>test</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

开启注解扫描和AOP切面编程自动生成代理对象配置
applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns: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
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--spring 包扫描-->
    <context:component-scan base-package="com.xiaohui"></context:component-scan>

    <!--aop autoProxy 自动生成代理对象-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

</beans>

准备接口 UserDao和EmpDao,以及实现类

package com.xiaohui.service;

public interface UserService {
    int addUser(int userId,String userName);
}

package com.xiaohui.service;

public interface EmpService {
    int addEmp(int empno,String ename,String job);
}

package com.xiaohui.service.impl;

import com.xiaohui.dao.UserDao;
import com.xiaohui.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDao userDao;

    @Override
    public int addUser(int userId, String userName) {
        System.out.println("userService add... ...");
        int res = userDao.addUser(userId,userName);
        return res;
    }
}

package com.xiaohui.service.impl;

import com.xiaohui.service.EmpService;
import org.springframework.stereotype.Service;

@Service
public class EmpServiceImpl implements EmpService {
    @Override
    public int addEmp(int empno, String ename, String job) {
        System.out.println("EmpService add... ...");
        return 0;
    }
}

准备切面 DaoAspect

package com.xiaohui.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Arrays;

@Aspect
@Component
public class DaoAspect {

    //定义公共切点  切点表达式直接指向接口
    @Pointcut("execution(* com.xiaohui.dao.*.add*(..))")
    public void addPointcut(){}

    /**
     * 前置通知:切点方法执行前先执行的功能
     * 参数列表可以用JoinPoint接收切点对象
     * 可以获取方法执行的参数
     */
    @Before("addPointcut()")
    public void methodBefore(JoinPoint joinPoint){
        System.out.println("methodBefore invoke... ...");
        Object[] args = joinPoint.getArgs();
        System.out.println("args:"+ Arrays.toString(args));
    }

    /**
     * 后置通知:方法执行之后要增强的功能
     * 论切点方法是否出现异常都会执行的方法
     * 参数列表可以用JoinPoint接收切点对象
     */
    @After("addPointcut()")
    public void methodAfter(JoinPoint joinPoint){
        System.out.println("methodAfter invoke... ...");
    }

    /**
     * 返回通知:切点方法正常运行结束后增强的功能
     * 如果方法运行过程中出现异常,则该功能不运行
     * 参数可以用JoinPoint joinPoint接收切点对象
     * 可以用Object res接收方法返回值,需要用returning指定返回值名称
     */
    @AfterReturning(value = "addPointcut()",returning = "res")
    public void methodAfterReturning(JoinPoint joinPoint,Object res){
        System.out.println("methodAfterReturning invoke... ...");
        System.out.println(res);
    }

    /**
     * 异常通知:切点方法出现异常运行时的增强功能
     * 如果方法运行没有出现异常,则该功能不运行
     * 参数列表可以用Exception exception接收异常对象,需要通过throwing指定异常名称
     */
    @AfterThrowing(value = "addPointcut()",throwing = "exception")
    public void methodAfterTrowing(Exception exception){
        System.out.println("methodAfterTrowing invoke... ...");
        System.out.println(exception.getMessage());
    }

    /**
     * 环绕通知:在切点方法之前和之后都进行功能的增强
     * 参数列表需要带上一个特殊的形参
     * 需要在通知中定义方法执行的位置,并在执行位置之前和之后自定义增强的功能
     * 方法列表可以通过ProceedingJoinPoint获取执行的切点(手动控制切点方法执行的位置)
     *  环绕通知的返回值必须是Object  在环绕通知中必须要将切点方法继续向上返回
     */
    @Around("addPointcut()")
    public Object methodAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("methodAroundA invoke... ...");
        Object proceed = proceedingJoinPoint.proceed();//控制切点方法在这里执行
        System.out.println("methodAroundB invoke... ...");
        return proceed;
    }
}

测试类

package com.xiaohui.test;

import com.xiaohui.service.UserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test01 {
    @Test
    public void getBean(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserDao userDao = applicationContext.getBean(UserDao.class);
        int res = userDao.addUser(1, "张三");
        System.out.println(res);
        System.out.println("---------------");
        EmpDao empDao = applicationContext.getBean(EmpDao.class);
        int empRes = empDao.addEmp(1, "Java", "aaa");
        System.out.println(empRes);
    }
}

结果:

methodAroundA invoke... ...
methodBefore invoke... ...
args:[1, 张三]
userDao add... ...
methodAfterReturning invoke... ...
1
methodAfter invoke... ...
methodAroundB invoke... ...
1
---------------
methodAroundA invoke... ...
methodBefore invoke... ...
args:[1, Java, aaa]
EmpDao add... ...
methodAfterReturning invoke... ...
0
methodAfter invoke... ...
methodAroundB invoke... ...
0

相关文档:参考【斜阳雨陌的JoinPoint的用法】
JoinPoint对象
JoinPoint对象封装了SpringAop中切面方法的信息,在切面方法中添加JoinPoint参数,就可以获取到封装了该方法信息的JoinPoint对象
常用api:

方法名功能
Signature getSignature();获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的Class等信息
Object[] getArgs();获取传入目标方法的参数对象
Object getTarget();获取被代理的对象
Object getThis();获取代理对象

ProceedingJoinPoint对象
ProceedingJoinPoint对象是JoinPoint的子接口,该对象只用在@Around的切面方法中
添加了
Object proceed() throws Throwable //执行目标方法
Object proceed(Object[] var1) throws Throwable //传入的新的参数去执行目标方法
两个方法.

有多个增强类对同一个方法进行增强,通过@Order注解设置增强类优先级
数字越小,优先级越高
数字越小,其代理位置越靠近注入位置
在这里插入图片描述
完全使用注解开发
创建配置类

package com.msb.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@ComponentScan(basePackages = "com.xiaohui")
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class SpringConfig {
}

测试代码:

@Test
    public void test2(){
        ApplicationContext context=new AnnotationConfigApplicationContext(SpringConfig.class);
        UserDao userDao = context.getBean( UserDao.class);
        int add = userDao.addUser(10,"晓明");
    }

结果和以上是相同的

4、AOPXML方式实现(了解)

1、创建两个类,增强类和被增强类,创建方法(参考上个知识点的内容)
2、在Spring配置中创建两个类对象

	<!--创建对象--> 
    <bean id="userDao" class="com.xiaohui.dao.impl.UserDaoImpl"></bean>
    <bean id="daoAspect" class="com.xiaohui.aspect.DaoAspect"></bean>

3、在spring配置文件中配置切入点

 <!--配置aop增强-->
    <aop:config>
        <!--切入点-->
        <aop:pointcut id="pointCutAdd" expression="execution(* com.msb.dao.UserDao.add*(..))"/>
        <!--配置切面-->
        <aop:aspect ref="daoAspect">
            <!--增强作用在具体的方法上-->
            <aop:before method="methodBefore" pointcut-ref="pointCutAdd"/>
            <aop:after method="methodAfter" pointcut-ref="pointCutAdd"/>
            <aop:around method="methodAround" pointcut-ref="pointCutAdd"/>
            <aop:after-returning method="methodAfterReturning"  pointcut-ref="pointCutAdd" returning="res"/>
            <aop:after-throwing method="methodAfterThrowing"  pointcut-ref="pointCutAdd" throwing="ex"/>
        </aop:aspect>
    </aop:config>

测试代码参考上面
测试结果:

methodBefore invoke... ...
args:[1, 张三]
methodAroundA invoke... ...
userDao add... ...
methodAfterReturning invoke... ...
methodAroundB invoke... ...
methodAfter invoke... ...
1

十、JDBCTemplate的使用

1、概述

JdbcTemplate是spring框架中提供的一个对象,是对原始繁琐的Jdbc API对象的简单封装。spring框架为我们提供了很多的操作模板类。例如:操作关系型数据的JdbcTemplate和,操作nosql数据库的RedisTemplate,操作消息队列的JmsTemplate等等。

2、基本的CRUD操作

1、创建一个Maven项目,并导入以下依赖

  <dependencies>
        <!--spring核心容器包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.5</version>
        </dependency>
        <!--spring切面包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.3.5</version>
        </dependency>
        <!--aop联盟包-->
        <dependency>
            <groupId>aopalliance</groupId>
            <artifactId>aopalliance</artifactId>
            <version>1.0</version>
        </dependency>
        <!--德鲁伊连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.10</version>
        </dependency>
        <!--mysql驱动-->
         <dependency>
             <groupId>mysql</groupId>
             <artifactId>mysql-connector-java</artifactId>
             <version>8.0.22</version>
         </dependency>
        <!--springJDBC包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.3.5</version>
        </dependency>
        <!--spring事务控制包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.3.5</version>
        </dependency>
        <!--spring orm 映射依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>5.3.5</version>
        </dependency>
        <!--Apache Commons日志包-->
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.2</version>
        </dependency>
        <!--Junit单元测试-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.1</version>
            <scope>test</scope>
        </dependency>
        <!--lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

2、准备JDBC.properties

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1/mybatis?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
jdbc.username=root
jdbc.password=root

3、准备applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c"
       xmlns:util="http://www.springframework.org/schema/util"
       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/util
       http://www.springframework.org/schema/util/spring-util.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd
">
    <!--spring 注解扫描-->
    <context:component-scan base-package="com.xiaohui"/>
    <!--读取jdbc配置文件-->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <!--配置德鲁伊连接池-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="driverClassName" value="${jdbc.driver}"></property>
    </bean>
    <!--配置JDBCTemplate对象,并向里面注入DataSource-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!--通过set方法注入连接池-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>
</beans>

3、准备实体类

package com.xiaohui.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.util.Date;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Emp implements Serializable {
    private Integer empno;
    private String ename;
    private String job;
    private Integer mgr;
    private Date hiredate;
    private Double sal;
    private Double comm;
    private Integer deptno;
}

4、准备service层的接口和实现类

package com.xiaohui.service;

import com.xiaohui.pojo.Emp;

import java.util.List;

public interface EmpService {

    int findEmpCount();

    Emp findByEmpno(int empno);

    List<Emp> findByDeptno(int deptno);

    int addEmp(Emp emp);

    int updateEmp(Emp emp);

    int deleteEmp(int empno);
}


package com.xiaohui.service.impl;

import com.xiaohui.dao.EmpDao;
import com.xiaohui.pojo.Emp;
import com.xiaohui.service.EmpService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class EmpServiceImpl implements EmpService {

    @Autowired
    private EmpDao empDao;

    @Override
    public int findEmpCount() {
        return empDao.findEmpCount();
    }

    @Override
    public Emp findByEmpno(int empno) {
        return empDao.findByEmpno(empno);
    }

    @Override
    public List<Emp> findByDeptno(int deptno) {
        return empDao.findByDeptno(deptno);
    }

    @Override
    public int addEmp(Emp emp) {
        return empDao.addEmp(emp);
    }

    @Override
    public int updateEmp(Emp emp) {
        return empDao.updateEmp(emp);
    }

    @Override
    public int deleteEmp(int empno) {
        return empDao.deleteEmp(empno);
    }
}

5、准备Dao层的接口以及实现类

package com.xiaohui.dao;

import com.xiaohui.pojo.Emp;

import java.util.List;

public interface EmpDao {

    int findEmpCount();

    Emp findByEmpno(int empno);

    List<Emp> findByDeptno(int deptno);

    int addEmp(Emp emp);

    int updateEmp(Emp emp);

    int deleteEmp(int empno);
}


package com.xiaohui.dao.impl;

import com.xiaohui.dao.EmpDao;
import com.xiaohui.pojo.Emp;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public class EmpDaoImpl implements EmpDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public int findEmpCount() {
        /**
         * @功能描述:查询员工个数
         * jdbcTemplate.queryForObject
         * 1.sql语句
         * 2.返回值类型字节码
         */
        String sql = "select count(1) from emp";
        Integer integer = jdbcTemplate.queryForObject(sql, Integer.class);

        return integer;
    }

    /**
     * @功能描述:查询单个员工信息
     * jdbcTemplate.queryForObject
     * 1.sql语句
     * 2.RowMapper接口中的一个实现类对象,用于指定结果集用什么实体类进行封装
     * 3.sql语句中需要传入的参数 可变参数
     */
    @Override
    public Emp findByEmpno(int empno) {
        String sql = "select * from emp where empno = ? ";
        BeanPropertyRowMapper<Emp> beanPropertyRowMapper = new BeanPropertyRowMapper<Emp>(Emp.class);
        Emp emp = jdbcTemplate.queryForObject(sql, beanPropertyRowMapper, empno);
        return emp;
    }

    /**
     * @功能描述:查询多个员工对象集合
     * jdbcTemplate.query
     * 1.sql语句
     * 2.RowMapper接口中的一个实现类对象,用于指定结果集用什么实体类进行封装
     * 3.sql语句中需要传入的参数 可变参数
     */
    @Override
    public List<Emp> findByDeptno(int deptno) {
        String sql = "select * from emp where deptno = ?";
        BeanPropertyRowMapper<Emp> beanPropertyRowMapper = new BeanPropertyRowMapper<Emp>(Emp.class);
        List<Emp> empList = jdbcTemplate.query(sql, beanPropertyRowMapper, deptno);
        return empList;
    }

    /**
     * @功能描述:增加员工信息
     * jdbcTemplate.update
     * 1.sql语句
     * 2.sql语句中的需要的参数
     */
    @Override
    public int addEmp(Emp emp) {
        String sql = "insert into emp values(DEFAULT,?,?,?,?,?,?,?)";
        Object[] args = {emp.getEname(),emp.getJob(),emp.getMgr(),emp.getHiredate(),emp.getSal(),
                emp.getComm(),emp.getDeptno()};
        int addEmpCount = jdbcTemplate.update(sql, args);
        return addEmpCount;
    }

    /**
     * @功能描述:修改某个员工的信息
     * jdbcTemplate.update
     * 1.sql语句
     * 2.sql语句中的需要的参数
     */
    @Override
    public int updateEmp(Emp emp) {
        String sql = "update emp set ename=?,job=?,mgr=?,hiredate=?,sal=?,comm=?,deptno=? where empno =?";
        Object[] args = {emp.getEname(),emp.getJob(),emp.getMgr(),emp.getHiredate(),emp.getSal(),
                emp.getComm(),emp.getDeptno(),emp.getEmpno()};
        int updateEmpCount = jdbcTemplate.update(sql, args);
        return updateEmpCount;
    }

    /**
     * @功能描述:删除某个员工信息
     * jdbcTemplate.update
     * 1.sql语句
     * 2.sql语句中的需要的参数
     */
    @Override
    public int deleteEmp(int empno) {
        String sql = "delete from emp where empno =?";
        int deleteEmpCount = jdbcTemplate.update(sql, empno);
        return deleteEmpCount;
    }
}

测试代码:

package com.xiaohui.test;

import com.xiaohui.pojo.Emp;
import com.xiaohui.service.EmpService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.Date;
import java.util.List;

public class Test01 {
    @Test
    public void test01(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        EmpService empService = applicationContext.getBean(EmpService.class);
        // 查询员工个数
        int empCount = empService.findEmpCount();
        System.out.println(empCount);
        // 根据员工编号查询员工对象
        Emp emp = empService.findByEmpno(7521);
        System.out.println(emp);
        //根据部门编号查询多个员工对象集合
        List<Emp> empList = empService.findByDeptno(20);
        empList.forEach(System.out::println);
        //增加员工信息
        int addEmprows = empService.addEmp(new Emp(null, "TOM", "SALESMAN", 7521, new Date(), 2000.0, 100.0, 10));
        System.out.println(addEmprows);
        //根据员工编号修改员工信息
        int updateEmprows = empService.updateEmp(new Emp(7936, "JERRY", "MANAGER", 7839, new Date(), 3000.0, 0.0, 20));
        System.out.println(updateEmprows);
        //根据员工编号删除员工信息
        int deleteEmprows = empService.deleteEmp(7936);
        System.out.println(deleteEmprows);
    }
}

3、JDBCTemplate批量操作

利用上面的项目,添加实体类Dept

package com.xiaohui.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Dept {
    private Integer deptno;
    private String dname;
    private String loc;
}

创建service层的接口和实现类

package com.xiaohui.service;

import com.xiaohui.pojo.Dept;

import java.util.List;

public interface DeptService {
    int[] deptBatchAdd(List<Dept> depts);

    int[] deptBatchUpdate(List<Dept> depts);

    int[] deptBatchDelete(List<Integer> deptnos);
}


package com.xiaohui.service.impl;

import com.xiaohui.dao.DeptDao;
import com.xiaohui.pojo.Dept;
import com.xiaohui.service.DeptService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class DeptServiceImpl implements DeptService {
    @Autowired
    private DeptDao deptDao;

    @Override
    public int[] deptBatchAdd(List<Dept> depts) {
        return deptDao.deptBatchAdd(depts);
    }

    @Override
    public int[] deptBatchUpdate(List<Dept> depts) {
        return deptDao.deptBatchUpdate(depts);
    }

    @Override
    public int[] deptBatchDelete(List<Integer> deptnos) {
        return deptDao.deptBatchDelete(deptnos);
    }
}

创建Dao层的接口和实现类

package com.xiaohui.dao;

import com.xiaohui.pojo.Dept;

import java.util.List;

public interface DeptDao {
    int[] deptBatchAdd(List<Dept> depts);

    int[] deptBatchUpdate(List<Dept> depts);

    int[] deptBatchDelete(List<Integer> deptnos);
}


package com.xiaohui.dao.impl;

import com.xiaohui.dao.DeptDao;
import com.xiaohui.pojo.Dept;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import java.util.LinkedList;
import java.util.List;

@Repository
public class DeptDaoImpl implements DeptDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    /**
     * @功能描述:批量增加操作
     */
    @Override
    public int[] deptBatchAdd(List<Dept> depts) {
        String sql = "insert into dept values(DEFAULT,?,?)";
        LinkedList<Object[]> args = new LinkedList<>();
        for (Dept dept : depts) {
            Object[] arg = {dept.getDname(), dept.getLoc()};
            args.add(arg);
        }
        int[] ints = jdbcTemplate.batchUpdate(sql, args);
        return ints;
    }
    
    /**
     * @功能描述:批量修改操作
     */
    @Override
    public int[] deptBatchUpdate(List<Dept> depts) {
        String sql = "update dept set dname =?, loc =? where deptno=?";
        LinkedList<Object[]> args = new LinkedList<>();
        for (Dept dept : depts) {
            Object[] arg = {dept.getDname(), dept.getLoc(),dept.getDeptno()};
            args.add(arg);
        }
        int[] ints = jdbcTemplate.batchUpdate(sql, args);
        return ints;
    }
    /**
     * @功能描述:批量删除操作
     */
    @Override
    public int[] deptBatchDelete(List<Integer> deptnos) {
        String sql = "delete from dept where deptno=?";
        LinkedList<Object[]> args = new LinkedList<>();
        for (Integer deptno : deptnos) {
            Object[] arg = {deptno};
            args.add(arg);
        }
        int[] ints = jdbcTemplate.batchUpdate(sql, args);
        return ints;
    }
}

测试代码

package com.xiaohui.test;

import com.xiaohui.pojo.Dept;
import com.xiaohui.service.DeptService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class Test02 {
    @Test
    public void testDeptBatchAdd(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        DeptService deptService = applicationContext.getBean(DeptService.class);
        List<Dept> depts =new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            depts.add(new Dept(null,"name"+i,"loc"+i));
        }
        int[] ints = deptService.deptBatchAdd(depts);
        System.out.println(Arrays.toString(ints));
    }

    @Test
    public void testDeptBatchUpdate(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        DeptService deptService = applicationContext.getBean(DeptService.class);
        List<Dept> depts =new ArrayList<>();
        for (int i = 46; i <= 55; i++) {
            depts.add(new Dept(i,"newName"+i,"newLoc"+i));
        }
        int[] ints = deptService.deptBatchUpdate(depts);
        System.out.println(Arrays.toString(ints));
    }

    @Test
    public void testDeptBatchDelete(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        DeptService deptService = applicationContext.getBean(DeptService.class);
        List<Integer> deptnos =new ArrayList<>();
        for (int i = 46; i <= 55; i++) {
            deptnos.add(i);
        }
        int[] ints = deptService.deptBatchDelete(deptnos);
        System.out.println(Arrays.toString(ints));
    }
}

所有操作执行成功后返回的结果一致为以下所示:

[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]

十一、事务管理

1、事务的相关介绍

a、概念

事务(Transaction)指的是一个操作序列,该操作序列中的多个操作要么都做,要么都不做,是一个不可分割的工作单位,是数据库环境中的逻辑工作单位,由DBMS中的事务管理子系统负责事务的处理。
目前常用的存储引擎有InnoDB(MySQL5.5以后默认的存储引擎)和MyISAM(MySQL5.5之前默认的存储引擎),其中InnoDB支持事务处理机制,而MyISAM不支持。

b、事务的特性

事务处理可以确保除非事务性序列内的所有操作都成功完成,否则不会永久更新面向数据的资源。通过将一组相关操作组合为一个要么全部成功要么全部失败的序列,可以简化错误恢复并使应用程序更加可靠。
但并不是所有的操作序列都可以称为事务,这是因为一个操作序列要成为事务,必须满足事务的原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。这四个特性简称为ACID特性。

  • 1、原子性
    • 原子是自然界最小的颗粒,具有不可再分的特性。事务中的所有操作可以看做一个原子,事务是应用中不可再分的最小的逻辑执行体。
    • 使用事务对数据进行修改的操作序列,要么全部执行,要么全不执行。通常,某个事务中的操作都具有共同的目标,并且是相互依赖的。如果数据库系统只执行这些操作中的一部分,则可能会破坏事务的总体目标,而原子性消除了系统只处理部分操作的可能性。
  • 2、一致性
    • 一致性是指事务执行的结果必须使数据库从一个一致性状态,变到另一个一致性状态。当数据库中只包含事务成功提交的结果时,数据库处于一致性状态。一致性是通过原子性来保证的。
    • 例如:在转账时,只有保证转出和转入的金额一致才能构成事务。也就是说事务发生前和发生后,数据的总额依然匹配。
  • 3、隔离性
    • 隔离性是指各个事务的执行互不干扰,任意一个事务的内部操作对其他并发的事务,都是隔离的。也就是说:并发执行的事务之间既不能看到对方的中间状态,也不能相互影响。
    • 例如:在转账时,只有当A账户中的转出和B账户中转入操作都执行成功后才能看到A账户中的金额减少以及B账户中的金额增多。并且其他的事务对于转账操作的事务是不能产生任何影响的。
  • 4、持久性
    • 持久性指事务一旦提交,对数据所做的任何改变,都要记录到永久存储器中,通常是保存进物理数据库,即使数据库出现故障,提交的数据也应该能够恢复。但如果是由于外部原因导致的数据库故障,如硬盘被损坏,那么之前提交的数据则有可能会丢失。

c、事务的并发问题

  • 脏读(Dirty read)
    • 当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。
      在这里插入图片描述
  • 不可重复读(Unrepeatableread)
    • :指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。
  • 幻读(Phantom read)
    • 幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。
      在这里插入图片描述
  • 不可重复度和幻读区别
    • 不可重复读的重点是修改,幻读的重点在于新增或者删除。
      解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表

d、事务的隔离级别

  • 事务的隔离级别用于决定如何控制并发用户读写数据的操作。数据库是允许多用户并发访问的,如果多个用户同时开启事务并对同一数据进行读写操作的话,有可能会出现脏读、不可重复读和幻读问题,所以MySQL中提供了四种隔离级别来解决上述问题。
  • 事务的隔离级别从低到高依次为READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ以及SERIALIZABLE,隔离级别越低,越能支持高并发的数据库操作。
隔离级别脏读不可重复读幻读
READ UNCOMMITTED
READ COMMITTED×
REPEATABLE READ××
READ UNCOMMITTED
SERIALIZABLE×××

spring中可以使用如下方式实现事务的控制

  • 1 编程式(不推荐)

  • 2 声明式(掌握)

    • 1、注解(简单,必会)

    • 2、XML配置(繁琐,了解)

2、声明式事务管理(掌握)

事务的管理应该放在我们的service层进行处理

  • spring中有两种事务的管理方式
    • 1 编程式事务管理(了解)
    • 2 声明式事务管理(掌握)
      • 基于注解方式实现(掌握)
      • XML方式实现(了解)

Spring声明式事务的实现方式,底层就是AOP,AOP的底层就是动态代理
事务管理器接口: PlatformTransactionManager 针对不同的框架,提供了不同的实现类
在这里插入图片描述

a、注解方式声明事务(掌握)

环境搭建,首先创建一个Maven项目,并导入相关包(参考 十、JDBCTemplate的使用的架包)
注解方式实现事务控制
在applicationContext.xml配置事务相关的配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c"
       xmlns:util="http://www.springframework.org/schema/util"
       xmlns:context="http://www.springframework.org/schema/context"
       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/util
       http://www.springframework.org/schema/util/spring-util.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx.xsd
">
    <!--spring 注解扫描-->
    <context:component-scan base-package="com.xiaohui"/>
    <!--读取jdbc配置文件-->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <!--配置德鲁伊连接池-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="driverClassName" value="${jdbc.driver}"></property>
    </bean>
    <!--配置JDBCTemplate对象,并向里面注入DataSource-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!--通过set方法注入连接池-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--配置一个事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    	<!--将数据源注入事务管理器-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!--开启事务注解-->
    <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>

</beans>

1、准备实体类

package com.xiaohui.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Account implements Serializable {
    private Integer id;
    private String name;
    private Integer money;
}

2、准备service层的接口和实现类

package com.xiaohui.service;

public interface AccountService {
    int transMoney(int from ,int to,int money);
}


package com.xiaohui.service.impl;

import com.xiaohui.dao.AccountDao;
import com.xiaohui.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
//@Transactional //加在类上,代表该类的所有方法都添加了事务控制
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountDao accountDao;

    @Override
    @Transactional //放在方法上,就是仅仅对当前方法添加了事务控制
    public int transMoney(int from, int to, int money) {
        int rows = 0;
        rows += accountDao.transMoney(from, 0 - money);
        // int i=1/0;
        rows += accountDao.transMoney(to, money);
        return rows;
    }
}

3、准备dao层的接口和实现类

package com.xiaohui.dao;

public interface AccountDao {
    int transMoney(int id,int money);
}


package com.xiaohui.dao.impl;

import com.xiaohui.dao.AccountDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository
public class AccountDaoImpl implements AccountDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Override
    public int transMoney(int id, int money) {
        String sql = "update account set money = money+? where id=?";
        return jdbcTemplate.update(sql,money,id);
    }
}

4、测试类

package com.xiaohui.test;

import com.xiaohui.service.AccountService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test01 {
    @Test
    public void test01(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        AccountService accountService = applicationContext.getBean(AccountService.class);
        int rews = accountService.transMoney(1, 2, 100);
        System.out.println(rews);
    }
}

再次测试,就算是service方法运行出现异常,自动会回滚,如果没有,那么自动提交

b、事务的其他配置参数

@Transactional 注解的一些参数和参数的含义
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.READ_UNCOMMITTED,readOnly = true,rollbackFor = ClassCastException.class,noRollbackFor = NullPointerException.class,timeout = 10)

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
    @AliasFor("transactionManager")
    String value() default "";
    
    @AliasFor("value")
    String transactionManager() default "";
    
    String[] label() default {};
    
    Propagation propagation() default Propagation.REQUIRED;// ***
    
    Isolation isolation() default Isolation.DEFAULT;// ***
    
    int timeout() default -1;// ***
    
    String timeoutString() default "";
    
    boolean readOnly() default false;// ***
    
    Class<? extends Throwable>[] rollbackFor() default {};// ***
    
    String[] rollbackForClassName() default {};
    
    Class<? extends Throwable>[] noRollbackFor() default {};// ***
    
    String[] noRollbackForClassName() default {};
}

propagation 事务的传播行为(面试)
多事务方法之间调用,事务是如何管理的

事务传播行为类型说明
PROPAGATION_REQUIRED如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择(默认)。
PROPAGATION_SUPPORTS支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY使用当前的事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。

如果service层 add方法调用了 addDept和addEmp两个方法

  • PROPAGATION_REQUIRED
    • 如果add方法有事务,那么addDept和addEmp就加入到add方法里的事务
    • 如果add方法没有事务,那么就新建一个事务,将addDept和addEmp加入到这个新的事务中
  • PROPAGATION_REQUIRES_NEW
    • 无论add是否有事务,都建立一个新的事务,所有的方法都加入到新的事务中,add原来的事务就不用了

isolation 事务的隔离级别

  • 1、DEFAULT (默认)
    • 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别。另外四个与JDBC的隔离级别相对应。
    • MySQL默认REPEATABLE_READ
    • Oracle默认READ_COMMITTED
  • 2、READ_UNCOMMITTED (读未提交)
    • 这是事务最低的隔离级别,它允许另外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。
  • 3、READ_COMMITTED (读已提交)
    • 保证一个事务修改的数据提交后才能被另外一个事务读取,另外一个事务不能读取该事务未提交的数据。这种事务隔离级别可以避免脏读出现,但是可能会出现不可重复读和幻像读。
  • 4、REPEATABLE_READ (可重复读)
    • 这种事务隔离级别可以防止脏读、不可重复读,但是可能出现幻像读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了不可重复读。
  • 5、SERIALIZABLE(串行化)
    • 这是花费最高代价但是最可靠的事务隔离级别,事务被处理为顺序执行。除了防止脏读、不可重复读外,还避免了幻像读。
      示例:
    @Override
    @Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.DEFAULT) //放在方法上,就是仅仅对当前方法添加了事务控制
    public int transMoney(int from, int to, int money) {
        int rows = 0;
        rows += accountDao.transMoney(from, 0 - money);
        rows += accountDao.transMoney(to, money);
        return rows;
    }

timeout 超时时间
事务一定要在多长时间之内提交,如果不提交就会回滚

readOnly 只读事务
事务是否只能读取数据库的数据,如果为true,则不允许进行增删改

rollbackFor 指定发生回滚的异常
当方法发生哪些异常时才会回滚

noRollbackFor 指定不发生回滚的异常
当方法发生哪些异常时,不会回滚

c、XML方式声明事务(了解)

首先注释掉service层的注解式事务@Transactional,新创建一个Spring配置文件
applicationContext2.xml 通过AOP实现事务的控制

<?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"
       xmlns:util="http://www.springframework.org/schema/util"
       xmlns:context="http://www.springframework.org/schema/context"
       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/util
       http://www.springframework.org/schema/util/spring-util.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx.xsd
">
    <!--spring 注解扫描-->
    <context:component-scan base-package="com.xiaohui"/>
    <!--读取jdbc配置文件-->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <!--配置德鲁伊连接池-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="driverClassName" value="${jdbc.driver}"></property>
    </bean>
    <!--配置JDBCTemplate对象,并向里面注入DataSource-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!--通过set方法注入连接池-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>

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

    <!--配置通知-->
    <tx:advice id="txAdvice">
        <!--配置事务参数-->
        <tx:attributes>
            <tx:method name="transMoney" isolation="DEFAULT" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>
    <!--配置AOP-->
    <aop:config>
        <!--配置切入点-->
        <aop:pointcut id="pt" expression="execution(* com.xiaohui.service.AccountService.transMoney(..))"/>
        <!--配置切面-->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt"></aop:advisor>
    </aop:config>

</beans>

测试代码

    @Test
    public void test02(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext2.xml");
        AccountService accountService = applicationContext.getBean(AccountService.class);
        int rews = accountService.transMoney(1, 2, 100);
        System.out.println(rews);

    }

结果:程序正常运行,自动提交,则数据库也正常修改,异常终止时,程序结束并回滚,不会修改数据库内的数据

d、零XML事务控制

创建配置类

package com.xiaohui.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration //配置类的标志注解
@ComponentScan(basePackages = "com.xiaohui")//spring包扫描
@PropertySource("classpath:jdbc.properties")//读取配置文件
@EnableTransactionManagement//开启事务注解
public class SpringConfig {

    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;

    /*创建数据库连接池*/
    @Bean
    public DruidDataSource getDruidDataSource(){
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setUsername(username);
        druidDataSource.setPassword(password);
        druidDataSource.setDriverClassName(driver);
        druidDataSource.setUrl(url);
        return druidDataSource;
    }

    /*创建JdbcTemplate对象*/
    @Bean
    public JdbcTemplate getJdbcTemplate(DruidDataSource dataSource){
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }

    /*创建事务管理器*/
    @Bean
    public PlatformTransactionManager getPlatformTransactionManager(DruidDataSource dataSource){
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dataSource);
        return dataSourceTransactionManager;
    }

}

测试方法

    @Test
    public void test03(){
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        AccountService accountService = applicationContext.getBean(AccountService.class);
        int rews = accountService.transMoney(1, 2, 100);
        System.out.println(rews);

    }

结果和以上为相同

十二、日志框架和测试支持

spring5框架自带了通用的日志封装,也可以整合自己的日志
1)spring移除了 LOG4jConfigListener,官方建议使用log4j2
2)spring5整合log4j2
导入log4j2依赖

        <!--log4j2 依赖-->
        <!--<dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.14.0</version>
        </dependency>-->
        <!--slf4-impl 包含了log4j2 依赖-->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j-impl</artifactId>
            <version>2.14.0</version>
            <scope>test</scope>
        </dependency>

在resources目录下准备log4j2.xml的配置文件

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="DEBUG">
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{YYYY-MM-dd HH:mm:ss} [%t] %-5p %c{1}:%L - %msg%n" />
        </Console>
    </Appenders>
    <Loggers>
        <Root level="debug">
            <AppenderRef ref="Console" />
        </Root>
    </Loggers>
</Configuration>

spring5关于测试工具的支持
整合junit4
依赖的jar

        <!--Junit4单元测试-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.1</version>
            <scope>test</scope>
        </dependency>
        <!--spring test测试支持包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.3.5</version>
            <scope>test</scope>
        </dependency>

测试代码:

package com.xiaohui.test;

import com.xiaohui.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class Test02 {
    @Autowired
    private AccountService accountService;

    @Test
    public void test03(){
        int rews = accountService.transMoney(1, 2, 100);
        System.out.println(rews);

    }
}

整合junit5

依赖的jar

        <!--junit5单元测试-->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.7.0</version>
            <scope>test</scope>
        </dependency>

测试代码

package com.xiaohui.test;

import com.xiaohui.service.AccountService;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;

//使用ExtentWith和ContextConfiguration注解
//@ExtendWith(SpringExtension.class)
//@ContextConfiguration("classpath:applicationContext.xml")
//使用复合注解
@SpringJUnitConfig(locations = "classpath:applicationContext.xml")
public class Test03 {
    @Autowired
    private AccountService accountService;

    @Test
    public void test03(){
        int rews = accountService.transMoney(1, 2, 100);
        System.out.println(rews);

    }
}

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

By丶小辉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值