Spring是JavaEE的必学的框架之一,之前只稍微接触过,并没有系统性的深入了解和学习,最近打算开始系统的学习Spring的。今天写写一下关于Spring的IOC。
Spring的两大特性即为IOC和AOP,一个叫控制反转一个叫面向切面的编程。关于IOC,其实还有个描述的更为透彻的名词叫注入(DI)。这两个名词都是描述的Spring的统一特性。因为这是我的学习记录,所以此处只记录下我的理解。IOC和DI的意思,就是以前我们在编写代码的时候,要获取对象时是通过自己去new对象来获得,我们需要自己去处理去控制这个对象。而利用IOC,Spring框架则可以帮助我们去获得对象,我们将把对象的控制权交给Spring框架,我们需要用的时候直接拿来用即可,不用过多的去关注对象的生成啊周期之类的。
而如何去利用IOC特性,如果去实现这个功能,是我们使用Spring框架的一大知识点。详细点说,我们是需要去配置这个对象注入的过程的。常见的过程是,我们利用一个配置文档,在配置文档中配置编写我们需要注入的Bean(Spring中的对象都想bean),在需要获取对象时,spring读取配置文档,给我们一个对象即可。
实例:
public class SampleDAOImpl implements SampleDAO {
public void sayByName(String word) {
System.out.println("autowiringDao:" + word);
}
@Override
public void insertSample(String s) {
// TODO Auto-generated method stub
System.out.println("保存数据" + s);
}
}
我们自己定义了一个实现了SampleDao的SampleDAOImpl对象,我们正常在其他位置处使用这个对象的方式是。SampleDAOImpl sampleDaoImple = new SampleDAOImpl(),再去使用。而利用在配置文档中配置,我们可以采用下面方式:
@Test
public void testGetBean() {
String springXmlpath = "classpath*:applicationContext.xml";
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext(springXmlpath.split("[,\\s]+"));
SampleDAOImpl sampleDAOImpl = (SampleDAOImpl) context.getBean("sampleDAOImp1");
}
上面代码的意思就是,获取配置文档信息,利用配置文档中的配置信息来返回对象。而配置文档中的配置信息为:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd"
>
<bean id="sampleDAOImp1l" class="parafeel.dao.SampleDAOImpl">
</bean>
</beans>
我们只在配置文档处,配置了一个bean,设置了它的id和class属性,既可利用IOC来获得对应的对象。这个流程就相当是一个简单的依赖注入过程。而这个过程中我们有几点是需要去特别了解和学习的,不然的话在使用过程中就知其然而不知其所以然。
一、利用配置文件的方式来实现IOC
1、Spring的注入方式(即在IOC容器中注入Bean的方式)
常见的方式有两种1)设值注入,2)构造注入。
1)设值注入就是在对我们想要注入的属性,设置其set方法,并且在配置文档中配置到即可,
例如:我们有一个SampleServiceImpl类,里面有一个属性是SampleDAOImpl,那么利用Spring,我们在使用时Spring会给我们自动注入这个daoImpl对象,我们只需要配置即可。配置方式为:
public class SampleServiceImpl implements SampleService {
private SampleDAOImpl sampleDAOImpl;
public void setSampleDAOImpl(SampleDAOImpl sampleDAOImpl) {
this.sampleDAOImpl = sampleDAOImpl;
}
}
我们可以看到SampleServiceImpl类里有个SampleDAOImpl属性,我们定义了这个属性的set方法,我们在配置文档中配置:
<bean id="sampleDAOImpl" class="parafeel.dao.SampleDAOImpl">
</bean>
<bean id="sampleServiceImpl" class="parafeel.service.SampleServiceImpl">
<property name="sampleDAOImpl" ref="sampleDAOImpl"></property>
</bean>
既有set方法,有配置了property,既可以实现设值注入。
2)构造注入,同理,构造注入就是在SampleServiceImpl类里有个SampleDAOImpl属性,我们定义把这个属性定义为SampleServiceImpl类构造方法中的一个变量,再配置配置文档即可:
public class SampleServiceImpl implements SampleService {
private SampleDAOImpl sampleDAOImpl;
public SampleServiceImpl(SampleDAOImpl sampleDAOImpl) {
super();
this.sampleDAOImpl = sampleDAOImpl;
}
}
配置文档:
<bean id="sampleDAOImpl" class="parafeel.dao.SampleDAOImpl">
</bean>
<bean id="sampleServiceImpl" class="parafeel.service.SampleServiceImpl">
<constructor-arg index="0" ref="sampleDAOImpl"></constructor-arg>
</bean>
上面两种设值注入及构造注入,就是spring的IOC提供的两种注入方式。而利用以上注入的方式,我们获取对象的方式变为:
@Test
public void testGetBean() {
String springXmlpath = "classpath*:applicationContext.xml";
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext(springXmlpath.split("[,\\s]+"));
SampleServiceImpl s = (SampleServiceImpl) context.getBean("sampleServiceImpl");
s.insertSample("test!");
}
此时,我们便将对对象的控制交予了Spring的IOC容器。我们只需要使用就行了!
上面对象的注入过程可以视为对Bean的装配,在装配过程中,我们需要了解许多其他的内容,包括:配置文档的配置项及其作用、Bean的作用域、Bean的生命周期、Aware接口、和Bean的自动装配。
2、Bean的配置项、作用域、生命周期及Aware接口
再重复一遍,在Spring的IOC容器里,每个类都是Bean。我们在注入类的时候,都是在配置Bean。在下面的配置文档中:
<bean id="sampleDAOImpl" class="parafeel.dao.SampleDAOImpl" scope="singleton">
</bean>
<bean id="sampleServiceImpl" class="parafeel.service.SampleServiceImpl">
<property name=""></property>
<constructor-arg index="0" ref="sampleDAOImpl"></constructor-arg>
</bean>
1)
我们可以看到的配置项就用,id、class、scope。以及property、constructor-arg,这些都是用来配置bean的各项属性的。例如id就是指bean的名字,class就是指这个bean对应的类名,scope就是这个bean在IOC容器的作用域。property指这个bean的属性、constructor-arg则为这个bean的构造函数的参数。正式这些配置项,使得IOC能够利用配置文档来操作管理每个bean。这块的话,用到什么配置项才会更加了解,此处就写这些最常用的配置项,其他的还有很多配置项。
2)而作用域,则是用来限定一个Bean在IOC容器中的作用域,常见的作用域范围有:单例(singleton)在IOC容器中是单例的,prototype每次请求都创建一个新的实例、request、session、global session,等。这些作用域对应的内容就是一个Bean在被IOC创造后,这个Bean能活多久。
3)除了作用域,第三个需要关注的配置细节是生命周期,一个Bean的生命周期有定义、初始化、使用、销毁,我们面对这四个部分,定义和使用是必然需要的,而有时候我们还需要配置一下初始化和销毁方法。
例如:常见的初始化(针对某个bean)方法有两种,一种是让这个bean实现org.springframework.beans.factory.initinlizingBean接口,同时重写其afterPropertiesSet方法。另一种方法是在XML中配置Bean时,在init-method字段加入其初始化所对应的方法,并在类里定义好这个方法。
第一种示例:实现了此接口
<bean id="sampleDAO" class="parafeel.dao.SampleDAO">
</bean>
public class SampleDAO implements InitializingBean{
public void daoSay(String s){
System.out.println("SampleDAO : " + s);
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("init");
}
}
在实现接口后,我们不需要在XML中配置初始化方法,只需配置一个Bean即可。
第二种示例:下面分别是XML配置文档写法,和类中的方法。那么在获取这个类时,就会自动运行init方法。
<bean id="sampleDAO" class="parafeel.dao.SampleDAO" init-method="init">
</bean>
public class SampleDAO {
public void init() {
System.out.println("init");
}
public void daoSay(String s){
System.out.println("SampleDAO : " + s);
}
}
以上便是配置bean的生命周期初始化部分的两个方法。而销毁的时候与此类似,也是两个方法。分别是,一实现org.springframework.beans.factory.DisposableBean接口实现其destroy方法。二是在XML配置文档中配置其destroy-method字段对应的方法,同时在bean中定义好对应方法。与初始化的步骤和内容相似,此处就不重复了。此外需要提一点的是,我们还可以在XML中配置所有bean的默认初始化和销毁方法,只需要设置default-init-method字段和default-destroy-method字段即可。
当然,我们可能分别实现了,设置默认的初始化或销毁方法,实现了初始化或销毁接口,定义了init-method与destroy-method字段对应的方法,这三种设置方式我们都实现了。那么运行的情况是,先开始初始化接口的方法,再进行init-method,先开始销毁接口的方法,再进行destroy-method,同时default的初始化或销毁方法不执行。
4)Aware。这块就我的了解,大概就是利用Spring下的各种Aware接口,来辅助Spring Bean编程访问Spring容器。之前的几项,我们发现只要配置好之后,我们是关注不了Sping的IOC容器里是怎么运行的,我们直接面对的是返回给我们的结果。但是有时候我们需要访问其容器内部,此时就需要Aware接口。例如,我们在一个类中,实现ApplicationAware接口,并实现其setApplication方法,如下:
public class SampleDAO implements ApplicationContextAware{
ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
// TODO Auto-generated method stub
this.applicationContext = applicationContext;
}
我们可以看到,当一个类
实现
ApplicationAware接口,并实现其
setApplication方法,我们可以把
ApplicationContext注入到此类的属性中,这就相当于把Spring容器内部的资源打开了,我们利用此接口,就可以操作容器内部的重要资源。这块的内容应该是比较复杂和多的,但是基本的应用方向应该就是这样。
3、Bean的自动装配
上面的这些内容就是Spring的IOC方面的主要内容了,但是实现这些内容的方式除了配置的方式之外
我们还可以采用注解的方式实现自动装配(Autowiring)
1)之前我们说过IOC容器注入Bean的两种方式,设置注入和构造注入,这两种都需要在XML中配置,而利用自动装配,我们可以减少甚至不用XML来配置。
常见的有设置@Autowiring的属性的方法,默认我们不设置自动装配属性时,就是@Autowiring(NO),此时我们在配置一个Bean的属性为另一个Bean时,就需要配置其preperties字段或constructir-arg字段,而利用@Autowiring(byName)或者@Autowiring(byType)和@Autowiring(constructor)则可以不需要在一个bean内部配置其各种属性,在这个bean对应的类的内部各属性只要也是配置了的Bean时,就会发生自动装配。而不需要我们手动配置。
示例:我们在XML中配置如下:
default-autowire="byName"
>
<bean id="sampleDAO" class="parafeel.dao.SampleDAO">
</bean>
<bean id="sampleService" class="parafeel.service.SampleService">
</bean>
在对应的SampleService的Bean里设置SampleDAO属性,并设置其set方法。
public class SampleDAO {
public void daoSay(String s){
System.out.println("SampleDAO : " + s);
}
}
public class SampleService implements serviceInter{
private SampleDAO sampleDAO;
public void setSampleDAO(SampleDAO sampleDAO) {
this.sampleDAO = sampleDAO;
}
@Override
public void saySampleService(String s) {
sampleDAO.daoSay(s);
System.out.println("SampleService:" + s);
}
}
而在下面的测试方法中,我们直接获取sampleService的过程中,sampleDAO已经被注入到这个示例里了,这就是自动装配的实现。
@Test
public void testS() {
SampleService sampleService = super.getBean("sampleService");
sampleService.saySampleService("test");
}
这个就是byName来实现自动装配,此外还有byType。而constructor则是需要在属性对应的类中设置其构造函数而不是set方法,两种的区别主要就是这一点。大概可以理解成,byName和byType是用来简化设值注入,而constructor是用来简化构造注入的。此外我们还要知道在使用 byName、byType和constructor,我们配置不正确则会爆出异常,假如配置正确而在对应内中没有set方法或构造方法,则不会报异常且不会运行。
4、Resources
Resources是针对资源文件的统一接口。利用这个接口,可以读取各个位置存放的资源文件。
二、利用注解的方式来实现IOC
在上面,我们其实基本也就介绍了IOC是干什么,一般怎么用,有什么功能。我们在使用过程中发现,在IOC的应用过程中,配置文件属于重要的一环,什么都需要手动去配置,所谓的自动装配也看上去没那么自动,这时,利用注解(annotation)来实现IOC,会使得配置更加简单。
1、classpath扫描与组件管理
我们可以利用java的注解来配置Bean,而不利用XML来配置。常用的注解有,@Component(通用注解)、@Repository(注解持久层)、@Service(注解服务层)、@Controller(注解控制层)。即把上面的这些注解,注解到各个包的类中,进而来表示之前XML文件中需要分别配置的Bean。在注解上去之后,我们还是需要在XML中,配置一下扫描范围
<context:component-scan base-package="parafeel"></context:component-scan>
<context:property-placeholder location="classpath:/config.properties"/>
通过配置扫描范围,来确定对应的注解已经开始启用了。上面的是典型的扫描,范围比较大。我们还可以利用在XML中配置过滤器来修改范围。
2、如何用注解定义一个Bean
在XML中,我们一般是:
<bean id="sampleDAO" class="parafeel.dao.SampleDAO">
</bean>
<bean id="sampleService" class="parafeel.service.SampleService" scope="singleton">
<property name="sampleDAO" ref="sampleDAO"></property>
</bean>
而,利用注解,我们可以:
@Repository
public class SampleDAO {
public void daoSay(String s){
System.out.println("SampleDAO : " + s);
}
}
@Service
@Scope("singleton")
public class SampleService implements serviceInter{
@Autowired
private SampleDAO sampleDAO;
@Override
public void saySampleService(String s) {
sampleDAO.daoSay(s);
System.out.println("SampleService:" + s);
}
}
而在配置文件中,我们只要顶一下扫描的范围:
<context:component-scan base-package="parafeel"></context:component-scan>
<context:property-placeholder location="classpath:/config.properties"/>
在使用注解的过程中,我们还要主要,例如
1)@Component是一个比@Repository、@Service、@Controller等更加广泛的注解。
2)在使用@Component放在类名前时,默认的Bean的名字就是类名首字母小写,我们也可以自己定义bean名字,例如@Component("test")。即可以定义名字,不定义有默认名字。
同时,关于名字,我们还可以利用实现BeanNameGenerator接口的类A,在XML中配置
<context:component-scan base-package="parafeel" name-generator="A"></context:component-scan>
来实现自定义的默认命名策略。
3)我们可以在Bean的上面通过@scope("singleton"),利用设置其值,来定义一个Bean的作用域。
同时关于作用域,我们也可以实现ScopeMetadataResolver接口的类A,在XML中配置scope-resolver="A",如上,来实现自定义的作用域。
4)设置scoped-proxy,来设置代理方式,指定代理。
最后,我们在设置类A中有一属性b,以及类B。面对这样情况,我们既可以在属性b上@Autowired,也可以在属性b对应的setter方法上@Autowired,还可以在有属性b的A内构造方法上@Autowired。其实与XML配置的道理类似,只是方式不一样。
3、详解@Autowired注解
我们在上面利用注解来定义一个Bean的解释中,可以发现@Autowired是一个很常用的接口。关于@Autowired这个接口,我们应该知道一下几点:
1)@Autowired可以直接注解众所周知的常用的解析依赖接口,例如
@Service
@Scope("singleton")
public class SampleService implements serviceInter{
@Autowired
private ApplicationContext applicationContext;
@Autowired
private SampleDAO sampleDAO;
在之前,我们有所过,要想获得ApplicationContext这个对象,来深度试用Spring容器的内部,一般可以使类继承相应的Aware接口。而利用
@Autowired,我们可以直接自动往类里自动装配这个属性。
2) @Autowired可以用来注解泛型(在3.1上实验为成功),如下:
@Service
@Scope("singleton")
public class SampleService implements serviceInter{
@Autowired
private daoInter<Integer> integerDAO;
@Autowired
private SampleDAO sampleDAO;
还可以注解在集合Set或者List容器上,用来获取对应泛型的所有Bean:
public class SampleService implements serviceInter{
@Autowired
private Set<daoInter> integerDAO;
@Autowired
private SampleDAO sampleDAO;
3)@Autowired可以用来注解接口,当一个接口只有一个属性类时,我们可以在此接口上注解,在自动装配时,就会自动装配此接口的唯一实现类,当有多个实现类的时候会报异常。
4)@Autowired是有Spring下的BeanPostProcessor处理的,所有不能再BeanPostProcessor上应用这个注解。
4)@Qualifier,当有多个Bean实例或者多个方法时,可以利用@Qualifier来缩小扫描的指定范围。
3、详解@Bean注解
@Bean注解类似于XML配置文档中的<bean/>,我们的常用方式为:
@Configuration
public class SampleService implements serviceInter{
@Bean
public SampleDAO mySampleDAO() {
return new SampleDAO();
}
等价于:
<bean id="mySampleDAO" class="....">
</bean>
当然,我们也可以在每个
@
Bean注解上配置其名称,生命周期的初始化和销毁方法等。@Bean(name=" " initmethod=" " destroymethod=" ")等。还能配置@Bean的scope作用域,默认作用域是singleton。
此外,我们还可以在@Bean上通过@ImportResource注解来获取对应位置的资源文件,@Value来给对应的属性进行赋值。此处可以用在例如数据库的配置处,我们只需要在Bean对应的类上@ImportResource对应位置的资源文件,再在类里各属性处利用@Value来进行赋值即可。
这就是IOC的基本内容。