# Framework
Spring 总共大约有 20 个模块, 由 1300 多个不同的文件构成。 而这些组件被分别整合在核心容器(Core Container) 、 AOP(Aspect Oriented Programming)和设备支持(Instrmentation) 、数据访问与集成(Data Access/Integeration) 、 Web、 消息(Messaging) 、 Test 等 6 个模块中。 以下是 Spring 5 的模块结构图:
- spring core:提供了框架的基本组成部分,包括控制反转(Inversion of Control,IOC)和依赖注入(Dependency Injection,DI)功能。
- spring beans:提供了 BeanFactory,是工厂模式的一个经典实现,Spring 将管理对象称为 Bean。
- spring context:构建于 core 封装包基础上的 context 封装包,提供了一种框架式的对象访问方法。
- spring jdbc:提供了一个 JDBC 的抽象层,消除了烦琐的 JDBC 编码和数据库厂商特有的错误代码解析, 用于简化 JDBC。
- spring aop:提供了面向切面的编程实现,让你可以自定义拦截器、切点等。
- spring Web:提供了针对 Web 开发的集成特性,例如文件上传,利用 servlet listeners 进行 ioc 容器初始化和针对 Web 的 ApplicationContext。
- spring test:主要为测试提供支持的,支持使用 JUnit 或 TestNG 对 Spring 组件进行单元测试和集成测试。
# AOP
AOP:Aspect oriented programming 面向切面编程,AOP 是 OOP(面向对象编程)的一种延续。
AOP 有两种实现方式:静态代理和动态代理。
# AOP 实现方式
静态代理
静态代理:代理类在编译阶段生成,在编译阶段将通知织入 Java 字节码中,也称编译时增强。AspectJ 使用的是静态代理。
缺点:代理对象需要与目标对象实现一样的接口,并且实现接口的方法,会有冗余代码。同时,一旦接口增加方法,目标对象与代理对象都要维护。
动态代理
动态代理:代理类在程序运行时创建,AOP 框架不会去修改字节码,而是在内存中临时生成一个代理对象,在运行期间对业务方法进行增强,不会生成新类。
# Spring AOP
Spring 的 AOP 实现原理其实很简单,就是通过动态代理实现的。如果我们为 Spring 的某个 bean 配置了切面,那么 Spring 在创建这个 bean 的时候,实际上创建的是这个 bean 的一个代理对象,我们后续对 bean 中方法的调用,实际上调用的是代理类重写的代理方法。而 Spring 的 AOP 使用了两种动态代理,分别是 JDK 的动态代理,以及 CGLib 的动态代理。
如果目标对象实现了接口,默认情况下会采用 JDK 的动态代理实现 AOP,也可以强制使用 CGLIB 实现 AOP。
如果目标对象没有实现了接口,必须采用 CGLIB 库。
# JDK 动态代理
如果目标类实现了接口,Spring AOP 会选择使用 JDK 动态代理目标类。代理类根据目标类实现的接口动态生成,不需要自己编写,生成的动态代理类和目标类都实现相同的接口。JDK 动态代理的核心是 InvocationHandler 接口和 Proxy 类。
缺点:目标类必须有实现的接口。如果某个类没有实现接口,那么这个类就不能用 JDK 动态代理。
# CGLIB 动态代理
通过继承实现。如果目标类没有实现接口,那么 Spring AOP 会选择使用 CGLIB 来动态代理目标类。CGLIB(Code Generation Library)可以在运行时动态生成类的字节码,动态创建目标类的子类对象,在子类对象中增强目标类。
CGLIB 是通过继承的方式做的动态代理,因此如果某个类被标记为 final,那么它是无法使用 CGLIB 做动态代理的。
优点:目标类不需要实现特定的接口,更加灵活。
# Spring AOP 相关术语
- 切面(Aspect):切面是通知和切点的结合。通知和切点共同定义了切面的全部内容。
- 连接点(Join point):指方法,在 Spring AOP 中,一个连接点总是代表一个方法的执行。连接点是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。
- 通知(Advice):在 AOP 术语中,切面的工作被称为通知。
- 切入点(Pointcut):切点的定义会匹配通知所要织入的一个或多个连接点。我们通常使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法名称来指定这些切点。
- 引入(Introduction):引入允许我们向现有类添加新方法或属性。
- 目标对象(Target Object): 被一个或者多个切面(aspect)所通知(advise)的对象。它通常是一个代理对象。
- 织入(Weaving):织入是把切面应用到目标对象并创建新的代理对象的过程。在目标对象的生命周期里有以下时间点可以进行织入:
- 编译期:切面在目标类编译时被织入。AspectJ 的织入编译器是以这种方式织入切面的。
- 类加载期:切面在目标类加载到 JVM 时被织入。需要特殊的类加载器,它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ5 的加载时织入就支持以这种方式织入切面。
- 运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP 容器会为目标对象动态地创建一个代理对象。SpringAOP 就是以这种方式织入切面。
# Spring AOP 通知类型
- 前置通知(Before):在目标方法被调用之前调用通知功能;
- 后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么;
- 返回通知(After-returning ):在目标方法成功执行之后调用通知;
- 异常通知(After-throwing):在目标方法抛出异常后调用通知;
- 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的逻辑。
# IOC
IoC(Inversion of Control)是控制反转的意思,这是一种面向对象编程的设计思想。在不采用这种思想的情况下,我们需要自己维护对象与对象之间的依赖关系,很容易造成对象之间的耦合度过高,在一个大型的项目中这十分的不利于代码的维护。IoC 则可以解决这种问题,它可以帮我们维护对象与对象之间的依赖关系,降低对象之间的耦合度。
说到 IoC 就不得不说 DI(Dependency Injection),DI 是依赖注入的意思,它是 IoC 实现的实现方式,就是说 IoC 是通过 DI 来实现的。由于 IoC 这个词汇比较抽象而 DI 却更直观,所以很多时候我们就用 DI 来代替它,在很多时候我们简单地将 IoC 和 DI 划等号,这是一种习惯。而实现依赖注入的关键是 IoC 容器,它的本质就是一个工厂。
在具体的实现中,主要由三种注入方式:
构造方法注入
就是被注入对象可以在它的构造方法中声明依赖对象的参数列表,让外部知道它需要哪些依赖对象。然后,IoC Service Provider 会检查被注入的对象的构造方法,取得它所需要的依赖对象列表,进而为其注入相应的对象。构造方法注入方式比较直观,对象被构造完成后,即进入就绪状态,可以马上使用。
setter 方法注入
通过 setter 方法,可以更改相应的对象属性。所以,当前对象只要为其依赖对象所对应的属性添加 setter 方法,就可以通过 setter 方法将相应的依赖对象设置到被注入对象中。setter 方法注入虽不像构造方法注入那样,让对象构造完成后即可使用,但相对来说更宽松一些, 可以在对象构造完成后再注入。
接口注入
相对于前两种注入方式来说,接口注入没有那么简单明了。被注入对象如果想要 IoC Service Provider 为其注入依赖对象,就必须实现某个接口。这个接口提供一个方法,用来为其注入依赖对象。IoC Service Provider 最终通过这些接口来了解应该为被注入对象注入什么依赖对象。相对于前两种依赖注入方式,接口注入比较死板和烦琐。
# IOC 容器初始化过程
ioc 容器初始化过程:BeanDefinition 的资源定位、解析和注册。
- 从 XML 中读取配置文件。
- 将 bean 标签解析成 BeanDefinition,如解析 property 元素, 并注入到 BeanDefinition 实例中。
- 将 BeanDefinition 注册到容器 BeanDefinitionMap 中。
- BeanFactory 根据 BeanDefinition 的定义信息创建实例化和初始化 bean。
# BeanFactory & ApplicationContext
它们都是 Spring 中的两个接口,用来获取 Spring 容器中的 bean。
BeanFactory
spring 容器中具有代表性的容器就是 BeanFactory 接口,这个是 spring 容器的顶层接口,提供了容器最基本的功能。
ApplicationContext
应用上下文,继承 BeanFactory 接口,它是 Spring 的一各更高级的容器,提供了更多的有用的功能;
- 国际化(MessageSource)
- 访问资源,如 URL 和文件(ResourceLoader)
- 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的 web 层
- 消息发送、响应机制(ApplicationEventPublisher)
- AOP(拦截器)
# 装载 bean 的区别
BeanFactory
BeanFactory 在启动的时候不会去实例化 Bean,中有从容器中拿 Bean 的时候才会去实例化;
ApplicationContext
ApplicationContext 在启动的时候就把所有的 Bean 全部实例化了。它还提供了更多的功能,如国际化、事件传播、资源加载等。它还可以为 Bean 配置 lazy-init=true
来让 Bean 延迟实例化;
# 循环依赖问题
spring 对循环依赖的处理有三种情况:
- 构造器的循环依赖:这种依赖 spring 是处理不了的,直接抛出 BeanCurrentlylnCreationException 异常。
- 单例模式下的 setter 循环依赖:通过「三级缓存」处理循环依赖。
- 非单例循环依赖:无法处理。
Spring 在创建 Bean 的过程中分为三步:
- 实例化,对应方法:AbstractAutowireCapableBeanFactory 中的 createBeanInstance 方法。
- 属性注入,对应方法:AbstractAutowireCapableBeanFactory 的 populateBean 方法。
- 初始化,对应方法:AbstractAutowireCapableBeanFactory 的 initializeBean 方法。
Spring 为了解决单例的循环依赖问题,使用了三级缓存。
/** Cache of singleton objects: bean name –> bean instance */ | |
private final Map singletonObjects = new ConcurrentHashMap(256); | |
/** Cache of singleton factories: bean name –> ObjectFactory */ | |
private final Map> singletonFactories = new HashMap>(16); | |
/** Cache of early singleton objects: bean name –> bean instance */ | |
private final Map earlySingletonObjects = new HashMap(16); |
三级缓存的作用
singletonFactories
: 进入实例化阶段的单例对象工厂的 cache (三级缓存)。
earlySingletonObjects
:完成实例化但是尚未初始化的,提前暴光的单例对象的 Cache (二级缓存)。
singletonObjects
:完成初始化的单例对象的 cache(一级缓存)。
步骤:
- Spring 首先从一级缓存 singletonObjects 中获取。
- 如果获取不到,并且对象正在创建中,就再从二级缓存 earlySingletonObjects 中获取。
- 如果还是获取不到且允许 singletonFactories 通过 getObject () 获取,就从三级缓存 singletonFactory.getObject ()(三级缓存)获取。
- 如果从三级缓存中获取到就从 singletonFactories 中移除,并放入 earlySingletonObjects 中。其实也就是从三级缓存移动到了二级缓存。
我们在创建 bean 的时候,会首先从 cache 中获取这个 bean,这个缓存就是 sigletonObjects。主要的调用方法是:
protected Object getSingleton(String beanName, boolean allowEarlyReference) { | |
// 1. 先从一级缓存中获取,获取到直接返回 | |
Object singletonObject = this.singletonObjects.get(beanName); | |
// 2. 如果获取不到并且对象正在创建,就到二级缓存中去获取,获取到直接返回 | |
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { | |
synchronized (this.singletonObjects) { | |
singletonObject = this.earlySingletonObjects.get(beanName); | |
// 3. 如果仍获取不到,且允许 singletonFactories (allowEarlyCurrentlyInCreation ())通过 getObject () 获取。 | |
// 就到三级缓存中用 getObject () 获取。 | |
// 获取到就从 singletonFactories 中移出,且放进 earlySingletonObjects。 | |
//(即从三级缓存移动到二级缓存) | |
if (singletonObject == null && allowEarlyReference) { | |
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); | |
if (singletonFactory != null) { | |
singletonObject = singletonFactory.getObject(); | |
this.earlySingletonObjects.put(beanName, singletonObject); | |
this.singletonFactories.remove(beanName); | |
} | |
} | |
} | |
} | |
return singletonObject; | |
} |
从三级缓存的分析来看,Spring 解决循环依赖的诀窍就在于 singletonFactories 这个滴三级 cache。这个 cache 的类型是 ObjectFactory,定义如下:
public interface ObjectFactory<T> { | |
T getObject() throws BeansException; | |
} |
该类型在 addSingletonFactory
方法中引用。方法又被 doCreateBean
调用。
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) { | |
Assert.notNull(singletonFactory, "Singleton factory must not be null"); | |
synchronized (this.singletonObjects) { | |
if (!this.singletonObjects.containsKey(beanName)) { | |
this.singletonFactories.put(beanName, singletonFactory); | |
this.earlySingletonObjects.remove(beanName); | |
this.registeredSingletons.add(beanName); | |
} | |
} | |
} |
doCreateBean
// 保存的是 FactoryBean 的 beanName -> FactoryBean 的 BeanWrapper | |
private final ConcurrentMap<String, BeanWrapper> factoryBeanInstanceCache = new ConcurrentHashMap<>(); | |
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) | |
throws BeanCreationException { | |
// Instantiate the bean. | |
BeanWrapper instanceWrapper = null; | |
if (mbd.isSingleton()) { | |
// 单例情况下清除缓存。这里保存的是 FactoryBean 和 BeanWrapper 的映射关系。 | |
//factoryBeanInstanceCache 是在创建其他 bean 的时候缓存了一下 FactoryBean 。至于单例模式下移除而不是获取,因为单例只需要创建一次 ? 尚未理解。 | |
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName); | |
} | |
// 如果没有缓存,则重新创建 | |
if (instanceWrapper == null) { | |
// 1. 根据指定的 bean 使用对应的策略创建新的实例。如:工厂方法、构造函数自动注入,简单初始化 | |
instanceWrapper = createBeanInstance(beanName, mbd, args); | |
} | |
// 获取 bean 实例 | |
final Object bean = instanceWrapper.getWrappedInstance(); | |
// 获取 bean 类型 | |
Class<?> beanType = instanceWrapper.getWrappedClass(); | |
// 将目标类型替换成实际生成的类型。纠正了上面说到类型错误(如果存在) | |
if (beanType != NullBean.class) { | |
mbd.resolvedTargetType = beanType; | |
} | |
// Allow post-processors to modify the merged bean definition. | |
// 2. 调用 MergedBeanDefinitionPostProcessor 后处理器 | |
synchronized (mbd.postProcessingLock) { | |
if (!mbd.postProcessed) { | |
try { | |
// 调用 MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition 后处理器的方法。 | |
applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName); | |
} | |
catch (Throwable ex) { | |
throw new BeanCreationException(mbd.getResourceDescription(), beanName, | |
"Post-processing of merged bean definition failed", ex); | |
} | |
mbd.postProcessed = true; | |
} | |
} | |
// Eagerly cache singletons to be able to resolve circular references | |
// even when triggered by lifecycle interfaces like BeanFactoryAware. | |
// 3. 判断是否需要提早曝光 : 单例 & 允许循环依赖 & 当前 bean 已经正在创建中 | |
// 由于当前 bean 已经在创建中,本次创建必然是循环引用造成的,所以这里判断是否可以需要提前曝光 | |
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && | |
isSingletonCurrentlyInCreation(beanName)); | |
if (earlySingletonExposure) { | |
if (logger.isTraceEnabled()) { | |
logger.trace("Eagerly caching bean '" + beanName + | |
"' to allow for resolving potential circular references"); | |
} | |
// 4. 为避免后期循环依赖,在 bean 初始化完成前将创建实例的 ObjectFactory 加入工程 -- 解决循环依赖 | |
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); | |
} | |
// Initialize the bean instance. | |
Object exposedObject = bean; | |
try { | |
// 5. 对 bean 进行属性填充,将各个属性值注入,其中如果存在依赖于其他 bean 的属性,则会递归初始依赖 bean | |
populateBean(beanName, mbd, instanceWrapper); | |
// 调用初始化方法,比如 init-method | |
exposedObject = initializeBean(beanName, exposedObject, mbd); | |
} | |
catch (Throwable ex) { | |
if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) { | |
throw (BeanCreationException) ex; | |
} | |
else { | |
throw new BeanCreationException( | |
mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex); | |
} | |
} | |
// 6. 进行循环依赖检查 | |
if (earlySingletonExposure) { | |
Object earlySingletonReference = getSingleton(beanName, false); | |
//earlySingletonReference 只有在检测到有循环依赖的情况下才会不为空 | |
if (earlySingletonReference != null) { | |
// 如果 exposedObject 没有在初始化方法中被改变,也就是没有被增强 | |
if (exposedObject == bean) { | |
exposedObject = earlySingletonReference; | |
} | |
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) { | |
String[] dependentBeans = getDependentBeans(beanName); | |
Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length); | |
for (String dependentBean : dependentBeans) { | |
// 检测依赖 | |
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) { | |
actualDependentBeans.add(dependentBean); | |
} | |
} | |
// 因为 bean 创建后其所依赖的 bean 一定是已经创建了的。actualDependentBeans 不为空说明当前 bean 创建后其依赖的 bena 却没有全部创建完,也就说说存在循环依赖。 | |
if (!actualDependentBeans.isEmpty()) { | |
throw new BeanCurrentlyInCreationException(beanName, | |
"Bean with name '" + beanName + "' has been injected into other beans [" + | |
StringUtils.collectionToCommaDelimitedString(actualDependentBeans) + | |
"] in its raw version as part of a circular reference, but has eventually been " + | |
"wrapped. This means that said other beans do not use the final version of the " + | |
"bean. This is often the result of over-eager type matching - consider using " + | |
"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example."); | |
} | |
} | |
} | |
} | |
// Register bean as disposable. | |
try { | |
// 7. 根据 Scopse 注册 bean | |
registerDisposableBeanIfNecessary(beanName, bean, mbd); | |
} | |
catch (BeanDefinitionValidationException ex) { | |
throw new BeanCreationException( | |
mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex); | |
} | |
return exposedObject; | |
} |
doCreateBean
大致逻辑如下:
createBeanInstance(beanName, mbd, args);
:实例化 bean,将 BeanDefinition 转换为 BeanWrapperapplyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
: MergedBeanDefinitionPostProcessor 后处理器的应用。bean 合并后的处理,比如 @Autowired、@Value 注解正是通过 AutowiredAnnotationBeanPostProcessor#postProcessMergedBeanDefinition 此方法实现的预解析。addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
: 关于循环依赖的处理,添加 ObjectFactory 到 singletonFactories 缓存中,同时这里给了用户一个机会通过调用 SmartInstantiationAwareBeanPostProcessor.getEarlyBeanReference 方法来由用户生成暴露的实例populateBean(beanName, mbd, instanceWrapper);
:对创建的 bean 内部的一些属性进行填充注入initializeBean(beanName, exposedObject, mbd);
: 初始化 bean 的一些属性,如 Aware 接口的实现, init-method 属性等- 循环依赖检查。和第四步不同的是,这里了是判断是否无法解决循环依赖,否则抛出异常。
registerDisposableBeanIfNecessary(beanName, bean, mbd);
: 注册 DisposableBean- 完成创建并返回。
addSingletonFactory
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); |
这一部分的逻辑就是为了解决循环依赖的问题,将未创建完成的当前 bean,通过 ObjectFactory 进行一个包装,提前暴露给其他 bean。
getEarlyBeanReference
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { | |
Object exposedObject = bean; | |
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { | |
for (BeanPostProcessor bp : getBeanPostProcessors()) { | |
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) { | |
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp; | |
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName); | |
} | |
} | |
} | |
return exposedObject; | |
} |
所有的
# Bean
# Spring 如何管理 Bean
Spring 通过 IoC 容器来管理 Bean,我们可以通过 XML 配置或者注解配置,来指导 IoC 容器对 Bean 的管理。因为注解配置比 XML 配置方便很多,所以现在大多时候会使用注解配置的方式。
以下是管理 Bean 时常用的一些注解:
@ComponentScan
用于声明扫描策略,通过它的声明,容器就知道要扫描哪些包下带有声明的类,也可以知道哪些特定的类是被排除在外的。
@Component
、 @Repository
、 @Service
、 @Controller
用于声明 Bean,它们的作用一样,但是语义不同。@Component 用于声明通用的 Bean, @Repository
用于声明 DAO 层的 Bean,@Service 用于声明业务层的 Bean, @Controller
用于声明视图层的控制器 Bean,被这些注解声明的类就可以被容器扫描并创建。
@Autowired
、 @Qualifier
用于注入 Bean,即告诉容器应该为当前属性注入哪个 Bean。其中,@Autowired 是按照 Bean 的类型进行匹配的,如果这个属性的类型具有多个 Bean,就可以通过 @Qualifier 指定 Bean 的名称,以消除歧义。
@Scope
用于声明 Bean 的作用域,默认情况下 Bean 是单例的,即在整个容器中这个类型只有一个实例。可以通过 @Scope
注解指定 prototype 值将其声明为多例的,也可以将 Bean 声明为 session 级作用域、request 级作用域等等,但最常用的还是默认的单例模式。
@PostConstruct
、 @PreDestroy
用于声明 Bean 的生命周期。其中,被 @PostConstruct
修饰的方法将在 Bean 实例化后被调用, @PreDestroy
修饰的方法将在容器销毁前被调用。
# Bean 的作用域
默认情况下,Bean 在 Spring 容器中是单例的,我们可以通过 @Scope 注解修改 Bean 的作用域。该注解有如下 5 个取值,它们代表了 Bean 的 5 种不同类型的作用域:
类型 | 说明 | |
---|---|---|
singleton | 在 Spring 容器中仅存在一个实例,即 Bean 以单例的形式存在。 | |
prototype | 每次调用 getBean () 时,都会执行 new 操作,返回一个新的实例。 | |
request | 每次 HTTP 请求都会创建一个新的 Bean。 | |
session | 同一个 HTTP | Session 共享一个 Bean,不同的 HTTP Session 使用不同的 Bean。 |
globalSession | 同一个全局的 Session 共享一个 Bean,一般用于 Portlet 环境。 |
# Bean 的生命周期
Spring bean 的生命周期只有四个主要阶段,其他都是在这四个主要阶段前后的扩展点,这四个阶段是:
- 实例化 Instantiation
- 属性赋值 Populate
- 初始化 Initialization
- 销毁 Destruction
# BeanFactory & FactoryBean
BeanFactory
:管理 Bean 的容器,Spring 中生成的 Bean 都是由这个接口的实现来管理的。
FactoryBean
:通常是用来创建比较复杂的 bean,一般的 bean 直接用 xml 配置即可,但如果一个 bean 的创建过程中涉及到很多其他的 bean 和复杂的逻辑,直接用 xml 配置比较麻烦,这时可以考虑用 FactoryBean,可以隐藏实例化复杂 Bean 的细节。
当配置文件中 bean 标签的 class 属性配置的实现类是 FactoryBean 时,通过 getBean () 方法返回的不是 FactoryBean 本身,而是调用 FactoryBean#getObject () 方法所返回的对象,相当于 FactoryBean#getObject () 代理了 getBean () 方法。如果想得到 FactoryBean 必须使用 '&' + beanName 的方式获取。
# Bean 注入容器的方式
将普通类交给 Spring 容器管理,通常有以下方法:
1、使用 @Configuration
与 @Bean
注解
2、使用 @Controller
、 @Service
、 @Repository
、 @Component
注解标注该类,然后启用 @ComponentScan
自动扫描
3、使用 @Import
注解把 bean 导入到当前容器中。
# 其它问题
# @Autowired & @Resource
- @Autowired 是 Spring 提供的注解,@Resource 是 JDK 提供的注解。
- @Autowired 是只能按类型注入,@Resource 默认按名称注入,也支持按类型注入。
- @Autowired 按类型装配依赖对象,默认情况下它要求依赖对象必须存在,如果允许 null 值,可以设置它 required 属性为 false,如果我们想使用按名称装配,可以结合 @Qualifier 注解一起使用。@Resource 有两个中重要的属性:name 和 type。name 属性指定 byName,如果没有指定 name 属性,当注解标注在字段上,即默认取字段的名称作为 bean 名称寻找依赖对象,当注解标注在属性的 setter 方法上,即默认取属性名作为 bean 名称寻找依赖对象。需要注意的是,@Resource 如果没有指定 name 属性,并且按照默认的名称仍然找不到依赖对象时, @Resource 注解会回退到按类型装配。但一旦指定了 name 属性,就只能按名称装配了。
# Spring 中默认提供的单例是线程安全的吗
不是线程安全的,Spring 容器本身并没有提供 Bean 的线程安全策略。如果单例的 Bean 是一个无状态的 Bean,即线程中的操作不会对 Bean 的成员执行查询以外的操作,那么这个单例的 Bean 是线程安全的。比如,Controller、Service、DAO 这样的组件,通常都是单例且线程安全的。如果单例的 Bean 是一个有状态的 Bean,则可以采用 ThreadLocal 对状态数据做线程隔离,来保证线程安全。
# Spring 如何管理事务?
参考答案
Spring 为事务管理提供了一致的编程模板,在高层次上建立了统一的事务抽象。也就是说,不管是选择 MyBatis、Hibernate、JPA 还是 Spring JDBC,Spring 都可以让用户以统一的编程模型进行事务管理。
# Spring 支持两种事务编程模型:
编程式事务
Spring 提供了 TransactionTemplate 模板,利用该模板我们可以通过编程的方式实现事务管理,而无需关注资源获取、复用、释放、事务同步及异常处理等操作。相对于声明式事务来说,这种方式相对麻烦一些,但是好在更为灵活,我们可以将事务管理的范围控制的更为精确。
声明式事务
Spring 事务管理的亮点在于声明式事务管理,它允许我们通过声明的方式,在 IoC 配置中指定事务的边界和事务属性,Spring 会自动在指定的事务边界上应用事务属性。相对于编程式事务来说,这种方式十分的方便,只需要在需要做事务管理的方法上,增加 @Transactional 注解,以声明事务特征即可。
# Spring 自动装配的方式有哪些
Spring 的自动装配有三种模式:byType(根据类型),byName(根据名称)、constructor(根据构造函数)。
# Spring Boot
从本质上来说,Spring Boot 就是 Spring,它做了那些没有它你自己也会去做的 Spring Bean 配置。Spring Boot 使用 “习惯优于配置” 的理念让你的项目快速地运行起来,使用 Spring Boot 很容易创建一个能独立运行、准生产级别、基于 Spring 框架的项目,使用 Spring Boot 你可以不用或者只需要很少的 Spring 配置。
简而言之,Spring Boot 本身并不提供 Spring 的核心功能,而是作为 Spring 的脚手架框架,以达到快速构建项目、预置三方配置、开箱即用的目的。Spring Boot 有如下的优点:
- 可以快速构建项目;
- 可以对主流开发框架的无配置集成;
- 项目可独立运行,无需外部依赖 Servlet 容器;
- 提供运行时的应用监控;
- 可以极大地提高开发、部署效率;
- 可以与云计算天然集成。
# 核心注解
核心注解是 @SpringBootApplication,它主要由 @SpringBootConfiguration,@EnableAutoConfiguration 和 @ComponentScan 这三个构成
-
@SpringBootConfiguration
里面就只有一个 @Configuration 主要注解,也就是把该类变成一个配置类所以 @SpringBootConfiguration 就相当于 @Configuration。 -
@EnableAutoConfiguration
是由 @AutoConfigurationPackage 和 @Import (EnableAutoConfigurationImportSelector.class) 这两个组成的- @AutoConfigurationPackage 是自动配置包,包括了一个 @Import 注解,给容器导入了自动配置包的注册器,AutoConfigurationPackages.Registrar.class:将主启动类的所在包及包下面所有子包里面的所有组件扫描到 Spring 容器
- @Import (AutoConfigurationImportSelector.class):导入自动配置导入选择器组件,AutoConfigurationImportSelector.class:自动配置导入选择器,主要是从类路径下的 META-INF/spring.factories 中获取资源
-
@ComponentScan
:扫描包,该注解默认会扫描该类所在的包下所有的配置类
# 启动流程
public ConfigurableApplicationContext run(String... args) { | |
// 创建 StopWatch,用于统计 Springboot 启动的耗时 | |
StopWatch stopWatch = new StopWatch(); | |
// 开始计时 | |
stopWatch.start(); | |
DefaultBootstrapContext bootstrapContext = createBootstrapContext(); | |
ConfigurableApplicationContext context = null; | |
configureHeadlessProperty(); | |
// 获取运行时监听器 | |
SpringApplicationRunListeners listeners = getRunListeners(args); | |
// 调用运行时监听器的 starting () 方法 | |
// 该方法需要在 Springboot 一启动时就调用,用于特别早期的初始化 | |
listeners.starting(bootstrapContext, this.mainApplicationClass); | |
try { | |
// 获取 args 参数对象 | |
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); | |
// 读取 Springboot 配置文件并创建 Environment 对象 | |
// 这里创建的 Environment 对象实际为 ConfigurableEnvironment | |
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments); | |
configureIgnoreBeanInfo(environment); | |
// 打印 Banner 图标 | |
Banner printedBanner = printBanner(environment); | |
// 创建 ApplicationContext 应用行下文,即创建容器 | |
context = createApplicationContext(); | |
context.setApplicationStartup(this.applicationStartup); | |
// 准备容器 | |
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner); | |
// 初始化容器 | |
refreshContext(context); | |
afterRefresh(context, applicationArguments); | |
// 停止计时 | |
stopWatch.stop(); | |
if (this.logStartupInfo) { | |
// 打印启动耗时等信息 | |
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch); | |
} | |
// 调用运行时监听器的 started () 方法 | |
// 该方法需要在应用程序启动后,CommandLineRunners 和 ApplicationRunners 被调用前执行 | |
listeners.started(context); | |
callRunners(context, applicationArguments); | |
} | |
catch (Throwable ex) { | |
handleRunFailure(context, ex, listeners); | |
throw new IllegalStateException(ex); | |
} | |
try { | |
// 调用运行时监听器的 running () 方法 | |
// 该方法需要在 SpringApplication 的 run () 方法执行完之前被调用 | |
listeners.running(context); | |
} | |
catch (Throwable ex) { | |
handleRunFailure(context, ex, null); | |
throw new IllegalStateException(ex); | |
} | |
return context; | |
} |
SpringApplication 在 run 方法中重点做了以下操作:
- 获取监听器和参数配置;
- 打印 Banner 信息;
- 创建并初始化容器;
- 监听器发送通知。
# Spring Cloud
Spring Cloud 是一整套基于 Spring Boot 的微服务解决方案。它为开发者提供了很多工具,用于快速构建分布式系统的一些通用模式,例如:配置管理、注册中心、服务发现、限流、网关、链路追踪等。
# 核心组件
- 注册中心组件(服务治理):Netflix Eureka;
- 负载均衡组件:Netflix Ribbon,各个微服务进行分摊,提高性能;
- 熔断器组件(断路器):Netflix Hystrix,Resilience4j ;保护系统,控制故障范围;
- 网关服务组件:Zuul,Spring Cloud Gateway;api 网关,路由,负载均衡等多种作用;
- 配置中心:Spring Cloud Config,将配置文件组合起来,放在远程仓库,便于管理;