IoC
控制反转,Ioc意味着将对象交给容器控制,而不是传统的在你的对象内部直接控制。
并管理它们的完整生命周期。降低了耦合度
构成了Spring框架IoC容器的基础:
- org.springframework.beans 包
- org.springframework.context包
配置注入方式
将Spring配置到应用开发中有以下三种方式:
- 基于XML的配置
- 基于注解的配置
主要的命名空间:context、beans、jdbc、tx、aop、mvc和aso
- 基于Java的配置
什么是依赖注入?
在依赖注入中,您不必创建对象,但必须描述如何创建它们。您不是直接在代码中将组件和服务连接在
一起,而是描述配置文件中哪些组件需要哪些服务。由 IoC容器将它们装配在一起。
可以通过多少种方式完成依赖注入?
在Java中依赖注入有以下三种实现方式:
1. 构造器注入
2. Setter方法注入
3. 注解注入
对于依赖关系无需变化的注入,尽量采用构造注入;而其他的依赖关系的注入,则考虑采用设值注入。
IoC 容器
BeanFactory就像一个包含bean集合的工厂类。它会在客户端要求时实例化bean。ApplicationContext-ApplicationContext接口扩展了BeanFactory接口。它在BeanFactory基础上提供了一些额外的功能。
BeanFactory | ApplicationContext |
---|---|
它使用懒加载 | 它使用即时加载 |
它使用语法显式提供资源对象 | 它自己创建和管理资源对象 |
不支持国际化 | 支持国际化 |
不支持基于依赖的注解 | 支持基于依赖的注解 |
列举** IoC **的一些好处。
它将最小化应用程序中的代码量。
它将使您的应用程序易于测试,因为它不需要单元测试用例中的任何单例或 JNDI 查找机制。
它以最小的影响和最少的侵入机制促进松耦合。
实现原理
1、xml配置文件
2、dom4j解析XML文件
3、工厂模式
4、反射机制
IoC实现的两种方式
1. BeanFactory接口
org.springframework.beans.factory.BeanFactory
BeanFactory接口是Spring IoC 容器的核心接口。 内部使用的接口
BeanFactory 接口提供了一个先进的配置机制,使得任何类型的对象的配置成为可能。
采用懒加载, 加载配置文件时不创建对象, 一般在使用时才会创建对象
2. ApplicationContext接口
开发人员使用
ApplicationContex接口对BeanFactory(是一个子接口)进行了扩展,在BeanFactory的基础上添加了其他功能,比如与Spring的AOP更容易集成,也提供了处理message resource的机制(用于国际化)、事件传播以及应用层的特别配置,比如针对Web应用的WebApplicationContext。
加载配置文件时创建对象
BeanFactory和ApplicationContext有什么区别?
BeanFactory 可以理解为含有bean集合的工厂类。
- BeanFactory 包含了种bean的定义,以便在接收到客户端请求时将对应的bean实例化。
- BeanFactory还能在实例化对象的时生成协作类之间的关系。此举将bean自身与bean客户端的配置中解放出来。
- BeanFactory还包含了bean生命周期的控制,调用客户端的初始化方法(initialization methods)和销毁方法(destruction methods)。
从表面上看,application context如同bean factory一样具有bean定义、bean关联关系的设置,根据请求分发bean的功能。但application context在此基础上还提供了其他的功能。
1.提供了支持国际化的文本消息
2.统一的资源文件读取方式
3.已在监听器中注册的bean的事件
以下是三种较常见的 ApplicationContext 实现方式:
1、ClassPathXmlApplicationContext:从classpath的XML配置文件中读取上下文,并生成上下文定义。应用程序上下文从程序环境变量中取得。ApplicationContext context = new ClassPathXmlApplicationContext(“bean.xml”);
2、FileSystemXmlApplicationContext :由文件系统中的XML配置文件读取上下文。ApplicationContext context = new FileSystemXmlApplicationContext(“bean.xml”);
3、XmlWebApplicationContext:由Web应用的XML文件读取上下文。
Bean
什么是 Bean
它们是构成用户应用程序主干的对象。
Bean由SpringIoC容器管理。
它们由SpringIoC容器实例化,配置,装配和管理。Bean是基于用户提供给容器的配置元数据创建。
- Bean : 在配置中定义的Bean的类型就是返回的类型
- FactoryBean :
Bean的创建过程????????
什么是BeanDefinition
BeanDefinition——spring bean的建模对象
BeanDefintion可以描述springbean当中的scope、lazy,以及属性和方法等等其他信息
https://blog.csdn.net/java_lyvee/article/details/102633067Spring Bean 作用域
Singleton(默认)-每个SpringIoC容器仅有一个单实例。加载配置文件时创建
Prototype-每次请求都会产生一个新的实例。 懒加载
Request-每一次网络请求都会产生一个新的实例,并且该bean仅在当前网络请求内有效。
Session-每一次网络请求都会产生一个新的bean,同时该bean仅在当前session内有效。
Global-session-类似于标准的HTTPSession作用域,不过它仅仅在基于portlet的web应用中才有意义。
仅当用户使用支持Web的ApplicationContext时,最后三个才可用。
Portlet规范定义了全局Session的概念,它被所有构成某个portletweb应用的各种不同的portlet所共享。在globalsession作用域中定义的bean被限定于全局portletSession的生命周期范围内。如果你在web中使用globalsession作用域来标识bean,那么web会自动当成session类型来使用。
单例 Bean 的优点
1.减少了新生成实例的消耗
新生成实例消耗包括两方面,第一,spring会通过反射或者cglib来生成bean实例这都是耗性能的操作,其次给对象分配内存也会涉及复杂算法。
2.减少jvm垃圾回收
由于不会给每个请求都新生成bean实例,所以自然回收的对象少了。
3.可以快速获取到bean
因为单例的获取bean操作除了第一次生成之外其余的都是从缓存里获取的所以很快。
单例Beans不是线程安全的
单例的bean一个很大的劣势就是他不能做到线程安全!!!,由于所有请求都共享一个bean实例,所以这个bean要是有状态的一个bean的话可能在并发场景下出现问题,而原型的bean则不会有这样问题(但也有例外,比如他被单例bean依赖),因为给每个请求都新创建实例。
但实际上,大部分的Spring bean并没有可变的状态(比如Serview类和DAO类)
最浅显的解决办法就是将多态bean的作用域由“singleton”变更为“prototype”。
Spring Bean 的生命周期
Spring bean factory 负责管理在spring容器中被创建的bean的生命周期。Bean的生命周期由两组回调(call back)方法组成。
- 初始化之后调用的回调方法。
- 销毁之前调用的回调方法。
Spring框架提供了以下四种方式来管理bean的生命周期事件:
InitializingBean和DisposableBean回调接口
针对特殊行为的其他Aware接口
Bean配置文件中的Custom init()方法和destroy()方法
@PostConstruct和@PreDestroy注解方式
使用customInit()和 customDestroy()方法管理bean生命周期的代码样例如下:
<beans>
<bean id="demoBean" class="com.howtodoinjava.task.DemoBean"
init-method="customInit" destroy-method="customDestroy"></bean>
</beans>
被Spring管理的对象叫做Bean。Bean的生成步骤如下:
- Spring扫描class得到BeanDefinition
- 根据得到的BeanDefinition去生成bean
- 初始化 首先根据class推断构造方法
实现InitializingBean接口,覆盖 afeterSetProperties 方法
配置 init 方法
- 根据推断出来的构造方法,反射,得到一个对象(暂时叫做原始对象)
- 填充原始对象中的属性(依赖注入)
- BeanPostProcessor 如果原始对象中的某个方法被AOP了,那么则需要根据原始对象生成一个代理对象
- 把最终生成的代理对象放入单例池(源码中叫做singletonObjects)中,下次getBean时就直接从单例池拿即可
key:beanName,value:对象
- 销毁
实现DisposableBean接口
生命周期: https://www.jianshu.com/p/1dec08d290c1
链接
初始化
InitializingBean
InitializingBean接口为bean提供了初始化方法的方式,它只包括afterPropertiesSet方法,凡是继承该接口的类,在初始化bean的时候都会执行该方法。
public interface InitializingBean {
void afterPropertiesSet() throws Exception;
}
Spring加载bean的源码类AbstractAutowiredCapableBeanFactory
protected void invokeInitMethods(String beanName, final Object bean, RootBeanDefinition mbd) throws Throwable {
//判断该bean是否实现了实现了InitializingBean接口,如果实现了InitializingBean接口,则只掉调用bean的afterPropertiesSet方法
boolean isInitializingBean = (bean instanceof InitializingBean);
if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
if (logger.isDebugEnabled()) {
logger.debug("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
}
if (System.getSecurityManager() != null) {
try {
AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
public Object run() throws Exception {
//直接调用afterPropertiesSet
((InitializingBean) bean).afterPropertiesSet();
return null;
}
},getAccessControlContext());
} catch (PrivilegedActionException pae) {
throw pae.getException();
}
}
else {
//直接调用afterPropertiesSet
((InitializingBean) bean).afterPropertiesSet();
}
}
if (mbd != null) {
String initMethodName = mbd.getInitMethodName();
//判断是否指定了init-method方法,如果指定了init-method方法,则再调用制定的init-method
if (initMethodName != null && !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
!mbd.isExternallyManagedInitMethod(initMethodName)) {
//进一步查看该方法的源码,可以发现init-method方法中指定的方法是通过反射实现
invokeCustomInitMethod(beanName, bean, mbd);
}
}
}
1、Spring为bean提供了两种初始化bean的方式,实现InitializingBean接口,实现afterPropertiesSet方法,或者在配置文件中通过init-method指定,两种方式可以同时使用。
2、实现InitializingBean接口是直接调用afterPropertiesSet方法,比通过反射调用init-method指定的方法效率要高一点,但是init-method方式消除了对spring的依赖。
3、如果调用afterPropertiesSet方法时出错,则不调用init-method指定的方法。
BeanFactoryPostProcessor和BeanPostProcessor
Spring容器的扩展点BeanFactoryPostProcessor
可以修改bean的定义(甚至改懒加载配置,class,构造器调用)
BeanPostProcessor:
https://www.jianshu.com/p/a90a3e617ba6
https://blog.csdn.net/caihaijiang/article/details/35552859
使用过程中会有这样的几个问题
1、可不可以在BeanFactoryPostProcessor去创建一个Bean,这样有什么问题?
从技术上来说这样是可以的,但是正常情况下我们不该这样做,这是因为可能会存在该执行的Bean工厂的后置处理器的逻辑没有被应用到这个Bean上。
2、BeanFactoryPostProcessor可以被配置为懒加载吗?
不能配置为懒加载,即使配置了也不会生效。我们将Bean工厂后置处理器配置为懒加载这个行为就没有任何意义
@ImportSelector
https://blog.csdn.net/elim168/article/details/88131614
循环依赖
单例Bean不存在循环依赖问题,
// 相互依赖
@Component
public class A {
// A中注入了B
@Autowired
private B b;
}
@Component
public class B {
// B中也注入了A
@Autowired
private A a;
}
// 依赖自己
@Component
public class A {
// A中注入了A
@Autowired
private A a;
}
A的Bean在创建过程中,在进行依赖注入之前,先把A的原始Bean放入Map缓存(提早暴露,只要放到缓存了,其他Bean需要时就可以从缓存中拿了),放入缓存后,再进行依赖注入,在后续的生命周期中操作引用
从上面这个分析过程中可以得出,只需要一个缓存就能解决循环依赖了,那么为什么Spring中还需要singletonFactories呢?
这是难点,基于上面的场景想一个问题:如果A的原始对象注入给B的属性之后,A的原始对象进行了AOP产生了一个代理对象,此时就会出现,对于A而言,它的Bean对象其实应该是AOP之后的代理对象,而B的a属性对应的并不是AOP之后的代理对象,这就产生了冲突。
1、Spring到底是如何解决循环依赖的呢,这里来一波文字的总结:
Spring通过三级缓存解决了循环依赖,
其中一级缓存为单例池(singletonObjects),
二级缓存为早期曝光对象earlySingletonObjects,
三级缓存为早期曝光对象工厂(singletonFactories)。
当A、B两个类发生循环引用时,在A完成实例化后,就使用实例化后的对象去创建一个对象工厂,并添加到三级缓存中,如果A被AOP代理,那么通过这个工厂获取到的就是A代理后的对象,如果A没有被AOP代理,那么这个工厂获取到的就是A实例化的对象。
当A进行属性注入时,会去创建B,同时B又依赖了A,所以创建B的同时又会去调用getBean(a)来获取需要的依赖,此时的getBean(a)会从缓存中获取,第一步,先获取到三级缓存中的工厂;第二步,调用对象工工厂的getObject方法来获取到对应的对象,得到这个对象后将其注入到B中。紧接着B会走完它的生命周期流程,包括初始化、后置处理器等。
当B创建完后,会将B再注入到A中,此时A再完成它的整个生命周期。至此,循环依赖结束!
2、为啥要用三级缓存,是否可以用二级缓存
在普通的循环依赖的情况下,三级缓存没有任何作用。三级缓存实际上跟Spring中的AOP相关。AOP场景下的getEarlyBeanReference 会拿到一个代理的对象,但是不确定有没有依赖,需不需要用到这个依赖对象,所以先给一个工厂放到三级缓存里。
这个工厂的目的在于延迟对实例化阶段生成的对象的代理,只有真正发生循环依赖的时候,才去提前生成代理对象,否则只会创建一个工厂并将其放入到三级缓存中,但是不会去通过这个工厂去真正创建对象。
属性注入
自动装配
@Autowired:根据属性类型自动装配
@javax.annotation.Resource: 可以根据类型注入,可以根据名称注入
@Qualifier: 根据名称注入和@Autowired一起使用
@Value: 注入普通类型
根据名称
根据类型