Spring知识点(2)
一、有哪些依赖注入方式?
[*]构造方法注入:通过调用类的构造方法,推荐用于强依赖(没有依赖对象就没法工作)。这也是官方推荐的注入方式。好处:依赖不可变(final修饰)、更安全。
@Component
public class UserService {
private final UserDao userDao;
// Spring 会自动注入 UserDao
@Autowired
public UserService(UserDao userDao) {
this.userDao = userDao;
}
}
[*]Setter注入:适合可选依赖(有无都能运行)
@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 代理,必须等真正用到的时候才生成代理对象 → 所以放工厂。
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页:
[1]