1. IoC容器
負責Bean的實例化、注入、生命週期方法管理,包括如下類型接口,前者更強大更經常使用html
ApplicationContextjava
BeanFactory (本文忽略此類型)web
1.1 ApplicationContext
- AppCtx負責實例化、配置和裝配託管Beanspring
- AppCtx間接繼承了BeanFactory,其繼承的接口有:EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory, MessageSource, ApplicationEventPublisher, ResourcePatternResolver編程
1.1.1 ApplicationContext類型
AnnotationConfig...session
ClassPathXml...app
FileSystemXml...ide
AnnotationConfigWeb...測試
XmlWeb...ui
1.1.2 啓動ApplicationContext容器
- 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>
1.1.3 關閉ApplicationContext容器
- Java代碼關閉
//在JVM註冊關閉鉤子,JVM容器關閉時,IoC容器調用Bean的destroy方法釋放資源 context.registerShutdownHook();
- Web容器裏的IoC容器會自動關閉
1.1.4 在託管Bean裏面使用ApplicationContext
// 方法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; } }
1.2 Bean的生命週期
XML定義例如:init-method, destroy-method 註解例如JSR250的@PostConstruct and @PreDestroy
1.3 定義Bean
1.3.1 定義方法
- 設置方法有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修飾
1.3.2 Bean的做用範圍
singleton,在容器內惟一,默認範圍
prototype,每次實例化時建立不一樣實例
request,web環境可用
session,web環境可用
global session,web portlet環境可用
application,web環境可用
自定義
1.3.3 定義導入
XML和JavaConfig的定義能夠互相導入
<import resource="services.xml"/>
@Configuration @Import(ConfigA.class) public class ConfigB { }
1.3.4 抽象Bean
用做配置項,不能夠實例化,能夠被繼承
<bean id="auto" class="x.y.z.Auto" abstract="true"> <!-- ... 若干配置 ... --> </bean> <!-- car繼承全部auto的配置,能夠覆蓋 --> <bean id="car" class="x.y.z.Car" parent="auto" />
1.3.5 定義依賴/繼承
- depends-on和ref都有依賴關係,前者沒必要引用被依賴者
- 被依賴者,先行實例化,先行destroy(單例)
<bean id="car" depends-on="driver1,model1">
@DependsOn
1.4 依賴注入
1.4.1 注入方式
- 方式有:構造器注入、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修飾
1.4.2 方法注入
往單例的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(); } } }
1.5 實例化Bean
- 當Bean容器打開後,全部定義的Bean都會被實例化,除了指定了lazy-init
- 若是指定了lazy-init,將在依賴Bean實例化或getBean時實例化
1.6 環境Environment和剖面Profile
1.6.1 PropertySource
- 鍵值對存儲對象
//建立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>
1.6.2 Environment
- 環境包含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.3 Profile
- 是類實例化或方法執行的先決條件之一
- 設置方法見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");
1.7 國際化i18n
- 實現包括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); //歡迎,張三!
1.8 事件Events
- 標準事件有: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) { //處理事件 } }