1.Spring的生命周期
一个受 Spring 管理的 bean,生命周期主要阶段有
创建:根据 bean 的构造方法或者工厂方法来创建 bean 实例对象
依赖注入:根据 @Autowired,@Value 或其它一些手段,为 bean 的成员变量填充值、建立关系
初始化:回调各种 Aware 接口,调用对象的各种初始化方法
销毁:在容器关闭时,会销毁所有单例对象(即调用它们的销毁方法)
prototype 对象也能够销毁,不过需要容器这边主动调
一些资料会提到,生命周期中还有一类 bean 后处理器:BeanPostProcessor,会在 bean 的初始化的前后,提供一些扩展逻辑。但这种说法是不完整的
更加详细的过程如下:
创建前后的增强
postProcessBeforeInstantiation
这里返回的对象若不为 null 会替换掉原本的 bean,并且仅会走 postProcessAfterInitialization 流程
postProcessAfterInstantiation
这里如果返回 false 会跳过依赖注入阶段
依赖注入前的增强
postProcessProperties
如 @Autowired、@Value、@Resource
初始化前后的增强
postProcessBeforeInitialization
这里返回的对象会替换掉原本的 bean
如 @PostConstruct、@ConfigurationProperties
postProcessAfterInitialization
这里返回的对象会替换掉原本的 bean
如代理增强
销毁之前的增强
postProcessBeforeDestruction
2.Spring注解
2.1、声明bean的注解
@Component 组件,没有明确的角色
@Service 在业务逻辑层使用(service层)
@Repository 在数据访问层使用(dao层)
@Controller 在展现层使用,控制器的声明(C)
2.2、注入bean的注解
@Autowired:由Spring提供
@Inject:由JSR-330提供
@Resource:由JSR-250提供
都可以注解在set方法和属性上,推荐注解在属性上(一目了然,少写代码)。
2.3、java配置类相关注解
@Configuration 声明当前类为配置类,相当于xml形式的Spring配置(类上)
@Bean 注解在方法上,声明当前方法的返回值为一个bean,替代xml中的方式(方法上)
@Configuration 声明当前类为配置类,其中内部组合了@Component注解,表明这个类是一个bean(类上)
@ComponentScan 用于对Component进行扫描,相当于xml中的(类上)
@WishlyConfiguration 为@Configuration与@ComponentScan的组合注解,可以替代这两个注解
2.4、切面(AOP)相关注解
Spring支持AspectJ的注解式切面编程。
@Aspect 声明一个切面(类上) 使用@After、@Before、@Around定义建言(advice),可直接将拦截规则(切点)作为参数。
@After 在方法执行之后执行(方法上) @Before 在方法执行之前执行(方法上) @Around 在方法执行之前与之后执行(方法上)
@PointCut 声明切点 在java配置类中使用@EnableAspectJAutoProxy注解开启Spring对AspectJ代理的支持(类上)
2.5、@Bean的属性支持
@Scope 设置Spring容器如何新建Bean实例(方法上,得有@Bean) 其设置类型包括:
Singleton (单例,一个Spring容器中只有一个bean实例,默认模式), Protetype (每次调用新建一个bean), Request (web项目中,给每个http request新建一个bean), Session (web项目中,给每个http session新建一个bean), GlobalSession(给每一个 global http session新建一个Bean实例)
@StepScope 在Spring Batch中还有涉及
@PostConstruct 由JSR-250提供,在构造函数执行完之后执行,等价于xml配置文件中bean的initMethod
@PreDestory 由JSR-250提供,在Bean销毁之前执行,等价于xml配置文件中bean的destroyMethod
2.6、@Value注解
@Value 为属性注入值(属性上) 支持如下方式的注入: 》注入普通字符
@Value("Michel Jackson")
String name;
》注入操作系统属性
@Value("#{systemProperties['os.name']}")
String osName;
》注入表达式结果
@Value("#{ T(java.lang.Mathe).random()*10}")
String randomNuber;
》注入其它bean属性
@Value("#{domeClass.name})
String name;
》注入文件资源
@Value("classpath:com.hgs/hello/test.txt")
String resourceFile;
》注入网站资源
@Value("http://www.fangzhipeng.com")
Resource url;
》注入配置文件
@Value ("${book.name}")
String bookName;
注入配置使用方法:
编写配置文件(test.properties)
book.name=《三体》
@PropertySource 加载配置文件(类上)
@PropertySource("classpath:com/hgs/hello/test.txt")
还需配置一个PropertySourcesPlaceholderConfigurer的bean。
2.7、环境切换
@Profile 通过设定Environment的ActiveProfiles来设定当前context需要使用的配置环境。(类或方法上)
@Conditional Spring4中可以使用此注解定义条件话的bean,通过实现Condition接口,并重写matches方法,从而决定该bean是否被实例化。(方法上)
2.8、异步相关
@EnableAsync 配置类中,通过此注解开启对异步任务的支持,叙事性AsyncConfigurer接口(类上)
@Async 在实际执行的bean方法使用该注解来申明其是一个异步任务(方法上或类上所有的方法都将异步,需要@EnableAsync开启异步任务)
2.9、定时任务相关
@EnableScheduling 在配置类上使用,开启计划任务的支持(类上)
@Scheduled 来申明这是一个任务,包括cron,fixDelay,fixRate等类型(方法上,需先开启计划任务的支持)
2.10、@Enable*注解说明
这些注解主要用来开启对xxx的支持。 @EnableAspectJAutoProxy 开启对AspectJ自动代理的支持
@EnableAsync 开启异步方法的支持
@EnableScheduling 开启计划任务的支持
@EnableWebMvc 开启Web MVC的配置支持
@EnableConfigurationProperties 开启对@ConfigurationProperties注解配置Bean的支持
@EnableJpaRepositories 开启对SpringData JPA Repository的支持
@EnableTransactionManagement 开启注解式事务的支持
@EnableTransactionManagement 开启注解式事务的支持
@EnableCaching 开启注解式的缓存支持
2.11、测试相关注解
@RunWith 运行器,Spring中通常用于对JUnit的支持
@Runwith(SpringJunit4ClassRunner.class)
@ContextConfiguration 用来加载配置ApplicationContext,其中classes属性用来加载配置类
@ContextCongireation(classes={Testconfig.class})
3.Spring的后置处理器
BeanFactory 的后置处理器BeanPostProcessor自增强功能
Bean 的后置处理器BeanPostProcessor自增强功能
4.Spring循环依赖
要求
掌握单例 set 方式循环依赖的原理
掌握其它循环依赖的解决方法
循环依赖的产生
首先要明白,bean 的创建要遵循一定的步骤,必须是创建、注入、初始化三步,这些顺序不能乱
set 方法(包括成员变量)的循环依赖如图所示
可以在【a 创建】和【a set 注入 b】之间加入 b 的整个流程来解决
【b set 注入 a】 时可以成功,因为之前 a 的实例已经创建完毕
a 的顺序,及 b 的顺序都能得到保障
构造方法的循环依赖如图所示,显然无法用前面的方法解决
构造循环依赖的解决
思路1
a 注入 b 的代理对象,这样能够保证 a 的流程走通
后续需要用到 b 的真实对象时,可以通过代理间接访问
思路2
a 注入 b 的工厂对象,让 b 的实例创建被推迟,这样能够保证 a 的流程先走通
后续需要用到 b 的真实对象时,再通过 ObjectFactory 工厂间接访问
示例1:用 @Lazy 为构造方法参数生成代理
public class App60_1 {
static class A {
private static final Logger log = LoggerFactory.getLogger("A");
private B b;
public A(@Lazy B b) {
log.debug("A(B b) {}", b.getClass());
this.b = b;
}
@PostConstruct
public void init() {
log.debug("init()");
}
}
static class B {
private static final Logger log = LoggerFactory.getLogger("B");
private A a;
public B(A a) {
log.debug("B({})", a);
this.a = a;
}
@PostConstruct
public void init() {
log.debug("init()");
}
}
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("a", A.class);
context.registerBean("b", B.class);
AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());
context.refresh();
System.out.println();
}
}
示例2:用 ObjectProvider 延迟依赖对象的创建
public class App60_2 {
static class A {
private static final Logger log = LoggerFactory.getLogger("A");
private ObjectProvider<B> b;
public A(ObjectProvider<B> b) {
log.debug("A({})", b);
this.b = b;
}
@PostConstruct
public void init() {
log.debug("init()");
}
}
static class B {
private static final Logger log = LoggerFactory.getLogger("B");
private A a;
public B(A a) {
log.debug("B({})", a);
this.a = a;
}
@PostConstruct
public void init() {
log.debug("init()");
}
}
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("a", A.class);
context.registerBean("b", B.class);
AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());
context.refresh();
System.out.println(context.getBean(A.class).b.getObject());
System.out.println(context.getBean(B.class));
}
}
示例3:用 @Scope 产生代理
public class App60_3 {
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context.getDefaultListableBeanFactory());
scanner.scan("com.itheima.app60.sub");
context.refresh();
System.out.println();
}
}
@Component
class A {
private static final Logger log = LoggerFactory.getLogger("A");
private B b;
public A(B b) {
log.debug("A(B b) {}", b.getClass());
this.b = b;
}
@PostConstruct
public void init() {
log.debug("init()");
}
}
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
@Component
class B {
private static final Logger log = LoggerFactory.getLogger("B");
private A a;
public B(A a) {
log.debug("B({})", a);
this.a = a;
}
@PostConstruct
public void init() {
log.debug("init()");
}
}
示例4:用 Provider 接口解决,原理上与 ObjectProvider 一样,Provider 接口是独立的 jar 包,需要加入依赖
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
public class App60_4 {
static class A {
private static final Logger log = LoggerFactory.getLogger("A");
private Provider<B> b;
public A(Provider<B> b) {
log.debug("A({}})", b);
this.b = b;
}
@PostConstruct
public void init() {
log.debug("init()");
}
}
static class B {
private static final Logger log = LoggerFactory.getLogger("B");
private A a;
public B(A a) {
log.debug("B({}})", a);
this.a = a;
}
@PostConstruct
public void init() {
log.debug("init()");
}
}
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("a", A.class);
context.registerBean("b", B.class);
AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());
context.refresh();
System.out.println(context.getBean(A.class).b.get());
System.out.println(context.getBean(B.class));
}
}
解决 set 循环依赖的原理
一级缓存
作用是保证单例对象仅被创建一次
第一次走
getBean("a")
流程后,最后会将成品 a 放入 singletonObjects 一级缓存后续再走
getBean("a")
流程时,先从一级缓存中找,这时已经有成品 a,就无需再次创建
一级缓存与循环依赖
一级缓存无法解决循环依赖问题,分析如下
无论是获取 bean a 还是获取 bean b,走的方法都是同一个 getBean 方法,假设先走
getBean("a")
当 a 的实例对象创建,接下来执行
a.setB()
时,需要走getBean("b")
流程,红色箭头 1当 b 的实例对象创建,接下来执行
b.setA()
时,又回到了getBean("a")
的流程,红色箭头 2但此时 singletonObjects 一级缓存内没有成品的 a,陷入了死循环
二级缓存
解决思路如下:
再增加一个 singletonFactories 缓存
在依赖注入前,即
a.setB()
以及b.setA()
将 a 及 b 的半成品对象(未完成依赖注入和初始化)放入此缓存执行依赖注入时,先看看 singletonFactories 缓存中是否有半成品的对象,如果有拿来注入,顺利走完流程
对于上面的图
a = new A()
执行之后就会把这个半成品的 a 放入 singletonFactories 缓存,即factories.put(a)
接下来执行
a.setB()
,走入getBean("b")
流程,红色箭头 3这回再执行到
b.setA()
时,需要一个 a 对象,有没有呢?有!factories.get()
在 singletonFactories 缓存中就可以找到,红色箭头 4 和 5b 的流程能够顺利走完,将 b 成品放入 singletonObject 一级缓存,返回到 a 的依赖注入流程,红色箭头 6
二级缓存与创建代理
“img/image-20210903103030877.png” could not be found.
二级缓存无法正确处理循环依赖并且包含有代理创建的场景,分析如下
spring 默认要求,在
a.init
完成之后才能创建代理pa = proxy(a)
由于 a 的代理创建时机靠后,在执行
factories.put(a)
向 singletonFactories 中放入的还是原始对象接下来箭头 3、4、5 这几步 b 对象拿到和注入的都是原始对象
三级缓存
“img/image-20210903103628639.png” could not be found.
简单分析的话,只需要将代理的创建时机放在依赖注入之前即可,但 spring 仍然希望代理的创建时机在 init 之后,只有出现循环依赖时,才会将代理的创建时机提前。所以解决思路稍显复杂:
图中
factories.put(fa)
放入的既不是原始对象,也不是代理对象而是工厂对象 fa当检查出发生循环依赖时,fa 的产品就是代理 pa,没有发生循环依赖,fa 的产品是原始对象 a
假设出现了循环依赖,拿到了 singletonFactories 中的工厂对象,通过在依赖注入前获得了 pa,红色箭头 5
这回
b.setA()
注入的就是代理对象,保证了正确性,红色箭头 7还需要把 pa 存入新加的 earlySingletonObjects 缓存,红色箭头 6
a.init
完成后,无需二次创建代理,从哪儿找到 pa 呢?earlySingletonObjects 已经缓存,蓝色箭头 9
当成品对象产生,放入 singletonObject 后,singletonFactories 和 earlySingletonObjects 就中的对象就没有用处,清除即可
5.Spring事务失效的几种场景及原因
案例演示:。。。。。。
6.Spring中用到的设计模式
单例模式
工厂模式
代理模式
模板方法模式
适配器模式
7.BeanFactory 和 ApplicationContext 有什么区别?
BeanFactory和ApplicationContext是Spring框架中的两个重要组件,它们在许多方面存在一些 区别:
加载方式:BeanFactory采用的是延迟加载注入bean,即只用使用到某个bean的时候,才对该Bean进行加载实例化。这意味着如果在启动时未使用某个bean,那么该bean不会被实例化,这有助于节省资源。而ApplicationContext在容器启动时,会一次性创建所有的bean,这有助于在后续程序使用过程中更加高效。
创建方式:BeanFactory通常以编程的方式创建bean,而ApplicationContext除了编程式创建外,还能以声明的方式创建bean。
注册方式:BeanFactory需要手动注册BeanPostProcessor和BeanFactoryPostProcessor,而ApplicationContext会自动注册。
国际化支持:ApplicationContext由于继承自MessageSource,因此可以支持国际化语言配置,这是BeanFactory所不具备的。
其他特性:ApplicationContext支持消息发送和响应机制以及AOP以及访问资源等,这些是BeanFactory不具备或实现起来更为复杂的功能。
总结来说,BeanFactory和ApplicationContext在加载方式、创建方式、注册方式、国际化支持和特性等方面存在区别。需要根据实际的应用需求来选择使用哪一个。
8.BeanFactory 和 FactoryBean 有什么区别?
BeanFactory和FactoryBean在Spring框架中都扮演着重要的角色,但它们之间存在一些明显的区别:
BeanFactory是Spring的核心,是IoC容器,主要负责创建、配置和管理bean。它提供了一种方法来支持外部程序对这些bean的访问,并且能在程序启动时根据传入的参数产生各种类型的bean,并添加到IOC容器中。BeanFactory有多种实现,如DefaultListableBeanFactory、XmlBeanFactory、ApplicationContext等。
FactoryBean则是一个接口,其实现类首先是一个bean,也存放在BeanFactory中。FactoryBean主要负责生产和管理bean,是IoC容器的核心接口,它的职责包括实例化、定位、配置应用程序中的对象及建立这些对象的依赖。
总的来说,BeanFactory是IoC容器,负责创建、配置和管理bean,而FactoryBean则是一个负责生产和管理bean的工厂接口。
9.Spring中的Aware 接口和InitializingBean 接口
9.1. Aware 接口提供了一种【内置】 的注入手段,例如
BeanNameAware 注入 bean 的名字
BeanFactoryAware 注入 BeanFactory 容器
ApplicationContextAware 注入 ApplicationContext 容器
EmbeddedValueResolverAware 注入 ${} 解析器
9.2. InitializingBean 接口提供了一种【内置】的初始化手段
9.3. 对比
内置的注入和初始化不受扩展功能的影响,总会被执行
而扩展功能受某些情况影响可能会失效
因此 Spring 框架内部的类常用内置注入和初始化
10.Bean初始化与销毁
Spring 提供了多种初始化手段,除了@PostConstruct,@Bean(initMethod) 之外,还可以实现 InitializingBean 接口来进行初始化,如果同一个 bean 用了以上手段声明了 3 个初始化方法,那么它们的
初始化,执行顺序是
1. @PostConstruct 标注的初始化方法
2. InitializingBean 接口的初始化方法
3. @Bean(initMethod) 指定的初始化方法
销毁手段,执行顺序为
1. @PreDestroy 标注的销毁方法
2. DisposableBean 接口的销毁方法
3. @Bean(destroyMethod) 指定的销毁方法
@SpringBootApplication
public class A07_1 {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(A07_1.class, args);
context.close();
}
@Bean(initMethod = "init3")
public Bean1 bean1() {
return new Bean1();
}
@Bean(destroyMethod = "destroy3")
public Bean2 bean2() {
return new Bean2();
}
}
public class Bean1 implements InitializingBean {
private static final Logger log = LoggerFactory.getLogger(Bean1.class);
@PostConstruct
public void init1() {
log.debug("初始化1");
}
@Override
public void afterPropertiesSet() throws Exception {
log.debug("初始化2");
}
public void init3() {
log.debug("初始化3");
}
}
public class Bean2 implements DisposableBean {
private static final Logger log = LoggerFactory.getLogger(Bean2.class);
@PreDestroy
public void destroy1() {
log.debug("销毁1");
}
@Override
public void destroy() throws Exception {
log.debug("销毁2");
}
public void destroy3() {
log.debug("销毁3");
}
}
11,Spring作用域Scope
在当前版本的 Spring 和 Spring Boot 程序中,支持五种 Scope
singleton,容器启动时创建(未设置延迟),容器关闭时销毁
prototype,每次使用时创建,不会自动销毁,需要调用 DefaultListableBeanFactory.destroyBean(bean) 销毁
request,每次请求用到此 bean 时创建,请求结束时销毁
session,每个会话用到此 bean 时创建,会话结束时销毁
application,web 容器用到此 bean 时创建,容器停止时销毁
但要注意,如果在 singleton 注入其它 scope 都会有问题,解决方法有
@Lazy
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
ObjectFactory
ApplicationContext.getBean
评论