SSM-Spring篇

技术体系架构

单一架构

一个项目,一个工程,导出为一个war包,在一个Tomcat上运行。也叫all in one。

单一架构,项目主要应用技术框架为:Spring , SpringMVC , Mybatis

分布式架构

一个项目(对应 IDEA 中的一个 project),拆分成很多个模块,每个模块是一个 IDEA 中的一个 module。每一个工程都是运行在自己的 Tomcat 上。模块之间可以互相调用。每一个模块内部可以看成是一个单一架构的应用。

分布式架构,项目主要应用技术框架:SpringBoot (SSM), SpringCloud , 中间件等

框架的概念与理解

框架( Framework )是一个集成了基本结构、规范、设计模式、编程语言和程序库等基础组件的软件系统,它可以用来构建更高级别的应用程序。框架的设计和实现旨在解决特定领域中的常见问题,帮助开发人员更高效、更稳定地实现软件开发目标。

 框架的优点包括以下几点:

1. 提高开发效率:框架提供了许多预先设计好了的组件和工具,能够帮助开发人员快速进行开发。相较于传统手写代码,在框架提供的规范化环境中,开发者可以更快地实现项目的各种要求。
2. 降低开发成本:框架的提供标准化的编程语言、数据操作等代码片段,避免了重复开发的问题,降低了开发成本,提供深度优化的系统,降低了维护成本,增强了系统的可靠性。
3. 提高应用程序的稳定性:框架通常经过了很长时间的开发和测试,其中的许多组件、代码片段和设计模式都得到了验证。重复利用这些组件有助于减少bug的出现,从而提高了应用程序的稳定性。
4. 提供标准化的解决方案:框架通常是针对某个特定领域的,通过提供标准化的解决方案,可以为开发人员提供一种共同的语言和思想基础,有助于更好地沟通和协作。

框架的缺点包括以下几个方面:

1. 学习成本高:框架通常具有特定的语言和编程范式。对于开发人员而言,需要花费时间学习其背后的架构、模式和逻辑,这对于新手而言可能会耗费较长时间。
2. 可能存在局限性:虽然框架提高了开发效率并可以帮助开发人员解决常见问题,但是在某些情况下,特定的应用需求可能超出框架的范围,从而导致应用程序无法满足要求。开发人员可能需要更多的控制权和自由度,同时需要在框架和应用程序之间进行权衡取舍。
3. 版本变更和兼容性问题:框架的版本发布和迭代通常会导致代码库的大规模变更,进而导致应用程序出现兼容性问题和漏洞。当框架变更时,需要考虑框架是否向下兼容,以及如何进行适当的测试、迁移和升级。
4. 架构风险:框架涉及到很多抽象和概念,如果开发者没有足够的理解和掌握其架构,可能会导致系统出现设计和架构缺陷,从而影响系统的健康性和安全性。 

站在文件结构的角度理解框架,可以将框架总结:框架 = jar包+配置文件

Spring和Spring Framework

广义上的 Spring 泛指以 Spring Framework 为基础的 Spring 技术栈。

狭义的 Spring 特指 Spring Framework,通常我们将它称为 Spring 框架。

对比理解:

QQ 和 腾讯

腾讯 = Spring

QQ = SpringFramework 

SpringFramework框架结构图:

功能模块功能介绍
Core Container核心容器,在 Spring 环境下使用任何功能都必须基于 IOC 容器。
AOP&Aspects面向切面编程
TX声明式事务管理。
Spring MVC提供了面向Web应用程序的集成功能。

 Spring Framework主要优势

1. 丰富的生态系统:Spring 生态系统非常丰富,支持许多模块和库,如 Spring Boot、Spring Security、Spring Cloud 等等,可以帮助开发人员快速构建高可靠性的企业应用程序。
2. 模块化的设计:框架组件之间的松散耦合和模块化设计使得 Spring Framework 具有良好的可重用性、可扩展性和可维护性。开发人员可以轻松地选择自己需要的模块,根据自己的需求进行开发。
3. 简化 Java 开发:Spring Framework 简化了 Java 开发,提供了各种工具和 API,可以降低开发复杂度和学习成本。同时,Spring Framework 支持各种应用场景,包括 Web 应用程序、RESTful API、消息传递、批处理等等。
4. 不断创新和发展:Spring Framework 开发团队一直在不断创新和发展,保持与最新技术的接轨,为开发人员提供更加先进和优秀的工具和框架。

因此,这些优点使得 Spring Framework 成为了一个稳定、可靠、且创新的框架,为企业级 Java 开发提供了一站式的解决方案。

Spring 使创建 Java 企业应用程序变得容易。它提供了在企业环境中采用 Java 语言所需的一切,支持 Groovy 和 Kotlin 作为 JVM 上的替代语言,并且可以根据应用程序的需求灵活地创建多种架构。从Spring Framework 6.0.6开始,Spring 需要 Java 17+。

Spring IoC容器 

什么是组件?

常规的三层架构处理请求流程:

整个项目就是由各种组件搭建而成的:

组件可以完全交给Spring 框架进行管理,Spring框架替代了程序员原有的new对象和对象属性赋值动作等!

Spring具体的组件管理动作包含:

- 组件对象实例化
- 组件属性属性赋值
- 组件对象之间引用
- 组件对象存活周期管理
- ......

我们只需要编写元数据(配置文件)告知Spring 管理哪些类组件和他们的关系即可!

注意:组件是映射到应用程序中所有可重用组件的Java对象,应该是可复用的功能对象!

组件一定是对象

对象不一定是组件

综上所述,Spring 充当一个组件容器,创建、管理、存储组件,减少了我们的编码压力,让我们更加专注进行业务编写!

组件交给spring管理的优势

1. 降低了组件之间的耦合性:Spring IoC容器通过依赖注入机制,将组件之间的依赖关系削弱,减少了程序组件之间的耦合性,使得组件更加松散地耦合。
2. 提高了代码的可重用性和可维护性:将组件的实例化过程、依赖关系的管理等功能交给Spring IoC容器处理,使得组件代码更加模块化、可重用、更易于维护。
3. 方便了配置和管理:Spring IoC容器通过XML文件或者注解,轻松的对组件进行配置和管理,使得组件的切换、替换等操作更加的方便和快捷。
4. 交给Spring管理的对象(组件),方可享受Spring框架的其他功能(AOP,声明事务管理)等 

 Spring IoC容器的接口与实现类

SpringIoc容器接口: 

`BeanFactory` 接口提供了一种高级配置机制,能够管理任何类型的对象,它是SpringIoC容器标准化超接口!

`ApplicationContext` 是 `BeanFactory` 的子接口。它扩展了以下功能:

- 更容易与 Spring 的 AOP 功能集成
- 消息资源处理(用于国际化)
- 特定于应用程序给予此接口实现,例如Web 应用程序的 `WebApplicationContext`

简而言之, `BeanFactory` 提供了配置框架和基本功能,而 `ApplicationContext` 添加了更多特定于企业的功能。 `ApplicationContext` 是 `BeanFactory` 的完整超集!

ApplicationContext容器实现类:

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

spring框架提供了多种配置方式:XML配置方式、注解方式和Java配置类方式

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

ioc容器

 Spring IoC 容器,负责实例化、配置和组装 bean(组件)核心容器。容器通过读取配置元数据来获取有关要实例化、配置和组装组件的指令 

ioc控制反转

IoC 主要是针对对象的创建和调用控制而言的,也就是说,当应用程序需要使用一个对象时,不再是应用程序直接创建该对象,而是由 IoC 容器来创建和管理,即控制权由应用程序转移到 IoC 容器中,也就是“反转”了控制权。这种方式基本上是通过依赖查找的方式来实现的,即 IoC 容器维护着构成应用程序的对象,并负责创建这些对象。 

DI注入

DI 是指在组件之间传递依赖关系的过程中,将依赖关系在容器内部进行处理,这样就不必在应用程序代码中硬编码对象之间的依赖关系,实现了对象之间的解耦合。在 Spring 中,DI 是通过 XML 配置文件或注解的方式实现的。它提供了三种形式的依赖注入:构造函数注入、Setter 方法注入和接口注入。 

 SpringIoc实践

XML方式

1.创建maven项目,引入需要的依赖

<!--spring context依赖-->
        <!--当你引入Spring Context依赖之后,表示将Spring的基础依赖引入了-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>6.0.6</version>
        </dependency>
        <!--junit5测试-->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.10.2</version>
        </dependency>

2.创建一些实体类(重要的会展示代码)

3.创建并编写spring配置文件(在resources目录下创建)

<?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标签:通过配置bean标签告诉IOC容器需要创建对象的组件信息
       - id属性:bean的唯一标识,方便后期获取Bean!
       - class属性:组件类的全限定符!
       -->

    <!--无参构造实例化-->
    <bean id="person1" class="org.example.pojo.Person"/>
    <bean id="son1" class="org.example.pojo.Son"/>

    <!--静态工厂方法来实例化-->
    <!--factory-method: 指定静态工厂方法,注意,该方法必须是static方法。-->
    <bean id="sonStatic1" class="org.example.pojo.SonStatic" factory-method="getInstance"/>

    <!--基于实例工厂方法实例化-->
    <!--factory-bean属性:指定当前容器中工厂Bean 的名称。
        factory-method:  指定实例工厂方法名。注意,实例方法必须是非static的!-->
    <bean id="fater" class="org.example.pojo.fater"/>
    <bean id="sonStatic2"  factory-bean="fater" factory-method="getSonStatic"/>


    <!--DI注入-->
    <!--基于有参构造实例化-->
    <!--
        constructor-arg标签:可以引用构造参数 ref引用其他bean的标识。
    -->
    <bean id="person2" class="org.example.pojo.Person">
           <constructor-arg name="name" value="张三"/>
            <constructor-arg name="age" value="18"/>
            <constructor-arg name="son" ref="son1"/>
    </bean>

    <!--set注入-->
    <!--基于set方法实例化-->
    <!--
    - property标签: 可以给setter方法对应的属性赋值
    - property 标签: name属性代表**set方法标识**、ref代表引用bean的标识id、value属性代表基本属性值
    -->
    <bean id="person3" class="org.example.pojo.Person">
        <property name="name" value="张三"/>
        <property name="son" ref="son1"/>
        <property name="age" value="18"/>
    </bean>

</beans>

需要特别注意:引用其他bean,使用ref属性。直接注入基本类型值,使用value属性

 实例工厂和静态工厂实体类代码

public class fater {
    private static SonStatic sonStatic=new SonStatic();

    public  SonStatic getSonStatic() {

        return sonStatic;
    }

}
public class SonStatic {
    private static SonStatic sonStatic=new SonStatic();
    SonStatic(){}

    private static SonStatic getInstance(){
        return sonStatic;
    }

}

DI注入实体类代码

@Data
public class Person {
    private String name;
    private  int age;
    private Son son;

    public Person(String name, int age, Son son) {
        this.name = name;
        this.age = age;
        this.son = son;
    }

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
public class Son {
}

测试运行

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

public class spring1 {
   @Test
    public void test1(){
        ApplicationContext context = new ClassPathXmlApplicationContext("SpringXML.xml");//这里可以写多个文件

        //方式2:先实例化,再指定配置文件,最后刷新容器触发Bean实例化动作 [springmvc源码和contextLoadListener源码方式]
    //    ApplicationContext context = new ClassPathXmlApplicationContext();
        //设置配置配置文件,方法参数为可变参数,可以设置一个或者多个配置
     //   iocContainer1.setConfigLocations("services.xml", "daos.xml");
        //后配置的文件,需要调用refresh方法,触发刷新配置
      //  iocContainer1.refresh();

       //根据id获取
       Object person1 = context.getBean("person1");
       System.out.println(person1);
       Object sonStatic1 = context.getBean("sonStatic1");
       System.out.println(sonStatic1);
       Object sonStatic2 = context.getBean("sonStatic2");
       System.out.println(sonStatic2);
       Person person2 = (Person) context.getBean("person2");
       System.out.println(person2);//也可以提前强转

       /*
       * //方式2: 根据类型获取
        //根据类型获取,但是要求,同类型(当前类,或者之类,或者接口的实现类)只能有一个对象交给IoC容器管理
        //配置两个或者以上出现: org.springframework.beans.factory.NoUniqueBeanDefinitionException 问题
        HappyComponent happyComponent = iocContainer.getBean(HappyComponent.class);
        happyComponent.doWork();
       * */

       /*
       *方式3: 根据id和类型获取
       HappyComponent happyComponent = iocContainer.getBean("bean的id标识", HappyComponent.class);
       happyComponent.doWork();
       */
   }
}

组件的作用域和周期方法

周期方法

 创建对应的实体类
public class zhouqi {
    public void init(){
        System.out.println("初始化");
    }
    public void destroy(){
        System.out.println("结束");
    }
}

加入配置文件中

 <bean id="zhouqi" class="org.example.pojo.zhouqi" init-method="init" destroy-method="destroy"/>

组件作用域配置

<bean> 标签声明Bean,只是将Bean的信息配置给SpringIoC容器!

在IoC容器中,这些`<bean>标签对应的信息转成Spring内部 `BeanDefinition` 对象,`BeanDefinition` 对象内,包含定义的信息(id,class,属性等等)!

这意味着,`BeanDefinition`与`类`概念一样,SpringIoC容器可以可以根据`BeanDefinition`对象反射创建多个Bean对象实例。

具体创建多少个Bean的实例对象,由Bean的作用域Scope属性指定!

取值含义创建对象的时机默认值
singleton在 IOC 容器中,这个 bean 的对象始终为单实例IOC 容器初始化时
prototype这个 bean 在 IOC 容器中有多个实例获取 bean 时

 如果是在WebApplicationContext环境下还会有另外两个作用域(但不常用):

取值含义创建对象的时机默认值
request请求范围内有效的实例每次请求
session会话范围内有效的实例每次会话
<bean id="zhouqi2" class="org.example.pojo.zhouqi" scope="prototype"/><!--默认单例,所以这里就设置多例了-->

 单例:创建出来的多个对象地址都一样,相当于只能创建一个对象

如p1对象,p2对象  p1==p2(true)

多例:创建出来的地址每个多不一样,可以创建多个对象

如:p1对象,p2对象等 p1==p2(false)

 Factorybean应用

准备FactoryBean实现类

public class HappyMachine {
}
public class HappyFactoryBean implements FactoryBean<HappyMachine> {

    private String machineName;

    public String getMachineName() {
        return machineName;
    }

    public void setMachineName(String machineName) {
        this.machineName = machineName;
    }

    @Override
    public HappyMachine getObject() throws Exception {

        // 方法内部模拟创建、设置一个对象的复杂过程
        HappyMachine happyMachine = new HappyMachine();
        return happyMachine;
    }

    @Override
    public Class<?> getObjectType() {

        // 返回要生产的对象的类型
        return HappyMachine.class;
    }

配置FactoryBean实现类

    <!-- FactoryBean机制 -->
    <!-- 这个bean标签中class属性指定的是HappyFactoryBean,但是将来从这里获取的bean是HappyMachine对象 -->
    <bean id="happyMachine7" class="org.example.pojo.HappyFactoryBean">
        <!-- property标签仍然可以用来通过setXxx()方法给属性赋值 -->
        <property name="machineName" value="iceCreamMachine"/>
    </bean>

测试读取FactoryBean和FactoryBean.getObject对象

  //注意: 直接根据声明FactoryBean的id,获取的是getObject方法返回的对象
       HappyMachine happyMachine = context.getBean("happyMachine7",HappyMachine.class);
       System.out.println("happyMachine = " + happyMachine);

       //如果想要获取FactoryBean对象, 直接在id前添加&符号即可!  &happyMachine7 这是一种固定的约束
       Object bean = context.getBean("&happyMachine7");
       System.out.println("bean = " + bean);


    <!-- 导入外部属性文件 -->
    <context:property-placeholder location="classpath:jdbc.properties" />

注解方式

1. 注解方式IoC只是标记哪些类要被Spring管理
2. 最终,我们还需要XML方式或者后面讲解Java配置类方式指定注解生效的包
3. 现阶段配置方式为 注解 (标记)+ XML(扫描)

Spring 提供了以下多个注解,这些注解可以直接标注在 Java 类上,将它们定义成 Spring Bean。

注解说明
@Component该注解用于描述 Spring 中的 Bean,它是一个泛化的概念,仅仅表示容器中的一个组件(Bean),并且可以作用在应用的任何层次,例如 Service 层、Dao 层等。 使用时只需将该注解标注在相应类上即可。
@Repository该注解用于将数据访问层(Dao 层)的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
@Service该注解通常作用在业务层(Service 层),用于将业务层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
@Controller该注解通常作用在控制层(如SpringMVC 的 Controller),用于将控制层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。

@Component(value = "commonComponent1")
public class CommonComponent {
}

在我们使用 XML 方式管理 bean 的时候,每个 bean 都有一个唯一标识——id 属性的值,便于在其他地方引用。现在使用注解后,每个组件仍然应该有一个唯一标识。

默认情况:

类名首字母小写就是 bean 的 id。例如:SoldierController 类对应的 bean 的 id 就是 soldierController。

也可以使用value属性来指定它的id

当注解中只设置一个属性时,value属性的属性名可以省略:

例如:@Component( "commonComponent1")

配置文件确定扫描范围

    <!--扫描外部的文件-->
       <context:property-placeholder location="classpath:jdbc.properties"/>

    <!-- 配置自动扫描的包
        1.包要精准,提高性能!
         2.会扫描指定的包和子包内容
         3.多个包可以使用,分割 例如: com.atguigu.controller,com.atguigu.service等
    -->
    <context:component-scan base-package="org.example.service"/>


    <!--排除不扫描的注解
    此处排除了Controller
    @Controller注解不可用
    -->
    <context:component-scan base-package="org.example.service">
        <!-- context:exclude-filter标签:指定排除规则 -->
        <!-- type属性:指定根据什么来进行排除,annotation取值表示根据注解来排除 -->
        <!-- expression属性:指定排除规则的表达式,对于注解来说指定全类名即可 -->
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

    <!--只扫描指定的注解-->
    <!-- 仅扫描 = 关闭默认规则 + 追加规则 -->
    <!-- use-default-filters属性:取值false表示关闭默认扫描规则 -->
    <context:component-scan base-package="org.example.service">
    <!-- context:include-filter标签:指定在原有扫描规则的基础上追加的规则 -->
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

周期方法

public class BeanOne {

  //周期方法要求: 方法命名随意,但是要求方法必须是 public void 无形参列表
  @PostConstruct  //注解制指定初始化方法
  public void init() {
    // 初始化逻辑
  }
}

public class BeanTwo {
  
  @PreDestroy //注解指定销毁方法
  public void cleanup() {
    // 释放资源逻辑
  }
}

组件作用域配置

@Scope(scopeName = ConfigurableBeanFactory.SCOPE_SINGLETON) //单例,默认值
@Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE) //多例  二选一
public class BeanOne {

  //周期方法要求: 方法命名随意,但是要求方法必须是 public void 无形参列表
  @PostConstruct  //注解制指定初始化方法
  public void init() {
    // 初始化逻辑
  }
}

bean的属性赋值:引用类型自动装配(DI)

- SoldierController 需要 SoldierService
- SoldierService 需要 SoldierDao

同时在各个组件中声明要调用的方法。

SoldierController中声明方法

@Controller(value = "tianDog")
public class SoldierController {
     @Autowired
    private SoldierService soldierService;

    public void getMessage() {
        soldierService.getMessage();
    }

}

SoldierService中声明方法


@Service("smallDog")
public class SoldierService {
     @Autowired
    private SoldierDao soldierDao;

    public void getMessage() {
        soldierDao.getMessage();
    }
}

SoldierDao中声明方法


@Repository
public class SoldierDao {

    public void getMessage() {
        System.out.print("I am a soldier");
    }

}

注意

参与自动装配的组件(需要装配、被装配)全部都必须在IoC容器中。

注意:不区分IoC的方式!XML和注解都可以!

@Autowired注解

在成员变量上直接标记@Autowired注解即可,不需要提供setXxx()方法。以后我们在项目中的正式用法就是这样。

@Autowired注解细节

标记位置:成员变量

与xml进行bean ref引用不同,他不需要有set方法! 

@Service("smallDog")
public class SoldierService {
    
    @Autowired
    private SoldierDao soldierDao;
    
    public void getMessage() {
        soldierDao.getMessage();
    }
}


//构造器
@Controller(value = "tianDog")
public class SoldierController {
    
    private SoldierService soldierService;
    
    @Autowired
    public SoldierController(SoldierService soldierService) {
        this.soldierService = soldierService;
    }
   
//setXxx()方法
@Controller(value = "tianDog")
public class SoldierController {

    private SoldierService soldierService;

    @Autowired
    public void setSoldierService(SoldierService soldierService) {
        this.soldierService = soldierService;
    }

 工作流程

- 首先根据所需要的组件类型到 IOC 容器中查找
    - 能够找到唯一的 bean:直接执行装配
    - 如果完全找不到匹配这个类型的 bean:装配失败
    - 和所需类型匹配的 bean 不止一个
        - 没有 @Qualifier 注解:根据 @Autowired 标记位置成员变量的变量名作为 bean 的 id 进行匹配
            - 能够找到:执行装配
            - 找不到:装配失败
        - 使用 @Qualifier 注解:根据 @Qualifier 注解中指定的名称作为 bean 的id进行匹配
            - 能够找到:执行装配
            - 找不到:装配失败

@Controller(value = "tianDog")
public class SoldierController {
    
    @Autowired
    @Qualifier(value = "maomiService222")
    // 根据面向接口编程思想,使用接口类型引入Service组件
    private ISoldierService soldierService;

佛系装配(跳过) 

@Value注解读取配置


    /**
     * 情况1: ${key} 取外部配置key对应的值!
     * 情况2: ${key:defaultValue} 没有key,可以给与默认值
     */
    @Value("${catalog:hahaha}")
    private String name;

. 注解+XML IoC方式问题总结
    1. 自定义类可以使用注解方式,但是第三方依赖的类依然使用XML方式!
    2. XML格式解析效率低!

配置类方式 (重点)

Spring 完全注解配置(Fully Annotation-based Configuration)是指通过 Java配置类 代码来配置 Spring 应用程序,使用注解来替代原本在 XML 配置文件中的配置。相对于 XML 配置,完全注解配置具有更强的类型安全性和更好的可读性。

使用 @Configuration 注解将一个普通的类标记为 Spring 的配置类 

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

//标注当前类是配置类,替代application.xml    
@Configuration
//使用注解读取外部配置,替代 <context:property-placeholder标签
@PropertySource("classpath:application.properties")
//使用@ComponentScan注解,可以配置扫描包,替代<context:component-scan标签
@ComponentScan(basePackages = {"com.atguigu.components"})
public class MyConfiguration {
    
}

 测试创建IoC容器

// AnnotationConfigApplicationContext 根据配置类创建 IOC 容器对象
ApplicationContext iocContainerAnnotation = 
new AnnotationConfigApplicationContext(MyConfiguration.class);

@Configuration指定一个类为配置类,可以添加配置注解,替代配置xml文件

@ComponentScan(basePackages = {"包","包"}) 替代<context:component-scan标签实现注解扫描

@PropertySource("classpath:配置文件地址") 替代 <context:property-placeholder标签

配合IoC/DI注解,可以进行完整注解开发!

@Bean组件

**场景需求**:将Druid连接池对象存储到IoC容器

**需求分析**:第三方jar包的类,添加到ioc容器,无法使用@Component等相关注解!因为源码jar包内容为只读模式!

@Bean 注释用于指示方法实例化、配置和初始化要由 Spring IoC 容器管理的新对象。对于那些熟悉 Spring 的 XML 配置的人来说, @Bean 注释与 元素起着相同的作用。

//标注当前类是配置类,替代application.xml    
@Configuration
//引入jdbc.properties文件
@PropertySource({"classpath:application.properties","classpath:jdbc.properties"})
@ComponentScan(basePackages = {"com.atguigu.components"})
public class MyConfiguration {

    //如果第三方类进行IoC管理,无法直接使用@Component相关注解
    //解决方案: xml方式可以使用<bean标签
    //解决方案: 配置类方式,可以使用方法返回值+@Bean注解
    @Bean
    public DataSource createDataSource(@Value("${jdbc.user}") String username,
                                       @Value("${jdbc.password}")String password,
                                       @Value("${jdbc.url}")String url,
                                       @Value("${jdbc.driver}")String driverClassName){
        //使用Java代码实例化
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        dataSource.setUrl(url);
        dataSource.setDriverClassName(driverClassName);
        //返回结果即可
        return dataSource;
    }

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

 @Bean 注释注释方法。使用此方法在指定为方法返回值的类型的 ApplicationContext 中注册 Bean 定义。缺省情况下,Bean 名称与方法名称相同。下面的示例演示 @Bean 方法声明:

@Configuration
public class AppConfig {

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

前面的配置完全等同于下面的Spring XML: 

<beans>
  <bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>

 @Bean 初始化和销毁方法指定

public class BeanOne {
    private void init() {
        System.out.println("初始化");
    }
}
public class BeanTwo {
    private void cleanup() {
        System.out.println("销毁");
    }
}
@Configuration
public class AppConfig {

    @Bean(initMethod = "init")
    public BeanOne beanOne() {
        return new BeanOne();
    }

    @Bean(destroyMethod = "cleanup")
    public BeanTwo beanTwo() {
        return new BeanTwo();
    }
}

@Bean Scope作用域

@Configuration
public class MyConfiguration {

  @Bean
  @Scope("prototype")
  public Encryptor encryptor() {
    // ...
  }
}

@Bean方法之间依赖

方法一

直接调用方法返回 Bean 实例:在一个 `@Bean` 方法中直接调用其他 `@Bean` 方法来获取 Bean 实例,虽然是方法调用,也是通过IoC容器获取对应的Bean,例如:

@Configuration
public class JavaConfig {

    @Bean
    public HappyMachine happyMachine(){
        return new HappyMachine();
    }

    @Bean
    public HappyComponent happyComponent(){
        HappyComponent happyComponent = new HappyComponent();
        //直接调用方法即可! 
        happyComponent.setHappyMachine(happyMachine());
        return happyComponent;
    }

}

方法二:

参数引用法:通过方法参数传递 Bean 实例的引用来解决 Bean 实例之间的依赖关系,例如: 

package com.atguigu.config;

import com.atguigu.ioc.HappyComponent;
import com.atguigu.ioc.HappyMachine;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * projectName: com.atguigu.config
 * description: 配置HappyComponent和HappyMachine关系
 */

@Configuration
public class JavaConfig {

    @Bean
    public HappyMachine happyMachine(){
        return new HappyMachine();
    }

    /**
     * 可以直接在形参列表接收IoC容器中的Bean!
     *    情况1: 直接指定类型即可
     *    情况2: 如果有多个bean,(HappyMachine 名称 ) 形参名称等于要指定的bean名称!
     *           例如:
     *               @Bean
     *               public Foo foo1(){
     *                   return new Foo();
     *               }
     *               @Bean
     *               public Foo foo2(){
     *                   return new Foo()
     *               }
     *               @Bean
     *               public Component component(Foo foo1 / foo2 通过此处指定引入的bean)
     */
    @Bean
    public HappyComponent happyComponent(HappyMachine happyMachine){
        HappyComponent happyComponent = new HappyComponent();
        //赋值
        happyComponent.setHappyMachine(happyMachine);
        return happyComponent;
    }

}

@Import

@Import 注释允许从另一个配置类加载 @Bean 定义 

@Configuration
public class ConfigA {

  @Bean
  public A a() {
    return new A();
  }
}

@Configuration
@Import(ConfigA.class)
public class ConfigB {

  @Bean
  public B b() {
    return new B();
  }
}

现在,在实例化上下文时不需要同时指定 `ConfigA.class` 和 `ConfigB.class` ,只需显式提供 `ConfigB` ,如以下示例所示:

public static void main(String[] args) {
  ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);

  // now both beans A and B will be available...
  A a = ctx.getBean(A.class);
  B b = ctx.getBean(B.class);
}

 此方法简化了容器实例化,因为只需要处理一个类,而不是要求您在构造期间记住可能大量的 @Configuration 类。

总结

xml方式总结

1. 所有内容写到xml格式配置文件中
2. 声明bean通过<bean标签
3. <bean标签包含基本信息(id,class)和属性信息 <property name value / ref
4. 引入外部的properties文件可以通过<context:property-placeholder
5. IoC具体容器实现选择ClassPathXmlApplicationContext对象

xml+注解方式总结

1. 注解负责标记IoC的类和进行属性装配
2. xml文件依然需要,需要通过<context:component-scan标签指定注解范围
3. 标记IoC注解:@Component,@Service,@Controller,@Repository 
4. 标记DI注解:@Autowired @Qualifier @Resource @Value
5. IoC具体容器实现选择ClassPathXmlApplicationContext对象

完全配置类方式总结

1. 完全注解方式指的是去掉xml文件,使用配置类 + 注解实现
2. xml文件替换成使用@Configuration注解标记的类
3. 标记IoC注解:@Component,@Service,@Controller,@Repository 
4. 标记DI注解:@Autowired @Qualifier @Resource @Value
5. <context:component-scan标签指定注解范围使用@ComponentScan(basePackages = {"com.atguigu.components"})替代
6. <context:property-placeholder引入外部配置文件使用@PropertySource({"classpath:application.properties","classpath:jdbc.properties"})替代
7. <bean 标签使用@Bean注解和方法实现
8. IoC具体容器实现选择AnnotationConfigApplicationContext对象

整合Spring-Test5搭建测试环境

1. 整合测试环境作用

    好处1:不需要自己创建IOC容器对象了

    好处2:任何需要的bean都可以在测试类中直接享受自动装配

导入相关依赖

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

整合测试注解使用

//配置类
@Configuration
@ComponentScan(basePackages = "org.example.service")

public class AppConfig {

    @Bean(initMethod = "init")
    public BeanOne beanOne() {
        return new BeanOne();
    }

    @Bean(destroyMethod = "cleanup")
    public BeanTwo beanTwo() {
        return new BeanTwo();
    }
}
@Service
public class BeanOne {
    private void init() {
        System.out.println("初始化");
    }
}

@Component
public class BeanTwo {
    private void cleanup() {
        System.out.println("销毁");
    }
    public void show(){
        System.out.println("666");
    }
}

//测试的
@SpringJUnitConfig(value = {AppConfig.class})  //指定配置类
public class spring2 {
    @Autowired
    private BeanTwo beanTwo;

   @Test
    public void test(){
       beanTwo.show();
       System.out.println(beanTwo);
    }
}

 

Spring AOP 面向切面编程

AOP:Aspect Oriented Programming面向切面编程

AOP可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。

AOP技术恰恰相反,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。

使用AOP,可以在不修改原来代码的基础上添加新功能。

AOP思想主要的应用场景

AOP(面向切面编程)是一种编程范式,它通过将通用的横切关注点(如日志、事务、权限控制等)与业务逻辑分离,使得代码更加清晰、简洁、易于维护。AOP可以应用于各种场景,以下是一些常见的AOP应用场景:

1. 日志记录:在系统中记录日志是非常重要的,可以使用AOP来实现日志记录的功能,可以在方法执行前、执行后或异常抛出时记录日志。
2. 事务处理:在数据库操作中使用事务可以保证数据的一致性,可以使用AOP来实现事务处理的功能,可以在方法开始前开启事务,在方法执行完毕后提交或回滚事务。
3. 安全控制:在系统中包含某些需要安全控制的操作,如登录、修改密码、授权等,可以使用AOP来实现安全控制的功能。可以在方法执行前进行权限判断,如果用户没有权限,则抛出异常或转向到错误页面,以防止未经授权的访问。
4. 性能监控:在系统运行过程中,有时需要对某些方法的性能进行监控,以找到系统的瓶颈并进行优化。可以使用AOP来实现性能监控的功能,可以在方法执行前记录时间戳,在方法执行完毕后计算方法执行时间并输出到日志中。
5. 异常处理:系统中可能出现各种异常情况,如空指针异常、数据库连接异常等,可以使用AOP来实现异常处理的功能,在方法执行过程中,如果出现异常,则进行异常处理(如记录日志、发送邮件等)。
6. 缓存控制:在系统中有些数据可以缓存起来以提高访问速度,可以使用AOP来实现缓存控制的功能,可以在方法执行前查询缓存中是否有数据,如果有则返回,否则执行方法并将方法返回值存入缓存中。
7. 动态代理:AOP的实现方式之一是通过动态代理,可以代理某个类的所有方法,用于实现各种功能。

综上所述,AOP可以应用于各种场景,它的作用是将通用的横切关注点与业务逻辑分离,使得代码更加清晰、简洁、易于维护。

 


AOP术语名词介绍

1-横切关注点

从每个方法中抽取出来的同一类非核心业务。在同一个项目中,我们可以使用多个横切关注点对相关方法进行多个不同方面的增强。

这个概念不是语法层面天然存在的,而是根据附加功能的逻辑上的需要:有十个附加功能,就有十个横切关注点。

AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事务、异常等。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。

2-通知(增强)

每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法就叫通知方法。

- 前置通知:在被代理的目标方法前执行
- 返回通知:在被代理的目标方法成功结束后执行(**寿终正寝**)
- 异常通知:在被代理的目标方法异常结束后执行(**死于非命**)
- 后置通知:在被代理的目标方法最终结束后执行(**盖棺定论**)
- 环绕通知:使用try...catch...finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置

3-连接点 joinpoint

这也是一个纯逻辑概念,不是语法定义的。

指那些被拦截到的点。在 Spring 中,可以被动态代理拦截目标类的方法

 

4-切入点 pointcut

定位连接点的方式,或者可以理解成被选中的连接点!

是一个表达式,比如execution(* com.spring.service.impl.*.*(..))。符合条件的每个方法都是一个具体的连接点。

 

5-切面 aspect

切入点和通知的结合。是一个类。

 

6-目标 target

被代理的目标对象。 

7-代理 proxy

向目标对象应用通知之后创建的代理对象 

8-织入 weave

指把通知应用到目标上,生成代理对象的过程。可以在编译期织入,也可以在运行期织入,Spring采用后者。 

AOP基于注解实现 

AOP底层

- 动态代理(InvocationHandler):JDK原生的实现方式,需要被代理的目标类必须实现接口。因为这个技术要求代理对象和目标对象实现同样的接口(兄弟两个拜把子模式)。
- cglib:通过继承被代理的目标类(认干爹模式)实现代理,所以不需要目标类实现接口。
- AspectJ:早期的AOP实现的框架,SpringAOP借用了AspectJ中的AOP注解。

实现

1.加入依赖,可以只加spring-aspects这个依赖

<!-- spring-aspects会帮我们传递过来aspectjweaver -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>6.0.6</version>
</dependency>

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

 2.创建功能接口

public interface Calculator {
    
    int add(int i, int j);
    
    int sub(int i, int j);
    
    int mul(int i, int j);
    
    int div(int i, int j);
    
}

3.创建对应的实现类(将实现类加入ioc容器中)

@Component
public class CalculatorPureImpl implements Calculator {
    @Override
    public int add(int i, int j) {

        int result = i + j;

        return result;
    }

    @Override
    public int sub(int i, int j) {

        int result = i - j;

        return result;
    }

    @Override
    public int mul(int i, int j) {

        int result = i * j;

        return result;
    }

    @Override
    public int div(int i, int j) {

        int result = i / j;

        return result;
    }

4.声明切面类(切面类也需要加入到ioc容器中)

// @Aspect表示这个类是一个切面类
@Aspect
// @Component注解保证这个切面类能够放入IOC容器
@Component
public class LogAspect {
    // @Before注解:声明当前方法是前置通知方法
    // value属性:指定切入点表达式,由切入点表达式控制当前通知方法要作用在哪一个目标方法上
    @Before(value = "execution(public int org.example.aop.interfaces.impl.CalculatorPureImpl.add(int,int))")
    public void printLogBeforeCore() {
        System.out.println("[AOP前置通知] 方法开始了");
    }

    @AfterReturning(value = "execution(public int org.example.aop.interfaces.impl.CalculatorPureImpl.add(int,int))")
    public void printLogAfterSuccess() {
        System.out.println("[AOP返回通知] 方法成功返回了");
    }

    @AfterThrowing(value = "execution(public int org.example.aop.interfaces.impl.CalculatorPureImpl.add(int,int))")
    public void printLogAfterException() {
        System.out.println("[AOP异常通知] 方法抛异常了");
    }

    @After(value = "execution(public int org.example.aop.interfaces.impl.CalculatorPureImpl.add(int,int))")
    public void printLogFinallyEnd() {
        System.out.println("[AOP后置通知] 方法最终结束了");
    }

}

5.开启aspectj注解支持

@Configuration
@ComponentScan(basePackages = "org.example") //确定扫描包
@EnableAspectJAutoProxy  //开启AOP功能 作用等于 <aop:aspectj-autoproxy /> 配置类上开启 Aspectj注解支持!
public class SpringConfig {
}

6测试

设置一个接口属性,给他设置自动注入注解,这样会找到它的实现类去

@SpringJUnitConfig(value = {SpringConfig.class})
public class test3 {

    @Autowired
    private Calculator calculator;

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

}

结果

JointPoint接口

需要获取方法签名、传入的实参等信息时,可以在通知方法声明JoinPoint类型的形参。

- 要点1:JoinPoint 接口通过 getSignature() 方法获取目标方法的签名(方法声明时的完整信息)
- 要点2:通过目标方法签名对象获取方法名
- 要点3:通过 JoinPoint 对象获取外界调用目标方法时传入的实参列表组成的数组

    @Before(value = "execution(public int org.example.aop.interfaces.impl.CalculatorPureImpl.add(int,int))")
    public void printLogBeforeCore(JoinPoint joinPoint) {
        // 1.通过JoinPoint对象获取目标方法签名对象
        // 方法的签名:一个方法的全部声明信息
        Signature signature = joinPoint.getSignature();
        // 2.通过方法的签名对象获取目标方法的详细信息
        String methodName = signature.getName();
        System.out.println("methodName = " + methodName);
        int modifiers = signature.getModifiers();
        System.out.println("modifiers = " + modifiers);
        String declaringTypeName = signature.getDeclaringTypeName();
        System.out.println("declaringTypeName = " + declaringTypeName);
        // 3.通过JoinPoint对象获取外界调用目标方法时传入的实参列表
        Object[] args = joinPoint.getArgs();
        // 4.由于数组直接打印看不到具体数据,所以转换为List集合
        List<Object> argList = Arrays.asList(args);
        System.out.println("[AOP前置通知] " + methodName + "方法开始了,参数列表:" + argList);
    }

 此刻在测试一下

方法返回值

在返回通知中,通过@AfterReturning注解的returning属性获取目标方法的返回值!

  @AfterReturning(value = "execution(public int org.example.aop.interfaces.impl.CalculatorPureImpl.add(int,int))"  ,
            returning = "targetMethodReturnValue"
    )
    public void printLogAfterCoreSuccess(JoinPoint joinPoint, Object targetMethodReturnValue) {

        String methodName = joinPoint.getSignature().getName();

        System.out.println("[AOP返回通知] "+methodName+"方法成功结束了,返回值是:" + targetMethodReturnValue);
    }

 测试效果

异常对象捕捉

在异常通知中,通过@AfterThrowing注解的throwing属性获取目标方法抛出的异常对象

  @AfterThrowing(value = "execution(public int org.example.aop.interfaces.impl.CalculatorPureImpl.div(int,int))",
            throwing = "targetMethodException"
    )
    public void printLogAfterCoreException(JoinPoint joinPoint, Throwable targetMethodException) {

        String methodName = joinPoint.getSignature().getName();

        System.out.println("[AOP异常通知] "+methodName+"方法抛异常了,异常类型是:" + targetMethodException.getClass().getName());
    }

 因为要测试出异常来,所有这里设置的是除法,不再是加法

   @Test
    public void testCalculator(){
        calculator.div(1,0);
    }

切点表达式语法 

AOP切点表达式(Pointcut Expression)是一种用于指定切点的语言,它可以通过定义匹配规则,来选择需要被切入的目标对象。

 

语法

第一位:execution( ) 固定开头

第二位:方法访问修饰符

第三位:方法返回值

注意:

特殊情况 不考虑 访问修饰符和返回值

  execution(* * ) 这是错误语法

  execution(*) == 你只要考虑返回值 或者 不考虑访问修饰符 相当于全部不考虑了

第四位:指定包的地址

 固定的包: com.atguigu.api | service | dao
 单层的任意命名: com.atguigu.*  = com.atguigu.api  com.atguigu.dao  * = 任意一层的任意命名
 任意层任意命名: com.. = com.atguigu.api.erdaye com.a.a.a.a.a.a.a  ..任意层,任意命名 用在包上!
 注意: ..不能用作包开头   public int .. 错误语法  com..
 找到任何包下:  *..

第五位:指定类名称

固定名称: UserService
任意类名: *
部分任意: com..service.impl.*Impl
任意包任意类: *..*
 

第六位:指定方法名称

语法和类名一致
任意访问修饰符,任意类的任意方法: * *..*.*

 第七位:方法参数

第七位: 方法的参数描述
       具体值: (String,int) != (int,String) 没有参数 ()
       模糊值: 任意参数 有 或者 没有 (..)  ..任意参数的意识
       部分具体和模糊:
         第一个参数是字符串的方法 (String..)
         最后一个参数是字符串 (..String)
         字符串开头,int结尾 (String..int)
         包含int类型(..int..)

切点统一管理

创建一个专门存储切点的类

单独维护切点表达式

其他类的切点方法,类的全限定符号,方法名 

创建类,并设置切点,让其他类来引用,方面后续统一管理 与维护

@Component
public class PointCutO {
//其他类就引用这里的切点
    @Pointcut("execution(* org.example.aop.*.*.*(..))")
    public void pc(){
    }
}

// @Pointcut("execution(* org.example.aop.interfaces.*.*(..))")
  @Before("org.example.aop.PointCut.PointCutO.pc()")
  public void printLogBeforeCore(JoinPoint joinPoint) {
//略
}

   @AfterReturning(value = "org.example.aop.PointCut.PointCutO.pc()"  ,
            returning = "targetMethodReturnValue"
    )
    public void printLogAfterCoreSuccess(JoinPoint joinPoint, Object targetMethodReturnValue) {
//略
}

环绕通知

环绕通知对应整个 try...catch...finally 结构,包括前面四种通知的所有功能。

 

@Component
@Aspect
public class Around {

    @org.aspectj.lang.annotation.Around("org.example.aop.PointCut.PointCutO.pc()")
    public Object tx(ProceedingJoinPoint proceedingJoinPoint){
        //报正目标方法被执行

        Object[] args = proceedingJoinPoint.getArgs();        //获得目标方法参数
        Object result=null;
        try {
            System.out.println("开启事务");
          result=  proceedingJoinPoint.proceed(args);  //执行目标方法
            System.out.println("结束事务");
        } catch (Throwable e) {
            System.out.println("事务回滚");
    throw new RuntimeException(e);
        }
        return result;
    }

}

切面优先级

相同目标方法上同时存在多个切面时,切面的优先级控制切面的内外嵌套顺序。

- 优先级高的切面:外面
- 优先级低的切面:里面

使用 @Order 注解可以控制切面的优先级:

- @Order(较小的数):优先级高
- @Order(较大的数):优先级低

@order

@Component
@Aspect
@Order(1)//越大越先执行(1-10)

 AOP对我们的影响

1. 情景一
    - bean 对应的类没有实现任何接口
    - 根据 bean 本身的类型获取 bean
        - 测试:IOC容器中同类型的 bean 只有一个

            正常获取到 IOC 容器中的那个 bean 对象
        - 测试:IOC 容器中同类型的 bean 有多个

            会抛出 NoUniqueBeanDefinitionException 异常,表示 IOC 容器中这个类型的 bean 有多个

1. 情景二
    - bean 对应的类实现了接口,这个接口也只有这一个实现类
        - 测试:根据接口类型获取 bean
        - 测试:根据类获取 bean
        - 结论:上面两种情况其实都能够正常获取到 bean,而且是同一个对象 

 

1. 情景三
    - 声明一个接口
    - 接口有多个实现类
    - 接口所有实现类都放入 IOC 容器
        - 测试:根据接口类型获取 bean

            会抛出 NoUniqueBeanDefinitionException 异常,表示 IOC 容器中这个类型的 bean 有多个
        - 测试:根据类获取bean

            正常

1. 情景四
    - 声明一个接口
    - 接口有一个实现类
    - 创建一个切面类,对上面接口的实现类应用通知
        - 测试:根据接口类型获取bean

            正常
        - 测试:根据类获取bean

            无法获取

    原因分析:

    - 应用了切面后,真正放在IOC容器中的是代理类的对象
    - 目标类并没有被放到IOC容器中,所以根据目标类的类型从IOC容器中是找不到的!

 

 总结

Spring声明式事务 

编程式事务

编程式事务是指手动编写程序来管理事务,即通过编写代码的方式直接控制事务的提交和回滚。在 Java 中,通常使用事务管理器(如 Spring 中的 `PlatformTransactionManager`)来实现编程式事务。

编程式事务的主要优点是灵活性高,可以按照自己的需求来控制事务的粒度、模式等等。但是,编写大量的事务控制代码容易出现问题,对代码的可读性和可维护性有一定影响。

Connection conn = ...;
  
try {
    // 开启事务:关闭事务的自动提交
    conn.setAutoCommit(false);
    // 核心操作
    // 业务代码
    // 提交事务
    conn.commit();
  
}catch(Exception e){
  
    // 回滚事务
    conn.rollBack();
  
}finally{
  
    // 释放数据库连接
    conn.close();
  
}

 

编程式的实现方式存在缺陷:

- 细节没有被屏蔽:具体操作过程中,所有细节都需要程序员自己来完成,比较繁琐。
- 代码复用性不高:如果没有有效抽取出来,每次实现功能都需要自己编写代码,代码就没有得到复用。

声明式事务

声明式事务是指使用注解或 XML 配置的方式来控制事务的提交和回滚。

开发者只需要添加配置即可, 具体事务的实现由第三方框架实现,避免我们直接进行事务操作!

使用声明式事务可以将事务的控制和业务逻辑分离开来,提高代码的可读性和可维护性。

区别:

- 编程式事务需要手动编写代码来管理事务
- 而声明式事务可以通过配置文件或注解来控制事务。

 

Spring事务管理器

1. Spring声明式事务对应依赖
    - spring-tx: 包含声明式事务实现的基本规范(事务管理器规范接口和事务增强等等)
    - spring-jdbc: 包含DataSource方式事务管理器实现类DataSourceTransactionManager
    - spring-orm: 包含其他持久层框架的事务管理器实现类例如:Hibernate/Jpa等 

1. Spring声明式事务对应事务管理器接口

    我们现在要使用的事务管理器是org.springframework.jdbc.datasource.DataSourceTransactionManager,将来整合 JDBC方式、JdbcTemplate方式、Mybatis方式的事务实现!

    DataSourceTransactionManager类中的主要方法:

    - doBegin():开启事务
    - doSuspend():挂起事务
    - doResume():恢复挂起的事务
    - doCommit():提交事务
    - doRollback():回滚事务

 

 spring事务的依赖

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

  <!-- 声明式事务依赖-->
  <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>6.0.6</version>
  </dependency>

编写配置类

package org.example.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.*;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;

@Configuration
@ComponentScan(basePackages = {"org.example.tx"}) //确定扫描包
@EnableAspectJAutoProxy  //开启AOP功能 作用等于 <aop:aspectj-autoproxy /> 配置类上开启 Aspectj注解支持!
@EnableTransactionManagement //开始事务注解
@PropertySource({"classpath:jdbc.properties"}) //加载多个配置文件"
public class SpringConfig {

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

    @Bean
    public DataSource getDataSource(){
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setUrl(url);
        druidDataSource.setPassword(password);
        druidDataSource.setUsername(username);
        druidDataSource.setDriverClassName(driver);
        return druidDataSource;
    }

    @Bean
    public JdbcTemplate getSqlSessionFactoryBean(DataSource dataSource){
        return new JdbcTemplate(dataSource);
    }
    /**
     * 装配事务管理实现对象
     * k这次式它,也可以式mybatis
     * @param dataSource
     * @return
     */
    @Bean
    public TransactionManager transactionManager(DataSource dataSource){
        return new DataSourceTransactionManager(dataSource);
    }
}
@Service
public class StudentService {
    @Autowired
    private StudentDao studentDao;

    @Transactional
    public void changeInfo(){
        studentDao.updateAgeById(10086,4);
   //     int i=1/0;
        System.out.println("-----------");
        studentDao.updateNameById("test1",4);

    }
}
@EnableTransactionManagement //开始事务注解
@Transactional放的位置:方法|类上

方法:当前方法有事务

类上:类里面所有方法有事务

 只读模式设置(适合查询)

// readOnly = true把当前事务设置为只读 默认是false!
@Transactional(readOnly = true)

 超时时间(默认永远不超时-1)

我这里设置的10秒,如果超过时间就是回滚事务和释放异常,(用Thread.sleep()测试更适合哦)

    @Transactional(timeout = 10)

事务异常

rollbackFor属性:指定哪些异常类才会回滚,默认是 RuntimeException and Error 异常方可回滚!

默认情况下指定发生运行时异常才会回滚

我们可以指定Exception异常来控制所有异常都回滚

事务回滚是指将该事务已经完成对数据库的更新操作撤销,在事务中,每个正确的原子都会被顺序执行,知道遇到错误的原子操作

@Transactional(rollbackFor = Exception.class)

 noRollbackFor属性:指定哪些异常不会回滚, 默认没有指定

 @Transactional(rollbackFor = Exception.class,noRollbackFor = FileNotFoundException.class)

事务隔离级别

1. 事务隔离级别

    数据库事务的隔离级别是指在多个事务并发执行时,数据库系统为了保证数据一致性所遵循的规定。常见的隔离级别包括:

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

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

事务隔离级别设置(Isolation  推荐设置第二个级别)

 @Transactional(isolation = Isolation.REPEATABLE_READ)

 事务传播行为

@Transactional
public void MethodA(){
    // ...
    MethodB();
    // ...
}

//在被调用的子方法中设置传播行为,代表如何处理调用的事务! 是加入,还是新事务等!
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void MethodB(){
    // ...
}

propagation属性

@Transactional 注解通过 propagation 属性设置事务的传播行为。它的默认值是:

Propagation propagation() default Propagation.REQUIRED;

 propagation 属性的可选值由 org.springframework.transaction.annotation.Propagation 枚举类提供:

名称含义
REQUIRED 默认值如果父方法有事务,就加入,如果没有就新建自己独立!
REQUIRES_NEW不管父方法是否有事务,我都新建事务,都是独立的!

父与子

默认情况下,如果一个子事务出现了事务回滚,那么另一个子事务也会出现事务回滚

REQUIRES_NEW 如果一个子事务出现了事务回滚,不会影响到另一个事务

 其他传播行为值(了解)
    1. Propagation.REQUIRED:如果当前存在事务,则加入当前事务,否则创建一个新事务。
    2. Propagation.REQUIRES_NEW:创建一个新事务,并在新事务中执行。如果当前存在事务,则挂起当前事务,即使新事务抛出异常,也不会影响当前事务。
    3. Propagation.NESTED:如果当前存在事务,则在该事务中嵌套一个新事务,如果没有事务,则与Propagation.REQUIRED一样。
    4. Propagation.SUPPORTS:如果当前存在事务,则加入该事务,否则以非事务方式执行。
    5. Propagation.NOT_SUPPORTED:以非事务方式执行,如果当前存在事务,挂起该事务。
    6. Propagation.MANDATORY:必须在一个已有的事务中执行,否则抛出异常。
    7. Propagation.NEVER:必须在没有事务的情况下执行,否则抛出异常。 

总结

核心点掌握目标
spring框架理解spring家族和spring framework框架
spring核心功能ioc/di , aop , tx
spring ioc / di组件管理、ioc容器、ioc/di , 三种配置方式
spring aopaop和aop框架和代理技术、基于注解的aop配置
spring tx声明式和编程式事务、动态事务管理器、事务注解、属性

 

  • 18
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值