Spring源码解析之基础应用(二)
方法注入
在spring容器中,大部分bean的作用域(scope)是单例(singleton)的,少部分bean的作用域是原型(prototype),如果一个bean的作用域是原型,我们A bean的作用域是原型,B bean中以@Autowired的方式注入A,那么B在A中依旧是单例。我们可以让B类实现ApplicationContextAware接口,这个接口会注入一个ApplicationContext对象,如果我们有需要A bean,则从ApplicationContextAware对象中获取。
package org.example.service; import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component; @Component
@Scope("prototype")
public class CommandService {
} import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component; @Component
public class CommandManager implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Autowired
private CommandService commandService; public CommandService getCommandService() {
return commandService;
} //方法注入
public CommandService createCommandService() {
return applicationContext.getBean(CommandService.class);
} @Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
} package org.example.config; import org.springframework.context.annotation.ComponentScan; @ComponentScan("org.example.service")
public class MyConfig {
}
为了验证笔者之前的说法,笔者特意在CommandManager类中注入一个原型的commandService的bean,也提供了方法注入,从applicationContext中获取CommandService对象。现在,我们编写测试用例,CommandManager有两种获取CommandService的方式,一种是getCommandService(),一种是createCommandService(),我们的目标是希望每次获取CommandService对象都是新创建的对象。
@Test
public void test05() {
ApplicationContext ac = new AnnotationConfigApplicationContext(MyConfig.class);
CommandManager commandManager = ac.getBean(CommandManager.class);
//获取@Autowired注入的CommandService
System.out.println(commandManager.getCommandService());
System.out.println(commandManager.getCommandService());
//获取方法注入的CommandService
System.out.println(commandManager.createCommandService());
System.out.println(commandManager.createCommandService());
}
运行结果:
org.example.service.CommandService@351d0846
org.example.service.CommandService@351d0846
org.example.service.CommandService@77e4c80f
org.example.service.CommandService@35fc6dc4
除了像上面那样从环境上下文获取CommandService对象之外,我们还可以借助@Lookup注解:
package org.example.service; import org.springframework.beans.factory.annotation.Lookup;
import org.springframework.stereotype.Component; @Component
public abstract class CommandManager1 {
@Lookup
public abstract CommandService createCommandService();
} import org.springframework.beans.factory.annotation.Lookup;
import org.springframework.stereotype.Component; @Component
public class CommandManager2 {
@Lookup
public CommandService createCommandService() {
return null;
}
}
测试用例:
@Test
public void test06() {
ApplicationContext ac = new AnnotationConfigApplicationContext(MyConfig.class);
CommandManager1 commandManager1 = ac.getBean(CommandManager1.class);
System.out.println(commandManager1.createCommandService());
System.out.println(commandManager1.createCommandService());
CommandManager2 commandManager2 = ac.getBean(CommandManager2.class);
System.out.println(commandManager2.createCommandService());
System.out.println(commandManager2.createCommandService());
}
运行结果:
org.example.service.CommandService@6193932a
org.example.service.CommandService@647fd8ce
org.example.service.CommandService@159f197
org.example.service.CommandService@78aab498
不管是CommandManager1还是CommandManager2,spring在对这两个类生成代理对象,当调用被@Lookup注解标记的方法时,产生新的原型对象返回。
生命周期
从spring2.5开始,我们有三种选项来控制bean生命周期行为:
- InitializingBean和DisposableBean回调接口。
- 自定义init()和destroy()方法。
- @PostConstruct和@PreDestroy注解。
我们可以使用上面三种选项来控制bean在不同生命周期时机进行函数回调,比如当一些dao在spring创建好之后,在回调方法里执行热点数据加载。下面的A类,我们按照第一和第三个选项,实现InitializingBean、DisposableBean接口,并标注@PostConstruct、@PreDestroy方法,分别是aaa()、bbb()。然后,我们在配置类MyConfig2通过@Bean注解获取一个类型为A的bean,并且按照第二个选项,在@Bean标注了init()和destroy()方法,分别是ccc()、ddd()。
package org.example.beans; import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean; import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy; public class A implements InitializingBean, DisposableBean {
@Override
public void destroy() throws Exception {
System.out.println("A destroy...");
} @Override
public void afterPropertiesSet() throws Exception {
System.out.println("A afterPropertiesSet...");
} @PostConstruct
public void aaa() {
System.out.println("A aaa...");
} @PreDestroy
public void bbb() {
System.out.println("A bbb...");
} public void ccc() {
System.out.println("A ccc...");
} public void ddd() {
System.out.println("A ddd...");
}
} package org.example.config; import org.example.beans.A;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration; @Configuration
@ComponentScan("org.example.service")
public class MyConfig2 {
@Bean(initMethod = "ccc", destroyMethod = "ddd")
public A getA() {
return new A();
}
}
测试用例:
@Test
public void test07() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MyConfig2.class);
ac.close();
}
运行结果:
A aaa...
A afterPropertiesSet...
A ccc...
A bbb...
A destroy...
A ddd...
从上面的执行结果,我们可以得出:如果存在不同的初始化方法,bean的回调顺序是:
- 先执行@PostConstruct注解所标注的方法。
- 再执行InitializingBean接口所实现的afterPropertiesSet()方法
- 最后执行自定义的init()方法。
销毁方法也是一样的顺序:
- 先执行@PreDestroy注解所标注的方法。
- 在执行DisposableBean接口所实现的destroy()方法。
- 最后执行自定义的destroy()方法。
服务启动关闭回调
之前我们学习了bean的生命周期回调,我们可以在bean的不同生命周期里执行不同的方法,如果我们想针对整个服务进行生命周期回调呢?比如要求在spring容器初始化好所有的bean之后、或者在关闭spring容器时进行方法回调。spring提供了LifeCycle接口来帮助我们实现针对服务的生命周期回调。LifeCycle提供了三个接口:
public interface Lifecycle { void start(); void stop(); boolean isRunning();
}
当isRunning()返回为false时,start()会在初始化完所有的bean之后,进行回调。比如我们可以在初始化好所有的bean之后,监听一个消息队列,并处理队列里的消息。当isRunning()返回为true时,stop()会在容器关闭时回调stop()方法。
来看下面的例子,TestLiftCycle的isRunning()默认返回false,所以在bean初始化后,允许spring容器回调start(),在start()方法中,将isRun置为true,允许在spring容器关闭时,回调stop()方法。
package org.example.service; import org.springframework.context.Lifecycle;
import org.springframework.stereotype.Component; @Component
public class TestLiftCycle implements Lifecycle {
private boolean isRun = false; @Override
public void start() {
isRun = true;
System.out.println("TestLiftCycle start...");
} @Override
public void stop() {
System.out.println("TestLiftCycle stop...");
} @Override
public boolean isRunning() {
return isRun;
}
}
要回调TestLiftCycle的start()和stop(),在测试用例里需要显式调用容器的start()和stop()方法。如果项目是部署在类似Tomcat、JBoss的微博服务器上,我们可以省去容器显式调用stop()方法,因为Tweb服务器在结束进程的时候,会回调spring容器的stop(),进而回调到我们编写的stop(),但是start()必须显式调用,不管是否部署在web服务器上。
测试用例:
@Test
public void test08() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MyConfig.class);
ac.start();
ac.stop();
}
运行结果:
TestLiftCycle start...
TestLiftCycle stop...
为了方便程序员偷懒,spring还提供了SmartLifecycle接口,这个接口不需要我们显式调用容器的start()方法,也可以在初始化所有的bean之后回调start()。下面,我们来看下SmartLifecycle接口:
public interface SmartLifecycle extends Lifecycle, Phased { boolean isAutoStartup(); void stop(Runnable callback);
}
SmartLifecycle扩展了Lifecycle和Phased两个接口,Phased要求实现int getPhase()接口,返回的数值越低越优先执行。boolean isAutoStartup()代表是否自动执行SmartLifecycle的start()方法,如果为true会自动调用start(),为false的话,需要显式调用容器的start()才会回调SmartLifecycle的start()。SmartLifecycle扩展了原先Lifecycle的stop()方法,当我们执行完stop(Runnable callback)中的业务,需要执行callback.run(),这将启用异步关闭,否则要等待30s才会关闭容器。当然,这30s是可以修改的:
<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
<!-- timeout value in milliseconds -->
<property name="timeoutPerShutdownPhase" value="10000"/>
</bean>
下面,我们来测试TestSmartLifecycle,这里的isRunning()依旧和Lifecycle一样,只是关闭容器时不会再回调TestSmartLifecycle的stop(),转而回调stop(Runnable callback)。
package org.example.service; import org.springframework.context.SmartLifecycle;
import org.springframework.stereotype.Component; @Component
public class TestSmartLifecycle implements SmartLifecycle {
private boolean isRun = false; @Override
public void start() {
isRun = true;
System.out.println("TestSmartLifecycle start...");
} @Override
public boolean isAutoStartup() {
return true;
} @Override
public void stop(Runnable callback) {
System.out.println("TestSmartLifecycle stop callback...");
//执行callback.run()可以让容器立即关闭,否则容器会等待30s再关闭
callback.run();
} @Override
public int getPhase() {
return 0;
} @Override
public void stop() {
} @Override
public boolean isRunning() {
return isRun;
} }
测试用例:
@Test
public void test09() {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(MyConfig.class);
ac.stop();
}
运行结果:
TestSmartLifecycle start...
TestSmartLifecycle stop callback...
Spring源码解析之基础应用(二)的更多相关文章
- Spring源码解析之ConfigurationClassPostProcessor(二)
上一个章节,笔者向大家介绍了spring是如何来过滤配置类的,下面我们来看看在过滤出配置类后,spring是如何来解析配置类的.首先过滤出来的配置类会存放在configCandidates列表, 在代 ...
- Spring源码解析之BeanFactoryPostProcessor(二)
上一章,我们介绍了在AnnotationConfigApplicationContext初始化的时候,会创建AnnotatedBeanDefinitionReader和ClassPathBeanDef ...
- Spring源码解析之基础应用(三)
组合Java配置 在XML中,我们可以使用<import/>标签,在一个XML文件中引入另一个XML文件,在Java类中,我们同样可以在一个配置类中用@Import引入另一个配置类,被引入 ...
- Spring源码解析之BeanFactoryPostProcessor(三)
在上一章中笔者介绍了refresh()的<1>处是如何获取beanFactory对象,下面我们要来学习refresh()方法的<2>处是如何调用invokeBeanFactor ...
- Spring源码解析 - AbstractBeanFactory 实现接口与父类分析
我们先来看类图吧: 除了BeanFactory这一支的接口,AbstractBeanFactory主要实现了AliasRegistry和SingletonBeanRegistry接口. 这边主要提供了 ...
- spring 源码解析
1. [文件] spring源码.txt ~ 15B 下载(167) ? 1 springн┤┬вио╬Ш: 2. [文件] spring源码分析之AOP.txt ~ 15KB 下载( ...
- Spring源码解析——循环依赖的解决方案
一.前言 承接<Spring源码解析--创建bean>.<Spring源码解析--创建bean的实例>,我们今天接着聊聊,循环依赖的解决方案,即创建bean的ObjectFac ...
- Spring源码解析-ioc容器的设计
Spring源码解析-ioc容器的设计 1 IoC容器系列的设计:BeanFactory和ApplicatioContext 在Spring容器中,主要分为两个主要的容器系列,一个是实现BeanFac ...
- Spring源码解析系列汇总
相信我,你会收藏这篇文章的 本篇文章是这段时间撸出来的Spring源码解析系列文章的汇总,总共包含以下专题.喜欢的同学可以收藏起来以备不时之需 SpringIOC源码解析(上) 本篇文章搭建了IOC源 ...
随机推荐
- Kubernetes K8S之Pod跨namespace名称空间访问Service服务
Kubernetes的两个Service(ServiceA.ServiceB)和对应的Pod(PodA.PodB)分别属于不同的namespace名称空间,现需要PodA和PodB跨namespace ...
- lua数据结构之table的内部实现
一.table结构 1.Table结构体 首先了解一下table结构的组成结构,table是存放在GCObject里的.结构如下: typedef struct Table { CommonH ...
- Java简介以及入门
JAVA基础知识 Java简介 作者:詹姆斯·高斯林(James Gosling) Java是一门面向对象编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承.指针等概念,因此J ...
- linux 信号机制
文章目录 1. 实时信号非实时信号 2. 信号状态: 3. 信号生命周期: 4. 信号的执行和注销 信号掩码和信号处理函数的继承 信号处理函数的继承 信号掩码的继承 sigwait 与多线程 sigw ...
- NOIP2017 Day1 T1 小凯的疑惑
题目描述 小凯手中有两种面值的金币,两种面值均为正整数且彼此互素.每种金币小凯都有 无数个.在不找零的情况下,仅凭这两种金币,有些物品他是无法准确支付的.现在小凯想知道在无法准确支付的物品中,最贵的价 ...
- Node.js使用npm安装模块太慢,解决办法
转自 淘宝 npm 地址: http://npm.taobao.org/ 如何使用 有很多方法来配置npm的registry地址,下面根据不同情境列出几种比较常用的方法.以淘宝npm镜像举例: 1.临 ...
- 【转】Locust性能-零基础入门系列(2) -重写wait_time
在虚拟模拟的时候,可能对等待时间有更高的要求,比如假如有这么一个场景要求:某任务要求每被执行1次,那么下次的等待时间就1秒钟.这种情况,是可以实现的,这也就体现了Locust的灵活性.可编程性,很多比 ...
- dubbo学习(十)spring boot整合dubbo
工程搭建与配置 生产者 1.创建一个生产者的spring boot工程,配置好依赖,并把接口实现类文件夹复制到新的工程里 2.pom.xml配置dubbo的相关依赖 <!-- Dubbo Spr ...
- zabbix_server.conf配置文件参数
NodeID: 在amster-child 的分布式架构中,这个ID是唯一标识zabbix node的号码 ListenPort:Trapper 类型Item监听的端口, SourceIP: 在连接其 ...
- react项目创建流程
react 项目搭建 系统: windows 1.安装 node node 下载地址.一路 next 如果遇到 windows 没有权限安装 msi 文件.打开 cmd,运行msiexec /pack ...