0%

查缺补漏-Spring

本章是整理知识内容,为强化知识长期更新。

Spring

  • Spring是一个轻量级的IoC和AOP容器框架。是为Java应用程序提供基础性服务的一套框架,目的是用于简化企业应用程序的开发,它使得开发者只需要关心业务需求。

  • 主要由以下几个模块组成:

    • Spring Core:核心类库,提供IOC服务
    • Spring Context:提供框架式的Bean访问方式,以及企业级功能(JNDI、定时任务等)
    • Spring AOP:AOP服务切面编程
    • Spring DAO:对JDBC的抽象,简化了数据访问异常的处理
    • Spring ORM:对现有的ORM框架的支持
    • Spring Web:提供了基本的面向Web的综合特性,例如多方文件上传
    • Spring MVC:提供面向Web应用的Model-View-Controller实现
  • IoC(Inversion of Control,翻译为“控制反转”)不是一个具体的技术,而是一种设计思想。与传统控制流相比,IoC 会颠倒控制流,在传统的编程中需要开发者自行创建并销毁对象,而在 IoC 中会把这些操作交给框架来处理,这样开发者就不用关注具体的实现细节了,拿来直接用就可以了,这就是控制反转。

  • IoC 很好的体现出了面向对象的设计法则之一——好莱坞法则:“别找我们,我们找你”。即由 IoC 容器帮对象找到相应的依赖对象并注入,而不是由对象主动去找。

  • DI(Dependency Injection,翻译为“依赖注入”)表示组件间的依赖关系交由容器在运行期自动生成,也就是说,由容器动态的将某个依赖关系注入到组件之中,这样就能提升组件的重用频率。通过依赖注入机制,我们只需要通过简单的配置,就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心资源来自哪里、由谁实现等问题。

  • IoC 和 DI 其实是同一个概念从不同角度的描述的,由于控制反转这个概念比较含糊(可能只理解成了容器控制对象这一个层面,很难让人想到谁来维护对象关系),所以 2004 年被开发者尊称为“教父”的 Martin Fowler(世界顶级专家,敏捷开发方法的创始人之一)又给出了一个新的名字“依赖注入”,相对 IoC 而言,“依赖注入”明确描述了“被注入对象依赖 IoC 容器配置依赖对象”。

依赖注入

  • 平常的Java开发中,程序员在某个类中需要依赖其它类的方法。通常是new一个依赖类再调用类实例的方法,这种开发存在的问题是new的类实例不好统一管理。 Spring提出了依赖注入的思想,即依赖类不由程序员实例化,而是通过Spring容器帮我们new指定实例并且将实例注入到需要该对象的类中。依赖注入的另一种说法是”控制反转”。通俗的理解是:平常我们new一个实例,这个实例的控制权是我们程序员。而控制反转是指new实例工作不由我们程序员来做而是交给Spring容器来做。
  • IOC对于Spring框架来说,就是由Spring来负责控制对象的生命周期和对象间的关系。
  • 所谓依赖注入,就是由IOC容器在运行期间,动态地将某种依赖关系注入到对象之中,所以,以来注入DI 和控制反转IOC 是从不同的角度描述的同一间事情,就是值通过引用IOC容器,利用以来关系注入的方式,实现对象之间的解耦。

注入方式

  • Set注入。
  • 构造方法注入。
  • 工厂的方法注入。
  • 通过属性注入。
  • 通过List注入。
  • 通过Map注入。

Spring AOP

  • AOP(Aspect-Oriented Programming:⾯向切⾯编程)能够将那些与业务⽆关,却为业务模块所共同调⽤的逻辑或责任(例如事务处理、⽇志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。

    • Spring AOP就是基于动态代理的,如果要代理的对象,实现了某个接⼝,那么Spring AOP会使⽤JDK Proxy,去创建代理对象,⽽对于没有实现接⼝的对象,就⽆法使⽤ JDK Proxy 去进⾏代理了,这时候Spring AOP会使⽤Cglib ,这时候Spring AOP会使⽤ Cglib ⽣成⼀个被代理对象的⼦类来作为代理。

    • Spring Aop是运行时增强。
    • Cglib Aop是编译器增强。
  • AOP专门用于处理系统中分布于各个模块中交叉关注点的问题,在JavaEE应用中,常常通过AOP来处理一些横切性质的系统级服务,如事物管理、安全检查、缓存对象池管理。

    • 1.)切面 -Aspect 切面类 比如日志类
    • 2.) 连接点 - Join Point 加入切点的那个点
    • 3.)通知 -Advice 是在切面某个特定的连接点上的动作。
    • 4.)切入点 - Point Cut 匹配连接点的断点
    • 5.)引入 - Introduction
    • 6.)目标对象 - Target Object 被一个或多个通知对象,永远被代理的对象。
    • 7.) AOP代理 - AOP Proxy AOP创建的对象用来实现切面对象。
    • 8.)织入 - Weaving
    • 9.)AOP通俗的理解:
      • 一个组建A,不关心其他常用的服务组件B,但是这个组件A使用组件B的时候不是组建A自身去调用,而是通过配置等其他方式,比如Spring中可以通过XML配置文件。这样就使的A压根不需要知道服务组件B是怎么样的,A只关心自己的业务逻辑,具体A使用B的时候,配置文件去做,与具体的A组件无关。

Spring框架中的设计模式

  • 工厂模式:BeanFactory就是简单工厂模式的体现,用来创建对象的实例
  • 单例模式:Bean默认为单例模式
  • 代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术
  • 模板方法:用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate
  • 观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新,如Spring中listener的实现–ApplicationListener,Spring 事件驱动模型就是观察者模式很经典的⼀个应⽤。
  • 适配器模式 :Spring AOP 的增强或通知(Advice)使⽤到了适配器模式、spring MVC 中也是⽤到了适配器模式适配 Controller 。
  • 策略模式: Bean的实例化的时候决定采用何种方式初始化bean实例(反射或者CGLIB动态字节码生成),Resource 接口是具体资源访问策略的抽象。
  • 装饰器模式:Spring中用到的包装器模式在类名上有两种表现:一种是类名中含有Wrapper,另一种是类名中含有Decorator。
  • 模版模式:Spring Template 都采用了该模式。

Spring中Autowired和Resource关键字

  • @Resource和@Autowired都是做bean的注入时使用,其实@Resource并不是Spring的注解,它的包是javax.annotation.Resource,需要导入,但是Spring支持该注解的注入。
  • Autowired注解是按照类型(byType)装配依赖对象,默认情况下它要求依赖对象必须存在,如果允许null值,可以设置它的required属性为false。如果我们想使用按照名称(byName)来装配,可以结合@Qualififier注解一起使用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class TestServiceImpl { 
// 下面两种@Autowired只要使用一种即可
@Autowired
private UserDao userDao; // 用于字段上

@Autowired // 用于属性的方法上
public void setUserDao(UserDao userDao)
{
this.userDao = userDao;
}
}
// @Autowired注解是按照类型(byType)装配依赖对象,默认情况下它要求依赖对象必须存在,如果允
// 许null值,可以设置它的required属性为false。如果我们想使用按照名称(byName)来装配,可以结
// 合@Qualifier注解一起使用。如下:
public class TestServiceImpl {
@Autowired
@Qualifier("userDao") private UserDao userDao;
}
  • Resource默认按照ByName自动注入,由J2EE提供,需要导入包javax.annotation.Resource。@Resource有两个重要的属性:name和type,而Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。所以,如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。如果既不制定name也不制定type属性,这时将通过反射机制使用byName自动注入策略。
1
2
3
4
5
6
7
8
9
10
11
12
13
public class TestServiceImpl { 
// 下面两种@Resource只要使用一种即可

// 用于字段上
@Resource(name="userDao")
private UserDao userDao;

// 用于属性的setter方法上
@Resource(name="userDao")
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}

Bean注册方式

  • XML 配置文件的注册方式
1
2
3
4
<bean id="person" class="org.springframework.beans.Person">
   <property name="id" value="1"/>
   <property name="name" value="Java"/>
</bean>

Java 注解的注册方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 可以使用 @Component 注解方式来注册 Bean
@Component
public class Person {
   private Integer id;
   private String name
   // 忽略其他方法
}

// @Bean 注解方式来注册 Bean
@Configuration
public class Person {
   @Bean
   public Person  person(){
      return new Person();
   }
   // 忽略其他方法
}

Java API 的注册方式

1
2
3
4
5
6
7
8
9
10
11
12
// 使用 BeanDefinitionRegistry.registerBeanDefinition() 方法的方式注册 Bean	
public class CustomBeanDefinitionRegistry implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
RootBeanDefinition personBean = new RootBeanDefinition(Person.class);
// 新增 Bean
registry.registerBeanDefinition("person", personBean);
}
}

Spring Bean 的作用域

  • singleton
    • 表示在 Spring 容器中只有一个 Bean 实例,以单例的形式存在,是默认的 Bean 作用域。
  • prototype
    • 原型作用域,每次调用 Bean 时都会创建一个新实例,也就是说每次调用 getBean() 方法时,相当于执行了 new Bean()。
  • request
    • 每次 Http 请求时都会创建一个新的 Bean,该作用域仅适应于 WebApplicationContext 环境。
  • session
    • 同一个 Http Session 共享一个 Bean 对象,不同的 Session 拥有不同的 Bean 对象,仅适用于 WebApplicationContext 环境。
  • global session
    • 全局session共享一个bean对象。
  • application
    • 全局的 Web 作用域,类似于 Servlet 中的 Application。

Spring 中的单例 bean 的线程安全问题

  • 单例 bean 存在线程问题,主要是因为当多个线程操作同⼀个对象的时候,对这个对象的⾮静态成员变量的写操作会存在线程安全问题。在类中定义⼀个ThreadLocal成员变量,将需要的可变成员变量保存在 ThreadLocal 中(推荐的⼀种⽅式)。

Bean 生命周期

  • 对于 Spring Bean 来说,并不是启动阶段就会触发 Bean 的实例化,只有当客户端通过显式或者隐式的方式调用 BeanFactory 的 getBean() 方法时,它才会触发该类的实例化方法。当然对于 BeanFactory 来说,也不是所有的 getBean() 方法都会实例化 Bean 对象,例如作用域为 singleton 时,只会在第一次,实例化该 Bean 对象,之后会直接返回该对象。但如果使用的是ApplicationContext 容器,则会在该容器启动的时候,立即调用注册到该容器所有 Bean 的实例化方法。getBean() 方法是属于 BeanFactory 接口的,它的真正实现是AbstractAutowireCapableBeanFactory 的 createBean() 方法,而 createBean() 是通过 doCreateBean() 来实现的,具体源码实现。

  • 实例化Bean
    • 对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用createBean进行实例化。对于ApplicationContext容器,当容器启动结束后,通过获取BeanDefifinition对象中的信息,实例化所有的bean。
  • 设置对象属性(依赖注入)
    • 实例化后的对象被封装在BeanWrapper对象中,紧接着,Spring根据BeanDefifinition中的信息 以及 通过BeanWrapper提供的设置属性的接口完成依赖注入。
  • 处理Aware接口
    • 接着,Spring会检测该对象是否实现了xxxAware接口,并将相关的xxxAware实例注入给Bean。
      • 如果这个Bean已经实现了BeanNameAware接口,会调用它实现的setBeanName(String beanId)方法,此处传递的就是Spring配置文件中Bean的id值。
      • 如果这个Bean已经实现了BeanFactoryAware接口,会调用它实现的setBeanFactory()方法,传递的是Spring工厂自身。
      • 如果这个Bean已经实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文。
  • BeanPostProcessor
    • 如果想对Bean进行一些自定义的处理,那么可以让Bean实现了BeanPostProcessor接口,那将会调用postProcessBeforeInitialization(Object obj, String s)方法。
  • InitializingBean 与 init-method
    • 如果实现相关方法或者配置了 init-method则会自动调用其配置的初始化方法
  • BeanPostProcessor
    • 如果这个Bean实现了BeanPostProcessor接口,将会调用postProcessAfterInitialization(Object obj, String s)方法;由于这个方法是在Bean初始化结束时调用的,所以可以被应用于内存或缓存技术。
  • getBean
    • 可以正常使用这个bean了。
  • DisposableBean
    • 当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean这个接口,会调用其实现的destroy()方法。
  • destroy-method
    • 如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法。
  • 精简下
    • 实例化Bean: Ioc容器通过获取BeanDefinition对象中的信息进行实例化,实例化对象被包装在BeanWrapper对象中。
    • 设置对象属性(DI):通过BeanWrapper提供的设置属性的接口完成属性依赖注入。
    • 注入Aware接口(BeanFactoryAware, 可以用这个方式来获取其它 Bean,ApplicationContextAware):Spring会检测该对象是否实现了xxxAware接口,并将相关的xxxAware实例注入给bean。
    • BeanPostProcessor:自定义的处理(分前置处理和后置处理)
    • InitializingBean和init-method:执行我们自己定义的初始化方法使用
    • destroy:bean的销毁

Spring自动装配

SpringBoot 工程的启动类上都必须要加一个 @SpringBootApplication 注解,而且还要在 run 方法中导入我们的主类,我们就从这个被导入的主类上的注解开始分析。

1
2
3
4
5
6
@SpringBootApplication
public class BlogApplication {
public static void main(String[] args) {
SpringApplication.run(VueBlogApplication.class, args);
}
}

SpringBootApplication

@SpringBootApplication 注解是一个复合注解。除去 Java 的 4 个元注解外,分别是 @SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan

1
2
3
4
5
6
7
8
9
10
11
12
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

}

SpringBootConfiguration

@SpringBootConfiguration 注解本质是一个 @Configuration 注解,而 @Configuration 本质是一个 @Component 注解。

  • @SpringBootConfiguration 注解的启动类,本质就是一个配置类(ConfigurationClass),此类中可以声明一个或者多个被 @Bean 注解标记的方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {

}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
@AliasFor(annotation = Component.class)
String value() default "";
}

ComponentScan

执行包的扫描,如果 useDefaultFilters 属性为 true,那么它会将被 @component 注解标记以及以 @component 为元注解的注解标记的类都扫描到 IOC 容器中去。

1
2
3
4
5
6
7
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {

}

EnableAutoConfiguration

通过@Import注解将特定的类导入到容器中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

}


/**
* Return the auto-configuration class names that should be considered. By default
* this method will load candidates using {@link SpringFactoriesLoader} with
* {@link #getSpringFactoriesLoaderFactoryClass()}.
* @param metadata the source metadata
* @param attributes the {@link #getAttributes(AnnotationMetadata) annotation
* attributes}
* @return a list of candidate configurations
*/
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
  • AutoConfigurationImportSelector类中通过 SpringFactoriesLoader.loadFactoryNames() 去 classpath 下的 META-INF/spring.factories 中获取了所有需要自动装配的类,放入到了 IOC 容器中。

![image-20211102145526599](/Users/zengqingfeng/Library/Application Support/typora-user-images/image-20211102145526599.png)

  • 默认的信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# Logging Systems
org.springframework.boot.logging.LoggingSystemFactory=\
org.springframework.boot.logging.logback.LogbackLoggingSystem.Factory,\
org.springframework.boot.logging.log4j2.Log4J2LoggingSystem.Factory,\
org.springframework.boot.logging.java.JavaLoggingSystem.Factory

# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader

# ConfigData Location Resolvers
org.springframework.boot.context.config.ConfigDataLocationResolver=\
org.springframework.boot.context.config.ConfigTreeConfigDataLocationResolver,\
org.springframework.boot.context.config.StandardConfigDataLocationResolver

# ConfigData Loaders
org.springframework.boot.context.config.ConfigDataLoader=\
org.springframework.boot.context.config.ConfigTreeConfigDataLoader,\
org.springframework.boot.context.config.StandardConfigDataLoader

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

# Error Reporters
org.springframework.boot.SpringBootExceptionReporter=\
org.springframework.boot.diagnostics.FailureAnalyzers

# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.env.EnvironmentPostProcessorApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor,\
org.springframework.boot.env.RandomValuePropertySourceEnvironmentPostProcessor,\
org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor,\
org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor,\
org.springframework.boot.reactor.DebugAgentEnvironmentPostProcessor

# Failure Analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
org.springframework.boot.context.config.ConfigDataNotFoundFailureAnalyzer,\
org.springframework.boot.context.properties.IncompatibleConfigurationFailureAnalyzer,\
org.springframework.boot.context.properties.NotConstructorBoundInjectionFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BeanCurrentlyInCreationFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BeanDefinitionOverrideFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BeanNotOfRequiredTypeFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BindFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BindValidationFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.UnboundConfigurationPropertyFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.ConnectorStartFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.NoSuchMethodFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.NoUniqueBeanDefinitionFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.PortInUseFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.ValidationExceptionFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyNameFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyValueFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.PatternParseFailureAnalyzer,\
org.springframework.boot.liquibase.LiquibaseChangelogMissingFailureAnalyzer

# Failure Analysis Reporters
org.springframework.boot.diagnostics.FailureAnalysisReporter=\
org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter

Spring SPI

SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。

  • Spring 的 SPI 配置文件是一个固定的文件 - META-INF/spring.factories,功能上和 JDK 的类似,每个接口可以有多个扩展实现,使用起来非常简单:
1
2
3
//获取所有factories文件中配置的LoggingSystemFactory
List<LoggingSystemFactory>> factories =
SpringFactoriesLoader.loadFactories(LoggingSystemFactory.class, classLoader);

Spring循环依赖

当bean的作用域是Prototype的是不支持循环依赖的。不然会出现套娃,

  • 可能出现的依赖场景
    • 构造器循环依赖
    • 属性注入循环依赖
  • Spring维护了三个Map,所谓的三级缓存。
    • singletonObjects:第一级缓存,里面放置的是实例化好的单例对象
    • earlySingletonObjects:第二级缓存,里面存放的是提前曝光的单例对象。
    • singletonFactories:第三级缓存,里面存放的是要被实例化的对象的对象工厂。
    • 创建bean的时候Spring首先从一级缓存singletonObjects中获取。如果获取不到,并且对象正在创建中,就再从二级缓存earlySingletonObjects中获取,如果还是获取不到就从三级缓存singletonFactories中取(Bean调用构造函数进行实例化后,即使属性还未填充,就可以通过三级缓存向外提前暴露依赖的引用值(提前曝光),根据对象引用能定位到堆中的对象,其原理是基于Java的引用传递),取到后从三级缓存移动到了二级缓存完全初始化之后将自己放入到一级缓存中供其他使用,因为加入singletonFactories三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
/*
* 一级缓存
* singletonObjects 这就是人们常说的单例池,从微观意义上来说,这就是我们的 Spring 容器,
* 它用于存放完全初始化好的 bean,从缓存中取出的 bean 可以直接使用。
*/
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/*
* 三级缓存
* 存放 bean 工厂对象,提前曝光引用,解决循环依赖,虽然这个对象不完整,但是已经可以根据引用定位到堆内存中的对象,
* 此对象可能是通过构造方法创建,还没装配属性。
*/
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

/*
* 二级缓存
* 存放原始的 bean 对象用于解决循环依赖,存到里面的对象还没有被填充属性。
*/
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

/** Names of beans that are currently in creation */
// 标记为正在创建的 bean 的集合(A 依赖于 B,实例化 A 的过程中发现依赖 B,就去创建 B,那么 A 就会放在此集合中)
private final Set<String> singletonsCurrentlyInCreation =
Collections.newSetFromMap(new ConcurrentHashMap<>(16));
}
  • 首先尝试去从一级缓存 singletonObjects 单例池中去获取,没有获取到并且要获取的 Bean 正在创建中,再去查找二级缓存 earlySingletonObjects,如果还是没有找到,就再去三级缓存 singletonFactories 中去找,找到了就从三级缓存 singletonFactories 中移除,放入到二级缓存 earlySingletonObjects 中去,这样做的好处就是,如果还有其他对象尝试获取这个 Bean,那么可以直接从二级缓存中取出来,就不需要再调用 getEarlyBeanReference() 方法了。最终,创建好的 Bean,全部 put 到了单例池 singletonObjects 中了。

  • 如何解决循环依赖

    • 构造器循环依赖解决办法:在构造函数中使用@Lazy注解延迟加载。在注入依赖时,先注入代理对象,当首次使用时再创建对象说明:一种互斥的关系而非层次递进的关系,故称为三个Map而非三级缓存的缘由 完成注入;

Spring中Filter和Interceptor的区别

  • Filter是基于函数回调(doFilter()方法)的,而Interceptor则是基于Java反射的(AOP思想)。
  • Filter依赖于Servlet容器,而Interceptor不依赖于Servlet容器。
  • Filter对几乎所有的请求起作用,而Interceptor只能对action请求起作用。
  • Interceptor可以访问Action的上下文,值栈里的对象,而Filter不能。
  • 在action的生命周期里,Interceptor可以被多次调用,而Filter只能在容器初始化时调用一次。
  • Filter在过滤是只能对request和response进行操作,而interceptor可以对request、response、handler、modelAndView、exception进行操作。
  • interceptor 的执行顺序大致为:
    • 请求到达 DispatcherServlet
    • DispatcherServlet 发送至 Interceptor ,执行 preHandle
    • 请求达到 Controller
    • 请求结束后,postHandle 执行
  • Spring 中主要通过 HandlerInterceptor 接口来实现请求的拦截,实现 HandlerInterceptor 接口需要实现下面三个方法:
    • preHandle() – 在handler执行之前,返回 boolean 值,true 表示继续执行,false 为停止执行并返回。
    • postHandle() – 在handler执行之后, 可以在返回之前对返回的结果进行修改
    • afterCompletion() – 在请求完全结束后调用,可以用来统计请求耗时等等
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

public class ExecuteTimeInterceptor extends HandlerInterceptorAdapter{

private static final Logger logger = Logger.getLogger(ExecuteTimeInterceptor.class);

//before the actual handler will be executed
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler)
throws Exception {

long startTime = System.currentTimeMillis();
request.setAttribute("startTime", startTime);

return true;
}

//after the handler is executed
public void postHandle(
HttpServletRequest request, HttpServletResponse response,
Object handler, ModelAndView modelAndView)
throws Exception {

long startTime = (Long)request.getAttribute("startTime");

long endTime = System.currentTimeMillis();

long executeTime = endTime - startTime;

//modified the exisitng modelAndView
modelAndView.addObject("executeTime",executeTime);

//log it
if(logger.isDebugEnabled()){
logger.debug("[" + handler + "] executeTime : " + executeTime + "ms");
}
}
}
  • Servlet 的 Filter 接口需要实现如下方法:
    • void init(FilterConfig paramFilterConfig) – 当容器初始化 Filter 时调用,该方法在 Filter 的生命周期只会被调用一次,一般在该方法中初始化一些资源,FilterConfig 是容器提供给 Filter 的初始化参数,在该方法中可以抛出 ServletException 。init 方法必须执行成功,否则 Filter 可能不起作用,出现以下两种情况时,web 容器中 Filter 可能无效: 1)抛出 ServletException 2)超过 web 容器定义的执行时间。
    • doFilter(ServletRequest paramServletRequest, ServletResponse paramServletResponse, FilterChain paramFilterChain) – Web 容器每一次请求都会调用该方法。该方法将容器的请求和响应作为参数传递进来, FilterChain 用来调用下一个 Filter。
    • void destroy() – 当容器销毁 Filter 实例时调用该方法,可以在方法中销毁资源,该方法在 Filter 的生命周期只会被调用一次。

Spring 事务

  • Spring事物的特性

    • 隔离级别
      • 处理并发事务带来的问题
    • 事物传播
      • 处理业务代码互相调用事物问题
    • 事物回滚
      • 处理业务失败会滚业务事物问题
    • 事物超时
      • 处理事物允许的执行时间问题
    • 是否只读
      • 据说可以只读提高事物执行效率
  • 事物开启方式

    • 编程式事务 代码中硬编码开启事物。
    • 声明式事务
      • 基于注解开启事物。
      • 基于xml配置文件开启事物。

Spring 事务中的隔离级别

TransactionDefinition 接⼝中定义了五个表示隔离级别的常量:

  • TransactionDefinition.ISOLATION_DEFAULT: 使⽤后端数据库默认的隔离级别,Mysql 默认采⽤的 REPEATABLE_READ隔离级别 Oracle 默认采⽤的 READ_COMMITTED隔离级别。

  • TransactionDefinition.ISOLATION_READ_UNCOMMITTED: 最低的隔离级别,允许读取尚未提交的阻⽌脏读,但是幻读或不可重复读仍有可能发⽣。

  • TransactionDefinition.ISOLATION_READ_COMMITTED: 允许读取并发事务已经提交的数据,可以阻⽌脏读,但是幻读或不可重复读仍有可能发⽣。

  • TransactionDefinition.ISOLATION_REPEATABLE_READ: 对同⼀字段的多次读取结果都是⼀致的,除⾮数据是被本身事务⾃⼰所修改,可以阻⽌脏读和不可重复读,但幻读仍有可能发⽣。

  • TransactionDefinition.ISOLATION_SERIALIZABLE: 最⾼的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执⾏,这样事务之间就完全不可能产⽣⼲扰,也就是说,该级别可以防⽌脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会⽤到该级别。

Spring 事务中事务传播⾏为

  • ⽀持当前事务的情况:
    • TransactionDefinition.PROPAGATION_REQUIRED: 如果当前存在事务,则加⼊该事务;如果当前没有事务,则创建⼀个新的事务。
    • TransactionDefinition.PROPAGATION_SUPPORTS: 如果当前存在事务,则加⼊该事务;如果当前没有事务,则以⾮事务的⽅式继续运⾏。
    • TransactionDefinition.PROPAGATION_MANDATORY: 如果当前存在事务,则加⼊该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)
  • 不⽀持当前事务的情况:
    • TransactionDefinition.PROPAGATION_REQUIRES_NEW: 创建⼀个新的事务,如果当前存在事务,则把当前事务挂起。
    • TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以⾮事务⽅式运⾏,如果当前存在事务,则把当前事务挂起。
    • TransactionDefinition.PROPAGATION_NEVER: 以⾮事务⽅式运⾏,如果当前存在事务,则抛出异常。
  • 其他情况:
    • TransactionDefinition.PROPAGATION_NESTED: 如果当前存在事务,则创建⼀个事务作为当前事务的嵌套事务来运⾏;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。

TransactionStatus接口介绍

  • TransactionStatus接口用来记录事务的状态 该接口定义了一组方法,用来获取或判断事务的相应状态信息.
1
2
3
4
5
6
7
public interface TransactionStatus{
boolean isNewTransaction(); // 是否是新的事物
boolean hasSavepoint(); // 是否有恢复点
void setRollbackOnly(); // 设置为只回滚
boolean isRollbackOnly(); // 是否为只回滚
boolean isCompleted; // 是否已完成
}

Spring Boot 启动流程

Spring Boot 程序的入口是 SpringApplication.run(Application.class, args) 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public ConfigurableApplicationContext run(String... args) {
// 1.创建并启动计时监控类
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// 2.声明应用上下文对象和异常报告集合
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
// 3.设置系统属性 headless 的值
this.configureHeadlessProperty();
// 4.创建所有 Spring 运行监听器并发布应用启动事件
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting();
Collection exceptionReporters;
try {
// 5.处理 args 参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 6.准备环境
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
this.configureIgnoreBeanInfo(environment);
// 7.创建 Banner 的打印类
Banner printedBanner = this.printBanner(environment);
// 8.创建应用上下文
context = this.createApplicationContext();
// 9.实例化异常报告器
exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
// 10.准备应用上下文
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 11.刷新应用上下文
this.refreshContext(context);
// 12.应用上下文刷新之后的事件的处理
this.afterRefresh(context, applicationArguments);
// 13.停止计时监控类
stopWatch.stop();
// 14.输出日志记录执行主类名、时间信息
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
}
// 15.发布应用上下文启动完成事件
listeners.started(context);
// 16.执行所有 Runner 运行器
this.callRunners(context, applicationArguments);
} catch (Throwable var10) {
this.handleRunFailure(context, var10, exceptionReporters, listeners);
throw new IllegalStateException(var10);
}
try {
// 17.发布应用上下文就绪事件
listeners.running(context);
// 18.返回应用上下文对象
return context;
} catch (Throwable var9) {
this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
throw new IllegalStateException(var9);
}
}
  • Spring Boot 的启动流程

    • 1、创建并启动计时监控类

      • 此计时器是为了监控并记录 Spring Boot 应用启动的时间的,它会记录当前任务的名称,然后开启计时器。
    • 2.声明应用上下文对象和异常报告集合

      • 此过程声明了应用上下文对象和一个异常报告的 ArrayList 集合。
    • 3.设置系统属性 headless 的值

      • 设置 Java.awt.headless = true,其中 awt(Abstract Window Toolkit)的含义是抽象窗口工具集。设置为 true 表示运行一个 headless 服务器,可以用它来作一些简单的图像处理。
    • 4.创建所有 Spring 运行监听器并发布应用启动事件

      • 此过程用于获取配置的监听器名称并实例化所有的类。
    • 5.初始化默认应用的参数类

      • 也就是说声明并创建一个应用参数对象。
    • 6.准备环境

      • 创建配置并且绑定环境(通过 property sources 和 profiles 等配置文件)。
    • 7.创建 Banner 的打印类

      • 就是出个那个logo也可以自己定义。
    • 8.创建应用上下文

      • 根据不同的应用类型来创建不同的 ApplicationContext 上下文对象。
    • 9.实例化异常报告器

      • 它调用的是 getSpringFactoriesInstances() 方法来获取配置异常类的名称,并实例化所有的异常处理类。
    • 10.准备应用上下文

      • 此方法的主要作用是把上面已经创建好的对象,传递给 prepareContext 来准备上下文,例如将环境变量 environment 对象绑定到上下文中、配置 bean 生成器以及资源加载器、记录启动日志等操作。
    • 11.刷新应用上下文

      • 此方法用于解析配置文件,加载 bean 对象,并且启动内置的 web 容器等操作。
    • 12.应用上下文刷新之后的事件处理

      • 这个方法的源码是空的,可以做一些自定义的后置处理操作。
    • 13.停止计时监控类

      • 停止此过程第一步中的程序计时器,并统计任务的执行信息。
    • 14.输出日志信息

      • 把相关的记录信息,如类名、时间等信息进行控制台输出。
    • 15.发布应用上下文启动完成事件

      • 触发所有 SpringApplicationRunListener 监听器的 started 事件方法。
    • 16.执行所有 Runner 运行器

      • 执行所有的 ApplicationRunner 和 CommandLineRunner 运行器。
    • 17.发布应用上下文就绪事件

      • 触发所有的 SpringApplicationRunListener 监听器的 running 事件。
    • 18.返回应用上下文对象

      • 到此为止 Spring Boot 的启动程序就结束了。

Spring代理

  • 动态代理
    • 在程序运行时利用反射动态创建代理对象<复用性,易用性,更加集中都调用invoke。
  • 静态代理
    • 程序运行前代理类的.class文件就存在了。

Spring中获取代理对象

spring中获取代理对象的三种方式:

  1. 直接autowire基于接口注入。
  2. @Autowire applicationcontext,通过applicationcontext.getbean获取代理对象。
  3. 通过aopcontext.currentproxy方法获取(注意:需要启动类上增加注解@enableaspectjautoproxy(exposeproxy=true));
  • 如果spring启动时循环依赖导致报错,可以通过依赖注入属性增加@lazy注解解决。

JDK Proxy 和 CGLib 有什么区别

  • JDK Proxy
    • JDK Proxy 是 Java 语言自带的功能,无需通过加载第三方类实现。
    • JDK Proxy 是通过拦截器加反射的方式实现的;
    • JDK Proxy 只能代理继承接口的类;
  • CGLib
    • CGLib 是第三方提供的工具,基于 ASM 实现的,性能比较高;
    • CGLib 无需通过接口来实现,它是通过实现子类的方式来完成调用的。