科普文:深入理解Spring框架

153 篇文章 0 订阅
52 篇文章 0 订阅

概叙

        Spring 是一个开源的轻量级 Java 开发框架,它通过 DI (Dependency Injection) 和 AOP (Aspect-Oriented Programming) 提供了简单的编程模型,帮助开发者构建健壮的、可测试的企业级应用程序。Spring 框架为现代基于 java 的企业应用程序提供了一个全面的编程和配置模型——在任何类型的部署平台上。

为什么要学spring?

        Spring 的特点包括模块化、灵活性、高度可测试性和一组丰富的 API。它几乎适用于所有的 Java 应用,包括后端服务、微服务、大数据处理等。 Spring 是一个应用框架,框架一般是半成品,我们在框架的基础上可以不用每个项目自己实现架构、基础设施和常用功能性组件,而是可以专注业务逻辑。因此学习Spring 在架构和模式方面的结构和原理,对我们在架构和模块级别的理解帮助极大。

        Spring 的宗旨是简化Java开发,主要的手段如下:

        (1)在架构上解耦:通过DI(依赖注入)管理类型依赖,通过AOP分离关注点,减少重复代码。

        (2)在设计上广泛采用DIP(依赖倒置)和ISP(接口隔离)等原则和Facade(外观)等模式:提供简化的调用接口并封装了众多出色的第三方组件。

        (3)在语言层面上采用注解:通过配置文件和Annotation简化应用配置。

        还因为Spring具有 以下优点:

Spring的优点

  • 轻量级:
    • 相较于EJB容器,Spring采用的IoC容器非常的轻量级,基础版本的Spring框架大约只有2MB。Spring可以让开发者们仅仅使用POJO(Plain Old Java Object,相对于EJB)就能够开发出企业级的应用。这样做的好处是,你不需要使用臃肿庞大的 EJB容器(应用服务器),你只需要轻量的servlet容器(如Tomcat)。尤其在一些开发当中,很稀缺内存和CPU资源时,采用Spring比EJB无论是开发还是部署应用都更节约资源。
    • Spring 框架使用的 jar 都比较小,一般在 1M 以下或者几百 kb。Spring 核心功能的所需的 jar 总共在 3M 左右。Spring 框架运行占用的资源少,运行效率高,不依赖其他 jar。
  • 控制反转(IOC):
    • Spring使用控制反转技术实现了松耦合。依赖被注入到对象,而不是创建或寻找依赖对象。
    • 方便解耦,简化开发;Spring就是一个大工厂,可以将所有对象创建和依赖的关系维护,交给Spring管理。针对接口编程,解耦合Spring 提供了 Ioc 控制反转,由容器管理对象,对象的依赖关系。原来在程序代码中的对象创建方式,现在由容器完成。对象之间的依赖解耦合。
  • 面向切面编程(AOP):
    • Spring支持面向切面编程,同时把应用的业务逻辑与系统的服务分离开来。
    • 通过 Spring 提供的 AOP 功能,方便进行面向切面的编程,许多不容易用传统 OOP 实现的功能可以通过 AOP 轻松应付,可以方便的实现对程序进行权限拦截、运行监控等功能。
    • 在 Spring 中,开发人员可以从繁杂的事务管理代码中解脱出来,通过声明式方式灵活地进行事务的管理,提高开发效率和质量。
  • MVC框架:
    • Spring MVC是一个非常好的MVC框架,可以替换其他web框架诸如Struts。
  • 集成性:
    • Spring非常容易和其他的流行框架一起集成开发,这些框架包括:ORM框架,logging框架,JEE, Quartz,以及Struts等表现层框架。
    • Spring不排斥各种优秀的开源框架,其内部提供了对各种优秀框架的直接支持(如:Struts、Hibernate、MyBatis等)。 Spring 可以降低各种框架的使用难度,Spring提供了对各种优秀框架(如 Struts,Hibernate、MyBatis)等的直接支持。简化框架的使用。Spring 像插线板一样,其他框架是插头,可以容易的组合到一起。需要使用哪个框架,就把这个插头放入插线板。不需要可以轻易的移除。
    • 降低JavaEE API的使用难度: Spring对JavaEE开发中非常难用的一些API(JDBC、JavaMail、远程调用等),都提供了封装,使这些API应用难度大大降低。

  • 事务管理:
    • Spring强大的事务管理功能,能够处理本地事务(一个数据库)或是全局事务(多个数据,采用JTA)。
    • 声明式事务的支持:只需要通过配置就可以完成对事务的管理,而无需手动编程。
    • 编程式事务支持:Spring提供的最原始的事务管理方式是基于TransactionDefinition、PlatformTransactionManager、TransactionStatus 编程式事务。而TransactionTemplate的编程式事务管理是使用模板方法设计模式对原始事务管理方式的封装。编程式事务是一种在数据库操作中使用的事务处理方式,通过编写程序来控制事务的提交或回滚。与声明式事务相比,编程式事务需要手动指定事务的启动、提交和回滚等操作,具有更精细的控制能力。
  • 模块分离:
    • Spring框架是由模块构成的。虽然已经有太多的包和类了,但它们都按照模块分好类了,你只需要考虑你会用到的模块,而不用理其他的模块。
  • 异常处理:
    • 由于Java的JDBC,Hibernate等API中有很多方法抛出的是checked exception,而很多开发者并不能很好的处理异常。Spring提供了统一的API将这些checked exception的异常转换成Spring的unchecked exception。
  • 单元测试:
    • Spring写出来的代码非常容易做单元测试,可以采用依赖注射(Dependency Injection)将测试的数据注射到程序中。
    • Spring对Junit4支持,可以通过注解方便的测试Spring程序。
  • 方便解耦,简化开发:
    • Spring就是一个大工厂,可以将所有对象创建和依赖的关系维护,交给Spring管理。

    •  针对接口编程,解耦合Spring 提供了 Ioc 控制反转,由容器管理对象,对象的依赖关系。原来在程序代码中的对象创建方式,现在由容器完成。对象之间的依赖解耦合。

Spring体系结构

        Spring框架至今已集成了20多个模块,核心是控制反转(IoC)和面向切面编程(AOP)。这些模块分布在以下模块中:

核心容器(Core Container)
数据访问/集成(Data Access/Integration)层
Web层
AOP(Aspect Oriented Programming)模块
植入(Instrumentation)模块
消息传输(Messaging)
测试(Test)模块
Spring Core:提供了框架的基本组成部分,包括控制反转(IoC)和依赖注入。

Spring AOP:提供了面向切面编程的实现,可以定义将要应用于业务逻辑的切面。

Spring MVC:提供了Web应用的模型视图控制器实现,简化了Web应用的开发。

Spring Boot:为快速开发和部署Spring应用提供了一种方法,它自动配置Spring应用。

Spring Data:是一个数据访问层的抽象层,支持NoSQL和SQL存储。

Spring Security:提供了安全方面的支持,保护应用程序免受外部攻击。

Spring Integration:提供了企业集成模式的实现,简化了企业服务的集成。

Spring Cloud:为分布式系统开发提供工具,如服务发现、配置管理、负载均衡、断路器等。

1、Spring Core核心容器

        Spring的核心容器是其他模块建立的基础,有Spring-core、Spring-beans、Spring-context、Spring-context-support和Spring-expression(String表达式语言)等模块组成。在这里我们重点关注Spring-core、Spring-beans、Spring-context及其IOC和DI两大功能。

        Spring-core模块:提供了框架的基本组成部分,包括控制反转(Inversion of Control,IOC)和依赖注入(Dependency Injection,DI)功能。
        Spring-beans模块:提供了BeanFactory,是工厂模式的一个经典实现,Spring将管理对象称为Bean。
        Spring-context模块:建立在Core和Beans模块的基础之上,提供一个框架式的对象访问方式,是访问定义和配置的任何对象的媒介。ApplicationContext接口是Context模块的焦点。
        Spring-context-support模块:支持整合第三方库到Spring应用程序上下文,特别是用于高速缓存(EhCache、JCache)和任务调度(CommonJ、Quartz)的支持。
        Spring-expression模块:提供了强大的表达式语言去支持运行时查询和操作对象图。这是对JSP2.1规范中规定的统一表达式语言(Unified EL)的扩展。该语言支持设置和获取属性值、属性分配、方法调用、访问数组、集合和索引器的内容、逻辑和算术运算、变量命名以及从Spring的IOC容器中以名称检索对象。它还支持列表投影、选择以及常用的列表聚合。

1.1 IOC控制反转(Inversion of Control,IOC)

        控制反转(Inversion of Control)是说创建对象的控制权发生转移,以前创建对象的主动权和创建时机由应用程序把控,而现在这种权利转交给 IoC 容器(记牢IOC容器就是ApplicationContext的实体对象,很多时候,我们也将他称为spring 容器),它就是一个专门用来创建对象的工厂,你需要什么对象,它就给你什么对象。有了 IoC 容器,依赖关系就改变了,原先的依赖关系就没了,它们都依赖 IoC容器了,通过 IoC 容器来建立它们之间的关系。

        IoC容器:Context.applicationcontext 接口代表 spring ioc 容器,负责实例化、配置和装配 bean。

  • Spring IOC容器就是一个org.springframework.context.ApplicationContext的实例化对象
  • 容器负责了实例化,配置以及装配一个bean

        ApplicationContext 接口表示 Spring IoC 容器,负责实例化、配置和装配 bean。容器通过读取配置元数据获取关于实例化、配置和组装什么对象的指令。配置元数据用 XML、 Java 注释或 Java 代码表示。它允许您表达组成应用程序的对象以及这些对象之间丰富的相互依赖关系。

  • ClassPathXmlApplicationContext
  • FileSystemXmlApplicationContext

        有了IOC机制后,bean的创建就交给spring了,无需再new。

        控制反转 (Inversion of Control) 是 Spring 最核心的理念之一。它将对象的创建和依赖关系的管理交给 Spring 容器处理,而不是在代码中手动管理。这使得代码更加模块化和松散耦合。

Spring IoC 容器

        创建容器的方式:类路径加载和文件路径加载

方式一:ClassPathXmlApplicationContext类路径加载配置文件
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
加载多个配置文件
ApplicationContext ctx = new ClassPathXmlApplicationContext("bean1.xml", "bean2.xml");

方式二:FileSystemXmlApplicationContext文件路径加载配置文件
ApplicationContext ctx = new FileSystemXmlApplicationContext("D:\\applicationContext.xml");

        ApplicationContext继承了BeanFactory接口

1.2 DI依赖注入(Dependency Injection,DI)

        依赖注入 (Dependency Injection) 是 IoC 的具体实现方式。在 Spring 中,我们可以通过构造函数注入、Setter 注入和接口注入等多种方式为对象注入依赖。

        DIP:DIP(依赖倒置原则)是DI(依赖注入)的核心。

        (1)高层模块不应该依赖于低层模块。两者都应该依赖于抽象。

        (2)抽象不应该依赖于细节。细节应该依赖于抽象。

        说人话就是:将对具体类的引用转换成对其接口的引用,具体类只引用接口(引用==依赖,接口==接口或抽象类)。事实上我们调用具体类的时候在头脑里也是只关心其提供的API而非实现,DIP则通过在设计和重构阶段在技术手段上保证了解耦。

        每个基于应用程序的 java 都有几个对象,由这些对象一起工作来呈现出终端用户所看到的工作的应用程序。当编写一个复杂的 Java 应用程序时,应用程序类应该尽可能独立于其他 Java 类来增加这些类重用的可能性,并且在做单元测试时,测试独立于其他类的独立性。依赖注入(或有时称为布线)有助于把这些类粘合在一起,同时保持他们独立。

        假设你有一个包含文本编辑器组件的应用程序,并且你想要提供拼写检查。标准代码看起来是这样的:

public class TextEditor {
   private SpellChecker spellChecker;  
   public TextEditor() {
      spellChecker = new SpellChecker();
   }
}

        在这里我们所做的就是创建一个 TextEditor 和 SpellChecker 之间的依赖关系。而在控制反转IoC的场景中,我们会这样做:

public class TextEditor {
   private SpellChecker spellChecker;
   public TextEditor(SpellChecker spellChecker) {
      this.spellChecker = spellChecker;
   }
}

        在这里,TextEditor 不应该担心 SpellChecker 的实现。SpellChecker 将会独立实现,并且在 TextEditor 实例化的时候将提供给 TextEditor,整个过程是由 Spring 框架的控制。

        在这里,我们已经从 TextEditor 中删除了全面控制,并且把它保存到其他地方(即 XML 配置文件),且依赖关系(即 SpellChecker 类)通过类构造函数被注入到 TextEditor 类中。因此,控制流通过依赖注入(DI)已经“反转”,因为你已经有效地委托依赖关系到一些外部系统。

        依赖注入的第二种方法是通过 TextEditor 类的 Setter 方法,我们将创建 SpellChecker 实例,该实例将被用于调用 setter 方法来初始化 TextEditor 的属性。

因此,DI 主要有两种变体和下面的两个子章将结合实例涵盖它们:

序号依赖注入类型 & 描述
1Constructor-based dependency injection

当容器调用带有多个参数的构造函数类时,实现基于构造函数的 DI,每个代表在其他类中的一个依赖关系。

2Setter-based dependency injection

基于 setter 方法的 DI 是通过在调用无参数的构造函数或无参数的静态工厂方法实例化 bean 之后容器调用 beans 的 setter 方法来实现的。

        你可以混合这两种方法,基于构造函数和基于 setter 方法的 DI,然而使用有强制性依存关系的构造函数和有可选依赖关系的 sette r是一个好的做法。

        代码是 DI 原理的清洗机,当对象与它们的依赖关系被提供时,解耦效果更明显。对象不查找它的依赖关系,也不知道依赖关系的位置或类,而这一切都由 Spring 框架控制的。

DI依赖注入的方式

1.settter方法注入

        以@Autowired注解为例,即把@Autowired注解标记在目标bean的引用bean的setter方法上。

@RestController
@RequestMapping("/abc")
public class AbcController {
    private AbcService abcService;

    @Autowired
    public void setAbcService(AbcService abcService) {
        this.abcService = abcService;
    }

    @GetMapping("/rateLimit")
    public  String  rateLimit(){

        System.out.println("start");
        abcService.printABC();
        return "abc";
    }
}

优点:
    相比构造器注入,set注入类似于选择性注入。
    允许在类构造完成后重新注入。
缺点: 暂无
2.构造器注入

@RestController
@RequestMapping("/abc")
public class AbcController {

    private AbcService abcService;    

    public AbcController(AbcService abcService) {
        this.abcService = abcService;
    }

    @GetMapping("/rateLimit")
    public  String  rateLimit(){
        System.out.println("start");
        abcService.printABC();
        return "abc";
    }
}

优点:
保证依赖不可变(final关键字)。
保证依赖不为空(省去了我们对其检查)。
保证返回客户端(调用)的代码的时候是完全初始化的状态。
避免了循环依赖。
提升了代码的可复用性。
可以明确成员变量的注入顺序。

缺点:
当注入参数较多时,代码臃肿。

3.字段 注入
@RestController
@RequestMapping("/abc")
public class AbcController {

    @Autowired
    private AbcService abcService;


    @GetMapping("/rateLimit")
    public  String  rateLimit(){
        System.out.println("start");
        abcService.printABC();
        return "abc";
    }
}


可以看到,通过 @Autowired 注解,字段注入的实现方式非常简单而直接,代码的可读性也很强。事实上,字段注入是三种注入方式中最常用、也是最容易使用的一种。但它也是三种注入方式中最应该避免使用的。
就像开篇说的,我们在 IDEA 中使用字段注入时会遇到“Field injection is not recommended”的提示。
你知道为什么吗?
原因有三点。
public class ClientService { 
    @Autowired
    private HealthRecordService healthRecordService; 
    public void recordUserHealthData() { 
        healthRecordService.recordUserHealthData(); 
    } 
}

问题一、

字段注入的最大问题是对象的外部可见性。在前面的 ClientService 类中,我们通过定义一个私有变量 HealthRecordService 来注入该接口的实例。显然,这个实例只能在 ClientService 类中被访问,脱离了容器环境我们无法访问这个实例,来看下面这段代码:


ClientService clientService = new ClientService(); 
clientService.recordUserHealthData ();
执行这段代码的结果就是抛出一个 NullPointerException 空指针异常,原因就在于无法在 ClientService 的外部实例化 HealthRecordService 对象。采用字段注入,类与容器的耦合度过高,我们无法脱离容器来使用目标对象。如果编写测试用例来验证 ClientService 类的正确性,那么想要使用 HealthRecordService 对象,就只能使用反射的方式,这种做法实际上是不符合 JavaBean 开发规范的,而且可能一直无法发现空指针异常的存在。

问题二、

我们无法设置需要注入的对象为 final,也无法注入那些不可变对象,final类型的变量在调用class的构造函数的这个过程当中就得初始化完成,这个是基于字段的依赖注入做不到的地方,只能使用基于构造函数的依赖注入的方式.

问题三、循环依赖

字段注入的第三个问题是可能导致潜在的循环依赖,即两个类之间互相进行注入,例如下面这段示例代码:

public class ClassA { 
    @Autowired
    private ClassB classB; 
} 
public class ClassB { 
    @Autowired
    private ClassA classA; 
}
这里的 ClassA 和 ClassB 发生了循环依赖。上述代码在 Spring 中是合法的,容器启动时并不会报任何错误,而只有在使用到具体某个 ClassA 或 ClassB 时才会报错。

问题四、

无法对注入的属性进行安全检查 在程序启动的时候无法拿到这个类,只有在真正的业务使用的时候才会拿到,若注入的是null,因为不调用将一直无法发NullPointException的存在。 或者想在属性注入的时候,增加验证措施,也无法办到。

基于以上四点,无论是 IDEA,还是 Spring 官方,都不推荐开发人员使用字段注入这种注入模式,而是推荐构造器注入。

在面试中,针对字段注入,请记住它主要的三点缺陷:不具备外部可见性、会导致循环依赖,以及无法注入不可变对象。
4.DI依赖注入支持的数据类型

        DI支持注入bean类型、基本数据类型和字符串、List集合、Set集合、Map集合、Properties对象类型等

1.3 实战:向IOC容器中注入一个对象

        找了一个比较老的图,基本可以看清楚向spring ioc容器注入一个对象的过程。

1.配置元数据:通过XML配置文件、注解或Java配置类来定义组件的元数据。
2.容器启动:Spring容器启动时,会读取配置元数据,并根据这些信息创建和配置组件。
3.依赖注入:容器根据配置的依赖关系,通过构造器注入、Setter注入等方式,将依赖注入到组件中。
4.应用上下文:Spring容器充当应用上下文的角色,管理所有组件的生命周期和依赖关系。
5.懒加载和预加载:Spring容器可以配置为懒加载(按需加载)或预加载(启动时加载)组件。

        下面通过三种方式构建对象,在ioc容器中。

一、通过构造函数创建对象。
1 利用无参构造函数+setter方法注入值

        最基本的对象创建方式,只需要有一个无参构造函数(类中没有写任何的构造函数,默认就是有一个构造函数,如果写了任何一个构造函数,默认的无参构造函数就不会自动创建哦!!)和字段的setter方法。

Person类:

package com.mc.base.learn.spring.bean;

public class Person {

    private String name;
    private Integer id;
    
    public String getName() {
        return name;
    }

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

    public Integer getId() {
        return id;
    }

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

    @Override
    public String toString() {
        return "Person [name=" + name + ", id=" + id + "]";
    }

}

XML配置:

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

    <bean class="com.mc.base.learn.spring.bean.Person" id="person">
        <property name="name" value="LiuChunfu"></property>
        <property name="id" value="125"></property>
    </bean>
    
</beans>

其本质为:

SpringContext利用无参的构造函数创建一个对象,然后利用setter方法赋值。所以如果无参构造函数不存在,Spring上下文创建对象的时候便会报错。

Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'person' defined in class path resource [applicationContext.xml]: Instantiation of bean failed; 
nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.mc.base.learn.spring.bean.Person]: No default constructor found; 
nested exception is java.lang.NoSuchMethodException: com.mc.base.learn.spring.bean.Person.<init>()
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1105)
  。。。。。

2 利用有参构造函数直接注入
Person类:

public class Person {

    private String name;
    private Integer id;
    
    public Person(String name, Integer id) {
        super();
        this.name = name;
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public Integer getId() {
        return id;
    }

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

    @Override
    public String toString() {
        return "Person [name=" + name + ", id=" + id + "]";
    }

}

XML配置:

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

    <bean class="com.mc.base.learn.spring.bean.Person" id="person">
        <constructor-arg name="id" value="123"></constructor-arg>
        <constructor-arg name="name" value="LiuChunfu"></constructor-arg>
    </bean>
    
</beans>

二、通过静态工厂方式创建对象。

 静态工厂的对象,在容器加载的时候就被创建了。

package com.mc.base.learn.spring.factory;

import com.mc.base.learn.spring.bean.Person;

public class PersonStaticFactory {
    
    public static Person createPerson(){
        return new Person();
    }
    
    /**
     * 工厂方法带有参数如何处理?
     * @Title: createPerson 
     * @Description: TODO(这里用一句话描述这个方法的作用) 
     * @param  @param id
     * @param  @param name
     * @param  @return 
     * @return Person    返回类型 
     * @throws
     */
    public static Person createPerson(Integer id,String name){
        return new Person(name,id);
    }
}
    <!--静态的工厂方法核心是class+factory-method -->
     <bean id="person" class="com.mc.base.learn.spring.factory.PersonStaticFactory" factory-method="createPerson">
        <!--通过property方法向createPerson传递参数 -->
        <property name="name" value="LiuChunfu"></property>
        <property name="id" value="125"></property>
    </bean>
 测试如下:

    @Test
    public void testName() throws Exception {
        ApplicationContext ac=new ClassPathXmlApplicationContext("applicationContext.xml");
        Person person=ac.getBean("person3", Person.class);
        System.out.println(person);//Person [name=LiuChunfu, id=125]
    }
三、通过实例工厂方式创建对象。

        实例工厂,就是通过实例来调用对象,但是所得到的对象最终也是单利模式。实例工厂和静态工厂创建的对象都是单例模式,两者的区别就是创建对象的实际不同,静态工厂是在创建容器的时候就创建了,实例工厂是在调用方法的时候才创建。知道Java设计模式中的单例模式设计(饿汉式和懒汉式)的读者,对这里的静态工厂模式和实例工厂模式肯定有所体会。

package com.mc.base.learn.spring.factory;

import com.mc.base.learn.spring.bean.Person;

public class PersonFactory {

    public Person createInstance() {
        return new Person();
    }
}


    <bean id="personFactory" class="cn.test.util.PresonFactoryInstance"></bean>
    <bean id="person4" factory-bean="personFactory" factory-method="createPerson">
        <property name="name" value="LiuChunfu"></property>
        <property name="id" value="125"></property>
    </bean>

    @Test
    public void testName() throws Exception {
        ApplicationContext ac=new ClassPathXmlApplicationContext("applicationContext.xml");
        Person person=ac.getBean("person4",Person.class);
        System.out.println(person);//Person [name=LiuChunfu, id=125]
    }
    当然上面的创建对象传递参数,除了能够在创建对象的时候通过传入默认的参数,也可以在后面通过setter方法进行传参。

1.配置元数据:关键类 BeanDefinition

        BeanDefinition表示Bean定义

BeanDefinition中存在很多属性用来描述一个Bean的特点。
比如:
class,表示Bean类型
scope,表示Bean作用域,单例或原型等
lazyInit:表示Bean是否是懒加载
initMethodName:表示Bean初始化时要执行的方法
destroyMethodName:表示Bean销毁时要执行的方法

还有很多...

        看源码,BeanDefinition是接口继承了AttributeAccessor, BeanMetadataElement,在BeanDefinition接口里面定义了Bean定义所需的所有方法。上面截图是它的实现类和子接口。

申明式定义Bean

在Spring中,我们经常会通过以下几种方式来定义Bean:
<bean/>
@Bean
@Component(@Service,@Controller)

这些,我们可以称之申明式定义Bean。 

编程式定义Bean

        直接通过BeanDefinition,可称为编程式定义bean,示例如下:

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

// 生成一个BeanDefinition对象,并设置beanClass为User.class,并注册到ApplicationContext中
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition.setBeanClass(User.class);
context.registerBeanDefinition("user", beanDefinition);


我们还可以通过BeanDefinition设置一个Bean的其他属性

beanDefinition.setScope("prototype"); // 设置作用域
beanDefinition.setInitMethodName("init"); // 设置初始化方法
beanDefinition.setLazyInit(true); // 设置懒加载 (缓解Bean的循环引用)

        和申明式事务、编程式事务类似,通过<bean/>,@Bean,@Component等申明式方式所定义的Bean,最终都会被Spring解析为对应的BeanDefinition对象,并放入Spring的IoC容器中。 

        Bean信息定义类图

BeanDefinition是配置文件元素标签在容器中的内部表示形式
其中RootBeanDefinition是最常用的实现类,它对应一般性的元素标签,GenericBeanDefinition是自2.5版本以后新加入的bean文件配置属性定义类,是一站式服务类
在配置文件中可以定义父和子,父用RootBeanDefinition表示,而子用ChildBeanDefiniton表示,而没有父的就使用RootBeanDefinition表示。AbstractBeanDefinition对两者共同的类信息进行抽象。
Spring通过BeanDefinition将配置文件中的配置信息转换为容器的内部表示,并将这些BeanDefiniton注册到BeanDefinitonRegistry中。Spring容器的BeanDefinitionRegistry就像是Spring配置信息的内存数据库,主要是以map的形式保存,后续操作直接从BeanDefinition Registry中读取配置信息

2.容器启动和创建对象:ApplicationContext

        Spring容器启动时,会读取配置元数据,并根据这些信息创建和配置组件。

a.读取配置元数据--BeanDefinition读取器(BeanDefinitionReader)

        接下来,我们来介绍几种在Spring源码中所提供的BeanDefinition读取器(BeanDefinitionReader),这些BeanDefinitionReader在我们使用Spring时用得少,但在Spring源码中用得多,相当于Spring源码的基础设施。

AnnotatedBeanDefinitionReader(注解定义bean解析器)

        可以直接把某个类转换为BeanDefinition,并且会解析该类上的注解,比如:

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

AnnotatedBeanDefinitionReader annotatedBeanDefinitionReader = new AnnotatedBeanDefinitionReader(context);

// 将User.class解析为BeanDefinition
annotatedBeanDefinitionReader.register(User.class);

System.out.println(context.getBean("user"));
注意:它能解析的注解是:@Conditional,@Scope、@Lazy、@Primary、@DependsOn、@Role、@Description

XmlBeanDefinitionReader(Xml定义bean解析器)

可以解析<bean/>标签

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(context);
int i = xmlBeanDefinitionReader.loadBeanDefinitions("spring.xml");

System.out.println(context.getBean("user"));
ClassPathBeanDefinitionScanner

ClassPathBeanDefinitionScanner是扫描器,但是它的作用和BeanDefinitionReader类似,它可以进行扫描,扫描某个包路径,对扫描到的类进行解析,比如,扫描到的类上如果存在@Component注解,那么就会把这个类解析为一个BeanDefinition,比如:


AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.refresh();

ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context);
scanner.scan("com.zhouyu");

System.out.println(context.getBean("userService"));

Xml定义bean解析器类图

上图分析:

ResourceLoader:定义资源加载器,主要应用于根据给定的资源文件地址返回对应的Resource。
BeanDefinitionReader:主要定义资源文件读取并转换为BeanDefinition的各个功能。
EnvironmentCapable:定义获取Environment方法。
DocumentLoader:定义从资源文件加载到转换为Document的功能。
BeanDefinitionDocumentReader:定义读取Docuemnt并注册BeanDefinition功能。
BeanDefinitionParserDelegate:定义解析Element的各种方法。


整个流程如下:

通过继承自AbstractBeanDefinitionReader中的方法,来使用ResourLoader将资源文件路径转换为对应的Resource文件。
通过DocumentLoader对Resource文件进行转换,将Resource文件转换为Document文件
通过实现接口BeanDefinitionDocumentReader的DefaultBeanDefinitionDocumentReader类对Document进行解析,并使用BeanDefinitionParserDelegate对Element进行解析。

资源文件处理类图

上图分析:

不同的资源文件都有相应的Resource实现
文件(FileSystemResource)
Classpath资源(ClassPathResource)
URL资源(UrlResource)
InputStream资源(InputStreamResource)
Byte数组(ByteArrayResource)

资源加载文件类图

上图分析:

ResourceLoader,定义资源加载器,主要应用于根据给定的资源文件地址,返回对应的 Resource 。
DefaultResourceLoader是ResourceLoader的默认实现
FileSystemResourceLoader从文件系统加载资源

b.读取元数据后,创建bean

BeanFactory
        BeanFactory表示Bean工厂,所以很明显,BeanFactory会负责创建Bean,并且提供获取Bean的API。

        而ApplicationContext是BeanFactory的一种,在Spring源码中,是这么定义的:

public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,
        MessageSource, ApplicationEventPublisher, ResourcePatternResolver {

            ...
}

        首先,在Java中,接口是可以多继承的,我们发现ApplicationContext继承了ListableBeanFactory和HierarchicalBeanFactory,而ListableBeanFactory和HierarchicalBeanFactory都继承至BeanFactory,

        所以我们可以认为ApplicationContext继承了BeanFactory,相当于苹果继承水果,宝马继承汽车一样,ApplicationContext也是BeanFactory的一种,拥有BeanFactory支持的所有功能,不过ApplicationContext比BeanFactory更加强大,

        ApplicationContext还基础了其他接口,也就表示ApplicationContext还拥有其他功能,

        比如MessageSource表示国际化,ApplicationEventPublisher表示事件发布,EnvironmentCapable表示获取环境变量,等等,关于ApplicationContext后面再详细讨论。

        在Spring的源码实现中,当我们new一个ApplicationContext时,其底层会new一个BeanFactory出来,当使用ApplicationContext的某些方法时,比如getBean(),底层调用的是BeanFactory的getBean()方法。
        在Spring源码中,BeanFactory接口存在一个非常重要的实现类是:DefaultListableBeanFactory,也是非常核心的。具体重要性,随着后续课程会感受更深。

        所以,我们可以直接来使用DefaultListableBeanFactory,而不用使用ApplicationContext的某个实现类,比如:

DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();

AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition.setBeanClass(User.class);

beanFactory.registerBeanDefinition("user", beanDefinition);

System.out.println(beanFactory.getBean("user")); 

        DefaultListableBeanFactory是非常强大的,支持很多功能,可以通过查看DefaultListableBeanFactory的类继承实现结构来看 。

它实现了很多接口,表示,它拥有很多功能:
AliasRegistry:支持别名功能,一个名字可以对应多个别名
BeanDefinitionRegistry:可以注册、保存、移除、获取某个BeanDefinition
BeanFactory:Bean工厂,可以根据某个bean的名字、或类型、或别名获取某个Bean对象
SingletonBeanRegistry:可以直接注册、获取某个单例Bean
SimpleAliasRegistry:它是一个类,实现了AliasRegistry接口中所定义的功能,支持别名功能
ListableBeanFactory:在BeanFactory的基础上,增加了其他功能,可以获取所有BeanDefinition的beanNames,可以根据某个类型获取对应的beanNames,可以根据某个类型获取{类型:对应的Bean}的映射关系
HierarchicalBeanFactory:在BeanFactory的基础上,添加了获取父BeanFactory的功能
DefaultSingletonBeanRegistry:它是一个类,实现了SingletonBeanRegistry接口,拥有了直接注册、获取某个单例Bean的功能
ConfigurableBeanFactory:在HierarchicalBeanFactory和SingletonBeanRegistry的基础上,添加了设置父BeanFactory、类加载器(表示可以指定某个类加载器进行类的加载)、设置Spring EL表达式解析器(表示该BeanFactory可以解析EL表达式)、设置类型转化服务(表示该BeanFactory可以进行类型转化)、可以添加BeanPostProcessor(表示该BeanFactory支持Bean的后置处理器),可以合并BeanDefinition,可以销毁某个Bean等等功能
FactoryBeanRegistrySupport:支持了FactoryBean的功能
AutowireCapableBeanFactory:是直接继承了BeanFactory,在BeanFactory的基础上,支持在创建Bean的过程中能对Bean进行自动装配
AbstractBeanFactory:实现了ConfigurableBeanFactory接口,继承了FactoryBeanRegistrySupport,这个BeanFactory的功能已经很全面了,但是不能自动装配和获取beanNames
ConfigurableListableBeanFactory:继承了ListableBeanFactory、AutowireCapableBeanFactory、ConfigurableBeanFactory
AbstractAutowireCapableBeanFactory:继承了AbstractBeanFactory,实现了AutowireCapableBeanFactory,拥有了自动装配的功能
DefaultListableBeanFactory:继承了AbstractAutowireCapableBeanFactory,实现了ConfigurableListableBeanFactory接口和BeanDefinitionRegistry接口,所以DefaultListableBeanFactory的功能很强大
AliasRegistry:定义对alias的简单增删改等操作
SimpleAliasRegistry:主要使用map作为alias的缓存,并对接口AliasRegistry进行实现
SingletonBeanRegistry:定义对单例的注册及获取
BeanFactory:定义获取bean及bean的各种属性。
DefaultSingletonBeanRegistry:对接口SingletonBeanRegistry各函数的实现。
HierarchicalBeanFactory:继承BeanFactory,也就是在BeanFactory定义的功能的基础上增加了对parentFactory的支持。
BeanDefinitionRegistry:定义对BeanDefinition的各种增删改操作。
FactoryBeanRegistrySupport:在DefaultSingletonBeanRegistry基础上增加了对FactoryBean的特殊处理功能。
ConfigurableBeanFactory:提供配置Factory的各种方法。
ListableBeanFactory:根据各种条件获取bean的配置清单。
AbstractBeanFactory:综合FactoryBeanRegistrySupport和ConfigurableBeanFactory的功能。
AutowireCapableBeanFactory:提供创建bean、自动注入、初始化以及应用bean的后处理器。
AbstractAutowireCapableBeanFactory:综合AbstractBeanFactory并对接口AutowireCapable BeanFactory进行实现
ConfigurableListableBeanFactory:BeanFactory配置清单,指定忽略类型及接口等
DefaultListableBeanFactory,而DefaultListableBeanFactory是整个bean加载的核心部分,是Spring注册及加载bean的默认实现。综合上面所有功能,主要是对Bean注册后的处理
XmlBeanFactory对DefaultListableBeanFactory类进行了扩展,主要用于从XML文档中读取BeanDefinition,对于注册及获取Bean都是使用从父类DefaultListableBeanFactory继承的方法去实现,而唯独与父类不同的个性化实现就是增加了XmlBeanDefinitionReader类型的reader属性。在XmlBeanFactory中主要使用reader属性对资源文件进行读取和注册。

ApplicationContext

        ApplicationContext是个接口,实际上也是一个BeanFactory,不过比BeanFactory更加强大,比如:

HierarchicalBeanFactory:拥有获取父BeanFactory的功能
ListableBeanFactory:拥有获取beanNames的功能
ResourcePatternResolver:资源加载器,可以一次性获取多个资源(文件资源等等)
EnvironmentCapable:可以获取运行时环境(没有设置运行时环境功能)
ApplicationEventPublisher:拥有广播事件的功能(没有添加事件监听器的功能)
MessageSource:拥有国际化功能 

        我们先来看ApplicationContext两个比较重要的实现类:

  1. AnnotationConfigApplicationContext
  2. ClassPathXmlApplicationContext

3.示例

定义一个类Student
/**
 * @author zhouxx
 * @create 2024-07-06 20:34
 */
@Data
@Builder
// @Component  // Component注解告诉spring iod容器,创建1个单例对象,对象属性都是默认值  int=0,string=“”
public class Student {
    private int studentNo;
    private String studentName;
}


注入三个Student对象到ioc容器中
/**
 * @author zhouxx
 * @create 2024-07-06 20:40
 */
@Configuration
public class AppConfig {

    @Bean
    public Student student() {
        return Student.builder()
                .studentNo(-1)
                .studentName("Tony")
                .build();
    }


    @Bean
    public Student Tom() {
        return Student.builder()
                .studentNo(1)
                .studentName("Tom")
                .build();
    }

    @Bean(name = "Jim")
    public Student Jim() {
        return Student.builder()
                .studentNo(2)
                .studentName("Jim")
                .build();
    }
}


依赖注入到BeanController对象中

/**
 * @author zhouxx
 * @create 2024-07-06 20:48
 */
@RestController
@Slf4j
@Validated
@RequestMapping("/api/v1/student")
public class BeanController {

    @Value("${server.port}")
    private String serverPort;


    @Autowired
    private Student student;
    @Autowired
    private Student1 student1;

    @Autowired
    private Student Tom;

    @Autowired
    private Student Jim;

    @Autowired
    private LilyConfig LilyConfig;

    @Autowired
    private com.zxx.study.web.config.LilyConfig1 lilyConfig1;

    @PostConstruct
    public void init(){
        log.info("修改前:"+student.toString());
        student.setStudentNo(-100);
        log.info("修改后:[{}]",student.toString());
    }

    @GetMapping("/find")
    public ApiResult find(@RequestParam(value = "name", required = false) String name) {
        //log.info("hi==========");
        Student student = this.student;
        if (name == null) {
            student = this.student;
        } else if ("Tony".equals(name)) {
            student = this.student;
        } else if ("Tom".equals(name)) {
            student = this.Tom;
        } else if ("Jim".equals(name)) {
            student = this.Jim;
        } else if ("Lily".equals(name)) {
            // Ioc容器中 Lily这个对象的类型是Lily,不是Student类型
            student.setStudentNo(this.LilyConfig.getStudentNo());
            student.setStudentName(this.LilyConfig.getStudentName());
        }
        Map data=new HashMap<>(4);
        data.put("student",student);
        data.put("student1",student1);
        data.put("LilyConfig1",lilyConfig1);
        return ApiResult.success(data);
    }
}

下面是运行结果

1.4 实战:理解Spring中对象的生命周期

        Spring框架管理的对象,其生命周期可以概括为以下几个阶段:

  1. 实例化(Instantiation):Spring容器通过反射或者工厂方法创建bean的实例。

  2. 属性赋值(Populate Properties):Spring设置bean的依赖关系,即为bean的属性赋值。

  3. 初始化(Initialization):Spring调用bean的任何初始化方法,如afterPropertiesSet()或自定义的初始化方法。

  4. 使用(In Use):bean可以被应用程序使用了。

  5. 销毁(Destruction):当bean不再需要时,Spring会调用bean的销毁方法,如destroy(),进行资源清理。

        以下是一个简单的Spring Bean的定义和生命周期的示例代码:

import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.BeanPostProcessor;
 
public class MyBean implements InitializingBean, DisposableBean {
 
    private String name;
 
    public MyBean() {
        System.out.println("实例化MyBean");
    }
 
    public void setName(String name) {
        this.name = name;
        System.out.println("设置属性name为: " + name);
    }
 
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("调用afterPropertiesSet初始化MyBean");
    }
 
    public void customInit() {
        System.out.println("自定义初始化方法customInit");
    }
 
    @Override
    public void destroy() throws Exception {
        System.out.println("销毁MyBean");
    }
 
    // 省略其他方法和属性...
}


<bean id="myBean" class="com.example.MyBean" init-method="customInit">
    <property name="name" value="Spring Bean"/>
</bean>

在这个例子中,Spring首先通过反射创建MyBean的实例,
然后调用setName()方法设置依赖关系,
接着调用afterPropertiesSet()方法和customInit()方法进行初始化。(InitializingBean接口)
当Spring容器关闭时,destroy()方法被调用来清理资源。(DisposableBean 接口)
Spring 中的 bean 的作用域有哪些?
  • singleton : 唯一 bean 实例,Spring 中的 bean 默认都是单例的。
  • prototype : 每次请求都会创建一个新的 bean 实例。
  • request : 每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP request 内有效。
  • session : 每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP session 内有效。
  • global-session: 全局 session 作用域,仅仅在基于 Portlet 的 web 应用中才有意义,Spring5 已经没有了。Portlet 是能够生成语义代码(例如:HTML)片段的小型 Java Web 插件。它们基于 portlet 容器,可以像 servlet 一样处理 HTTP 请求。但是,与 servlet 不同,每个 portlet 都有不同的会话。

        Bean 创建流程入口 AbstractApplicationContext#refresh() 方法的 finishBeanFactoryInitialization(beanFactory) 处带大家跟一下源码,想了想还是不带入过多的代码进来,直接给到最终的主要逻辑。

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException { 

BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) { 

instanceWrapper = (BeanWrapper)this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) { 

// 实例化阶段
instanceWrapper = this.createBeanInstance(beanName, mbd, args);
}
...
Object exposedObject = bean;
try { 

// 属性赋值阶段
this.populateBean(beanName, mbd, instanceWrapper);
// 初始化阶段
exposedObject = this.initializeBean(beanName, exposedObject, mbd);
} catch (Throwable var18) { 

...
}
...
}


至于销毁,是在容器关闭时调用的,详见 ConfigurableApplicationContext#close()

        主要是后处理器方法,比如下图的 InstantiationAwareBeanPostProcessorBeanPostProcessor 接口方法。这些接口的实现类是独立于 Bean 的,并且会注册到 Spring 容器中。在 Spring 容器创建任何 Bean 的时候,这些后处理器都会发生作用。

在这里插入图片描述

源码分析参考:一文读懂 Spring Bean 的生命周期「建议收藏」-腾讯云开发者社区-腾讯云

1.5 实战:理解Spring中的循环依赖

        循环依赖就是两个或两个以上的对象相互依赖,形成了环状的依赖关系

        Spring 解决循环依赖的机制主要基于三级缓存和提前曝露半初始化的 Bean 的思想。

        具体步骤如下:

1.实例化对象并放入缓存

        当 Spring 容器创建 Bean 时,会先实例化对象,然后将对象放入第一级缓存(singletonObjects)中。此时对象还未完全初始化。

2.设置对象引用

        Spring 将对象放入第二级缓存(earlySingletonObjects),并设置对象的引用。这时候对象已经可以被其他对象引用,但仍未完成初始化。

3.提前曝光半初始化的 Bean

        如果 Bean A 依赖于 Bean B,而 Bean B 又依赖于 Bean A,Spring 在创建 Bean A 时,会提前曝光一个半初始化的 Bean A 到第二级缓存中。这个半初始化的 Bean A 具有 Bean A 的代理对象,可以提供给 Bean B 使用。

4.完成 Bean 的初始化

        Spring 继续初始化 Bean B,当 Bean B 初始化完成后,Spring 再回头来完成 Bean A 的初始化。这时,Bean A 已经可以通过代理对象访问到 Bean B。

5.将对象移至第三级缓存

        当 Bean A 和 Bean B 都初始化完成后,Spring 将它们从第二级缓存移动到第三级缓存(singletonFactories)中,同时清除第一级和第二级缓存中的对象。

public class DefaultSingletonBeanRegistry {
    
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(64); // 第一级缓存
    private final Map<String, Object> earlySingletonObjects = new HashMap<>(16); // 第二级缓存
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); // 第三级缓存
    
    public Object getSingleton(String beanName) {
        // 1. 从第一级缓存中获取对象
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject != null) {
            return singletonObject;
        }
        
        // 2. 从第二级缓存中获取对象
        singletonObject = this.earlySingletonObjects.get(beanName);
        if (singletonObject != null) {
            return singletonObject;
        }
        
        // 3. 从第三级缓存中获取对象工厂,并使用工厂创建对象
        ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
        if (singletonFactory != null) {
            synchronized (this.singletonObjects) {
                singletonObject = singletonFactory.getObject();
                this.earlySingletonObjects.put(beanName, singletonObject);
                this.singletonFactories.remove(beanName);
            }
            // 4. 完成 Bean 的初始化
            initializeBean(beanName, singletonObject);
            // 5. 将对象移至第一级缓存
            addToSingletons(beanName, singletonObject);
            return singletonObject;
        }
        
        return null;
    }
    
    private void initializeBean(String beanName, Object singletonObject) {
        // 初始化 Bean 的逻辑,包括填充属性、调用初始化方法等
    }
    
    private void addToSingletons(String beanName, Object singletonObject) {
        this.singletonObjects.put(beanName, singletonObject);
    }
}

        在这段代码中,模拟了 Spring 的 DefaultSingletonBeanRegistry 类,其中包含了三级缓存 singletonObjects、earlySingletonObjects 和 singletonFactories。当获取单例 Bean 时,首先会从第一级缓存中获取,如果没有找到则尝试从第二级缓存中获取,如果还没有则尝试从第三级缓存中获取对象工厂,并使用工厂创建对象。创建过程中会将对象暂时放入第二级缓存中,等待完成初始化后再移至第一级缓存中。

1.构造器循环依赖


        构造器循环依赖 :两个或多个 Bean 通过构造函数相互依赖时,就会发生构造器循环依赖。例如,BeanA 的构造函数参数需要 BeanB,而 BeanB 的构造函数参数需要 BeanA。这种循环依赖是无法解决的,因为在创建 Bean 实例时需要提供所有的构造函数参数,而循环依赖导致无法满足这个条件。
代码实例如下:

@Component
public class AService {
    public BService bService;
    public AService(BService bService) {
        this.bService = bService;
    }
    public void test() {
        System.out.println("bService = " + bService);
    }
}

@Component
public class BService {
    public AService aService;
    public BService(AService aService) {
        this.aService = aService;
    }
    public void test() {
        System.out.println("aService = " + aService);
    }
}


        上述代码是存在构造器循环依赖的。

产生原因:

        当 Spring 实例化 AService 对象时,会推断使用 AService 中的哪一个构造方法,因为重写了该类的构造方法,该类的空参构造被覆盖掉了,因此 Spring 只能选择该构造方法;
使用该构造方法时,发现形参为 BService,Spring 首先会调用 getBean() 方法,在容器内寻找BService;
        此时容器内还未实例化 BService,因此无法在容器内找到 BService,然后 Spring 会调用 doCreateBean() 方法,去实例化 BService;
        当实例化 BService时,又会重复上述过程,出现报错。
解决方法:
        在 AService 中的构造方法上,添加 @Lazy 注解。
        当 AService 中的构造方法,添加该注解后,在调用 AService 的构造方法时,Spring 后创建一个 BService 的代理对象,给 AService 的构造方法去使用,以此来实例化 AService,并完成后续的生命周期。

2.属性循环依赖


代码示例

@Component
public class AService {

    public BService bService;

    public void test() {
        System.out.println("bService = " + bService);
    }
}

@Component
public class BService {

    public AService aService;

    public void test() {
        System.out.println("aService = " + aService);
    }
}

        上述代码存在属性方法间的循环依赖,该类循环依赖,由 Spring 通过三级缓存方式,进行了解决。

产生原因(假设不存在三级缓存):

        AService 并没有新增构造方法,因此 Spring 采用默认的无参构造去进行对象的实例化,实例化对象后,进入属性注入环节。
        AService 中 有 BService 属性,Spring 会在容器中寻找 BService 对象。
        由于 Spring 中不存在 BServce 对象,会去执行创建 BService 的流程。
        在创建 BSerivce 的流程中,发现 AService 并没有存在,会去进行创建 AService,从而循环上述问题,出现循环依赖。
解决思路:
        添加一层缓存进行解决:
        在对象进行实例化后,将实例化好的对象,提前暴露,放到一个缓存中,后续有对象需要该对象,去缓存中寻找。
        如下图所示:


为什么需要三级缓存
        通过上述流程可以看出,二级缓存就能够解决 Spring 的属性循环依赖,为什么需要三级缓存呢?

        假设A的原始对象为@00a1,A并且需要进行AOP,得到的代理对象为@00a2。
由于B对象中的A对象,得到的是A的原始对象,并非代理对象。
        此时就会出现不一致问题,即 B.A = @00a1,通过容器获取的A对象为@00a2。
        因此需要三级缓存。

三级缓存
        一级缓存:singletonObjects,也就是平时所说的单例池,存放的经过完整的生命周期的 bean 对象。
        二级缓存:earlySingletonObjects,存放的是早期的 bean 对象,是没有经过完整生命周期的 bean 对象。
        三级缓存:singletonFactories,对象工厂,用于创建早期 bean 对象的工厂。

1.6 实战:小结spring依赖注入DI过程中的注解

        在 Spring 中,我们可以使用注解来简化 Bean 定义和依赖注入的过程。Spring 提供了多个注解来实现依赖注入和生命周期回调等功能。

        下面是一些常用的 Spring 注解:



@Autowired:自动根据类型进行依赖注入,当Spring上下文中存在唯一的bean类型匹配时,将其注入。如果存在多个匹配,则需要配合@Qualifier使用。
@Qualifier:与@Autowired配合使用,指定需要注入的bean的id。
@Resource:默认按照名称进行依赖注入,名称通过属性name指定,若未指定name,则按照变量名或字段名进行注入。
@Value:注入简单类型的值,如字符串、数字等。
@Inject:与@Autowired类似,都是按类型自动注入,是JSR330标准的一部分。
@Configuration:标识该类为配置类,可以使用@Bean注解方法来定义Bean。
@ComponentScan:指定Spring扫描组件的位置,用于启动自动扫描。
@Component:用于标记一个类为 Bean。
@Scope:指定bean的作用域,如@Scope("prototype")表示多例。
@PostConstruct:标识初始化方法,在依赖注入完成后执行。
@PreDestroy:标识销毁方法,在Bean销毁前执行。

@Component: 标注一个普通的Spring Bean类
@Controller: 标注一个控制器组件类
@Service: 标注一个业务逻辑组件类
@Repository: 标注一个DAO组件类

在这个例子中,AppConfig类使用@Configuration注解标识它是一个配置类,并通过@Bean注解定义了一个Bean。MyService类中的myBean字段使用@Autowired自动注入MyBean类型的Bean。MyBean类中的init方法使用@PostConstruct注解标识为初始化方法,而cleanup方法使用@PreDestroy注解标识为销毁前执行的方法。


@Configuration
public class AppConfig {
    @Bean
    public MyBean myBean() {
        return new MyBean();
    }
}
 
public class MyService {
    @Autowired
    private MyBean myBean;
 
    public void doSomething() {
        myBean.action();
    }
}
 
public class MyBean {
    @PostConstruct
    public void init() {
        // 初始化代码
    }
 
    public void action() {
        // 业务逻辑
    }
 
    @PreDestroy
    public void cleanup() {
        // 销毁前代码
    }
}


2、AOP和Instrumentation

        Spring-aop模块:提供了一个符合AOP要求的面向切面的编程实现,允许定义方法拦截器和切入点,将代码按照功能进行分离,以便干净地解耦。
        Spring-aspects模块:提供了与AspectJ的集成功能,AspectJ是一个功能强大且成熟的AOP框架。
        Spring-instrument模块:提供了类植入(Instrumentation)支持和类加载器的实现,可以在特定的应用服务器中使用。

2.1 AOP 面向切面编程

        面向切面编程,是针对面向对象编程的一种补充,同时也是spring中第二个最核心的功能,例如可以进行权限认证,日志输出等,可以无侵入的对原来的功能进行切面加入自定义的非业务功能。

        AOP(Aspect Orient Programming)也就是面向切面编程,作为面向对象编程的一种补充,已经成为一种比较成熟的编程方式。其实AOP问世的时间并不太长,AOP和OOP互为补充,面向切面编程将程序运行过程分解成各个切面。

        AOP专门用于处理系统中分布于各个模块(不同方法)中的交叉关注点的问题,在JavaEE应用中,常常通过AOP来处理一些具有横切性质的系统级服务,如事务管理、安全检查、缓存、对象池管理等,AOP已经成为一种非常常用的解决方案。

相关注解介绍:

@Around:环绕增强,相当于MethodInterceptor
@AfterReturning:后置增强,相当于AfterReturningAdvice,方法正常退出时执行
@Before:标识一个前置增强方法,相当于BeforeAdvice的功能,相似功能的还有
@AfterThrowing:异常抛出增强,相当于ThrowsAdvice
@After: final增强,不管是抛出异常或者正常退出都会执行


在Spring中,多个AOP(面向切面编程)通知可以应用于同一连接点(如方法执行)。
执行这些通知的顺序取决于它们是在前置通知、后置通知、环绕通知还是异常通知中定义的。

前置通知(Before advice):按照声明顺序先后执行。
后置通知(After returning advice):按照声明顺序先后执行。
环绕通知(Around advice):可以自定义执行顺序,依赖于@Around注解中的逻辑。
异常通知(After throwing advice):按照声明顺序先后执行。
最终通知(After (finally) advice):按照声明顺序先后执行。

如果你想控制特定的通知执行顺序,可以使用@Order注解或者实现Ordered接口。

2.2多切面执行顺序

        多切面执行时,采用了责任链设计模式。

        切面的配置顺序决定了切面的执行顺序,多个切面执行的过程,类似于方法调用的过程,在环绕通知的proceed()执行时,去执行下一个切面或如果没有下一个切面执行目标方法,从而达成了如下的执行过程:(目标方法只会执行一次)

        执行的顺序和配置顺序等有关
目标方法执行前: 前置,环绕前(顺序可以变)
目标方法执行后: 最终,环绕后,最终(顺序可以变)

        责任链设计模式:为解除请求的发送者和接收者之间的耦合,而使多个对象都有机会处理这个请求。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它

        以下有两个切面,first切面和second切面:

        如果目标方法抛出异常


        通知间的跳转顺序


        多个切入点责任链模式如下图:

2.3 AOP代理实现方式

Spring AOP的两种动态代理方式的原理和实现(JDK和CGLIB)_aop动态代理原理的两种方式-CSDN博客

        代理是实现AOP的核心和关键技术。只要是AOP,就一定会涉及代理技术
. 代理技术的分类
        按照是否是在程序运行期间产生代理类可以将代理分为静态代理和动态代理

        1.静态代理:就是手动为每一个目标类的每一个方法都增加交叉业务,也就是手动为每一个目标类增加代理类
        缺点:如果目标类数量非常多或者目标类中的功能非常多,直接使用静态代理的方式来为目标类增加交叉业务会非常的繁琐。

        2.动态代理:通过特定的设置,在程序运行期间指示JVM动态地生成类的字节码。这种动态生成的类往往被用作代理类,即动态代理类。也就是运行时做 编译的事情并且把生成的字节码加载成这个类的Class对象

静态代理

等等

package com.ice.spring.service;
//抽象角色:增删改查业务
public interface UserService {
    void add();
    void delete();
    void update();
    void query();
}

package com.ice.spring.service;
//真实对象,完成增删改查操作的人
public class UserServiceImpl implements UserService {
    public void add() {
        System.out.println("增加了一个用户");
    }
    public void delete() {
        System.out.println("删除了一个用户");
    }
    public void update() {
        System.out.println("更新了一个用户");
    }
    public void query() {
        System.out.println("查询了一个用户");
    }
}

现在我们需要增加一个日志功能,怎么实现!

思路1 :在实现类上增加代码 繁琐,影响业务
思路2:使用静态代理来做,能够不改变原来的业务情况下,可以实现此功能

设置一个代理类来处理日志! 代理角色
package com.ice.spring.service;
//代理角色,在这里面增加日志的实现
public class UserServiceProxy implements UserService {
    private UserServiceImpl userService;
    public void setUserService(UserServiceImpl userService) {
        this.userService = userService;
    }
    public void add() {
        log("add");
        userService.add();
    }
    public void delete() {
        log("delete");
        userService.delete();
    }
    public void update() {
        log("update");
        userService.update();
    }
    public void query() {
        log("query");
        userService.query();
    }
    public void log(String msg){
        System.out.println("执行了"+msg+"方法");
    }
}

测试访问类:
package com.ice.spring;

import com.ice.spring.service.UserServiceImpl;
import com.ice.spring.service.UserServiceProxy;

public class StaticTest {


        public static void main(String[] args) {
            //真实业务
            UserServiceImpl userService = new UserServiceImpl();
            //代理类
            UserServiceProxy proxy = new UserServiceProxy();
            //使用代理类实现日志功能!
            proxy.setUserService(userService);
            proxy.add();
        }

}

缺点:
1)目标类增多,代理类会成倍增多
2)接口和功能增加了修改了,会影响较多的实现类,厂家类,代理类都需要修改,工作量巨大

两种动态代理

1.动态代理的角色和静态代理的一样 .

2.动态代理的代理类是动态生成的 . 静态代理的代理类是我们提前写好的

3.动态代理分为两类 : 一类是基于接口动态代理 , 一类是基于类的动态代理

          1)  基于接口的动态代理——JDK动态代理
          2)  基于类的动态代理—cglib动态代理

JDK的动态代理

        核心 : InvocationHandler 接口和 Proxy类

        1.前提:要求目标类必须实现某些接口

        2.JVM生成的动态代理类只能作用于具有相同接口目标类的代理类

        3.要求目标类实现特定的接口才能生成和目标类对应的动态代理类

CGLIB的动态代理

        1.目标类没有实现相应的接口,又需要为这个类动态生成代理类。此时第三方类库CGLIB是最好的选择。

        2.CGLIB可以为目标类动态生成目标类的子类,并把这个动态生成的子类作为这个类的代理类。

总结:
一、有接口的情况
1)默认使用JDK动态代理
2)可以开启CGLIB动态代理,该方式执行效率远远高于JDK的
开启方法如下
<aop:aspectj-autoproxy proxy-target-class="true"/>

二、没有接口的情况
1)默认开启CGLIB动态代理,由spring自动生成代理类,该方式执行效率远远高于JDK的

3、消息

        Spring4.0以后新增了消息(Spring-messaging)模块,该模块提供了对消息传递体系结构和协议的支持。

4、数据访问/集成

        数据访问/集成层由JDBC、ORM、OXM、JMS和事务模块组成。

        Spring-jdbc模块:提供了一个JDBC的抽象层,消除了烦琐的JDBC编码和数据库厂商特有的错误代码解析。
        Spring-orm模块:为流行的对象关系映射(Object-Relational Mapping)API提供集成层,包括JPA和Hibernate。使用Spring-orm模块可以将这些O/R映射框架与Spring提供的所有其他功能结合使用,例如声明式事务管理功能。
        Spring-oxm模块:提供了一个支持对象/XML映射的抽象层实现,例如JAXB、Castor、JiBX和XStream。
        Spring-jms模块(Java Messaging Service):指Java消息传递服务,包含用于生产和使用消息的功能。自Spring4.1以后,提供了与Spring-messaging模块的集成。
        Spring-tx模块(事务模块):支持用于实现特殊接口和所有POJO(普通Java对象)类的编程和声明式事务管理。


5、Web

        Web层由Spring-web、Spring-webmvc、Spring-websocket和Portlet模块组成。

        Spring-web模块:提供了基本的Web开发集成功能,例如多文件上传功能、使用Servlet监听器初始化一个IOC容器以及Web应用上下文。
        Spring-webmvc模块:也称为Web-Servlet模块,包含用于web应用程序的Spring MVC和REST Web Services实现。Spring MVC框架提供了领域模型代码和Web表单之间的清晰分离,并与Spring Framework的所有其他功能集成。
        Spring-websocket模块:Spring4.0以后新增的模块,它提供了WebSocket和SocketJS的实现。
        Portlet模块:类似于Servlet模块的功能,提供了Portlet环境下的MVC实现。


6、测试

        Spring-test模块支持使用JUnit或TestNG对Spring组件进行单元测试和集成测试。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

-无-为-

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值