一直在使用Spring提供的IoC容器, 但是始终没有系统化的梳理一下. 今天在这里写下, 也是以备以后参考之用.
Ioc container的核心是BeanFactory
接口, 它提供的方法能够管理任何类型的对象. ApplicationContext
是它的子接口, 集成了Spring AOP的特性.
BeanFactory or ApplicationContext?
BeanFactory目前只是Spring为了维持向后兼容性而保留的接口, 任何新开发的框架或者程序应该使用
ApplicationContext
而不是BeanFactory
.
那么ApplicationContext
相比BeanFactory
提供了哪些好处呢? 简单的来说就是很多增强的功能, 更细粒度的干预bean生命周期的定制, AOP, 国际化, 事件支持等, 详见下表:
所有的bean在使用前都需要在ApplicationContext
注册(register), 常用的有以下几种形式:
- xml形式定义的;
- 注解定义;
- Java Config
- 手动注册在Spring容器之外实例化的bean;
Dependency Injection的实现方式
一共有三种方法: 构造器注入, 设值注入和方法注入. 构造器注入在设值注入之前.
- 构造器注入: 注入强制性的依赖.
- 设值注入: 注入可选依赖或配置(例如
@ConfigurationProperties
); 当设置注入加入了@Required
标识, 就会变成一个强制性依赖. - 方法注入: 通常是在bean构造后对bean进行增强设置.
Constructor-based
构造器注入是推荐的方式, 能够实现一个immutable的bean, 确保所有的依赖不是
null
. 并能够保证调用者使用时, 依赖是实例化完成的.
无需提供getter/setter方法.
setter-based
设值注入应被仅用于注入可选依赖, 或者是能够被赋予默认值的依赖.
使用设值注入的好处是, 能够在之后通过setter
方法进行注入的变更, 在JMX MBeans管理上必须使用此种方式.
方法注入
下面是一个例子:
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired // 自动注入--方法注入参数auth, 对已创建的AuthenticationManagerBuilder类型的bean进行增强.
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("admin").password("admin").roles("USER");
}
}
依赖解析步骤
如果bean是单例的, 并且被标注为pre-instantiated(默认就是这个)会在容器创建的时候被创建. 否则的话会按需创建.
lazy-initialized beans
通常来讲单例bean会在容器创建的时候初始化, 并且会按照依赖关系顺次进行初始化, 但是你也可以通过指定
lazy-init=true
到某个bean
上使其在第一次使用的时候才被初始化.
注意: 即使某个bean开启了lazy-init, 也不一定是延迟初始化的, 因为如果依赖它的bean是单例的, 且没有开启延迟初始化, 那么Spring容器为了保证准确性, 会将这个bean也立即初始化.
Bean作用域(Bean Scopes)
bean一共有七种作用域:
作用域 | 描述 |
---|---|
singleton | 默认, 单例, 通常用于无状态bean, 如dao. service, controller等 |
prototype | 原型, 每次调用产生一个新的实例, 通常用在domain object上. 相当于java的new 关键字, Spring在实例化bean之后, 执行完initialization lifecycle callback, 之后就不在负责这个bean的生命周期了, 完全交给客户端负责 |
request | HTTP 请求范围 |
session | HTTP Session范围 |
application | 和ServletContext 的生命周期一致, 也就是web应用的范围 |
websocket | 和一个WebSocket 的生命周期一致 |
request, session, application, websocket作用域
这些作用域需要线程与Http Request或者Http Sesson对象绑定. 当使用Spring MVC时, 自动绑定.
使用以下注解在bean上显式指定:
- @RequestScope
- @SessionScope
- @ApplicationScope
application scope
是ServletContext
级别的, 作为一个ServletContext
的attribute存取, 这个范围非常像singleton范围, 但是有以下两点主要区别:
- 它是与
ServletContext
绑定, 不是与ApplicatinContext
绑定. 一个ServletContext
可以有多个ApplicationContext
. 也就是说, application scope的bean是与ApplicationContext
平行的, 二者都是ServletContext
的一个属性. - 它是
ServletContext
的一个属性.
代理
一般有两种类型:
- interface-based proxy, 典型的是JDK内置的动态代理
- 一种是inherated-based proxy, 典型的是CGLIB
interface-based proxy
优点是不需要引入额外的库依赖, 缺点是想要代理的方法必须是在被代理类实现的接口中定义的.
inherated-based proxy
缺点是需要引入额外依赖, 优点是可以代理所有的共有类型方法.
Bean生命周期(lifecycle)
- initialization callback 使用
@PostConstruct
- destruction callback 使用
@PreDestroy
以下是一个使用的例子:
public class CachingMovieLister {
@PostConstruct // 该方法会在bean初始化之后被回调
public void populateMovieCache() {
// populates the movie cache upon initialization...
}
@PreDestroy // 该方法会在容器销毁bean之前被回调
public void clearMovieCache() {
// clears the movie cache upon destruction...
}
}
Startup and shotdown callbacks
是容器启动和容器停止的回调, 发生在所有的bean创建之前和销毁之前.
优雅的关闭非web应用下的Spring IoC容器
需要为ApplicationContext
在JVM里注册一个shutdown hook. 这会确保所有自定义的destroy
方法被正确调用.
ApplicationContextAware and BeanNameAware
是bean用来感知容器存在的接口, 通常不需要实现这个接口, 因为通常bean是不需要显式感知容器的存在的, 而且这样做会导致与Spring框架耦合.
使用注解指导依赖注入
@Autowired
@Autowired
是按照注入的类型名进行依赖查找, 当同一个类型有多种实现bean的时候, 产生歧义并报错.
@Autowired+@Qualifier
当有两个以上相同类型的bean时, 单独的@Autowired
会产生歧义, 可以用额外的@Qualifier("这里是bean的名字")
注解按bean的名字进行注入.
@Resource
相当于@Autowired
+@Qualifire
的组合.
自定义@Qualifier
@Qualifier
注解支持继承, 这样我们就可以扩展@Qualifier
的语义, 进行更细力度的@Quelifier
定义, 例如:
// 扩展@Quelifier语义
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Offline {
}
@Configuration
public class MovieConfiguration {
@Bean
@Offline // 使用自定义的@Qualifier子类区分bean
public MovieCatalog firstMovieCatalog() { ... }
@Bean
public MovieCatalog secondMovieCatalog() { ... }
// ...
}
public class MovieRecommender {
@Autowired
@Offline // 根据自定义的@Qualifier子类注入bean
private MovieCatalog offlineCatalog;
// ...
}
下面是一个更复杂的例子:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MovieQualifier {
String genre();
Format format();
}
public enum Format {
VHS, DVD, BLURAY
}
public class MovieRecommender {
@Autowired
@MovieQualifier(format=Format.VHS, genre="Action")
private MovieCatalog actionVhsCatalog;
@Autowired
@MovieQualifier(format=Format.VHS, genre="Comedy")
private MovieCatalog comedyVhsCatalog;
@Autowired
@MovieQualifier(format=Format.DVD, genre="Action")
private MovieCatalog actionDvdCatalog;
@Autowired
@MovieQualifier(format=Format.BLURAY, genre="Comedy")
private MovieCatalog comedyBluRayCatalog;
// ...
}
使用Java Config指导依赖注入
核心是使用@Configuration
和@Bean
注解.
@Bean
@Bean是一个方法级的注解, 它的返回值将作为一个singleton的bean注册到spring的IoC容器中.
如果一个@Bean
的创建依赖于其他bean的创建, 那么需要将所有依赖作为方法的参数, 会触发类似@Autowired
的效果.
下面是一个例子:
@Configuration
public class AppConfig {
@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}
}
这种配置方式特别像contructor-based注入, 可以参照理解
@Configuration
所有的通过
@Bean
方式手动创建bean, 都建议在@Configuration
的类中进行定义.
@Configuration
标注的类会在运行时被CGLIB动态代理, 所有对原类方法的调用都会被拦截.
这个动态代理做了以下重要的事:
- 通过缓存singleton bean, 防止原类中重复实例化同一个名字的singleton bean.
@Import
@Import
是用来组合多个@Configuration
的定义. 使通过Java Config进行手动bean定义的配置文件集中到一起.
下面是一个使用@Configuration
+ @Import
进行跨文件bean组装的例子:
@Configuration
public class ServiceConfig {
@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}
}
@Configuration
public class RepositoryConfig {
@Bean
public AccountRepository accountRepository(DataSource dataSource) {
return new JdbcAccountRepository(dataSource);
}
}
@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {
@Bean
public DataSource dataSource() {
// return new DataSource
}
}
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
// everything wires up across configuration classes...
TransferService transferService = ctx.getBean(TransferService.class);
transferService.transfer(100.00, "A123", "C456");
}
组合xml, 注解, Java Config形式的依赖注入
只需要在一个@Configuration
类上加上@ImportResource
注解即可, 以下是一个例子:
@Configuration
@ImportResource("classpath:/com/acme/properties-config.xml")
public class AppConfig {
// 正常的bean定义
}
Environment abstraction
环境允许通过定义多个profile文件, 允许在不同环境(开发, 测试, 生产)使用不同的配置.
下面是一个例子:
@Configuration
@Profile("dev")
public class StandaloneDataConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
}
@Configuration
@Profile("production")
public class JndiDataConfig {
@Bean(destroyMethod="")
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
想要启动一个特定的配置, 在全局配置中加上如下属性设置即可:
# 启用dev profile
spring.profiles.active=dev
除此以外, 我们还可以提供个一个默认的profile:
@Configuration
@Profile("default")
public class DefaultDataConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.build();
}
}
上述配置在没有显式指定spring.profiles.active
时自动启用.
小结
以上.
参考链接: