负责Bean的实例化、注入、生命周期方法管理,包括以下类型接口,前者更强大更常用
ApplicationContext
BeanFactory (本文忽略此类型)
- AppCtx负责实例化、配置和装配托管Bean
- AppCtx间接继承了BeanFactory,其继承的接口有:EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory, MessageSource, ApplicationEventPublisher, ResourcePatternResolver
AnnotationConfig...
ClassPathXml...
FileSystemXml...
AnnotationConfigWeb...
XmlWeb...
- Java代码启动
ApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"services.xml", "daos.xml"}); ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class, OtherConfig.class); AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.register(AppConfig.class, OtherConfig.class); ctx.register(AdditionalConfig.class); ctx.refresh();
- 随其他容器例如servlet容器启动,在web.xml定义ContextLoaderListener或DispatchServlet,最终产生WebApplicationContext。[参考]
<!-- 不指定ctxCfg,默认为/WEB-INF/applicationContext.xml --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/**/*Context.xml /WEB-INF/applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
- Java代码关闭
//在JVM注册关闭钩子,JVM容器关闭时,IoC容器调用Bean的destroy方法释放资源 context.registerShutdownHook();
- Web容器里的IoC容器会自动关闭
// 方法1: 让Bean实现ApplicationContext接口 class Car implements ApplicationContextAware { private ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } } // 方法2: 直接注入 @Component class Car implements ApplicationContextAware { private ApplicationContext applicationContext; @Autowired public void setApplicationContext(ApplicationContext applicationContext) { this.applicationContext = applicationContext; } }
XML定义例如:init-method, destroy-method 注解例如JSR250的@PostConstruct and @PreDestroy
- 设置方法有XML、JavaConfig、自动(Annotation)
- 三种方式可以混用
- Spring In Action推荐:如果可以为Bean代码加上注解,优先自动;否则优先JavaConfig;最后XML。
- XML
<!-- 基本用法 --> <bean id="car" name="car,auto" class="x.y.z.Car"/> <bean alias="sedan" name="car" /> <!-- 静态工厂方法 --> <bean id="logger" class="x.y.z.Logger" factoryMetohd="getInstance" /> <!-- 实例工厂方法 --> <bean id="iOReader" factoryBean="x.y.z.ReaderFactory" factoryMetohd="createIOReader" />
- JavaConfig
//基本用法 @Bean(name={"car","auto"}, initMethod = "init") @Scope("prototype") @Description("It's a car Bean") public Car car(@Value("#{}") String name) { return new Car(); } //静态工厂方法 @Bean(name="logger") public Logger logger() { return Logger.getInstance(); } //实例工厂方法 @Bean(name="iOReader") public IOReader iOReader() { return new ReaderFactory().createIOReader(); }
- 自动(Annotation)
//通过扫描有注解的Bean,自动发现 //JavaConfig扫描设置 //默认扫描配置类所在的包及下级包 @Configuration @ComponentScan(basePackages = "x.y.z") public class AppConfig { }
//XML扫描设置 <context:component-scan>会自动启动<context:annotation-config>
//注解 @Component, @Service, @Controller, @Repository, @Named(JSR330),可以用@Scope、@Qualifier修饰
singleton,在容器内唯一,默认范围
prototype,每次实例化时创建不同实例
request,web环境可用
session,web环境可用
global session,web portlet环境可用
application,web环境可用
自定义
XML和JavaConfig的定义可以互相导入
<import resource="services.xml"/>
@Configuration @Import(ConfigA.class) public class ConfigB { }
用作配置项,不可以实例化,可以被继承
<bean id="auto" class="x.y.z.Auto" abstract="true"> <!-- ... 若干配置 ... --> </bean> <!-- car继承所有auto的配置,可以覆盖 --> <bean id="car" class="x.y.z.Car" parent="auto" />
- depends-on和ref都有依赖关系,前者不必引用被依赖者
- 被依赖者,先行实例化,先行destroy(单例)
<bean id="car" depends-on="driver1,model1">
@DependsOn
- 方式有:构造器注入、Setter方法注入、成员变量注入、自动注入
- Spring Reference推荐必须的依赖用构造器注入,非必须的可用其他方法注入
- 可以注入基本类型、collection、Bean实例
- XML方式例如
<bean id="car" class="x.y.z.Car" p:brand="BENZ" p:model-ref="model1" lazy-init="true" scope="singleton"> <constructor-arg value="1"/> <property name="driver" ref="driver1" required="false" /> <!-- 注入的是park1的Bean Id --> <property name="destName" idref="park1" /> <property name="packages"> <list> <value>瓶装水</value> <value>面巾纸</value> </list> </property> <property name="color" value=""/> <property name="tent"> <null/> </property> <property name="engine"> <!-- 嵌入Bean,scope=prototype --> <bean class="x.y.z.Engine" autowire="byName" /> </property> </bean>
- 注解有
@Autowired(required=false)、@Resource、@Inject(JSR330);可用@Qualifier、@Named(JSR330)、@Value修饰
往单例的Bean里面注入非单例的Bean,需要使用Lookup进行方法注入
public abstract class CommandManager { public Object process(Object commandState) { Command command = createCommand(); command.setState(commandState); return command.execute(); } protected abstract Command createCommand(); }
- XML方式
<bean id="command" class="x.y.z.AsyncCommand" scope="prototype" /> <bean id="commandManager" class="x.y.z.CommandManager"> <lookup-method name="createCommand" bean="command"/> </bean>
- JavaConfig方式,继承抽象类
@Bean public CommandManager commandManager() { return new CommandManager() { protected Command createCommand() { return new AsyncCommand(); } } }
- 当Bean容器打开后,所有定义的Bean都会被实例化,除了指定了lazy-init
- 如果指定了lazy-init,将在依赖Bean实例化或getBean时实例化
- 键值对存储对象
//创建MapPropertySource Map<String, Object> map = new HashMap<>(); map.put("encoding", "gbk"); PropertySource ps1 = new MapPropertySource("map", map); String encoding = (String) ps1.getProperty("encoding")); env.getPropertySources().addFirst(ps1); //创建ResourcePropertySource ResourcePropertySource ps2 = new ResourcePropertySource("resource", "classpath:resources.properties"); encoding = (String) ps2.getProperty("encoding"); //注解@PropertySource,自动加入到Environment @Configuration @PropertySource(value = "classpath:/x/y/z/app.properties", ignoreResourceNotFound = false) public class AppConfig { @Autowired Environment env; @Bean public TestBean testBean() { TestBean testBean = new TestBean(); testBean.setName(env.getProperty("testbean.name")); return testBean; } } //Java启动参数可以设置env property java -Dspring.profiles.active=dev JavaApp
<!-- web.xml --> <context-param> <param-name>spring.profiles.active</param-name> <param-value>dev</param-value> </context-param>
<!-- 占位符替换,不会加入到Environment --> <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/> <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean>
- 环境包含Profile配置和Properties属性
- 实现包括MockEnv和StdEnv。前者用于测试;后者可用来获取systemEnv (系统变量,例如JAVA_HOME等) 和 systemProp (系统属性,例如JVM属性,文件编码等)
- 用法如下
//创建StdEnv StandardEnvironment environment = new StandardEnvironment(); Map<String, Object> systemEnvironment = environment.getSystemEnvironment(); Map<String, Object> systemProperties = environment.getSystemProperties(); //在托管Bean中直接注入 @Autowired Environment environment; //通过上下文获取 Environment environment = context.getEnvironment(); String property = environment.getProperty(""); String[] defaultProfiles = environment.getActiveProfiles(); String[] defaultProfiles = environment.getDefaultProfiles();
- 是类实例化或方法执行的先决条件之一
- 设置方法见1.6.1,此外,还有以下
//注解@Profile @Configuration @Profile("dev") public class StandaloneDataConfig { @Bean public DataSource dataSource() { //... } } //Environment直接设置 env.setActiveProfiles("dev", "test");
<beans <!-- ... --> profile="dev"> <!-- ... --> </beans>
- 获取方法
String[] defaultProfiles = environment.getActiveProfiles(); String[] defaultProfiles = environment.getDefaultProfiles(); boolean acceptsProfiles = outerBean1.environment.acceptsProfiles("dev", "test");
- 实现包括ResourceBundleMessageSource和StaticMessageSource,后者用得少,但可以用编程的方式增加message
- AppCtx接口继承了MessageSource接口
<beans> <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> <property name="basenames"> <list> <value>account</value> <value>exceptions</value> </list> </property> </bean> </beans> <!-- 定时刷新 --> <bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource" p:name="cacheSeconds" p:value="3000" > <property name="basenames"> <!-- ... --> </property> </bean>
#account.properties文件内容 slogan=欢迎,{0}! #account.properties_en_GB文件内容 slogan=Welcome, {0}!
MessageSource resources = new ClassPathXmlApplicationContext("beans.xml"); String message1 = resources.getMessage("slogan", new Object [] {"张三"}, "Default", null); //欢迎,张三! String message2 = resources.getMessage("slogan", new Object [] {"Peter"}, "Required", Locale.UK); //Welcome, Peter! //也可以直接使用AppCtx String message3 = context.getMessage("slogan", new Object [] {"张三"}, "Default", null); //欢迎,张三!
- 标准事件有:ContextRefreshedEvent、ContextStartedEvent、ContextStoppedEvent、ContextClosedEvent、RequestHandledEvent
- 自定义事件,更复杂的应用参考Spring Integration
//自定义黑名单事件 public class BlackListEvent extends ApplicationEvent { private final String address; public BlackListEvent(Object source, String address, String test) { super(source); this.address = address; } } //发布事件 public class EmailService implements ApplicationEventPublisherAware { private List<String> blackList; private ApplicationEventPublisher publisher; public void sendEmail(String address, String text) { if (blackList.contains(address)) { BlackListEvent event = new BlackListEvent(this, address, text); publisher.publishEvent(event); return; } // send email... } } //接收并处理事件 public class BlackListNotifier implements ApplicationListener<BlackListEvent> { public void onApplicationEvent(BlackListEvent event) { //处理事件 } }