当前位置: 首页 > news >正文

网站代理在线做食品网站有哪些内容

网站代理在线,做食品网站有哪些内容,网站建设有哪些软件有哪些,利为汇网站建设接上次博客#xff1a;JavaEE进阶#xff08;14#xff09;Linux基本使用和程序部署#xff08;博客系统部署#xff09;-CSDN博客 目录 关于Bean的作用域 概念 Bean的作用域 Bean的生命周期 源码阅读 Spring Boot自动配置 Spring 加载Bean 问题描述 原因分析 …接上次博客JavaEE进阶14Linux基本使用和程序部署博客系统部署-CSDN博客 目录 关于Bean的作用域 概念 Bean的作用域 Bean的生命周期 源码阅读 Spring Boot自动配置 Spring 加载Bean 问题描述 原因分析 解决方案 ComponentScan Import 导入类 导入ImportSelector接口实现类 SpringBoot原理分析 源码阅读 1、元注解 2. SpringBootConfiguration 3.EnableAutoConfiguration (开启自动配置) 4.ComponentScan (包扫描) EnableAutoConfiguration 详解 关于Bean的作用域 概念 在Spring的IoC控制反转和DI依赖注入阶段我们深入学习了Spring框架如何帮助我们管理对象从而实现松耦合和模块化的设计JavaEE进阶5Spring IoCDI入门、IoC介绍、IoC详解两种主要IoC容器实现、IoC和DI对对象的管理、Bean存储、方法注解 Bean)、DI详解注入方式、总结_java的ioc-CSDN博客 通过注解声明Bean对象: Spring提供了多种注解来声明Bean对象如Controller、Service、Repository、Component、Configuration和Bean。这些注解可以告诉Spring容器哪些类需要被管理并在需要时创建相应的对象。通过ApplicationContext或者BeanFactory获取对象: Spring提供了两种主要的容器来管理对象分别是ApplicationContext和BeanFactory。这些容器负责创建、配置和管理应用中的Bean对象我们可以通过它们来获取需要的对象实例。通过注入方式注入依赖: 在Spring中我们可以通过多种方式来进行依赖注入。常用的方式包括使用Autowired注解、Setter方法注入和构造方法注入。这些方式可以帮助我们在需要时将依赖的Bean对象注入到目标对象中从而实现对象之间的解耦和灵活配置。 现在我们来简单回顾一下 我们新建一个项目 通过 Bean 声明bean把bean存在Spring容器中  package com.example.springtest.Bean;import lombok.Data;public class Dog {private String name;public String getName() {return name;}public void setName(String name) {this.name name;}}package com.example.springtest.Bean;import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;Configuration public class BeanConfig {Beanpublic Dog dog(){Dog dog new Dog(gougou);return dog;}}从Spring容器中获取Bean  package com.example.springtest;import com.example.springtest.Bean.Dog; import com.sun.glass.ui.Application; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ApplicationContext;SpringBootApplication public class SpringTestApplication {public static void main(String[] args) {ApplicationContext context SpringApplication.run(SpringTestApplication.class, args);Dog bean context.getBean(Dog.class);System.out.println(bean);}}也可以通过在代码中直接注入ApplicationContext的方式来获取Spring容器 package com.example.springtest.scope;import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.annotation.RequestScope;RestController public class ScopeController {Autowiredprivate ApplicationContext context;RequestMapping(/getDog)public String getDog(){return context.getBean(Dog.class).toString(); }}从Spring容器中多次获取Bean package com.example.springtest.scope;import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.annotation.RequestScope;RestController public class ScopeController {Autowiredprivate ApplicationContext context;Autowiredprivate Dog dog;RequestMapping(/getDog)public String getDog(){Dog contextDog context.getBean(Dog.class);Dog contextDog2 context.getBean(Dog.class);Dog contextDog3 context.getBean(Dog.class);System.out.println(contextDog);System.out.println(contextDog2);System.out.println(contextDog3);return dog:dog.toString(),context:contextDog.toString();}}发现不管我们拿了几次拿到的都是同一个对象甚至包括我们注入进来的那个。 当输出的bean对象地址值相同时表明每次从Spring容器中取出的对象都是同一个实例。这种现象符合单例模式的特征。单例模式确保一个类只有一个实例无论多次创建都不会产生多个实例。在默认情况下Spring容器中的bean都是单例的这种行为模式我们称之为Bean的作用域。 单例模式在Spring中非常常见因为它可以有效地节省系统资源并且避免不必要的对象创建和销毁开销。在大多数情况下如果一个bean的作用域是单例的那么Spring容器只会创建该bean的一个实例并在需要时重复使用这个实例。这样可以确保整个应用程序中的组件都共享同一个实例从而实现资源的共享和减少内存消耗。 需要注意的是虽然Spring默认情况下的bean作用域是单例的但也可以通过配置来修改bean的作用域如设置为原型prototype作用域这样每次请求时Spring容器都会创建一个新的实例。选择合适的bean作用域可以根据具体需求来决定以达到最佳的性能和资源利用效率。 Bean 的作用域指的是在Spring框架中定义Bean时指定的一种行为模式。 常见的作用域包括单例singleton、原型prototype、请求request、会话session、全局会话global session等。 其中单例作用域是最常见的一种。它表示在整个Spring容器中只存在一个Bean实例这个实例是全局共享的。换句话说当一个Bean对象被创建后Spring容器会在整个应用程序生命周期内保持这个实例的唯一性。因此如果一个人修改了这个单例Bean的值那么另一个人读取到的就是被修改后的值。 这种全局共享的特性使得单例Bean在应用程序中非常有用可以用来保存应用程序的全局状态或共享资源。但同时也需要小心并发访问和状态修改的问题确保线程安全性。 其他作用域如原型作用域表示每次请求Bean时都会创建一个新的实例请求作用域表示每个HTTP请求都会创建一个新的实例会话作用域表示每个会话session都会创建一个新的实例全局会话作用域表示全局会话例如集群环境下的分布式会话都会创建一个新的实例。这些作用域可以根据具体的需求来选择以满足不同场景下的需求。 那么能不能将bean对象设置为非单例的也就是每次获取的bean都是⼀个新对象呢  当然可以这些就是Bean的不同作用域了。 Bean的作用域 在Spring框架中提供了丰富的Bean作用域来满足不同场景下的需求。这些作用域可以通过在Bean的声明中使用Scope注解来指定从而控制Bean的生命周期和实例化方式。 Singleton单例作用域在Spring IoC容器中默认的作用域就是单例作用域。这意味着每个容器内同名称的Bean只有一个实例它是全局共享的。在整个应用程序中只有一个实例存在所有对该Bean的引用都将指向同一个实例。 Prototype原型作用域原型作用域是一种多例作用域每次获取该Bean时都会创建一个新的实例。这意味着每次使用该Bean时都会获得一个新的对象实例而不是共享同一个实例。 以下四种作用域在Spring MVC环境中才生效 Request请求作用域请求作用域表示每个HTTP请求的生命周期内都会创建一个新的实例。这意味着每个HTTP请求都会使用一个新的Bean实例适用于需要在请求级别上维护状态的Bean。 Session会话作用域会话作用域表示每个HTTP Session的生命周期内都会创建一个新的实例。这意味着每个会话都会使用一个新的Bean实例适用于需要在会话级别上维护状态的Bean而同一个会话中是相同的Bean。 Application全局作用域全局作用域表示每个ServletContext的生命周期内都会创建一个新的实例。这意味着每个ServletContext都会使用一个新的Bean实例适用于需要在应用程序级别上共享资源的Bean。 WebSocketWebSocket作用域WebSocket作用域表示每个WebSocket的生命周期内都会创建一个新的实例。这意味着每个WebSocket连接都会使用一个新的Bean实例适用于需要在WebSocket会话级别上维护状态的Bean。 了解和合理使用不同作用域的Bean是Spring应用程序设计中的重要一环学习后我们就可以根据具体的业务需求和性能考虑来选择合适的作用域。 参考官方文档Bean Scopes :: Spring Framework 在Spring中通过注解我们可以方便地指定Bean的作用域而不必在配置文件中进行显式的配置。  我们来简单看下代码实现定义几个不同作用域的Bean Scope用于在Spring框架中指定Bean的作用域。该注解用于类或者方法上并且标记为运行时保留。 Target({ElementType.TYPE, ElementType.METHOD})指定了该注解可以被应用在类或者方法上。 Retention(RetentionPolicy.RUNTIME)指定了该注解在运行时保留这样Spring容器可以在运行时读取并处理这个注解。 Documented指定了该注解会被包含在JavaDoc文档中。 AliasFor(scopeName)表示value和scopeName是互相别名的属性它们可以互相替代使用。 String value() default 用于指定Bean的作用域名称。这个属性与scopeName()是互相别名的。 ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT用于指定Bean的代理模式。默认值为ScopedProxyMode.DEFAULT表示Spring将根据需要自动选择代理模式。 也就是说该注解可以通过Scope注解来声明一个Bean的作用域并且可以指定作用域的名称和代理模式。通过在类或者方法上添加这个注解我们可以告诉Spring容器如何处理这个Bean的作用域。 下面的它们三个其实也继承与scope:  具体来说 RequestScope等同于Scope(value WebApplicationContext.SCOPE_REQUEST, proxyMode ScopedProxyMode.TARGET_CLASS)这个注解表示将Bean的作用域设置为请求作用域。在Web应用程序中每个HTTP请求都会创建一个新的实例。同时需要设置proxyMode属性为ScopedProxyMode.TARGET_CLASS这样才能基于CGLIB实现动态代理。SessionScope等同于Scope(value WebApplicationContext.SCOPE_SESSION, proxyMode ScopedProxyMode.TARGET_CLASS)这个注解表示将Bean的作用域设置为会话作用域。在Web应用程序中每个HTTP Session的生命周期内都会创建一个新的实例。同样需要设置proxyMode属性为ScopedProxyMode.TARGET_CLASS。ApplicationScope等同于Scope(value WebApplicationContext.SCOPE_APPLICATION, proxyMode ScopedProxyMode.TARGET_CLASS)这个注解表示将Bean的作用域设置为全局作用域。在Web应用程序中每个ServletContext的生命周期内都会创建一个新的实例。同样需要设置proxyMode属性为ScopedProxyMode.TARGET_CLASS。 proxyMode属性用于为Spring Bean设置代理这在某些情况下是必需的尤其是在Web环境中。设置proxyMode为ScopedProxyMode.TARGET_CLASS表示使用CGLIB动态代理来实现对Bean的代理以确保在运行时能够正确地创建和管理作用域内的Bean实例。 ConfigurableBeanFactory该接口定义了一些方法用于配置可配置的Bean工厂。这个接口扩展了HierarchicalBeanFactory和SingletonBeanRegistry接口从而继承了它们的功能。 在Spring框架中BeanFactory是负责创建、管理和解析Bean对象的核心接口。ConfigurableBeanFactory接口提供了一些额外的配置功能使得BeanFactory更加灵活和可配置。 该接口中的方法包括设置父级Bean工厂、设置类加载器、设置Bean表达式解析器、设置转换服务、注册作用域、注册别名、销毁Bean等。这些方法可以用于配置Bean工厂的各种属性和行为以满足特定的应用需求。 综上ConfigurableBeanFactory接口提供了一种可扩展和可配置的方式来管理Bean对象并且提供了一些灵活的方法来对Bean工厂进行配置和定制。 我们开始写我们的代码 package com.example.springtest.scope;import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Scope; import org.springframework.web.context.annotation.ApplicationScope; import org.springframework.web.context.annotation.RequestScope; import org.springframework.web.context.annotation.SessionScope; //import org.springframework.context.annotation.Scope; //import org.springframework.web.context.annotation.ApplicationScope; //import org.springframework.web.context.annotation.RequestScope; //import org.springframework.web.context.annotation.SessionScope;Configuration public class BeanConfig {Beanpublic Dog dog(){Dog dog new Dog(gougou);return dog;}Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)Beanpublic Dog singleDog(){return new Dog();}Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)Beanpublic Dog prototypeDog(){return new Dog();}RequestScopeBeanpublic Dog requestDog(){return new Dog();}SessionScopeBeanpublic Dog sessionDog(){return new Dog();}ApplicationScopeBeanpublic Dog applicationDog(){return new Dog();}}package com.example.springtest.scope;import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.annotation.RequestScope;RestController public class ScopeController {Autowiredprivate ApplicationContext context; // public String getDog(){ // return context.getBean(Dog.class).toString(); // }Autowiredprivate Dog dog;Autowiredprivate Dog singleDog;Autowiredprivate Dog prototypeDog;Autowiredprivate Dog requestDog;Autowiredprivate Dog sessionDog;Autowiredprivate Dog applicationDog;RequestMapping(/getDog)public String getDog(){Dog contextDog context.getBean(Dog.class);Dog contextDog2 context.getBean(Dog.class);Dog contextDog3 context.getBean(Dog.class);System.out.println(contextDog);System.out.println(contextDog2);System.out.println(contextDog3);return dog:dog.toString(),context:contextDog.toString();}RequestMapping(/single)public String single(){Dog contextDog (Dog)context.getBean(singleDog);return singleDog:singleDog.toString(),context:contextDog.toString();}RequestMapping(/prototype)public String prototype(){Dog contextDog (Dog)context.getBean(prototypeDog);return prototypeDog:prototypeDog.toString(),context:contextDog.toString();}RequestMapping(/request)public String request(){Dog contextDog (Dog)context.getBean(requestDog);return requestDog:requestDog.toString(),context:contextDog.toString();}RequestMapping(/session)public String session(){Dog contextDog (Dog)context.getBean(sessionDog);return sessionDog:sessionDog.toString(),context:contextDog.toString();}RequestMapping(/application)public String application(){Dog contextDog (Dog)context.getBean(applicationDog);return applicationDog:applicationDog.toString(),context:contextDog.toString();}}测试不同作用域的Bean取到的对象是否一样 单例作用域多次请求拿到的是同一个对象 多例作用域每次拿到的不是同一个对象  请求作用域在一个请求中是同一个对象不同请求中是不同对象。在一次请求中, Autowired 和 applicationContext.getBean() 是同⼀个对象 session作用域同一个会话中是同一个对象 我们直接更换一个浏览器访问发现会重新创建对象  application作用域在一个应用中多次访问都是同⼀个对象  换一个浏览器 Application作用域和Singleton作用域的相似之处在于它们都是在应用程序的整个生命周期内只创建一个实例确保了全局唯一性。但是它们的区别在于作用域的范围不同 Application作用域Bean的作用域是ServletContext级别的也就是说在整个Web容器中只有一个实例存在。这意味着无论有多少个Web应用部署在同一个容器中它们共享同一个Application作用域中的Bean实例。换句话说Application作用域是ServletContext的单例。 Singleton作用域Bean的作用域是ApplicationContext级别的也就是说在每个ApplicationContext中只有一个实例存在。在一个Web容器中可以存在多个ApplicationContext每个Web应用都有自己的ApplicationContext实例。因此Singleton作用域是在ApplicationContext级别的单例。 这样的区别使得在特定的场景下能够选择合适的作用域以满足不同的需求。特别是在多个Web应用部署在同一个容器中的情况下使用Application作用域可以确保Bean的实例在所有应用之间的共享而不会受到其他应用的影响。 我们来说得更明白些在单个Web应用程序内部无论是使用Application作用域还是Singleton作用域都会导致在整个应用程序中只存在一个实例。因此从单个Web应用程序的角度来看它们的行为确实类似都可以被视为单例。 但是它们的区别在于跨多个Web应用程序的情况下 Application作用域在整个Web容器中无论有多少个Web应用程序部署在其中它们共享同一个Application作用域中的Bean实例。这意味着如果在一个Web应用程序中修改了Application作用域中的Bean实例那么其他所有Web应用程序都会看到这个修改。 Singleton作用域在单个Web应用程序内部Singleton作用域确实表现为单例但是如果有多个Web应用程序部署在同一个容器中每个Web应用程序都有自己的ApplicationContext实例因此它们各自拥有自己的Singleton作用域。这意味着即使在同一个容器中部署了多个Web应用程序它们的Singleton作用域中的Bean实例是相互独立的一个应用程序对其进行的修改不会影响其他应用程序。 再补充说明几点 对于Spring框架中的Singleton作用域来说它是在每个ApplicationContext级别上的单例。在典型的Web应用程序中每个Web应用程序通常都有自己的ApplicationContext实例。所以无论是同一个浏览器还是不同的浏览器只要它们都属于同一个Web应用程序它们共享的是同一个ApplicationContext因此对于Singleton作用域的Bean来说它们确实是同一个对象实例。 在Web开发中一个Web应用程序通常指的是一个独立的、功能完整的网络应用它包含了一组相关的Web资源例如HTML、CSS、JavaScript文件、服务器端代码例如Java、Python等、以及一些配置文件。这个应用程序通过HTTP协议与用户交互提供特定的功能和服务比如电子商务网站、社交网络、博客系统等。 在一个Web应用程序中可能有多个不同的模块或组件每个模块都承担不同的功能。例如在Java的Web开发中一个Web应用程序通常会包含多个Servlet、Filter、Listener等组件它们协同工作来处理用户请求、实现业务逻辑等。 关于浏览器的配置问题通常情况下一个Web应用程序对应一个独立的部署单元例如一个WAR文件或者一个独立的目录。在一个Web应用程序中无论用户使用什么样的浏览器都是访问同一个Web应用程序即使用同一个ApplicationContext实例。这意味着无论用户使用何种浏览器访问同一个Web应用程序它们都会共享同一个ApplicationContext因此对于Singleton作用域的Bean来说它们确实是同一个对象实例。 Bean的生命周期 生命周期指的是一个对象从诞生到销毁的整个生命过程我们把这个过程就叫做一个对象的生命周期。 Bean 的生命周期分为以下5个部分 实例化为Bean分配内存空间。 这个阶段是指在内存中为对象分配空间并创建对象的实例。通常情况下实例化是通过Java的new关键字来完成的。 属性赋值Bean注入与装配。 这一阶段涉及到将Bean所需的属性值设置到相应的属性中。属性值可以通过各种方式注入比如使用Autowired注解自动装配或者通过XML配置文件或Java代码手动设置。 初始化 在初始化阶段Bean的状态会被准备好以供使用。这一阶段主要包括两个部分 a. 执行各种通知在这一步中Bean会执行一些特定的方法来获取关于自身的信息比如BeanNameAware、BeanFactoryAware、ApplicationContextAware等接口方法通过这些方法可以获取关于Bean的一些元数据信息或者对Spring容器的引用。 b. 执行初始化方法在这一步中Bean的初始化方法会被调用初始化方法可以通过以下方式定义 ① 在XML中使用init-method属性来指定初始化方法。 ② 使用PostConstruct注解来标记初始化方法。 ③ 执行初始化后置方法即调用实现了BeanPostProcessor接口的类中的postProcessBeforeInitialization和postProcessAfterInitialization方法。 使用Bean 一旦Bean完成了初始化阶段它就可以被其他对象或组件使用了。在这个阶段Bean会被注入到其他对象中或者在容器中被获取并调用其方法。 销毁Bean 当Bean不再需要时Spring容器会在适当的时候销毁它。这个阶段包括执行一些清理工作以释放资源通常包括 a. 调用销毁回调方法可以通过以下方式定义销毁回调方法 ① 在XML中使用destroy-method属性来指定销毁方法。 ② 使用PreDestroy注解来标记销毁方法。 b. 执行DisposableBean接口方法 如果Bean实现了DisposableBean接口Spring容器会在销毁Bean时调用其destroy()方法。 上述这些是Bean的完整生命周期的详细阶段每个阶段都是Spring框架管理Bean的重要组成部分。 执行流程如图 举个例子来说明当考虑到购买一栋房子的生命周期时 房子建造 就像是购买一栋房子的第一步一样房子的建造是从无到有的过程。在这个阶段房子的各个构件被建造并组装在一起创造出了一个新的物理空间。 装修与装饰 在房子建造完成之后接下来就是装修和装饰的阶段。这包括选择合适的家具、粉刷墙壁、铺设地板等等以及根据个人喜好进行装饰让房子变得温馨舒适。 购买家电和家居用品 在房子装修完毕后接下来就是购买各种家电和家居用品的阶段。这包括购买洗衣机、冰箱、电视、空调等设备以及购买床上用品、厨房用具等日常生活所需的物品。 入住 一切准备就绪后就可以搬入新房子开始新的生活了。这个阶段代表了房子被实际使用的过程人们可以在这里生活、工作、休息享受新的家庭生活。 卖房或迁出 当房子不再符合需求或者因为其他原因需要离开时就需要考虑卖房或者搬迁的问题。这个阶段类似于Bean销毁的过程房子可能会被重新出售给其他买家或者被拆除重建或者被改建成其他用途。 实例化和属性赋值对应构造方法和setter方法的注入初始化和销毁是用户能自定义扩展的两个阶段可以在实例化之后类加载完成之前进行自定义“事件”处理。 实例化阶段通常涉及到类的构造方法这是对象在内存中被创建的时刻。在Spring中通过构造方法实例化Bean对象并且在此时Spring会为Bean的属性赋予默认值如果有的话。属性赋值阶段是在实例化之后通过setter方法或者字段注入等方式将Bean的属性值设置好。这是为了满足依赖注入和装配的需要确保Bean在使用之前具备了必要的数据。初始化阶段是用户能够自定义扩展的阶段允许在Bean实例化并且属性设置完毕后进行一些定制化操作。这些操作可能包括执行特定的初始化方法、注册监听器或者事件处理器等等。Spring提供了多种自定义扩展的机制比如使用PostConstruct注解标记初始化方法实现InitializingBean接口的afterPropertiesSet()方法等。销毁阶段也是用户能够自定义扩展的阶段允许在Bean被销毁之前进行一些清理工作释放资源等操作。在这个阶段可以执行一些定制化的销毁方法也可以注册一些监听器来捕获Bean被销毁的事件。Spring提供了多种自定义扩展的机制比如使用PreDestroy注解标记销毁方法实现DisposableBean接口的destroy()方法等。 总的来说Spring框架为Bean的生命周期提供了丰富的扩展点允许开发者在不同阶段插入自定义的逻辑以满足各种需求确保应用程序的正常运行和资源的有效管理。 我们来用代码演示   package com.example.springtest.beanlife;import com.example.springtest.scope.Dog; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component;import javax.annotation.PostConstruct; import javax.annotation.PreDestroy;Slf4j Component public class BeanLifeComponent {private Dog dog;public BeanLifeComponent() {log.info(执行构造函数……);}Autowiredpublic void setDog(Dog dog) {this.dog dog;log.info(执行setter方法……);}PostConstructpublic void init(){log.info(执行init方法……);}public void use(){log.info(执行use方法……);}PreDestroypublic void destroy(){log.info(执行destroy方法……);} }package com.example.springtest;import com.example.springtest.beanlife.BeanLifeComponent; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest;SpringBootTest class SpringTestApplicationTests {Autowiredprivate BeanLifeComponent beanLifeComponent;Testvoid contextLoads() {beanLifeComponent.use();} }在我们的代码中虽然只调用了use()方法但是Spring Boot应用程序在启动时会扫描并加载所有的Component、Service、Controller等注解标记的类并且实例化这些类并加入到Spring容器中进行管理。 因此即使只调用了use()方法但是在Spring Boot应用程序启动时BeanLifeComponent组件已经被实例化并加入了Spring容器中。而在实例化过程中Spring会按照Bean的生命周期顺序执行相应的方法包括构造函数、属性注入、初始化方法等。也就是说在Spring Boot应用程序启动时BeanLifeComponent的生命周期方法已经被执行了。 源码阅读 以上步骤在源码中皆有体现. 创建Bean的代码入口在AbstractAutowireCapableBeanFactory的createBean 这段代码是Spring Framework中创建Bean实例的核心方法之一主要负责根据Bean的定义信息创建Bean实例。 protected Object createBean(String beanName, RootBeanDefinition mbd, Nullable Object[] args) throws BeanCreationException {// 如果日志级别为TRACE则记录创建bean实例的日志if (this.logger.isTraceEnabled()) {this.logger.trace(Creating instance of bean beanName );}// 复制原始的RootBeanDefinition对象RootBeanDefinition mbdToUse mbd;// 解析Bean的类Class? resolvedClass this.resolveBeanClass(mbd, beanName, new Class[0]);// 如果解析到了类并且RootBeanDefinition中未设置beanClass则将解析到的类设置为beanClassif (resolvedClass ! null !mbd.hasBeanClass() mbd.getBeanClassName() ! null) {mbdToUse new RootBeanDefinition(mbd);mbdToUse.setBeanClass(resolvedClass);}try {// 准备方法覆盖mbdToUse.prepareMethodOverrides();} catch (BeanDefinitionValidationException var9) {// 如果准备方法覆盖失败则抛出BeanDefinitionStoreException异常throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(), beanName, Validation of method overrides failed, var9);}Object beanInstance;try {// 在实例化之前解析BeanbeanInstance this.resolveBeforeInstantiation(beanName, mbdToUse);// 如果解析到了Bean实例则直接返回if (beanInstance ! null) {return beanInstance;}} catch (Throwable var10) {// 如果解析失败则抛出BeanCreationException异常throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName, BeanPostProcessor before instantiation of bean failed, var10);}try {// 创建Bean实例beanInstance this.doCreateBean(beanName, mbdToUse, args);// 如果日志级别为TRACE则记录创建bean实例完成的日志if (this.logger.isTraceEnabled()) {this.logger.trace(Finished creating instance of bean beanName );}// 返回创建的Bean实例return beanInstance;} catch (ImplicitlyAppearedSingletonException | BeanCreationException var7) {// 如果出现单例异常或者Bean创建异常则直接抛出throw var7;} catch (Throwable var8) {// 如果创建Bean过程中出现其他异常则抛出BeanCreationException异常throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName, Unexpected exception during bean creation, var8);} }大致流程和思想如下 解析Bean的定义信息包括Bean的类和方法覆盖等信息。在实例化Bean之前执行BeanPostProcessor的前置处理器。实例化Bean对象并根据Bean的定义信息进行属性注入等操作。执行BeanPostProcessor的后置处理器完成Bean的初始化工作。返回创建的Bean实例。 总的来说这段代码负责实现了Bean的创建过程包括解析Bean定义、实例化Bean、属性注入、Bean的前置处理和后置处理等。 protected Object doCreateBean(String beanName, RootBeanDefinition mbd, Nullable Object[] args) throws BeanCreationException {// 创建BeanWrapper对象实例用于包装BeanBeanWrapper instanceWrapper null;// 如果Bean是单例模式并且存在于factoryBeanInstanceCache中则从缓存中移除if (mbd.isSingleton()) {instanceWrapper (BeanWrapper)this.factoryBeanInstanceCache.remove(beanName);}// 如果instanceWrapper为null则通过createBeanInstance方法创建Bean实例if (instanceWrapper null) {instanceWrapper this.createBeanInstance(beanName, mbd, args);}// 获取包装实例的Bean对象Object bean instanceWrapper.getWrappedInstance();// 获取包装实例的Bean对象的类型Class? beanType instanceWrapper.getWrappedClass();// 如果Bean类型不为NullBean则设置mbd的resolvedTargetType为beanTypeif (beanType ! NullBean.class) {mbd.resolvedTargetType beanType;}// 同步块确保后续处理的原子性synchronized(mbd.postProcessingLock) {// 如果BeanDefinition尚未被后处理则进行后处理if (!mbd.postProcessed) {try {this.applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);} catch (Throwable var17) {throw new BeanCreationException(mbd.getResourceDescription(), beanName, Post-processing of merged bean definition failed, var17);}// 标记BeanDefinition已经被后处理mbd.postProcessed true;}}// 是否提前暴露单例Bean用于解决循环依赖boolean earlySingletonExposure mbd.isSingleton() this.allowCircularReferences this.isSingletonCurrentlyInCreation(beanName);// 如果允许提前暴露单例Beanif (earlySingletonExposure) {// 输出日志表示将Bean放入早期缓存以解决潜在的循环引用if (this.logger.isTraceEnabled()) {this.logger.trace(Eagerly caching bean beanName to allow for resolving potential circular references);}// 将Bean的提前引用放入单例工厂this.addSingletonFactory(beanName, () - {return this.getEarlyBeanReference(beanName, mbd, bean);});}// 暴露Bean对象Object exposedObject bean;try {// 填充Bean属性this.populateBean(beanName, mbd, instanceWrapper);// 初始化BeanexposedObject this.initializeBean(beanName, exposedObject, mbd);} catch (Throwable var18) {if (var18 instanceof BeanCreationException beanName.equals(((BeanCreationException)var18).getBeanName())) {throw (BeanCreationException)var18;}throw new BeanCreationException(mbd.getResourceDescription(), beanName, Initialization of bean failed, var18);}// 如果允许提前暴露单例Beanif (earlySingletonExposure) {Object earlySingletonReference this.getSingleton(beanName, false);if (earlySingletonReference ! null) {if (exposedObject bean) {exposedObject earlySingletonReference;} else if (!this.allowRawInjectionDespiteWrapping this.hasDependentBean(beanName)) {String[] dependentBeans this.getDependentBeans(beanName);SetString actualDependentBeans new LinkedHashSet(dependentBeans.length);String[] var12 dependentBeans;int var13 dependentBeans.length;for(int var14 0; var14 var13; var14) {String dependentBean var12[var14];if (!this.removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {actualDependentBeans.add(dependentBean);}}if (!actualDependentBeans.isEmpty()) {throw new BeanCurrentlyInCreationException(beanName, Bean with name beanName has been injected into other beans [ StringUtils.collectionToCommaDelimitedString(actualDependentBeans) ] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using getBeanNamesForType with the allowEagerInit flag turned off, for example.);}}}}try {// 注册可销毁的Bean如果有必要的话this.registerDisposableBeanIfNecessary(beanName, bean, mbd);return exposedObject;} catch (BeanDefinitionValidationException var16) {throw new BeanCreationException(mbd.getResourceDescription(), beanName, Invalid destruction signature, var16);} }上面提供的代码是Spring框架中用于创建Bean实例的关键方法doCreateBean()。该方法负责完成Bean的实例化、属性填充、初始化等过程并且处理了循环依赖的情况。 大致流程 创建BeanWrapper对象 首先声明了一个BeanWrapper对象实例用于包装Bean。检查是否从缓存中获取Bean 如果Bean是单例模式并且在factoryBeanInstanceCache中存在则从缓存中移除。创建Bean实例 如果无法从缓存中获取Bean则调用createBeanInstance()方法创建Bean实例。获取Bean的类型并设置到BeanDefinition中 获取Bean的实例和类型如果Bean类型不是NullBean则将其设置到BeanDefinition的resolvedTargetType属性中。后处理BeanDefinition 使用同步块确保后续处理的原子性如果BeanDefinition尚未被后处理则进行后处理操作并标记为已处理。提前暴露单例Bean 根据配置如果允许提前暴露单例Bean则将Bean放入早期缓存以解决潜在的循环引用。填充Bean属性和初始化Bean 调用populateBean()方法填充Bean属性然后调用initializeBean()方法初始化Bean。处理循环依赖 如果允许提前暴露单例Bean则在此处处理循环依赖的情况。注册可销毁的Bean 最后如果有必要注册可销毁的Bean。 总的来说这段代码是Spring中创建Bean的核心逻辑。它负责实例化Bean、处理Bean的属性注入、初始化Bean、处理循环依赖等任务确保了Bean能够正确地被创建和管理。该方法通过调用一系列的子方法来完成这些任务包括 createBeanInstance()、populateBean()、initializeBean()等。 这三个方法与Bean的生命周期的不同阶段对应 createBeanInstance() - 实例化这个方法负责创建Bean的实例。在这个阶段会调用构造函数来实例化对象并且初始化其成员变量。这一过程可以看作是Bean的诞生过程即从无到有的过程。 populateBean() - 属性赋值在实例化完成后容器会对Bean进行属性赋值也就是将所需的属性值设置到相应的属性中。这一阶段涉及到依赖注入、装配等过程确保Bean的各个属性都被正确地初始化和设置。 initializeBean() - 初始化在属性赋值完成后容器会调用初始化方法对Bean进行初始化。这一阶段包括执行各种初始化操作比如调用init-method指定的初始化方法、执行PostConstruct注解标注的方法等。在这个阶段开发者可以进行一些自定义的初始化操作确保Bean在被使用前已经做好了准备。 这三个方法依次对应了Bean生命周期的不同阶段确保了Bean在被实例化、属性赋值和初始化之后能够正确地被使用。 所以说这段代码实现了Spring容器对Bean的创建和初始化过程的管理其中涉及到了对单例Bean的缓存处理以及对循环依赖的解决。 Spring框架中通过三级缓存来解决循环依赖的问题。 当一个Bean A依赖于另一个Bean B而Bean B又依赖于Bean A时就会产生循环依赖的问题。为了解决这个问题Spring使用了三级缓存的机制。 第一级缓存 在第一次创建Bean A的过程中如果发现了循环依赖Spring会将Bean A的创建过程放入第一级缓存中。这样在创建Bean A的过程中如果需要依赖Bean BSpring会发现Bean B的创建过程已经在进行中而不会再次创建Bean B从而避免了循环依赖问题。 第二级缓存 如果第一级缓存中无法解决循环依赖问题那么Spring会将Bean A的半成品还未完成初始化放入第二级缓存中。这样在创建Bean B的过程中如果需要依赖Bean ASpring会发现Bean A的半成品已经存在从而避免了循环依赖问题。 因为当Spring发现Bean A的半成品已经存在于第二级缓存中时说明Bean A已经被实例化但尚未完成初始化。这时候如果Bean B需要依赖Bean ASpring可以获取到Bean A的半成品即可使用它来满足Bean B的依赖关系。这种情况下虽然Bean A的实例尚未完全初始化但已经足够提供给其他Bean使用因为它的实例已经存在并且可以被引用。因此通过将Bean A的半成品放入第二级缓存中Spring能够解决循环依赖问题确保了Bean的实例化过程不会出现死锁或循环引用的情况。 第三级缓存 如果第二级缓存也无法解决循环依赖问题那么Spring会将Bean A的ObjectFactory放入第三级缓存中。这样在创建Bean B的过程中如果需要依赖Bean ASpring会使用ObjectFactory创建Bean A的代理对象从而避免了循环依赖问题。 通过这三级缓存的机制Spring能够在创建Bean时动态地解决循环依赖问题确保了Bean的正确创建和初始化。 这个过程可以简单地概括为以下几个步骤 创建Bean的原始定义 在Spring容器启动时首先会读取配置文件或者扫描注解将Bean的原始定义比如Bean的类名、属性、依赖等解析为一个BeanDefinition对象。 实例化Bean并放入一级缓存 在Bean的原始定义得到解析后Spring会尝试实例化这些Bean对象。如果Bean的实例化过程中发现了循环依赖Spring会将尚未完成实例化的Bean放入一级缓存中。 提前暴露半成品Bean并放入二级缓存 当有循环依赖时Spring会提前暴露尚未完成实例化的Bean使得其他Bean可以引用到它的半成品状态。这样一旦其他Bean需要引用该Bean就可以通过二级缓存中的半成品对象来满足依赖关系。 完成Bean的实例化并放入三级缓存 当Bean的实例化完成后Spring会将其放入三级缓存中以便后续的Bean的实例化过程中能够直接获取到该Bean的完整实例而不再需要递归实例化依赖的Bean。 循环依赖的处理 在实例化Bean的过程中如果发现了循环依赖Spring会通过提前暴露半成品Bean的方式来破解循环依赖从而保证Bean的实例化过程能够正常完成。 总结来说Spring通过三级缓存的机制来解决循环依赖的问题保证了Bean的实例化过程能够顺利进行并且能够正确处理循环依赖的情况确保了Spring容器的稳定运行。 接回上面的代码我们再点进去看看 protected Object initializeBean(String beanName, Object bean, Nullable RootBeanDefinition mbd) {// 检查是否有安全管理器如果有则使用特权执行否则直接调用invokeAwareMethods方法if (System.getSecurityManager() ! null) {AccessController.doPrivileged(() - {this.invokeAwareMethods(beanName, bean);return null;}, this.getAccessControlContext());} else {this.invokeAwareMethods(beanName, bean);}// 初始化时的Bean实例Object wrappedBean bean;// 如果Bean定义不是合成的则应用初始化之前的Bean后置处理器if (mbd null || !mbd.isSynthetic()) {wrappedBean this.applyBeanPostProcessorsBeforeInitialization(bean, beanName);}try {// 调用初始化方法this.invokeInitMethods(beanName, wrappedBean, mbd);} catch (Throwable var6) {// 捕获异常并抛出BeanCreationExceptionthrow new BeanCreationException(mbd ! null ? mbd.getResourceDescription() : null, beanName, Invocation of init method failed, var6);}// 如果Bean定义不是合成的则应用初始化之后的Bean后置处理器if (mbd null || !mbd.isSynthetic()) {wrappedBean this.applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);}// 返回初始化后的Bean实例return wrappedBean; }// 调用Aware接口的相关方法 private void invokeAwareMethods(String beanName, Object bean) {// 如果Bean实现了Aware接口则调用相关的方法if (bean instanceof Aware) {// 如果Bean实现了BeanNameAware接口则调用setBeanName方法设置Bean名称if (bean instanceof BeanNameAware) {((BeanNameAware)bean).setBeanName(beanName);}// 如果Bean实现了BeanClassLoaderAware接口则调用setBeanClassLoader方法设置Bean类加载器if (bean instanceof BeanClassLoaderAware) {ClassLoader bcl this.getBeanClassLoader();if (bcl ! null) {((BeanClassLoaderAware)bean).setBeanClassLoader(bcl);}}// 如果Bean实现了BeanFactoryAware接口则调用setBeanFactory方法设置BeanFactoryif (bean instanceof BeanFactoryAware) {((BeanFactoryAware)bean).setBeanFactory(this);}} }这段代码主要负责Bean的初始化过程包括调用Aware接口的相关方法、应用初始化前后的Bean后置处理器以及调用初始化方法。其中invokeAwareMethods方法用于调用Bean实现了Aware接口的相关方法而initializeBean方法则包含了整个Bean初始化过程的核心逻辑。  initializeBean方法首先检查是否有安全管理器如果有则使用特权执行否则直接调用invokeAwareMethods方法。在调用Aware接口的相关方法后应用初始化之前的Bean后置处理器。接着调用Bean的初始化方法。如果初始化过程中出现异常则捕获异常并抛出BeanCreationException。最后应用初始化之后的Bean后置处理器并返回初始化后的Bean实例。 这一段代码展示了Spring容器在初始化Bean时的核心流程确保了Bean能够正确地完成初始化过程。 我们通过代码再来演示一下 package com.example.springtest.beanlife;import com.example.springtest.scope.Dog; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeanWrapperImpl; import org.springframework.beans.factory.BeanNameAware; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component;import javax.annotation.PostConstruct; import javax.annotation.PreDestroy;Slf4j Component public class BeanLifeComponent implements BeanNameAware {private Dog dog;public BeanLifeComponent() {log.info(执行构造函数……);}Autowiredpublic void setDog(Dog dog) {this.dog dog;log.info(执行setter方法……);}PostConstructpublic void init(){log.info(执行init方法……);}public void use(){log.info(执行use方法……);}PreDestroypublic void destroy(){log.info(执行destroy方法……);}Overridepublic void setBeanName(String s) {log.info(执行setBeanName方法……s);} }Spring Boot自动配置 Spring Boot的自动配置是Spring Boot框架提供的一项特性它在Spring容器启动后会自动加载一些配置类和Bean对象到IoC容器中无需手动声明从而简化了开发流程减少了繁琐的配置操作。 具体来说Spring Boot的自动配置是通过扫描classpath下的各种配置类和标记了特定注解的类来实现的。这些配置类通常位于依赖jar包中其中可能包含了一些默认的配置信息、Bean定义以及各种组件。Spring Boot会自动识别这些配置类并根据特定的规则将它们加载到Spring IoC容器中。 在加载这些配置类时Spring Boot会根据条件判断是否需要应用这些配置。例如它会检查当前项目中的其他配置、环境变量、系统属性等来决定是否应用某个配置类。这样一来开发者可以根据自己的需求对自动配置进行定制从而实现更灵活和个性化的配置。 总的来说Spring Boot的自动配置使得开发者可以更加专注于业务逻辑的实现而无需过多关注底层的配置细节极大地提高了开发效率和代码质量。 我们了解主要分以下两个方面 1. Spring 是如何把对象加载到SpringIoC容器中的 2. SpringBoot 是如何实现的 Spring 加载Bean 问题描述 需求使用Spring管理第三方的jar包配置。 引入第三方的包其实就是在该项目下引⼊第三方的代码。我们采⽤在该项目下创建不同的目录来模拟第三方的代码引入。 数据准备 第三方文件代码 写测试代码 运行程序 观察日志 在我们的测试类 ConfigDemoTest 中已经使用了 SpringBootTest 注解来加载 Spring 上下文这通常可以自动配置并加载所有的 bean。但是我们收到了一个错误说明找不到 ConfigDemo 类型的 bean。 这可能是由于 Spring 上下文没有正确加载 ConfigDemo 类或者 ConfigDemo 类没有被正确识别为一个 bean。在这种情况下我们需要确保以下几点 1、ConfigDemo 类被正确标记为一个 bean。我们已经在 ConfigDemo 类上使用了 Component 注解这是正确的。但是确保这个注解被正确导入…… 但是我们明明加了Component了。 2、ConfigDemo 类所在的包被正确扫描。在 Spring Boot 项目中通常会将 SpringBootApplication 注解添加到启动类上以确保所有包都被正确扫描。如果 ConfigDemo 类不在启动类所在的包或其子包中我们可能需要在启动类上添加 ComponentScan 注解来指定要扫描的包。 我们需要确保 ConfigDemo 类所在的包在测试类的包或其父包下。否则ConfigDemo 类可能不会被扫描到。如果我们使用的是自定义配置文件来配置 bean则需要确保配置文件被正确加载。在 SpringBootTest 注解中可以使用 properties 参数来指定要加载的配置文件。 检查以上几点只能猜测是第二个原因了。 原因分析 Spring通过五大注解和 Bean 注解可以帮助我们把Bean加载到SpringIoC容器中但是以上有个前提就是这些注解类需要和SpringBoot启动类在同一个项目下即 SpringBootApplication 标注的类也就是SpringBoot项⽬的启动类。 当我们引入第三方的Jar包时第三方的Jar代码目录肯定不在启动类的目录下。 此时我们如何告诉Spring帮忙管理这些Bean呢? 解决方案 当我们引入第三方的 jar 包时其代码目录通常不会与 Spring Boot 项目的启动类在同一个目录下。为了让 Spring 能够扫描到这些第三方 jar 包中的组件并加载到 Spring IoC 容器中我们需要采取一些解决方案。常见的解决方案有两种 ComponentScan 组件扫描 在 Spring Boot 项目中通常会有一个主启动类该类上会使用 SpringBootApplication 注解进行标注。这个注解会自动进行组件扫描并将其加载到 Spring IoC 容器中。除了主启动类之外其他需要被 Spring 管理的组件也可以使用 ComponentScan 注解或者其他方式指定扫描的包路径以确保它们被 Spring 框架扫描到并加载到 IoC 容器中。 Import 导入 使用 Import 注解可以导入其他的配置类或者普通的 Java 类被导入的类会被 Spring 加载到 IoC 容器中。通过在主配置类上使用 Import 注解导入需要加载的类可以让 Spring 加载这些类并管理它们。 我们通过代码来看如何解决 ComponentScan 通过 ComponentScan 注解指定Spring扫描路径。 此时我们已经在 SpringTest2Application 类中使用了 ComponentScan(com.example) 来指定要扫描的包及其子包。因此理论上来说Spring 应该能够扫描到 com.example.config 包中的 ConfigDemo 类。 然而可能还会遇到问题这是由于测试类 ConfigDemoTest 没有指定要加载的 Spring Boot 配置类所致。由于 ConfigDemoTest 测试类位于一个不是 Spring Boot 主配置类的地方Spring Boot 并不知道应该加载哪个配置类来创建应用程序上下文。这可能导致无法自动扫描到 ConfigDemo 类型的 bean。 为了解决这个问题我们可以使用 SpringBootTest 注解的 classes 参数来显式指定要加载的配置类。 例如我们可以在 SpringBootTest 注解中添加 classes SpringTest2Application.class以告诉 Spring Boot 在测试时使用 SpringTest2Application 主配置类。 现在运行 除此之外ComponentScan 注解允许我们指定要扫描的多个包可以通过传递一个包含包名的字符串数组来实现。 例如我们可以这样使用 ComponentScan 注解来同时扫描 com.bite.autoconfig 和 com.example.demo 包及其子包ComponentScan({com.bite.autoconfig, com.example.demo}) 这样设置后Spring 将会扫描这两个指定的包及其子包以查找带有 Component 或其他注解的组件并将它们注册到 Spring IoC 容器中。这样做可以确保我们的应用程序中的所有相关组件都会被正确地扫描和加载。 Spring 是否使用了这种方式呢? 非常明显Spring Boot 并没有采用这种方式来管理第三方依赖的配置。(因为我们引⼊第三方框架时没有加扫描路径。比如mybatis)。如果每次引入一个第三方依赖都需要手动配置扫描路径将会非常繁琐。Spring Boot 采用了自动配置的方式来简化开发流程它会根据类路径下的依赖和条件自动配置 Spring应用程序。对于大多数常用的第三方库和框架Spring Boot 已经提供了自动配置无需手动干预。 具体来说 Spring Boot 采用了自动配置的方式来管理第三方依赖而不是使用 ComponentScan 这种方式。自动配置是通过在 Spring Boot 启动类上使用 SpringBootApplication 注解来实现的。这个注解包含了多个注解的功能其中包括了 ComponentScan 注解。 当我们引入第三方依赖时Spring Boot 会根据依赖的类路径自动检测并配置相应的组件。例如当引入 MyBatis 依赖时Spring Boot 会自动检测到 MyBatis 相关的配置类并将其集成到应用程序中。 因此我们无需手动配置扫描路径或使用 ComponentScan 注解来扫描第三方依赖的包。这种自动配置的方式大大简化了开发流程并使得应用程序的配置更加简洁和易于维护。 Import Import 注解主要有以下几种形式 导入类 可以直接将一个或多个类导入到当前的配置类中。这些类可以是普通的配置类、普通的 Bean 类或者其他注解类。 Import({MyConfiguration.class, MyBean.class})导入 ImportSelector 接口实现类 ImportSelector 接口允许我们根据条件动态地选择要导入的配置类。通过实现 ImportSelector 接口我们可以根据条件返回需要导入的配置类的全限定名数组。 public class MyImportSelector implements ImportSelector {Overridepublic String[] selectImports(AnnotationMetadata importingClassMetadata) {// 根据条件动态选择要导入的配置类if (someCondition) {return new String[]{MyConfiguration1.class.getName()};} else {return new String[]{MyConfiguration2.class.getName()};}} }然后需要在配置类中使用 Import 注解导入 ImportSelector 接口实现类。 Import(MyImportSelector.class)导入类 尽管 Import 注解提供了一种动态导入类的方式但它在实际使用中可能会显得繁琐尤其是在需要导入多个类或根据条件动态选择导入类时。因此虽然 Import 注解是 Spring 框架提供的强大功能之一但 Spring Boot 并没有采用这种方式来管理第三方依赖的配置而是选择了更简洁、更智能的自动配置方式来简化开发流程。 导入ImportSelector接口实现类 ImportSelector 接口实现类 package com.example.springtest2;import org.springframework.context.annotation.ImportSelector; import org.springframework.core.type.AnnotationMetadata;import java.util.function.Predicate;public class MySelect implements ImportSelector {Overridepublic String[] selectImports(AnnotationMetadata importingClassMetadata){//需要导入的全限定类名return new String[]{com.example.config.ConfigDemo};} }启动类  可以看到, 我们确实也采用这种方式导人第三方依赖提供的Bean。 但是使用导入类、实现 ImportSelector 接口的类、以及 ComponentScan 注解等方式存在一个明显问题。这些方式需要使用者明确地知道第三方依赖中存在哪些Bean对象或配置类并且手动进行相应的配置。如果使用者在配置过程中漏掉了其中的某些Bean可能会导致项目出现严重的问题或者故障。 这对程序员来说是不友好的因为他们需要花费额外的精力去了解第三方库中提供的所有组件并且手动地配置它们。这增加了出错的风险也降低了开发的效率。 依赖中有哪些Bean使用时需要配置哪些bean第三方依赖最清楚。那能否由第三方依赖来做这件事呢? 通常情况下第三方依赖会提供一个专门的注解以 EnableXxxx 开头通过这个注解来帮助使用者将第三方依赖中的相关组件自动配置到Spring应用程序中。这个注解内部可能会使用 Import 注解来导入需要的类或配置类从而简化了配置的过程让程序员不需要手动管理第三方依赖中的Bean。 通过这种方式第三方依赖可以清楚地告诉使用者需要哪些Bean以及如何配置这些Bean使得使用者不需要深入了解第三方库的具体细节从而降低了出错的可能性提高了开发效率。 第三方依赖提供注解 import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;Target(ElementType.TYPE) Retention(RetentionPolicy.RUNTIME) Import(MySelect.class)public interface EnableConfig { }注解中封装 Import 注解导入 MySelector.class 在启动类上使用第三方提供的注解 SpringBoot原理分析 源码阅读 SpringBoot 是如何帮助我们做的呢? ⼀切的起源自SpringBoot的启动类 定义一个注解 SpringBootApplication用于标识 Spring Boot 应用程序的主类。该注解包含了 SpringBootConfiguration、EnableAutoConfiguration 和 ComponentScan 注解并且提供了一些属性用于配置自动配置和组件扫描。 // 声明一个注解用于标注 Spring Boot 应用的主类 package org.springframework.boot.autoconfigure;import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.context.TypeExcludeFilter; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.FilterType; import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.core.annotation.AliasFor;Target({ElementType.TYPE}) // 该注解可以作用于类上 Retention(RetentionPolicy.RUNTIME) // 注解信息保留到运行时 Documented // 注解包含在 Javadoc 中 Inherited // 允许子类继承父类的注解 SpringBootConfiguration // 该注解标注当前类是 Spring Boot 的配置类 EnableAutoConfiguration // 启用自动配置 ComponentScan(excludeFilters {Filter(type FilterType.CUSTOM,classes {TypeExcludeFilter.class} ), Filter(type FilterType.CUSTOM,classes {AutoConfigurationExcludeFilter.class} )} ) public interface SpringBootApplication {AliasFor(annotation EnableAutoConfiguration.class)Class?[] exclude() default {}; // 排除自动配置的类AliasFor(annotation EnableAutoConfiguration.class)String[] excludeName() default {}; // 排除自动配置的类的名称AliasFor(annotation ComponentScan.class,attribute basePackages)String[] scanBasePackages() default {}; // 扫描的基础包路径AliasFor(annotation ComponentScan.class,attribute basePackageClasses)Class?[] scanBasePackageClasses() default {}; // 扫描的基础包路径下的类AliasFor(annotation ComponentScan.class,attribute nameGenerator)Class? extends BeanNameGenerator nameGenerator() default BeanNameGenerator.class; // 生成 Bean 名称的策略类默认为 BeanNameGeneratorAliasFor(annotation Configuration.class)boolean proxyBeanMethods() default true; // 是否启用代理 Bean 方法默认为 true }SpringBootApplication 是一个组合注解注解中包含了:  1、元注解 元注解是一种特殊类型的注解用于对其他注解进行注解。在Java开发工具包JDK中有四个标准的元注解也称为meta-annotation分别是Target、Retention、Documented和Inherited。 Target用于描述被修饰的注解可以应用的范围。这意味着它指定了注解可以放置在哪些元素上例如类、方法、字段等。通过指定不同的ElementType参数可以限制注解的使用范围从而确保注解的正确使用。 Retention用于描述注解被保留的时间范围。这决定了注解的生命周期即它在什么时候会被丢弃。有三种保留策略 RetentionPolicy.SOURCE编译器将会丢弃该注解它不会包含在编译后的类文件中。RetentionPolicy.CLASS注解将会被包含在编译后的类文件中但在运行时不可获取。RetentionPolicy.RUNTIME注解将被包含在类文件中并且在运行时可以通过反射机制获取到。 Documented用于描述是否在使用Java文档工具如javadoc为类生成文档时保留其注解信息。如果一个注解被Documented修饰那么在生成文档时这个注解会被包含进去使得开发者能够清晰地了解类的注解信息。 Inherited使被它修饰的注解具有继承性。如果某个类使用了被Inherited修饰的注解则其子类将自动具有该注解。这意味着如果一个类被标注为某个注解那么它的子类也会继承这个注解除非子类显式地覆盖了这个注解。这对于定义一些通用的行为或特征并让子类继承这些行为或特征是非常有用的。 2. SpringBootConfiguration 这段代码定义了一个自定义的注解SpringBootConfiguration它实际上是对Configuration注解的封装并添加了一些额外的功能。 Indexed这是一个额外的注解用于加速应用程序启动但在这段描述中我们不需要关注该注解的作用。 AliasFor(annotation Configuration.class)这是一个别名注解指定了该注解的属性proxyBeanMethods与Configuration注解的proxyBeanMethods属性具有相同的语义默认为true。这意味着通过SpringBootConfiguration注解标注的类也会被视为Configuration并且proxyBeanMethods属性默认为true。 总之SpringBootConfiguration注解是对Configuration的封装并且提供了一些额外的功能使得在Spring Boot应用中更加方便地使用。 3.EnableAutoConfiguration (开启自动配置) EnableAutoConfiguration注解是Spring Boot中开启自动配置的核心注解它通过导入自动配置类来简化Spring应用程序的配置。 AutoConfigurationPackage这是一个额外的注解用于指示Spring Boot应该在包的根目录下搜索Configuration类并将它们注册为Bean。 Import({AutoConfigurationImportSelector.class})用于导入其他配置类。在这里导入了AutoConfigurationImportSelector类该类是用于选择自动配置类并将它们导入Spring容器的重要组件。 这个注解非常重要我们一会儿会详细讲解。 4.ComponentScan (包扫描) 它用于告诉Spring在哪些包下扫描组件并且可以通过一些参数进行定制。 Repeatable(ComponentScans.class)这个注解表明ComponentScan是可重复的可以在同一个类上多次使用ComponentScan注解。 value()、basePackages()、basePackageClasses()这些属性用于指定要扫描的特定包。如果没有定义特定的包则从声明该注解的类的包开始扫描。basePackages和value是别名可以互相替代。basePackageClasses可以指定一些特定的类Spring将扫描这些类所在的包。 nameGenerator()用于指定生成Bean名称的策略默认为BeanNameGenerator.class。 scopeResolver()用于指定作用域解析器的类默认为AnnotationScopeMetadataResolver.class。 scopedProxy()用于指定作用域代理模式默认为ScopedProxyMode.DEFAULT。 resourcePattern()用于指定要扫描的资源模式默认为**/*.class。 useDefaultFilters()用于指定是否使用默认的过滤器默认为true。 includeFilters()、excludeFilters()用于自定义过滤器通常用于包含或排除一些类、注解等。 lazyInit()用于指定是否使用懒加载初始化默认为false。 ComponentScan注解的主要作用是告诉Spring在哪些包下扫描组件以及如何处理这些组件。通过使用该注解我们可以方便地配置Spring应用程序的组件扫描行为。注意如果没有参数默认值为该注解所在类的路径。 EnableAutoConfiguration 详解 我们来一起看一下 EnableAutoConfiguration 注解的实现 /*** 这个方法根据提供的注解元数据动态选择并导入自动配置类。** param annotationMetadata 提供有关注解组件上的注解信息的注解元数据。* return 一个字符串数组表示所选自动配置类的类名*/ public String[] selectImports(AnnotationMetadata annotationMetadata) {// 根据注解元数据检查是否启用了自动配置if (!this.isEnabled(annotationMetadata)) {// 如果未启用自动配置则返回一个空的导入数组return NO_IMPORTS;} else {// 如果启用了自动配置则获取AutoConfigurationEntryAutoConfigurationEntry autoConfigurationEntry this.getAutoConfigurationEntry(annotationMetadata);// 从AutoConfigurationEntry获取配置并将其转换为字符串数组return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());} }这个方法负责根据提供的注解元数据动态选择并导入自动配置类。首先它通过调用 isEnabled 方法检查自动配置是否已启用。如果启用了自动配置则使用 getAutoConfigurationEntry 方法检索 AutoConfigurationEntry。然后它从 AutoConfigurationEntry 获取配置并将其转换为表示所选自动配置类的类名的字符串数组。最后它返回这个类名数组。 从上面的代码可以发现主方法就是getAutoConfigurationEntry 方法 /*** 根据提供的注解元数据获取自动配置条目。** param annotationMetadata 提供有关注解组件上的注解信息的注解元数据。* return AutoConfigurationEntry表示自动配置条目*/ protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {// 如果未启用自动配置则返回一个空的自动配置条目if (!this.isEnabled(annotationMetadata)) {return EMPTY_ENTRY;} else {// 获取注解属性AnnotationAttributes attributes this.getAttributes(annotationMetadata);// 获取候选配置类列表ListString configurations this.getCandidateConfigurations(annotationMetadata, attributes);// 去除重复的配置类configurations this.removeDuplicates(configurations);// 获取排除的配置类列表SetString exclusions this.getExclusions(annotationMetadata, attributes);// 检查排除的配置类是否存在重复this.checkExcludedClasses(configurations, exclusions);// 从候选配置类中移除排除的配置类configurations.removeAll(exclusions);// 应用配置类过滤器configurations this.getConfigurationClassFilter().filter(configurations);// 触发自动配置导入事件this.fireAutoConfigurationImportEvents(configurations, exclusions);// 返回新的自动配置条目return new AutoConfigurationEntry(configurations, exclusions);} }这个方法根据提供的注解元数据获取自动配置条目。首先它检查自动配置是否已启用。如果未启用则返回一个空的自动配置条目。接着它获取注解的属性并使用这些属性获取候选的配置类列表。然后它去除重复的配置类并获取排除的配置类列表。在检查排除的配置类是否存在重复后它从候选配置类中移除排除的配置类。接下来它应用配置类过滤器将最终的配置类列表返回。最后它触发自动配置导入事件并返回新的自动配置条目。 从上面的代码可以发现主方法就是getCandidateConfigurations 方法 /*** 获取候选的自动配置类列表。** param metadata 注解元数据包含有关注解的信息* param attributes 注解属性* return 包含候选自动配置类的列表*/ protected ListString getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {// 从META-INF/spring.factories加载自动配置类列表ListString configurations SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());// 如果找不到任何自动配置类则抛出异常Assert.notEmpty(configurations, No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.);return configurations; }这个方法用于获取候选的自动配置类列表。它通过SpringFactoriesLoader从META-INF/spring.factories文件中加载自动配置类的名称列表。如果找不到任何自动配置类则会抛出异常。最后它返回包含候选自动配置类的列表。 那么我们点开一个看看 看样子都是一些.class文件 在很多文件里面我们还会发现名字中包含了“Conditional”注解比如 /*** 当资源链启用时才生效的条件注解* 用于在运行时根据资源链是否启用来决定是否应该创建一个bean或者配置一个bean*/ Target({ ElementType.TYPE, ElementType.METHOD }) Retention(RetentionPolicy.RUNTIME) Documented Conditional({ OnEnabledResourceChainCondition.class }) public interface ConditionalOnEnabledResourceChain { }它们其实都是来自于 “Conditional”注解 /*** 用于在运行时根据条件决定是否应该创建一个bean或者配置一个bean* Spring在处理bean定义时会考虑是否满足Conditional注解的条件* 如果满足条件则会创建或者配置该bean否则将会跳过该bean*/ Target({ ElementType.TYPE, ElementType.METHOD }) Retention(RetentionPolicy.RUNTIME) Documented public interface Conditional {/*** 返回用于评估条件的类的列表* 如果指定的条件类的所有条件都返回true则应创建或配置该bean* 否则将跳过bean的创建或配置*/Class? extends Condition[] value(); }综上Import({AutoConfigurationImportSelector.class})这一行代码的作用就是通过导入AutoConfigurationImportSelector类来选择自动配置类的注解用于在配置类中通过导入指定的选择器类来动态地选择自动配置类。 我们还剩下一个注解 点进去往下扒拉 /*** 内部静态类Registrar实现了ImportBeanDefinitionRegistrar和DeterminableImports接口* 用于注册Bean定义和确定导入项*/ static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {Registrar() {}/*** 注册Bean定义的方法将自动配置包注册到注册表中* param metadata 注解元数据* param registry Bean定义注册表*/public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {// 使用PackageImports类获取包名并将其注册到自动配置包中AutoConfigurationPackages.register(registry, (String[])(new PackageImports(metadata)).getPackageNames().toArray(new String[0]));}/*** 确定导入项的方法* param metadata 注解元数据* return 包含PackageImports类的集合*/public SetObject determineImports(AnnotationMetadata metadata) {return Collections.singleton(new PackageImports(metadata));} }这段代码的目的是将特定的包名注册到Spring Boot的自动配置包中以便Spring能够自动扫描这些包中的类并将它们实例化为Bean以供应用程序使用。 具体来说 PackageImports(metadata)这行代码创建了一个PackageImports对象该对象从传入的metadata中获取了与注解相关的信息然后通过这些信息确定了需要注册到自动配置包的包名。new PackageImports(metadata).getPackageNames()这部分代码调用了PackageImports对象的getPackageNames()方法以获取到需要注册到自动配置包中的包名集合。.toArray(new String[0])将获取到的包名集合转换为数组。AutoConfigurationPackages.register(registry, ...)这一行代码调用了AutoConfigurationPackages类的register方法该方法接受一个BeanDefinitionRegistry对象和一个包名数组作为参数。在Spring Boot内部AutoConfigurationPackages类负责管理自动配置包并将注册的包名添加到自动配置包中。这样一来Spring在启动时就会扫描这些包寻找带有特定注解的类并将它们实例化为Bean。 综上所述我们可以来总结一下 当我们使用EnableAutoConfiguration注解时它实际上是启动了Spring Boot的自动配置功能其实现原理涉及几个关键部分 Import({AutoConfigurationImportSelector.class}) 通过Import注解导入了实现了ImportSelector接口的AutoConfigurationImportSelector类。ImportSelector接口的实现类可以根据条件动态地选择需要导入的配置类。 AutoConfigurationImportSelector AutoConfigurationImportSelector类的selectImports()方法负责选择需要导入的配置类。底层调用了getAutoConfigurationEntry()方法获取可自动配置的配置类信息集合。 getAutoConfigurationEntry()方法 getAutoConfigurationEntry()方法通过调用getCandidateConfigurations()方法获取在配置文件中配置的所有自动配置类的集合。 getCandidateConfigurations()方法 getCandidateConfigurations()方法获取所有基于META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件和META-INF/spring.factories文件中配置类的集合。这些配置文件通常包含了许多第三方依赖的自动配置类。 动态加载自动配置类 在加载自动配置类时并不是将所有的配置全部加载进来而是通过Conditional等注解的判断进行动态加载。Conditional是Spring底层注解根据不同的条件进行不同的条件判断如果满足指定的条件配置类里边的配置才会生效。 AutoConfigurationPackage注解 AutoConfigurationPackage注解将启动类所在的包下面所有的组件都扫描注册到Spring容器中。这样Spring容器就能够扫描到启动类所在包及其子包中的所有组件并将其注册为Spring Bean。 总的来说EnableAutoConfiguration注解启用了Spring Boot的自动配置功能它通过导入AutoConfigurationImportSelector类和AutoConfigurationPackage注解动态加载自动配置类并根据条件进行判断和加载最终完成Spring应用程序的自动配置。 SpringBoot ⾃动配置原理的大概流程如下 当Spring Boot程序启动时会自动加载配置文件中所定义的配置类并通过Import注解将这些配置类全部加载到Spring的IOC容器中交由IOC容器管理。这样做的目的是为了简化Spring应用的配置和开发过程让开发者专注于业务逻辑的实现而不必过多关注框架的配置。 具体来说Spring Boot的自动配置原理可以概括如下 启动过程当Spring Boot应用启动时会自动扫描classpath下的META-INF/spring.factories文件该文件中列出了所有自动配置类的全限定名。自动配置类Spring Boot通过这些自动配置类来自动配置应用的各种组件比如数据源、JPA、Web容器等等。这些自动配置类通过注解Configuration标识告诉Spring这是一个配置类。条件装配自动配置类中的各个Bean的创建是有条件的Spring Boot利用条件注解如ConditionalOnClass、ConditionalOnMissingBean等来根据类路径、Bean是否存在等条件来决定是否创建某个Bean。加载配置Spring Boot会加载应用的配置文件application.properties或application.yml并将这些配置信息注入到相应的Bean中。IOC容器管理最终这些自动配置类中的Bean会被添加到Spring的IOC容器中进行管理。开发者可以通过Autowired注解或者其他方式来获取并使用这些Bean。 总之Spring Boot的自动配置机制大大简化了Spring应用的开发和部署流程我们只需要遵循约定大于配置的原则即可快速搭建出一个功能完善的Spring应用。
http://www.w-s-a.com/news/304071/

相关文章:

  • a5网站建设如果建设淘宝导购网站
  • html5响应式网站开发教程在国内做跨境电商怎么上外国网站
  • win7配置不能运行wordpress关键词快速优化排名软件
  • 餐饮公司最好的网站建设手机网站 搜索优化 百度
  • 17网站一起做网批做服装团购网站
  • 广州网站制作知名企业网站搭建品牌
  • 如何去除网站外链个人网页制作全过程
  • 保洁公司网站怎么做科技设计网站有哪些内容
  • 建设厅网站查询网页设计好就业吗
  • 惠东县网站建设wordpress 如何回到初始
  • 如何让公司网站网站转备案
  • 获得网站所有关键字北京网站建设116net
  • 铜陵电子商务网站建设做龙之向导网站有用吗
  • 购物网站制作费用沧州新华区
  • 信宜网站设计公司在线购物商城系统
  • 网站维护是什么样如何制作网站教程视频讲解
  • 网站建设网络推广代理公司wordpress图片防盗链
  • 网站备案关站沈阳男科医院哪家好点
  • 王者荣耀网站建设的步骤网站页面用什么软件做
  • 典型网站开发的流程房屋装修效果图三室一厅
  • 制作微网站多少钱阿里巴巴做网站的电话号码
  • 风铃建站模板安卓手机软件开发外包
  • 深圳市住房和建设局门户网站域名转移影响网站访问吗
  • 做母婴网站赚钱汕头百姓网
  • 那个网站建设好动漫制作技术升本可以升什么专业
  • 网站建设企业响应式网站模板广西建设部投诉网站
  • app营销的特点wordpress优化方案
  • 静安网站建设公司如何编辑wordpress
  • 做网站的职位叫什么问题常州金坛网站建设
  • 保健品网站模板用jsp做的网站前后端交互