Spring 复习笔记

文章目录

Spring IoC / DI

Spring IoC / DI 核心概念

Spring 组件管理概念


Spring 框架可管理组件,替代程序员的 new 对象及属性赋值操作,其管理动作包括组件对象实例化、属性赋值、对象间引用及存活周期管理等。我们通过编写元数据(配置文件)指明需管理的类组件及其关系。组件是应用中可重用的 Java 对象,组件必是对象,对象未必是组件。总之,Spring 作为组件容器,创建、管理和存储组件,减轻编码压力,使我们专注业务编写。

Spring IoC / DI 概念


  • IoC容器
    Spring IoC 容器作为核心组件管理容器,承担着实例化、配置以及组装 bean(组件)的职责。它依据所读取的配置元数据,获取对组件进行实例化、配置和组装的具体指令,从而实现对组件的有效管理和协调,以满足应用程序的运行需求。简单来说就是需要 DI 注入 的类就放进容器里面
  • IoC(Inversion of Control)控制反转
    IoC 主要针对对象创建与调用的控制权,当应用程序需用某对象时,不再自行创建,改由 IoC 容器负责,从而使控制权从应用程序转至 IoC 容器,实现 "反转"。其多通过依赖查找实现,即 IoC 容器负责维护并创建应用程序所需对象。
  • DI (Dependency Injection) 依赖注入
    DI 是在组件间传递依赖时,于容器内处理依赖关系,避免应用代码硬编码依赖,实现解耦。Spring 中,DI 可通过 XML 配置或注解达成,有构造函数、Setter 方法和接口三种注入形式。比如说:注解方式。直接给个注解它就实例化了对象。

Spring Ioc 容器具体接口和实现类


Spring Ioc 容器接口

BeanFactory 是 Spring IoC 容器标准化超接口 ,提供高级配置机制管理对象。能够管理任何类型的对象。ApplicationContext 是其子接口 ,扩展了与 AOP 集成、消息资源处理及特定应用(如 WebApplicationContext)相关功能。总之,BeanFactory 有基础配置和功能,ApplicationContext 增添企业级功能,是完整超集。

ApplicationContext 容器实现类

类型名 简介
ClassPathXmlApplicationContext 通过读取类路径下的 XML 格式的配置文件创建 IOC 容器对象
FileSystemXmlApplicationContext 通过文件系统路径读取 XML 格式的配置文件创建 IOC 容器对象
AnnotationConfigApplicationContext 通过读取Java配置类创建 IOC 容器对象
WebApplicationContext 专门为 Web 应用准备,基于 Web 环境创建 IOC 容器对象,并将对象引入存入 ServletContext 域中。

Spring Ioc 的管理方式


Spring框架提供了多种配置方式:XML配置方式,注解方式,Java配置类方式

  • XML配置方式是Spring框架最早的配置方式之一,通过在XML文件中定义Bean及其依赖关系、Bean的作用域等信息,让Spring IoC容器来管理Bean之间的依赖关系。
  • 注解方式 :*从Spring 2.5版本开始提供支持,可以通过在Bean类上使用注解来代替XML配置文件中的配置信息。通过在Bean类上加上相应的注解(如@Component, @Service, @Autowired等),将Bean注册到Spring IoC容器中,这样Spring IoC容器就可以管理这些Bean之间的依赖关系。
  • Java配置类方式 :*从Spring 3.0版本开始提供支持,通过Java类来定义Bean、Bean之间的依赖关系和配置信息,从而代替XML配置文件的方式。Java配置类是一种使用Java编写配置信息的方式,通过@Configuration、@Bean等注解来实现Bean和依赖关系的配置。
    当前开发以 配置类 + 注解方式 为主

基于 XML 方式管理 Bean

Spring IoC/ / DI 实现步骤

第一步:导入依赖配置元数据

配置元数据,既是编写交给Spring IoC容器管理组件的信息。

xml 复制代码
<dependencies>
      <!--spring context依赖-->
      <!--当你引入SpringContext依赖之后,表示将Spring的基础依赖引入了-->
      <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-context</artifactId>
          <version>6.0.6</version>
      </dependency>
</dependencies> 
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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 定义 bean 实例 -->
    <bean id="beanId1" class="fully.qualified.ClassName1">  
        <!-- 在此处配置该 bean 的协作对象及其他相关设置 -->
    </bean>

    <bean id="beanId2" class="fully.qualified.ClassName2">
        <!-- 同样在此配置其协作与设置信息 -->
    </bean>
    <!-- 可继续添加更多 bean 定义 -->
</beans>

Spring IoC 容器管理一个或多个组件。这些 组件是使用你提供给容器的配置元数据(例如,以 XML <bean/> 定义的形式)创建的。

  • <bean /> 标签就相当于对组件信息进行声明。
  • id 属性是标识单个 Bean 定义的字符串。也就是别名
  • class 属性定义 Bean 的类型并使用完全限定的类名。
第二步:实例化 IoC 容器

提供给 ApplicationContext 构造函数的位置路径是资源字符串地址,允许容器从各种外部资源(如本地文件系统、Java CLASSPATH 等)加载配置元数据。
我们应该选择一个合适的容器实现类,进行IoC容器的实例化工作:
注意:如果是没合并状态需要手动 refresh

第一种方式

java 复制代码
//实例化ioc容器,读取外部配置文件,最终会在容器内进行ioc和di动作 这一步就已经把 IoC 容器中的类全部创建出来了
ApplicationContext context = 
           new ClassPathXmlApplicationContext("services.xml", "daos.xml");

第二种方式

第一种相当于这两步合并了: 如果使用这种方式需要手动 refresh 创建 bean 对象

java 复制代码
// 使用ClassPathXmlApplicationContext创建Spring的IOC容器,它能管理bean。
ClassPathXmlApplicationContext applicationContext =
                new ClassPathXmlApplicationContext();
// 设置配置文件位置,告诉容器要加载哪个XML配置文件来创建bean等信息。
applicationContext.setConfigLocation("services.xml,daos.xml");
 // 触发容器进行IOC和DI操作,让容器按配置文件初始化并准备好可用的bean对象。
applicationContext.refresh();
第三步:获取 Bean(组件)

ApplicationContext 是一个高级工厂的接口,能够维护不同 bean 及其依赖项的注册表。通过使用方法 T getBean(String name, Class requiredType) ,您可以检索 bean 的实例。

注意:获取Bean组件默认都是获取同一个,也就是单例的

第一种方式

直接根据 beanId 获取即可 返回值类型是Object 需要强转不推荐

java 复制代码
Object happyConponent = applicationContext.getBean("happyComponent");

第二种方式

根据 beanId 同时定制 bean 的类型 Class

java 复制代码
HappyComponent happyComponent1 = applicationContext.getBean("happyComponent", HappyComponent.class);

第三种方式

直接根据类型获取

.
条件1:同一个类型, 在 ioc 容器中只能有一个 bean!,如果 ioC 容器中存在多个同类型的 Bean 会出现: NoUniqueBeanFinitionException
条件2:ioc的配置一定是实现类, 但是可以通过接口类型获取触发多态

.
原理: 根据类型来获取bean时,在满足bean唯一性的前提下,其实只是看:『对象 instanceof 指定的类型』的返回结果,只要返回的是true就可以认定为和类型匹配,能够获取到。

java 复制代码
//A 是接口 HappyComponent 是实现类
A bean = applicationContext.getBean(A.class);
bean.doWork();

图解

配置 IoC

基于无参构造函数配置 IoC

通过构造函数方法创建一个 bean(组件对象) 时,所有普通类都可以由 Spring 使用并与之兼容。也就是说,正在开发的类不需要实现任何特定的接口或以特定的方式进行编码。只需指定 Bean 类信息就足够了。但是,默认情况下,我们需要一个默认(空)构造函数。简单来说:就是一个类有个无参构造就行

  • 第一步:准备 Bean 组件类 NonConstructorComponent
java 复制代码
public class NonConstructorComponent {
    public NonConstructorComponent() {
        System.out.println("NonConstructorComponent work !!!");
    }
}
  • 第二步:配置元数据 spring-bean-01 [ 把 NonConstructorComponent 放入 IoC 容器 ]
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">

    <!--无参构造方法实例化-->
    <bean id="nonConstructorComponent" class="com.mangfu.NonConstructorComponent"/>
</beans>
  • bean标签通过配置bean标签告诉IOC容器需要创建对象的组件信息
  • id属性bean的唯一标识,方便后期获取Bean!
  • class属性组件类的全限定符!
  • 注意要求当前组件类必须包含无参数构造函数!
  • 第三步:测试数据
java 复制代码
 //1. 创建 ioc 容器对象
        // 使用ClassPathXmlApplicationContext创建Spring的IOC容器,它能管理bean。
        ClassPathXmlApplicationContext applicationContext =
                new ClassPathXmlApplicationContext("spring-bean-01.xml");
       
 //2. 读取 ioc 容器组件
        NonConstructorComponent nonConstructorComponent = 	applicationContext.getBean(NonConstructorComponent.class);
 
 //3. 输出 NonConstructorComponent Work !!! 
 //       com.mangfu.NonConstructorComponent@14ec4505       
        System.out.println(nonConstructorComponent);    
基于静态工厂方法实例配置 IoC

可以简单理解为静态方法返回对象

  • 第一步:创建 Bean 组件类 UserComponent
java 复制代码
public class UserComponent {
}
  • 第二步:创建静态工厂类 StaticGetUserComponent
java 复制代码
public class StaticGetUserComponent {
    public static UserComponent getUserComponent() {
        System.out.println("UserComponent work !!!");
        return new UserComponent();
    }
}
  • 第三步:配置元数据 spring-bean-01 [ 把 UserComponent 加入 IoC 容器 ]
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">

    <!--无参构造方法实例化-->
    <bean id="nonConstructorComponent" class="com.mangfu.NonConstructorComponent"/>

    <!--静态工厂方法实例化: IOC 静态工厂模式可以理解为通过静态方法返回 bean 对象,-->
    <bean id="staticGetUserComponent" class="com.mangfu.StaticGetUserComponent" rfactory-method="getUserComponent"/>
</beans>
  • class属性:指定工厂类的全限定符!
  • factory-method: 指定静态工厂方法,注意,该方法必须是static方法。
  • 第四步:测试数据
java 复制代码
public class ComponentTest {
    public static void main(String[] args) {
        //1. 创建 ioc 容器对象
        // 使用ClassPathXmlApplicationContext创建Spring的IOC容器,它能管理bean。
        ClassPathXmlApplicationContext applicationContext =
                new ClassPathXmlApplicationContext("spring-bean-01.xml");


        //2. 读取 ioc 容器组件
        UserComponent userComponent = applicationContext.getBean(UserComponent.class);
        System.out.println(userComponent);
    }
}
基于实例工厂方法配置 IoC

可以简单理解为实例方法返回对象

  • 第一步:创建 Bean 组件类 AnimalComponent
java 复制代码
public class AnimalComponent {
}
  • 第二步:创建实例工厂类 NonStaticGetAnimalComponetn
java 复制代码
public class NonStaticGetAnimalComponetn {
    public AnimalComponent getAnimalComponent() {
        System.out.println("AnimalComponent work !!!");
        return new AnimalComponent();
    }
}
  • 第三步:配置元数据 spring-bean-01 [ 把 AnimalComponent 加入 IoC 容器 ]
xml 复制代码
   <!--实例工厂方法实例化: IOC 实例工厂模式可以理解为通过实例方法返回 bean 对象,-->
    <!--将工厂类进行 ioc 配置-->
    <bean id="getAnimalComponent" class="com.mangfu.NonStaticGetAnimalComponetn"/>
    <!--根据工厂对象的实例工厂方法进行实例化组件对象-->
    <bean id="animalComponent" factory-bean="getAnimalComponent" factory-method="getAnimalComponent"/>

    <bean id="happyComponent" class="com.mangfu.HappyComponent"/>
  • factory-bean属性:指定当前容器中工厂Bean 的名称.
  • factory-method: 指定实例工厂方法名。注意,实例方法必须是非static的!
    .
    这里必须把工厂类加入 IoC 容器而 静态工厂不用。是因为静态工厂可以直接通过类名调用。而实例工厂必须先 new 出来才能用
  • 第四步:测试数据
java 复制代码
public class ComponentTest {
    public static void main(String[] args) {
        //1. 创建 ioc 容器对象
        // 使用ClassPathXmlApplicationContext创建Spring的IOC容器,它能管理bean。
        ClassPathXmlApplicationContext applicationContext =
                new ClassPathXmlApplicationContext("spring-bean-01.xml");
                
        AnimalComponent animalComponent = applicationContext.getBean(AnimalComponent.class);
        System.out.println(animalComponent);
        AnimalComponent animalComponent2 = applicationContext.getBean(AnimalComponent.class);
        System.out.println(animalComponent2);
    }
IoC 容器中的 BeanName 问题

  • 优先查找 id:如果定义了 id 属性,则首先使用 id 来查找Bean。
  • 其次查找 name:如果没有定义 id 或者 id 查找失败,则会尝试使用 name 属性来查找Bean。
  • 没有 id 和 name:按照首字母小写的类名
xml 复制代码
<bean id="myBean" class="com.example.MyClass"/>
<!-- 或者 -->
<bean name="myBean" class="com.example.MyClass"/>
<!-- 或者同时定义 id 和 name -->
<bean id="myBean" name="anotherName" class="com.example.MyClass"/>

依赖注入 DI

  • ref :注入对象引用
  • value : 注入普通类型
基于构造函数的依赖注入(单个构造参数)

  • 第一步:创建 Bean 组件 UserDao, UserService
java 复制代码
//UserDao Bean
public class UserDao {
}

//UserService Bean 。要把 UserDao 注入 UserService
public class UserService {
    
    private UserDao userDao;
    
    public UserService(UserDao userDao) {
        System.out.println("注入");
        this.userDao = userDao;
    }

    public UserDao getUserDao() {
        return userDao;
    }
}

}
  • 第二步:编写元数据 spring-bean-02
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">

    <!--被引用的UserDao bean-->
    <bean id="userDao" class="com.mangfu2.UserDao"/>
    
    <!--需要引用 bean 的 bean-->
    <bean id="userService" class="com.mangfu2.UserService">
        <!--引用userDao bean-->
        <constructor-arg ref="userDao"/>
    </bean>
</beans>
  • constructor-arg标签:可以引用构造参数 ref 引用其他bean的标识。和 value 注入普通属性
  • 第三步:测试
java 复制代码
 @Test
    public void test3() {
        ClassPathXmlApplicationContext applicationContext =
                new ClassPathXmlApplicationContext("spring-bean-02.xml");

		//获取 UserService Bean
        UserService userService = applicationContext.getBean(UserService.class);
		//get方法获取 UserDao
        UserDao userDao = userService.getUserDao();
        //输出查看是否注入成功
        System.out.println(userDao);
    }
基于构造函数的依赖注入(多个构造参数)

  • 第一步:创建 Bean 组件 UserDao UserService
java 复制代码
public class UserDao {
}

@Data
public class UserService {

    private UserDao userDao;

    private int age;

    private String name;

    public UserService(UserDao userDao, int age, String name) {
        this.userDao = userDao;
        this.age = age;
        this.name = name;
    }


    public UserService(String s) {
    }

}
  • 第二步: 编写元数据 spring-bean-03

方案1

构造参数的顺序填写值 value 直接赋值 ref 引用其他的 beanId。要考虑顺序

xml 复制代码
<beans>
        <bean id="userDao" class="com.mangfu2.UserDao"/>
        <bean id="userService" class="com.mangfu2.UserService">
            <constructor-arg  ref="userDao"/>
            <constructor-arg  value="18"/>
            <constructor-arg  value="mangfu"/>
        </bean>
</beans>

方案2

通过构造参数的名称填写,不用考虑顺序 name = 构造参数的名词 [推荐]

xml 复制代码
<beans>
        <bean id="userDao" class="com.mangfu2.UserDao"/>
        <bean id="userService" class="com.mangfu2.UserService">
            <constructor-arg name="userDao" ref="userDao"/>
            <constructor-arg name="age" value="18"/>
            <constructor-arg name="name" value="mangfu"/>
        </bean>
</beans>

方案3

构造参数的参数的下标指定填写,不用考虑顺序 index = 构造参数的下标 从左到右从0开始 [推荐]

xml 复制代码
<beans>
        <bean id="userDao" class="com.mangfu2.UserDao"/>
        <bean id="userService" class="com.mangfu2.UserService">
            <constructor-arg index = "0" ref="userDao"/>
            <constructor-arg index ="1" value="18"/>
            <constructor-arg index ="2" value="mangfu"/>
        </bean>
</beans>
  • constructor-arg 标签:指定构造参数和对应的值
  • constructor-arg 标签:name属性指定参数名、index属性指定参数角标、value属性指定普通属性值,ref属性引用其他的 beanId
  • 第三步:测试数据
java 复制代码
 @Test
    public void test4() {
        ClassPathXmlApplicationContext applicationContext =
                new ClassPathXmlApplicationContext("spring-bean-03.xml");

        UserService userService = applicationContext.getBean(UserService.class);

        System.out.println(userService.getUserDao());
        System.out.println(userService.getAge());
        System.out.println(userService.getName());

    }
基于 Setter 方法依赖注入

  • 第一步:创建 Bean 组件 UserDao UserService
java 复制代码
public class MovieFinder {
}


public class SimpleMovieLister {
    
    private MovieFinder movieFinder;
    
    private String movieName;

    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    public void setMovieName(String movieName) {
        this.movieName = movieName;
    }
}
  • 第二步:编写元数据 spring-bean-04
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">

    <bean id="movieFinder" class="com.mangfu3.MovieFinder"/>
    <bean id="simpleMovieLister" class="com.mangfu3.SimpleMovieLister">
        <property name="movieFinder" ref="movieFinder"/>
        <property name="movieName" value="整蛊专家"/>
    </bean>
</beans>
  • property标签: 可以给setter方法对应的属性赋值
  • property 标签: name属性代表set方法标识、ref代表引用bean的标识id、value属性代表基本属性值

第三步:编写测试数据

java 复制代码
 @Test
    public void test5() {
        ClassPathXmlApplicationContext applicationContext =
                new ClassPathXmlApplicationContext("spring-bean-04.xml");

        SimpleMovieLister simpleMovieLister = applicationContext.getBean(SimpleMovieLister.class);
        System.out.println(simpleMovieLister.getMovieFinder());
        System.out.println(simpleMovieLister.getMovieName());
    }

注意

  • 依赖注入(DI)包含引用类型和基本数据类型,同时注入的方式也有多种!主流的注入方式为setter方法注入和构造函数注入,两种注入语法都需要掌握!
  • 需要特别注意:引用其他bean,使用ref属性。直接注入基本类型值,使用value属性。

组件(bean)作用域和周期方法配置

组件(bean)周期方法配置

概念

我们可以在组件类中定义方法,然后当IoC容器实例化和销毁组件对象的时候进行调用!这两个方法我们称为生命周期方法!类似于Servletinit / destroy方法,我们可以在周期方法完成初始化和释放资源等工作。

  • init-method = "初始化方法名"
  • destroy-method = "销毁方法名"
  • 第一步:准备 Bean 组件类 JavaBean
java 复制代码
public class JavaBean {

    public void init() {
        System.out.println("init");
    }

    public void clear() {
        System.out.println("destroy");
    }
}
  • 第二步:配置元数据 spring-bean-05
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">

    <!--
      init-method = "初始化方法名"
      destroy-method = "销毁方法名"
      spring ioc 容器就会在对应的时间时间节点回调对应的方法,我们可以在其中写对应多的业务就可以了
    -->
    <bean id="javaBean" class="com.mangfu4.JavaBean" init-method="init" destroy-method="clear"/>
</beans>
  • 第三步:测试数据
java 复制代码
	@Test
    public void test6() {
        //执输出 init
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-bean-05.xml");

        //关闭 ClassPathXmlApplicationContext 输出 destroy
        applicationContext.close();
    }
组件(Bean)作用域配置(单例 or 多例)

概念

<bean>标签 声明 Bean,只是将 Bean 的信息配置给 SpringIoC容器!在IoC容器中,这些<bean>标签对应的信息转成 Spring内部 BeanDefinition 对象BeanDefinition 对象内,包含定义的信息(id,class,属性等等) ! 这意味着,BeanDefinition 与类概念一样,SpringIoC容器 可以可以根据 BeanDefinition对象 反射创建多个 Bean对象实例。具体创建多少个 Bean的实例对象,由Bean 的作用域 Scope属性 指定!

作用域可选值

取值 含义 创建对象的时机 默认值
singleton 在 IOC 容器中,这个 bean 的对象始终为单实例 IOC 容器初始化时
prototype 这个 bean 在 IOC 容器中有多个实例 获取 bean 时
  • 第一步:准备 Bean 组件类 JavaBean,JavaBean2
java 复制代码
public class JavaBean {
}

public class JavaBean2 {
}
  • 第二步:编写配置文件spring-bean-06
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">

    <!--这里默认就是单例, 也可以不写-->
    <bean id="javaBean2" class="com.mangfu5.JavaBean2" scope="singleton"/>

    <!--多例-->
    <bean id="javaBean" class="com.mangfu5.JavaBean" scope="prototype"/>
    
</beans>
  • 第三步:测试数据
java 复制代码
 @Test
    public void test7() {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-bean-06.xml");

        //获取 单例的 Bean
        JavaBean2 javaBean2_bean = applicationContext.getBean(JavaBean2.class);
        JavaBean2 javaBean2_bean2 = applicationContext.getBean(JavaBean2.class);
        System.out.println(javaBean2_bean == javaBean2_bean2); //true

        //获取 多例的 Bean
        JavaBean javaBean_bean = applicationContext.getBean(JavaBean.class);
        JavaBean javaBean_bean2 = applicationContext.getBean(JavaBean.class);
        System.out.println(javaBean_bean == javaBean_bean2); //false

        applicationContext.close();

    }

FactoryBean 特性和使用

特性

FactoryBean 接口是Spring IoC容器实例化逻辑的可插拔性点。用于配置复杂的Bean对象,可以将创建过程存储在 FactoryBean 的getObject方法!

.
FactoryBean 使用场景

  • 代理类的创建
  • 第三方框架整合
  • 复杂对象实例化等
    .

FactoryBeanBeanFactory区别

  • FactoryBean:Spring 里的特殊 bean,启动时创建,能在getObject()方法里按自定义逻辑产出其他 bean,初始化逻辑可按需定制,整合第三方框架常用它。
  • BeanFactory:Spring 基础接口,定义容器管理 bean 生命周期、加载解析配置、装配注入等基本行>为,用getBean()获取 bean 实例,可从多种源获取 bean 定义并转化为实例,还有ApplicationContext等子类拓展功能。
  • 二者区别:FactoryBean 专注创建 bean,有灵活定制初始化功能;BeanFactory 重在管理 bean,提供基础容器和生命周期管理功能。
使用

FactoryBean<T>接口有三个关键方法

  • T getObject():用于创建对象实例,创建的对象会被放入 IoC 容器中。
  • boolean isSingleton():判断工厂创建的对象是否为单例,默认返回 true,但使用 Lombok 插件可能有影响。
  • Class<?> getObjectType():返回getObject()所创建对象的类型,若事先不知则返回 null 。

第一步:准备 Bean 组件类 MyJavaBean

java 复制代码
public class MyJavaBean {

    private String name;

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

    public String getName() {
        return name;
    }
}
  • 第二步:准备工厂类 JavaBeanFactoryBean 实现 FactoryBean 接口
java 复制代码
ublic class JavaBeanFactoryBean implements FactoryBean<MyJavaBean> {

    private String value;

    public void setValue(String value) {
        this.value = value;
    }

    public String getValue() {
        return value;
    }

	//自定义 Bean 
    @Override
    public MyJavaBean getObject() throws Exception {
        MyJavaBean myJavaBean = new MyJavaBean();
        myJavaBean.setName(value);
        return myJavaBean;
    }

	//配置 Bean 返回类型
    @Override
    public Class<?> getObjectType() {
        return MyJavaBean.class;
    }

	//true 为 单例 false 为多例
    @Override
    public boolean isSingleton() {
        return true;
    }
}
  • 第三步:编写元数据 spring-bean-07
xml 复制代码
<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">
	<!--
		id 就是 getObject 方法返回的对象的标识
		Class 就是 factoryBean 标准化工厂类
	-->
    <bean id="myJavaBean" class="com.mangfu6.JavaBeanFactoryBean">、
    	<!--此位置的属性是注入 JavaBean 工厂类-->
        <property name="value" value="莽夫"/>
    </bean>
</beans>
  • 第四步:测试数据
java 复制代码
 @Test
    public void test8() {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-bean-07.xml");

        MyJavaBean myJavaBean = applicationContext.getBean(MyJavaBean.class);
        System.out.println(myJavaBean.getName());

        //factoryBean 工厂也会加入到 IoC 容器中名字是 &id
        JavaBeanFactoryBean javaBeanFactoryBean = applicationContext.getBean(JavaBeanFactoryBean.class);
        System.out.println(javaBeanFactoryBean.getValue());

        applicationContext.close();

    }
注意点

  • 元数据中:idgetObject 方法返回的对象的标识 ClassJavaBean 工厂类
  • JavaBean 工厂类也会加入到 IoC 容器中。名字是 &id (就是元数据中的 id 加个 &)

基于注解方式管理 Bean

Bean 注解标记和扫描(IoC)

概述

  • 注解本质与作用:注解如同 XML 配置文件,自身不具备执行能力,仅作标记。其具体功能是在框架检测到标记位置后,依标记功能执行操作。本质上,所有操作皆由 Java 代码完成,XML 和注解只是指示框架中的 Java 代码执行方式。
  • Spring 扫描机制:Spring 为确定程序员标记的注解位置,需通过扫描来检测,进而依据注解开展后续操作。
使用

Spring 提供多种注解用于定义 Bean:

  • @Component:通用注解,标记类为 Spring Bean,可用于各层,如 Service、Dao 等,直接标注在类上即可。
  • @Repository:专用于 Dao 层类,功能同 @Component。
  • @Service:用于 Service 层类,功能同 @Component。
  • @Controller:用于控制层类(如 SpringMVC 的 Controller),功能同 @Component。
    源码显示,@Controller、@Service、@Repository 基于 @Component 起名,对 Spring IOC 管理无实质区别,仅为方便开发者区分组件作用。虽本质相同,但为保证代码可读性与结构严谨性,不可随意标注。
第一步:导入依赖准备组件
xml 复制代码
<dependencies>
    <!--spring context依赖-->
    <!--当你引入Spring Context依赖之后,表示将Spring的基础依赖引入了-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>6.0.6</version>
    </dependency>
</dependencies>
java 复制代码
//相当于<bean id="commonComponent" class="com.atguigu.ioc_01.CommonComponent">
@Component
public class CommonComponent {}

@Controller
public class XxxController {}

@Repository
public class XxxDao {}

@Service
public class XxxService {}
第二步:配置元数据确定扫描范围
  • 第一种方式:基本扫描配置
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 https://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 配置自动扫描的包 -->
    <!-- 1.包要精准,提高性能!
         2.会扫描指定的包和子包内容
         3.多个包可以使用,分割 例如: com.mangfu.controller, com.mangfu.service等
    -->
     <context:component-scan base-package="com.mangfu7"/>
</beans>
  • 第二种方式:指定排除组件
xml 复制代码
<!-- 指定不扫描的组件 -->
    <context:component-scan base-package="com.mangfu7">
        <!-- context:exclude-filter标签:指定排除规则 -->
        <!-- type属性:指定根据什么来进行排除,annotation取值表示根据注解来排除 -->
        <!-- expression属性:指定排除规则的表达式,对于注解来说指定注解的全类名即可比如下面是排除 Controller 注解 -->
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
  • 第三种方式:仅扫描指定的组件
xml 复制代码
    <!-- 仅扫描指定的组件 -->
    <!-- 仅扫描 = 关闭默认规则 + 追加规则 -->
    <!-- use-default-filters属性:取值false表示关闭默认扫描规则 -->
    <context:component-scan base-package="com.mangfu7" use-default-filters="false">
        <!-- context:include-filter标签:指定在原有扫描规则的基础上追加的规则。比如下面是只扫描 Controller -->
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
组件 BeanName 问题

使用 XML 方式管理 bean 时,每个 bean 靠 id 属性值作为唯一标识,方便在别处引用。而采用注解管理组件后,组件同样需要唯一标识。默认情况下,组件对应的 bean 的 id 是其 类名首字母小写 形式,比如 SoldierController 类对应的 bean 的 id 就是 soldierController 如果指定了 value 那别名就是 value 指定的值

java 复制代码
//因为注解源码里面只有 value 一个属性所以 value 可以省略
@Controller(value = "tianDog")
public class SoldierController {
}

依赖注入:引用类型自动装配 (DI)

自动装配实现

  • 前提条件:参与自动装配的组件,无论是要被装配的还是提供装配的,都必须在 IoC 容器里,并且 IoC 容器的实现方式(XML 配置或者注解方式)对此没有影响。
  • @Autowired 注解使用方法:可以直接在成员变量上标记 @Autowired 注解 来自动注入对象,在实际项目中我们通常采用这种方式。甚至第三方类放入容器后,也能使用此注解来实现自动装配。
  • 原理:1. 在ioc 容器中查找符合类型的组件对象, 2. 设置给当前属性(di)
第一步:准备 Bean 组件
java 复制代码
@Repository
public class UserDao {

}

@Service
public class UserService {
 
    private UserDao userDao;

    public UserDao getUserDao() {
        return userDao;
    }
}
第二步:配置元数据扫描包
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 https://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 
    	扫描包
    	UserService 和 UserDao 都加入 IoC 容器了 
    -->
    <context:component-scan base-package="com.mangfu8"/>
</beans>
第三步:加 @Autowired 注解注入
java 复制代码
@Service
public class UserService {

    @Autowired
    private UserDao userDao;

    public UserDao getUserDao() {
        return userDao;
    }
}
@Autowired 注解标记位置

注意:注入和被注入的都要在 IoC 容器中

  • 成员变量
java 复制代码
@Repository
//这里 UserDao 也必须在 IoC 容器中
public class UserDao {
    @Autowired
    private UserService userService;
    
}
  • 构造器
java 复制代码
@Repository
public class UserDao {
    
    private UserDao userDao;

    @Autowired
    public UserDao(UserDao userDao) {
        this.userDao = userDao;
    }
}
  • Set 方法
java 复制代码
@Repository
public class UserDao {
    
    private UserDao userDao;

    @Autowired
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
}
所需类型匹配 Bean 不止一个的解决办法

装配 Bean 工作流程

在进行装配操作时,先依据所需组件类型到 IOC 容器中查找:

  • 若能找到唯一的 bean,则 @Autowired 直接执行装配。
  • 要是完全找不到匹配该类型的 bean,装配就会失败。

.
而当和所需类型匹配的 bean 不止一个时:

  • 若没有 @Qualifier 注解,会以 @Autowired 标记位置成员变量的变量名作为 bean 的 id 来匹配,匹配成功就执行装配,匹配不到则装配失败。
  • 若使用了 @Qualifier 注解,便按照 @Qualifier 注解中指定的名称作为 bean 的 id 进行匹配,能找到就执行装配,找不到则装配失败。
  • 此外,还可以使用 @Resource 注解来进行相关资源的装配,其装配规则与上述情况类似,也会按照指定的名称等规则去 IOC 容器中查找匹配的 bean 来完成装配操作。
@Qualifier 注解

根据 @Qualifier 注解中指定的名称作为 bean 的id进行匹配

java 复制代码
@Repository
public class UserDao {
    
    private UserDao userDao;

	//匹配 BeanName 为 userDao 进行装配
	//value 可以省略
	@Autowired
    @Qualifier(value = "userDao")
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
}
@ JSR-250 @Resource 注解

JSR 系列注解背景:

  • JSR(Java Specification Requests) 是 Java 平台标准化进程中的技术规范,其中的 JSR 注解按分类及语义不同分为多个系列。
  • JSR-250:用于在 Java EE 5 中定义支持注解,像@Resource用于标识需要注入的资源,是实现 Java EE 组件间依赖关系的方式,还有@PostConstruct(标识初始化方法)、@PreDestroy(标识销毁方法)等注解也在此规范中定义。需注意,JSR 只是规定注解及含义,具体实现由第三方框架(如 Spring)等完成。

@Resource 与 @Autowired 的区别:

所属不同:

  • @Resource注解 属于 JDK 扩展包,是 JSR-250 标准制定的注解类型,更具通用性。
  • @Autowired注解 是 Spring 框架自身的
    .

装配方式区别

  • @Resource注解 默认按 Bean 名称装配,未指定 name 时,用属性名作为 name,通过 name 找不到会自动按类型装配。
  • @Autowired注解 默认按类型装配,若要按名称装配,需配合@Qualifier注解使用。

.
使用位置不同:

  • @Resource注解 用在 属性上、setter 方法上。
  • @Autowired注解 用在 属性上、setter 方法上、构造方法上、构造方法参数上。

使用

@Resource注解 属于 JDK 扩展包,不在 JDK 当中,高于 JDK11 或低于 JDK8 时需要引入以下依赖:

  • 如果没有指定 name,先根据属性名查找 IoC 中组件 xxxService
  • 如果没有指定 name,并且属性名没有对应的组件,会根据属性类型查找
  • 可以指定 name 名称查找! @Resource(name='test') == @Autowired + @Qualifier(value='test')
xml 复制代码
<dependency>
    <groupId>jakarta.annotation</groupId>
    <artifactId>jakarta.annotation-api</artifactId>
    <version>2.1.1</version>
</dependency>
java 复制代码
@Repository
public class UserDao {
    
    private UserDao userDao;
	/**
     * 1. 如果没有指定name,先根据属性名查找IoC中组件xxxService
     * 2. 如果没有指定name,并且属性名没有对应的组件,会根据属性类型查找
     * 3. 可以指定name名称查找!  @Resource(name='test') == @Autowired + @Qualifier(value='test')
     */
     //也可以不指定 name
    @Resource(name = "userDao")
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
}

依赖注入:基本类型属性赋值(DI)

第一步:编写外部配置文件

properties 复制代码
username=root
第二步:xml 引入外部配置文件

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 https://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 扫描包 -->
    <context:component-scan base-package="com.mangfu8"/>
    <context:property-placeholder location="application.properties"/>
</beans>
第三步:@Value 注解注入

情况1: ${key} 取外部配置key对应的值!
情况2: ${key:defaultValue} 没有key,可以给与默认值

java 复制代码
@Repository
public class UserDao {
	/**
     * 情况1: ${key} 取外部配置key对应的值!
     * 情况2: ${key:defaultValue} 没有key,可以给与默认值
     */
    @Value("${userName}")
    private String userName;

    public String getUserName() {
        return userName;
    }
}

基于配置类方式管理 Bean

完全注解开发理解

Spring 完全注解配置,即使用 Java 配置类代码与注解来搭建 Spring 应用程序,取代传统的 XML 配置文件。这种方式类型更安全,可读性也更佳

配置类方式 IoC

基本使用

第一步:准备组件

注解方式加入 IoC 容器

java 复制代码
@Component
public class MyComponent {
    private String name;

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

    public String getName() {
        return name;
    }
}
第二步:声明配置类扫描组件

@ComponentScan:扫描包中注解,以便加入 IoC 容器
@Configuration:声明为配置类,它本身也会加入 IoC 容器

java 复制代码
//包扫描: 相当于 XML 中的 <context:component-scan base-package="com.mangfu11"/>
@ComponentScan(basePackages = "com.mangfu11")
//声明为配置类
@Configuration
public class MyConfiguration {
}
第三步:获取 Bean
  • 第一种方式
java 复制代码
   @Test
    public void test10() {
        //1. 创建 ioc 容器对象: 参数是配置类
        AnnotationConfigApplicationContext annotationConfigApplicationContext =
                new AnnotationConfigApplicationContext(MyJavaBean.class);

        //2. 获取 bean
        MyJavaBean bean = annotationConfigApplicationContext.getBean(MyJavaBean.class);
        bean.setName("莽夫");
        System.out.println(bean.getName());
    }
  • 第二种方式
java 复制代码
@Test
    public void test11() {
        //第二种创建 ioc 容器的方式
        AnnotationConfigApplicationContext applicationContext1 = new AnnotationConfigApplicationContext();
        //外部设置配置类
        applicationContext1.register(MyConfiguration.class);
        //刷新后方可生效
        applicationContext1.refresh(); //ioc di

        MyComponent bean = applicationContext1.getBean(MyComponent.class);
        bean.setName("莽夫");
        System.out.println(bean.getName());

    }

依赖注入:基本类型属性赋值(DI)

注入引用来行用注解方式就行,基本类型属性赋值可以改成这种

第一步:准备配置类和组件

  • 配置类
java 复制代码
//包扫描: 相当于 XML 中的 <context:component-scan base-package="com.mangfu11"/>
@ComponentScan(basePackages = "com.mangfu11")
//声明为配置类
@Configuration
public class MyConfiguration {
}
  • 组件
java 复制代码
@Component
@Data
public class MyComponent {
    private String name;
    private String age;
    private String gender;
}
第二步:使用 @PropertySource 注解
  • @PropertySource: 用于将外部的属性文件(如.properties文件)加载到 Spring 的环境中。这样,在 Spring 容器管理的组件中,就可以使用这些属性文件中的配置信息。
  • @Value:将外部配置文件(已由@PropertySource引入)里的具体值,注入到被 Spring 管理的组件(像带@Component的类)的字段中。比如看到@Value("${某个属性名}"),Spring 启动时就会拿配置文件里对应属性的值,填到这个字段,实现配置与代码分离,改配置不用动代码。
java 复制代码
@Component
//导入 properties配置文件
@PropertySource("classpath:application.properties")
@Data
public class MyComponent {
	//注入
    @Value("${username}")
    private String name;

    @Value("${age}")
    private String age;

    @Value("${gender}")
    private String gender;

}

第三方 jar 无法使用注解加入 IoC容器 解决方案

使用 @Bean 定义组件

@Bean:方法返回值作为 Bean 类型:在使用@Bean注解的方法中,方法的返回值类型决定了 Bean 的类型。例如,如果方法返回一个DataSource类型的对象,那么这个 Bean 在 Spring 容器中的类型就是DataSource。这个类型可以是具体的类,也可以是接口或者抽象类

  • 配置文件
properties 复制代码
dataSource.driver=com.mysql.cj.jdbc.Driver;
dataSource.url=jdbc:mysql://localhost:3306/db02;
dataSource.username=root;
dataSource.password=Ting123321;
  • 配置类
java 复制代码
@Configuration
@PropertySource("classpath:application.properties")
public class MyConfiguration {
    @Bean
    public DruidDataSource dataSource(
            @Value("${dataSource.driver}")
            String driverClassName,
            @Value("${dataSource.url}")
            String url,
            @Value("${dataSource.username}")
            String username,
            @Value("${dataSource.password}")
            String password
    ) {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driverClassName);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);

        return dataSource;

    }
}
  • 测试类
java 复制代码
   @Test
    public void test13() {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext("com.mangfu12");
        DruidDataSource bean = applicationContext.getBean(DruidDataSource.class);
        System.out.println(bean.getUrl());
        System.out.println(bean.getUsername());
        System.out.println(bean.getPassword());


    }
@Bean 细节
@Bean 的 BeanName问题
  • @Bean 源码
java 复制代码
public @interface Bean {
    //前两个注解可以指定Bean的标识
    //@AliasFor 是设置等价关系。这里 name 就是 value
    @AliasFor("name")
    String[] value() default {};
    @AliasFor("value")
    String[] name() default {};
  
    //autowireCandidate 属性来指示该 Bean 是否候选用于自动装配。
    //autowireCandidate 属性默认值为 true,表示该 Bean 是一个默认的装配目标,
    //可被候选用于自动装配。如果将 autowireCandidate 属性设置为 false,则说明该 Bean 不是默认的装配目标,不会被候选用于自动装配。
    boolean autowireCandidate() default true;

    //指定初始化方法
    String initMethod() default "";
    //指定销毁方法
    String destroyMethod() default "(inferred)";
}
  • 指定 Bean 名称
java 复制代码
@Configuration
public class MyConfig {
	@Bean("mytest") //指定名称可以省略
	public Test test() {
		return new Test();
	}
}
  • @Bean 缺省情况下

Bean 名称和开头字母小写的方法名相同`

java 复制代码
@Configuration
public class AppConfig {

  @Bean
  public TransferServiceImpl transferService() {
    return new TransferServiceImpl();
  }
}

以上配置完全等同于
    
<beans>
  <bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>
@Bean 初始化和销毁方法指定

@Bean 注解支持指定任意初始化和销毁回调方法,非常类似于 Spring XML 在 bean 元素上的 init-methoddestroy-method 属性,如以下示例所示:

  • Bean
java 复制代码
public class MyBean {

   public void init() {
      System.out.println("初始化");
   }

   public void destroy() {
      System.out.println("销毁");
   }
}
  • Config 配置类
  • initMethod:指定初始化方法
  • destroyMethod:指定销毁方法
java 复制代码
@Configuration
@ComponentScan("com.mangfu12")
public class Config {

    @Bean(initMethod = "init", destroyMethod = "destroy")
    public MyBean bean() {
        return new MyBean();
    }
}
  • 测试类
java 复制代码
 @Test
    public void test15() {
        //输出初始化
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext("com.mangfu13");

        //输出销毁
        applicationContext.close();
        
    }
@Bean 作用域(单例还是多例)

使用 @Bean注解 定义 bean 时,能指定其特定的作用域,可选用在 Bean 作用域 关内容里规定的任一标准作用域。其默认作用域是 singleton(单例),不过,我们可以借助@Scope注解来改变这个默认范围。

  • @Scope:切换单例或者多例
java 复制代码
@Configuration
@PropertySource(value = "classpath:application.properties")
public class JavaConfiguration {

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

    @Bean
    @Scope("prototype") 
    public DruidDataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driverClassName);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }
}
@Bean 互相注入
  • 第一种方式

如果其他组件也是 @Bean 方法 也可以直接的调用方法 | 本质上从 ioc 容器获取组件

java 复制代码
@Configuration
@PropertySource(value = "classpath:application.properties")
public class JavaConfiguration {

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

    @Bean
    @Scope("prototype")
    public DruidDataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driverClassName);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate() {
        JdbcTemplate jdbcTemplate = new JdbcTemplate();

        //需要DataSource 需要 ioc 容器的其他组件
        //方案1: 如果其他组件也是 @Bean 方法 也可以直接的调用方法 | 本质上从 ioc 容器获取组件
        //不推荐
        jdbcTemplate.setDataSource(dataSource());
        return jdbcTemplate;
    }
}
  • 第二种方式

形参变量注入

  • 要求必须有对应的类型的组件, 如果没有抛异常
  • 如果只有一个这种类型。自动注入
  • 如果有多个这种类型。可以使用形参名称 = BeanName 指定注入
java 复制代码
@Configuration
@PropertySource(value = "classpath:application.properties")
public class JavaConfiguration {

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

    @Bean
    @Scope("prototype")
    public DruidDataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driverClassName);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }



    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        JdbcTemplate jdbcTemplate = new JdbcTemplate();

        //需要DataSource 需要 ioc 容器的其他组件
        //方案2: 形参列表声明想要的组件类型, 可以是一个也可以是多个。ioc 容器会自动注入

        //如果没有: 形参变量注入, 要求必须有对应的类型的组件, 如果没有抛异常
        //如果有多个: 可以使用形参名称 = 对应的bean id
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }
}
@Import 注解

@Import 注释允许从另一个配置类加载 @Bean 定义,现在,在实例化上下文时不需要同时指定 ConfigA.DataSourceConfigMainAppConfig ,只需显式提供 MainAppConfig ,如以下示例所示:

java 复制代码
@Configuration
public class DataSourceConfig {
    @Bean
    public DruidDataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/db02");
        dataSource.setUsername("root");
        dataSource.setPassword("Ting123321");
        return dataSource;
    }
}
java 复制代码
~~~java
@Configuration
@Import(DataSourceConfig.class)
public class MainAppConfig {
}
java 复制代码
public class ComponentTest {
    @Test
    public void testImportBeanConfig() {
        ApplicationContext context = new AnnotationConfigApplicationContext(MainAppConfig.class);
        DruidDataSource bean = context.getBean(DruidDataSource.class);

    }
}

Spring 三种配置方式总结

XML 方式配置总结

配置文件

  • 用 XML 格式写配置文件,把相关配置都放里面。
    .

bean 声明

  • <bean> 标签声明 bean,它包含 "id"(用于标识)、"class"(指定对应类)这些基本信息,还有通过 <property> 标签设置的属性信息,像用 "name" 指定属性名,"value" 设简单值,"ref" 引用其他 bean
    .

引入外部文件

  • 若引入外部的配置文件,可用 <context:property-placeholder> 来操作。

.
IoC 容器选择

  • IoC 容器就选 ClassPathXmlApplicationContext 对象来实现相关功能。

XML + 注解方式配置总结

注解的用途

  • 使用注解来标记参与 IoC(控制反转)的类以及完成属性装配工作。
    .

XML 文件作用

  • 仍需 XML 文件,要借助 <context:component-scan> 标签指定扫描注解的范围,以便框架知晓哪些类上的注解需要处理。
    .

常用 IoC 注解

  • @Component:通用标记组件类。
  • @Service:用于业务逻辑层类。
  • @Controller:标记控制层(如 Web 应用中处理请求)类。
  • @Repository:针对数据访问层类做标记。
    .

常用 DI 注解

  • @Autowired:按类型自动装配依赖对象。
  • @Qualifier:配合 @Autowired,精准指定注入的 bean。
  • @Resource:可按名称或类型进行依赖注入。
  • @Value:注入简单字面量值。
    .

容器选择

  • IoC 具体容器实现选用 ClassPathXmlApplicationContext 对象,实例化它并传入 XML 配置文件路径,就能让框架基于配置和注解完成管理、实例化等操作。*

完全注解方式配置总结

完全注解方式概述

  • 完全注解方式就是不再使用 XML 文件了,而是依靠配置类结合各种注解来实现相关配置功能。
    .

配置类替代 XML 文件

  • 原本放在 XML 文件里的配置,现在用添加了 @Configuration 注解的类来替代,这个类就相当于之前 XML 文件所承担的配置角色。
    .

常用 IoC 相关注解

  • @Component:通用的组件标记,表明类是被 Spring 管理的组件。
  • @Service:专门用于标记业务逻辑层的类。
  • @Controller:多用于标记控制层(比如 Web 应用里处理请求的类)的类。
  • @Repository:常用来标记数据访问层(像操作数据库相关类)的类。
    .

常用 DI 相关注解

  • @Autowired:按类型自动装配依赖对象。
  • @Qualifier:配合 @Autowired,用于更精准地指定要注入的 bean。
  • @Resource:可按名称或者类型来进行依赖注入。
  • @Value:用来注入像字符串、数字等简单的字面量值。
    .

注解范围指定

  • 之前用 <context:component-scan> 标签指定扫描注解范围,现在换成在配置类里使用 @ComponentScan(basePackages = {"com.atguigu.components"})注解来指定要扫描的包路径范围,这样 Spring 就能知道去哪些地方查找带有相关注解的类了。
    .

引入外部配置文件

  • 过去依靠 <context:property-placeholder> 引入外部配置文件,现在使用 @PropertySource({"classpath:application.properties","classpath:jdbc.properties"}) 注解来替代,它可以指定要加载的外部配置文件的路径,让配置类能获取到外部文件里的配置信息。
    .

Bean 声明替代

  • 之前用 <bean> 标签声明 bean,现在通过在配置类的方法上添加 @Bean 注解来实现,这个带注解的方法返回的对象就相当于之前 <bean> 标签定义的 bean
    .

IoC 容器实现选择

  • IoC 具体容器实现选择 AnnotationConfigApplicationContext 对象,通过它来加载配置类,进而实现 Spring 框架的管理、实例化等相关操作。

整合 Spring5-Test5 搭建测试环境

优点

在没有使用 SpringTest5自动装配 的情况下,如果要在测试类中获取 Spring 容器中的 PersonDruidDataSource 这两个 Bean,通常需要通过ApplicationContextgetBean 方法来获取。例如

java 复制代码
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class ComponentTestTraditional {
    @Test
    public void test() {
        ApplicationContext context = new AnnotationConfigApplicationContext(MainAppConfig.class);
        // 使用getBean方法获取Person和DruidDataSource
        Person person = (Person) context.getBean("person");
        DruidDataSource dataSource = (DruidDataSource) context.getBean("dataSource");
        System.out.println(person);
        System.out.println(dataSource);
    }
}

使用了 SpringTest5 后 可以在测试类中使用自动装配, 并且不用手动创建容器

java 复制代码
@SpringJUnitConfig(MainAppConfig.class)
public class ComponentTest {
    @Autowired
    private Person person;
    @Autowired
    private DruidDataSource dataSource;
    @Test
    public void test() {
        System.out.println(person);
        System.out.println(dataSource);
    }
}

使用第一步:导入依赖

xml 复制代码
<!--junit5测试-->
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-api</artifactId>
    <version>5.3.1</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>6.0.6</version>
    <scope>test</scope>
</dependency>

使用第二步:整合测试注解使用

  • Bean
java 复制代码
@Component
public class Person {
}
  • DataSouceConfig 配置类1
java 复制代码
@Configuration
public class DataSourceConfig {
    @Bean
    public DruidDataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/db02");
        dataSource.setUsername("root");
        dataSource.setPassword("Ting123321");
        return dataSource;
    }
}
  • MainAppConfig 配置类2
java 复制代码
@Configuration
@Import(DataSourceConfig.class)
@ComponentScan(basePackages = {"com.mangfu15"})
public class MainAppConfig {
}
java 复制代码
//@SpringJUnitConfig(locations = {"classpath:spring-context.xml"})  //指定配置文件xml
@SpringJUnitConfig(value = {MainAppConfig.class})  //指定配置类
public class Junit5IntegrationTest {
    
    //这里不用 AnnoAnnotationConfigApplicationContext 获取 IoC 容器
    //在测试范围内就能注入 Bean
    @Autowired
    private User user;
	
	//不用 getbean 获取了。直接注入就行
	@Autowired
    private DruidDataSource dataSource;
    
    @Test
    public void testJunit5() {
        System.out.println(user);
    }
}

Spring AoP 面向切面编程

动态代理技术

JDK 动态代理

  • JDK 动态代理是 Java 自带的一种代理方式。它要求目标类必须有接口,基于这个接口,JDK 在运行时会动态生成一个代理对象。这个代理对象和目标对象就像 "拜把子" 的兄弟,因为它们都实现了相同的接口,能以相同的方式被调用,只是代理对象在调用真正的方法前后可以添加额外的逻辑。
    .

CGLIB 动态代理

  • CGLIB 动态代理是另一种代理技术。它的厉害之处在于不需要目标类有接口,通过让代理类继承目标类来实现代理,就好比代理类 "认" 目标类为 "干爹"。这样,代理类就能在目标类方法调用的前后插入自己的逻辑,从而对目标类的行为进行增强。

Spring AoP

AoP 概述


AOP 核心概念

  • 切入点:实际被 AOP 控制的方法,需要被增强的方法
  • 通知:封装共享功能的方法就是通知

AoP 通知类型

环绕就是一部分在目标方法之前,一部分在之后

  • 它的返回值代表的是原始方法执行完毕的返回值
java 复制代码
try {
	前置通知 @Before
  	目标方法执行
  	返回后通知 @AfterReturning
} catch() {
  	异常后通知 @AfterThrowing
} finally {
	后置通知 @After
}

AoP 通知顺序

AoP 切点表达式 @execution


AoP 切点表达式重用

第一种:创建存储切点的类维护

创建一个存储切点的类
单独维护切点表达式
execution 使用:类全限定符.方法名()

  • 切点维护类
java 复制代码
@Component
public class MyPointCut {

    @Pointcut("execution(* com.atguigu.service.impl.*.*(..))")
    public void pc(){}
  • 重用类演示类
java 复制代码
    @Before("com.atguigu.pointcut.MyPointCut.pc()")
    public void start() {
        System.out.println("方法开始了");
    }

    @After("com.atguigu.pointcut.MyPointCut.pc()")
    public void after() {
        System.out.println("方法结束了");
    }

    @AfterThrowing("com.atguigu.pointcut.MyPointCut.pc()")
    public void error() {
        System.out.println("方法报错了");
    }
第二种:当前类中提取表达式

定义一个空方法
注解 @Pointcut()
增强注解中引用切点 直接调用方法名

java 复制代码
@Aspect
@Component
public class UserServiceAspect {

    // 定义一个空方法,使用@Pointcut注解来定义切点表达式,这里表示匹配UserService接口下的所有方法
    @Pointcut("execution(* com.example.demo.service.UserService.*(..))")
    public void userServicePointcut() {}

    // 在前置通知中复用上面定义的切点表达式,直接写切点方法名即可
    @Before("userServicePointcut()")
    public void beforeAddUser() {
        System.out.println("在执行UserService的方法前执行的逻辑");
    }

AoP 基本使用

底层技术组成

动态代理(InvocationHandler):

  • JDK原生 的实现方式,需要被代理的目标类必须实现接口。因为这个技术要求代理对象和目标对象实现同样的接口(兄弟两个拜把子模式)。
  • cglib:通过继承被代理的目标类(认干爹模式)实现代理,所以不需要目标类实现接口。
  • AspectJ:SpringAOP借用了AspectJ中的AOP注解。也就是说 @Before 等注解都来自 AspectJ
默认代理方式演示(JDK原生)

第一步:导入依赖

xml 复制代码
 <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>6.0.6</version>
</dependency>
第二步:准备接口和实现类

java 复制代码
public interface Calculator {
    int add(int i, int j);
}
java 复制代码
@Component
public class CalculatorPureImpl implements Calculator {
    @Override
    public int add(int i, int j) {

        int result = i + j;

        return result;
    }
}
第三步:声明切面类 @Aspect

切面类要加入 IoC 容器
并且要使用 @Aspect 声明为切面类

java 复制代码
@Aspect
@Component
public class LogAdvice {
    @Pointcut("execution(* com.mangfu.Calculator.*(..))")
    public void Mypointcut(){}

    @Before("Mypointcut()")
    public void testBefore() {
        System.out.println("before");
    }

    @AfterReturning("Mypointcut()")
    public void testAfterReturning() {
        System.out.println("afterReturning");
    }

    @AfterThrowing("Mypointcut()")
    public void testAfterThrowing() {
        System.out.println("afterThrowing");
    }

    @After("Mypointcut()")
    public void testAfter() {
        System.out.println("after");
    }
}
第四步:用配置类开启 aspectj 注解支持 @EnableAspectJAutoProxy

@EnableAspectJAutoProxy:开启 aspectj 注解支持

java 复制代码
@Configuration
@ComponentScan("com.mangfu")
@EnableAspectJAutoProxy
public class MyConfig {
}

测试类

默认使用原生 JDK 代理。必须用接口来接收 IoC 类型的代理组件。因为它是继承自目标类接口代理类。不是目标类,所以不能用目标类类型接收

java 复制代码
@SpringJUnitConfig(MyConfig.class)
public class MyTest {

	//此时注入的是代理对象
    @Autowired
    private Calculator calculator;
    

    @Test
    public void test() {
        System.out.println(calculator.add(1, 2));
    }
}
环绕通知

就相当于直接进入切面类执行

  • 接口和实现类
java 复制代码
public interface Calculator {
    int add(int i, int j);
}
java 复制代码
@Component
public class CalculatorPureImpl implements Calculator {
    @Override
    public int add(int i, int j) {

        int result = i + j;

        return result;
    }
}
  • 配置类开启 AspectJ
java 复制代码
@Configuration
@ComponentScan("com.mangfu")
@EnableAspectJAutoProxy
public class MyConfig {
}
  • 切面类
  • ProceedingJoinPoint joinPoint:目标方法对象
  • jointPoint.getArgs(): 获取目标方法运行的参数
  • joinPoint.proceed(args): 执行目标方法
java 复制代码
@Aspect
@Component
public class LogAdvice {
   @Around("execution(* com.mangfu.Calculator.*(..))")
   public Object around(ProceedingJoinPoint joinPoint) {
       Object[] args = joinPoint.getArgs(); //获取目标方法运行的参数
       Object result = null; //用于接收目标参数的返回值

       try {
           System.out.println("Before");
           result = joinPoint.proceed(args); //调用目标方法
           System.out.println("AfterReturning");
       } catch (Throwable e) {
           System.out.println("Afterthrowing");
           throw new RuntimeException(e);
       } finally {
           System.out.println("After");
       }
       return result;

   }
}
  • 测试类
java 复制代码
@SpringJUnitConfig(MyConfig.class)
public class MyTest {

    @Autowired
    private Calculator calculator;

    @Test
    public void test() {
        System.out.println(calculator.add(1, 2));
    }
}
JoinPoint 详解


CGLib 动态代理生效情况

在目标类没有实现任何接口的情况下,Spring会自动使用cglib技术实现代理。

  • 如果目标类有接口, 选择使用 jdk 动态代理:实现目标接口
  • 如果目标类没有接口, 选择 cglib 动态代理:继承目标对象
  • 如果有接口, 就用接口接值
  • 如果没有接口, 就用类进行接值
  • 没实现接口的实体类和切面类
java 复制代码
@Component
public class Calculator {
    public int add(int i, int j) {

        int result = i + j;

        return result;
    }
}
java 复制代码
@Aspect
@Component
public class testBefore {
    @Before("execution(* com.mangfu2.Calculator.add(..))" )
    public void before(){
        System.out.println("test sucessful");

    }
}
  • 配置类
java 复制代码
@Component
public class Calculator {
    public int add(int i, int j) {

        int result = i + j;

        return result;
    }
}
  • 测试类
java 复制代码
@SpringJUnitConfig(MyConfig.class)
public class MyTest {

   //建议用接口取。防止后期取不到代理类对象
    //正常:aop - aop底层选择代理 - 选择jdk代理 - 根据接口生成代理类 - 代理对象和目标对象 是拜把子 兄弟关系。不是同一个
    
    //这里实现类没有实现接口所以用 CGLib 动态代理
    //aop - ioc 容器中真正存储的是代理对象不是目标对象
    @Autowired
    private Calculator calculator;

    @Test
    public void test(){
        calculator.add(1,2);
    }
    
}

Spring AoP 对获取 Bean 的影响

JDK 原生代理

声明一个接口,其仅有一个实现类,同时创建切面类对该接口的实现类应用通知:

  • 按接口类型获取 bean 可正常获取。
  • 按类获取 bean 则无法获取,原因在于应用切面后,实际存放在 IOC 容器中的是代理类对象,目标类本身并未放入 IOC 容器,所以依据目标类类型无法从 IOC 容器中找到相应对象


CGLib 代理

声明一个类,创建一个切面类,对上面的类应用通知

  • 根据类获取 bean,能获取到


Spring TX 声明式事务

声明式事务概念

  • 定义:通过注解或 XML 配置的方式控制事务的提交和回滚,具体事务实现由第三方框架负责,开发者只需添加相应配置,无需直接进行事务操作。*
  • 优点:可将事务控制和业务逻辑分离,提升代码的可读性与可维护性。


  • spring-tx: 包含声明式事务实现的基本规范(事务管理器规范接口和事务增强等等)
  • spring-jdbc: 包含DataSource方式事务管理器实现类DataSourceTransactionManager
  • spring-orm: 包含其他持久层框架的事务管理器实现类例如:Hibernate/Jpa等
    .
    spring-tx 就是 Spring 官方提供的事务接口,各大持久层框架需要自己实现这个接口,而 spring-jdbc 就是 jdbc, jdbcTemplate, mybatis 的实现类 DataSourceTranscationManagerspring-orm 就是 Hibernate/Jpa 等持久层框架的实现类

声明式事务基本使用

第一步:加入依赖


  • jdbc, jdbcTemplate, mybatis 这三个持久层框架情况
xml 复制代码
<!-- 声明式事务依赖-->
<!-- 就是 tx 就是 spring 提供的事务接口-->
<!-- 声明式事务底层是 aop 所以要 aop 依赖-->
  <!-- 声明式事务依赖-->
  <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>6.0.6</version>
  </dependency>

  <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>6.0.6</version>
  </dependency>

<!-- 第三方实现 spring 事务接口的类的依赖-->
<!-- spring-jdbc -->
  <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>6.0.6</version>
  </dependency>
  • 如果是 Hibernate 等其他持久层框架就用 spring-jdbc 换成 spring-orm 依赖

第二步:在配置类上加上 @EnableTransactionManagement 注解开启事务管理


java 复制代码
@Configuration
@ComponentScan("com.mangfu")
@PropertySource("classpath:jdbc.properties")
//@EnableAspectJAutoProxy //开启 aspectj 注解的支持
@EnableTransactionManagement //开启事务管理
public class JavaConfig {

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


    //配置druid连接池
    @Bean
    public DataSource dataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }


	//配置 JDBCTemplate
    @Bean
    //jdbcTemplate
    public JdbcTemplate jdbcTemplate(DataSource dataSource){
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }
}

第三步:在配置类里配置事务管理器


事务管理器要设置基于哪个连接池进行操作

java 复制代码
@Configuration
@ComponentScan("com.mangfu")
@PropertySource("classpath:jdbc.properties")
//@EnableAspectJAutoProxy //开启 aspectj 注解的支持
@EnableTransactionManagement //开启事务管理
public class JavaConfig {

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


    //druid连接池
    @Bean
    public DataSource dataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }


    @Bean
    //jdbcTemplate
    public JdbcTemplate jdbcTemplate(DataSource dataSource){
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }

//配置事务管理器
    @Bean
    public TransactionManager transactionManager(DataSource dataSource){
        //内部要进行事务的操作, 基于的连接池。所以连接池的对象要给它。他才能对连接池的对象进行操作
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();

        //需要连接池对象
        dataSourceTransactionManager.setDataSource(dataSource);

        return dataSourceTransactionManager;
    }

}

第四步:给需要事务的地方加上 @Transactional 注解


@Treanscational 加在类上就是类里所有方法开启事务。加在方法上就单独那个方法有事务

  • Dao层
java 复制代码
@Repository
public class StudentDao {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    public void updateNameById(String name,Integer id){
        String sql = "update students set name = ? where id = ? ;";
        int rows = jdbcTemplate.update(sql, name, id);
    }

    public void updateAgeById(Integer age,Integer id){
        String sql = "update students set age = ? where id = ? ;";
        jdbcTemplate.update(sql,age,id);
    }
}
  • Service 层
java 复制代码
@Service
public class StudentService {
    
    @Autowired
    private StudentDao studentDao;

    /**
     * 添加事务:
     *      @Transctional
     *      位置: 方法 | 类上
     *      方法: 当前方法有事务
     *      类上: 泪下所有方法都有事务
     */
     //这里有自动事务了。所以可以报错可以自动回滚
    @Transactional
    public void changeInfo(){
        studentDao.updateAgeById(88,1);
        int i = 1/0;
        System.out.println("-----------");
        studentDao.updateNameById("test1",1);
    }
}
  • 测试类
java 复制代码
@SpringJUnitConfig(JavaConfig.class)
public class TxTest {

    @Autowired                               
    private StudentService studentService;

    @Test
    public void  testTx(){
        studentService.changeInfo();
    }
}

事务的属性

事务属性:只读


只读模式可以提升查询事务的效率!,一般情况都是通过类添加注解添加事务,类下的所有方法都有事务,而查询方法一般不用添加事务。这个时候可以再次添加事务注解,设置为只读,提高效率查询效率

  • @Transactional(readOnly = ...)
java 复制代码
@Transactional
@Service
public class StudentService {

    @Autowired
    private StudentDao studentDao;


	 //查询 没有必要添加事务。这里设置为只读提高效率
    @Transactional(readOnly = true)
    public void getStudentInfo() {
       
        //获取学生信息 查询数据库 不修改

    }

}

事务属性:超时


事务在执行过程中,有可能因为遇到某些问题,导致程序卡住,从而长时间占用数据库资源。而长时间占用资源,大概率是因为程序运行出现了问题(可能是Java程序或MySQL数据库或网络连接等等)。此时这个很可能出问题的程序应该被回滚,撤销它已做的操作,事务结束,把资源让出来,让其他正常程序可以执行。概括来说就是一句话:超时回滚,释放资源。

  • @Transactional(timeout = ...)
java 复制代码
@Transactional(timeout = 3)
@Service
public class StudentService {

    @Autowired
    private StudentDao studentDao;
    public void changeInfo() {
        studentDao.updateAgeById(88,1);
        System.out.println("-----------");
        try {
            Thread.sleep(4000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        studentDao.updateNameById("test2",1);
    }

	//因为这个注解会覆盖掉类上注解。所以要再设置一遍
    @Transactional(readOnly = true, timeout = 3)
    public void getStudentInfo() {
     
    }

}

事务属性:回滚


默认情况下,发生运行时 (RuntimeException) 异常事务才回滚,所以我们可以指定 Exception 异常来控制所有异常都回滚

  • roollbackFor = 回滚的异常范围:设置的异常都回滚
  • noRollbackFor = 不回滚的异常范围:控制某个异常不回滚
java 复制代码
//所有异常都回滚,除了 FileNotFoundException 
@Transactional(rollbackFor = Exception.class, noRollbackFor = FileNotFoundException.class)
    public void changeInfo() throws FileNotFoundException {
        studentDao.updateAgeById(99,1);

        new FileInputStream("xxxx");

        studentDao.updateNameById("test2",1);
    }

事务属性:事务隔离级别


事务并发可能引发的问题

  • 脏读:一个事务读取另一个事务未提交的数据
  • 不可重复读:一个事务就是读取了另一个事务提交的修改数据
  • 幻读: 一个事务读取了另一个事务提交的插入数据

事务隔离级别

  • 读未提交(Read Uncommitted):事务可以读取未被提交的数据,容易产生脏读、不可重复读和幻读等问题。实现简单但不太安全,一般不用。
  • 读已提交(Read Committed):事务只能读取已经提交的数据,可以避免脏读问题,但可能引发不可重复读和幻读。
  • 可重复读(Repeatable Read):在一个事务中,相同的查询将返回相同的结果集,不管其他事务对数据做了什么修改。可以避免脏读和不可重复读,但仍有幻读的问题。
  • 串行化(Serializable):最高的隔离级别,完全禁止了并发,只允许一个事务执行完毕之后才能执行另一个事务。可以避免以上所有问题,但效率较低,不适用于高并发场景。
    .

不同的隔离级别适用于不同的场景,需要根据实际业务需求进行选择和调整。

建议设置第二个隔离级别
isolation = Isolation.事务的隔离级别

  • READ_UNCOMMITTED 读未提交
  • READ_COMMITTED 读已提交
  • REPEATABLE_READ 可重复读
  • SERIALIZABLE 串行化
java 复制代码
//设置事务隔离级别为可串行化 
@Transactional(isolation = Isolation.SERIALIZABLE)
    public void changeInfo() throws FileNotFoundException {
        studentDao.updateAgeById(99,1);

        new FileInputStream("xxxx");

        studentDao.updateNameById("test2",1);
    }

事务属性:事务传播行为


  • propagation = 传播规则【默认是 Propagation.REQUIRED】
    我们一般使用默认就行
名称【传播规则】 含义
REQUIRED 如果父方法有事务,就加入,如果没有就新建自己独立!父方法有事务报错,子方法会回滚
REQUIRES_NEW 不管父方法是否有事务,我都新建事务,都是独立的!即使父方法有有事务报错。两个子方法也不会回滚

就是用父方法 topService() 调用 子方法changeAge()changeName()。这两个子方法是否会进行回滚

相关推荐
nuise_3 分钟前
李宏毅机器学习课程笔记02 | 机器学习任务攻略General Guide
人工智能·笔记·机器学习
JermeryBesian8 分钟前
Flink概念知识讲解之:Restart重启策略配置
java·flink·apache
天草二十六_简村人12 分钟前
微服务框架,Http异步编程中,如何保证数据的最终一致性
java·spring boot·后端·http·微服务·架构
小馋喵知识杂货铺12 分钟前
XPath语法详解及案例讲解
java·前端·javascript
一二小选手15 分钟前
【SpringBoot】日志处理-异常日志(Logback)
java·spring boot·后端·logback
来一杯龙舌兰19 分钟前
【Jboss/Windows】Tomcat 8 + JDK 8 升级为 Jboss eap 7 + JDK8
java·windows·tomcat·jboss·jboss升级·tomcat迁移
vv啊vv25 分钟前
ROS笔记
笔记
夏壹-10分分享29 分钟前
get和post有什么区别
java
龙之叶40 分钟前
Android13实时刷新频率的实现代码
android·java·ui