@SpingFrameWork

@SpingFrameWork

一、技术体系结构

  1. 单一架构,一个大的项目,一个工程,导出war包在一个tomcat上运行(主要用spring、springMVC、Mybatis)
  2. 分布式架构,一个项目,但是有多个模块,但是每个模块都在一个tomcat上运行

这里需要与前后端分离、前后端混合做个辨析,架构是说软件系统的设计,后者是开发模式

  1. 框架 = jar包+配置文件,也就是说框架是可以通过配置文件个性化的

二、SpringFrameWork

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

三、Spring IOC容器和核心概念

组件和组件概念

什么是组件

简单的说就是各种java程序,控制层程序:Servlet、业务层程序:Service、持久化层程序:Dao

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

我们的期待

我们希望更加注重于对业务逻辑的思考和编写,把一些对对象的创建、销毁、属性的赋值等都交给别人去做

这样的角色其实就是spring:

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

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

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

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

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

  • 组件一定是对象
  • 对象不一定是组件

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

Spring管理好处

  1. 不用我们管理,方便管理和配置
  2. 降低了组件之间的耦合性,我们都把组件交给spring,组件之间的直接交流减少,比如:在A类中new了一个B对象
  3. 提高了代码的可重用性和可维护性,耦合性降低自然更好维护,一个组件可以到处用,不需要再反复的先new再用

SpringIOC容器介绍

SpringIOC容器其实就是Spring容器的核心之一,负责存储、实例化、配置、组装组件(IOC:中文是控制反转的意思)

实例化这些(类)组件,本来是我们程序员的事情,new对象、赋值等等;但是现在权力交给了IOC容器;

怎么实例化:容器通过读取配置元数据来获取有关要实例化、配置和组装组件的指令。

配置组件时又通过另一个核心之一:DI (依赖注入),去配置组件之间的依赖关系;

SpringIOC容器的具体接口和实现类

最高的接口:BeanFactory,但是它只是提供了基本的依赖注入功能

子接口:ApplicationContext:它提供了更为具体且高级的功能,如事件发布、国际化支持、资源访问等

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和依赖关系的配置。

为了迎合当下开发环境,我们将以配置类+注解方式为主进行讲解!

四、SpringIOC实践和应用

SpringIOC/DI实现步骤:

  1. 编写配置文件(三种方法)
  2. 读取配置文件,并选择合适的容器类进行实例化
  3. 通过容器对象,获取组件

基于XML的配置方式和组件管理

学习重点:如何通过定义XML配置文件,声明组件类信息,交给 Spring 的 IoC 容器进行组件管理!

  1. 准备项目

    1. 创建maven工程(spring-ioc-xml-01)

    2. 导入SpringIoC相关依赖

      pom.xml

XML 复制代码
<dependencies>
    <!--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.3.1</version>
    </dependency>
</dependencies>

对于在xml文件中声明组件信息(事实上,就是我们告诉容器,怎么去创建组件对象)

**第一种:**组件类中是无参构造函数

java 复制代码
package com.atguigu.ioc;


public class HappyComponent {

    //默认包含无参数构造函数

    public void doWork() {
        System.out.println("HappyComponent.doWork");
    }
}
xml 复制代码
<!-- 实验一 [重要]创建bean -->
<bean id="happyComponent" class="com.atguigu.ioc.HappyComponent"/>

**第二种:**组件中是静态工厂方法

java 复制代码
public class ClientService {
  private static ClientService clientService = new ClientService();
  private ClientService() {}

    //静态工厂方法
  public static ClientService createInstance() {
    return clientService;
  }
}
xml 复制代码
<bean id="clientService"
  class="examples.ClientService"
  factory-method="createInstance"/>
  • class属性:指定工厂类的全限定符!
  • factory-method: 指定静态工厂方法,注意,该方法必须是static方法。

**第三种:**组件中是实例工厂方法

java 复制代码
public class DefaultServiceLocator {

  private static ClientServiceImpl clientService = new ClientServiceImpl();

    //实例工厂方法
  public ClientServiceImpl createClientServiceInstance() {
    return clientService;
  }
}
xml 复制代码
1. 创建ClientServiceImpl类:

package com.example;

public class ClientServiceImpl {
    // 可以添加一些服务方法
    public void doSomething() {
        System.out.println("Doing something...");
    }
}

2.配置 DefaultServiceLocator类
 <?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.springframework.org/schema/xsi"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 配置 ClientServiceImpl -->
    <bean id="clientService" class="com.example.ClientServiceImpl" />

    
    <!-- 配置 DefaultServiceLocator -->
    <bean id="serviceLocator" class="com.example.DefaultServiceLocator">
        <!-- 指定使用工厂方法,想要使用这个方法实例化一个ClientService对象-->
        <property name="clientService" 
                  factory-method="createClientServiceInstance" />
    </bean>

</beans>

<!--其中,这两个类的配置顺序并没有严格的规定,但是最好按照依赖顺序配置;这里是ClientServiceImpl注入了DefaultServiceLocator
DefaultServiceLocator依赖ClientServiceImpl,那么就先配置clientService类,
-->

组件依赖注入配置(DI)

(其实,上面的两个类就已经涉及到了依赖配置了)

通过配置文件, 实现IoC容器中Bean之间的引用(依赖注入DI配置)。

主要涉及注入场景:基于构造函数的依赖注入和基于 Setter 的依赖注入。

基于构造函数的依赖注入

这里就不分几个参数的构造函数了,其实都是一个道理:

xml 复制代码
<!-- 下面都是多参数的例子,但是如果是单参数,其实就是只有一个<constructor-arg>标签,其中要说的是:标签中的属性
1.value:可以直接赋值的类型,我们把它先叫做"基本类型注入",包括String、int等类型
2.ref:引用其他组件的类型,我们先把他叫做"引用类型",用于指向引用的组件的名字(id属性),那么被引用的类型也需要在ioc中声明bean
-->

<!-- 场景1: 多参数,可以按照相应构造函数的顺序注入数据 -->
<beans>
  <bean id="userService" class="x.y.UserService">
    <!-- value直接注入基本类型值 -->
    <constructor-arg  value="18"/>
    <constructor-arg  value="赵伟风"/>
    
    <constructor-arg  ref="userDao"/>
  </bean>
  <!-- 被引用类bean声明 -->
  <bean id="userDao" class="x.y.UserDao"/>
</beans>


<!-- 场景2: 多参数,可以按照相应构造函数的名称注入数据 -->
<beans>
  <bean id="userService" class="x.y.UserService">
    <!-- value直接注入基本类型值 -->
    <constructor-arg name="name" value="赵伟风"/>
    <constructor-arg name="userDao" ref="userDao"/>
    <constructor-arg name="age"  value="18"/>
  </bean>
  <!-- 被引用类bean声明 -->
  <bean id="userDao" class="x.y.UserDao"/>
</beans>

<!-- 场景2: 多参数,可以按照相应构造函数的角标注入数据 
           index从0开始 构造函数(0,1,2....)
-->
<beans>
    <bean id="userService" class="x.y.UserService">
    <!-- value直接注入基本类型值 -->
    <constructor-arg index="1" value="赵伟风"/>
    <constructor-arg index="2" ref="userDao"/>
    <constructor-arg index="0"  value="18"/>
  </bean>
  <!-- 被引用类bean声明 -->
  <bean id="userDao" class="x.y.UserDao"/>
</beans>
 

基于 Setter 的依赖注入

使用条件:

组件类中有setter方法

java 复制代码
public Class MovieFinder{

}

public class SimpleMovieLister {

  private MovieFinder movieFinder;
  
  private String movieName;

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

  // business logic that actually uses the injected MovieFinder is omitted...
}
xml 复制代码
<bean id="simpleMovieLister" class="examples.SimpleMovieLister">
  <!-- setter方法,注入movieFinder对象的标识id
       name = 属性名  ref = 引用bean的id值
   -->
  <property name="movieFinder" ref="movieFinder" />

  <!-- setter方法,注入基本数据类型movieName
       name = 属性名 value= 基本类型值
   -->
  <property name="movieName" value="消失的她"/>
</bean>

<bean id="movieFinder" class="examples.MovieFinder"/>

以上示例中,要说明的是:

java 复制代码
/*
1.property标签用于注入带有setter方法的组件
2.该标签下的name属性,就是setter方法中去掉set并且首字母小写的那段字符串,其实就是后面的"宾语"
3.还有ref属性和value属性是 二选一,这取决于参数的类型是"引用"还是"直接",其中ref还是引用bean的id值,value随便赋值好了
*/

ioc容器的创建和使用

上面的实验只是讲解了 如何在XML格式的配置文件编写 IoC 和 DI 配置

下面我们要说的是 核心 ioc容器对象的声明,为什么说是核心?因为它是环境,没有它读取那些配置,上面都白做功夫了,下面将要介绍容器的实例化和使用

容器的实例化
java 复制代码
/*
我们用下面的例子讲:有两种实例化方式
1.(常用)直接选择一种ioc容器实现类,new出来就好了,参数为指定的配置文件

2.(源码常用)先new,但不指定参数,用setConfigLocations方法中指定配置文件,最后调用刷新方法refresh方法

3.配置文件的指定都是可以多个的
*/


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

ioc怎么用?用它干嘛?

我们说了,ioc容器是用来存储组件并对他们进行管理的,有了容器,想要使用它,不就是打开它,拿出里面的东西嘛!!!所以呢,ioc容器对象的使用其实就是获得组件并且使用。组件是什么?是一个一个的类,类里面有属性,有方法,所以拿到组件其实就是为了得到里面的方法和属性

bean对象的读取

java 复制代码
/*
介绍方法:容器.getBean
通过上面的方法就可以得到bean对象,但是要注意参数:
1.可以用id(bean的名字)【但是获得的是Object类型,需要转换类型】
2.可以用类型获取,但是要求,同类型(当前类,或者之类,或者接口的实现类)只能有一个对象交给IoC容器管理。也就是说,组件是单例的,只能有一个对象
3.根据id和类型获取

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

其实,这句话的意思就是:用接口的类型也可以获取bean组件,但是,放入ioc容器的都是实现类
*/


//方式1: 根据id获取
//没有指定类型,返回为Object,需要类型转化!
HappyComponent happyComponent = 
        (HappyComponent) iocContainer.getBean("bean的id标识");
        
//使用组件对象        
happyComponent.doWork();

//方式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();

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

bean的作用域和周期方法设置

周期方法设置主要就是两个方法属性:

xml 复制代码
其中,下面方法的属性值其实就是方法名,随便起,对应上就行
<beans>
  <bean id="beanOne" class="examples.BeanOne" init-method="init" />
  <bean id="beanTwo" class="examples.BeanTwo" destroy-method="cleanup" />
</beans>

作用域就一个属性:

它是bean的属性,所以直接就是scope="???"

其中参数,单例:singleton ;多例:prototype

一般其实都是单例,默认也是

高级特性:FactoryBean的特性和使用

简介:FactoryBean接口是Spring IoC容器实例化逻辑的可插拔性点。

用于配置复杂的Bean对象,可以将创建过程存储在FactoryBean 的getObject方法!

首先,它是什么?

简单的说:它是一个接口

其次,他有什么用?

用来配置复杂的Bean对象

最后,怎么用?

java 复制代码
// 实现FactoryBean接口时需要指定泛型
// 泛型类型就是当前工厂要生产的对象的类型
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();
    
        happyMachine.setMachineName(this.machineName);
    
        return happyMachine;
    }
    
    @Override
    public Class<?> getObjectType() {
    
        // 返回要生产的对象的类型
        return HappyMachine.class;
    }
}
xml 复制代码
<!-- FactoryBean机制 -->
<!-- 这个bean标签中class属性指定的是HappyFactoryBean,但是将来从这里获取的bean是HappyMachine对象 -->
<bean id="happyMachine7" class="com.atguigu.ioc.HappyFactoryBean">
    <!-- property标签仍然可以用来通过setXxx()方法给属性赋值 -->
    <property name="machineName" value="iceCreamMachine"/>
</bean>
java 复制代码
@Test
public void testExperiment07()  {

    ApplicationContext iocContainer = new ClassPathXmlApplicationContext("spring-bean-07.xml");

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

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

以上三个代码块,分别展示了:factoryBean的实现类、Bean的配置、读取Bean对象和factoryBean对象。

我们可以这样理解:我们的组件是Bean,我们的助手是factoryBean,它帮助我们生产Bean,所以,我们在配置文件中只声明配置这里干活的"助手"------factoryBean,但是在test中,我们通过factoryBean的方法直接获得的是Bean对象。

我们再来辨析一个与上面无关的话题:

FactoryBean和BeanFactory区别

**FactoryBean **是 Spring 中一种特殊的 bean,可以在 getObject() 工厂方法自定义的逻辑创建Bean!是一种能够生产其他 Bean 的 Bean。FactoryBean 在容器启动时被创建,而在实际使用时则是通过调用 getObject() 方法来得到其所生产的 Bean。因此,FactoryBean 可以自定义任何所需的初始化逻辑,生产出一些定制化的 bean。

一般情况下,整合第三方框架,都是通过定义FactoryBean实现!!!

BeanFactory 是 Spring 框架的基础,其作为一个顶级接口定义了容器的基本行为,例如管理 bean 的生命周期、配置文件的加载和解析、bean 的装配和依赖注入等。BeanFactory 接口提供了访问 bean 的方式,例如 getBean() 方法获取指定的 bean 实例。它可以从不同的来源(例如 Mysql 数据库、XML 文件、Java 配置类等)获取 bean 定义,并将其转换为 bean 实例。同时,BeanFactory 还包含很多子类(例如,ApplicationContext 接口)提供了额外的强大功能。

总的来说,FactoryBean 和 BeanFactory 的区别主要在于前者是用于创建 bean 的接口,它提供了更加灵活的初始化定制功能,而后者是用于管理 bean 的框架基础接口,提供了基本的容器功能和 bean 生命周期管理。

基于xml方式整合三层架构组件

搭建一个三层架构案例,模拟查询全部学生(学生表)信息,持久层使用JdbcTemplate和Druid技术,使用XML方式进行组件管理!

  1. 准备东西:Controller层、Service层、Dao层;依赖JdbcTemplate、Druid;数据库和表信息
  2. Controller层需要提供完成需求的业务方法,就要调用Service层的方法:查询全部学生(学生表)信息
  3. Service层:向Controller层提供接口方法、完成方法编写,方法编写调用Dao层的方法
  4. Dao层:利用JdbcTemplate、Druid依赖,sql语句,完成对数据库的操作
  5. xml文件配置相关依赖、读取外部配置文件(主要是.properties文件)
  6. 把所有层的组件以及外部依赖进行ioc配置,并对三层组件进行DI(依赖注入)配置
  7. 最后在test中测试,获取ioc容器,获取controller组件,再调用组件提供的方法

基于注解的配置方式管理Bean

通过学习xml文件进行配置,管理bean,我们发现很难受:

  1. 组件需要被配置入ioc容器,而且组件还有属性,那么组件就需要为属性准备setter方法,用property标签在bean标签内再次用name属性,value/ref属性赋值
  2. 配置文件和java代码分离,编写时,需要根据java代码准备各种组件,并准备各种属性,特别麻烦
  3. xml配置文件的解析效率低

所以,我们学习注解方式,随时去对组件配置

怎么理解注解?

注解其实只是一个标志,起了一个名,交给一个特定的程序去识别,然后去做"交代的事"。所以,本质上,xml和注解都一样,为了告诉框架,你该怎么做。。。

怎么理解扫描?

Spring为了知道程序员们在哪些地方标记了什么注解,就需要通过扫描的方式,来进行检测。然后根据注解进行后续操作。

由于这只是对xml文件的代替,而且本质相同,所以我们大可以对比着学习

下面就直接展示注解,并做以下几方面的解释:

  1. 分别说明代替的部分
  2. 分别说明使用的地方
  3. 分别说明注解的参数

注解大全:

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

下面是 Spring IOC 容器配置中常用的注解,以及它们的基本说明,以表格形式展示:

注解 说明
@Component 标识一个类为 SpringIoc容器管理的组件。(IOC)参数:beanID
@Service 特定于服务层的 @Component 注解。(IOC)参数:beanID
@Repository 特定于数据访问层的 @Component 注解。(IOC)参数:beanID
@Controller 特定于表现层的 @Component 注解。(IOC)参数:beanID
@RestController 用于创建 RESTful Web 服务的控制器,组合了 @Controller@ResponseBody。(IOC)参数:beanID
@Autowired 用于自动注入依赖,可以作用于字段、构造函数、设置方法。(DI)放在被注入的字段、构造函数、 setter 方法
@Qualifier 和所需类型匹配的 bean 不止一个时,可以有参数id
@Value 读取配置文件(.properties)的属性值,注入外部化属性.用法:"${key}", 其实就是让被注解部分与配置文件内的内容相对应
@Qualifier 当有多个同类型的 bean 时,用于指定 @Autowired 要注入的 bean 名称。参数就是那个需要注入的组件名
@Scope 用于指定 bean 的作用域。属性:singleton/prototype/request/session(后面的两个在WebApplicationContext环境下,但不常用)

以上就是用纯注解配置ioc容器和Di的注解

基于配置类方式管理Bean

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

怎么配置?

步骤:

  1. 准备一个java程序,作为配置ioc的程序,假设起名为JavaConfig

  2. 目的是写一个ioc配置类,注解:@Configuration,说明是一个配置类

  3. 根据配置类创建IOC容器对象,选择一个合适的实现类作为ioc容器对象并创建(new)

    类型名 简介
    ClassPathXmlApplicationContext 通过读取类路径下的 XML 格式的配置文件创建 IOC 容器对象
    FileSystemXmlApplicationContext 通过文件系统路径读取 XML 格式的配置文件创建 IOC 容器对象
    AnnotationConfigApplicationContext 通过读取Java配置类创建 IOC 容器对象
    WebApplicationContext 专门为 Web 应用准备,基于 Web 环境创建 IOC 容器对象,并将对象引入存入 ServletContext 域中。
  4. 若需要外部配置文件,配合@PropertySource(value = "classpath:jdbc.properties")注解和@Value注解读取配置文件并注入依赖给字段,@Value也可以作为参数直接传入

  5. 第三方依赖,直接通过方法返回对象,并记得为该方法使用 @Bean 标志是组件,可指定组件名字

  6. Bean之间的依赖注入:

方案一:直接在一个 @Bean 方法中直接调用其他 @Bean 方法来获取 Bean 实例

java 复制代码
@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 方法来获取 Bean 实例+被注入部分作为参数

java 复制代码
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 定义,如以下示例所示:

Java 复制代码
@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.classConfigB.class ,只需显式提供 ConfigB ,如以下示例所示:

Java 复制代码
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 类。

三种方式总结:

4.5.1 XML方式配置总结
1. 所有内容写到xml格式配置文件中
2. 声明bean通过<bean标签
3. <bean标签包含基本信息(id,class)和属性信息 <property name value / ref
4. 引入外部的properties文件可以通过<context:property-placeholder
5. IoC具体容器实现选择ClassPathXmlApplicationContext对象
4.5.2 XML+注解方式配置总结
1. 注解负责标记IoC的类和进行属性装配
2. xml文件依然需要,需要通过<context:component-scan标签指定注解范围
3. 标记IoC注解:@Component,@Service,@Controller,@Repository 
4. 标记DI注解:@Autowired @Qualifier @Resource @Value
5. IoC具体容器实现选择ClassPathXmlApplicationContext对象
4.5.3 完全注解方式配置总结
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 AOP 面向切面编程

简单的说,当我们专注于业务代码的编写时,总有一些边界料来干扰我们,而且迫于某些要求,这些边角料非要不行,烦不烦?比如说:

java 复制代码
package com.atguigu.proxy;

/**
 * 在每个方法中,输出传入的参数和计算后的返回结果!
 */
public class CalculatorLogImpl implements Calculator {
    
    @Override
    public int add(int i, int j) {
    
        System.out.println("参数是:" + i + "," + j);
        int result = i + j;
        System.out.println("方法内部 result = " + result);
      
        return result;
    }
    
    @Override
    public int sub(int i, int j) {
    
        System.out.println("参数是:" + i + "," + j);
    
        int result = i - j;
    
        System.out.println("方法内部 result = " + result);
        return result;
    }
    
    @Override
    public int mul(int i, int j) {
    
        System.out.println("参数是:" + i + "," + j);
    
        int result = i * j;
    
        System.out.println("方法内部 result = " + result);
    
        return result;
    }
    
    @Override
    public int div(int i, int j) {
    
        System.out.println("参数是:" + i + "," + j);
    
        int result = i / j;
    
        System.out.println("方法内部 result = " + result);
        
        return result;
    }
}

这些代码相似嘛?都带了个日志的作用,代码都一摸一样,那我们为什么还要写这么多次呢?能不能写一次,然后到处用呢?在 javaSE 部分我们接触过一个设计模式--代理模式,虽然和这里不太一样,但其实用到的思维是相同的----AOP编程思维

问题来了,代理怎么还有几种呢?怎么代理?怎么写?

首先,代理分为:静态代理和动态代理。动态代理中又根据底层用到的技术不同分为:jdk的代理、cglib的代理

在 Spring 中, SpringAOP框架封装了动态代理技术,简化了使用动态代理的步骤。我们只需要写配置文件,告诉框架,指定生效范围就行了。

所以怎么写呢?

不急,既然我们要用框架,得先了解了解框架。AspectJ是早期的AOP实现的框架,SpringAOP借用了AspectJ中的AOP注解。因此得先导入框架和AspectJ的依赖

xml 复制代码
<!-- 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>

下面就开始写了:

初步实现步骤:

  1. 直接src下创建一个类作为切面类(需要注解声明:@Aspect)(切面类就是声明冗杂任务的类,就是按照位置,某个位置需要完成一个工作,就创建一个方法,这一个个方法就构成了切面类)。

  2. 其中,方法也叫增强,切面类也叫"增强类",切面 = 增强 + 切点。那么切点就用切入点表达式指定,并作为注解的参数

  3. 为这个类补全注解:

    @Component注解保证这个切面类能够放入IOC容器

    @EnableAspectJAutoProxy//开启AspectJ的注解支持||相当于在xml文件中加入aop:aspectj-autoproxy/

    @ComponentScan("com.yym")//扫描器,扫描增强类代码

实现细节:

需要获取方法的细节信息

主要就是获取方法的权限符、返回值、参数列表、异常对象

  1. JointPoint接口

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

    • 要点1:JoinPoint 接口通过 getSignature() 方法获取目标方法的签名(方法声明时的完整信息)
    • 要点2:通过目标方法签名对象获取方法名
    • 要点3:通过 JoinPoint 对象获取外界调用目标方法时传入的实参列表组成的数组
Java 复制代码
// @Before注解标记前置通知方法
// value属性:切入点表达式,告诉Spring当前通知方法要套用到哪个目标方法上
// 在前置通知方法形参位置声明一个JoinPoint类型的参数,Spring就会将这个对象传入
// 根据JoinPoint对象就可以获取目标方法名称、实际参数列表
@Before(value = "execution(public int com.atguigu.aop.api.Calculator.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);
}
  1. 方法返回值

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

Java 复制代码
// @AfterReturning注解标记返回通知方法
// 在返回通知中获取目标方法返回值分两步:
// 第一步:在@AfterReturning注解中通过returning属性设置一个名称
// 第二步:使用returning属性设置的名称在通知方法中声明一个对应的形参
@AfterReturning(
        value = "execution(public int com.atguigu.aop.api.Calculator.add(int,int))",
        returning = "targetMethodReturnValue"
)
public void printLogAfterCoreSuccess(JoinPoint joinPoint, Object targetMethodReturnValue) {
    
    String methodName = joinPoint.getSignature().getName();
    
    System.out.println("[AOP返回通知] "+methodName+"方法成功结束了,返回值是:" + targetMethodReturnValue);
}
  1. 异常对象捕捉

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

Java 复制代码
// @AfterThrowing注解标记异常通知方法
// 在异常通知中获取目标方法抛出的异常分两步:
// 第一步:在@AfterThrowing注解中声明一个throwing属性设定形参名称
// 第二步:使用throwing属性指定的名称在通知方法声明形参,Spring会将目标方法抛出的异常对象从这里传给我们
@AfterThrowing(
        value = "execution(public int com.atguigu.aop.api.Calculator.add(int,int))",
        throwing = "targetMethodException"
)
public void printLogAfterCoreException(JoinPoint joinPoint, Throwable targetMethodException) {
    
    String methodName = joinPoint.getSignature().getName();
    
    System.out.println("[AOP异常通知] "+methodName+"方法抛异常了,异常类型是:" + targetMethodException.getClass().getName());
}
*5.5.5 重用(提取)切点表达式
1. 重用切点表达式优点
Java 复制代码
 // @Before注解:声明当前方法是前置通知方法
// value属性:指定切入点表达式,由切入点表达式控制当前通知方法要作用在哪一个目标方法上
@Before(value = "execution(public int com.atguigu.proxy.CalculatorPureImpl.add(int,int))")
public void printLogBeforeCore() {
    System.out.println("[AOP前置通知] 方法开始了");
}

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

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

@After(value = "execution(public int com.atguigu.proxy.CalculatorPureImpl.add(int,int))")
public void printLogFinallyEnd() {
    System.out.println("[AOP后置通知] 方法最终结束了");
}
  上面案例,是我们之前编写切点表达式的方式,发现, 所有增强方法的切点表达式相同!

  出现了冗余,如果需要切换也不方便统一维护!

  我们可以将切点提取,在增强上进行引用即可!
  1. 同一类内部引用

    提取

Java 复制代码
// 切入点表达式重用
@Pointcut("execution(public int com.atguigu.aop.api.Calculator.add(int,int)))")
public void declarPointCut() {}
  注意:提取切点注解使用@Pointcut(切点表达式) , 需要添加到一个无参数无返回值方法上即可!

  引用
Java 复制代码
@Before(value = "declarPointCut()")
public void printLogBeforeCoreOperation(JoinPoint joinPoint) {
  1. 不同类中引用

    不同类在引用切点,只需要添加类的全限定符+方法名即可!

Java 复制代码
@Before(value = "com.atguigu.spring.aop.aspect.LogAspect.declarPointCut()")
public Object roundAdvice(ProceedingJoinPoint joinPoint) {
  1. 切点统一管理

    建议:将切点表达式统一存储到一个类中进行集中管理和维护!

Java 复制代码
@Component
public class AtguiguPointCut {
    
    @Pointcut(value = "execution(public int *..Calculator.sub(int,int))")
    public void atguiguGlobalPointCut(){}
    
    @Pointcut(value = "execution(public int *..Calculator.add(int,int))")
    public void atguiguSecondPointCut(){}
    
    @Pointcut(value = "execution(* *..*Service.*(..))")
    public void transactionPointCut(){}
}
*环绕通知

这个其实,就是用一个注解,用try-catch-finally代替以上三种注解。

一个例子去对应前面的三种注解:

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

Java 复制代码
// 使用@Around注解标明环绕通知方法
@Around(value = "com.atguigu.aop.aspect.AtguiguPointCut.transactionPointCut()")
public Object manageTransaction(
    
        // 通过在通知方法形参位置声明ProceedingJoinPoint类型的形参,
        // Spring会将这个类型的对象传给我们
        ProceedingJoinPoint joinPoint) {
    
    // 通过ProceedingJoinPoint对象获取外界调用目标方法时传入的实参数组
    Object[] args = joinPoint.getArgs();
    
    // 通过ProceedingJoinPoint对象获取目标方法的签名对象
    Signature signature = joinPoint.getSignature();
    
    // 通过签名对象获取目标方法的方法名
    String methodName = signature.getName();
    
    // 声明变量用来存储目标方法的返回值
    Object targetMethodReturnValue = null;
    
    try {
    
        // 在目标方法执行前:开启事务(模拟)
        log.debug("[AOP 环绕通知] 开启事务,方法名:" + methodName + ",参数列表:" + Arrays.asList(args));
    
        // 过ProceedingJoinPoint对象调用目标方法
        // 目标方法的返回值一定要返回给外界调用者
        targetMethodReturnValue = joinPoint.proceed(args);
    
        // 在目标方法成功返回后:提交事务(模拟)
        log.debug("[AOP 环绕通知] 提交事务,方法名:" + methodName + ",方法返回值:" + targetMethodReturnValue);
    
    }catch (Throwable e){
    
        // 在目标方法抛异常后:回滚事务(模拟)
        log.debug("[AOP 环绕通知] 回滚事务,方法名:" + methodName + ",异常:" + e.getClass().getName());
    
    }finally {
    
        // 在目标方法最终结束后:释放数据库连接
        log.debug("[AOP 环绕通知] 释放数据库连接,方法名:" + methodName);
    
    }
    
    return targetMethodReturnValue;
}
切面优先级设置

介绍一个标签:

java 复制代码
@Order(数字)
/*其中,数字越大,切面优先级越低
但是注意一个问题:这个切面其实是一个包裹,说白了,切面就是一层层的包裹着核心代码
所以,优先级越高,先执行,但是最后结束*/

六、声明式事务

在说声明式事务之前,我们先来认识一下什么叫做编程式事务

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

下面是一个编程事务的示例:

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

我们会发现,只要写一个事务,就是这套代码,复用性太差。细节没有被屏蔽:具体操作过程中,所有细节都需要程序员自己来完成,比较繁琐。

因此,我们提出了一种方便程序员的方式,声明式事务。

在声明式事务中,和编程式事务最大的区别就是:

  1. 不用自己写代码管理事务
  2. 通过配置文件或者注解来控制事务

什么是声明式事务?

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

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

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

区别:

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

Spring事务管理器

没错,有了声明式事务这个方法后,我们就可以通过配置文件来使用第三方框架进行事务操作,但是,有一个问题是:你用哪一个框架?每一个框架的代码编写不都是一样的,所以Spring需要整合他们,都能用我这个框架,怎么做?那就是提出事务管理器接口

所以事实上,事务管理器就是实现了事务管理器接口的实现类,目前spring为第三方框架准备了三个实现类

还有一个是:JSP TM 专门为jsp框架实现的事务管理器

我们要学的Mybatis框架和JDBC还有JDBCTemplate框架都是DataSourceTransactionManager类事务管理器。要使用需要导入相应的依赖:

  • spring-tx: 包含声明式事务实现的基本规范(事务管理器规范接口和事务增强等等)
  • spring-jdbc: 包含DataSource方式事务管理器实现类DataSourceTransactionManager

要使用其他的事务管理器我们只需要导入:

spring-orm的依赖

DataSourceTransactionManager类中的主要方法:

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

基于注解的声明式事务

  1. 引入声明式事务注解需要的依赖

    spring-tx、spring-jdbc

  2. @EnableTransactionManagement在配置类上注解,开启事务支持

  3. 在配置类上装配事务管理实现对象(简单说就是增加一个组件用于返回声明式事务的框架对象),并放入ioc容器(@Bean)

  4. 在需要事务的方法上或类上(类中所有方法),使用声明事务注解@Transactional,可以加参数,设置事务为只读、超时时间、事务异常(指定异常类型回滚)、事务隔离级别、事务传播行为,具体知识点看下面:

    java 复制代码
    /**
     * 1.只读模式
     *  只读模式可以提高查询事务的效率,推荐 如果事务中只有查询语句,使用只读模式
     * 2.查询本来不需要设置事务,为什么要设置为只读模式的事务?
     * 解释:因为一般情况下,都是通过类去添加事务,因此会覆盖到类下的每一个方法开启事务,
     * 为了提高查询事务的效率,开启只读模式,提高效率
     * 3.方法上的事务注解覆盖类上的事务注解
     * 4.业务方法中,默认存在指定异常回滚(默认是RuntimeException);指定异常不回滚
     * 5.指定异常回滚和指定异常不回滚
     * 我们可以指定Exception异常使得一旦发生异常就回滚   rollbackFor = Exception.class
     *  noRollbackFor = 回滚范围内,控制某个异常不回滚
     * 6.设置事务隔离级别:Isolation = ...  推荐设置为 读已提交,效率最高
     * 7.事务之间存在调用情况,子事务是加入父事务?还是独立事务?
     * 这就由事务的传播行为决定,可以在子事务上设置 传播行为
     * 对应属性:propagation  (推荐使用默认值)
     * 常用的两个属性值:REQUIRED(默认值) :父方法有事务则加入,没事务就新建;REQUIRED_NEW:独立事务
     * 注意:同类下不产生任何事务传播行为(搞懂到底为什么?)
     */
相关推荐
Wx-bishekaifayuan2 小时前
django电商易购系统-计算机设计毕业源码61059
java·spring boot·spring·spring cloud·django·sqlite·guava
小白冲鸭2 小时前
【报错解决】使用@SpringJunitConfig时报空指针异常
spring·java后端开发
LuckyLay3 小时前
Spring学习笔记_27——@EnableLoadTimeWeaving
java·spring boot·spring
Stringzhua3 小时前
【SpringCloud】Kafka消息中间件
spring·spring cloud·kafka
成富8 小时前
文本转SQL(Text-to-SQL),场景介绍与 Spring AI 实现
数据库·人工智能·sql·spring·oracle
鹿屿二向箔9 小时前
基于SSM(Spring + Spring MVC + MyBatis)框架的汽车租赁共享平台系统
spring·mvc·mybatis
豪宇刘9 小时前
SpringBoot+Shiro权限管理
java·spring boot·spring
一只爱打拳的程序猿10 小时前
【Spring】更加简单的将对象存入Spring中并使用
java·后端·spring
ajsbxi13 小时前
苍穹外卖学习记录
java·笔记·后端·学习·nginx·spring·servlet
鹿屿二向箔13 小时前
基于SSM(Spring + Spring MVC + MyBatis)框架的咖啡馆管理系统
spring·mvc·mybatis