主要内容出自:Java知识体系最强总结(2020版)
一、Spring Beans
1、容器中bean的生命周期
2、bean的作用域
Spring框架支持以下五种bean的作用域:
- singleton : bean在每个Spring ioc 容器中只有一个实例。
- prototype:一个bean的定义可以有多个实例。
- request:每次http请求都会创建一个bean,该作用域仅在基于web的Spring ApplicationContext情形下有效。
- session:在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。
- global-session:在一个全局的HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。
注意: 缺省的Spring bean 的作用域是Singleton
。使用 prototype 作用域需要慎重的思考,因为频繁创建和销毁 bean 会带来很大的性能开销。
3、Spring框架中的单例bean是线程安全的吗?
不是
在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域,因为Spring对一些Bean中非线程安全状态采用ThreadLocal
进行处理,解决线程安全问题。
二、IOC与DI
IOC 控制反转
“控制反转”概念就是对组件对象控制权的转移,从程序代码本身转移到了外部容器
IOC作用:
- 由容器管理对象的创建和整个生命周期,并进行依赖关系的维护。对象的创建并不是一件简单的事,在对象关系比较复杂时,如果依赖关系需要程序猿来维护的话,那是相当头疼的
- 解耦,由容器去维护具体的对象
IOC的实现方式:依赖注入(DI)和依赖查找
依赖注入(DI)
让容器全权负责依赖查询,受管组件只需要暴露JavaBean的setter方法
或者带参数的构造器
或者接口
,使容器可以在初始化时组装对象的依赖关系。
依赖注入与依赖查找方式相比,主要优势为:
- 查找定位操作与应用代码完全无关。
- 不依赖于容器的API,可以很容易地在任何容器以外使用应用对象。
- 不需要特殊的接口,绝大多数对象可以做到完全不必依赖容器。
IOC实现原理:
- 加载配置文件,解析成 BeanDefinition 放在 Map 里。
- 调用 getBean 的时候,从 BeanDefinition 所属的 Map 里,拿出 Class 对象进行实例化,同时,如果有依赖关系,将递归调用 getBean 方法 —— 完成依赖注入。
三、AOP
用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。可用于权限认证、日志、事务处理等。
1、AOP 有哪些实现方式?Spring AOP 和 AspectJ AOP 有什么区别?
AOP实现的关键在于 代理模式
,AOP代理主要分为静态代理和动态代理
。静态代理的代表为AspectJ;动态代理则以Spring AOP为代表。
-
AspectJ是静态代理的增强
,所谓静态代理,就是AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强,他会在编译阶段
将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象。 -
Spring AOP使用的动态代理
,所谓的动态代理就是说AOP框架不会去修改字节码,而是每次运行时
在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。
2、JDK动态代理和CGLIB动态代理的区别
Spring AOP中的动态代理主要有两种方式:JDK动态代理和CGLIB动态代理:
-
JDK动态代理只提供接口的代理,不支持类的代理。核心InvocationHandler接口和Proxy类,InvocationHandler 通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;接着,Proxy利用 InvocationHandler动态创建一个符合某一接口的的实例, 生成目标类的代理对象。
-
如果代理类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
-
就二者的效率来说,大部分情况都是 JDK 动态代理更优秀,随着 JDK 版本的升级,这个优势更加明显。
3、AOP中特定名词
-
切面(Aspect):切面是通知和切点的结合。 在Spring AOP中,切面可以使用通用类(基于模式的风格) 或者在普通类中以 @AspectJ 注解来实现。
-
连接点(Join point):指方法,在Spring AOP中,一个连接点 总是 代表一个方法的执行。连接点是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。
-
通知(Advice):在AOP术语中,切面的工作被称为通知。
-
切点(Pointcut):切点的定义会匹配通知所要织入的一个或多个连接点。我们通常使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法名称来指定这些切点。
-
引入(Introduction):引入允许我们向现有类添加新方法或属性。
-
目标对象(Target Object): 被一个或者多个切面(aspect)所通知(advise)的对象。它通常是一个代理对象。
-
织入(Weaving):织入是把切面应用到目标对象并创建新的代理对象的过程。
编译期:切面在目标类编译时被织入。AspectJ的织入编译器是以这种方式织入切面的。
类加载期:切面在目标类加载到JVM时被织入。需要特殊的类加载器,它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ5的加载时织入就支持以这种方式织入切面。
运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态地创建一个代理对象。SpringAOP就是以这种方式织入切面。
4、Spring AOP的通知类型
- 前置通知(Before):在目标方法被调用之前调用通知功能;
- 后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么;
- 返回通知(After-returning ):在目标方法成功执行之后调用通知;
- 异常通知(After-throwing):在目标方法抛出异常后调用通知;
- 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。
注意:环绕通知,最后不要忘了把结果return出去
同一个aspect,不同advice的执行顺序:
①没有异常情况下的执行顺序:
环绕通知 around before advice
前置通知 before advice
目标方法 target method 执行
环绕通知 around after advice
后置通知 after advice
返回通知 afterReturning
②有异常情况下的执行顺序:
环绕通知 around before advice
前置通知 before advice
目标方法 target method 执行
环绕通知 around after advice
后置通知 after advice
异常通知 afterThrowing
异常发生 java.lang.RuntimeException:
5、Spring AOP内部调用失效问题
一个需要进行AOP增强的类,外部调用methodA()且该方法中调用methodB(),调用methodB()不会执行AOP的增强逻辑。
原因:真正执行methodA()的是目标对象,那么methodA()中调用的methodB()是目标对象的方法,而不是代理对象的。也就自然不会执行AOP的增强逻辑
/**
* 目标对象类
*/
@Service
public class TestAopService {
public void methodA() {
System.out.println("method A run");
//内部调用方法B时AOP的增强处理方法不会执行
methodB();
}
public void methodB() {
System.out.println("method B run");
}
}
解决方法:
1、开启暴露代理对象
(1)开启暴露代理对象(expose-proxy=“true”),它使用的是ThreadLocal设计模式
(2)目标对象内部调用时获取代理对象,再调用代理对象的方法
<!-- 通过@Aspect注解实现AOP -->
<!-- proxy-target-class="true"表示使用CGlib动态代理 -->
<!-- expose-proxy="true"暴露代理对象 -->
<aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true"/>
import org.springframework.aop.framework.AopContext;
import org.springframework.stereotype.Service;
/**
* 目标对象类
* @author Gufung
*
*/
@Service
public class TestAopService {
public void methodA() {
System.out.println("method A run");
//methodB();
//从spring上下文获取代理对象执行方法
((TestAopService) AopContext.currentProxy()).methodB();
}
public void methodB() {
System.out.println("method B run");
}
}
2、利用 初始化方法 在目标对象中注入代理对象
在目标对象类中注入ApplicationContext,通过ApplicationContext获取代理对象,并调用代理对象的方法。
注意:该方案对于scope为prototype的bean无法适用,因为每次获取bean时都返回一个新的对象。
@Service
public class TestServiceImpl implements TestService {
@Autowired
private ApplicationContext context;
private TestService proxyObject;
@PostConstruct
// 初始化方法,在IOC注入完成后会执行该方法
private void setSelf() {
// 从spring上下文获取代理对象(直接通过proxyObject=this是不对的,this是目标对象)
// 此种方法不适合于prototype Bean,因为每次getBean返回一个新的Bean
proxyObject = context.getBean(TestService.class);
}
public void methodA() throws Exception {
System.out.println("method A run");
System.out.println("method A 中调用method B,通过注入的代理对象,调用代理对象的方法,解决内部调用实现的问题。");
proxyObject.methodB(); //调用代理对象的方法,解决内部调用失效的问题
}
public void methodB() {
System.out.println("method B run");
}
}
变形题
一个类有两个成员方法 A 和 B,两者都加了事务处理注解,定义了事务传播级别为 REQUIRES_NEW,问 A 方法内部直接调用 B 方法时能否触发事务处理机制。
Spring 的事务处理其实是通过AOP实现的,而实现AOP的方法有好几种,对于通过 Jdk 和 cglib 实现的 aop 处理,上述问题的答案为否,对于通过AspectJ实现的,上述问题答案为是。
Spring Boot教程(20) – 用AspectJ实现AOP内部调用
在一个Service内部,事务方法之间的嵌套调用,普通方法和事务方法之间的嵌套调用,都不会开启新的事务.
-
spring采用动态代理机制来实现事务控制,而动态代理最终都是要调用原始对象的,而原始对象在去调用方法时,是不会再触发代理了!
-
Spring的事务管理是通过AOP实现的,其AOP的实现对于非final类是通过cglib这种方式,即生成当前类的一个子类作为代理类,然后在调用其下的方法时,会判断这个方法有没有@Transactional注解,如果有的话,则通过动态代理实现事务管理(拦截方法调用,执行事务等切面)。当b()中调用a()时,发现b()上并没有@Transactional注解,所以整个AOP代理过程(事务管理)不会发生。
Spring事务管理嵌套事务详解 : 同一个类中,一个方法调用另外一个有事务的方法
四、Spring事务
Spring支持两种类型的事务管理:
编程式事务管理:这意味你通过编程的方式管理事务,给你带来极大的灵活性,但是难维护。
声明式事务管理:这意味着你可以将业务代码和事务管理分离,你只需用注解和XML配置来管理事务。
1、Spring事务的实现方式和实现原理
Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。真正的数据库层的事务提交和回滚是通过binlog、redo log、undo log实现的。
2、Spring事务传播行为
spring事务的传播行为说的是,当多个事务同时存在的时候,spring如何处理这些事务的行为。
-
PROPAGATION_REQUIRED
:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。 -
PROPAGATION_SUPPORTS
:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。 -
PROPAGATION_MANDATORY
:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。(mandatory) -
PROPAGATION_REQUIRES_NEW
:创建新事务,无论当前存不存在事务,都创建新事务。 -
PROPAGATION_NOT_SUPPORTED
:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 -
PROPAGATION_NEVER
:以非事务方式执行,如果当前存在事务,则抛出异常。 -
PROPAGATION_NESTED
:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED属性执行。
3、Spring事务隔离级别
spring 有五大隔离级别,默认值为 ISOLATION_DEFAULT(使用数据库的设置),其他四个隔离级别和数据库的隔离级别一致:
-
ISOLATION_DEFAULT
:用底层数据库的设置隔离级别,数据库设置的是什么我就用什么; -
ISOLATION_READ_UNCOMMITTED
:未提交读,最低隔离级别、事务未提交前,就可被其他事务读取(会出现幻读、脏读、不可重复读); -
ISOLATION_READ_COMMITTED
:提交读,一个事务提交后才能被其他事务读取到(会造成幻读、不可重复读),SQL server 的默认级别; -
ISOLATION_REPEATABLE_READ
:可重复读,保证多次读取同一个数据时,其值都和事务开始时候的内容是一致,禁止读取到别的事务未提交的数据(会造成幻读),MySQL 的默认级别; -
ISOLATION_SERIALIZABLE
:序列化,代价最高最可靠的隔离级别,该隔离级别能防止脏读、不可重复读、幻读。
4、Spring事务常用属性
属性名 | 说明 |
---|---|
propagation | 事务的传播行为,默认值为 REQUIRED |
isolation | 事务的隔离级别,默认值采用 DEFAULT |
timeout | 事务的超时时间,默认值为-1(不会超时)。如果超过该时间限制但事务还没有完成,则自动回滚事务 |
readOnly | 指定事务是否为只读事务,默认值为 false |
rollbackFor | 回滚规则,用于指定能够触发事务回滚的异常类型,并且可以指定多个异常类型 |
五、Spring如何解决循环依赖问题
循环依赖问题在Spring中主要有三种情况:
(1)通过构造方法进行依赖注入时产生的循环依赖问题。
(2)通过setter方法进行依赖注入且是在多例(原型)模式下产生的循环依赖问题。
(3)通过setter方法进行依赖注入且是在单例模式下产生的循环依赖问题。
在Spring中,只有第(3)种方式的循环依赖问题被解决了,其他两种方式在遇到循环依赖问题时都会产生异常。这是因为:
第一种构造方法注入的情况下,在new对象的时候就会堵塞住了,其实也就是”先有鸡还是先有蛋“的历史难题。
第二种setter方法(多例)的情况下,每一次getBean()时,都会产生一个新的Bean,如此反复下去就会有无穷无尽的Bean产生了,最终就会导致OOM问题的出现。
Spring在单例模式下的setter方法依赖注入引起的循环依赖问题,主要是通过二级缓存和三级缓存来解决的,其中三级缓存是主要的。解决的核心原理就是:在对象实例化之后,依赖注入之前,Spring提前暴露的Bean实例的引用在第三级缓存中进行存储。
/**
* 单例对象的缓存:bean 名称——bean 实例,即:所谓的单例池。
* 表示已经经历了完整生命周期的 Bean 对象
* <b>第一级缓存</b>
*/
Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/**
* 早期的单例对象的高速缓存:bean 名称——bean 实例。
* 表示 Bean 的生命周期还没走完(Bean 的属性还未填充)就把这个 Bean 存入该缓存中
* 也就是实例化但未初始化的 bean 放入该缓存里
*
* 存放早期暴露出来的 Bean 对象,Bean 的生命周期未结束(属性还未填充完),
* 可能是代理对象,也可能是原始对象
*
* <b>第二级缓存</b>
*/
Map<String, Object> earlySingletonObjects = new HashMap<>(16);
/**
* 单例工厂的高速缓存:bean 名称——ObjectFactory。
* 表示存放生成 bean 的工厂
* 工厂主要用来生成 Bean 的代理对象
* <b>第三级缓存</b>
*/
Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
(1)仅使用一级缓存可以吗?
不可以。 如果直接将未初始化完的 Bean 放在 singletonObjects
里面。当其他类需要使用这个Bean时,但这个Bean还未初始化完,可能会产生空指针异常。
我们以 A 、 B、C 举例子
- 先实例化 A 类,叫 a
- 将 a 放入 singletonObjects 中(此时 a 中的 b 属性还是空的呢)
- C 类需要使用 A 类,去 singletonObjects 获取,且获取到了 a
- C 类使用 a,拿出 a 类的 b 属性,然后 NullPointerException了
(2)使用二级缓存可以吗?
二级缓存可以解决循环依赖的问题
看完图,可能还会有疑惑,A 没初始化完成放入了缓存,那么 B 用的岂不是就是未完成的 A,是这样的没错! 在整个过程当中,A 是只有 1 个,而 B 那里的 A 只是 A 的引用,所以后面 A 完成了初始化,B 中的 A 自然也就完成了。
(3)仅使用二级缓存,存在的问题
Spring 中 Bean 的管理
由 Spring 来管理 Bean,要经过的主要步骤有:
- Spring 加载配置文件,扫描哪些类由 Spring 来管理,并为每个类生成一个 BeanDefintion,里面封装了类的一些信息,如全限定类名、哪些属性、是否单例等等
- 根据 BeanDefintion 的信息,通过反射,去实例化 Bean(此时就是实例化但未初始化 的 Bean)
- 填充上述未初始化对象中的属性(依赖注入)
- 如果上述未初始化对象中的方法被 AOP 了,那么就需要生成代理类(也叫包装类)
- 最后将完成初始化的对象存入缓存中( singletonObjects),下次使用从缓存获取
二级缓存的问题
如果 Bean 没有 AOP,那么用二级缓存其实没有什么问题的,一旦有上述生命周期中第四步,就会导致的一个问题。因为 AOP 处理后,往往是需要生成代理对象的,代理对象和原来的对象根本就不是 1 个对象。
- 生成 a 的实例,然后放入缓存,a 需要 b
- 再生成 b ,填充 b 的时候,需要 a,从缓存中取到了 a,完成 b 的初始化;
- 紧接着 a 把初始化好的 b 拿过来用,完成 a 的属性填充和初始化
- 由于 A 类涉及到了 AOP,再然后 a 要生成一个代理类:代理 a
a 最终的产物是代理 a,那 b 中其实也应该用代理 a,而现在 b 中用的却是原始的 a 。
代理 a 和原始的 a 不是一个对象,现在这就有问题了。
(4)三级缓存
singletonFactories 中存的是某个 beanName 及对应的 ObjectFactory,这个 ObjectFactory 其实就是生成这个 Bean 的工厂。
实际中,这个 ObjectFactory 是个 Lambda 表达式:() -> getEarlyBeanReference(beanName, mbd, bean)
,而且,这个表达式并没有执行。
getEarlyBeanReference
核心就是两步:
- 根据 beanName 将它对应的实例化后且未初始化完的 Bean,存入
Map<Object, Object> earlyProxyReferences = new ConcurrentHashMap<>(16);
。这个earlyProxyReferences
其实就是用于记录哪些 Bean 执行过 AOP,防止后期再次对 Bean 进行 AOP - 生成该 Bean 对应的代理类返回
那么 getEarlyBeanReference 什么时候被触发,什么时候执行?
-
在二级缓存示例中,填充 B 的属性时候,需要 A,
-
然后去缓存中拿 A,此时先去第三级缓存中去取 A,如果存在,此时就执行
getEarlyBeanReference
函数,然后该函数就会返回 A 对应的代理对象。 -
后续再将该代理对象放入第二级缓存中,也就是
Map<String, Object> earlySingletonObjects
里。
(5)三级缓存之间的转换
假如 A 经过第三级缓存,获得代理对象,这个代理对象仍然是未初始化完的。那么就暂时把这个代理对象放入第二级缓存,然后删除该代理对象原本在第三级缓存中的数据(确保后期不会每次都生成新的代理对象),后面其他类要用了 A,就去第二级缓存中找,就获取到了 A 的代理对象,而且都用的是同一个 A 的代理对象,这样后面只需要对这一个代理对象进行完善,其他引入该代理对象的类就都完善了。
再往后面,继续完成 A 的初始化,那么先判断 A 是否存在于 earlyProxyReferences 中, 存在就说明 A 已经经历过 AOP 了,就无须再次 AOP。那 A 就从二级缓存中获取,把 A 的代理类拿出来,填充代理类的属性。
完成后再将 A 的代理对象加入到第一级缓存,再把它原本在第二级缓存中的数据删掉,确保后面还用到 A 的类,直接从第一级缓存中获取。
六、Spring MVC的控制器是不是单例模式,如果是,有什么问题,怎么解决?
是单例模式,所以在多线程访问的时候有线程安全问题。不要用同步,会影响性能,解决方案是在控制器里面不能写字段。
七、SpringMVC工作流程
(1)用户发送请求至前端控制器(DispatcherServlet);
(2)前端控制器收到请求后,调用处理器映射器(HandlerMapping);
(3)处理器映射器根据请求url找到具体的处理器,生成处理器对象和处理器拦截器(如果有则生成),并返回给前端控制器;
(4)前端控制器调用处理器适配器(HandlerAdapter);
(5)处理器适配器经过适配调用 具体处理器(Handler,也叫后端控制器);
(6)处理器执行完成返回ModelAndView;
(7)处理器适配器将处理器的执行结果ModelAndView返回给前端控制器;
(8)前端控制器将ModelAndView传给视图解析器(ViewResolver)进行解析;
(9)视图解析器解析后返回具体View;
(10)前端控制器对View进行渲染视图(即将模型数据填充至视图中)
(11)前端控制器响应用户。
八、SpringBoot自动装配原理
-
@SpringBootApplication
是自动装配的核心,它由@Configuration
、@EnableAutoConfiguration
、@ComponentScan
注解组成。其中@EnableAutoConfiguration
是关键。@EnableAutoConfiguration
:启用 SpringBoot 的自动配置机制
@Configuration
:允许在上下文中注册额外的 bean 或导入其他配置类
@ComponentScan
: 扫描被@Component (@Service,@Controller)注解的 bean,注解默认会扫描启动类所在的包下所有的类 ,可以自定义不扫描某些 bean。 -
@EnableAutoConfiguration
导入自动配置文件选择器,获取所有候选的配置 -
通过
spring.factories
获取配置类的位置 -
根据
@ConditionalOnClass
检测相关类是否存在,如果不存在,则不加载该配置文件 -
如果存在,则获取配置类,在上层方法中封装为
Properties
供用户使用
九、@Autowired和@Resource之间的区别
@Autowired是Spring提供的注解,@Resource是JDK提供的注解
(1)@Autowired的执行流程
- 先按照类型匹配,即按照当前bean类型寻找是否存在指定类型的bean
- 如果找到唯一的bean直接注入
- 如果找不到或找到多个则开始按照id注入(名字)
- 找到唯一的则注入,找不到则抛出异常,不可能找到多个。
@Autowired和@Qualifier一起使用
- 此注解可以配合@Autowired使用
- 一旦同时配置@Autowired和@Qualifier,则不再按照类型去匹配,直接按照id匹配
- 如果按照id能找到就注入,找不到就抛出异常
- 此注解可以指定要使用的id,如果不指定默认是空串,会抛出异常
required属性
- @Autowired(required=true):当使用@Autowired注解的时候,其实默认就是@Autowired(required=true),表示注入的时候,该bean必须存在,否则就会注入失败。
- @Autowired(required=false):表示忽略当前要注入的bean,如果有直接注入,没有跳过,不会报错。
(2)@Resource的执行流程
- 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常
- 如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常
- 如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常
- 如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则按类型进行匹配,如果匹配则自动装配;
十、BeanFactory 和 ApplicationContext有什么区别?
BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器。其中ApplicationContext是BeanFactory的子接口
依赖关系
BeanFactory:是Spring里面最底层的接口,包含了各种Bean的定义,读取bean配置文档,管理bean的加载、实例化,控制bean的生命周期,维护bean之间的依赖关系。
ApplicationContext接口作为BeanFactory的派生,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能:
- 继承MessageSource,因此支持国际化。
- 统一的资源文件访问方式。
- 提供在监听器中注册bean的事件。
- 同时加载多个配置文件。
- 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层。
加载方式
BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。这样,我们就不能发现一些存在的Spring的配置问题。如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常。
ApplicationContext在容器启动时,一次性创建了所有的单实例Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。 ApplicationContext启动后预载入所有的单实例Bean,通过预载入单实例bean ,确保当你需要的时候,你就不用等待,因为它们已经创建好了。
相对于基本的BeanFactory,ApplicationContext 唯一的不足是占用内存空间。当应用程序配置Bean较多时,程序启动较慢。
创建方式
BeanFactory通常以编程的方式被创建,ApplicationContext还能以声明的方式创建,如使用ContextLoader
注册方式
BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册。
十一、为什么要用Spring Boot的Starter ,而不是和普通的maven一样导入
可以认为starter是一种服务(和JS的插件类似)——使得使用某个功能的开发者不需要关注各种依赖库的处理,不需要具体的配置信息,由Spring Boot自动通过classpath路径下的类发现需要的Bean,并织入bean
即SpringBoot的自动配置功能
十二、让你写个starter 你会怎么写?
1、编写自己的properties类(用来加载属性文件进行默认的配置)和核心服务类(要自动配置的bean)
2、自定义AutoConfiguration 配置类CustomerAutoConfiguration ,通过@Condition*系列注解控制自动配置的条件。
3、然后在src/main/resources新建文件夹META-INF,然后新建一个spring.factories文件
在里面使用org.springframework.boot.autoconfigure.EnableAutoConfiguration指定我们自定义的自动配置类的全路径。
这样,一个自定义的spring-boot-start工程就完成了。
SpringBoot——自定义一个spring-boot-starter包
十三、Spring中用到的设计模式
工厂模式 : Spring使用工厂模式通过 BeanFactory、ApplicationContext 创建 bean 对象。
代理模式 : Spring AOP 功能的实现。
单例模式 : Spring 中的 Bean 默认都是单例的。
模板方法模式 : Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。
适配器模式 :Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配Controller。(springmvc流程—>处理器适配器)
装饰者模式 : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个应用。