一、有哪些依赖注入方式?
- 构造方法注入:通过调用类的构造方法,推荐用于强依赖(没有依赖对象就没法工作)。这也是官方推荐的注入方式。好处:依赖不可变(final修饰)、更安全。
- @Component
- public class UserService {
- private final UserDao userDao;
- // Spring 会自动注入 UserDao
- @Autowired
- public UserService(UserDao userDao) {
- this.userDao = userDao;
- }
- }
复制代码- @Component
- public class UserService {
- private UserDao userDao;
- @Autowired
- public void setUserDao(UserDao userDao) {
- this.userDao = userDao;
- }
- }
复制代码
- 字段注入:直接在字段上加@Autowired。(简单,但不利于单元测试mock,因为会依赖Spring框架)
二、Spring有哪些自动装配的方式
1、什么是自动装配
装配:把bean之间的依赖关系配置清楚
自动装配:让Spring容器自己根据规则把依赖对象诸如进去,而不是开发者手动写。这样可以减少配置,提升开发效率。
2、Spring提供了哪几种自动装配类型
- no(默认值):不自动装配,必须显式依赖(XML里面写)
- byName:根据属性名找到和Bean的id一样的Bean注入
- byType:根据属性的类型找到容器里唯一匹配的Bean注入
- constructor:根据构造方法参数的类型,去容器里找匹配的 Bean 注入。
【注意】
在 Spring Boot 和注解驱动里,主要用:
- @Autowired(默认 byType,可结合 @Qualifier 指定名字)
- @Resource(JDK自带,默认 byName,找不到再 byType)
- @Inject(JSR-330 标准注解,行为类似 @Autowired)
- // 接口
- public interface UserDao {
- void save();
- }
- // 第一个实现
- @Component("mysqlUserDao")
- public class UserDaoMysql implements UserDao {
- @Override
- public void save() {
- System.out.println("保存到 MySQL 数据库");
- }
- }
- // 第二个实现
- @Component("oracleUserDao")
- public class UserDaoOracle implements UserDao {
- @Override
- public void save() {
- System.out.println("保存到 Oracle 数据库");
- }
- }
复制代码
三、Spring中Bean的作用域有哪些
- singleton(默认):在一个Spring 容器(ApplicationContext)中,只创建一个 Bean 实例。每次 getBean() 拿到的都是同一个对象。
- prototype:每次调用 getBean(),都会新建一个对象。
- request(仅 Web 应用中有效):每个 HTTP 请求都会创建一个新的 Bean。Bean 生命周期和一次请求绑定,请求结束后 Bean 被销毁。
- session(仅 Web 应用中有效):每个 HTTP 会话(Session)只对应一个 Bean。同一个 Session 里的请求共享 Bean,不同 Session 用不同 Bean。
- @Component
- @Scope("prototype") // 或 "request" / "session"
- public class UserService {}
复制代码
- Session = 会话
- 在 Web 里,Session 表示 用户从打开浏览器访问网站,到关闭浏览器/超时退出的这一段交互过程。
四、Spring中的单例Bean会存在线程安全问题吗
Spring的单例Bean不是天然线程安全的。是否有问题,取决于Bean是否有状态。
- 无状态Bean(只做方法调用,不保存共享数据):线程安全
- 有状态Bean(持有成员变量,并且会被多个线程同时读写):线程不安全
解决办法:
- 改为多例(prototype):每次请求都新建一个实例-->不共享-->没有线程安全问题。但这样会失去单例的优势,容器管理和性能都会受影响。
- 避免在Bean中设计为无状态,方法里只用局部变量
- 使用ThreadLocal 保存状态(推荐 ✅):给每个线程准备一份独立副本,避免了线程之间的数据覆盖。
- @Service
- public class UserContextService {
- private ThreadLocal<String> currentUser = new ThreadLocal<>();
- public void setUser(String user) {
- currentUser.set(user); // 每个线程有自己独立的副本
- }
- public String getUser() {
- return currentUser.get(); // 取的就是当前线程的值
- }
- public void clear() {
- currentUser.remove(); // 防止内存泄漏(重要!)
- }
- }
复制代码
五、说说循环依赖
1、什么是循环依赖
两个或多个Bean互相依赖,形成“死循环”,只在单例下会出现。如果是prototype的话会无限套娃。
那Spring能解决哪些情况?
- 两边都是构造器注入(不支持):无法提前暴露“半成品”对象,会直接报错。
- 两边都是setter/字段注入(支持):Spring创建A会调用构造方法得到一个空对象,把A的工厂放到三级缓存。给A注入依赖时发现需要B,就去创建B。-->创建B的时候发现需要A,就去一级、二级缓存找,找不到就调用三级缓存的ObjectFactory得到一个“早期A”,把这个早期A放进二级缓存,然后注入到B。B创建完后,返回给A。完成A的初始化。这时候A、B都是成品Bean,放进一级缓存。
- 一边构造器、一边setter注入->看情况(A构造器、Bsetter):若Spring先创建A,构造器立刻需要B,但B还没创建-->报错❌。若Spring先创建B,B可以先用setter注入半成品A-->✅ 能成功。
六、Spring怎么解决循环依赖
1️⃣ 一级缓存:singletonObjects
- 存放 完全创建好的单例 Bean(成品)。
- 以后再来 getBean(),直接从这里拿。
2️⃣ 二级缓存:earlySingletonObjects
- 存放 提前曝光的单例 Bean(早期引用)。
- 这里的 Bean 已经实例化,但可能还没注入属性、没初始化。
- 如果别的 Bean 需要,可以先用它占个坑,后面再补全。
3️⃣ 三级缓存:singletonFactories
- 存放 ObjectFactory(对象工厂),可以生成早期 Bean。
- 为什么要三级?因为有些 Bean 可能需要 AOP 代理,必须等真正用到的时候才生成代理对象 → 所以放工厂。
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |
|
|
|
相关推荐
|
|