IoC (Inversion of control )
控制反转:不通过 new 关键字来创建对象,而是通过 IoC 容器(Spring 框架) 来帮助我们实例化对象。我们需要哪个对象,直接从 IoC 容器里面去取即可。
- 降低对象间的耦合度,类的构造发生变化,不需要手动处理其他地方的创建,容器自动处理
- 资源管控更容易,避免资源重复定义出现浪费
- DI(Dependency Injection) 依赖注入,是最常见、最合理的实现方式
- spring 的 IoC 容器就是一个 Map
Spring 对事务的支持
前提是数据库支持事务,读取也建议开启事务,避免出现幻读
两种事务管理方式
- 编程式事务管理:通过
TransactionManager手动执行/提交/回滚,用的较少 - 声明式事务管理:使用
@Transactional注解实现- 可以放到方法(必须 public)或者类上(类中所有 public 方法都生效),不推荐在接口上使用
- 通过 AOP 在实际调用方法前增加开启事务,后增加提交或回滚
- 如果当前类的方法调用另一个方法,事务会失效(不要自己调用自己)
- Spring AOP 会在对象从容器中取出时拦截增加前后置事务,内部调用不走容器,无法生效
- 需要这个类被 Spring 管理,否则也不生效
事务传播行为:解决不同方法间的事务问题
- PROPAGATION_REQUIRED(默认):
- 当前存在事务(调用方启动了),加入该事务
- 否则新建一个事务
- PROPAGATION_REQUIRES_NEW:
- 一定新建一个事务,和外部事务独立
- 但是可以抛出异常给外层,让外层一起回滚
- PROPAGATION_NESTED:
- 如果存在外部事务,开启一个嵌套事务,内部事务回滚不影响外部,外部事务回滚会让内部也回滚
- 否则开启一个独立事务
- PROPAGATION_MANDATORY:
- 如果存在外部事务,加入
- 否则抛出错误
- 还有以非事务运行的方案(用的少)
- 如果存在事务,加入,否则非事务运行
- 无论有无外部事务,当前无事务运行
- 一定以无事务运行,如果存在外部事务,抛出异常
可以配置事务的超时时间,超过自动回滚,默认没有超时时间 还可以配置事务遇到哪些异常会回滚,哪些不会 还可以配置事务为只读,会有一定的优化
Spring AOP
AOP(Aspect-Oriented Programming:面向切面编程)将共用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,减少重复代码,降低耦合度。Spring AOP 就是基于动态代理的
- 代理对象实现了某个接口,使用 JDK Proxy,去创建代理对象,
- 否则会使用 Cglib 生成一个被代理对象的子类来作为代理
- 或者使用AspectJ(Spring AOP 已经继承),是 Java 生态最完整的 AOP 框架
- 前两个是运行时增强(动态代理),这个是编译时增强(注入字节码)
- 后者性能更好,Spring AOP 只能用于 bean 容器管理对象,且无法拦截静态方法
- 包括:前置、后置、返回、异常和环绕通知
- 可以定义切面的执行顺序
- 常见的术语:
- 横切关注点(cross-cutting concerns) :多个类或对象中的公共行为(如日志记录)。
- 切面(Aspect):对横切关注点进行封装的类,一个切面是一个类(通常为注解类)。切面可以定义多个通知,用来实现具体的功能。
- 连接点(JoinPoint):方法调用的某个特定时刻(如方法调用、异常抛出等)。
- 通知(Advice):通知就是切面在某个连接点要执行的操作。通知有五种类型,分别是前置通知(Before)、后置通知(After)、返回通知(AfterReturning)、异常通知(AfterThrowing)和环绕通知(Around)。前四种通知都是在目标方法的前后执行,而环绕通知可以控制目标方法的执行过程。
- 切点(Pointcut):一个切点是一个表达式,它用来匹配哪些连接点需要被切面所增强。(通常就是注解添加的地方)
- 织入(Weaving):织入是将切面和目标对象连接起来的过程,也就是将通知应用到切点匹配的连接点上。常见的织入时机有两种,分别是编译期织入(AspectJ)和运行期织入(AspectJ)。
Spring 注解
@Autowired/@Resource:自动导入对象到类中,被注入进的类要被 Spring 容器管理(能找到)。- 前者是 Spring 提供的,后者是 JDK 提供的
- 前者默认 byType,后者默认 byName
@Component,@Repository,@Service,@Controller@PathVariable,@RequestParam,@RequestBody@Value:读取配置文件
Spring 包括的设计模式
- 工厂模式
- 单例模式:
- 代理模式:
- 动态代理(AOP)
- 模板方法
- 观察者模式:
- 适配器模式
- 装饰器模式:
三大组件
- Spring:
- Spring MVC:Spring 的重要模块,分为 Model(模型)、View(视图)、Controller(控制器)
- SpringBoot:配置简化
Bean
- 作用域:
- singleton:单例(默认)
- 如果定义了成员变量,可能线程不安全,尽量不要定义,可以使用 ThreadLocal 变量替代
- 大多数 bean 如 dao 一般也都是无状态的(无可变成员变量)
- prototype:每次获取返回一个新的
- request:每一个 HTTP 请求一个新的 bean
- session:每一个 session 一个新的 bean
- singleton:单例(默认)
- 生命周期:
- 创建
- 属性赋值/填充,设计三层缓存,循环依赖
- 包括前置处理,后置处理
- 初始化:
- 销毁
- 循环依赖:两个或多个 Bean 之间相互持有对方的引用,单个对象的自我依赖算是代码错误。(new 之后会 stackOverFlow)
- 必须单例 bean,非单例无法用缓存
- 必须有无参构造,否则必须拿到完整依赖才能构造,无法通过中间态解决
- 解决方案:三级缓存,在需要依赖时,会依次从一级->二级->三级缓存找
- 当 Spring 创建 A 之后,发现 A 依赖了 B ,将 A 的对象工厂放入三级缓存
- 去创建 B,B 依赖了 A,将 B 的对象工厂放入三级缓存
- 依次从 1/2/3 级缓存找 A,找到三级缓存有 A,调用 A 的对象工厂的
getObject()方法 - 如果 A 需要 AOP 代理,生成代理对象,否则生成普通原始半成品对象
- 将 A 的对象工厂从三级缓存移除,将 A 的半成品对象放入二级缓存,并将 A 注入到 B 中
- 依次从 1/2/3 级缓存找 A,找到三级缓存有 A,调用 A 的对象工厂的
- B 初始化完成(如果有 AOP 就生成代理对象),将 B 放入一级缓存
- 从二级缓存移除 B(如果有),从三级缓存移除 B 的对象工厂(如果有)
- 回到 A 的初始化,从一级缓存取出成品 B,注入到 A 中,将 A 放入一级缓存,将二级缓存的 A 移除
- 每次获取 bean 都是通过一级缓存获取的成品 bean
- 无二级缓存时,多次调用 getObject () 会生成多个代理对象,违反单例原则
- 二级缓存中,也可以 A 被
getObject()后移除二级缓存的对象工厂,将半成品 A 放入二级缓存,但是职责混乱,不如三级缓存清晰 - 这里加一步对象工厂是为了兼容代理对象生成
- 这里如果直接二级缓存放入代理对象,可能有些按需注册的因为前置的 bean 没有注册,而不会触发
- 三级缓存不能保证一定完整,比如上面按需注册的,在三级缓存下可能也缺失,但是能保证尽量的完整
- 二级缓存中,也可以 A 被
- 但是 Spring 官方也不建议使用循环依赖,因为涉及有缺陷
- 也可以用 @lazy 注解,可以加载类、成员变量等加了这个注解的 bean 不会在启动时加载,只在使用时加载
- 如果 A、B 相互依赖,A 的 B 属性有@lazy 注解,那么创建 A 时,如果需要 B,会先创建一个 B 的代理对象,不需要直接创建 A
- 后续创建 B 时,A 是完整的了,可以直接使用
- A 需要 B 的时候,代理对象会判定
- 如果 B 不存在,创建 B 的实际对象
- 如果 B 存在,直接调用实际 B 的对象,比三级缓存更简单
- 但是@lazy 会导致启动快,执行慢(执行过程可能需要加载类)
- 并且有错误会在执行时才能被发现,不利于稳定
- 且调试时看到的是代理对象,调试麻烦
java
@Configuration
class MyConfig {
// 这个配置类可能在A之后创建!
@Bean
public BeanPostProcessor customProcessor() {
return new CustomProcessor();
}
}
// 1. 开始创建A
Object a = new A(); // a.b = null
addSingletonFactory("A", factoryA); // 三级缓存
// 2. 给A注入属性B(populateBean)
// 发现需要注入B
Object b = getBean("B"); // 触发B的创建
// 3. 创建B
Object b = new B(); // b.a = null
addSingletonFactory("B", factoryB); // 三级缓存
// 4. 给B注入属性A
// 调用getBean("A") → getSingleton("A") → factoryA.getObject() // 这里 MyConfig 很有可能还没有创建
// 这里返回的是a(可能是代理对象,但a.b还是null)
// 把这个a给B:b.a = a
// 5. B创建完成!
// B现在有:b.a = a(但a.b = null)
// ⭐⭐⭐ 关键来了!⭐⭐⭐
// 6. 回到第2步:给A注入属性B
// 现在B已经创建完成,把完整的B注入给A
// a.b = b(完整的B,且b.a = a)
// 7. A现在:a.b = b(且b.a = a)
// 完美的循环依赖解决了!java
@Configuration
class MyConfig {
// 这个配置类可能在A之后创建!
@Bean
public BeanPostProcessor customProcessor() {
return new CustomProcessor();
}
}
// 1. 开始创建A
Object a = new A(); // a.b = null
if a.AOP(){
addSingleton("A", new AOP_A()); // 二级缓存,如果是 AOP,注入代理对象
}else {
addSingleton("A", a);
}// 这里 MyConfig 很大概率还没有创建
// 2. 给A注入属性B(populateBean)
// 发现需要注入B
Object b = getBean("B"); // 触发B的创建
// 3. 创建B
Object b = new B(); // b.a = null
if b.AOP(){
addSingleton("B", new AOP_B()); // 二级缓存,如果是 AOP,注入代理对象
}else {
addSingleton("B", b);
}
// 4. 给B注入属性A
// 调用getBean("A") → getSingleton("A")
// 这里返回的是a(可能是代理对象,但a.b还是null)
// 把这个a给B:b.a = a
// 5. B创建完成!
// B现在有:b.a = a(但a.b = null)
// ⭐⭐⭐ 关键来了!⭐⭐⭐
// 6. 回到第2步:给A注入属性B
// 现在B已经创建完成,把完整的B注入给A
// a.b = b(完整的B,且b.a = a)
// 7. A现在:a.b = b(且b.a = a)
// 完美的循环依赖解决了!