Spring的定义
对DI的初步理解
对AOP的初步理解
对DI的深入探究
对AOP的深入探究
Spring的事务管理
Spring MVC
spring的特性之一是DI,而DI的关键在于bean的装配,即创建系统各组件之间的协作关系。而组件是存活在spring容器中的,容器是spring的核心,spring提供多种容器的实现。第一种是bean工厂,即BeanFactory接口;第二种是ApplicationContext接口。BeanFactory是最简单的容器,只提供了基础的依赖注入支持;而后者是在BeanFactory基础之上的,提供了更多的系统服务。
Spring中有多种Beanfactory的实现,其中最常见的一种就是根据XML配置文件来装载bean的XmlBeanFactory。但是要创建一个XmlBeanFactory实例需要传递一个Resource类型的实例给构造函数,正是此Resource对象提供了XML文件给BeanFactory。因此,创建一个Beanfactory可以这样做:
BeanFactory factory = new XmlBeanFactory( new FileSystemResource( “C:/abc.xml” ) );
这是通过文件系统来读取xml文件初始化BeanFactory的,还有许多种方法,比如ClassPathResource、InputStreamResource等。但是,执行上述代码并没有实例化任何一个bean,要实例化一个bean还要调用getBean方法:factory.getBean(“beanName”); 当调用过该方法之后,BeanFactory会依据xml配置文件设置该bean的属性并实例化它。
然而,很多时候我们需要一些更强大的功能,这时我们就需要用到ApplicationContext了。而且在大多数情况下,我们是会使用后者的。然而spring的应用上下文对象也很好用,载入一个上下文很简单:
ApplicationContext context = new FileSystemXmlApplicationContext(“C:/abc.xml”);
或者像这样:
ApplicationContext context = new ClassPathXmlApplicationContext(“abc.xml”);
这时要获得一个bean和BeanFactory时的一样,毕竟ApplicationContext是继承自BeanFactory的。
下面将通过一些例子来由浅入深的进行阐述。一年一度的Java天才大赛来了,每个参赛者都可以表演任何节目。因此定义一个接口,每位参赛者都要实现该接口:
public interface Performer
{ public void perform(); }
首先出场的是一位魔术师,它是一个简单的bean:
public class Juggler implements Performer {
private int beanBags = 3;
public Juggler(int beanBags) {
this.beanBags = beanBags;
}
public void perform(){
System.out.println("JUGGLING " + beanBags + " BEANBAGS");
}
}
传统的注入该bean方式如下:
<bean id=”duke” class=”com.alibaba.Juggler” / >
spring容器装载该bean时,会调用该类的默认构造函数,因此该类的实例的beanBags变量的值是3。我们还可以通过如下的配置方法让该类的实例的变量值是15:
<bean id=”duke” class=”com.alibaba.Juggler” >
<constructor-arg value=”15” />
</bean>
这样,spring注入该bean的时候,就会调用该类的重载构造函数。如果对于一些特殊的构造函数的参数中包含对象的(非基本类型),可以通过如下方式指定参数的值:
<constructor-arg ref=”object-Bean-Id” />
其中“object-Bean-Id”就是另一个bean的id
下面,为了演示spring的setter注入方式,我们荣幸的请到了出色的乐器演奏家Kenny。
public class InstrumentKenny implements Performer
{
public void perform()
{
System.out.print("Playing " + song + " : ");
instrument.play();
}
private String song;
public void setSong(String song) {
this.song = song;
}
private Instrument instrument;
public void setInstrument(Instrument instrument) {
this.instrument = instrument;
}
}
当然,我们需要定义一个乐器接口Instrument:
public interface Instrument
{
public void play();
}
现在,我们创造一个萨克斯风乐器:
public class Saxophone implements Instrument
{
public void play()
{
System.out.println("TOOT TOOT TOOT");
}
}
这时,我们只需要进行如下配置,就可以让Kenny去演奏萨克斯风了:
<bean id="saxophone" class="com.alibaba.Saxophone" /> <bean id=”kenny” class=”com.alibaba.InstrumentKenny”> <property name=”song” value=”Jingle bells” /> <property name=”instrument” ref=” saxophone” /> </bean>
其实我们会发现,使用setter注入和使用构造函数注入,没什么区别,代码几乎相同。如果,kenny很聪明并且还会演奏钢琴的话,我们只需要声明一个钢琴类,再修改一下配置文件,ref=” piano” 即可。几乎不需要修改任何已有代码,这样就实现了松耦合的目的。
现在,kenny可以演奏任何实现Instrument接口的乐器了,然而,这些乐器也可以被其他人所演奏,因此,就产生了乐器的个人卫生问题(萨卡斯风是要使用嘴吹的)。我们可以通过注入内部bean来解决这个问题:
<bean id=”kenny” class=”com.alibaba.InstrumentKenny”> <property name=”song” value=”Jingle bells” /> <property name=”instrument”> <bean class=”com.alibaba. saxophone” /> </property> </bean>
内部bean不能被其他bean所复用,因此该bean也就没有必要有id属性。当然,在构造函数注入方式下也可以使用内部bean。
下面,将阐述一下spring的自动注入。spring提供了四种自动装配类型:byname、bytype、constructor、autodetect。其中,byname使用最为广泛。现在让我们来回忆一下kenny演奏萨克斯风的配置:
<bean id="saxophone" class="com.alibaba.Saxophone" /> <bean id=”kenny” class=”com.alibaba.InstrumentKenny”> <property name=”song” value=”Jingle bells” /> <property name=”instrument” ref=” saxophone” /> </bean>
我们可以将上面的配置改成如下这样,以实现spring的自动注入:
<bean id=" instrument" class="com.alibaba.Saxophone" /> <bean id=”kenny” class=”com.alibaba.InstrumentKenny” autowire=”buName”> <property name=”song” value=”Jingle bells” /> </bean>
然而,我们也可以使用混合的方式,如:在设置autowire=”byType”时,显式的装配某个bean,而不需要spring自己去寻找一个type相同的bean,以避免有多个相同类型的bean时spring抛出异常。
由此我们可以发现,spring的自动装配存在很多问题,其中最大的缺点就是缺乏代码的可读性和透明性。
我们已经了解了spring对bean的装载的基础知识。下面我们来说一下spring中bean的范围。在spring中,bean的范围可以是如下几种:singleton、prototype、request、session、global-session,比如:
<bean id=”” class=”” scope=”” />
其中,如果不指定scope的话,默认会是单例的,即singleton的,意思是在整个spring容器中,该bean只有一个实例;而prototype则与之相反,每次使用的时候都会创建一次。当然,我们也可以通过其他手段来实现单例:比如我们可以设置bean标签的factory-method属性来指定该类的工厂方法。
在bean生命周期中有两个重要的环节:初始化和销毁。为一个bean指定一个初始化方法和销毁方法很简单,只要设置bean标签的init-method属性和destroy-method即可。当然我们还有另一个方法:实现InitializingBean和DisposableBean接口,然后,在该类中实现afterPropertiesSet方法和destroy方法,以实现bean的初始化和销毁。
下面我们来看一下bean的继承。spring的bean标签中有两个属性:parent和abstract。前者相当于java里的extends,后者则是告诉spring容器,该bean不能被实例化。举个例子,前面我们说过,kenny是一个擅长吹萨克斯的选手,他的配置文件如下:
<bean id="saxophone" class="com.alibaba.Saxophone" />
<bean id=”kenny” class=”com.alibaba.InstrumentKenny”>
<property name=”song” value=”Jingle bells” />
<property name=”instrument” ref=” saxophone” />
</bean>
可是,这个比赛不止kenny一个人会吹萨克斯,现在又有一个david也吹萨克斯,而且也演奏“Jingle bells”。这样的话,如果配置两条除id以外都相同的bean多少有些累赘。这时我们就需要使用到bean的继承特性。配置如下:
<bean id=”baseSaxophonist” class=”com.alibaba.Instrument” abstract=”true” > <property name=”song” value=”Jingle bells” /> <property name=”instrument” ref=” saxophone” /> </bean> <bean id=”kenny” parent=”baseSaxophonist” />
当然,如果现在又来了一位选手,他会吹萨克斯,但是演奏的不是“Jingle bells”,这是否意味着已有的父bean不能使用了呢?当然不是的,我们还可以覆盖父bean的属性。比如:
<bean id=”frank” parent=”baseSaxophonist” > <property name=”song” value=”another song” /> </bean>
然而,声明父bean时我们也可以不指定bean的class属性,具体的class由子bean去指定,这样,我们就可以将一些常用到的属性提出到父bean,让各个bean去继承,而各子bean不必是相同的类型。