Spring 笔记

Spring Framework

仅供参考!!!

笔记作者:谢禹宏

邮箱:190464906@qq.com

文章目录

Spring Framework 简介

  • Spring:春天 --> 给软件行业带来了春天
  • Spring是一个开源的免费的框架
  • Spring是一个轻量级的、非入侵式的框架
  • 控制反转(IOC),面向切面编程(AOP)
  • 支持事务的处理,对框架整合的支持

IOC(控制反转)

IOC本质

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

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

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

Bean管理

  • Bean管理指的是两个操作

    1. Spring创建对象
    2. Spring注入属性
  • Bean管理操作有两种方式

    1. 基于xml配置文件方式实现
    2. 基于注解方式实现

环境搭建

Maven

<dependencies>
    <!-- 导入spring-webmvc包, Maven会自动导入mvc所需jar包 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.2.0.RELEASE</version>
    </dependency>
    <!-- Lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.10</version>
    </dependency>
    <!-- Junit -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
</dependencies>

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

创建实体类

package org.hong.pojo;

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

import java.util.Date;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private String name;
    private Integer age;
    private String sex;
}

XML创建对象

注意点: 创建对象时的构造器必须要存在,否则Spring获取不到,就会报错

无参构造

<!-- 
 bean: 代表一个对象
  id: 对象的唯一标识
  class: 对象的全类名
-->
<bean id="user1" class="org.hong.pojo.User"></bean>

带参构造

下标赋值 (不推荐)
<bean id="user2" class="org.hong.pojo.User">
    <constructor-arg index="0" value="hong"></constructor-arg>
    <constructor-arg index="1" value="16"></constructor-arg>
    <constructor-arg index="2" value="男"></constructor-arg>
</bean>
类型赋值 (不推荐)
<bean id="user3" class="org.hong.pojo.User">
    <constructor-arg type="java.lang.String" value="Tom"/>
    <constructor-arg type="java.lang.Integer" value="18"/>
    <constructor-arg type="java.lang.String" value="男"/>
</bean>
参数名赋值 (推荐)
<bean id="user4" class="org.hong.pojo.User">
    <constructor-arg name="name" value="Jerry"/>
    <constructor-arg name="age" value="16"/>
    <constructor-arg name="sex" value="男"/>
</bean>
简化版(c名称空间:constructor)
<!-- 这个就是c名称空间 -->
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<bean id="user5" class="org.hong.pojo.User" 
      c:name="Bob" 
      c:age="17" 
      c:sex="女"></bean>

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: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: 对象的唯一标识
		    class: 对象的全类名
    -->
    <bean id="user1" class="org.hong.pojo.User"></bean>

    <!--
        constructor-arg: 代表构造器中的一个参数
            index: 在参数列表中的索引
            value: 参数的值
     -->
    <bean id="user2" class="org.hong.pojo.User">
        <constructor-arg index="0" value="hong"></constructor-arg>
        <constructor-arg index="1" value="18"></constructor-arg>
        <constructor-arg index="2" value="男"></constructor-arg>
    </bean>

    <!--
        constructor-arg: 代表构造器中的一个参数
            type: 参数的类型
            value: 参数的值
     -->
    <bean id="user3" class="org.hong.pojo.User">
        <constructor-arg type="java.lang.String" value="Tom"/>
        <constructor-arg type="java.lang.Integer" value="18"/>
        <constructor-arg type="java.lang.String" value="男"/>
    </bean>

    <!--
        constructor-arg: 代表构造器中的一个参数
            name: 参数名
            value: 参数的值
     -->
    <bean id="user4" class="org.hong.pojo.User">
        <constructor-arg name="name" value="Jerry"/>
        <constructor-arg name="age" value="16"/>
        <constructor-arg name="sex" value="男"/>
    </bean>

    <!--
        c:xxx=yyy 等价于
            <constructor-arg name="xxx" value="yyy"/>
     -->
    <bean id="user5" class="org.hong.pojo.User"
          c:name="Bob"
          c:age="17"
          c:sex="女"></bean>
</beans>

测试用例

package org.hong.test;

import org.hong.pojo.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class IOCTest1 {
    @Test
    public void testNewInstance(){
        /**
         * 1.加载Spring配置文件, 参数为配置文件的路径
         *   classpath: 类路径下
         *   file: (不写盘符的情况)项目工作路径
         *
         * classpath: 只能获取类路径下的
         * file: 可以获取类路径以外的
         * 不理解就跳过
         */
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");

        // 空参创建
        // 调用 getBean(String id), 获取指定id的bean, 返回 Object
        Object user1 = context.getBean("user1");
        System.out.println(user1);

        // 下标创建
        // 调用 getBean(String id, Class arg), 获取指定id的bean, 且类型为arg, 返回arg类型
        User user2 = context.getBean("user2", User.class);
        System.out.println(user2);

        // 类型创建
        User user3 = context.getBean("user3", User.class);
        System.out.println(user3);

        // 参数名
        User user4 = context.getBean("user4", User.class);
        System.out.println(user4);

        // 参数名
        User user5 = context.getBean("user5", User.class);
        System.out.println(user5);
    }
}

XML注入属性

创建一个 applicationContext2.xml 配置文件

基本注入

构造器注入

在上面,就是 XML创建对象

Set注入
<!--
    property: 完成属性注入节点
        name: 属性名
        value: 属性值, 只能注入基本类型, 包装类, String
    注意: property是调用set方法来赋值的, 如果没有set方法会报错
 -->
<bean id="user1" class="org.hong.pojo.User">
    <property name="name" value="hong"/>
    <property name="age" value="18"/>
    <property name="sex" value="男"/>
</bean>
Set注入简化版(p名称空间:property)
<!-- 这个时p名称空间 -->
xmlns:c="http://www.springframework.org/schema/c"
<bean id="user2" class="org.hong.pojo.User" 
      p:name="Tom" 
      p:age="16" 
      p:sex="男"></bean>
特殊字符
<!-- 
	CDATA: 注入特殊字符
 		CDATA不能写在双引号之间
		因此我们在property节点里面添加了value节点
-->
<bean id="user3" class="org.hong.pojo.User">
    <property name="name">
        <!-- 要求: 注入一个<Jerry>, 有尖括号哦 -->
        <value><![CDATA[<Jerry>]]></value>
    </property>
    <property name="age" value="16"/>
    <property name="sex" value="女"/>
</bean>
注入 null
<bean id="user4" class="org.hong.pojo.User">
    <property name="name">
        <value>null</value>
    </property>
    <property name="age" value="16"/>
    <property name="sex" value="女"/>
</bean>
application.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--
        property: 完成属性注入节点
            name: 属性名
            value: 属性值
        注意: property是调用set方法来赋值的, 如果没有set方法会报错
     -->
    <bean id="user1" class="org.hong.pojo.User">
        <property name="name" value="hong"/>
        <property name="age" value="18"/>
        <property name="sex" value="男"/>
    </bean>

    <bean id="user2" class="org.hong.pojo.User"
          p:name="Tom"
          p:age="16"
          p:sex="男"></bean>

    <!--
        CDATA: 注入特殊字符
            CDATA不能写在双引号之间
            因此我们在property节点里面添加了value节点
     -->
    <bean id="user3" class="org.hong.pojo.User">
        <property name="name">
            <!-- 要求: 注意一个<Jerry>, 有尖括号哦 -->
            <value><![CDATA[<Jerry>]]></value>
        </property>
        <property name="age" value="16"/>
        <property name="sex" value="女"/>
    </bean>

    <bean id="user4" class="org.hong.pojo.User">
        <property name="name">
            <value>null</value>
        </property>
        <property name="age" value="16"/>
        <property name="sex" value="女"/>
    </bean>

</beans>
测试用例
package org.hong.test;

import org.hong.pojo.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class IOCTest2 {
    @Test
    public void testSet(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext2.xml");

        // set注入
        Object user1 = context.getBean("user1");
        System.out.println(user1);

        // p名称空间
        User user2 = context.getBean("user2", User.class);
        System.out.println(user2);

        // 注入特殊字符
        User user3 = context.getBean("user3", User.class);
        System.out.println(user3);

        // 注入null
        User user4 = context.getBean("user4", User.class);
        System.out.println(user4);
    }
}

对象注入

实体类
package org.hong.pojo;

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

@Data
@AllArgsConstructor
@NoArgsConstructor
// 员工类
public class Employee {
    private String name;
    private String sex;
    // 员工所属的部门
    private Dept dept;
}
package org.hong.pojo;

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

@Data
@AllArgsConstructor
@NoArgsConstructor
// 部门类
public class Dept {
    private String deptName;
}
内部注入 ( 嵌套bean )
Set注入
<bean id="employee1" class="org.hong.pojo.Employee">
    <property name="name" value="hong"/>
    <property name="sex" value="男"/>
    <!-- 在property节点中嵌套bean节点 -->
    <property name="dept">
        <bean class="org.hong.pojo.Dept">
            <property name="deptName" value="开发部"/>
        </bean>
    </property>
</bean>
构造器注入
<bean id="employee2" class="org.hong.pojo.Employee">
    <constructor-arg name="name" value="Bob"/>
    <constructor-arg name="sex" value="女"/>
    <!-- 在constructor-arg节点中嵌套bean节点 -->
    <constructor-arg name="dept">
        <bean class="org.hong.pojo.Dept">
            <constructor-arg name="deptName" value="测试部"/>
        </bean>
    </constructor-arg>
</bean>
外部注入 ( 对象的引用 )
Set注入
<bean id="dept1" class="org.hong.pojo.Dept">
    <property name="deptName" value="开发部"/>
</bean>
<bean id="employee3" class="org.hong.pojo.Employee">
    <property name="name" value="hong"/>
    <property name="sex" value="男"/>
    <!-- ref: 对一个对象进行引用 -->
    <property name="dept" ref="dept1"/>
</bean>
构造器注入
<bean id="dept2" class="org.hong.pojo.Dept">
    <constructor-arg name="deptName" value="测试部"/>
</bean>
<bean id="employee2" class="org.hong.pojo.Employee">
    <constructor-arg name="name" value="Bob"/>
    <constructor-arg name="sex" value="女"/>
    <!-- 在constructor-arg节点中嵌套bean节点 -->
    <constructor-arg name="dept" ref="dept2"/>
</bean>
application.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">

    <!-- 内部set注入 -->
    <bean id="employee1" class="org.hong.pojo.Employee">
        <property name="name" value="hong"/>
        <property name="sex" value="男"/>
        <!-- 在property节点中嵌套bean节点 -->
        <property name="dept">
            <bean class="org.hong.pojo.Dept">
                <property name="deptName" value="开发部"/>
            </bean>
        </property>
    </bean>
    
	<!-- 内部构造器注入 -->
    <bean id="employee2" class="org.hong.pojo.Employee">
        <constructor-arg name="name" value="Bob"/>
        <constructor-arg name="sex" value="女"/>
        <!-- 在constructor-arg节点中嵌套bean节点 -->
        <constructor-arg name="dept">
            <bean class="org.hong.pojo.Dept">
                <constructor-arg name="deptName" value="测试部"/>
            </bean>
        </constructor-arg>
    </bean>

    <!-- 外部set注入 -->
    <bean id="dept1" class="org.hong.pojo.Dept">
        <property name="deptName" value="开发部"/>
    </bean>
    <bean id="employee3" class="org.hong.pojo.Employee">
        <property name="name" value="hong"/>
        <property name="sex" value="男"/>
        <!-- ref: 对一个对象进行引用 -->
        <property name="dept" ref="dept1"/>
    </bean>

    <!-- 外部构造器注入 -->
    <bean id="dept2" class="org.hong.pojo.Dept">
        <constructor-arg name="deptName" value="测试部"/>
    </bean>
    <bean id="employee4" class="org.hong.pojo.Employee">
        <constructor-arg name="name" value="Bob"/>
        <constructor-arg name="sex" value="女"/>
        <!-- 在constructor-arg节点中嵌套bean节点 -->
        <constructor-arg name="dept" ref="dept2"/>
    </bean>

</beans>
测试用例
package org.hong.test;

import org.hong.pojo.Employee;
import org.hong.pojo.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class IOCTest3 {
    @Test
    public void testSet(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext3.xml");

        // 内部set注入
        Employee employee1 = context.getBean("employee1", Employee.class);
        System.out.println(employee1);

        // 内部构造器注入
        Employee employee2 = context.getBean("employee2", Employee.class);
        System.out.println(employee2);

        // 外部set注入
        Employee employee3 = context.getBean("employee3", Employee.class);
        System.out.println(employee3);

        // 外部构造器注入
        Employee employee4 = context.getBean("employee4", Employee.class);
        System.out.println(employee4);
    }
}

集合注入

实体类
package org.hong.pojo;

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

import java.util.List;
import java.util.Map;
import java.util.Set;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Bean {
    private String[] array;
    private List<String> list;
    private Set<String> set;
    private Map<String, String> map;
}

Array
<property name="array">
	<array>
		<value>Java</value>
		<value>MySql</value>
	</array>
</property>
List
<property name="list">
	<list>
		<value>张三</value>
		<value>李四</value>
	</list>
</property>
Set
<property name="set">
	<set>
		<value>Java</value>
		<value>MySql</value>
	</set>
</property>
Map
<property name="map">
	<map>
		<!-- 方式一 -->
		<entry key="JAVA" value="java"></entry>
		<!-- 方式二 -->
		<entry>
			<key>
				<value>PHP</value>
			</key>
			<value>php</value>
		</entry>
		<!-- 方式三: key或value为对象属性时使用 -->
		<entry key-ref="key" value-ref="value"></entry>
	</map>
</property>
提取集合
  1. 引入uitl名称空间

    <!-- 这时util名称空间 -->
    xmlns:util="http://www.springframework.org/schema/util"
    
  2. 使用util标签完成list集合注入提取

    <util:list id="bookList">
        <value>西游记</value>
        <value>水浒传</value>
        <value>三国演义</value>
    </util:list>
    
application.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: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"><!-- 别忘了这里也需要添加 -->

    <bean id="bean" class="org.hong.pojo.Bean">
        <property name="array">
            <array>
                <value>Java</value>
                <value>MySql</value>
            </array>
        </property>
        <property name="list">
            <list>
                <value>张三</value>
                <value>李四</value>
            </list>
        </property>
        <property name="set">
            <set>
                <value>Java</value>
                <value>MySql</value>
            </set>
        </property>
        <property name="map">
            <map>
                <!-- 方式一 -->
                <entry key="JAVA" value="java"></entry>
                <!-- 方式二 -->
                <entry>
                    <key>
                        <value>PHP</value>
                    </key>
                    <value>php</value>
                </entry>
                <!-- 方式三: key或value为对象属性时使用 -->
                <entry key-ref="key" value-ref="value"></entry>
            </map>
        </property>
        <property name="bookList" ref="bookList"/>
    </bean>
    <bean id="key" class="java.lang.String">
        <constructor-arg name="original" value="Spring"/>
    </bean>
    <bean id="value" class="java.lang.String">
        <constructor-arg name="original" value="spring"/>
    </bean>
    <util:list id="bookList">
        <value>西游记</value>
        <value>水浒传</value>
        <value>三国演义</value>
    </util:list>

</beans>
测试用例
package org.hong.test;

import org.hong.pojo.Bean;
import org.hong.pojo.Employee;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class IOCTest4 {
    @Test
    public void testSet(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext4.xml");

        Bean bean = context.getBean("bean", Bean.class);
        System.out.println(bean);
    }
}

外部属性文件(properties)

maven
<!-- Druid连接池 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.6</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
	<version>5.1.47</version>
</dependency>
创建 jdbc.properties
driverClass=oracle.jdbc.driver.OracleDriver
url=jdbc:oracle:thin://localhost:1521/orcl
username=scott
password=ccat
标签引入外部属性文件

添加 context 名称空间

<context:property-placeholder location="classpath:jdbc.properties"/>
使用
<!-- 使用${}插值表达式来获取properties文件中对应的数据 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="${driverClass}"></property>
    <property name="url" value="${url}"></property>
    <property name="username" value="${username}"></property>
    <property name="password" value="${password}"></property>
</bean>

SpEL 表达式

<bean class="entity.User" id="user3">
	<!-- 1、算数运算 -->
	<property name="money" value="#{2399.9*12}"></property>
 
    <!-- 2、引用其他bean的属性值 -->
    <property name="name" value="#{user2.name}"></property>

 
    <!-- 3、引用其他bean -->
    <property name="dept" value="${dept1}"></property>

 
	<!-- 4、调用静态方法
			UUID.randomUUID().toString();
			#{T(全类名).静态方法名(1,2)}
	 -->
	<property name="email" value="#{T(java.util.UUID).randomUUID().toString()}"></property>

	<!-- 5、调用非静态方法 -->
	<property name="age" value="#{user2.getAge()}"></property>

</bean>

注解创建对象

创建一个 applicationContext3.xml 配置文件

Spring创建对象提供的注解

  1. @Component 创建组件的注解
  2. @Service 创建 service 层的注解
  3. @Controller 创建 controller 层的注解
  4. @Repository 创建 dao层的注解

使用这4个注解默认 id 为类名首字母小写,可以设置 value 属性指定 id

注意:这4个注解默认调用空参构造器;如果类中只有一个构造器,则调用,参数来源是 IOC 容器;如果类中即没有无参构造且有多个有参构造,则会报错。

这4个注解都可以创建对象。不理解这些注解的意义别死磕,学到后面自然就懂了。

使用步骤

1. 创建类, 在类上面添加创建对象的注解
package org.hong.dao;

import org.springframework.stereotype.Repository;

// 1.在类上添加Spring提供的注解
@Repository
public class UserDao {}
2. 开启组件扫描

需要添加 context 名称空间

xmlns:context="http://www.springframework.org/schema/context"

http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
<?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"><!-- 不同于p,c名称空间, 还需要额外添加 -->
    
    <!--
 		context:component-scan: 表示开启Spring的组件扫描功能
			base-package: 指定扫描哪些包, 多个包使用逗号隔开
			包太多可以直接写多个包的根包, Spring会扫描包下面的所有类和所有包
	-->
    <context:component-scan base-package="org.hong"/>
</beans>

测试用例

package org.hong.test;

import org.hong.dao.UserDao;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class IOCAnnotationTest1 {
    @Test
    public void testAnnotation(){
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext5.xml");
        UserDao bean = context.getBean(UserDao.class);
        System.out.println(bean);
    }
}

注解注入属性

Spring注入属性提供的注解

@AutoWired

可以添加在 属性方法

  • Filed:根据属性的类型自动注入对象属性

  • Method:IOC 容器创建时自动运行这个方法,方法上的每一个参数都会自动注入

  • @AutoWired 标注的属性默认一定要装配上,装配不上就报错

    解决方案:在注解中添加属性required=false,装配不上就为null

@Qualifier

  • 配合@AutoWired使用,指定一个 id,让 @AutoWired 不使用属性名作为 id

@Value

  • 注入普通类型属性,基本类型、包装类、String类型

@AutoWired 运行原理

@AutoWired
private Book book;
1)先参照类型去容器总找到对应的组件; book = ioc.getBean(Book.class);
	1)找到一个: 装配
	2)没找到: 抛异常
	3)找到多个?
		1)按照变量名作为id继续匹配: book = ioc.getBean(Book.class, "book");
			1)匹配成功: 装配
			2)匹配失败: 报错

Java提供的注解 ( 了解 )

​ @Resource:可以根据类型注入,也可以根据名称注入

使用示例

开启组件扫描

这里就不具体写了,注解创建对象中有,自己去看。

@Autowired
创建一个 dao 类,使用 @Repository 注解创建对象
package org.hong.dao;

import org.springframework.stereotype.Repository;

@Repository
public class UserDao {
}
创建一个 service 接口
package org.hong.service;

public interface IUserService {}
创建一个 IUserService 接口的实现类
package org.hong.service.serviceimpl;

import org.hong.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl1 implements IUserService {
    // 在属性上添加@Autowired注解, Spring在帮我们创建对象时会从ICO容器中自动注入一个合适的对象
    @Autowired
    private UserDao userDao;
}
测试用例
package org.hong.test;

import org.hong.service.IUserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class IOCAnnotationTest1 {
    @Test
    public void testAnnotationAutowired(){
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext5.xml");
        IUserService bean = context.getBean(IUserService.class);
        System.out.println(bean);
    }
}
@Qualifier
再创建一个 IUserService 接口的实现类
package org.hong.service.serviceimpl;

import org.hong.dao.UserDao;
import org.hong.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;

@Service
public class UserServiceImpl2 implements IUserService {
    // 在属性上添加@Autowired注解, Spring在帮我们创建对象时会从ICO容器中自动注入一个合适的对象
    @Autowired
    private UserDao userDao;
}
创建 UserController 类
package org.hong.controller;

import org.hong.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class UserController {
    @Autowired
    private IUserService iUserService;
}
测试用例
@Test
public void testAnnotationQualifier(){
    ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext5.xml");
    UserController bean = context.getBean(UserController.class);
    System.out.println(bean);
}

运行测试,报错,原因:找到了两个 IUserService 类型的 beanSpring 不知道该装配哪一个。

修改UserController 类
package org.hong.controller;

import org.hong.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;

@Controller
public class UserController {
    @Autowired
    // 添加@Qualifier注解, 在进行装配时使用指定的id进行查找
    @Qualifier("userServiceImpl1")
    private IUserService iUserService;
}

再次运行测试用例,正常运行,装配成功。

@Value
直接赋值
package org.hong.pojo;

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

@Component
@Data
public class Student {
    @Value("hong")
    private String name;
    @Value("18")
    private Integer age;
    @Value("男")
    private String sex;
}
获取 properties 文件中的值
  1. 创建 student.properties 文件

    name=谢禹宏
    email=1904647906@qq.com
    address=湖南
    
  2. 引入外部属性文件

    <?xml version="1.0" encoding="UTF-8" ?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
    
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context.xsd">
        <context:component-scan base-package="org.hong"/>
        <!-- 
     		location: 指定properties文件的地址
    		file-encoding: 设定properties文件的编码, 设置为UTF-8可以解决中文乱码
    	-->
        <context:property-placeholder location="classpath:student.properties" file-encoding="UTF-8"/>
    </beans>
    
  3. @Value 获取值

    package org.hong.pojo;
    
    import lombok.Data;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Component;
    
    @Component
    @Data
    public class Person {
        @Value("${name}")
        private String name;
        @Value("${email}")
        private String email;
        @Value("${address}")
        private String address;
    }
    
测试用例
@Test
public void testAnnotationValue(){
    ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext5.xml");

    // 直接赋值
    Student student = context.getBean(Student.class);
    System.out.println(student);

    // 获取properties文件中的值
    Person person = context.getBean(Person.class);
    System.out.println(person);
}

工厂Bean

  • 什么是工厂Bean

    Spring 有两种类型的 Bean, 普通Bean和工厂Bean(FactoryBean)

    1. 普通Bean: 在配置文件中定义Bean类型就是返回类型
    2. 工厂Bean: 在配置文件中定义Bean类型可以和返回类型不一样
  • 工厂Bean的使用

    1. 创建类, 让这个类作为工厂Bean, 实现接口FactoryBean
    2. 实现接口中的方法, 在实现的方法中定义返回的bean类型

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

       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">
    <context:component-scan base-package="org.hong.bean"/>
</beans>

创建工厂 Bean

package org.hong.bean;

import org.hong.pojo.User;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.stereotype.Component;

@Component
public class MyFactoryBean implements FactoryBean<User> {
    /**
     * 返回的对象是否是单例
     * @return
     */
    @Override
    public boolean isSingleton() {
        return true;
    }

    /**
     * 返回的真实对象
     * @return
     * @throws Exception
     */
    @Override
    public User getObject() throws Exception {
        return new User("hong", 18, "男");
    }

    /**
     * 返回真实对象的Class
     * @return
     */
    @Override
    public Class<?> getObjectType() {
        return User.class;
    }
}

测试用例

package org.hong.test;

import org.hong.pojo.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class FactoryBeanTest {
    @Test
    public void testFactoryBean(){
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext6.xml");
        User user = context.getBean(User.class);
        System.out.println(user);
    }
}

Bean的作用域

  • 在Spring中, 默认情况下, bean是单实例对象
    • 单实例的对象会在容器启动时立即创建,并存放在 IOC 容器中,由 Spring 管理
    • 多实例的对象在容器启动时不会创建,在调用 getBean() 方法时创建,不会存放 IOC 容器中

设置bean为多实例

实体类
package org.hong.pojo;

import lombok.Data;

@Data
public class Book {
    private String name;
    private String author;
    private Double price;

    public Book(){
        System.out.println("book被创建了");
    }
}
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">
    <!--
        scope: 设置bean实例的作用域
            prototype: 多实例
            singleton: 单实例
     -->
    <bean id="book1" class="org.hong.pojo.Book" scope="prototype">
        <property name="name" value="西游记"/>
        <property name="author" value="吴承恩"/>
        <property name="price" value="22.5"/>
    </bean>
</beans>
注解方式
package org.hong.pojo;

import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Data
@Component("book2")
@Scope(value = "prototype")
public class Book {
    @Value("水浒传")
    private String name;
    @Value("施耐庵")
    private String author;
    @Value("22.5")
    private Double price;

    public Book(){
        System.out.println("book被创建了");
    }
}

测试用例

package org.hong.test;

import org.hong.pojo.Book;
import org.hong.pojo.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class ScopeTest {
    @Test
    public void testFactoryBeanXml(){
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext7.xml");
        System.out.println("容器启动成功");

        Book book1 = context.getBean("book1", Book.class);
        Book book2 = context.getBean("book1", Book.class);
        System.out.println(book1);
        System.out.println(book1 == book2);
    }

    @Test
    public void testFactoryBeanAnnotation(){
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext7.xml");
        System.out.println("容器启动成功");

        Book book1 = context.getBean("book2", Book.class);
        Book book2 = context.getBean("book2", Book.class);
        System.out.println(book1);
        System.out.println(book1 == book2);
    }
}

自己观察控制台输出

Bean的生命周期

Bean 代码

package org.hong.pojo;

import lombok.Data;

@Data
public class Teacher {
    private String name;

    public Teacher(){
        System.out.println("1.构造器");
    }

    public void setName(String name) {
        this.name = name;
        System.out.println("2.注入属性");
    }

    /**
     * 初始化方法名随意
     */
    public void initMethod(){
        System.out.println("4.初始化");
    }
    /**
     * 销毁方法名随意
     * 注意: 只有单实例对象才会拥有销毁方法, 因为单实例对象在IOC容器中, Spring可以感知到bean实例的销毁
     *       多实例对象不在IOC容器中, Spring无法感知到bean实例的销毁, 因此无法触发
     */
    public void destroyMethod(){
        System.out.println("7、销毁");
    }
}

处理器代码

package org.hong.pojo;

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

/**
 * 创建的类实现BeanPostProcessor接口就成为了Bean初始化的处理器
 * 在Bean初始化前后调用
 */
public class MyBeanPost implements BeanPostProcessor {
    /**
     * 初始化之前
     * @param bean 当前进入处理器的bean实例本身
     * @param beanName 当前进入处理器的bean实例的id
     * @return bean实例只是经过处理器, 我们可以在处理器进行一些操作, 但是记得将bean实例返回给Spring, 除非你不想某些bean实例创建, 就返回null
     * @throws BeansException
     */
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("3.初始化之前");
        return bean;
    }

    /**
     * 初始化之后, 参数列表和返回值说明于postProcessBeforeInitialization方法一致
     * @param bean
     * @param beanName
     * @return
     * @throws BeansException
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("5.初始化之后");
        return bean;
    }
}

测试用例

package org.hong.test;

import org.hong.pojo.Teacher;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class LifeCycleTest {
    @Test
    public void testFactoryBean(){
        // 注意: ApplicationContext接口没有close()方法
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext8.xml");
        Teacher teacher = context.getBean(Teacher.class);
        System.out.println(teacher);
        System.out.println("6.获取Bean");
        // 手动销毁容器, 容器中的bean也被销毁
        context.close();
    }
}

AOP(面向切面)

什么是AOP

  1. 面向切面编程, 利用AOP可以对业务逻辑的各个部分进行隔离, 从而使得业务逻辑各部分的耦合度降低, 提高程序的重用性。

  2. 不修改源代码方式, 在主干功能中添加新功能, 利用的是动态代理模式

    AOP只是增强了方法, 使用还是与Bean管理一样

AOP操作术语

  1. 连接点

    类中可以被增强的方法

  2. 切入点

    实际被增强的方法

  3. 通知(增强)

    实际增强的逻辑部分

    通知的类型:

    ​ (1) 前置通知 @Before

    ​ (2) 后置通知 @AfterReturning

    ​ (3) 环绕通知 @Around

    ​ (4) 异常通知 @AfterThrowing

    ​ (5) 最终通知 @After

    通知的执行顺序

    ​ 正常执行: @Before > @After > @AfterReturning

    ​ 异常执行: @Before > @After > @AfterThrowing

    try{
        @Before
        method.invoke(obj, args);
        @AfterReturning
    } catch (Exception e){
        @AfterThrowing
    } finally {
        @After
    }
    
    Spring 笔记
  4. 切面

    把通知应用到切入点的过程

AOP前置知识

  • Spring框架一般都是基于AspectJ实现AOP操作

    AspectJ不是Spring组成部分, 它是独立的AOP框架, 一般把AspectJ和Spring框架一起使用, 进行AOP操作

    <!-- Maven配置 -->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.4</version>
    </dependency>
    
  • 切入点表达式

    1. 切入点表达式的作用:

      让AspectJ知道要对哪些方法进行增强

    2. 语法结构:

      execution([权限修饰符][返回类型][类的全路径][方法名]([参数列表]))
          //访问修饰符: 
      		指定访问修饰符
      		不写默认public, 且不能写 *
      	//返回类型: 
      		void			无返回	
      		*				所有返回值类型	
      		String			只能是String类型
      	//类的全路径:
      		*				只有一个*表示任意包下, 任意层数	
      		package.User	package包下的User类
      		package.*		package包下的所有类, 不包含子包下的类
      		package.*.util	package包下的任意子包的util类, 不包含后代包
      	//方法名:
      		add			add方法
      		proxy*		以proxy开头的方法
      		*.*			任意包下的任意方法
      	//参数列表: 
      		(int)		参数只能有1个, 并且是int类型
      		(int, *)	参数有2个, 第一个int, 第二个任意
      		(..)		参数个数和参数类型任意
      	//通配符: 
       		*
      			匹配一个或多个任意字符
      			匹配一层子包
      			匹配任意一个参数
        		..
      			匹配任意数量和类型的参数
      			匹配任意多层包
      //最模糊的: execution(* * (..)) 任意返回值,任意包的任意类,参数任意
      //最精确的: execution(public void package.User.add(int, int))
          
      //&&、||、!
      	execution1(...) && execution2(...)	同时匹配execution1和execution2
          execution1(...) || execution2(...)	匹配execution1或execution2
          !execution(...) 	不匹配execution
      

AOP操作(AspectJ注解)

创建 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">
    <!-- 导入context名称空间 -->
    <!-- 1.组件扫描 -->
    <context:component-scan base-package="org.hong"/>

    <!-- 导入aop名称空间 -->
    <!-- 2.开启自动生成代理对象 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

创建被增强类

package org.hong.pojo;

import org.springframework.stereotype.Component;

@Component // 创建被增强类, 将此类交给Spring管理, Spring才能帮你进行方法增强
public class Person {
    public String show(){
        System.out.println("show....");
        // 制造异常
        // int num = 10 / 0;
        return "Hello APO";
    }
}

创建增强类

package org.hong.proxy;

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

@Component // 需要增强类交给Spring管理
@Aspect // 标记此类是增强类
public class PersonProxy {
    // 前置通知 方法之前执行
    @Before(value = "execution(public * org.hong.pojo.Person.show(..))")
    public void before() {
        System.out.println("before......");
    }

    // 后置通知(返回通知)返回结果后执行, 有异常不执行
    @AfterReturning(value = "execution(* org.hong.pojo.Person.show(..))")
    public void afterReturning() {
        System.out.println("afterReturning ......");
    }

    // 最终通知 方法之后执行, 有异常也执行
    @After(value = "execution(* org.hong.pojo.Person.show(..))")
    public void after() {
        System.out.println("after......");
    }

    // 异常通知 方法有异常执行
    @AfterThrowing(value = "execution(* org.hong.pojo.Person.show(..))")
    public void afterThrowing() {
        System.out.println("afterThrowing......");
    }

    // 环绕通知 方法之前之后都执行
    @Around(value = "execution(* org.hong.pojo.Person.show(..))")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("环绕之前......");
        // 执行被增强方法
        proceedingJoinPoint.proceed();
        System.out.println("环绕之后......");
    }

}

在通知中获取连接点的信息

@Before(value = "execution(public * org.hong.pojo.Person.show(..))")
public void before(JoinPoint joinPoint){
	 // 被增强类的对象
     Object source = joinPoint.getThis();
     // 调用方法时传入的参数
     Object[] args = joinPoint.getArgs();
}

测试用例

package org.hong.test;

import org.hong.pojo.Person;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AOPTest {
    @Test
    public void annoAOPTest(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        Person person = context.getBean(Person.class);
        // 返回的是一个代理对象
        System.out.println(person.getClass());
        person.show();
    }
}

AOP操作(AspectJ配置文件)

创建被增强类

package org.hong.pojo;

public class User {
    public void show(){
        System.out.println("show...");
    }
}

创建增强类

package org.hong.proxy;

public class UserProxy {
    public void before(){
        System.out.println("before......");
    }
}

创建 applicationContext.xml 文件

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

       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 导入aop名称空间 -->
    <!-- 1.开启自动生成代理对象 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

    <!-- 2.创建增强类和被增强类对象 -->
    <bean id="user" class="org.hong.pojo.User"></bean>
    <bean id="userProxy" class="org.hong.proxy.UserProxy"></bean>

    <!-- 3.配置aop增强 -->
    <aop:config>
        <!-- 4.配置切入点 -->
        <aop:pointcut id="aop" expression="execution(* org.hong.pojo.User.show(..))"/>
        <!--
            5.配置切面
                ref: 使用哪个bean作为增强类
         -->
        <aop:aspect ref="userProxy">
            <!--
                aop:xxxx: 配置对应的通知
                    method: 配置该通知方法
                    pointcut-ref: 配置通知切入到哪里
             -->
            <aop:before method="before" pointcut-ref="aop"/>
        </aop:aspect>
    </aop:config>

</beans>

测试用例

@Test
public void xmlAOPTest(){
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext2.xml");
    User user = context.getBean(User.class);
    // 返回的是一个代理对象
    System.out.println(user.getClass());
    user.show();
}

声明式事务

Spring 进行声明式事务管理, 底层使用的是 AOP,因此是低侵入式的,只需要简单的配置就可以了,不需要更改代码

环境搭建

SQL

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for `user`
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(30) DEFAULT NULL,
  `pwd` varchar(30) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('1', 'Tom', '123');
INSERT INTO `user` VALUES ('2', 'jerry', '123');
INSERT INTO `user` VALUES ('3', 'hong', '12345');

Maven

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.2.0.RELEASE</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.4</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.49</version>
</dependency>

jdbc.properties 文件

driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?userSSL=true&useUnicode=true&characterEncoding=UTF-8
#在Spring的环境变量中有一个username的属性, 不能与Spring的发生冲突, Spring的优先级比我们的高
user=root
password=1234

注解式

创建 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:tx="http://www.springframework.org/schema/tx"
       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

       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!-- 组件扫描 -->
    <context:component-scan base-package="org.hong"/>
    <!-- 引入jdbc.properties配置文件 -->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <!-- 1.开启事务注解, 指定事务管理器 -->
    <tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>

    <!-- 2、DataSource: 配置数据源 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${driverClassName}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${user}"/>
        <property name="password" value="${password}"/>
    </bean>

    <!-- 3.配置JdbcTemplate -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 4.配置事务管理器 -->
    <bean id="dataSourceTransactionManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- 将连接池交给事务管理器, Spring才能对事务进行控制 -->
        <property name="dataSource" ref="dataSource"/>
    </bean>

</beans>

创建实体类

package org.hong.pojo;

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

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private Integer id;
    private String name;
    private String pwd;
}

创建 UserDao

package org.hong.dao;

import org.hong.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository
public class UserDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    public boolean save(User user){
        String sql = "insert into user(name, pwd) values(?, ?)";
        int result = jdbcTemplate.update(sql, user.getName(), user.getPwd());
        return result > 0 ? true : false;
    }

    public boolean delete(int id){
        String sql = "delete from user where id = ?";
        int result = jdbcTemplate.update(sql, id);
        return result > 0 ? true : false;
    }
}

创建 UserService

package org.hong.service;

import org.hong.dao.UserDao;
import org.hong.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;

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

    @Transactional
    public boolean action(){
        boolean save = userDao.save(new User(null, "谢禹宏", "20210416"));
        boolean delete = userDao.delete(7);

        // 手动制造异常
        int num = 10 / 0;

        // 要求: 添加成功并且至少删除一条数据才算正确操作
        if(save && delete){
            return true;
        }else{
            // 如果没有达到我们预想的要求, 我们可以手动回滚事务
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            return true;
        }
    }
}

测试用例

package org.hong.test;

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

public class TransactionTest {
    @Test
    public void testTransactionAnno(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = context.getBean(UserService.class);
        boolean action = userService.action();
        System.out.println(action);
    }
}

运行结果

在我们手动制造异常之前运行了 savedelete 方法,如果没有事务,数据库将新增一条数据同时被删除一条数据,由于我们配置了 Spring 的事务,被 @Transactional 注解标注的方法在运行时如果发生异常将自动回滚事务,因此数据库中并没有发生任何改变!!!看自己的数据库,我懒得截图了。

@Transactional

可以被标注在 方法 上!!!

类:这个类里面所有的方法都添加事务

方法:为方法添加事务

@Transactional 的参数
1、propagation: 事务传播行为
	某一个被事务传播行为修饰的方法被嵌套进另一个方法的时事务如何传播。
		REQUIRED: 默认
			外围方法未开启事务的情况下Propagation.REQUIRED修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。
			外围方法开启事务的情况下Propagation.REQUIRED修饰的内部方法会加入到外围方法的事务中,所有Propagation.REQUIRED修饰的内部方法和外围方法均属于同一事务,只要一个方法回滚,整个事务均回滚。
		REQUIRES_NEW:
			外围方法未开启事务的情况下Propagation.REQUIRES_NEW修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。
			在外围方法开启事务的情况下Propagation.REQUIRES_NEW修饰的内部方法依然会单独开启独立事务,且与外部方法事务也独立,内部方法之间、内部方法和外部方法事务均相互独立,互不干扰。

2、isolation: 事务隔离级别
	隔离级别					脏读	不可重复读	虚拟读取
	读未提交 READ_UNCOMMITTED	是		是		是
	读已提交READ_COMMITTED		否		是		是
	可重复读REPEATABLE_READ		否		否		是
	可序列化SERIALIZABLE		否		否		否

3、timeout: 超时时间
	事务需要在一定的时间内进行提交, 如果不提交就自动回滚
	Spring默认值为-1(不超时), 设置时间以秒为单位

4、readOnly: 是否只读
	readOnly默认值fals, 设置为true后, JDBC驱动程序和数据库就有可能根据这种情况对该事务进行一些特定的优化,比方说不安排相应的数据库锁,以减轻事务对数据库的压力。
	但是你非要在“只读事务”里面修改数据,也并非不可以,只不过对于数据一致性的保护不像“读写事务”那样保险而已。 
	“只读事务”: 最好只做select操作

5、rollbackFor: 回滚
	出现哪些异常进行回滚

6、onRollbackFor: 不回滚
	出现哪些异常不进行回滚

XML 声明式 ( 了解 )

创建 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: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
       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/tx
       http://www.springframework.org/schema/tx/spring-tx.xsd

       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 组件扫描 -->
    <context:component-scan base-package="org.hong"/>
    <!-- 引入jdbc.properties配置文件 -->
    <context:property-placeholder location="classpath:jdbc.properties"/>

    <!-- 2、DataSource: 配置数据源 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${driverClassName}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${user}"/>
        <property name="password" value="${password}"/>
    </bean>

    <!-- 3.配置JdbcTemplate -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 4.配置事务管理器 -->
    <bean id="dataSourceTransactionManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- 将连接池交给事务管理器, Spring才能对事务进行控制 -->
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- tx:advice: 配置事务通知
            id:给事务通知起一个唯一标识。
            transaction-manager:给事务通知提供一个事务管理器引用。
     -->
    <tx:advice id="txadvice" transaction-manager="dataSourceTransactionManager">
        <!-- 配置事务参数 -->
        <tx:attributes>
            <!--
                tx:method: 配置事务的属性, 于@Transactional注解的属性一样
                    name: 配置规则, 方法名匹配才启用这个标签配置的属性
                    *: 通配符
                    单个*: 所有方法都适用
                    save*: save开头的方法
                tx:method可以配置多个
             -->
            <tx:method name="*" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>
    <!-- 配置切入点 -->
    <aop:config>
        <aop:pointcut expression="execution(* org.hong.service.UserService2.*(..))" id="pt"/>
        <!-- 配置切面, 建立切入点表达式和事务通知的对应关系 -->
        <aop:advisor advice-ref="txadvice" pointcut-ref="pt"/>
    </aop:config>

</beans>

创建 UserService2

package org.hong.service;

import org.hong.dao.UserDao;
import org.hong.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;

@Service
public class UserService2 {
    @Autowired
    private UserDao userDao;

    // 跟UserService差不多, 就是删除了@Transactional注解
    public boolean action(){
        boolean save = userDao.save(new User(null,"hong", "123"));
        boolean delete = userDao.delete(7);

        // 手动制造异常
        int num = 10 / 0;

        // 要求: 添加成功并且至少删除一条数据才算正确操作
        if(save && delete){
            return true;
        }else{
            // 如果没有达到我们预想的要求, 我们可以手动回滚事务
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            return true;
        }
    }
}

测试用例

@Test
public void testTransactionXML(){
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext2.xml");
    UserService2 userService = context.getBean(UserService2.class);
    boolean action = userService.action();
    System.out.println(action);
}

运行结果

跟注解式声明一样,就不写了,看自己的数据库变化,我懒得截图。

上一篇:阿里云 Centos 7 部署Tableau server2021.4.2环境(log4j漏洞修复版)


下一篇:网关_订单