Spring 框架(Spring Framework)使用详解

概述

Spring 体系概述

  • Spring 是于2003年兴起的一个 full-stack 轻量级的 Java 开源框架,由 Rod Johnson 创建,使用 Spring 可以更快、更轻松、更安全地进行 Java 编程
  • Spring 是一个生态体系,或一个超级粘合平台,常见的 Spring 项目有:Spring Boot,Spring Framework,Spring Data,Spring Cloud,Spring Cloud Data Flow,Spring Security,Spring GraphQL,Spring Session 和 Spring Web Services 等
  • Spring 提供了展现层 Spring MVC、持久层 Spring JDBC、业务层事务管理等众多的企业级应用技术,Spring 还能整合开源世界众多的第三方框架和类库,逐渐成为使用最多的 Java EE企业应用开源框架
  • Spring 并不等同于 Spring 框架(Spring Framework),这是常见的误区

Spring 框架(Spring Framework)

概述

  • Spring 框架,即 Spring Framework 框架,是 Spring 生态的其中一个重要项目,也是其他 Spring 全家桶(SpringMVC、SpringBoot、SpringCloud、SpringData等)的基础和核心
  • Spring 框架分为多个模块,应用程序可以选择需要的模块。Spring 框架的核心是 Core Container(核心容器)模块,包括配置模型和依赖注入机制
  • Spring 框架为不同的应用程序架构提供基础支持,包括消息传递、事务数据和持久性以及 Web
  • Spring 框架还包括基于 Servlet 的 Spring MVC Web 框架,以及并行的 Spring WebFlux 反应式 Web 框架
  • Spring 框架是分层的JavaSE/EE一站式轻量级开源框架,以 IOC(控制反转)和 AOP (面向切面编程)为核心

Spring 框架的特点

  • 方便解耦,简化开发:将所有对象的创建和依赖关系维护交给Spring管理
  • 方便集成各种优秀框架:Spring内部提供了对各种优秀框架(Struts2、Hibernate、MyBatis)的直接支持
  • 降低了Java EE API 使用难度:对 JAVA EE开发中一些API( JDBC、JavaMail、远程调用)都提供了封装
  • 方便程序测试:支持 JUnit4,可以通过注解方便地测试 Spring 程序
  • AOP变成支持:面向切面编程,可以方便地实现对程序进行权限拦截和运行监控等功能
  • 声明式事务:通过配置就可以完成对事务的管理,无需编程

Spring 框架的架构和模块说明

在这里插入图片描述

Spring 框架的主要模块

  • Core Container(核心容器)

    是其他模块建立的基础,由 Beans 模块、Core 核心模块、Context 上下文模块和 SpEL 表达式语言模块组成

    • Core(spring-core):封装了Spring框架的底层部分,包括资源访问、类型转换以及Spring 框架基本的核心工具类

      Spring 其它组件要都要使用到这个包里的类,是其它组件的基本核心,也可以在自己的应用系统中使用这些工具类

      • 外部依赖:Commons Logging, (Log4J)
      • jar包:org.springframework spring-core 4.3.7.RELEASE
    • Beans(spring-beans):提供了框架的基础部分,包括访问配置文件、控制反转(IOC)和依赖注入(DI)

      • 外部依赖:spring-core
      • jar包:org.springframework spring-beans 4.3.7.RELEASE
    • SpEL(spring-expression):提供了强大的表达式语言支持,用于在运行时查询和处理对象图。该语言支持设置和获取属性值;属性赋值,方法调用,访问数组的内容,收集和索引器,逻辑和算术运算,命名变量,并从Spring的IOC容器的名字对象检索,它也支持列表选择和投影以及常见的列表聚合

      • jar包:org.springframework spring-expression 4.3.7.RELEASE
    • Context(spring-context):上下文模块,建立在 Core 和 Beans 模块的基础之上,集成Beans模块并添加资源绑定、数据验证、国际化、Java EE支持、容器生命周期、事件传播等

      为Spring 核心提供了大量扩展。可以找到使用Spring ApplicationContext特性时所需的全部类,JDNI 所需的全部类,instrumentation组件以及校验Validation 方面的相关类。

      • 外部依赖:spring-core,spring-beans,spring-expression,spring-aop
      • jar包:org.springframework spring-context 4.3.7.RELEASE

    依赖关系:

    在这里插入图片描述

  • Data Access/Integration(数据访问与集成)

    包含模块:JDBC、ORM、OXM、JMS、Transaction

    • JDBC(spring-jdbc):包含对Spring 对JDBC 数据访问进行封装的所有类

      • jar包:org.springframework spring-jdbc 4.3.7.RELEASE
    • ORM(spring-orm):提供了与“对象-关系”映射框架集成的API,包括 JPA、JDO、Hibernate、MyBatis等。

    • OXM(spring-oxm):提供了 Object/XML 映射的抽象层实现,如 JAXB、Castor、XMLBean、JiBX、XStream

      将 java对象映射成为XML数据,或者将 XML 数据映射成java对象

    • JMS(spring-jms):java消息服务,提供了一套“消息生产者、消息消费者”模板,JMS用于两个应用程序之间,或者分布式系统中发送消息,进行异步通信

    • Transaction(spring-tx):事务控制,为JDBC、Hibernate、JDO、JPA、Beans等提供的一致的声明式和编程式事务管理支持

      • jar包:org.springframework spring-tx 4.3.7.RELEASE

    依赖关系:

    在这里插入图片描述

  • Web

    包括模块:Web、servlet、websocket、portlet

    • Web(spring-web):提供了基本web集成特性,例如:多文件上传、使用 Servlet 监听器的 IOC 容器初始化以及 web 应用上下文

      包含Web 应用开发时,用到Spring 框架时所需的核心类,包括自动载入Web ApplicationContext 特性的类、Struts 与JSF 集成类、文件上传的支持类、Filter 类和大量工具辅助类

      • jar包:org.springframework spring-web 4.3.7.RELEASE
    • servlet(spring-webmvc):提供了 SpringMVC Web 框架实现。SpringMVC 提供了基于注解的请求资源注入、更简单的数据绑定、数据验证等以及一套非常简易的 JSP 标签

      包含Spring MVC 框架相关的所有类,包括框架的Servlets,Web MVC框架,控制器和视图支持

      如果应用使用了独立的MVC 框架,则无需这个JAR 文件里的任何类

      • jar包:org.springframework spring-webmvc 4.3.7.RELEASE
    • WebSocket:提供了简单的接口,用户只需要实现接口就可以快速搭建 websocket server,从而实现双向通讯

    • Portlet(spring-webmvc-portlet):提供了Portlet环境中MVC实现

    依赖关系:

    在这里插入图片描述

  • AOP、aspects、spring-instrument 、messaging

    aop部分包含4个模块

    • AOP(spring-aop):提供了面向切面编程,提供了比如日志记录、权限控制、性能统计等通用功能和业务逻辑分离的技术,并且能动态把这些功能添加到需要的代码中

      • jar包:org.springframework spring-aop 4.3.7.RELEASE
    • Aspects(spring-aspects):提供与AspectJ的集成,是一个功能强大且成熟的面向切面编程框架

      以便可以方便的将面向方面的功能集成进IDE中,比如Eclipse AJDT

      • jar包:org.springframework spring-aspects 4.3.7.RELEASE
    • InStrumentation(spring-instrument):检测,提供了类工具的支持和类加载器的实现

    • Messaging(消息处理):提供了对消息传递体系结构和协议的支持

    依赖关系:

    在这里插入图片描述

  • Test:Spring 支持 Junit 和 TestNG 测试框架,而且还额外提供了一些基于 Spring 的测试功能。


Spring 框架 的 API 体系

Spring Framework 的API体系异常庞大,暂时只介绍 BeanFactory 和 ApplicationContext:

  • BeanFactory

    • BeanFactory 是 Spring 的"心脏",IOC 容器的核心接口,定义了IOC的基本功能
    • Spring 使用它来配置文档,管理bean的加载,实例化并维护bean之间的依赖关系,负责bean的声明周期
  • ApplicationContext

    • ApplicationContext 由 BeanFactory 派生而来,可以比喻为Spring的躯体
    • ApplicationContext 在 BeanFactory 的基础上添加了很多功能:
      • 支持了 aop 功能和 web 应用
      • MessageSource,提供国际化的消息访问
      • 通过配置来实现 BeanFactory 中很多编码才能实现的功能
    • ApplicationContext 的常用实现类:
      • ClassPathXmlApplicationContext:从classpath目录读取配置文件
      • FileSystemXmlApplicationContext:从文件系统或者url中读取配置文件
      • AnnotationConfigApplicationContext:读取注解。当使用注解配置容器对象时,需要使用此类来创建 spring
        容器

BeanFactory 和 ApplicationContext 的区别:

  • beanFactory 主要是面向 Spring 框架的基础设施,也就是供 spring 自身内部调用

    Applicationcontext 主要面向 Spring 的使用者

  • BeanFactroy 是在第一次使用到某个Bean(调用getBean()方法)时,才对该Bean进行加载实例化

    ApplicationContext 是在容器启动时,一次性创建并加载了所有的Bean

在这里插入图片描述


Spring Boot 框架概述

  • Spring Boot 也是 Spring 生态中一个极其重要的项目,Spring Boot 是对 Spring Framework 的扩展,也可以说它是一个服务于框架的框架,其设计目的是用来简化新 Spring 应用的初始搭建以及开发过程

  • Spring Boot 的服务范围是简化配置文件,它消除了设置 Spring 应用程序所需的XML配置,即尽可能的自动配置 Spring 应用

  • Spring Boot 直接嵌入了 Tomcat、Jetty 或 Undertow(无需部署WAR文件),并且提供生产就绪功能,例如指标、运行状况检查和外部化配置等等,为更快和更高效的开发应用程序铺平了道路


IOC(控制反转)

IOC概述、IOC 容器工作原理

IOC(控制反转)是一种设计思想,目的是指导设计出更加松耦合的程序:

  • 控制:指的是对象控制权,在 java 中可以简单理解为对象的控制权限(比如对象的创建、销毁等权限)
  • 反转:指的是将对象的控制权由原来的程序员在类中主动控制反转到由 Spring 容器来控制
  • 主要功能:解耦

Spring IOC 容器 是对 IOC 思想的一种实现:

  • 对象的创建交由 Spring 框架管理,需要对象时从 Spring IOC 容器中获取即可
  • 底层原理:反射

Spring 的 IOC容器 工作原理:

  1. 当 Spring 的 IOC 容器加载时,会读取配置文件中的诸多 bean 配置

  2. 根据 bean 的 class 的值寻找对应的 Class 字节码文件

  3. 通过反射技术,创建出一个个对象

  4. 创建的对象会被存放到内部的一个 Map 结构中,等待被使用

  5. 当需要使用具体的对象时就无须手动创建,而是直接从 Spring 的IOC容器中


Spring IOC 的入门案例(xml)

案例:通过Spring中内置的容器获取对象

操作步骤:

  • 创建工程导入依赖
  • 配置接口和实现类
  • 编写Spring的配置文件
  • 测试:从容器中获取对象

创建工程导入依赖

    <dependencies>
        <!--spring的坐标-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.6.RELEASE</version>
        </dependency>
        <!--单元测试-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>

创建Dao接口和实现类(略)

创建Spring配置文件

  • spring配置文件约定俗称:applicationContext.xml
  • spring配置文件放置到:resource目录下
  • spring配置文件,需要引入名称空间(约束)
  • 在spring的配置文件中通过 <bean>标签,定义对象id和实现类全类名

在resource目录下创建applicationContext.xml配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/aop
                           http://www.springframework.org/schema/aop/spring-aop.xsd">
    
    <!--
        定义配置信息
            id:唯一标志(获取的时候,调用容器的getBean("id"))
            class:实现类的全限定类名
    -->
    <!--把数据库连接池对象放入IOC容器-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver"/>
        <property name="jdbcUrl" value="jdbc:mysql:///spring"/>
        <property name="user" value="root"/>
        <property name="password" value="root"/>
    </bean>
    <!--把QueryRunner放入到IOC容器中-->
    <bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
    	<constructor-arg name="ds" ref="dataSource"/>
    </bean>
    <!--把dao对象交给IOC容器-->
    <bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
    	<property name="queryRunner" ref="queryRunner"/>
    </bean>
    <!--把service交给IOC容器-->
    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
    	<property name="accountDao" ref="accountDao"/>
    </bean>
</beans>

测试

import cn.test.dao.UserDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * 测试从容器中获取对象
 */
public class UserDaoTest {
    
    

    public static void main(String[] args) {
    
    
        //1、根据配置文件获取容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        //2、调用容器的方法,获取对象
        UserDao userDao = (UserDao)ac.getBean("userDao");
        //3、测试
        userDao.save();
    }
}

执行过程分析

在这里插入图片描述

初始化 Spring Bean 对象(xml)

方式1:默认无参构造函数

IOC容器通过反射调用,根据默认无参构造函数来创建类对象。如果 bean 中没有默认无参构造函数,将会创建失败。

<bean id="userDao" class="cn.test.dao.impl.UserDaoImpl"></bean>

方式2:工厂模式创建对象

在Spring中还可以通过工厂模式来创建对象。工厂模式又分两种:

  • 静态工厂:不产生工厂的实例,直接调用工厂的静态方法创建对象
  • 实例工厂:先产生工厂的实例,再调用工厂实例的方法创建对象

(1)java代码编写工厂类

public class FactroyCreateBean {
    
    
    // 静态工厂
    public static UserDao createUserDao(){
    
    
    	return new UserDaoImpl();
    }
    // 实例工厂
    public UserDao createUserDaoSimple(){
    
    
    	return new UserDaoImpl();
    }
}

(2)xml配置文件配置

<!--使用静态工厂创建对象-->
<bean id="userDao1" class="cn.test.factory.FactroyCreateBean" factory-method="createUserDao"/>
<!--使用实例工厂创建对象-->
<bean id="factroyCreateBean" class="cn.test.factory.FactroyCreateBean"/>
<bean id="userDao2" factory-bean="factroyCreateBean" factory-method="createUserDaoSimple"/>

Spring Bean 的生命周期

概述、生命周期流程图

Bean对象生命周期指的是Bean创建到销毁的这么一段时间。

粗粒度生命周期:

  • spring中单例对象的生命周期为:

    出生:IOC容器加载时出生

    存活:IOC容器运行时存活

    死亡:IOC容器销毁时死亡

  • spring中多例对象的生命周期为:

    出生:使用对象时出生

    存活:一直存活

    死亡:由java垃圾回收器负


细粒度生命周期:

出生过程

  1. 实例化bean对象【IOC】

  2. 为对象属性赋值【DI】

  3. 处理实现的Aware接口

    ① 如果这个Bean已经实现了 BeanNameAware 接口,会调用它实现的 setBeanName(String beanId) 方法,此处传递的就是Spring配置文件中Bean的id值。

    ② 如果这个Bean已经实现了 BeanFactoryAware 接口,会调用它实现的 setBeanFactory() 方法,传递的是Spring工厂自身。

    ③ 如果这个Bean已经实现了 ApplicationContextAware 接口,会调用 setApplicationContext(ApplicationContext) 方法,传入Spring上下文。

  4. 通过 BeanPostProcessor 接口的 postProcessBeforeInitialization 方法对bean对象进行预处理

  5. 通过 InitializingBean 接口的 afterPropertiesSet 方法对bean对象进行处理

  6. 通过指定 init-method 方法对bean对象进行处理

  7. 通过 BeanPostProcessor 接口的 postProcessAfterInitialization 方法对bean对象进行后处理,这一步bean对象已经彻底创建成功了,可以做一些类似缓存的工作

死亡过程

  1. 如果Bean实现了 DisposableBean 接口,会调用其实现的 destroy() 方法。
  2. 如果指定 destroy-method 方法,可以在bean对象销毁前自动执行。

生命周期流程图

在这里插入图片描述

测试案例:

public class UserDaoImpl implements UserDao, BeanNameAware, BeanFactoryAware,ApplicationContextAware, InitializingBean, DisposableBean {
    
    
    public UserDaoImpl() {
    
    
    	System.out.println("IOC");
    }
    
    private Integer id;
        public void setId(Integer id) {
    
    
        System.out.println("DI");
        this.id = id;
    }
    
    @Override
    public void setBeanName(String s) {
    
    
        System.out.println("BeanNameAware:"+s);
    }
    
    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
    
    
    	System.out.println("BeanFactoryAware");
    }
    
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    
    
    	System.out.println("ApplicationContextAware");
    }
    
    @Override
    public void afterPropertiesSet() throws Exception {
    
    
    	System.out.println("InitializingBean的afterPropertiesSet");
    }

    public void initMethod(){
    
    
    	System.out.println("init-method");
    }
    
    @Override
    public void save() {
    
    
    	System.out.println("保存成功!");
    }
    
    @Override
    public void destroy() throws Exception {
    
    
    	System.out.println("DisposableBean的destroy");
    }
    
    public void destroyMethod(){
    
    
    	System.out.println("destroy-method");
    }
}
public class MyBeanProcessor implements BeanPostProcessor {
    
    
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    
    
    	System.out.println("BeanPostProcessor的before");
    	return bean;
    }

    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    
    
        System.out.println("BeanPostProcessor的after");
        return 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"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/aop
                           http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl" init-method="initMethod" destroy method="destroyMethod">
        <property name="id" value="1"/>
    </bean>
    <bean class="com.itheima.processor.MyBeanProcessor"/>
</beans>
// 测试类
public class UserDaoImplTest {
    
    
    @Test
    public void save() {
    
    
        ClassPathXmlApplicationContext ac = new
        ClassPathXmlApplicationContext("applicationContext.xml");
        UserDao userDao = ac.getBean(UserDao.class);
        userDao.save();
        ac.close();
    }
}

Spring Bean 的作用域

参考:https://blog.csdn.net/weixin_38676276/article/details/90382350

  • singleton:单例。在Spring IOC容器中仅存在一个共享的Bean实例。默认值

    当spring创建applicationContext容器的时候,spring会初始化所有的该作用域实例,加上lazy-init则可以避免预处理

  • prototype:原型(多例)

    每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()相当于执行new XxxBean()

    创建后spring将不再对其管理

    (下面是在web项目下才用到的作用域)

  • request:仅在当前HTTP request内有效

    每次HTTP请求都会创建一个新的Bean

    spring创建后会继续监听,当处理请求结束后销毁实例

  • session:仅在当前HTTP Session内有效

    同一个HTTP Session共享一个Bean,不同Session使用不同Bean

    spring创建后会继续监听,当HTTP Session最终被废弃时,在该HTTP Session作用域内的bean也会被废弃掉

  • global-session:全局的web域。类似于servlet中的application。一般用于Portlet应用环境


定义 Bean 的配置信息(xml)

  • id:唯一标志(获取的时候,调用容器的getBean(“id”))

  • class:实现类的全限定类名

  • scope:对象作用域(singleton(默认) | prototype | request | session | global-session)

  • init-method:对象创建成功之后,指定的初始化方法

  • destroy-method:容器关闭对象销毁之前,执行的销毁方法

    只有在scope=singleton(单例)的时候,才有效

<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl" scope="prototype" 
      init-method="init" destroy-method="destroy"/>

依赖注入(xml)

依赖注入:Dependency Injection(DI)。 它是 spring 框架核心 IOC 的具体实现。在编写程序时, 通过控制反转,把对象的创建交给了 spring,然后在代码中需要对象时,进行依赖对象的注入。

IOC 具有两个功能:依赖查找,依赖注入

本质:向对象中的私有属性赋值

  • 构造方法

  • set方法调用


构造方法注入

  • 向对象中添加有参构造方法
public class UserServiceImpl implements UserService {
    
    

    private String name;
    private Integer age;

    public UserServiceImpl(String name, Integer age) {
    
    
        this.name = name;
        this.age = age;
    }

    @Override
    public void save() {
    
    
        System.out.println(name + age);
    }
}
  • 在spring的配置文件中,通过bean标签配置对象创建(需要添加构造方法参数)
    <!--
        constructor-arg 设置对象的构造方法参数 (一个参数配置一个constructor-arg标签)
            name:构造参数名
            type:构造参数类型
            index:构造参数,参数索引位置(从0开始)
            以上三个属性,用于定位构造方法参数位置(三选一即可)
            value: 对基本数据类型的参数赋值(8大数据类型 + String)
            ref: 对对象属性的参数赋值。即必须得是在配置文件中配置过的bean
            以上两个属性,用于对构造参数赋值
     -->
    <bean id="userService" class="cn.test.service.impl.UserServiceImpl">
        <constructor-arg name="age" value="12"></constructor-arg>
        <constructor-arg name="name" value="王者荣耀"></constructor-arg>
    </bean>

set 方法注入

  • 提供属性的set方法

  • 在spring配置文件中,通过bean结合property配置set方法调用

    <bean id="book" class="com.itheima.spring.Book">
        <!--	name:找的是类中 set 方法后面的部分
    			ref:给属性赋值(其他bean类型)
    			value:给属性赋值(8大基本数据类型 + string类型)
    	-->
        <property name="name" value="程序员" />
        <property name="price" value="1" />
        <property name="publishDate" ref="date" />
    </bean>
    

注入复杂类型(集合)

给类中的集合成员传值,它用的也是 set方法注入的方式,只不过变量的数据类型都是集合。 这里介绍注入数组、List、Set、Map、Properties。

(1) 注入数组数据

配置set方法

public class UserServiceImpl implements UserService {
    
    

    /**
     * 注入数组(array数组,list集合,set集合)
     */
    private String [] names;
    private List<String> lists;
    private Set<String> sets;

    public void setNames(String[] names) {
    
    
        this.names = names;
    }

    public void setLists(List<String> lists) {
    
    
        this.lists = lists;
    }

    public void setSets(Set<String> sets) {
    
    
        this.sets = sets;
    }

    @Override
    public void save() {
    
    
    }
}

spring配置文件

<bean id="book" class="com.itheima.spring.Book">
	<!-- List -->
	<property name="list">
		<list>
			<value>1</value>
			<value>2</value>
		</list>
	</property>

	<!--Set-->
	<property name="set">
		<set>
			<value>3</value>
			<value>4</value>
		</set>
	</property>

	<!--数组-->
	<property name="array">
		<array>
			<value>5</value>
			<value>6</value>
		</array>
	</property>

	<!--Map-->
	<property name="map">
		<map>
			<entry key="7" value="7-1" />
			<entry key="8" value="8-1" />
		</map>
	</property>

	<!--Properties-->
	<property name="properties">
		<props>
			<prop key="9">9-1</prop>
			<prop key="10">10-1</prop>
		</props>
	</property>
</bean>

Spring 框架中的注解配置

介绍、注解和 xml 配置对应关系

  • 注解和 xml 是Spring提供的两种配置形式,二者的功能是完全一样的,都是要降低程序间的耦合

  • 注解的好处是配置简单,xml的好处是修改配置不用改动源码,企业开发中两种方式灵活使用

  • Spring中注解配置

    1. 注解 + xml 配置(开启注解的功能支持)
    2. 纯注解(Spring Boot + cloud))
  • 注意:在spring中使用注解开发,需要开启注解的功能支持(ioc注解,aop注解,事务注解)


注解和xml配置对应关系

xml配置 注解配置 说明
< bean id=“” class=“” > @Component @Controller @Service @Repository 将类的实例化对象放入Spring容器管理
< property name=“” ref=“”> @Autowired @Qualifier @Resource 从Spring容器中获取对象注入属性
< property name=“” value=“”> @Value bean的简单属性注入
< bean scope=“”> @Scope 控制bean的作用范围
< bean init-method=“init”
destroy method=“destory” />
@PostConstruct @PreDestroy bean创建之后和销毁之前分别调用的方法

开启包扫描(开启 IOC 注解支持)

开启包扫描(即 开启对 IOC 注解的支持):

  1. 扫描指定包下的所有 java 类中的 Spring 注解
  2. 如果扫描到类上有 IOC 注解,就会把当前类交给 IOC 容器管理,当容器启动时,自动创建对象存入容器
  3. 如果扫描到属性上有 DI 注解,则依据依赖注入的规则,给属性注入值

方式1:xml配置方式 开启包扫描

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

    <!-- 开启包扫描
		 - base-package:包名(自动扫描此包以及此包下的所有子包)
	-->
    <context:component-scan base-package="cn.test"></context:component-scan>
</beans>

方式2:配置类注解方式 开启包扫描

/**
 * 1、声明配置类:@Configuration
 * 2、开启包扫描:@ComponentScan
 */
@Configuration
@ComponentScan(basePackages = "cn.test")
public class SpringConfig {
    
    
}

IOC 注解(对象创建的注解)

IOC 注解(对象创建的注解)说明:

  • 标注在想要被 IOC 容器管理的类上,表明创建对象交给容器管理

  • 当 Spring 容器启动时,根据包扫描配置自动的扫描到 IOC 注解,反射创建注解标注的对象,存入容器,委托容器管理

  • 默认存入容器的 id(唯一标识)为当前类名首字母小写。可以通过 value 属性自定义存入容器中对象的 id

一共有四个:

  •  **@Controller**:一般标注在表现层(web层)的类上
    
  •  **@Service**: 一般标注在业务层(service层)的类上
    
  •  **@Repository**: 一般标注在持久层(dao层)的类上
    
  •  **@Component**:组件, 非三层模式范围的类上使用
    

相当于xml中的如下配置

<bean id="userDaoImpl" class="cn.test.dao.impl.UserDaoImpl"></bean>

生命周期的相关注解

  • @Scope : 在类上配置对象的作用域

    通过value属性指定作用范围(singleton|prototype),默认为 singleton(单例)

  • @PostConstruct : 配置对象创建后的触发动作

    当对象创建完成之后,自动执行的方法。相当于 xml 配置文件中的 init-method

  • @PreDestroy :配置对象销毁前的触发动作(仅在 Scope = singleton 时有效)

    容器关闭,对象销毁之前执行的方法。相当于 xml 配置文件中的destory-method

示例:

@Repository
@Scope(value="singleton")
public class UserDaoImpl implements UserDao {
    
    

    public UserDaoImpl() {
    
    
        System.out.println("创建UserDaoImpl");
    }

    @Override
    public void save() {
    
    
        System.out.println("调用dao保存数据");
    }


    //初始化方法:在对象创建完成之后执行
    @PostConstruct
    public void init() {
    
    
        System.out.println("执行init方法");
    }

    //销毁方法:在容器关闭对象销毁之前执行
    @PreDestroy
    public void destory() {
    
    
        System.out.println("执行destory方法");
    }
}

DI 注解(依赖注入的注解)

DI 注解都相当于直接给属性赋值,而无需借助于 set 方法或构造方法

@Autowired

使用方式1:标注在属性上

  • 直接给属性赋值(通过 @Autowired 依赖注入,不需要配置 set 方法)

  • 默认是通过 by Type(根据类型,即接口类型)的形式从 IOC 容器中查找对象并给属性注入值

  • 如果 IOC 容器中存在多个与属性同类型的对象(一个接口有多个实现类),

    • 则会按照属性名(by Name)作为唯一标志从容器中查找对象并给属性注入值

    • 也可以和 @Qualifier 注解共同使用,指定唯一标志从容器中查找对象并给属性注入值

      • value:指定 IOC 容器中对象唯一标志(id)

      注意:@Qualifier 只能结合 @Autowired 一起使用

使用方式2:标注在方法上

  • 表示自动执行当前方法,如果方法有参数,会自动从IOC容器中寻找同类型的对象给参数传值
  • 也可以在参数上添加 @Qualifier(“IOC容器中对象id”) 注解按照名称寻找对象给参数传值

@Autowired 使用注意事项

  1. 只能在被 Spring 容器托管(标注了 @Controller 等 IOC 注解)的类中使用 @Autowired 注解

  2. 自动注入与权限修饰符无关,即使是 private 修饰的字段也可以自动注入

  3. 默认情况下,使用 @Autowired 注解进行自动注入的属性一定要被装配( Spring 容器托管)

    如果在容器中找不到该类型的bean 进行注入,就会报错

    如果允许不被装配,可以将 @Autowired 的 required 属性为 false

  4. @Autowired 是基于类型的注入,如果当前类型属性在容器中只有一个 Bean,那么属性名不限制,但一般建议遵循类名首字母小写的规则

  5. 如果当前属性类型在容器中有个多个Bean,那么必须要通过属性名或者同时标注 @Qualifier 注解指定 Bean name


示例:

@Service
public class UserServiceImpl implements UserService {
    
    
    @Autowired
    @Qualifier(value = "userDao2")		// 从容器中获取指定唯一标志的对象
    private UserDao userDao;

    @Override
    public void save() {
    
    
        userDao.save();
    }
}

@value

配置在属性上。可用于简单数据类型的注入,相当于 < property name="" value="" > ,但通常不这么使用

一般用于解析 properties 配置文件或注册中心的配置文件中的内容

  • 根据key值获取对应的 value,语法规则: @Value(“${key}”)
  • 使用步骤:
    1. properties 配置文件交给Spring容器管理
    2. 通过 @Value,从容器中得到配置项,并注入

示例:

(1) 配置jdbc.properties文件

jdbc.username=root
jdbc.password=root
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///heima23

(2) 配置文件交给Spring

修改spring配置文件applicationContext.xml

    <!--将properties文件,交给spring管理-->
    <context:property-placeholder location="jdbc.properties"></context:property-placeholder>

(3) 属性注入

@Repository(value = "userDao")
public class UserDaoImpl implements UserDao {
    
    

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

    public void save() {
    
    
        System.out.println(username);
        System.out.println(password);
        System.out.println(driver);
        System.out.println(url);
        System.out.println("调用dao11111完成保存");
    }
}

@Reource(了解)

jdk提供的依赖注入的注解,此注解在jdk9及以上版本已经移除

  • 只能放在属性上
  • 表示先按照属性名匹配 IOC 容器中对象id给属性注入值【by name】
  • 若没有成功,会继续根据当前属性的类型匹配IOC容器中同类型对象来注入值 【by type】
  • 若指定了 name 属性 @Resource(name = “对象id”),则只能按照对象 id 注入值

示例:

@Service
public class UserServiceImpl implements UserService {
    
    
    @Resource(name = "userDao1")
    private UserDao userDao;

    @Override
    public void save() {
    
    
        userDao.save();
    }
}

Configuration 配置类(注解)

配置类中的常用注解:

  • @Configuration:标注在类上,声明该类为 Spring 配置类

    Spring 在启动的时候会自动扫描并加载所有配置类,配置 Spring 容器(应用上下文),将配置类中的 Bean 放入容器管理

  • @Bean:标注在 Spring 配置类中的方法上,注册 bean 对象到 IOC 容器

    • name 属性:给生成的bean指定唯一标志

    在 Spring 容器启动的时候,自动的扫描并执行所有配置了 @Bean 的方法,并将返回值存入Spring容器

    注意:

    • 被标注的方法,需要返回某个实例
    • 被标注的方法,可以配置依赖的属性参数,Spring 会自动从容器中获取到依赖的对象,自动调用方法
  • @ComponentScan:开启包扫描(Spring IOC注解支持),默认扫描当前包及子包下所有类

    • basePackage 属性:指定扫描的包路径。可以减少加载时间

    如果扫描到类上有 IOC 注解,就会把当前类交给 IOC 容器管理,当容器启动时,自动创建对象存入容器

    如果扫描到属性上有 DI 注解,则依据依赖注入的规则,给属性注入值

  • @PropertySource:加载本地 properties 文件交给 Spring 容器管理

    • value 属性:指定本地 properties 路径
  • @Import:在一个配置类中导入其它配置类的内容

    • value 属性:指定其他的配置类的 class 类路径

    Spring 支持多配置类(配置类的模块),若配置类臃肿,可以拆分配置类,然后在主配置类中引入子配置类(子配置类上不用配置注解)

示例:

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import javax.sql.DataSource;

@Configuration		// 声明配置类
@ComponentScan(basePackages = "cn.test")	// 开启包扫描,并指定包扫描路径
@PropertySource(value="jdbc.properties")	// 通过注解将此文件交给spring容器管理
@Import(value=DataSourceConfig.class)		// 引入其他的配置类
public class SpringConfig {
    
    

    @Bean
    public QueryRunner getQueryRunner(DataSource dataSource) {
    
    
        QueryRunner qr = new QueryRunner(dataSource);
        return qr;
    }
}
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import javax.sql.DataSource;

// 子配置类
public class DataSourceConfig {
    
    

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

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

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

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

    @Bean
    public DataSource getDataSource() {
    
    
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        dataSource.setUrl(url);
        dataSource.setDriverClassName(driver);
        return dataSource;
    }
}
# properties文件
jdbc.username=root
jdbc.password=root
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///heima23

AOP(面向切面编程)

概念

AOP((Aspect Oriented Programming)面向切面编程

  • 是一种思想,目的是在不修改源代码的基础上,对原有功能进行增强
  • 通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术
  • AOP是一种编程范式,是OOP的延续,在OOP基础之上进行横向开发。
  • AOP研究的不是每层内部如何开发,而是同一层面上各个模块之间的共性功能。比如:事务、日志、统计

Spring AOP 是对 AOP 思想的一种实现,将那些与业务无关,却为业务模块所共用的逻辑封装起来,减少系统的重复代码,降低模块间的耦合度。另外,AOP还能解决一些系统层面上的问题,比如日志、事务、权限等。

Spring 底层是通过动态代理的方式实现的 AOP。同时支持 jdk 和 cglib 动态代理,Spring会根据被代理的类是否有接口自动选择代理方式:

  • 如果有接口,就采用 JDK 动态代理(也可以强制使用 CGLIB 动态代理)

  • 没有接口就采用用 CGLIB 动态代理的方式


Spring AOP工作流程

开发阶段分别开发,运行阶段组装运行

  • 开发阶段(开发者完成)

    • 开发共性功能,制作成增强

    • 开发非共性功能,制作成切点

    • 在配置文件中,声明切点与增强之间的关系,即切面

  • 运行阶段/容器启动阶段(AOP完成)

    1. Spring读取配置文件中的切面信息,根据切面中的描述,将 增强功能 增加在 目标对象 的 切点方法 上,动态创建代理对象
    2. 最后将经过代理之后对象放入容器中(注意:存入容器的是动态代理对象!)

术语及说明

  • 目标对象(target ):需要被增强的对象,即切入点方法所在对象

  • 连接点(jointPoint):被代理对象中的方法

  • 切点(pointCut):按照AOP的规则去切(匹配)连接点,匹配出来的就叫切点,即 需要被增强的方法

    • 切入点表达式 : execution(方法的修饰符 返回值类型 包名.类名.方法名(参数))

      切点表达式的作用:定义一组规则,用于在连接点(所有方法)中挑选切入点(被增强方法)

      支持通配符:

      *		// 一个或多个任意字符
      ..		// 配置到参数上,标识任意参数
      
    1. 方法修饰符可以省略
    2. 方法返回值可以通过 * 标识任意返回值类型
    3. 包名可以通过 * 标识任意包
    4. 类名可以通过 * 标识任意类
    5. 方法名可以通过 * 标识任意方法
    6. 参数可以通过 … 标识任意参数
    // 切入点的全统配方式,表示整个项目中所有类中所有方法都要被增强
    execution(* *..*.*(..))
    // 最常用的切入点表达式:精确到具体包路径
    execution(* com.test.controller.*.*(..))
    
  • 增强(通知)(advice):一个具体的增强功能。增强方法在切点方法的什么位置上执行

    Spring AOP 通知(增强)分为5种类型:

    • 前置通知(before):在切点运行之前执行

    • 后置通知(after-returning):在切点正常运行结束之后执行

    • 异常通知(after-throwing):在切点发生异常的时候执行

    • 最终通知(after):在切点的最终执行

    • 环绕通知(around):一种特殊的通知,在一个方法中定义多个增强逻辑(和手动定义动态代理类似)

      环绕通知的自定义方法:

      1. 参数 ProceedingJoinPoint:被代理对象的方法
      2. 返回值:被增强方法的返回值
  • 代理对象(proxy ):目标对象被增强后成为代理对象

  • 切面(aspect):是一个设计概念,包含了Advice 和 Pointcut。切面 = 切入点 + 增强

    Advice定义了Aspect的任务和什么时候执行,Pointcut定义在哪里切入

    即 Aspect定义了一个什么样的增强功能,切入到哪个核心方法的哪个位置

  • 织入(Weaving):一个动作。将增强代码加入到核心代码的过程就叫织入


配置 Spring AOP(xml)

xml文件 配置切入点

  • <aop:pointcut
    • id:当前切点的唯一标志
    • expression:切入点表达式

xml文件 配置切面

  • <aop:aspect :配置一个切面
    • id:当前切面的唯一标志
    • ref:指定当前切面使用哪个通知

xml文件 配置通知类型

  • <aop:before :指定通知在切入点方法中执行的位置
    • method : 切面类中的增强方法名
    • pointcut-ref:切入点的id

xml文件 配置AOP示例

	<!--声明AOP配置-->
    <aop:config>
        <!-- 配置切入点(被增强的方法) -->
        <aop:pointcut id="pt" expression="execution(* cn.test.dao.impl.*.*(..))"/>

        <!--配置切面-->
        <aop:aspect ref="logger">
        	<!-- 配置通知类型 -->
            <!-- 前置通知 -->
            <aop:before method="before" pointcut-ref="pt"></aop:before>
            <!-- 后置通知 -->
            <aop:after-returning method="afterReturning" pointcut-ref="pt"></aop:after-returning>
            <!-- 异常通知 -->
            <aop:after-throwing method="afterThrowing" pointcut-ref="pt"></aop:after-throwing>
            <!-- 最终通知 -->
            <aop:after method="after" pointcut-ref="pt"></aop:after>-->
            <!-- 环绕通知 -->
            <aop:around method="around" pointcut-ref="pt"></aop:around>
        </aop:aspect>
    </aop:config>
import org.aspectj.lang.ProceedingJoinPoint;

/**
 * 切面类:此类中具有所有的增强代码逻辑
 */
public class Logger {
    
    

    /**
     * 前置通知:执行被代理对象方法之前执行
     * 方法:无参数,无返回值
     */
    public void before() {
    
    
        System.out.println("执行前置通知");
    }

    /**
     * 后置后置:正常执行被代理对象方法获取返回值之后执行
     */
    public void afterReturning() {
    
    
        System.out.println("执行后置通知");
    }

    /**
     * 异常通知:执行代码抛出异常的时候执行
     */
    public void afterThrowing() {
    
    
        System.out.println("执行异常通知");
    }


    /**
     * 最终通知:finally代码块中执行的逻辑
     */
    public void after() {
    
    
        System.out.println("执行最终通知");
    }

    /**
     * 环绕通知:在一个方法中定义多个增强逻辑
     */
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
    
    
        Object obj = null;

        try {
    
    
            System.out.println("执行前置通知");
            //执行被代理对象方法
            obj = pjp.proceed();
            System.out.println("执行后置通知");
        }catch (Exception e){
    
    
            System.out.println("执行异常通知");
        }finally {
    
    
            System.out.println("执行最终通知");
        }
        return obj;
    }

配置 Spring AOP(注解)

AOP注解版有两种:

  • 基于 XML 结合注解的配置方式
  • 基于纯注解的配置方式

开启 AOP 注解支持(xml 方式)

xml配置文件

  • 开启IOC注解的支持,包扫描
    • 自定义的对象,通过IOC注解进行对象创建和依赖注入
    • 第三方的对象,通过XML配置对象创建和依赖注入
  • 开启AOP注解的支持,自动代理
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
			    http://www.springframework.org/schema/beans/spring-beans.xsd
			    http://www.springframework.org/schema/context
			    http://www.springframework.org/schema/context/spring-context.xsd
			    http://www.springframework.org/schema/aop
			    http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--开启包扫描-->
    <context:component-scan base-package="cn.test"></context:component-scan>

    <!--开启对AOP注解的支持-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

    <!--在切面类中通过注解完成AOP配置-->
</beans>

开启 AOP 注解支持(配置类方式)

  • @EnableAspectJAutoProxy:标注在配置类上,开启 aop 注解支持
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

/**
 * 配置类
 */
@Configuration
@ComponentScan(basePackages = "cn.test")
@EnableAspectJAutoProxy
public class SpringConfig {
    
    
}

切面类(注解)

  • @Aspect:标注在自定义类上,声明切面类

    注意:该切面类同时需要标注 IOC注解(@Component),交给 Spring 容器管理

  • 在切面类中的通知(增强)方法上通过注解配置通知类型:

    • @Before:前置通知
    • @AfterReturning: 后置通知
    • @AfterThrowing:异常通知
    • @After:最终通知
    • @Around:环绕通知

    通知注解的属性:

    • value / argNames 属性:切入点表达式 或 被 @Pointcut 标注的方法名()

      	@Around("pt()")
          public Object around(ProceedingJoinPoint pjp)
      
  • @Pointcut:标注在切面类中的空的方法上,抽取公共的切入点表达式

    • value / argNames 属性:切入点表达式
    • 通知注解配置 方法名() 即可引入公共的切入点表达式
        @Pointcut(value="execution(* cn.test.service.impl.*.*(..))")
        public void pt() {
          
          }
    
/**
 * 切面类:此类中具有所有的增强代码逻辑
 */
@Component
@Aspect
public class Logger {
    
    

    /**
     * 前置通知:执行被代理对象方法之前执行
     * 方法:无参数,无返回值
     */
    //@Before(value="execution( * cn.test.dao.impl.*.*(..) )")
    public void before() {
    
    
        System.out.println("执行前置通知");
    }

    /**
     * 后置通知:正常执行被代理对象方法获取返回值之后执行
     */
    //@AfterReturning(value="execution( * cn.test.dao.impl.*.*(..) )")
    public void afterReturning() {
    
    
        System.out.println("执行后置通知");
    }

    /**
     * 异常通知:执行代码抛出异常的时候执行
     */
   // @AfterThrowing("execution( * cn.test.dao.impl.*.*(..) )")
    public void afterThrowing() {
    
    
        System.out.println("执行异常通知");
    }


    /**
     * 最终通知:finally代码块中执行的逻辑
     */
    //@After("execution( * cn.test.dao.impl.*.*(..) )")
    public void after() {
    
    
        System.out.println("执行最终通知");
    }

    /**
     * 环绕通知:在一个方法中定义多个增强逻辑
     */
    //@Around("execution( * cn.test.dao.impl.*.*(..) )")
    @Around("pt()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
    
    
        Object obj = null;
        try {
    
    
            System.out.println("1执行前置通知");
            //执行被代理对象方法
            obj = pjp.proceed();
            System.out.println("2执行后置通知");
        }catch (Exception e){
    
    
            System.out.println("3执行异常通知");
        }finally {
    
    
            System.out.println("4执行最终通知");
        }
        return obj;
    }
    
    // @Pointcut:抽取公共的切入点表达式
    @Pointcut(value="execution(* cn.test.service.impl.*.*(..))")
    public void pt() {
    
    }
}

四大通知的执行顺序问题

参考:https://blog.csdn.net/qq_45193304/article/details/109430545

SpringAOP四大通知(前置、后置、异常、最终通知)的执行顺序非常复杂,跟Spring版本、配置先后顺序、配置方式(xml | 注解)都有关,故建议使用环绕通知

xml方式正确顺序配置aop的执行顺序:

try {
    
    
	// 前置通知(before) : 在切点运行之前执行
   
    // 切点执行,被代理对象方法调用
       
	// 后置通知(after-returning): 在切点正常运行结束之后执行
}catch (Exception e){
    
    
    // 异常通知(after-throwing): 在切点发生异常的时候执行
}finally {
    
    
    // 最终通知(after): 在切点的最终执行
}

注解方式正确顺序配置aop的执行顺序:

try{
    
    
    try{
    
    
        //@Before  -- 首先执行前置通知
        method.invoke(..); -- 然后执行切入点方法
    }finally{
    
    
        //@After  -- 再而肯定会执行最终通知 --- 注解配置的注意点
    }
    //@AfterReturning  -- 如果没有异常,则继续执行后置通知
    return;  -- 返回结果
}catch(){
    
    
    //@AfterThrowing  -- 如果有异常,则执行异常通知
}

Spring 中的事务管理

Spring支持两种事务管理方式:编程式事务和声明式事务。官方大力推荐使用声明式事务。

  • 编程式事务:将业务代码和事务代码放在一起书写,它的耦合性太高,开发中不使用

  • 声明式事务:

    • 声明式事务是建立在 AOP 的基础上的。

      其本质是在方法前后进行拦截,在方法开始之前创建事务,在方法结束后根据执行情况提交或回滚事务。

    • 声明式事务的优点:可以将事务代码和业务代码完全分离开发,然后通过配置的方式实现运行时组装运行。

    • 声明式事务的不足:只能作用到方法级别,无法像编程式事务那样可以作用到代码块级别


Spring 中事务管理相关 API

Spring中的事务控制主要就是通过这三个API实现的:

  • PlatformTransactionManager 接口:事务管理器,负责事务的管理,其子类负责具体工作
  • TransactionDefinition 接口:定义了事务的一些相关参数
  • TransactionStatus 接口:代表事务运行的一个实时状态

三者的关系:事务管理器 通过读取 事务定义参数 进行事务管理,然后会产生一系列的 事务状态


PlatformTransactionManager 接口:

  • Spring进行事务管理的一个根接口,使用其实现类做事务管理器(增强的事务处理的功能)

  • 接口方法:

    // 获取事务的状态信息
    TransactionStatus getTransaction(TransactionDefinition def)
    // 提交事务
    void commit(TransactionStatus status)
    // 回滚事务
    void rollback(TransactionStatus status)
    
  • 常用实现类:

    • DataSourceTransactionManager :使用 JDBC 和 MyBatis 进行持久化数据时使用
    • JpaTransactionManager :使用 JPA 进行持久化时使用(jpa,hibernate)
    • HibernateTransactionManager :使用Hibernate进行持久化数据时使用
    • JtaTransactionManager :事务跨越多个事务管理源【分布式事务】时使用

TransactionStatus 接口 :

  • 事务运行的一个实时状态

  • 接口方法

    // 是否是新事物
    boolean isNewTransaction()
    // 是否有回滚点
    boolean hasSavepoint()
    // 设置为只回滚事务
    void setRollbackOnly()
    // 是否是只回滚事务
    boolean isRollbackOnly()
    // 刷新事务状态 
    void flush()
    // 事务是否完成
    boolean isCompleted()
    

TransactionDefinition 接口:

  • 事务的定义信息(事事务隔离级别,传播行为,是否只读事务,超时时间等)

在这里插入图片描述

  • 事务隔离级别

    • Spring中配置事务,支持所有的4中隔离级别

    • 默认值:自动选择当前数据库合适的配置项

    • // 事务隔离级别相关【不设置事务隔离级别,可能引发脏读、不可重复读、幻读】
      ISOLATION_READ_UNCOMMITTED	读未提交	mysq1支持四种,默认可重复度
      ISOLATION_READ COMMITTED	读已提交	oracle支持两种(读己提交和串行化),默认是读已提交
      ISOLATION_REPEATABLE READ	可重复度
      ISOLATION SERIALIZABLE		串行化
      
  • **事务传播行为:**描述多个方法嵌套调用时,被调用方法对事务的支持

    • PROPAGATION_REQUIRED = 0(必须有事务,这是默认值)

      如果存在一个事务,则加入到当前事务。如果没有事务则开启一个新的事务。

    • PROPAGATION_SUPPORTS = 1(支持有事务)

      如果存在一个事务,则加入到当前事务。如果没有事务则非事务运行。

    • PROPAGATION_MANDATORY = 2(强制有事务,自己还不负责创建)

      如果存在一个事务,则加入到当前事务。如果没有事务,则抛出异常。

    • PROPAGATION_REQUIRES_NEW = 3(必须有新的)

      总是开启一个新的事务。如果存在一个事务,则将这个存在的事务挂起,重新开启一个新的事务。

    • PROPAGATION_NOT_SUPPORTED = 4(不支持有事务)

      总是非事务地执行,并挂起任何存在的事务。

    • PROPAGATION_NEVER = 5(强制不要事务,自己还不负责挂起)

      总是非事务地执行,如果存在一个活动事务,则抛出异常

    • PROPAGATION_NESTED = 6(嵌套事务)

      如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务, 则开启一个新的事务。

      内层事务依赖于外层事务。外层事务失败时,会回滚内层事务所做的动作。

      而内层事务操作失败并不会引起外层事务的回滚

  • 是否只读事务

    isReadOnly : true | false(默认)

    只读事务: 只能查询,不能增 删 改。只读事务只能用于查询方法

  • 事务超时时长

    TIMEOUT_DEFAULT = -1 :事务的超时时间,需要底层数据库支持才能使用此配置,默认值是 -1,代表无限制。

    超时时间:使用默认值


声明式事务(xml 配置事务)

基于XML的方式完成声明式事务配置:

  • 配置事务管理器交给Spring容器管理(切面类)
  • 配置事务通知
  • 配置事务的AOP
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
             http://www.springframework.org/schema/beans/spring-beans.xsd
             http://www.springframework.org/schema/context
             http://www.springframework.org/schema/context/spring-context.xsd
             http://www.springframework.org/schema/aop
             http://www.springframework.org/schema/aop/spring-aop.xsd
             http://www.springframework.org/schema/tx
             http://www.springframework.org/schema/tx/spring-tx.xsd">
    <!--包扫描-->
    <context:component-scan base-package="cn.test"></context:component-scan>
    <!--自定义的java对象:注解-->

    <!--第三方jar包中的对象:xml-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="username" value="root"></property>
        <property name="password" value="root"></property>
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql:///heima23"></property>
    </bean>

    <!--配置Spring中的事务-->
    <!--1、事务管理器交给容器管理-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!-- 2、配置事务通知。配置service层中所有类中所有方法,对事务的要求(支持) 
			id="advice" :表示IOC容器中真正的通知对象的id
			transaction-manager="transactionManager" :表示指定当前要对哪个事务管理器进行配置
			如果事务管理器在IOC容器中的id为transactionManager,此配置可以省略。
	-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <!--
                <tx:method :指定目标对象中切入点的方法名指定方法对事务的要求
                    name :方法名称。支持通配符 *
                    isolation :事务的隔离级别
                    timeout :超时时间
                    propagation :传播行为(REQUIRED)
                    read-only :是否只读事务(false)
            -->
            <tx:method name="save*" propagation="REQUIRED" read-only="false"></tx:method>
            <tx:method name="update*"></tx:method>
            <tx:method name="delete*"></tx:method>
            <tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method>
            <tx:method name="*"></tx:method>
        </tx:attributes>
    </tx:advice>
    <!--3、事务的AOP配置-->
    <aop:config>
        <!--切入点表达式-->
        <aop:pointcut id="pt" expression="execution(* cn.test.service.impl.*.*(..))"/>
        <!--配置切面。<aop:advisor只有在spring的声明式事务配置时才能使用-->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt"></aop:advisor>
    </aop:config>
</beans>

声明式事务(注解)

开启事务注解支持(xml 方式)

  • 在XML配置文件中,开启事务注解的支持:事务注解驱动
  • 在XML配置文件中,创建事务管理器交给容器管理
  • 在需要事务的类或者方法上,使用 @Transactional 注解配置事务
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
			    http://www.springframework.org/schema/beans/spring-beans.xsd
			    http://www.springframework.org/schema/context
			    http://www.springframework.org/schema/context/spring-context.xsd
			    http://www.springframework.org/schema/aop
			    http://www.springframework.org/schema/aop/spring-aop.xsd
			    http://www.springframework.org/schema/tx
			    http://www.springframework.org/schema/tx/spring-tx.xsd">
    <!--包扫描-->
    <context:component-scan base-package="cn.test"></context:component-scan>

    <!--开启事务注解的支持-->
    <tx:annotation-driven></tx:annotation-driven>

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

    <!--自定义的java对象:注解-->

    <!--第三方jar包中的对象:xml-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="username" value="root"></property>
        <property name="password" value="root"></property>
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql:///test"></property>
    </bean>
</beans>

开启事务注解支持(配置类方式)

  • @EnableTransactionManagement:标注在配置类上,开启事务注解支持
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;

@Configuration
@ComponentScan(basePackages = "cn.test")
@EnableTransactionManagement
public class SpringConfig {
    
    

    /**
     * 创建datasource
     */
    @Bean
    public DataSource getDataSource() {
    
    
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUsername("root");
        dataSource.setPassword("root");
        dataSource.setUrl("jdbc:mysql:///test");
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        return dataSource;
    }

    /**
     * 创建jdbctemplate
     *  1、从容器中获取datasource
     *  2、调用方法
     */
    @Bean
    public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
    
    
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }

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

声明式事务注解的使用

  • @Transactional:配置事务

    常用属性:

    • rollbackFor 属性:设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则进行事务回滚

      • 指定单一异常类:@Transactional(rollbackFor=Exception.class)

      • 指定多个异常类:@Transactional(rollbackFor={RuntimeException.class, Exception.class})

    • readOnly 属性:是否只读事务 ( true | false(默认值) )

    • propagation 属性:事务传播行为 ( SUPPORTS | REQUIRED(默认值) )

    • transactionManager 属性:多个事务管理器托管在 Spring 容器中时,指定事务管理器的 bean 名称

    • isolation 属性:设置底层数据库的事务隔离级别,事务隔离级别用于处理多事务并发的情况

      通常使用数据库的默认隔离级别即可,基本不需要进行设置

    • noRollbackFor 属性:设置不需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,不进行事务回滚

    标注位置说明:

    • 标注在类上:该类中所有方法都具有相同的事务配置
    • 标注在方法上:该方法具有事务配置
    • 同时标注在类上和方法上:就近原则(方法上的事务配置生效)
import cn.test.dao.AccountDao;
import cn.test.domain.Account;
import cn.test.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional(propagation = Propagation.SUPPORTS)
public class AccountServiceImpl implements AccountService {
    
    

    @Autowired
    private AccountDao accountDao;

    @Transactional
    //@Transactional(propagation = Propagation.REQUIRED,readOnly = false)
    public void transfer(String sourceName, String targetName, float money) throws Exception {
    
    
        //1、根据账户名称查询两个账户
        Account sourceAccount = accountDao.findByName(sourceName); //转出账户
        Account targetAccount = accountDao.findByName(targetName); //转入账户
        //2、操作金额转换(转出账户扣除金额,转入账户添加金额)
        sourceAccount.setMoney(sourceAccount.getMoney() - money);
        targetAccount.setMoney(targetAccount.getMoney() + money);
        //3、更新账户
        accountDao.update(sourceAccount);
        int i=1/0;
        accountDao.update(targetAccount);
    }
}

拓展

Spring 整合单元测试

当在单元测试中,点击 run 的时候,底层工作的其实是一个运行器,默认是 junit 提供的 ParentRunner 运行器,它是不认识Spring的环境,这也就意味着,它无法从 Spring 的容器中获取bean。

如果想要从 Spring 的容器中获取对象,需要使用 Spring 提供的运行器。

  • 引入依赖

    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
    
    <!--spring-junit 整合单元测试-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.1.6.RELEASE</version>
    </dependency>
    
  • @RunWith 注解:设置单元测试的运行器,通过 value 属性指定单元测试运行环境

    • JUnit4.class :指定使用 JUnit4 来运行
    • SpringJUnit4ClassRunner.class :Spring 测试环境
    • SpringRunner.class:Spring 测试环境

    注:

    • SpringRunner extends SpringJUnit4ClassRunner.class

    • 使用上 JUnit4.12 或更高版本以上 SpringRunner,SpringJUnit4ClassRunner 都可以使用

      但是推荐使用 SpringRunner,final类型,安全

    • JUnit4.12 以下版本就只能使用 SpringJUnit4ClassRunner

  • @ContextConfiguration 注解:指定容器的配置信息

    • localtions 属性:配置文件路径
    • classes 属性:配置类
@RunWith(SpringRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class AccountJunitTest {
    
    

    @Autowired
    private AccountService accountService;

    //测试保存
    @Test
    public void testInsert() {
    
    
        //3、调用方法保存
        Account account = new Account();
        account.setMoney(100f);
        account.setName("小李1");
        accountService.saveAccount(account);
    }
}

配置文件的模块化

若配置都集中配在了一个applicationContext.xml文件中,当开发人员过多时, 如果所有bean都配置到同一个配置文件中,会使这个文件巨大,而且也不方便维护。针对这个问题,Spring提供了多配置文件的方式,也就是所谓的配置文件模块化。

  1. 并列的多个配置文件
    直接编写多个配置文件,比如说beans1.xml,beans2.xml…, 然后在创建ApplicationContext的时候,直接
    传入多个配置文件。
ApplicationContext act = new ClassPathXmlApplicationContext("beans1.xml","beans2.xml","...");
  1. 主从配置文件
    先陪一个主配置文件,然后在里面导入其它的配置文件。
  <import resource="beans1.xml" />
  <import resource="beans2.xml" />

  <!--拓展:引入本地properties配置文件-->
  <context:property-placeholder location="classpath:db.properties"/>

注意事项:

  • 同一个xml文件中不能出现相同名称的bean,如果出现会报错
  • 多个xml文件如果出现相同名称的bean,不会报错,但是后加载的会覆盖前加载的bean,所以企业开发中尽
    量保证bean的名称是唯一的。

Spring Bean 单例的高并发安全问题

Spring的bean默认都是单例的,某些情况下,单例是并发不安全的,以Controller举例,问题根源在于,若在Controller中定义成员变量,多个请求来临,进入的都是同一个单例的Controller对象,若对此成员变量的值进行修改操作,则会互相影响,无法达到并发安全(不同于线程隔离的概念)的效果。

抛出问题

多次访问此url,可以看到每次的结果都是自增的,所以这样的代码显然是并发不安全的。

@Controller
public class HomeController {
    
    
    private int i;
    @GetMapping("testsingleton1")
    @ResponseBody
    public int test1() {
    
    
        return ++i;
    }
}

解决方案

方案1:尽量避免使用成员变量

在业务允许的条件下,尽量避免使用成员变量,使用方法中的局部变量


方案2:使用并发安全的类

Java作为功能性超强的编程语言,API丰富,如果非要在单例bean中使用成员变量,可以考虑使用并发安全的容器,如ConcurrentHashMap、ConcurrentHashSet等,将成员变量(一般可以是当前运行中的任务列表等这类变量)包装到这些并发安全的容器中进行管理即可。


方案3:分布式或微服务的并发安全

如果还要进一步考虑到微服务或分布式服务的影响,方案2便不足以处理了,所以可以借助于可以共享某些信息的分布式缓存中间件如Redis等,这样即可保证同一种服务的不同服务实例都拥有同一份共享信息(如当前运行中的任务列表等这类变量)。


方案4:单例变原型

对web项目,可以Controller类上加注解@Scope(“prototype”)或@Scope(“request”),对非web项目,在Component类上添加注解@Scope(“prototype”)。

优点:实现简单

缺点:很大程度上增大了bean创建实例化销毁的服务器资源开销


不可用方案:线程隔离类ThreadLocal

web服务器默认的请求线程池大小为10,这10个核心线程可以被之后不同的Http请求复用。

ThreadLocal的方式可以达到线程隔离,但还是无法达到并发安全。


使用 @Autowired 注解给静态变量赋值

描述:

在一些工具类中可能会用到Ioc容器中的对象,而工具类中的成员变量往往是静态的,此时使用@Autowired注解就会出现NullpointerException(空指针异常)。

原理剖析:

静态变量、类变量不是对象的属性,而是一个类的属性,所以静态方法是属于类(class)的,普通方法才是属于实体对象(也就是New出来的对象)的,spring注入是在容器中实例化对象,所以不能使用静态方法。

而使用静态变量、类变量扩大了静态方法的使用范围。静态方法在spring是不推荐使用的,依赖注入的主要目的,是让容器去产生一个对象的实例,然后在整个生命周期中使用他们,同时也让testing工作更加容易。

一旦使用静态方法,就不再需要去产生这个类的实例,这会让testing变得更加困难,同时也不能为一个给定的类,依靠注入方式去产生多个具有不同的依赖环境的实例,这种static field是隐含共享的,并且是一种global全局状态,spring同样不推荐这样去做。


**解决方案1:**将@Autowire注解加到set方法上

@Component
public class Test {
    
    
    
    private static SessionFactory sessionFactory;
    
    @Autowired
    public void setSessionFactory(SessionFactory sessionFactory) {
    
    
        Test.sessionFactory = sessionFactory;
    }
}

**解决方案2:**用@PostConstruct注解

@Component
public class Test {
    
    
    
    private static SessionFactory sessionFactory;
    
    @Autowired
    private SessionFactory sessionFactory2;
    
    @PostConstruct
    public void beforeInit() {
    
    
        SessionFactory = sessionFactory2;
    }
}

**解决方案3:**将@Autowire注解加到构造方法上

@Component
public class Test {
    
    
    
    private static SessionFactory sessionFactory;
    
    @Autowired
    public Test(SessionFactory sessionFactory) {
    
    
        Test.SessionFactory = SessionFactory;
    }
}

非容器中的类调用容器中的类

描述:

使用@Autowired注入对象时,一般被注入的类都带有@Coponent、@Controller、@Service 、@repository等注解才可以。注入类和被注入类都被spring所管理,可以完成调用。但是当非容器类(没加以上注解时)使用@Autowired调用容器中的类时,注入对象为空,报空指针异常。

解决方案:

创建工具类BeanUtils,在这个工具类中的getBean可以得到容器中的类,在非容器类中使用

@Component
public class BeanUtils implements ApplicationContextAware {
    
    
    /**
     * 以静态变量保存ApplicationContext,可在任意代码中取出ApplicaitonContext.
     */
    private static ApplicationContext context;

    /**
     * 实现ApplicationContextAware接口的context注入函数, 将其存入静态变量.
     */
    @Override
    public void setApplicationContext(ApplicationContext context) {
    
    
        BeanUtils.context = context;
    }

    public static ApplicationContext getApplicationContext() {
    
    
        return context;
    }

    /**
     * 从静态变量ApplicationContext中取得Bean, 自动转型为所赋值对象的类型.  方法返回值的类型由调用者决定
     */
    public static <T> T getBean(String name) {
    
    
        return (T) context.getBean(name);
    }

    /// 获取当前环境
    public String getActiveProfile() {
    
    
        return context.getEnvironment().getActiveProfiles()[0];
    }
}

非容器类中使用容器中的类

public class StationFactory {
    
    
    Map<String, StationOperation> map = new HashMap<>();
    {
    
    
        map.put("定损中心主管指标表", BeanUtils.getBean("leadDSZXOperation"));
        map.put("定损中心员工指标表", BeanUtils.getBean("empDSZXOperation"));
        map.put("视频查勘中心主管指标表", BeanUtils.getBean("leadVideoSurveyCenterOperation"));
        map.put("视频查勘中心员工指标表", BeanUtils.getBean("empVideoSurveyCenterOperation"));
        map.put("视频定损中心主管指标表", BeanUtils.getBean("leadVideoDSCenterOperation"));
	}
}

猜你喜欢

转载自blog.csdn.net/footless_bird/article/details/126252162