Spring IOC原理描述
IOC是一种设计思想,在日常开发中,面对创建的对象(new),都会出现不好的开发习惯。导致代码量大,更糟糕都会导致程序崩溃、耦合度极高等问题。因此,IOC(控制反转)实现了不需要使用new的操作符来实例化,而是在运行时由容器实例将依赖组建注入组建,对依赖性的控制由组建转到容器,这就是控制反转。“不要给我们打电话,我们会给你打电话”。
Spring IOS是对IOC设计思想的一种实现。
这个容器负责了三个核心(基础)功能:对象创建、对象存储(map)、对象管理(依赖查找、依赖注入)。
- 依赖查找:容器向其管理的组建提供了回调方法,而组建则通过这些回调方法于容器进行交互并显式地获取他们的依赖项。
- 依赖注入:组建提供了合适的组建函数或Setter方法,以便容器可以注入依赖组建。
配置元数据
|
所有的Bean都被定义在
示例中,accountService Bean又一个accountDao的属性,而该属性又满足了配置中定义的accountDao Bean。
下面是基于接口实现的数据示例:
- AccountDao,Dao层
public interface AccountDao {
void testAccount();
} - AccountDaoImpl,实现类
这里实现了Dao层中的功能public class AccountDaoImpl implements AccountDao{
@Override
public void testAccount() {
System.out.println("TestAccount");
}
} - AccountService,Service接口
public interface AccountService {
void serviceTest();
} - AccountServiceImpl,Service层
这里实现了Service层中的功能,并且引入了AccountDao依赖。public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void serviceTest() {
System.out.println("ServiceTest");
accountDao.testAccount();
}
}
下面是基于注解的配置元数据示例:
|
上面的代码咋一个使用@Configuration注解的Java类中定义Bean。每一个使用注解@Bean的公共方法都对应一个Bean定义。通过调用构造函数示例化Bean,然后通过调用其他Bean的定义方法获取其依赖项,并将其注入。
|
上面代码使用Java注解定义Bean。其中@Service和@Repository被用来定义两个Bean。实际上,他们又是注解@Component的具体形式。此外,注解@Autowired通常用来制定在运行时被Spring容器所注入的Bean依赖。
在一个庞大的项目中,将配置元数据划分到多个不同的文件是一个好主意。
- beans-web.xml:定义了web层/表示层的Bean。
- beans-service.xml:定义了服务层/业务层的Bean。
- beans-dao.xml:定义了数据访问层中的Bean。
- beans-config.xml:定义了激活几个特定功能所需要的Bean。
- beans-security.xml:定义了应用程序安全性需求的Bean。
依赖注入
Setter注入
是在Bean示例创建完毕之后执行。通过调用于Bean的配置元数据中定义的所有属性相对于的Setter方法注入这些属性。<bean id="account" class=".....Account">
<property name="id" value="1" />
<property name="ownerName" value="John" />
<property name="locked" value="false" />
<property name="maps">
<map>
<entry key="1" value-ref="one"/>
<entry key="2" value-ref="two"/>
</map>
</property>
</bean>
可以看出Spring需要尽可能多地处理必要的类型转换。此外还允许注入Collection或Map值。
构造函数注入
构造函数注入在组建创建期间被执行。依赖项被表示为构造函数的参数,容器通过检测Bean定义中制定的构造函数参数来确定调用哪个函数。
- 该构造函数接收一个对象作为输入参数。
public class AccountServiceImpl implements AccountService{
/**
* 需要注入的对象Dao层对象
*/
private AccountDao accountDao;
/**
* 构造注入
*/
public AccountServiceImpl(AccountDao accountDao){
this.accountDao=accountDao;
}
} 使用构造函数注入的accountService Bean定义如下
<bean name="accountService" class="com.spring.springIoc.service.impl.AccountServiceImpl">
<constructor-arg ref="accountDao"/>
</bean>
<bean name="accountDao" class="com.spring.springIoc.dao.impl.AccountDaoImpl"/>xml引入Spring容器
public class Main {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("...xml");
}
}调用
AccountService service = context.getBean("accountService", AccountService.class);
除了上述的对其他Bean的引用外,还可以对非Bean内容引用,比如int、boolean等。通过
循环依赖
构造函数注入的其中一个确定是无法处理循环依赖。例如:public class A {
private B b;
public A(B b) {
this.b = b;
}
}
public class B {
private A a;
public B(A a) {
this.a = a;
}
}<bean id="a" class="com.spring.springioc.ch2.A">
<constructor-arg ref="b" />
</bean>
<bean id="b" class="com.spring.springioc.ch2.B">
<constructor-arg ref="a" />
</bean>
这样会导致BeanCurrentlyInCreationException异常。
依赖解析过程
大致分为两个主要阶段。
- 容器处理配置元数据,并建立元数据中存在的Bean定义,这个过程中会对Bean定义进行验证。在这个过程中Bean并没有被创建,相关的属性也没有被注入。
- 完成Bean的创建,然后完成依赖注入。但是这个过程中仅创建了无状态作用域的Bean。
重写Bean定义
提供了两种不同形式的重写机制。
第一种
第一种发生在Bean配置元数据文件级别。
首先将配置元数据划分到多个不同的文件或类中,然后在ApplicationContext创建期间制定这些文件和类组合在一起。在合并期间配置源的顺序非常重要。如果两个不同的配置源中拥有两个相同的Bean定义,那么根据给定的顺序第二个Bean定义将重写第一个Bean定义。只有当同名的Bean定义被放置知道不同的配置源数据源时,这些类型的Bean重写才会发生。
创建Foo类
public class Foo {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}创建两个@Configuration
@Configuration
public class Configuration1 {
@Bean
public Foo foo() {
Foo foo = new Foo();
foo.setName("My Foo");
return foo;
}
}
@Configuration
public class Configuration2 {
@Bean
public Foo foo() {
Foo foo = new Foo();
foo.setName("Your Foo");
return foo;
}
}修改Main方法中的Configuration
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Configuration1.class, Configuration2.class);
}
}访问ApplicationContext中的foo bean
Foo foo = context.getBean(Foo.class);
System.out.println(foo.getName());修改Configuration顺序
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Configuration2.class, Configuration1.class);
}
}
上述代码看到两个@Configuration类,检查两个顺序,在第一次调用中使用了类Configuration2中的定义;第二次调用中使用了类Configuration1的定义。
第二种
发生在容器级别。
ApplicationContext可能有父ApplicationContext,在父子示例中存在两个同名的Bean示例完全可能。在使用重复时,Spring将提供子ApplicationContext中所定义的Bean。
使用depends-on特性
如果Bean a直接或间接依赖Bean b,那么可以肯定Spring容器将首先创建Bean b。相反如果没有依赖关心,则无法确保两者之前创建的顺序。如果需要这个操作,则使用该特性。<bean id="a" class="xxxxxx.A" depends-on="b,c"/>
这样就标志类Bean b、c在Bean a之前被创建。
自动装配
自动装配有三种模式:byType、byName、constructor。
byType
通过Java反射查看Bean定义中的类,然后尝试将容器中可用的Bean注入与其类型相匹配的属性。主要通过这些属性的Setter方法完成。<bean id="accountService" autowire="byType" class="...impl.AccountServiceImpl" />
如果有多个Bean实例合适自动装配到某一个特定属性,这个注入就会失败。此时可以通过aotuwire-acandidate特性:
设置当前bean在被其他对象作为自动注入对象的时候,是否作为候选bean,默认值是true。<bean id="accountService" autowire="byType" class="...impl.AccountServiceImpl" autowire-candidate="false" />
byName
|
constructor
该模式类似byType模式,Spring容器尝试找到那些类型于构造函数参数匹配的Bean。
基于注解的自动装配
需要添加注解驱动。<context:annotation-config/>
@Autowired
可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。 通过@Autowired的使用标注到成员变量时不需要有set方法,请注意@Autowired默认按类型匹配的,。如果需要按名称匹配,可以使用@Qualifier和@Autowired合并使用。
@Resource
默认byName模式。可以标注在成员变量和set方法上,但无法标注构造函数。
@Value
针对基本类型如int、boolean等类型,提供了@Value注入方式。接收一个String值,指定了被注入到java类型属性值。
Bean查找
在Spring的应用中,尽量增加Bean的数量。
由于在使用过程中需要在Spring管理的Bean内显式Bean查找。提供了该接口:public class SpringContextUtil implements ApplicationContextAware {
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {:w
}
}
如果创建的bean类实现了该接口,那么则可以在Bean内访问自己的容器。对于基于Java和基于注解的配置,甚至不需要实现该接口,只需要将@Autowired注解放到字段或setter方法上就可以进行注入。
Bean管理
命名Bean
Bean通过名称进行区分。如果没有,Spring容器将会为其自动分配个内部名称。在xml配置中,id特性讲一个名称分配给Bean。
如果在Bean定义中为其分配多个名称,可以使用name特性。在该特性中可以使用空格、逗号、分毫来分离Bean定义的多个名称。<bean name="a,b,c" class="xxxx.AccountDao"/>
除了次名称外,还可以使用别名(aliase)操作赋予别名。<bean name="a,b,c" class="xxxx.AccountDao"/>
<alias name="a" alias="d"/>
Bean示例化方法
- 创建Bean的首要方式就是通过构造函数,Spring管理着他们。
- 调用可用的静态或实例工厂方法。xml中通过factory-method特性进行配置,其接收一个静态工厂方法名称作为一个参数值。
public class FooFactory {
public static Foo createFoo3() {
Foo foo = new Foo();
foo.setName("foo3");
return foo;
}
public Foo createFoo4() {
Foo foo = new Foo();
foo.setName("foo4");
return foo;
}
}而面对实例工厂方法需要引入factory-bean特性:<bean id="foo3" class="......FooFactory" factory-method="createFoo3" />
<bean id="fooFactory" class="......FooFactory" />
<bean id="foo4" factory-bean="fooFactory" factory-method="createFoo4" />
在基于注解的配置中,首先需要使用@Component元素注解FooFactory类。然后使用@Bean注解fooFactory Bean中的静态和实例工厂方法。@Component
public class FooFactory {
@Bean(name="foo3")
public static Foo createFoo3() {
Foo foo = new Foo();
foo.setName("foo3");
return foo;
}
@Bean(name="foo4")
public Foo createFoo4() {
Foo foo = new Foo();
foo.setName("foo4");
return foo;
}
}
- 最后是通过FactoryBean
接口实现 public class FooFactoryBean implements FactoryBean<Foo> {
@Override
public Foo getObject() {
Foo foo = new Foo();
foo.setName("foo3");
return foo;
}
@Override
public Class<?> getObjectType() {
return Foo.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
<bean id="foo5" class="xxxxxxx.FooFactoryBean"/>
Bean作用域
由Spring容器创建的Bean的生存期被称为其作用域。默认情况下,有Spring容器创建的所有Bean都是Singleton作用域。就是一个Bean定义只创建一个Bean实例,并且在整个应用程序中被使用。此时创建的实例都是无状态的。<bean id="manager" class="xxxxxx.Manager" scope="signleton"/>
使用scope特性来指定其作用域。默认为singleton。
其支持的第二种作用域是prototype。类似于Java中使用new操作符创建对象。每当容器中或者通过Bean引用其他Bean定义中,都会创建他们。
Spring2.5中新的作用域Request和Session。这些作用域只能在Web应用程序中使用。
当定义Request作用域和Sesion作用域的Bean是,必须在\
延迟初始化
默认情况下,Spring容器在启动阶段创建Bean。给过程被称为Bean初始化。优点在于尽早发现配置错误。
Spring还支持延迟Bean初始化。延迟直到真正需要时才创建。使用lazy-init特性触发。并且可以添加default-lazy-init特性配置默认值。
生命周期回调
可以定义回调方法,这些方法可以在Bean生命期的任何特定时间点由容器调用。在xml配置中有init-method特性和destroy-method特性。public class Foo {
public void init() {
}
public void destroy() {
}
}
<bean id="foo" class="xxxxxx.Foo" init-method="init" destroy-method="destroy"/>
在bean初始化后,回调用相应的init函数,并注入相关属性。相反,destroy函数则是在Bean生命周期结束之前由容器调用。
针对JSR-250 Common提供了@PostConstruct和@PreDestroy,具有相同功能。
但是需要添加
实现的第三种。Spring Framework提供了两个特殊的接口:org.springframework.beans.factory.InitializingBean和org.springframework.beans.factory.DisposableBean。他们分别声明了afterPropertiesSet()方法和destroy()方法。前者将在属性注入完毕后调用、后者则在Bean销毁前调用。public class Baz implements InitializingBean, DisposableBean {
@Override
public void afterPropertiesSet() throws Exception {
}
@Override
public void destroy() throws Exception {
}
}
<bean class="xxxxxx.Baz"/>
Bean定义配置文件
有时,我们需要根据运行时环境来定义Bean。针对开发环境、生产环境的不同而使用不同的数据库。针对不同环境创建不同的xml文件:dataSource-dev.xml和dataSource-prod.xml。或者在beans元素中添加profile特性,该特性可以配置bean环境。<beans profile="dev,test">
<bean>
</bean>
</beans>
环境
Spring3.1引入新的接口org.springframe.core.env.Environment来代表应用程序所在的运行环境。