简介
控制反转(IoC)又称作依赖注入(DI)
在 Spring 中 IoC容器 管理的对象叫做 Bean
IoC容器负责配置和实例化 Bean
org.springframework.context.ApplicationContext
就是一个 Ioc容器 接口
区别于 Java Beans
JavaBeans是Java中一种特殊的类,可以将多个对象封装到一个对象(bean)中。特点是可序列化,提供无参构造器,提供getter方法和setter方法访问对象的属性。名称中的“Bean”是用于Java的可重用软件组件的惯用叫法。
容器简介
使用容器需要我们提供配置元数据(Configuration metadata),它告诉Spring如何实配置,组装,实例化 应用中的对象。
Spring 提供了许多方式来为程序提供配置元数据,如XML,注解,Java代码。
使用XML进行配置时,根节点为beans
,里面包含了许多的bean
节点。
使用注解进行配置时,通常使用 @Component
@Repository
@Service
@Controller
。
使用 Java 配置通常在 @Configuration
类中使用 @Bean
注解。
另外还可以是用 Spring’s Groovy Bean Definition DSL 来提供配置元数据。
这些 Bean 对应了真实应用中的对象,比如典型的 服务层类,持久层类,DAOs,Hibernate SessionFactories,JMS Queues 等等。
ApplicationContext
接口是一个高级工厂,它为 Bean 及其依赖维护了一个注册表,使用 T getBean(String name, Class<T> requiredType)
方法就可以检索 Bean 的实例。
ApplicationContext
可以让你用以下的方式编写代码。
1
2
3
4
5
6
7
8
// create and configure beans
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
// retrieve configured instance
PetStoreService service = context.getBean("petStore", PetStoreService.class);
// use configured instance
List<String> userList = service.getUsernameList();
Bean 简介
容器自身使用 BeanDefinition
类来表述 Bean 的定义,它包含了以下元数据:
- 一个带包名的完整类名 - 通常是bean的实现类
- Bean的行为控制元素 - 作用域,生命周期回调等等
- 其他Bean的引用 - 通常也叫做协作者或依赖
- 对Bean实例的其他配置 - 如线程池的大小
Property | Explained in… |
---|---|
class | Instantiating beans |
name | Naming beans |
scope | Bean scopes |
constructor arguments | Dependency Injection |
properties | Dependency Injection |
autowiring mode | Autowiring collaborators |
lazy-initialization mode | Lazy-initialized beans |
initialization method | Initialization callbacks |
destruction method | Destruction callbacks |
每个Bean都有一个或多个标识符(Identifier),这些标识符在Bean所处的容器中必须是唯一的。
Bean 的命名约定
和在Java中命名成员变量一样,使用小驼峰命名法。
‘accountManager’, ‘accountService’, ‘userDao’, ‘loginController’
Bean 的实例化
通常是使用构造函数,也可以通过静态工厂方法和实例工厂方法
依赖
容器会在创建Bean时注入它们的依赖。
DI只通过 使用构造方法的参数
,使用工厂方法的参数
,对象实例化后再对对象设置属性
这几个方法来处理依赖。
DI一般分为两类,一种是基于构造方法的,另一种则是基于Setter方法。
那么如何选择呢?
由于我们可以同时使用这两种方式,所以我们可以使用构造方法处理必要的依赖,使用Setter处理可选的依赖。
Spring的团队更加提倡使用构造方法的方式,因为使用这种方式可以确保依赖完整,依赖的所有者始终能得到一个完全初始化的Bean。
使用Setter的实现方式主要用于依赖可以有合理默认值的情况,否则,在代码的各个地方都要判断依赖是否为空。
如果 Bean 的作用域是 Singleton
,那么它将在container创建时被创建,其他情况下 Bean 只在被需要用到时被创建。
循环依赖
当我们大量使用构造函数来完成依赖注入时,可能会产生循环依赖,如果Spring在运行时发现循环依赖,就会抛出BeanCurrentlyInCreationException
,解决办法就是修改源码,对一些Bean使用Setter处理依赖。
Spring会在container加载时检测配置是否有问题,比如依赖的Bean不存在或是存在循环依赖。
简单类型
基本类型,String 之类的。
Spring’s conversion service
is used to convert these values from a String to the actual type of the property or argument.
1
2
3
4
5
6
7
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<!-- results in a setDriverClassName(String) call -->
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
<property name="username" value="root"/>
<property name="password" value="masterkaoli"/>
</bean>
另外还有 p-namespace
形式的XML配置。
然后还有好几种配置方法。
引用其他的Bean
1
<ref bean="someBean"/>
Inner Beans
Inner Beans 不需要id或是name,如果有,容器也不会使用这个值。
Inner Beans 也会忽略Scope,Inner Beans是匿名的且由外部Bean来创建。
1
2
3
4
5
6
7
8
9
<bean id="outer" class="...">
<!-- instead of using a reference to a target bean, simply define the target bean inline -->
<property name="target">
<bean class="com.example.Person"> <!-- this is the inner bean -->
<property name="name" value="Fiona Apple"/>
<property name="age" value="25"/>
</bean>
</property>
</bean>
集合
<list/>, <set/>, <map/>, <props/>
这些XML元素用来设置 List, Set, Map, Properties
类型的属性和参数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<bean id="moreComplexObject" class="example.ComplexObject">
<!-- results in a setSomeList(java.util.List) call -->
<property name="someList">
<list>
<value>a list element followed by a reference</value>
<ref bean="myDataSource" />
</list>
</property>
<!-- results in a setSomeMap(java.util.Map) call -->
<property name="someMap">
<map>
<entry key="an entry" value="just some string"/>
<entry key ="a ref" value-ref="myDataSource"/>
</map>
</property>
</bean>
Map的键值和Set的值可以是以下类型
bean | ref | idref | list | set | map | props | value | null
Spring 也支持集合合并。
null 及 空字符串
1
2
3
<bean class="ExampleBean">
<property name="email" value=""/>
</bean>
1
2
3
4
5
<bean class="ExampleBean">
<property name="email">
<null/>
</property>
</bean>
自动装配依赖
自动装配( autowire )可以极大的减少 属性 和 构造函数参数 的配置。
自动装配可以自动更新配置,当你添加一个依赖时,不必修改配置文件。
自动装配的几个模式
模式 | 解释 |
---|---|
no | 默认 |
byName | 查找和属性同名的Bean |
byType | 在容器中找到对应类型的Bean |
constructor | 和byType类似,但是应用于构造函数的参数 |
显式声明的依赖会覆盖自动装配的依赖。
简单类型的属性不能自动装配。
Bean的域
Spring支持6种scope
scope | 描述 |
---|---|
singleton | 默认,容器中单例 |
prototype | Bean可以有任意数量的实例 |
request | 每个HTTP请求都一个新的实例,只在 web-aware Spring ApplicationContext 中有效 |
session | 与 HTTP Session 同生命周期,只在 web-aware Spring ApplicationContext 中有效 |
application | 与ServletContext 同生命周期,只在 web-aware Spring ApplicationContext 中有效 |
websocket | 与WebSocket 同生命周期,只在 web-aware Spring ApplicationContext 中有效 |
另外还有 SimpleThreadScope
,但是需要额外的配置才可以使用。
基于注解的容器配置
@Required
用于Bean的setter方法,标记这个Setter的Bean必须在配置时期就设置好。
1
2
3
4
5
6
7
8
9
10
11
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Required
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
@Autowired
可以用于成员变量,构造函数和Setter方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
private MovieCatalog movieCatalog;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
@Resource
@Resource 的参数 name 指定 Bean 的 name,如果没有指明的话,对于成员变量则默认为成员变量名,对于Setter方法,则默认为方法参数名。
1
2
3
4
5
6
7
8
9
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Resource(name="myMovieFinder")
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
Classpath扫描及组件管理
前面提到的注解注入,Bean都是在XML中显示声明的,这节将会讨论通过Classpath扫描隐式检测候选组件。
候选组件 (Candidate components) 是容器中满足一定筛选标准且有对应Bean定义的一些类。
我们也可以通过注解而不是XML来注册Bean。
@Component 及其他模板注解
@Component
是一个通用模板标记,用于标记受Spring管理的组件的。
@Repository
@Service
@Controller
是特定场景下的 @Component
。
因此也可以使用 @Component
替代他们,但是使用 @Repository
@Service
@Controller
有利于工具识别,也更方便应用Aspect。
另外也便于后续版本的Spring框架给他们添加新的语义。
所以选择更加详细的注解会比较好。
@Repository is already supported as a marker for automatic exception translation in your persistence layer.
元注解
元注解就是可以应用在注解之上的注解。比如@Service的元注解有@Component
1
2
3
4
5
6
7
8
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component // Spring will see this and treat @Service in the same way as @Component
public @interface Service {
// ....
}
元注解也可以用来创建复合注解,比如 RestController
就是由 @Controller
和 @ResponseBody
复合而成。
自动扫描Java类及注册Bean定义
需要在@Configuration类上添加@ComponentScan
,@ComponentScan
告诉Spring去哪搜索Bean。
1
2
3
4
5
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig {
...
}
如果 @ComponentScan
没有指定包名,默认使用此注解所在类的包名。
使用过滤器定制扫描
使用 @ComponentScan
自动检测组件的命名
@Component 的 name 参数 就是 组件的名字,如果没有指明,默认为首字母小写的类目,如 MovieFinderImpl 的默认名字为 movieFinderImpl
1
2
3
4
@Service("myMovieLister")
public class SimpleMovieLister {
// ...
}
自动检测组件的作用域
1
2
3
4
5
@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {
// ...
}
生成索引
虽然扫描十分迅速,我们仍然可以优化启动速度。
使用 JSR 330 标准注解
1
2
3
4
5
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
使用 @Inject
取代 @Autowired
使用 @Named("movieListener")
取代 @Component("movieListener")
基于Java的容器配置
@Bean 及 @Configuration
@Bean
注解 用于标记一个方法 来为容器实例化,初始化对象。
@Configuration
用来标记一个主要功能是为容器提供 Bean 定义的类。
@Configuration
is meta-annotated with @Component
, therefore @Configuration
classes are candidates for component scanning
1
2
3
4
5
6
7
8
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}
ApplicationContext 的其他能力
使用MessageSource实现国际化
ApplicationContext 继承了 MessageSource
接口,因此也提供了国际化的功能。
1
2
3
4
5
public interface MessageSource {
String getMessage(String code, Object[] args, String defaultMessage, Locale locale);
String getMessage(String code, Object[] args, Locale locale) throws NoSuchMessageException;
String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException;
}
标准及自定义事件
疑问
为何使用IoC
首先是方便测试,依赖都在接口或是抽象类中,这样就可以方便的在unitTest中使用 Stub
, Mock
。
为何使用XML来配置
低耦合,低侵入。
词汇
English | 中文 |
---|---|
Autowire | 自动装配 |