IoC(Inversion of Control)控制反转:控制反转是一种设计思想,它将组件的创建和管理交给容器,从而降低组件之间的耦合度。控制是指实例化以及管理对象的权力,反转是指将控制权交给 IoC 容器。IoC 容器负责对象的创建、初始化等一系列工作,被创建或被管理的对象在 IoC 容器中统称为 bean。
面向切面编程的核心思想就是将横切关注点从核心业务逻辑中分离出来,形成一个个的切面。横切关注点指的是一些分散在多个类或对象中的公共行为(如日志记录、事务管理、权限控制、接口限流、接口幂等操作),如果我们在每个类或对象中都重复实现这些行为,那么会导致代码的冗余、复杂和难以维护。
Spring AOP 基于动态代理实现。当目标对象实现了某个接口时,Spring AOP 会使用 JDK 动态代理来生成对应接口的代理对象。对于没有实现接口的对象,Spring AOP 会使用 CGLIB 生成一个目标对象的子类作为代理对象。在运行时,代理类会替代原始目标类来执行方法调用,而代理类中的功能是基于切面类定义和实现的。
AOP 中的核心概念:
切入点表达式用来描述切入点方法的表达式,主要用来决定目标对象中的哪些连接点需要加入通知,主要包括使用 execution() 根据方法的签名来匹配和使用 @annotation() 根据注解匹配。
Aware 相关依赖,如 ApplicationContextAware,执行对应方法。@PostConstruct 注解标识的方法。BeanPostProcessor 接口,将当前类作为参数执行自定义的 postProcessBeforeInitialization 方法。InitializingBean 接口,执行 afterPropertiesSet 方法。@Bean(initMethod = ""),执行对应方法。BeanPostProcessor 接口,将当前类作为参数执行自定义的 postProcessAfterInitialization 方法。@Bean(destroyMethod = ""),执行对应方法。DisposableBean 接口,执行 destory() 方法。final 修饰、难以进行单元测试等问题,因此并不推荐使用字段注入。final 修饰的注入方法,同时能够检测循环依赖。但是,当一个类有很多依赖项时构造函数的参数列表可能会变得很长。此外,如果一个类有可选的依赖项,可能需要创建多个构造函数重载,灵活性较低。final 修饰。总的使用原则是:强制的依赖就用构造器注入,可选、可变的依赖就用 setter 注入。
bean 是否线程安全,取决于其作用域和状态。
以最常用的两种作用域 prototype 和 singleton 为例:
prototype 作用域下,每次获取都会创建一个新的 bean 实例,不存在资源竞争问题,所以不存在线程安全问题。singleton 作用域下,IoC 容器中只有唯一的 bean 实例,可能会存在资源竞争问题,具体要看 bean 是否有状态,如果是无状态 bean,那就不存在线程安全问题,如果是有状态 bean,那就存在线程安全问题。这里的有无状态指的是对于成员变量,除了查询以外,是否还会对其进行修改。对于有状态单例 bean 的线程安全问题,有以下几种解决办法:
ThreadLocal 或互斥锁控制成员变量的修改和访问。prototype 作用域。SpringMVC 技术与 Servlet 技术功能等同,均属于表现层开发技术。

声明 bean 的作用域,常见的有以下四种:
使用示例:
@Component
@Scope("singleton")
public class Solution {
}
@PostConstruct 和 @PreDestroy 并非由 Spring 提供,而是 Java 自带的注解。@PostConstruct 会在依赖注入完成后被自动调用,并且只会被调用一次,用于完成一些初始化操作。而 @PreDestroy 则会在容器销毁 bean 的时候回调执行,用于完成相关的销毁操作。
bean 初始化过程中的执行顺序为:
Constructor(构造方法)-> @Autowired(依赖注入)-> @PostConstruct(初始化方法)
使用示例:
@Component
public class Solution {
@PostConstruct
void init() {
System.out.println("init...");
}
@PreDestroy
void destroy() {
System.out.println("destroy...");
}
}
@Component 注解作用于类,标识这是一个 bean,而 @Bean 注解作用于方法,通常方法体中包含产生 bean 的逻辑。@Component 通常是通过类路径扫描来自动侦测 bean 并装配到 Spring 容器中,可以使用 @ComponentScan 定义要扫描的路径,而 @Bean 通常指定了这个方法的返回值将被注册为一个 bean。@Bean 注解比 @Component 注解的自定义性更强,可以在方法内部实现更复杂的 bean 创建和初始化逻辑,同时第三方依赖中的 bean 只能通过 @Bean 声明(因为第三方的 bean 是只读的,没法加 @Component 注解)。@Autowired 是 Spring 提供的注解,@Resource 是 JDK 提供的注解。@Autowired 默认的注入方式是根据类型匹配,@Resource 默认的注入方式是根据名称匹配。@Autowired 和 @Resource 除了都能够通过名称匹配到对应的 bean 以外,@Autowired 还可以通过 @Qualifier 注解来显式指定,@Resource 则可以通过 name 属性来显式指定。@Autowired 支持在构造函数、方法、字段和参数上使用,@Resource 主要用于字段和方法上的注入,不支持在构造函数或参数上使用。抽象产品:
package atreus.ink.log;
import org.springframework.stereotype.Component;
@Component
public abstract class AbstractLog {
protected abstract String getName();
protected void doLog() {
System.out.println("atreus.ink.log.AbstractLog#doLog");
}
}
具体产品:
package atreus.ink.log;
import org.springframework.stereotype.Component;
@Component
public class JavaLog extends AbstractLog {
@Override
public String getName() {
return "JavaLog";
}
@Override
public void doLog() {
super.doLog();
System.out.println("atreus.ink.log.JavaLog#doLog");
}
}
package atreus.ink.log;
import org.springframework.stereotype.Component;
@Component
public class CppLog extends AbstractLog {
@Override
public String getName() {
return "CppLog";
}
@Override
public void doLog() {
super.doLog();
System.out.println("atreus.ink.log.CppLog#doLog");
}
}
工厂:
package atreus.ink.log;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@Component
public class LogFactory implements ApplicationContextAware {
private static final Map<String, AbstractLog> map = new HashMap<>();
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
Map<String, AbstractLog> beansOfType = applicationContext.getBeansOfType(AbstractLog.class);
beansOfType.forEach((k, v) -> map.put(v.getName(), v));
}
public static AbstractLog getLog(String name) {
return map.get(name);
}
}
测试:
package atreus.ink.log;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class LogFactoryTest {
@Test
void getLogTest() {
AbstractLog javaLog = LogFactory.getLog("JavaLog");
javaLog.doLog();
System.out.println("----------");
AbstractLog cppLog = LogFactory.getLog("CppLog");
cppLog.doLog();
Assertions.assertTrue(true);
}
}
atreus.ink.log.AbstractLog#doLog
atreus.ink.log.JavaLog#doLog
----------
atreus.ink.log.AbstractLog#doLog
atreus.ink.log.CppLog#doLog
Spring 事务失效场景:
@Repository、@Service、@Controller 或 @Component 注解。public 的,@Transactional 只能用于 public 的方法上,如果要用在非 public 方法上,可以开启 AspectJ 代理模式。AopContext.currentProxy() 手动获取代理对象或者在内部注入自己对应的 bean。Spring 三级缓存(以 singleton 作用域为例):
singletonObjects,缓存已经完成实例化和初始化的成品 bean。earlySingletonObjects,缓存已经被其他对象引用的半成品 bean,这些 bean 被提前暴露。singletonFactories,缓存未被其他对象引用的半成品 bean 的工厂,使用时通过这些工厂创建 bean。三级缓存解决循环依赖(假设 A 和 B 之间存在循环依赖):
singletonFactories 中。singletonFactories 中。两级缓存也能解决循环依赖,为什么还需要第三级缓存,第三级缓存又为什么缓存 bean 工厂而不是 bean呢?
其实第三级缓存的主要目的是延迟代理对象的创建,如果没有循环依赖的话,第三级缓存可以将代理对象的创建延迟到初始化完成之后,不需要提前。
具体来说,如果创建的 bean 是有代理的,那么注入的就应该是代理 bean,而不是原始的 bean。按照 Spring 的设计原则,Spring 会在完成属性赋值与依赖注入并且执行完初始化方法之后再为其创建代理。
对于三级缓存来说,如果需要创建代理,在没有循环依赖的情况下,Spring 首先会为已经实例化的 bean 在三级缓存中创建一个工厂,然后进行属性赋值、依赖注入和初始化,最后创建代理放入一级缓存,也即在完成初始化等操作后创建代理类。如果出现了循环依赖,Spring 会通过三级缓存中工厂类的方法去提前创建代理对象,放入二级缓存,在完成初始化等操作后再放入一级缓存,这种情况下就是先创建代理再初始化。