2026/1/12 14:43:42
网站建设
项目流程
建站宝盒源码,seo公司如何优化,wordpress 子域名建站,四川通管局网站概念
Bean 代指的就是那些被 IoC 容器所管理的对象。
下图简单地展示了 IoC 容器如何使用配置元数据来管理对象。 将一个类声明为bean的注解
Component#xff1a;通用的注解#xff0c;可标注任意类为 Spring 组件。如果一个 Bean 不知道属于哪个层#xff0c;可以使用C…概念Bean 代指的就是那些被 IoC 容器所管理的对象。下图简单地展示了 IoC 容器如何使用配置元数据来管理对象。将一个类声明为bean的注解Component通用的注解可标注任意类为Spring组件。如果一个 Bean 不知道属于哪个层可以使用Component注解标注。Repository: 对应持久层即 Dao 层主要用于数据库相关操作。Service: 对应服务层主要涉及一些复杂的逻辑需要用到 Dao 层。Controller: 对应 Spring MVC 控制层主要用于接受用户请求并调用Service层返回数据给前端页面。Component和Bean的区别是什么Component注解作用于类而Bean注解作用于方法。Component通常是通过类路径扫描来自动侦测以及自动装配到 Spring 容器中我们可以使用ComponentScan注解定义要扫描的路径从中找出标识了需要装配的类自动装配到 Spring 的 bean 容器中。Bean注解通常是我们在标有该注解的方法中定义产生这个 bean,Bean告诉了 Spring 这是某个类的实例当我需要用它的时候还给我。Bean注解比Component注解的自定义性更强而且很多地方我们只能通过Bean注解来注册 bean。比如当我们引用第三方库中的类需要装配到Spring容器时则只能通过Bean来实现。Configuration public class AppConfig { Bean public TransferService transferService() { return new TransferServiceImpl(); } }注入bean的方法注入bean的方式有哪些依赖注入 (Dependency Injection, DI) 的常见方式构造函数注入通过类的构造函数来注入依赖项。Setter 注入通过类的 Setter 方法来注入依赖项。Field字段 注入直接在类的字段上使用注解如Autowired或Resource来注入依赖项。Spring 官方推荐构造函数注入这种注入方式的优势如下依赖完整性确保所有必需依赖在对象创建时就被注入避免了空指针异常的风险。不可变性有助于创建不可变对象提高了线程安全性。初始化保证组件在使用前已完全初始化减少了潜在的错误。测试便利性在单元测试中可以直接通过构造函数传入模拟的依赖项而不必依赖 Spring 容器进行注入。Autowired和Resource的区别是什么AnnotationPackageSourceAutowiredorg.springframework.bean.factorySpring 2.5Resourcejavax.annotationJava JSR-250Injectjavax.injectJava JSR-330Autowired是 Spring 提供的注解Resource是 JDK 提供的注解。Autowired默认的注入方式为byType根据类型进行匹配Resource默认注入方式为byName根据名称进行匹配。当一个接口存在多个实现类的情况下Autowired和Resource都需要通过名称才能正确匹配到对应的 Bean。Autowired可以通过Qualifier注解来显式指定名称Resource可以通过name属性来显式指定名称。Autowired支持在构造函数、方法、字段和参数上使用。Resource主要用于字段和方法上的注入不支持在构造函数或参数上使用。bean的作用域Spring 中 Bean 的作用域通常有下面几种singleton: IoC 容器中只有唯一的 bean 实例。Spring 中的 bean 默认都是单例的是对单例设计模式的应用。Service Scope(singleton) public class UserServiceImpl implements UserService { }prototype: 每次获取都会创建一个新的 bean 实例。也就是说连续getBean()两次得到的是不同的 Bean 实例。补充request仅 Web 应用可用: 每一次 HTTP 请求都会产生一个新的 bean请求 bean该 bean 仅在当前 HTTP request 内有效。session仅 Web 应用可用 : 每一次来自新 session 的 HTTP 请求都会产生一个新的 bean会话 bean该 bean 仅在当前 HTTP session 内有效。application/global-session仅 Web 应用可用每个 Web 应用在启动时创建一个 Bean应用 Bean该 bean 仅在当前应用启动时间内有效。websocket仅 Web 应用可用每一次 WebSocket 会话产生一个新的 bean。配置bean的作用域bean id... class... scopesingleton/beanBean Scope(value ConfigurableBeanFactory.SCOPE_PROTOTYPE) public Person personPrototype() { return new Person(); }bean是线程安全的吗Spring 框架中的 Bean 是否线程安全取决于其作用域和状态。我们这里以最常用的两种作用域 prototype 和 singleton 为例介绍。几乎所有场景的 Bean 作用域都是使用默认的 singleton 重点关注 singleton 作用域即可。prototype作用域下每次获取都会创建一个新的 bean 实例不存在资源竞争问题所以不存在线程安全问题。singleton作用域下IoC 容器中只有唯一的 bean 实例可能会存在资源竞争问题取决于 Bean 是否有状态。如果这个 bean 是有状态的话那就存在线程安全问题有状态 Bean 是指包含可变的成员变量的对象。什么叫做bean有状态就是看当前的成员变量是否可以被修改可以被修改的就是有状态bean线程不安全无法被修改的就是无状态bean线程安全。大部分 Bean 实际都是无状态没有定义可变的成员变量的比如 Dao、Service这种情况下 Bean 是线程安全的。// 定义了一个购物车类其中包含一个保存用户的购物车里商品的 List Component public class ShoppingCart { private ListString items new ArrayList(); public void addItem(String item) { items.add(item); } public ListString getItems() { return items; } }// 定义了一个用户服务它仅包含业务逻辑而不保存任何状态没有定义可变的成员变量。 Component public class UserService { public User findUserById(Long id) { //... } //... }对于有状态单例 Bean 的线程安全问题常见的三种解决办法是避免可变成员变量: 尽量设计 Bean 为无状态。使用ThreadLocal: 将可变成员变量保存在ThreadLocal中确保线程独立。使用同步机制: 利用synchronized或ReentrantLock来进行同步控制确保线程安全。public class UserThreadLocal { private UserThreadLocal() {} private static final ThreadLocalSysUser LOCAL ThreadLocal.withInitial(() - null); public static void put(SysUser sysUser) { LOCAL.set(sysUser); } public static SysUser get() { return LOCAL.get(); } public static void remove() { LOCAL.remove(); } }bean的生命周期Spring启动查找并加载需要被Spring管理的bean进行Bean的实例化Bean实例化后对将Bean的引入和值注入到Bean的属性中如果Bean实现了BeanNameAware接口的话Spring将Bean的Id传递给setBeanName()方法如果Bean实现了BeanFactoryAware接口的话Spring将调用setBeanFactory()方法将BeanFactory容器实例传入如果Bean实现了ApplicationContextAware接口的话Spring将调用Bean的setApplicationContext()方法将bean所在应用上下文引用传入进来。如果Bean实现了BeanPostProcessor接口Spring就将调用他们的postProcessBeforeInitialization()方法。如果Bean 实现了InitializingBean接口Spring将调用他们的afterPropertiesSet()方法。类似的如果bean使用init-method声明了初始化方法该方法也会被调用如果Bean 实现了BeanPostProcessor接口Spring就将调用他们的postProcessAfterInitialization()方法。此时Bean已经准备就绪可以被应用程序使用了。他们将一直驻留在应用上下文中直到应用上下文被销毁。如果bean实现了DisposableBean接口Spring将调用它的destroy()接口方法同样如果bean使用了destroy-method声明销毁方法该方法也会被调用。如何记忆呢整体上可以简单分为四步实例化 — 属性赋值 — 初始化 — 销毁。初始化这一步涉及到的步骤比较多包含Aware接口的依赖注入、BeanPostProcessor在初始化前后的处理以及InitializingBean和init-method的初始化操作。销毁这一步会注册相关销毁回调接口最后通过DisposableBean和destory-method进行销毁。bean的循环依赖问题循环依赖是指 Bean 对象循环引用是两个或多个 Bean 之间相互持有对方的引用egCircularDependencyA → CircularDependencyB → CircularDependencyA。Component public class CircularDependencyA { Autowired private CircularDependencyB circB; } Component public class CircularDependencyB { Autowired private CircularDependencyA circA; }循环依赖问题在Spring中主要有三种情况第一种通过构造方法进行依赖注入时产生的循环依赖问题。第二种通过setter方法进行依赖注入且是在多例原型模式下产生的循环依赖问题。第三种通过setter方法进行依赖注入且是在单例模式下产生的循环依赖问题。只有【第三种方式】的循环依赖问题被Spring解决了其他两种方式在遇到循环依赖问题时Spring都会产生异常。解决方案Spring 通过 三级缓存 和 提前暴露未完全初始化的对象引用 的机制来解决单例作用域 Bean 的 setter 注入方式的循环依赖问题。// 一级缓存 /** Cache of singleton objects: bean name to bean instance. */ private final MapString, Object singletonObjects new ConcurrentHashMap(256); // 二级缓存 /** Cache of early singleton objects: bean name to bean instance. */ private final MapString, Object earlySingletonObjects new HashMap(16); // 三级缓存 /** Cache of singleton factories: bean name to ObjectFactory. */ private final MapString, ObjectFactory? singletonFactories new HashMap(16);一级缓存singletonObjects单例池缓存已经经历了完整的生命周期已经初始化完成的bean对象二级缓存earlySingletonObjects缓存早期的bean对象生命周期还没走完三级缓存singletonFactories缓存的是ObjectFactory表示对象工厂用来创建某个对象的只用两级缓存在没有 AOP 的情况下确实可以只使用一级和二级缓存来解决循环依赖问题。三级缓存解决bean循环依赖但是当涉及到 AOP 时三级缓存就显得非常重要了因为它确保了即使在 Bean 的创建过程中有多次对早期引用的请求也始终只返回同一个代理对象从而避免了同一个 Bean 有多个代理对象的问题。解决流程第一步创建 BeanA 的实例并提前暴露工厂。Spring首先调用 BeanA 的构造函数进行实例化此时得到一个原始对象尚未填充属性。紧接着Spring会将一个特殊的 ObjectFactory 工厂对象存入第三级缓存singletonFactories。这个工厂的使命是当其他Bean需要引用 BeanA 时它能动态返回当前这个半成品的 BeanA可能是原始对象也可能是为应对AOP而提前生成的代理对象。此时 BeanA 的状态是“已实例化但未初始化”像一座刚搭好钢筋骨架的大楼。第二步填充 BeanA 的属性时触发 BeanB 的创建。Spring开始为 BeanA 注入属性发现它依赖 BeanB。于是容器转向创建 BeanB同样先调用其构造函数实例化并将 BeanB 对应的 ObjectFactory 工厂存入三级缓存。至此三级缓存中同时存在 BeanA 和 BeanB 的工厂它们都代表未完成初始化的半成品。第三步Bean 属性注入时发现循环依赖。当Spring试图填充 Bean 的属性时检测到它需要注入 BeanA。此时容器启动依赖查找在一级缓存存放完整Bean中未找到 BeanA在二级缓存存放已暴露的早期引用中同样未命中最终在三级缓存中定位到 BeanA 的工厂。Spring立即调用该工厂的 getObject()方法。这个方法会执行关键决策若 BeanA 需要AOP代理则动态生成代理对象即使 BeanA 还未初始化若无需代理则直接返回原始对象。得到的这个早期引用可能是代理被放入二级缓存earlySingletonObjects同时从三级缓存清理工厂条目。最后Spring将这个早期引用注入到 BeanB 的属性中。至此BeanB 成功持有 BeanA 的引用——尽管 BeanA 此时仍是个半成品。第四步完成 BeanB 的生命周期。BeanB 获得所有依赖后Spring执行其初始化方法如postConstruct将其转化为完整可用的 Bean。随后BeanB 被提升至一级缓存singletonObjects二级和三级缓存中关于 BeanB 的临时条目均被清除。此时 BeanB 已准备就绪可被其他对象使用。第五步回溯完成 BeanA 的构建。随着 BeanB 创建完毕流程回溯到最初中断的 BeanA 属性注入环节。Spring将已完备的 BeanB 实例注入 BeanA接着执行 BeanA 的初始化方法。这里有个精妙细节若之前为 BeanA 生成过早期代理Spring会直接复用二级缓存中的代理对象作为最终Bean而非重复创建。最终完全初始化的 BeanA可能是原始对象或代理入驻一级缓存其早期引用从二级缓存移除。至此循环闭环完成两个Bean皆可用。Lazy能解决循环依赖吗Lazy用来标识类是否需要懒加载/延迟加载可以作用在类上、方法上、构造器上、方法参数上、成员变量中。如非必要尽量不要用全局懒加载。全局懒加载会让 Bean 第一次使用的时候加载会变慢并且它会延迟应用程序问题的发现当 Bean 被初始化时问题才会出现。Spring Boot 2.2 新增了全局懒加载属性开启后全局 bean 被设置为懒加载需要时再去创建。#默认false spring.main.lazy-initializationtrueSpringApplication springApplicationnew SpringApplication(Start.class); springApplication.setLazyInitialization(false); springApplication.run(args);Lazy如何解决循环依赖问题eg比如说有两个 BeanA 和 B他们之间发生了循环依赖那么 A 的构造器上添加Lazy注解之后延迟 Bean B 的实例化加载的流程如下首先 Spring 会去创建 A 的 Bean创建时需要注入 B 的属性由于在 A 上标注了Lazy注解因此 Spring 会去创建一个 B 的代理对象将这个代理对象注入到 A 中的 B 属性之后开始执行 B 的实例化、初始化在注入 B 中的 A 属性时此时 A 已经创建完毕了就可以将 A 给注入进去。从上面的加载流程可以看出Lazy解决循环依赖的关键点在于代理对象的使用。没有Lazy的情况下在 Spring 容器初始化A时会立即尝试创建B而在创建B的过程中又会尝试创建A最终导致循环依赖即无限递归最终抛出异常。使用Lazy的情况下Spring 不会立即创建B而是会注入一个B的代理对象。由于此时B仍未被真正初始化A的初始化可以顺利完成。等到A实例实际调用B的方法时代理对象才会触发B的真正初始化。Lazy能够在一定程度上打破循环依赖链允许 Spring 容器顺利地完成 Bean 的创建和注入。但这并不是一个根本性的解决方案尤其是在构造函数注入、复杂的多级依赖等场景中Lazy无法有效地解决问题。因此最佳实践仍然是尽量避免设计上的循环依赖。