Spring基础知识

一、Spring概述

相关概念

Spring框架是一个开源的Java应用程序框架,用于开发企业级Java应用程序。它提供了一种轻量级的、非侵入式的开发方式,通过依赖注入和面向切面编程等特性,简化了Java应用程序的开发过程。Spring框架包含了多个模块,包括核心容器、数据访问/集成、Web开发、AOP、消息传递等,可以根据需要选择使用不同的模块。Spring框架广泛应用于企业级Java应用程序的开发,提供了一种灵活、可扩展的架构,使得开发人员可以更加高效地开发和维护应用程序。

spring体系结构

项目搭建以及快速入门

1、创建maven项目

2、导入相关依赖

 <!--导入spring的坐标spring-context,对应版本是5.2.10.RELEASE-->
  <dependency>
       <groupId>org.springframework</groupId>
       <artifactId>spring-context</artifactId>
       <version>5.2.10.RELEASE</version>
   </dependency>

3、编写测试类

package com.xiao.bean;

import com.alibaba.fastjson2.JSONObject;

public class TestBean {

    private int id;

    private String name;

    public TestBean() {
        System.out.println("构造方法执行");
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    @Override
    public String toString() {
        return JSONObject.toJSONString(this);
    }
}

4、在 项目 src\main\resources\ 目录下建立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 class="com.xiao.bean.TestBean" id="testBean"/>
</beans>

5、测试

@Test
public void testBean(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    TestBean testBean = ((TestBean) applicationContext.getBean("testBean"));
    System.out.println(testBean.toString());
}

输出结果:

构造方法执行
{"id":0}

二、IOC 和 DI

IOC(控制反转)

相关概念

IOC:控制反转(Inversion of Control,简称IoC)是Spring框架的核心概念之一。它是一种设计原则,通过将对象的创建和依赖关系的管理交给框架来完成,从而实现了对象之间的解耦和灵活性。

在传统的开发模式中,对象之间的依赖关系通常是通过手动创建对象并直接调用其方法来实现的。而在IoC容器中,对象的创建和依赖关系的管理是由框架来完成的。开发人员只需要定义对象的配置信息,框架会根据配置信息自动创建对象,并将对象之间的依赖关系注入到相应的对象中。

Bean: 由Spring容器管理的对象被称作Bean。Bean是应用程序的核心组件,它们是在Spring容器中创建、组装和管理的。
Spring的Bean是一个由Spring容器实例化、组装和管理的对象。它们是应用程序的基本构建块,可以代表任何类型的对象,包括实体类、服务类、数据访问对象、控制器等。

Spring的Bean具有以下特点:

  • 实例化:Spring容器负责创建Bean的实例。
  • 组装:Spring容器负责将Bean的依赖关系注入到相应的属性或构造函数中。
  • 生命周期管理:Spring容器负责管理Bean的生命周期,包括初始化和销毁。
  • 配置:Bean的配置信息通常是通过XML配置文件、注解或Java配置类来定义的。
  • 单例或多例:Spring的Bean可以是单例的,也可以是多例的,可以根据需要进行配置。
  • 通过Spring的IoC容器,我们可以方便地创建和管理Bean对象,实现了对象之间的解耦和灵活性。同时,Spring的Bean还支持AOP(面向切面编程)等功能,使得应用程序的开发更加简化和高效。

创建bean的几种方式

  • 构造方法创建bean
    构造方法创建bean方式和入门案例一样。
<!--  构造方法创建bean  -->
<bean class="com.xiao.bean.TestBean" id="testBean"/>
  • 静态工厂方法创建bean

1、创建工厂类和静态工厂方法

package com.xiao.bean.factory;

import com.xiao.bean.TestBean;

public class TestBeanFactory {

    public static TestBean getTestBeanByStatic(){
        return new TestBean();
    }
}

2、创建 bean 配置

<!-- 静态工厂创建bean class 为静态工厂类   factory-method 为静态工厂方法 -->
<bean class="com.xiao.bean.factory.TestBeanFactory" id="testBeanByStaticFactory" factory-method="getTestBeanByStatic"/>
  • 实例工厂创建bean
    1、创建实例工厂方法
package com.xiao.bean.factory;

import com.xiao.bean.TestBean;

public class TestBeanFactory {
    public TestBean getTestBean(){
        return new TestBean();
    }
}

2、先创建工厂bean 再从工厂beanz中获取bean

<!--  先创建工厂bean  -->
<bean class="com.xiao.bean.factory.TestBeanFactory" id="testBeanFactory"/>
<!-- 从工厂bean 中调用工厂方法 获取bean 加载到 spring 容器中  factory-bean:工厂bean id  factory-method:工厂方法  -->
<bean class="com.xiao.bean.TestBean" id="testBeanByFactory" factory-bean="testBeanFactory" factory-method="getTestBean"/>

bean 的相关配置属性

  • id:指定Bean的唯一标识符,必须是唯一的。可以通过id来获取对应的Bean对象。

  • name:指定Bean的名称,可以是一个或多个名称,用逗号或分号分隔。可以通过名称来获取对应的Bean对象。

  • class:指定Bean的类名或类型。可以使用全限定类名或接口名称,Spring会根据指定的类名实例化对应的Bean对象。

  • scope:用于指定Bean的作用域,默认值是singleton。常见的作用域有:
        singleton:单例模式,每次获取该Bean时都返回同一个实例。
        prototype:原型模式,每次获取该Bean时都返回一个新的实例。
        request:Web应用中,每个HTTP请求都会创建一个新的实例。
        session:Web应用中,每个HTTP会话都会创建一个新的实例。
        global session:全局会话模式,通常用于基于portlet的Web应用。

  • lazy-init:指定Bean是否延迟初始化(懒加载),默认为false。如果设置为true,Bean将在第一次使用时才被实例化。

  • autowire:用于指定自动装配的方式。常见的取值有:
        no:不自动装配,默认值。
        byName:按照Bean的名称自动装配。
        byType:按照Bean的类型自动装配。
        constructor:按照构造函数参数类型自动装配。
        autodetect:自动检测,如果找到匹配的Bean,则按照byType方式装配;否则按照byName方式装配。

  • autowire-candidate:指定是否作为自动装配的候选Bean,默认为true。如果设置为false,该Bean将不参与自动装配。

  • depends-on:指定Bean的依赖关系,用于确保某些Bean在当前Bean之前被实例化。

  • init-method:在Bean初始化完成后调用的方法,用于执行一些初始化操作。

  • destroy-method:在Bean销毁前调用的方法,用于执行一些清理操作。

  • actory-bean:指定工厂Bean的名称,用于创建当前Bean的实例。

  • factory-method:指定工厂方法的名称,用于创建当前Bean的实例。

获取bean的方式及区别

BeanFactory

BeanFactory是Spring框架中的一个核心接口,用于管理和提供应用程序中的对象(即Bean)。它是Spring IoC(控制反转)容器的基础。
具体来说,BeanFactory负责以下任务:
对象实例化:BeanFactory负责在需要时创建Bean的实例。
依赖注入:BeanFactory通过将依赖关系注入到Bean中,管理Bean之间的依赖关系。
生命周期管理:BeanFactory管理Bean的生命周期,包括初始化、使用和销毁。
配置元数据管理:BeanFactory负责管理Bean的配置元数据,以确定如何创建和配置Bean。

ApplicationContext

ApplicationContext(应用上下文)是Spring框架中更高级别的容器,它扩展了BeanFactory接口,并提供了更多的功能和特性。
与BeanFactory相比,ApplicationContext在以下方面提供了更多的功能:

  • Bean生命周期管理:ApplicationContext提供了更强大的生命周期管理特性,包括Bean的初始化、销毁、后置处理等。

  • 自动装配:ApplicationContext支持自动装配(autowiring),通过自动识别Bean的依赖关系,自动将依赖注入到相应的Bean中。

  • 国际化支持:ApplicationContext提供了国际化支持,可以根据不同的区域设置加载相应的资源文件,实现多语言支持。

  • 事件机制:ApplicationContext支持事件机制,允许Bean发送事件并监听事件,实现Bean之间的解耦。

  • AOP支持:ApplicationContext集成了Spring的AOP功能,可以通过配置方式实现面向切面的编程。

  • 资源管理:ApplicationContext提供了统一的资源访问和管理,包括文件、URL、类路径等。

  • 配置元数据的灵活性:ApplicationContext支持多种类型的配置元数据,如XML、注解、JavaConfig等,提供了更灵活的配置方式。

总之,ApplicationContext是一个功能更加强大、更加高级的容器,提供了更多的特性和功能,使得开发人员能够更方便地管理和使用Spring框架中的对象。

俩者获取bean示例:

@Test
public void beanFactoryAndApplicationContext(){
     // beanFactory 获取bean
     ClassPathResource resource = new ClassPathResource("applicationContext3.xml");
     BeanFactory factory = new XmlBeanFactory(resource);

     TestBean testBean = ((TestBean) factory.getBean("testBean"));
     System.out.println(testBean);

     System.out.println("------------------------------------------------");

     ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext3.xml");
     TestBean testBean2 = ((TestBean) context.getBean("testBean"));
     System.out.println(testBean2);

 }

BeanFactory 和 ApplicationContext类图

俩者区别:

  1. 类图结构 ApplicationContext 继承自 BeanFactory 它扩展了BeanFactory接口,并提供了更多的功能和特性。
  2. BeanFactory默认是懒加载的,ApplicationContext 默认是即时加载的

DI(依赖注入)

相关概念

DI:依赖注入,Spring框架通过依赖注入(Dependency Injection,简称DI)来实现控制反转。依赖注入是指将一个对象所依赖的其他对象通过构造函数、属性或者方法参数的方式注入到该对象中。通过依赖注入,对象之间的依赖关系由框架来管理,开发人员只需要关注对象的功能实现。

控制反转和依赖注入使得应用程序的组件之间解耦,提高了代码的可维护性和可测试性。同时,它也使得应用程序的架构更加灵活,可以方便地替换、扩展和重用组件。

准备工作,建立TestBean类:

package com.xiao.bean;

import com.alibaba.fastjson2.JSONObject;
import lombok.Data;

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

@Data
public class TestBean {

    private int intValue;

    private String strValue;

    private RefBean refBean;

    private String[] strArr;

    private RefBean[] refBeanArray;

    private List<String> stringList;

    private List<RefBean> refBeanList;

    private Set<String> stringSet;

    private Set<RefBean> refBeanSet;

    private Map<String,String> map;

    private Map<String,RefBean> refBeanMap;

    private Map<String,List<RefBean>> stringListMap;

    private Properties properties;

    public TestBean() {
        System.out.println("构造方法执行");
    }

    public TestBean(int intValue, String strValue, RefBean refBean) {
        this.intValue = intValue;
        this.strValue = strValue;
        this.refBean = refBean;
    }

    @Override
    public String toString() {
        return JSONObject.toJSONString(this);
    }
}

依赖注入之构造方法注入

<!--  创建引用类型 refBean  -->
<bean class="com.xiao.bean.RefBean" id="refBean"/>

<!--  构造方法注入-根据属性名称注入  -->
<bean class="com.xiao.bean.TestBean" id="testBean">
	<constructor-arg name="intValue" value="1"/>
	<constructor-arg name="strValue" value="stringValue"/>
	<constructor-arg name="refBean" ref="refBean"/>
</bean>
<!--  构造方法注入-根据属性索引注入  -->
<bean class="com.xiao.bean.TestBean" id="testBean1">
	<constructor-arg index="0" value="1"/>
	<constructor-arg index="1" value="stringValue"/>
	<constructor-arg index="2" ref="refBean"/>
</bean>

依赖注入之set方法注入

<!--  set注入   -->
<bean class="com.xiao.bean.TestBean" id="testBean2">
	<!--   普通类型注入     -->
	<property name="intValue" value="1"/>
	<property name="strValue" value="stringValue"/>
	<!--   引用类型注入     -->
	<property name="refBean" ref="refBean"/>
</bean>

复杂类型注入

<bean class="com.xiao.bean.TestBean" id="testBean2">
	<!--   注入数组    -->
	<property name="strArr">
		<array>
			<value>arrStr1</value>
			<value>arrStr2</value>
		</array>
	</property>
	<!--   注入数组 (value 为引用类型)     -->
	<property name="refBeanArray">
		<array>
			<ref bean="refBean"/>
		</array>
	</property>
	<!--   注入List     -->
	<property name="stringList">
		<list>
			<value>str1</value>
			<value>str2</value>
		</list>
	</property>
	<!--   注入List (value 为引用类型)   -->
	<property name="refBeanList">
		<list>
			<ref bean="refBean"/>
			<ref bean="refBean1"/>
			<ref bean="refBean2"/>
		</list>
	</property>
	<!--   注入Set    -->
	<property name="stringSet">
		<set>
			<value>setStr1</value>
			<value>setStr2</value>
		</set>
	</property>
	<!--   注入Set (value 为引用类型)    -->
	<property name="refBeanSet">
		<set>
			<ref bean="refBean"/>
			<ref bean="refBean1"/>
			<ref bean="refBean2"/>
		</set>
	</property>
	<!--   注入Map    -->
	<property name="map">
		<map>
			<entry key="mapKey1" value="mapValue1"/>
			<entry key="mapKey2" value="mapValue2"/>
		</map>
	</property>
	<!--   注入Map (value 为引用类型)   -->
	<property name="refBeanMap">
		<map>
			<entry key="beanMapKey1" value-ref="refBean1"/>
			<entry key="beanMapKey2" value-ref="refBean2"/>
		</map>
	</property>
	<!--   注入Map (Map<String,List<RefBean>>)   -->
	<property name="stringListMap">
		<map>
			<entry key="stringListMap1">
				<list>
					<ref bean="refBean1"/>
				</list>
			</entry>
		</map>
	</property>
	<!--   Properties 类型   -->
	<property name="properties">
		<props>
			<prop key="username">root</prop>
			<prop key="password">123456</prop>
		</props>
	</property>
</bean>

从properties文件中注入属性

在项目中有时候需要将一些属性写成配置文件,达到当应用程序的运行环境发生改变时,只需要修改属性文件,而不需要改变源码的目的,例如 jdbc.properties、redis.properties,从properties配置文件加载属性大概分为以下步骤:

  1. 准备相关配置类和配置文件

建立配置类:

package com.xiao.bean;

import lombok.Data;

@Data
public class PropertiesBean {

    private String userName;

    private String password;

    @Override
    public String toString() {
        return "{" + "\"userName\":\"" + userName + '\"' + ",\"password\":\"" + password + '\"' + '}';
    }
}

在src\main\resources\properties 下建立一个 test.properties 文件,内容如下:

test.username=admin
test.password=123456
  1. 在Spring 配置文件l中开启开启context命名空间,加载properties属性文件
    在 beans 标签中加入下面三个约束:
    开启context命名空间
    Spring 配置文件中引入外部配置文件:
<!--引入外部配置文件-->
<context:property-placeholder location="classpath:properties/*.properties" ignore-unresolvable="false" ignore-resource-not-found="false" />

context:property-placeholder 相关属性说明:

  • location:指定属性文件的位置。可以使用类路径前缀(classpath:)或文件系统路径。可以指定多个属性文件,用逗号或分号分隔。

  • ignore-unresolvable:指定是否忽略无法解析的属性。默认值为 false,表示如果属性无法解析,将抛出异常。设置为 true 时,将忽略无法解析的属性。

  • ignore-resource-not-found:指定是否忽略找不到的属性文件。默认值为 false,表示如果属性文件找不到,将抛出异常。设置为 true 时,将忽略找不到的属性文件。

  • system-properties-mode:指定系统属性的处理方式。有三个可选值:
       never:不处理系统属性。
       fallback:如果属性文件中的属性不存在,则使用系统属性作为默认值。
       override:如果属性文件中的属性存在,则使用系统属性覆盖它。

  • properties-ref:指定一个 bean 的名称,该 bean 是一个 java.util.Properties 对象,用于存储属性文件中的属性。如果指定了该属性,则属性文件中的属性将被加载到该 Properties 对象中,而不是注入到 bean 中。

  1. 使用占位符 ${} 替换注入属性
 <bean class="com.xiao.bean.PropertiesBean" id="propertiesBean">
    <property name="userName" value="${test.username}"/>
     <property name="password" value="${test.password}"/>
 </bean>

测试:

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

    <!--引入外部配置文件-->
    <context:property-placeholder location="classpath:properties/*.properties" ignore-unresolvable="false" ignore-resource-not-found="false" />
    
    <bean class="com.xiao.bean.PropertiesBean" id="propertiesBean">
        <property name="userName" value="${test.username}"/>
        <property name="password" value="${test.password}"/>
    </bean>
</beans>
@Test
public void testProperties(){
     ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext2.xml");

     PropertiesBean propertiesBean = ((PropertiesBean) context.getBean("propertiesBean"));
     System.out.println(propertiesBean.getUserName());
     System.out.println(propertiesBean.getPassword());
 }

开启p命名空间 和 c命名空间 注入属性

通过构造函数或 setter 方法进行属性注入时,通常是在 元素中嵌套 和 元素来实现的。这种方式虽然结构清晰,但书写较繁琐。
Spring 框架提供了 2 种短命名空间,可以简化 Spring 的 XML 配置:

短命名空间 简化的 XML 配置说明
p 命名空间<bean> 元素中嵌套的 <property> 元素是 setter 方式属性注入的一种快捷实现方式
c 命名空间<bean> 元素中嵌套的 <constructor> 元素是构造函数属性注入的一种快捷实现方式

实现步骤:

  1. 在Spring beans标签添加 p 命名空间约束 和 c 命名空间约束:
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
  • 配置bean:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--  c 命名空间注入  -->
    <bean class="com.xiao.bean.TestBean" id="testBean3"  c:intValue="1" c:strValue="stringValue" c:refBean-ref="refBean" />

    <!--  p 命名空间注入  -->
    <bean class="com.xiao.bean.TestBean" id="testBean4"  p:strValue="stringVal" p:refBean-ref="refBean" />
</beans>

bean的生命周期

在bean 标签中存在俩个属性,init-method:在Bean初始化完成后调用的方法,destroy-method:在Bean销毁前调用的方法。

同时,在Spring 中还提供了俩个接口:

  • InitializingBean 接口:该接口定义了一个方法 afterPropertiesSet(),在 bean 的属性赋值完成后,容器会调用该方法进行初始化操作。可以在该方法中进行一些需要在属性赋值后立即执行的操作,例如数据初始化、资源加载等。
  • DisposableBean 接口:该接口定义了一个方法 destroy(),在容器关闭时,会调用该方法进行销毁操作。可以在该方法中进行一些需要在容器关闭前执行的清理操作,例如释放资源、关闭连接等。

使用InitializingBean和DisposableBean接口可以方便地在bean的生命周期中执行初始化和销毁操作,而无需手动配置和管理。但是,这种方式与Spring的依赖注入机制紧密耦合,不够灵活。因此,通常更推荐使用@PostConstruct和@PreDestroy注解或配置init-method和destroy-method来定义初始化和销毁方法。

我们创建一个类分别实现这俩个接口以及定义 initMethod 和 destroyMethod:

package com.xiao.bean;

import com.alibaba.fastjson2.JSONObject;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;

public class TestBean implements InitializingBean, DisposableBean {

    private String strValue;

    public TestBean() {
        System.out.println("构造方法执行");
    }

    public String getStrValue() {
        return strValue;
    }

    public void setStrValue(String strValue) {
        System.out.println("set 注入执行");
        this.strValue = strValue;
    }

    public void initMethod(){
        System.out.println("initMethod 执行");
    }

    public void destroyMethod(){
        System.out.println("destroyMethod 执行");
    }


    public void destroy() throws Exception {
        System.out.println("DisposableBean destroy 执行");
    }

    public void afterPropertiesSet() throws Exception {
        System.out.println("afterPropertiesSet 执行");
    }

    @Override
    public String toString() {
        return JSONObject.toJSONString(this);
    }
}

在bean标签中有俩个属性值会影响 bean 的创建时机 和销毁的时机,分别是scope 和 lazy-init,存在以下场景:

  • 单例模式 即时加载 (scope=“singleton” lazy-init=“false”)
<bean class="com.xiao.bean.TestBean" id="testBean" scope="singleton" lazy-init="false" init-method="initMethod" destroy-method="destroyMethod" p:strValue="stringValue"/>
@Test
public void testBean(){
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    TestBean testBean = ((TestBean) applicationContext.getBean("testBean"));
    applicationContext.destroy();
}

执行顺序:
构造方法执行 -----> 属性注入 -----> afterPropertiesSet() 执行 -----> initMethod() 执行 -----> DisposableBean.destroy() 执行 ----> destroyMethod()执行
其中 构造方法、属性注入、afterPropertiesSet()、 initMethod() 都是在容器创建的时候执行,
getBean() 只负责从容器中拿bean ,在 执行容器销毁的时候执行 DisposableBean.destroy() 和destroyMethod()。

  • 单例模式 延时加载(懒加载)(scope=“singleton” lazy-init=“true”)
 <bean class="com.xiao.bean.TestBean" id="testBean1" scope="singleton" lazy-init="true" init-method="initMethod" destroy-method="destroyMethod" p:strValue="stringValue"/>
@Test
public void testBean(){
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    
    TestBean testBean = ((TestBean) applicationContext.getBean("testBean1"));

    TestBean testBean2 = ((TestBean) applicationContext.getBean("testBean1"));

    applicationContext.destroy();
}

执行顺序:
构造方法执行 -----> 属性注入 -----> afterPropertiesSet() 执行 -----> initMethod() 执行 -----> DisposableBean.destroy() 执行 ----> destroyMethod()执行
执行顺序和即时加载一样,但不同的是 构造方法、属性注入、afterPropertiesSet()、 initMethod() 都是第一次getBean() 的时候执行,后面再获取这个bean 不执行 ,在 执行容器销毁的时候执行 DisposableBean.destroy() 和destroyMethod()。

  • 多例模式(scope=“prototype”)
<bean class="com.xiao.bean.TestBean" id="testBean2" scope="prototype"  init-method="initMethod" destroy-method="destroyMethod" p:strValue="stringValue"/>
@Test
public void testBean(){
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");

    TestBean testBean = ((TestBean) applicationContext.getBean("testBean2"));

    TestBean testBean2 = ((TestBean) applicationContext.getBean("testBean2"));

    applicationContext.destroy();
}

执行顺序:容器创建时不执行任何操作,每次获取bean时执行 构造方法执行 -----> 属性注入 -----> afterPropertiesSet() 执行 -----> initMethod() 执行
不执行 DisposableBean.destroy() 和 destroyMethod() ,原因是什么周期不受spring 容器控制。

总结:

  • 对于单例(scope=“singleton”) 类型的bean 只有一个实例,当lazy-init=“false” 时,生命周期由Spring 容器控制,随着容器的创建而创建,随着容器的销毁而销毁。
  • 对于原型作用域(scope=“prototype”)的 bean,每次获取都会创建一个新的实例,无论是否设置懒加载。每次获取bean的时候创建对象,销毁对象由JVM垃圾回收控制。

FactoryBean

在前面创建bean 方式中讲到可以通过bean 标签中设置工厂方法来创建一些复杂的bean 添加到 spring 容器中,但那种方式比较局限,spring 提供了一个更加灵活的接口FactoryBean 。
FactoryBean是Spring Framework中的一个接口,用于创建和管理复杂对象的工厂。它允许开发人员以灵活和可定制的方式创建对象,并将其作为依赖注入到应用程序的其他部分中。
FactoryBean接口定义了两个主要方法:

  • getObject():该方法负责创建并返回目标对象的实例。可以在此方法中执行任何必要的初始化或配置操作。
  • getObjectType():该方法返回FactoryBean创建的对象的类型。Spring容器在将对象注入到其他bean时使用该方法确定对象的类型。
  • isSingleton():该方法返回一个布尔值,指示FactoryBean创建的对象是否为单例。如果返回true,则表示创建的对象是单例,Spring容器将缓存并重用该对象;如果返回false,则表示创建的对象是原型,每次请求时都会创建一个新的对象实例。

通过实现FactoryBean接口,开发人员可以对对象的创建和配置具有精细的控制。可以定义自定义逻辑来创建对象,处理对象池,或在返回对象给容器之前执行任何其他必要的操作。

FactoryBean通常在创建对象复杂或需要额外配置的场景中使用。与仅使用new关键字或依赖注入构造函数相比,它提供了更灵活和可定制的对象创建方式。

TestFactoryBean.java

package com.xiao.bean.factory;

import com.xiao.bean.TestBean;
import org.springframework.beans.factory.FactoryBean;

public class TestFactoryBean implements FactoryBean<TestBean> {

    public TestBean getObject() throws Exception {
        TestBean testBean = new TestBean();
        testBean.setStrValue("使用FactoryBean创建的对象");
        return testBean;
    }

    public Class<?> getObjectType() {
        return TestBean.class;
    }

    public boolean isSingleton() {
        return true;
    }
}

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">
   <bean class="com.xiao.bean.factory.TestFactoryBean" id="testFactoryBean" />
</beans>
@Test
public void testFactoryBean(){
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

    TestBean testBean = ((TestBean) context.getBean("testFactoryBean"));
    TestBean testBean1 = ((TestBean) context.getBean("testFactoryBean"));
    System.out.println(testBean == testBean1);

    //如果要获取 TestFactoryBean 本身 则在 bean name 前加上 "&" 符号
    TestFactoryBean factoryBean = (TestFactoryBean)context.getBean("&testFactoryBean");
    TestFactoryBean factoryBean2 = (TestFactoryBean)context.getBean("&testFactoryBean");
    System.out.println(factoryBean == factoryBean2);
}

输出结果:

true
true

注意:使用FactoryBean实现类创建的对象,默认都是懒加载的。
当使用FactoryBean创建对象时,默认情况下,Spring容器会先创建FactoryBean本身的实例,并将其放入容器中。但是,FactoryBean创建的目标对象并不会立即创建,而是在第一次访问该对象时才会调用FactoryBean的getObject()方法来创建实际的对象。

这种懒加载的机制可以提高应用程序的性能和资源利用率,避免不必要的对象创建和初始化。只有在需要使用对象时才会创建,可以减少启动时间和内存消耗。

IOC相关注解

对于普通的xml配置方式开发,太过于繁琐,Spring 为我们提供了注解开发模式,需要开启注解开发,需要在Spring 的配置文件中开启组建扫描让Spring知道需要扫描哪些包下的注解,后文中@ComponentScan 具有同样的功效,使用该注解可以实现纯注解开发模式,连Spring配置文件都不需要了。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns: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">
    <!--
    开启组建扫描:让spring 容器能够识别注解
     base-package :指定需要扫描的包,可以配置多个包,
     中间使用逗号分隔,扫描到的包及其子包下所有能够被Spring容器识别的注解生效
     -->
    <context:component-scan base-package="com.xiao.*"/>
</beans>

创建bean相关注解

@Component

@Component 注解主要用于类级别上,用于将一个类标记为 Spring 容器中的组件。将我们自己写的类注入到Spring 容器中。

@Controller、@Service、@Repository

@Controller、@Service、@Repository 三个注解是@Component的衍生注解,功能与@Component一样,主要用在标记MVC三层架构上的注解。

注:
默认情况下,@Component 注解及其衍生注解 再普通类(类名首字母大写,第二个字母小写)将类名的第一个字母转换为小写,并使用转换后的字符串作为默认的 Bean ID 和 beanName,而对于特殊类(类名首字母和第二个人字母都是大写的类)。bean ID 和 bean Name 就是类名本身。

例如,对于以下代码:

@Component
public class MyComponent  {
}

@Component
public class MYComponent2 {
}

//测试
public class SpringAnnotationTest {
    @Test
    public void IocAnnotationTest(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-anno.xml");

        MyComponent myComponent = (MyComponent)applicationContext.getBean("myComponent");

        MYComponent2 myComponent2 = (MYComponent2)applicationContext.getBean("MYComponent2");
    }
}
@Bean

@Bean 注解主要用于方法上,用于将当前方法的返回值注入到Spring 容器中,默认id为方法名,一般用于注入第三方包内的类。
注:@Bean注解标记的方法必须出现在@Component注解或其衍生注解标记的类中,或者出现在Spring 的配置类中(@Configuration)

@Component
public class SpringConfig {
    @Bean
    public UserServiceImpl UserService(){
        return new UserServiceImpl();
    }
}
//测试方法
@Test
public void IocAnnotationTest(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-anno.xml");
    Object object = applicationContext.getBean("UserService");
    System.out.println(object);
    //输出 com.xiao.service.impl.UserServiceImpl@3d680b5a
}

依赖(属性)注入相关注解

@Autowired 和 @Qualifier

@Autowired 是 Spring 框架中的一个注解,用于自动装配(自动注入)依赖对象。

当一个类中需要使用其他类的实例时,通常需要通过创建对象、设置属性或者通过构造函数传递依赖对象。而使用 @Autowired 注解可以让 Spring 框架自动为我们完成这些操作,将依赖对象自动注入到需要的地方。

@Autowired 注解可以用在字段、构造函数、Setter 方法上。当 Spring 容器启动时,会自动扫描并创建被 @Autowired 注解标记的对象,并将其注入到需要的地方。

使用 @Autowired 注解时,Spring 框架会根据类型进行自动装配。如果容器中存在多个匹配的对象,可以通过 @Qualifier 注解指定具体的对象名称进行装配。但需要注意的是 @Qualifier 不能作用在构造方法上。

测试代码:
service:

public interface UserService {
    User queryUser(long id);
}

@Service
public class UserServiceImpl implements UserService {

    public User queryUser(long id){
        System.out.println("query user 方法执行");
        User user = new User();
        user.setId(id);
        user.setAge(18);
        user.setUserName("张三");
        return user;
    }
}


@Service
public class UserServiceImpl2 implements UserService {
    public User queryUser(long id) {
        User user = new User();
        user.setId(id);
        user.setAge(19);
        user.setUserName("李四");
        return user;
    }
}


@Service
public class UserServiceImpl3 implements UserService {
    public User queryUser(long id) {
        User user = new User();
        user.setId(id);
        user.setAge(20);
        user.setUserName("王五");
        return user;
    }
}

controller:

@Controller
public class UserController {

    @Autowired
    @Qualifier("userServiceImpl")
    private UserService userService;

    public UserController() {
    }

    /**
     * 构造方法注入
     */
    @Autowired
    public UserController(@Qualifier("userServiceImpl2") UserService userService) {
        this.userService = userService;
    }


    public UserService getUserService() {
        return userService;
    }

    /**
     * set注入
     */
    @Autowired
    public void setUserService(@Qualifier("userServiceImpl3") UserService userService) {
        this.userService = userService;
    }

    /**
     * 调用service 
     */
    public void getUser(){
        User user = userService.queryUser(1L);
        System.out.println(user);
    }
}

测试:

public class SpringAnnotationTest {
    @Test
    public void IocAnnotationTest(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-anno.xml");
        UserController bean = applicationContext.getBean(UserController.class);
        bean.getUser();
        //输出 {"id":1,"userName":"王五","age":20}
    }
}
@Resource

@Resource 是 Java EE 中的一个注解,用于进行依赖注入(DI)和资源查找。它可以用于注入依赖对象、查找和引用容器中的资源。

@Resource 注解可以用在字段、Setter 方法和构造函数上。当使用 @Resource 注解时,容器会根据名称或类型进行自动装配。

如果使用 @Resource 注解时不指定 name 或 type 属性,那么默认按照字段或方法的名称进行查找和注入。如果指定了 name 属性,容器会根据指定的名称查找对应的资源或依赖对象进行注入。如果指定了 type 属性,容器会根据指定的类型查找对应的资源或依赖对象进行注入。

测试代码:
service 沿用 上面@Autowired 案例的代码。
controller:

@Controller
public class UserController {

    /**
     * 默认根据属性名注入
     */
    @Resource
    private UserService userServiceImpl;

    public UserController() {
    }

    /**
     * set注入
     */
    @Resource(name = "userServiceImpl2")
    public void setUserService( UserService userService) {
        this.userServiceImpl = userService;
    }

    /**
     * 调用service
     */
    public void getUser(){
        User user = userServiceImpl.queryUser(1L);
        System.out.println(user);
    }
}

测试代码:

public class SpringAnnotationTest {
    @Test
    public void IocAnnotationTest(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-anno.xml");
        UserController bean = applicationContext.getBean(UserController.class);
        bean.getUser();
        //输出:{"id":1,"userName":"李四","age":19}
    }
}
@Autowired 和 @Resource 区别
  • 俩个注解都能注入依赖,@Autowired 是Spring 的注解,而@Resource 是java的注解;

  • @Autowired 是先根据类型(byType)查找,如果存在多个 Bean 再根据名称(byName)进行查找

  • @Resource 是先根据名称查找,如果(根据名称)查找不到,再根据类型进行查找

@Value

@Value 是 Spring Framework 中的注解之一,用于将值赋给类的字段或方法参数。

通过在字段、方法参数或方法返回值上添加 @Value 注解,可以将指定的值注入到目标对象中。这样可以方便地将配置文件中的值、环境变量等注入到代码中。

作用在字段(成员变量)、方法上、方法参数上(局部变量)上:

package com.xiao.controller;

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

@Controller
public class UserController {

    //作用在字段(成员变量)上
    @Value("张三")
    private String userName;

    public String getUserName() {
        return userName;
    }

    //作用在方法和方法参数上
    @Value("李四")
    public void setUserName(@Value("王老七") String userName) {
        this.userName = userName;
    }
}

测试:

public class SpringAnnotationTest {
    @Test
    public void IocAnnotationTest(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-anno.xml");
        UserController userController = applicationContext.getBean(UserController.class);
        System.out.println(userController.getUserName());
        //输出  王老七
    }
}

从配置文件中读取参数注入:
上面列举过通过xml 配置方式从配置文件中读取属性注入,下面我们列举如何通过注解实现从配置文件中注入:
1、添加Spring配置类,并在配置类中添加@PropertySource 注解,用于扫描配置
依旧沿用src/main/resources/properties 的配置文件,建了一个spring配置类:

package com.xiao.config;

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

@Configuration
@ComponentScan(basePackages = "com.xiao.*")
@PropertySource("classpath:properties/test.properties")
public class SpringConfig {

}

UserController:

package com.xiao.controller;

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

@Controller
public class UserController {

    @Value("${test.username}")
    private String userName;

    public String getUserName() {
        return userName;
    }
}

测试:

package com.xiao.test;


import com.xiao.config.SpringConfig;
import com.xiao.controller.UserController;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class SpringAnnotationTest {
    @Test
    public void IocAnnotationTest(){
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        UserController userController = applicationContext.getBean(UserController.class);
        System.out.println(userController.getUserName());
        //输出  admin
    }
}

其他注解

@Configuration

@Configuration注解用于标识一个类作为Spring配置类,它充当了传统XML配置文件的替代品。通过@Configuration注解,可以将Java类声明为应用程序上下文的Bean定义的源。只能作用在类上,该注解存在以下属性:

  • name:用于为配置类指定一个名称。默认情况下,使用类名的首字母小写作为配置类的名称。可以使用@Configuration(“name”)指定不同的名称。
  • proxyBeanMethods:默认值为true。在Spring Boot 2.2及更高版本中引入了这个参数。它控制是否使用CGLIB代理来创建@Bean方法的实例(仅适用于方法级别的@Bean声明)。设置为true时,将启用代理,以便支持容器间的依赖关系和条件化Bean注册。设置为false时,将禁用代理,使得@Bean方法直接返回原始实例。
@ComponentScan

@ComponentScan注解用于指定Spring容器扫描和自动装配组件的规则。通过@ComponentScan注解,可以告诉Spring在哪些包下进行扫描,并注册自动装配的组件。

以下是一些相关参数的定义:

  • basePackages:指定要扫描的包的名称。可以使用字符串数组指定多个包,也可以使用basePackageClasses属性指定一个或多个类所在的包。这个参数的默认值为空数组,表示从注解所在的类的包开始扫描。
  • basePackageClasses:指定要扫描的类所在的包。当需要指定多个类所在的包时,可使用该属性。
  • includeFilters:用于指定包扫描时包含的过滤规则。可以通过@ComponentScan.Filter注解定义过滤规则,例如只包含特定类型的组件,或者使用自定义的过滤器。
  • excludeFilters:用于指定包扫描时排除的过滤规则。同样可以通过@ComponentScan.Filter注解定义过滤规则。
  • lazyInit:标识是否延迟初始化扫描到的组件,默认为false。当设置为true时,容器在需要使用组件时才实例化它。
  • useDefaultFilters:指定是否使用默认的过滤规则,默认为true。如果设置为false,则需要手动通过includeFilters和excludeFilters指定扫描和排除的规则。

@ComponentScan.Filter注解用于定义过滤规则,有以下几种过滤类型:

  • FilterType.ANNOTATION:按照注解类型过滤。
  • FilterType.ASSIGNABLE_TYPE:按照指定类型过滤。
  • FilterType.ASPECTJ:使用AspectJ表达式过滤。
  • FilterType.REGEX:使用正则表达式过滤。
  • FilterType.CUSTOM:使用自定义的过滤器类过滤。
    例如:
@Configuration
@ComponentScan(basePackages = "com.example", excludeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = "com.example.excluded.*"))
public class AppConfig {
   // 配置类的内容
}

在上述示例中,AppConfig是一个配置类,通过@ComponentScan注解指定要扫描的包为com.example,并排除了以com.example.excluded开头的包。

@Lazy

@Lazy注解是Spring框架中的一个注解,用于指定Bean的延迟初始化方式。通过@Lazy注解,可以将Bean的实例化过程推迟到第一次使用该Bean时进行。

需要注意的是,@Lazy注解可以用于类级别或方法级别。当应用于类级别时,表示整个Bean会被延迟初始化;当应用于方法级别时,表示特定的方法会被延迟初始化。

另外,@Lazy注解只对单例(Singleton)作用域的Bean起作用,对于原型(Prototype)作用域的Bean,不支持延迟初始化。

以下是一些相关参数的定义:

  • value:布尔值,表示是否启用延迟初始化。默认为true。设置为true时,表示启用延迟初始化;设置为false时,表示不启用延迟初始化。

  • proxyBeanMethods:布尔值,表示是否在使用延迟初始化的同时也要使用代理模式。默认为true。设置为true时,Spring会创建一个代理对象来管理延迟初始化的Bean;设置为false时,Spring不会使用代理对象。

@Scope

@Scope和基于xml配置中的bean 标签中的 scop 属性功能一致。

@Import

@Import注解是Spring框架中的一个注解,用于在配置类中导入其他配置类或Bean定义。通过@Import注解,可以实现配置类的模块化和组合。
例如:

@Configuration
@Import({OtherConfig.class, Bean1.class, Bean2.class})
public class MyConfig {
   // 配置类的内容
}

在上述示例中,MyConfig是一个被注解为@Configuration的配置类,同时使用了@Import注解。通过@Import注解,导入了OtherConfig配置类和Bean1、Bean2两个Bean定义。

需要注意的是,导入的配置类或Bean定义会成为当前配置类中的一部分,并在Spring容器中进行管理。这样可以实现配置类的模块化和复用。

另外,@Import注解可以应用于类级别。它可以与其他Spring注解(如@Configuration、@ComponentScan等)一起使用,以实现更复杂的配置。

@PostConstruct 和 @PreDestroy

@PostConstruct注解用于指定一个方法,在Bean初始化完成后自动执行。这个方法将在依赖注入完成后、以及任何指定的init方法之前被调用。它常用于执行一些初始化操作,例如数据加载、资源分配等。

@PreDestroy注解用于指定一个方法,在Bean销毁之前自动执行。这个方法将在容器关闭之前、以及任何指定的destroy方法之前被调用。它常用于执行一些清理操作,例如资源释放、连接关闭等。

三、AOP

相关概念

AOP(Aspect-Oriented Programming)是一种编程范式,它通过将横切关注点(如日志记录、事务管理等)与业务逻辑分离,提高了代码的可维护性和重用性。

以下是对AOP相关概念的解释和理解:

  • 连接点(Joinpoint):在应用程序执行过程中可以插入额外代码的特定点,例如方法调用、异常抛出等。连接点是AOP的基础。
  • 切点(Pointcut):用于定义一组连接点的表达式,指定在哪些连接点上应用通知。切点是连接点的集合。
  • 切面(Aspect):切面是通知和切点的组合,它定义了在哪些连接点上应用哪些通知。切面是横切关注点的模块化单元。
  • 通知(Advice):在切面中定义的具体行为,它是在连接点上执行的代码,例如前置通知、后置通知等。通知是切面的具体实现。
  • 目标(Target):被通知的对象,也就是应用程序中的原始对象。通知可以在目标对象的方法执行前、后或异常抛出时被触发。
  • 植入(Weaving):将切面应用到目标对象上的过程,可以在编译时、类加载时或运行时进行。植入是将切面与目标对象关联起来的过程。
  • 代理(Proxy):AOP框架创建的对象,它包装了目标对象,并拦截对目标对象的方法调用,以便应用通知。代理对象负责将通知应用到目标对象上,实现了横切逻辑的织入。

AOP通知

Spring AOP有以下几种通知:

  • 前置通知(Before advice):在目标方法执行之前执行的通知。
  • 后置通知(After advice):在目标方法执行之后执行的通知,无论目标方法是否抛出异常。
  • 返回通知(After returning advice):在目标方法正常返回之后执行的通知。
  • 异常通知(After throwing advice):在目标方法抛出异常之后执行的通知。
  • 环绕通知(Around advice):包围目标方法的通知,可以在目标方法执行前后进行额外的处理。

AOP应用

  1. 引入相关依赖:
<!--spring核心依赖,会将spring-aop传递进来-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.10.RELEASE</version>
        </dependency>
        <!--切入点表达式依赖,目的是找到切入点方法,也就是找到要增强的方法-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.4</version>
        </dependency>
  1. 准备相关代码
public interface UserService {
    User queryUser(long id);
}

public class UserServiceImpl implements UserService {

    public User queryUser(long id) {
        System.out.println("query user 方法执行");
        User user = new User();
        user.setId(id);
        user.setAge(18);
        user.setUserName("张三");
        return user;
    }
}

通知(增强)类:

package com.xiao.aspect;

import com.alibaba.fastjson2.JSONObject;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;

import java.util.Arrays;

/**
 * 通知(增强)类
 */
public class TestAdvice {

    public void beforeAdvice(JoinPoint joinPoint){
        //获取 切入点的 方法名
        String methodName = joinPoint.getSignature().getName();
        // 获取切入点的 方法传参
        Object[] args = joinPoint.getArgs();
        String params = args == null?"": Arrays.toString(args);
        System.out.println(methodName+"方法 前置通知执行......方法参数:"+params);
    }

    public void afterAdvice(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        String params = args == null?"": Arrays.toString(args);
        System.out.println(methodName+"方法 后置通知执行......方法参数:"+params);
    }

    public void afterReturningAdvice(JoinPoint joinPoint,Object object){
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        String params = args == null?"": Arrays.toString(args);
        String returnMessage = JSONObject.toJSONString(object);
        System.out.println(methodName+"方法 后置返回通知执行......方法参数:"+params+" 方法返回值:"+returnMessage);
    }

    public void afterThrowingAdvice(JoinPoint joinPoint,Exception exception){
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        String params = args == null?"": Arrays.toString(args);
        System.out.println(methodName+"方法 异常通知执行......方法参数:"+params+"异常信息:"+exception.getMessage());
    }

    public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        String methodName = proceedingJoinPoint.getSignature().getName();
        Object[] args = proceedingJoinPoint.getArgs();
        String params = args == null?"": Arrays.toString(args);

        System.out.println("环绕通知 方法"+methodName+"执行前执行......方法参数:"+params);

        Object result = proceedingJoinPoint.proceed();

        System.out.println("环绕通知 方法"+methodName+"执行后执行......");

        return result;
    }
}
  1. 配置切面
<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">
	
    <bean id="userService" class="com.xiao.service.impl.UserServiceImpl"/>


    <!--  通知(增强)对象  -->
    <bean id="testAdvice" class="com.xiao.aspect.TestAdvice"/>
    <aop:config>
        <!--    定义切点 哪些连接点可以被增强     -->
        <aop:pointcut id="servicePointCut" expression="execution(* com.xiao.service..*(..))"/>
        <aop:aspect ref="testAdvice">
			<!--    前置通知     -->
            <aop:before method="beforeAdvice" pointcut-ref="servicePointCut"/>
			<!--    后置通知    -->
            <aop:after method="afterAdvice" pointcut-ref="servicePointCut"/>
			<!--    后置返回通知 需要配置 returning 且 returning 配置的名称需要和 返回后置通知的方法 afterReturningAdvice 的参数 Object object 名称一致  -->
            <aop:after-returning method="afterReturningAdvice" returning="object" pointcut-ref="servicePointCut"/>
			<!--    异常通知   需要配置 throwing 且 throwing 配置的名称需要和异常通知方法 afterThrowingAdvice 的参数 Exception exception 名称一致    -->
            <aop:after-throwing method="afterThrowingAdvice" throwing="exception" pointcut-ref="servicePointCut"/>
			<!--    环绕通知     -->
            <aop:around method="aroundAdvice" pointcut-ref="servicePointCut"/>
        </aop:aspect>
    </aop:config>
</beans>
  1. 测试
@Test
public void testAopXml() {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("aopContext.xml");

    UserService userService = (UserService) applicationContext.getBean("userService");
    User user = userService.queryUser(1L);

    System.out.println(JSONObject.toJSONString(user));
}

执行结果:

queryUser方法 前置通知执行......方法参数:[1]
环绕通知 方法queryUser执行前执行......方法参数:[1]
query user 方法执行
环绕通知 方法queryUser执行后执行......
queryUser方法 后置返回通知执行......方法参数:[1] 方法返回值:{"age":18,"id":1,"userName":"张三"}
queryUser方法 后置通知执行......方法参数:[1]
{"age":18,"id":1,"userName":"张三"}

切面表达式

在Spring AOP中,可以使用切面表达式来定义切点,以指定在哪些连接点上应用通知(定义哪些方法可以被增强)。切面表达式使用execution关键字来指定方法的执行,其语法如下:

execution( [方法的修饰符] 方法的返回类型 方法所在的类的全限定名.方法的名称 (方法参数类型) [throws 方法抛出的异常类型])

其中,各个部分的含义如下:

  • 方法的修饰符:例如public、private等。可以使用*表示任意修饰符,可以省略。
  • 方法的返回类型:可以使用*表示任意返回类型。
  • 方法所在的类的全限定名:包名与类名之间一个点 (.) 代表当前包下的类,两个点(…)表示当前包及其子包下的类 可以使用*表示任意 。
  • 方法的名称:可以使用*表示任意方法名,也可以使用正则表达式进行匹配。
  • 方法的参数类型:使用两个点(…)表示任意个数,任意类型的参数列表。
  • 方法抛出的异常类型:可以使用*表示任意异常类型。(必须在目标方法上显示的抛出异常 与是否发生异常无关,单纯的 为方法上显示抛出该异常的方法增强 )

例如,以下是一些切面表达式的示例:

execution(public * com.example.service…(…)):匹配 com.example.service 包下(含子包)所有public方法。

execution(* com.example.service.UserService.*(…)):匹配 com.example.service.UserService 类中的所有方法。

execution(* com.example.service.UserService.find*(String)):匹配 com.example.service.UserService 类中以find开头且有一个String参数的方法。

execution(* com.example.service…(…) throws java.sql.SQLException):匹配com.example.service包下所有抛出java.sql.SQLException异常的方法。

多个切面控制执行顺序

在Spring AOP中,切面的order属性用于指定切面的执行顺序。切面的order属性是一个整数值,默认为0。
order值越小,优先级越高,即先执行。如果多个切面具有相同的order值,则它们的执行顺序将根据它们在配置中的声明顺序来确定,在xml 配置文件中位置越前越先执行。

 <aop:aspect ref="logAdvice" order="1">
     <aop:before method="beforeAdvice" pointcut-ref="pointCut"/>
     <aop:after method="afterAdvice" pointcut-ref="pointCut"/>
 </aop:aspect>

实现原理

Spring AOP(面向切面编程)的实现原理是基于动态代理技术。在 Spring AOP 中,主要使用了两种类型的代理:JDK 动态代理和 CGLIB 代理。

  • 当目标类实现了至少一个接口时,Spring AOP 使用 JDK 动态代理。
  • 当目标类没有实现任何接口时,Spring AOP 使用 CGLIB 代理。
  • 当 proxy-target-class=“true” 时,Spring AOP 将使用 CGLIB 代理来创建代理对象。
    proxy-target-class参数

Aop注解式开发

添加配置类

package com.xiao.config;

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

@Configuration
@ComponentScan(basePackages = "com.xiao.*")
/*Spring会自动检测标记为@Aspect的切面类,并在IOC容器中创建相应的代理对象。
这样,当调用被切入的目标方法时,Spring会自动触发切面中定义的通知逻辑。
 */
@EnableAspectJAutoProxy
public class SpringConfig {


}

定义切面类(通知增强类)

package com.xiao.aspect;

import com.alibaba.fastjson2.JSONObject;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Arrays;

/**
 * 通知(增强)类
 */
@Component
@Aspect
public class TestAdvice {

    @Before(value = "execution(* com.xiao.service..*(..))")
    public void beforeAdvice(JoinPoint joinPoint){
        //获取 切入点的 方法名
        String methodName = joinPoint.getSignature().getName();
        // 获取切入点的 方法传参
        Object[] args = joinPoint.getArgs();
        String params = args == null?"": Arrays.toString(args);
        System.out.println(methodName+"方法 前置通知执行......方法参数:"+params);
    }

    @After("execution(* com.xiao.service..*(..))")
    public void afterAdvice(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        String params = args == null?"": Arrays.toString(args);
        System.out.println(methodName+"方法 后置通知执行......方法参数:"+params);
    }

    @AfterReturning(value = "execution(* com.xiao.service..*(..))",returning = "object")
    public void afterReturningAdvice(JoinPoint joinPoint,Object object){
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        String params = args == null?"": Arrays.toString(args);
        String returnMessage = JSONObject.toJSONString(object);
        System.out.println(methodName+"方法 后置返回通知执行......方法参数:"+params+" 方法返回值:"+returnMessage);
    }

    @AfterThrowing(value = "execution(* com.xiao.service..*(..))",throwing = "exception")
    public void afterThrowingAdvice(JoinPoint joinPoint,Exception exception){
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        String params = args == null?"": Arrays.toString(args);
        System.out.println(methodName+"方法 异常通知执行......方法参数:"+params+"异常信息:"+exception.getMessage());
    }

    @Around("execution(* com.xiao.service..*(..))")
    public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        String methodName = proceedingJoinPoint.getSignature().getName();
        Object[] args = proceedingJoinPoint.getArgs();
        String params = args == null?"": Arrays.toString(args);

        System.out.println("环绕通知 方法"+methodName+"执行前执行......方法参数:"+params);

        Object result = proceedingJoinPoint.proceed();

        System.out.println("环绕通知 方法"+methodName+"执行后执行......");

        return result;
    }
}

四、Spring事务管理

Spring事务是用于管理数据库事务的框架,它提供了对声明式事务管理和编程式事务管理的支持。Spring事务管理的主要目标是简化事务管理的代码,并提供灵活性和可配置性。

在Spring中,事务管理是通过AOP(面向切面编程)实现的。它允许开发人员在需要进行事务管理的方法上添加注解或XML配置,然后由Spring框架自动处理事务的开始、提交和回滚等操作。

我们可以通过下面三种方式实现Springs事务:

基于xml声明式事务控制

Spring 可以基于xml声明事务,具体步骤如下:
1、spring 整合 mybatis
2、配置事务管理器
3、配置事务通知和切面
src\main\resources\properties\jdbc.properties

db.driver=com.mysql.jdbc.Driver
db.url=jdbc:mysql://localhost:3306/nf
db.userName=root
db.password=123456
#初始化连接数
db.pool.init=3
#高峰期过后,保留连接吃的个数
db.pool.minIdle=5
#高峰期,最大能创建连接的个数
db.pool.MaxActive=20
#等待的时间
db.pool.timeout=30

src\main\resources\mybatis-spring.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:tx="http://www.springframework.org/schema/tx"
       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/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:property-placeholder location="classpath:properties/jdbc.properties"/>
    <!--  开启组建扫描  -->
    <context:component-scan base-package="com.xiao.*"/>

    <!--配置Druid DataSources-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${db.driver}"/>
        <property name="url" value="${db.url}"/>
        <property name="username" value="${db.userName}"/>
        <property name="password" value="${db.password}"/>
        <property name="initialSize" value="${db.pool.init}"/>
        <property name="minIdle" value="${db.pool.minIdle}"/>
        <property name="maxActive" value="${db.pool.MaxActive}"/>
        <property name="maxWait" value="${db.pool.timeout}"/>
    </bean>

    <!--  配置SqlSessionFactory  -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--配置数据源-->
        <property name="dataSource" ref="dataSource"/>
        <!--配置需要mybatis的主配置文件-->
        <property name="configLocation" value="mybatis.xml"/>
        <property name="mapperLocations">
            <list>
                <value>classpath*:mapper/**/*.xml</value>
            </list>
        </property>
    </bean>

    <!--  在配置MapperScannerConfigurer中主要是加载dao包中的所有dao接口,
    通过SqlSessionFactory获取SqlSession,然后创建所有dao接口对象,存储在spring容器  -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer" >
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
        <property name="basePackage" value="com.xiao.dao"/>
    </bean>

    <!--  事务管理器  -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" >
        <property name="dataSource" ref="dataSource"/>
    </bean>

     <!-- 配置事务通知   -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="*" propagation="REQUIRED" isolation="DEFAULT" timeout="-1" rollback-for="" no-rollback-for="" read-only="false"/>
        </tx:attributes>
    </tx:advice>

    <!--  定义切面  -->
    <aop:config>
        <aop:pointcut id="transactionalPointCut" expression="execution(* com.xiao.service..*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="transactionalPointCut"/>
    </aop:config>
</beans>

service、dao

public interface UserService {

    User queryUser(long id);

    int transfer(long sourcesId, long targetId, BigDecimal transferMoney);
}

@Service
public class UserServiceImpl implements UserService {

    @Resource
    private UserDao userDao;

    public User queryUser(long id) {
        return userDao.queryUserById(id);
    }

    public int transfer(long sourcesId, long targetId, BigDecimal transferMoney){
        User sourcesUser = userDao.queryUserById(sourcesId);
        if(sourcesUser == null){
            throw new RuntimeException("转出用户不存在");
        }
        User targetUser = userDao.queryUserById(targetId);
        if(targetUser == null){
            throw new RuntimeException("转入用户不存在");
        }
        //转出
        userDao.updateUserMoney(sourcesId,transferMoney,1);
        //执行出异常
        int i = 1/0;
        //转入
        userDao.updateUserMoney(targetId,transferMoney,0);
        return 1;
    }
}

@Repository
public interface UserDao {

    /**
     * 查找用户信息
     * @param id id
     * @return 用户信息
     */
    User queryUserById(@Param("id") long id);


    /**
     * 更新金额
     * @param id id
     * @param operateMoney 操作金额
     * @param operateType 操作类型 0:增加钱    1:扣钱
     * @return 操作数量
     */
    int updateUserMoney(@Param("id") long id,
                        @Param("operateMoney") BigDecimal operateMoney,
                        @Param("operateType") int operateType);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.xiao.dao.UserDao">
    <select id="queryUserById" resultType="com.xiao.pojo.User">
        SELECT id,user_name AS userName,money FROM test_user WHERE id = #{id}
    </select>
    
    <update id="updateUserMoney">
        UPDATE test_user SET
        <if test="operateType == 0">
            money = money + #{operateMoney},
        </if>
        <if test="operateType == 1">
            money = money - #{operateMoney},
        </if>
        update_time = NOW()
        WHERE id = #{id}
    </update>
</mapper>

测试:

public class SpringTransactionalTest {
    @Test
    public void transactionalTest(){
        ApplicationContext context = new ClassPathXmlApplicationContext("mybatis-spring.xml");
        UserService userService = context.getBean(UserService.class);
        userService.transfer(1,2,new BigDecimal("10"));
    }
}

事务相关属性介绍

  • name:指定方法名或者方法名的通配符,用于匹配需要应用事务的方法。可以使用*通配符来匹配所有方法。
  • propagation:指定事务的传播行为。默认值为REQUIRED。
  • isolation:指定事务的隔离级别。默认值为DEFAULT 表示使用底层数据库默认的隔离级别。
  • timeout:指定事务的超时时间,单位为秒。如果事务执行时间超过指定的超时时间,事务将被回滚。默认值为-1,表示不设置超时时间。
  • read-only:指定事务是否为只读事务。如果设置为true,表示事务只读,不允许对数据库进行修改操作。默认值为false。
  • rollback-for:指定需要回滚的异常类型。可以指定一个或多个异常类型,多个异常类型之间使用逗号分隔。
  • no-rollback-for:指定不需要回滚的异常类型。可以指定一个或多个异常类型,多个异常类型之间使用逗号分隔。

事务的传播行为

  • REQUIRED(默认):如果当前存在事务,则加入该事务;如果没有事务,则创建一个新事务。这是最常用的传播行为,确保方法始终在一个事务中执行。
  • REQUIRES_NEW:无论当前是否存在事务,方法都要创建一个新的事务,将已存在的事务挂起。即使调用者方法已经在一个事务中,被调用方法也会创建一个新的事务,并独立于调用者事务的提交或回滚。
  • SUPPORTS:如果当前存在事务,则加入该事务;如果没有事务,则以非事务方式执行。这意味着方法可以使用已存在的事务,也可以不使用事务。
  • NOT_SUPPORTED:方法不应在事务中执行。如果当前存在事务,则将其挂起,并在方法执行完毕后恢复事务。
  • MANDATORY:方法必须在一个已存在的事务中执行。如果当前没有事务,则抛出异常。
  • NEVER:方法不应在事务中执行。如果当前存在事务,则抛出异常。
  • NESTED:方法在一个已存在的事务中执行,但是可以在一个已存在的事务中创建一个嵌套事务。嵌套事务是独立于外部事务的子事务,它可以独立地提交或回滚。如果外部事务回滚,则嵌套事务也会被回滚,但是嵌套事务的提交并不会影响外部事务的提交。

事务的隔离级别

在mysql中我们可以通过:

show variables like 'tx_isolation';

SELECT @@tx_isolation;

来查看隔离级别。

  • READ UNCOMMITTED(读未提交):事务可以读取其他事务未提交的数据,可能会出现脏读、不可重复读和幻读的问题。

  • READ COMMITTED(读已提交):事务只能读取其他事务已经提交的数据,可以避免脏读问题,但仍可能出现不可重复读和幻读的问题。

演示:

读取已提交

  • REPEATABLE READ(可重复读):能确保同一事务多次读取同一数据的结果是一致的。可以解决脏读的问题,但理论上无法解决幻读(Phantom Read)的问题。幻读:某一个范围内的数据行数变多了或变少了,是 INSERT 和 DELETE 操作
    演示:
    可重复读
  • SERIALIZABLE(串行化):事务串行执行,可以避免脏读、不可重复读和幻读的问题,但会降低并发性能。

基于注解式事务控制

此外,我们可以基于 @Transactional 实现事务控制,具体步骤如下:
1、开启注解事务配置

 <!--  开启事务注解  -->
    <tx:annotation-driven transaction-manager="transactionManager"/>

2、在Service 方法上加上@Transactional 注解

@Service
public class UserServiceImpl implements UserService {

    @Resource
    private UserDao userDao;

    public User queryUser(long id) {
        return userDao.queryUserById(id);
    }

    @Transactional
    public int transfer(long sourcesId, long targetId, BigDecimal transferMoney){
       //
    }
}

编程式事务控制

硬编码型事务可以帮助我们更好的控制事务的粒度,比如说在 @Transactional 注解的方法中存在远程调用或者一些耗时的非数据库操作,这会将事务耗时拉长,这类事务被称作大事务,如果并发请求过多从而导致占用数据库连接导致连接不够用等情况。
针对上面这种情况我们可以通过硬编码的方式来控制事务。

纯编程式事务涉及到以下三个类:

DataSourceTransactionManager

DataSourceTransactionManager是Spring框架提供的一个实现了PlatformTransactionManager接口的事务管理器。它用于管理基于数据源(DataSource)的事务。

使用DataSourceTransactionManager可以对数据库操作进行事务管理。它有三个核心方法

  • public final TransactionStatus getTransaction(TransactionDefinition definition)
    该方法的作用是根据指定的 TransactionDefinition 对象获取事务状态。TransactionStatus 表示了当前事务的状态,可以用来进行事务管理操作,如提交事务或回滚事务。
  • void rollback(TransactionStatus status)
    用于回滚指定事务状态的事务操作。
  • void commit(TransactionStatus status)
    用于提交指定事务状态的事务操作。

DefaultTransactionDefinition

DefaultTransactionDefinition 是 Spring 框架提供的 TransactionDefinition 接口的默认实现类。TransactionDefinition 用于定义事务的属性和行为,如隔离级别、超时时间等。

DefaultTransactionDefinition 提供了一些常用的事务属性设置方法,可以根据业务需要进行配置。下面是一些常用的方法:

  • setPropagationBehavior(int propagationBehavior): 设置事务的传播行为。传播行为决定了事务在不同方法调用之间如何传播。例如,PROPAGATION_REQUIRED 表示如果当前没有事务,则创建一个新的事务,如果已经存在事务,则加入到当前事务中。
  • setIsolationLevel(int isolationLevel): 设置事务的隔离级别。隔离级别用于控制事务执行期间对数据的并发访问方式和程度。例如,ISOLATION_READ_COMMITTED 表示事务只能读取提交了的数据。
  • setTimeout(int timeout): 设置事务的超时时间(以秒为单位)。如果事务在指定的超时时间内未能完成,则会回滚事务。
  • setReadOnly(boolean readOnly): 设置事务是否为只读事务。如果设置为只读事务,那么在事务过程中不允许对数据库进行写操作。

TransactionStatus

TransactionStatus 是 Spring 框架中用于表示事务状态的接口。它提供了一系列方法,用于获取和管理事务的状态以及执行相关操作。

TransactionStatus 接口定义了以下常用方法:

  • boolean isNewTransaction(): 判断当前事务是否是新事务(即没有已存在的事务)。
  • boolean isRollbackOnly(): 判断当前事务是否被标记为只能回滚。
  • void setRollbackOnly(): 将当前事务标记为只能回滚。
  • boolean hasSavepoint(): 判断当前事务是否有保存点。
  • void setRollbackOnly(Throwable cause): 将当前事务标记为只能回滚,并指定造成回滚的异常原因。
  • void flush():将事务中的更改进行持久化或刷新到数据库。
  • void flushIfNecessary():根据事务管理器的配置,在特定条件下将事务中的更改进行持久化或刷新到数据库。

以下是通过编程式事务实现案例:

package com.xiao.service.impl;

import com.xiao.dao.UserDao;
import com.xiao.pojo.User;
import com.xiao.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;

import javax.annotation.Resource;
import java.math.BigDecimal;

@Service
public class UserServiceImpl implements UserService {

    @Resource
    private UserDao userDao;

    @Autowired
    private DataSourceTransactionManager dataSourceTransactionManager;

    public User queryUser(long id) {
        return userDao.queryUserById(id);
    }

    public int transfer(long sourcesId, long targetId, BigDecimal transferMoney){
        DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
        transactionDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);

        try {
            User sourcesUser = userDao.queryUserById(sourcesId);
            if(sourcesUser == null){
                throw new RuntimeException("转出用户不存在");
            }
            User targetUser = userDao.queryUserById(targetId);
            if(targetUser == null){
                throw new RuntimeException("转入用户不存在");
            }
            //转出
            userDao.updateUserMoney(sourcesId,transferMoney,1);

             int i = 1/0;

            //转入
            userDao.updateUserMoney(targetId,transferMoney,0);
        } catch (RuntimeException e) {
            dataSourceTransactionManager.rollback(transactionStatus);
            throw  e;
        }
        dataSourceTransactionManager.commit(transactionStatus);
        return 1;
    }
}

另外:Spring 还为我们提供了 TransactionTemplate 可以实现编程式事务:
1、配置TransactionTemplate bean

<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
        <property name="transactionManager" ref="transactionManager"/>
    </bean>

2、测试

package com.xiao.service.impl;

import com.xiao.dao.UserDao;
import com.xiao.pojo.User;
import com.xiao.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;

import javax.annotation.Resource;
import java.math.BigDecimal;

@Service
public class UserServiceImpl implements UserService {

    @Resource
    private UserDao userDao;

    @Autowired
    private TransactionTemplate transactionTemplate;

    @Override
    public User queryUser(long id) {
        return userDao.queryUserById(id);
    }

    @Override
    public int transfer(final long sourcesId, final long targetId, final BigDecimal transferMoney){

        User sourcesUser = userDao.queryUserById(sourcesId);
        if (sourcesUser == null) {
            throw new RuntimeException("转出用户不存在");
        }
        User targetUser = userDao.queryUserById(targetId);
        if (targetUser == null) {
            throw new RuntimeException("转入用户不存在");
        }

        return transactionTemplate.execute(new TransactionCallback<Integer>() {
            public Integer doInTransaction(TransactionStatus transactionStatus) {
                //转出
                userDao.updateUserMoney(sourcesId, transferMoney, 1);
                // 模拟异常,事务回滚
                int i = 1/0;
                //转入
                userDao.updateUserMoney(targetId, transferMoney, 0);
                return 1;
            }
        });
    }
}

参考:
Spring事务编程实战
Spring编程式事务

五、Spring 事件

从 Spring 4.2 开始,Spring 提供了一种基于观察者模式的事件机制,用于在应用程序内部进行事件的发布和订阅。用于在应用程序内部不同组件之间进行通信和解耦。通过使用 Spring 事件,应用程序中的不同部分可以发布和订阅事件,以便在事件发生时进行响应处理。

下面是 Spring 事件的关键要点:

  • 事件发布者(Event Publisher):负责发布事件的对象。它通常是一个组件、服务或模块,在某个特定的时间点或条件下触发事件。事件发布者使用 ApplicationEventPublisher 接口提供的方法来发布事件。

  • 事件(Event):表示应用程序中发生的某个事情或状态的类。通常继承自 ApplicationEvent 类。你也可以自定义事件类来满足特定需求。

  • 事件监听器(Event Listener):订阅并处理特定类型事件的对象。监听者通过@EventListener注解或实现ApplicationListener接口来订阅事件,并通过泛型指定所监听的事件类型。当事件被发布时,事件监听器中相应的处理方法会被调用。

  • 事件发布/订阅机制:通过使用 Spring 的事件发布/订阅机制,事件发布者将事件通知给所有订阅该事件的监听器。监听器可以在接收到事件后执行逻辑操作,从而实现松耦合的组件之间的通信和协作。

自定义事件

1、定义事件类

package com.xiao.event;

import org.springframework.context.ApplicationEvent;

public class MyEvent extends ApplicationEvent {

    private String message;

    public MyEvent(Object source,String message) {
        super(source);
        this.message = message;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

2、定义事件发布者

package com.xiao.event;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;

@Component
public class MyEventPublisher {

    @Autowired
    private ApplicationEventPublisher eventPublisher;

    public void publishEvent(String message) {
        MyEvent event = new MyEvent(this, message);
        eventPublisher.publishEvent(event);
    }
}

3、定义事件监听者

package com.xiao.event;

import org.springframework.context.ApplicationListener;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

@Component
public class MyEventListener implements ApplicationListener<MyEvent> {

    @Override
    public void onApplicationEvent(MyEvent myEvent) {
        System.out.println("MyEventListener onApplicationEvent 执行");
        System.out.println(myEvent.getMessage());
    }

    @EventListener
    public  void onApplicationEvent2(MyEvent myEvent){
        System.out.println("MyEventListener onApplicationEvent2 执行");
        System.out.println(myEvent.getMessage());
    }
}

4、测试

public class SpringEventTest {
    @Test
    public void eventTest(){
        ApplicationContext context = new ClassPathXmlApplicationContext("mybatis-spring.xml");

        MyEventPublisher eventPublisher = context.getBean(MyEventPublisher.class);
        eventPublisher.publishEvent("Hello Spring Event!");
        /**
         * 输出:
         * MyEventListener onApplicationEvent2 执行
         * Hello Spring Event!
         * MyEventListener onApplicationEvent 执行
         * Hello Spring Event!
         */
    }
}

Spring 中常见的事件

Spring Framework 内置了几个常用的事件,这些事件提供了不同的功能和作用,用于在应用程序中进行通信和处理。以下是一些常见的 Spring 内置事件及其作用:

  • ContextRefreshedEvent:当应用上下文(ApplicationContext)被初始化或刷新时触发该事件。它在所有 bean 被定义、初始化和配置之后被发布。可以使用它来执行在应用程序启动后需要进行的初始化操作。
  • ContextClosedEvent:当应用上下文关闭时触发该事件。它在所有单例 bean 销毁之后被发布。可以使用它来执行在应用程序关闭前需要进行的清理操作,比如释放资源、关闭连接等。
  • RequestHandledEvent:当一个 HTTP 请求被处理完毕时触发该事件。它在 Spring MVC 中非常有用,可以用于监听和记录请求的处理情况,收集统计信息等。
  • ServletRequestHandledEvent:类似于 RequestHandledEvent,但更具体地针对 Servlet 请求的事件。它提供了额外的信息,比如请求的 URL、HTTP 方法、响应状态等。
  • ApplicationEvent:这是一个基本的事件类,其他所有 Spring 事件都继承自它。可以根据具体需求自定义自己的事件,并发布给相应的监听器进行处理。

这些内置事件可以通过实现 ApplicationListener 接口或使用基于注解的方式来监听和处理。通过监听这些事件,可以在合适的时机执行相应的逻辑,实现不同组件之间的解耦和协作

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值