找回密码
 立即注册
首页 业界区 业界 Spring知识点(2)

Spring知识点(2)

万俟谷雪 昨天 22:25
 
一、有哪些依赖注入方式?


  • 构造方法注入:通过调用类的构造方法,推荐用于强依赖(没有依赖对象就没法工作)。这也是官方推荐的注入方式。好处:依赖不可变(final修饰)、更安全。
  1. @Component
  2. public class UserService {
  3.     private final UserDao userDao;
  4.     // Spring 会自动注入 UserDao
  5.     @Autowired
  6.     public UserService(UserDao userDao) {
  7.         this.userDao = userDao;
  8.     }
  9. }
复制代码

  • Setter注入:适合可选依赖(有无都能运行)
  1. @Component
  2. public class UserService {
  3.     private UserDao userDao;
  4.     @Autowired
  5.     public void setUserDao(UserDao userDao) {
  6.         this.userDao = userDao;
  7.     }
  8. }
复制代码

  • 字段注入:直接在字段上加@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)
  1. // 接口
  2. public interface UserDao {
  3.     void save();
  4. }
  5. // 第一个实现
  6. @Component("mysqlUserDao")
  7. public class UserDaoMysql implements UserDao {
  8.     @Override
  9.     public void save() {
  10.         System.out.println("保存到 MySQL 数据库");
  11.     }
  12. }
  13. // 第二个实现
  14. @Component("oracleUserDao")
  15. public class UserDaoOracle implements UserDao {
  16.     @Override
  17.     public void save() {
  18.         System.out.println("保存到 Oracle 数据库");
  19.     }
  20. }
复制代码
 
三、Spring中Bean的作用域有哪些


  • singleton(默认):在一个Spring 容器(ApplicationContext)中,只创建一个 Bean 实例。每次 getBean() 拿到的都是同一个对象。
  • prototype:每次调用 getBean(),都会新建一个对象。
  • request(仅 Web 应用中有效):每个 HTTP 请求都会创建一个新的 Bean。Bean 生命周期和一次请求绑定,请求结束后 Bean 被销毁。
  • session(仅 Web 应用中有效):每个 HTTP 会话(Session)只对应一个 Bean。同一个 Session 里的请求共享 Bean,不同 Session 用不同 Bean。
  1. @Component
  2. @Scope("prototype")   // 或 "request" / "session"
  3. public class UserService {}
复制代码


  • Session = 会话
  • 在 Web 里,Session 表示 用户从打开浏览器访问网站,到关闭浏览器/超时退出的这一段交互过程
四、Spring中的单例Bean会存在线程安全问题吗

  Spring的单例Bean不是天然线程安全的。是否有问题,取决于Bean是否有状态。

  • 无状态Bean(只做方法调用,不保存共享数据):线程安全
  • 有状态Bean(持有成员变量,并且会被多个线程同时读写):线程不安全
  解决办法:

  • 改为多例(prototype):每次请求都新建一个实例-->不共享-->没有线程安全问题。但这样会失去单例的优势,容器管理和性能都会受影响。
  • 避免在Bean中设计为无状态,方法里只用局部变量
  • 使用ThreadLocal 保存状态(推荐 ✅):给每个线程准备一份独立副本,避免了线程之间的数据覆盖。
  1. @Service
  2. public class UserContextService {
  3.     private ThreadLocal<String> currentUser = new ThreadLocal<>();
  4.     public void setUser(String user) {
  5.         currentUser.set(user);  // 每个线程有自己独立的副本
  6.     }
  7.     public String getUser() {
  8.         return currentUser.get();  // 取的就是当前线程的值
  9.     }
  10.     public void clear() {
  11.         currentUser.remove();  // 防止内存泄漏(重要!)
  12.     }
  13. }
复制代码
 
五、说说循环依赖

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 代理,必须等真正用到的时候才生成代理对象 → 所以放工厂。


来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册