Spring源码解析之基础应用(二)
方法注入
在spring容器中,bean的作用域分(scope)分:单例(singleton)和原型(prototype),如果bean的作用域是单例,spring只会创建这个bean一次,之后将这个bean缓存,当我们从spring容器获取bean时spring会将之前缓存下来的bean返回,如果bean的作用域是原型,每次从spring容器获取bean,spring都为会我们创建一个新的bean对象。
单例和原型有各自的使用场景,比如调用一个bean的任何方法都不会对bean产生副作用,那么这个bean是线程安全的,这个bean的作用域可以是单例。但如果调用bean的方法会对bean本身产生副作用,比如下面的CommandService,这个类有个属性i默认值为1,调用add()方法时对i+1,那么这个bean的方法是存在副作用的,如果我们将CommandService声明为单例,那么当我们从spring容器中获取这个bean对象时,bean的属性i有可能不是1了,因为可能存在别的请求获取到CommandService这个bean并调用add()方法修改i的值,如果我们期望每次从spring容器获取bean的时候,属性i都为默认值1,那么就要将bean的作用域标记为原型,这样每次从spring容器获取原型bean时,spring都会为我们创建一个新的对象。
需要注意一点是:标记为原型的bean如果以@Autowired的方式注入到其他bean的字段中,比如下面<1>处的代码,如果这样注入原型bean那么同一个bean对象。我们可以让下面的CommandManager类实现ApplicationContextAware接口,这个接口会注入一个ApplicationContext对象,如果我们有需要CommandService这个原型bean,就从ApplicationContext对象中获取,这样获取原型bean就能保证每次spring都会为我们创建一个新的bean对象。
package org.example.service; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @Component @Scope("prototype") public class CommandService { private int i = 1; public void add() { i++; } } 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//<1> 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的方式,一种是getCommandService(),一种是createCommandService(),getCommandService()返回的是用依赖注入的原型bean,createCommandService()是从spring容器获取原型bean,我们的目标是希望每次获取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的作用域为原型,但如果用依赖注入的方式注入到其他bean,这个bean依旧是复用同一个bean对象,而每次从spring容器获取原型bean,就能保证都是一个新的bean对象。除了像上面那样从spring容器获取新的CommandService原型bean对象之外,我们还可以借助@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()方法,因为web服务器在结束进程的时候,会回调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...