Skip to content

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
  • 生命周期:
    • 创建
    • 属性赋值/填充,设计三层缓存,循环依赖
      • 包括前置处理,后置处理
    • 初始化:
    • 销毁
  • 循环依赖:两个或多个 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 中
      • B 初始化完成(如果有 AOP 就生成代理对象),将 B 放入一级缓存
        • 从二级缓存移除 B(如果有),从三级缓存移除 B 的对象工厂(如果有)
      • 回到 A 的初始化,从一级缓存取出成品 B,注入到 A 中,将 A 放入一级缓存,将二级缓存的 A 移除
      • 每次获取 bean 都是通过一级缓存获取的成品 bean
    • 无二级缓存时,多次调用 getObject () 会生成多个代理对象,违反单例原则
      • 二级缓存中,也可以 A 被 getObject() 后移除二级缓存的对象工厂,将半成品 A 放入二级缓存,但是职责混乱,不如三级缓存清晰
      • 这里加一步对象工厂是为了兼容代理对象生成
        • 这里如果直接二级缓存放入代理对象,可能有些按需注册的因为前置的 bean 没有注册,而不会触发
        • 三级缓存不能保证一定完整,比如上面按需注册的,在三级缓存下可能也缺失,但是能保证尽量的完整
    • 但是 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)
// 完美的循环依赖解决了!

正在精进