Bean作用域
默认作用域是Singleton,多个线程访问同一个Bean时会存在线程不安全问题。
保障线程安全方法:
- 在Bean对象中尽量避免定义可变的成员变量(不太现实)
- 在类中定义⼀个ThreadLocal成员变量,将需要的可变成员变量保存在 ThreadLocal 中。
启动初始化
@PostConstruct:
在Bean创建期间由 Spring 调用的初始化方法。
- 使用:将 @PostConstruct 注解添加到方法上就行。
InitializingBean:
可以实现 InitializingBean 接口,让 Spring 调用某个初始化方法。
Controller和Service是线程安全的吗
默认情况下,Scope值是单例(Singleton)的,是线程不安全的。
尽量不要在
@Controller/@Service
等容器中定义静态变量,不论是单例(Singleton)还是多实例(Prototype)都是线程不安全的。
BeanFactoryPostProcessor 和 BeanPostProcessor
BeanFactoryPostProcessor:
在 Bean 工厂实例化 Bean 之前对 Bean 的定义进行修改。
可以读取和修改 Bean 的定义元数据,例如修改 Bean 的属性值、添加额外的配置信息等。
BeanPostProcessor:
- 在 Bean 实例化后对 Bean 进行增强或修改。
- 它可以在 Bean 的初始化过程中对 Bean 进行后处理,例如对 Bean 进行代理、添加额外的功能等。
Bean的生命周期
Spring的生命周期大致分为:创建
-> 属性填充
-> 初始化Bean
-> 使用
-> 销毁
几个核心阶段。
创建阶段:
- 主要是创建对象,对象的创建权交由Spring管理。
属性填充阶段:
- 主要是进行依赖的注入,将当前对象依赖的bean对象,从Spring容器中找出来,然后填充到对应的属性中去。
初始化bean阶段:
- 包括回调各种Aware接口、回调各种初始化方法、生成AOP代理对象也在该阶段进行,该阶段主要是完成初始化回调。
使用bean阶段:
- 主要是bean创建完成,在程序运行期间,提供服务的阶段。
销毁bean阶段:
- 主要是容器关闭或停止服务,对bean进行销毁处理。
循环依赖
循环依赖指的是一个实例或多个实例存在相互依赖的关系(类之间循环嵌套引用)。
public class AService {
private BService bService;
}
public class BService {
private AService aService;
}
Bean的创建步骤:
在创建 Bean 之前,Spring 会通过扫描获取 BeanDefinition。
BeanDefinition就绪后会读取 BeanDefinition 中所对应的 class 来加载类。
实例化阶段:根据构造函数来完成实例化 (未属性注入以及初始化的对象(原始对象))。
属性注入阶段:对 Bean 的属性进行依赖注入。
如果 Bean 的某个方法有AOP操作,则需要根据原始对象生成代理对象。
- AOP代理是
BeanPostProcessor
实现的,而BeanPostProcessor
是发生在属性注入阶段后的。最后把代理对象放入单例池(一级缓存
singletonObjects
)中。
为什么 Spring Bean 会产生循环依赖问题?
当
AService
创建时,会先对AService
实例化生成一个原始对象,然后在进行属性注入时发现了需要BService
对应的 Bean,此时就会去为BService
进行创建,在BService
实例化后生成一个原始对象后进行属性注入,此时会发现也需要AService
对应的 Bean。
三大循环依赖问题场景:
单例作用域下的Setter方法注入/Field属性注入出现的循环依赖。
- 在 Spring 中是不会产生循环依赖问题的,这主要是靠 三级缓存 机制解决。
单例作用域下的构造器注入出现的循环依赖。
- 因为 构造器注入 发生在 实例化阶段,而 Spring 解决循环依赖问题依靠的 三级缓存 在 属性注入阶段,也就是说调用构造函数时还未能放入三级缓存中,所以无法解决 构造器注入 的循环依赖问题。
原型作用域下的属性注入出现的循环依赖问题。
- 因为 Spring 不会缓存 原型 作用域的 Bean,所以 Spring 无法解决 原型 作用域的 Bean。
三级缓存:
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
一级缓存(
singletonObjects
):缓存的是已经实例化、属性注入、初始化后的 Bean 对象。二级缓存(
earlySingletonObjects
):缓存的是实例化后,但未属性注入、初始化的 Bean对象(用于提前暴露 Bean)。三级缓存(
singletonFactories
):缓存的是一个ObjectFactory
,主要作用是生成原始对象进行 AOP 操作后的代理对象(这一级缓存主要用于解决 AOP 问题)。
为什么缓存可以解决循环依赖问题?
在创建
AService
时,实例化后将 原始对象 存放到缓存中(提早暴露),然后依赖注入时发现需要BService
,便会去创建BService
,实例化后同样将 原始对象 存放到缓存中,然后依赖注入时发现需要AService
便会从缓存中取出并注入,这样BService
就完成了创建,随后AService
也就能完成属性注入,最后也完成创建。
为什么还需要第三级缓存?
第三级缓存(
singletonFactories
) 是为了处理 Spring 的 AOP的。如果
AService
中方法没有使用 AOP 操作,会发现BService
注入的 原始对象 与最后AService
完成创建后的最终对象是同一个对象。如果
AService
方法中有 AOP 操作时,当AService
的原始对象赋值(注入)给BService
,AService
会进行 AOP 操作产生一个 代理对象,这个代理对象最后会被放入单例池(一级缓存)中,也就是说此时BService
中注入的对象是原始对象,而AService
最终创建的完成后是代理对象,这样就会导致BService
依赖的AService
和 最终的AService
不是同一个对象。出现这个问题原因:AOP 是通过
BeanPostProcessor
实现的,而BeanPostProcessor
是在 属性注入阶段后 才执行的,所以会导致注入的对象有可能和最终的对象不一致。
Spring 是如何通过第三级缓存来避免 AOP 问题的?
三级缓存通过利用
ObjectFactory
和getEarlyBeanReference()
做到了提前执行 AOP 操作从而生成代理对象。在上移到二级缓存时,可以做到如果 Bean 中有 AOP 操作,那么提前暴露的对象会是 AOP 操作后返回的代理对象;如果没有 AOP 操作,那么提前暴露的对象会是原始对象。
- 只有等完成了属性注入、初始化后的 Bean 才会上移到一级缓存(单例池)中。
这样就能做到出现循环依赖问题时,注入依赖的对象和最终生成的对象是同一个对象。
- 相当于 AOP 提前在属性注入前完成,这样就不会导致后面生成的代理对象与属性注入时的对象的不一致。
设计模式
单例设计模式 :
- Spring 中的 Bean 默认都是单例的。
⼯⼚设计模式 :
- 通过 BeanFactory、 ApplicationContext 创建 Bean 对象。
代理设计模式 :
- Spring AOP 功能的实现。
观察者模式:
- Spring 事件驱动模型。
适配器模式:
- Spring AOP 的增强或通知(Advice)使⽤到了适配器模式、Spring MVC 中也是⽤到了适配器模式适配 Controller。
基本概念
IOC(控制反转)
Spring中有一个容器,我们将Bean放到这个容器中,让这个容器为我们创建实例,当需要时我们直接从这个容器中进行获取即可。
使用IOC最大的好处就是减少了代码的耦合度,降低了程序的维护成本。
实现原理:
Spring实现IOC容器的是通过:工厂 + 反射,实现的。
如果是基于全注解形式的话,只是将读取配置文件的步骤改成了读取配置类,然后通过配置类获取需要创建实现的Bean,并通过反射将其创建。
DI依赖注入
站在容器的角度,将对象创建依赖的其他对象注入到对象中。
AOP
AOP
面向切面的编程:在不改变源代码的情况下,实现对逻辑功能的修改。
- 常用的场景包括记录日志、异常处理、性能监控、安全控制(例如拦截器)等。
AOP
原理:
动态的生成代理类,代理类的执行过程为:
- 执行增加的代码(例如方法日志记录)-> 回调原方法 -> 增加的代码逻辑。
Spring AOP
动态代理采用JDK
动态代理或CGlib
动态生成代理类。
- 判断被切面的类是否有其实现的接口,如果有对应的接口,则采用
JDK
动态代理,否则采用CGlib
字节码生成机制动态代理方式。
@SuppressWarnings("serial")
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
if (targetClass.isInterface()) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
SpringMVC
SpringMVC是一个基于Servlet容器的Web应用框架。
- Servlet容器通常指Tomcat等服务容器。
Servlet容器会负责监听端口消息并映射为Request/Response对象,然后交给Servlet实例去处理。
SpringMVC框架的作用核心就是Servlet实例。
- 这个实例在Spring中默认是DispatcherServlet。
客户端(浏览器)发送请求,直接请求到 DispatcherServlet。
DispatcherServlet 根据请求信息调⽤ HandlerMapping,解析请求对应的 Handler。
解析到对应的 Handler (也就是 Controller 控制器)后,开始由HandlerAdapter 适配器处理。
HandlerAdapter 会根据 Handler 来调⽤真正的处理器开处理请求,并处理相应的业务逻辑。
处理器处理完业务后,会返回⼀个 ModelAndView 对象, Model 是返回的数据对象。
ViewResolver 会根据逻辑 View 查找实际的 View。
DispaterServlet 把返回的 Model 传给 View(视图渲染)。
把 View 返回给请求者(浏览器)。
核心组件DispatcherServlet
在Spring容器启动阶段读取映射规则,如RequestMapping。
在请求到来的时候,按照加载的请求映射规则找到合适的处理方法。
当处理请求的过程中出现了异常,对异常进行处理,如返回合适的界面或状态码。
解析返回View的主题、时区等信息。
渲染返回的视图。
请求映射HandlerMapping
请求映射用于根据请求找到该请求需要调用的所有方法,包含过滤器和处理方法等。
比较常用的一种请求映射是RequestMappingHandlerMapping。
- 从名称上可以看出来这个HandlerMapping是用于处理
@RequestMapping
注解请求映射的类。该类的主要作用是在收到请求之后,按顺序拿到所有需要调用的方法(主要包含拦截器和处理方法),然后一一调用这些方法。
拦截器HandlerInterceptor
Spring的拦截器可以灵活的在请求处理前、请求处理后和请求完成三个阶段自定义操作,比如用户权限校验等。
处理方法Handler
处理方法在DispatcherServlet定义为Object类型,如果使用了
@RequestMapping
来根据请求查找处理方法,那么查找到的处理方法就是HandlerMethod
类型,对应于Controller中添加了对应RequestMapping的方法。
处理方法适配器HandlerAdapter
DispatcherServlet从HandlerMapping中获取到的处理方法是Object类型,意味着不同的处理方法可能返回不同的对象。
DispatcherServlet本身是一个调度器,不应该关注如何调用不同的处理方法。
- 所以Spring提供了HandlerAdapter列表用户处理不同的调度方法。
异常处理HandlerExceptionResolver
HandlerExceptionResolver用于处理请求过程中出现的异常。
- 日常开发中使用比较多的是ExceptionHandlerExceptionResolver,也就是
@ExceptionHandler
注解。
@ExceptionHander
可以放在@ControllerAdvice
注解的类中,表示对所有的Controller都会生效。
事务
嵌套事务
举例,methodA 中调用了 methodB,两个方法都有对数据库的操作,而且都需要事务。
这种多个方法调用链中都有事务的场景,就是嵌套事务。
要注意的是,并不是说多个方法使用一个事务才叫嵌套,哪怕是不同的事务,只要在这个方法的调用链中,都是嵌套事务。
// MethodA:
public void methodA(){
int updated = connection.prepareStatement().executeUpdate();
methodB();
// ...
}
// MethodB:
public void methodB(){
// ...
}
事务传播行为
方法调用链中的子方法,是用一个新事务,还是使用当前事务呢?
- 这个子方法决定使用新事务还是当前事务(或不使用事务)的策略,就叫事务传播。
Spring传播行为 | 介绍 |
---|---|
REQUIRED | 支持当前事务,如果不存在,就新建一个 |
SUPPORTS | 支持当前事务,如果不存在,就不使用事务 |
MANDATORY | 支持当前事务,如果不存在,抛出异常 |
REQUIRES_NEW | 如果有事务存在,挂起当前事务,创建一个新的事务 |
NOT_SUPPORTED | 以非事务方式运行,如果有事务存在,挂起当前事务 |
NEVER | 以非事务方式运行,如果有事务存在,抛出异常 |
NESTED | 如果当前事务存在,则嵌套事务执行(嵌套式事务) |
事务失效
1、事务方法被Final、Static
关键字修饰:
如果一个方法被声明为
final
或者static
,则该方法不能被子类重写
- 也就是说无法在该方法上进行动态代理,这会导致
Spring
无法生成事务代理对象来管理事务。
@Service
public class ServiceImpl {
@Autowired
private TestMapper1 mapper1;
@Autowired
private TestMapper2 mapper2;
@Transactional
public final void add(Test test) {
mapper1.save();
mapper2.save();
}
}
2、同一个类中,方法内部调用:
事务是通过
Spring AOP
代理来实现的,而在同一个类中,一个方法调用另一个方法时
- 调用方法直接调用目标方法的代码,而不是通过代理类进行调用
- 即调用目标
add
方法不是通过代理类进行的,因此事务不生效。解决方案:可以新建多一个类,让这两个方法分开,分别在不同的类中。
@Service
public class ServiceImpl implements Service {
@Autowired
private TestMapper1 mapper1;
@Autowired
private TestMapper2 mapper2;
public void add(Test test){
// 调用内部的事务方法
this.add(test);
}
@Transactional
public void add(Test test) {
mapper1.save();
mapper2.save();
}
}
3、方法的访问权限不是Public:
代理的事务方法不是
public
的话,spring
的computeTransactionAttribute()
就会返回null
- 这时事务属性不存在了。
4、RollbackFor
属性配置错误:
rollbackFor
属性指定的异常必须是Throwable
或者其子类。
- 默认情况下,
RuntimeException
和Error
两种异常都是会自动回滚的。代码例子:
- 指定了
rollbackFor = Error.class
,但是抛出的异常又是Exception
,因此事务就不生效。
@Transactional(rollbackFor = Error.class)
public void add(Test test) {
mapper1.save(test);
mapper2.save(test);
//模拟异常抛出
throw new Exception();
}
5、事务注解被覆盖导致事务失效:
MyTestService
是MyService
的子类,并且覆盖了doSomething()
方法。
- 在该方法中,使用了不同的传播行为
(REQUIRES_NEW)
来覆盖父类的@Transactional
注解。在这种情况下,当调用
MyTestService
的doSomething()
方法时
- 由于子类方法中的注解覆盖了父类的注解,
Spring
框架将不会在父类的方法中启动事务。因此,当
MyRepository
的save()
方法被调用时,事务将不会被启动,也不会回滚。
- 这将导致数据不一致的问题,因为在
MyRepository
的save()
方法中进行的数据库操作将不会回滚。
public interface MyRepository {
@Transactional
void save(String data);
}
public class MyRepositoryImpl implements MyRepository {
@Override
public void save(String data) {
// 数据库操作
}
}
public class MyService {
@Autowired
private MyRepository myRepository;
@Transactional
public void doSomething(String data) {
myRepository.save(data);
}
}
public class MyTestService extends MyService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void doSomething(String data) {
super.doSomething(data);
}
}
6、嵌套事务:
如果
saveFlow
出现运行时异常,会继续往上抛,到外层add
的方法
- 导致
mapper.save
也会回滚啦。如果不想因为被内部嵌套的事务影响,可以用
try-catch
包住。
@Service
public class ServiceInOutService {
@Autowired
private FlowService flowService;
@Autowired
private Mapper mapper;
@Transactional
public void add(Test test) throws Exception {
mapper.save(test);
flowService.saveFlow(test);
}
}
@Service
public class FlowService {
@Autowired
private Mapper mapper;
@Transactional(propagation = Propagation.NESTED)
public void saveFlow(Test test) {
mapper.save(test);
throw new RuntimeException();
}
}
@Transactional
public void add(Test test) throws Exception {
mapper.save(test);
try {
flowService.saveFlow(test);
} catch (Exception e) {
log.error("save flow fail,message:{}",e.getMessage());
}
}
7、事务多线程调用:
Spring
事务是基于线程绑定的,每个线程都有自己的事务上下文
- 而多线程环境下可能会存在多个线程共享同一个事务上下文的情况,导致事务失效。
Spring
事务管理器通过使用线程本地变量(ThreadLocal
)来实现线程安全。
@Service
public class Service {
@Autowired
private Mapper1 mapper1;
@Mapper1
private FlowService flowService;
@Transactional
public void add(Test test) {
mapper1.save(test);
//多线程调用
new Thread(() -> {
flowService.save(test);
}).start();
}
}
@Service
public class FlowService {
@Autowired
private Mapper1 mapper1;
@Transactional
public void save(Test test) {
mapper1.saveFlow(test);
}
}
8、异常被捕获并处理了,没有重新抛出:
事务中的异常已经被业务代码捕获并处理
- 而没有被正确地传播回事务管理器,事务将无法回滚。
在
spring
事务方法中,当我们使用了try-catch
,catch
住异常,一定要重新把异常抛出来。
@Service
public class ServiceImpl implements Service {
@Autowired
private Mapper1 mapper1;
@Autowired
private Mapper2 mapper2;
@Transactional
public void add(Test test) {
try {
mapper1.save();
mapper2.save();
} catch (Exception e) {
log.error("add error,id:{},message:{}", tianluo.getId(),e.getMessage());
//throw e;
}
}
}
9、手动抛了别的异常:
如果手动抛了
Exception
异常,但是是不会回滚的
- 因为Spring默认只处理
RuntimeException和Error
,对于普通的Exception
不会回滚
- 除非,用
rollbackFor
属性指定配置。
@Transactional加不加rollbackFor=Exception.class的区别
@Transactional
只能回滚RuntimeException
和RuntimeException
下面的子类抛出的异常,不能回滚Exception
异常。如果需要支持回滚
Exception
异常请用@Transactional(rollbackFor = Exception.class)
。这里如果是增删改的时候我建议都使用
@Transactional(rollbackFor = Exception.class)
。
SpringBoot
Spring Boot Starter
实现一个Starter:
- Starter 命名。
- 自动配置类,用来初始化相关的 bean。
- 指明自动配置类的配置文件
spring.factories
。- 自定义属性实体类,声明 starter 的应用配置属性。
名字:
官方的 starter 的命名格式为
spring-boot-starter-{name}
。非官方的 starter 的命名格式为
{name}-spring-boot-starter
,把自定义的 starter 命名为fish-spring-boot-starter
,命名在 pom 文件里。
<groupId>fish.springcloud</groupId>
<artifactId>fish-spring-boot-starter</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
引入自动配置包及其它相关依赖包:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
创建 spring.factories
文件:
在
resource/META-INF
目录下创建名称为spring.factories
的文件。当 Spring Boot 启动的时候,会在 classpath 下寻找所有名称为
spring.factories
的文件,然后运行里面的配置指定的自动加载类,将指定类(一个或多个)中的相关 bean 初始化。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
kite.springcloud.boot.starter.example.FishAutoConfigure
编写自动配置类:
自动配置类是用来初始化 starter 中的相关 bean 的。
@Configuration
@ConditionalOnClass(FishService.class)
@EnableConfigurationProperties(FishProperties.class)
@Slf4j
public class FishAutoConfigure {
@Autowired
private FishProperties fishProperties;
@Bean
@ConditionalOnMissingBean(FishService.class)
@ConditionalOnProperty(prefix = "fish.example",value = "enabled", havingValue = "true")
FishService fishService(){
return new FishService(fishProperties);
}
}
实现属性配置类:
@Data
@ConfigurationProperties("fish.example")
public class FishProperties {
private String host;
private int port;
}
实现相关功能类:
@Slf4j
public class FishService {
private String host;
private int port;
public FishService(FishProperties fishProperties){
this.host = fishProperties.getHost();
this.port = fishProperties.getPort();
}
public void print(){
log.info(this.host + ":" +this.port);
}
}
打包:
通过 maven
mvn install
命令将 starter 安装到本地 maven 仓库。也可以通过
mvn package deploy
发布到私服。或者发布到中央仓库。
自动配置原理
Spring启用全自动配置功能的注解就是
@EnableAutoConfiguration
,应用添加了@EnableAutoConfiguration
注解之后,会读取所有jar包下面的spring.factories
文件,获取文件中配置的自动装配模块,然后去装配对应的模块。
@EnableAutoConfiguration
的功能可总结为:
- 使Spring启用factories机制导入各个starter模块的配置。
启动流程
构造SpringBootApplication对象
和执行run方法
。核心注解
@SpringBootConfiguration
标识启动类为配置类。
@EnableAutoConfiguration
通过内部@Import
注解AutoConfigurationImportSelector.class
实现自动装配。
@ComponentScan
默认扫描当前目录及子目录下的Bean。SpringBootApplication的构造方法主要做了几件事:
- 根据是否加载servlet类判断是否是web环境。
- 获取所有初始化器,扫描所有
META-INF/spring.factories
下的ApplicationContextInitializer
子类通过反射拿到实例,在spring实例启动前后做一些回调工作。- 获取所有监听器,同2,也是扫描配置加载对应的类实例。
run方法主要创建了配置环境、事件监听、启动应用上下文,其中refresh方法贯穿
springbean
的生命周期。执行bean的生命周期的前后置钩子方法,并且处理spring的注解标注的类。
在
onRefresh
中通过Java代码构建出tomcat容器并启动。