我们来看一个老式Java对象POJO的例子:
package com.habuma.spring;
public class HelloWorldBean{
public String sayHelllo(){
return "Hello World";
}
}
DI
spring的一项重要作用就是依赖注入,我们可以DI来装配上面的POJO式代码,来帮助应用对象彼此之间保持松散耦合。
它的意义在于:在实际的应用中,有两个或者两个以上的类组成,它们需要相互协作来完成特定的功能。每个对象负责管理与自己
相互协作的对象(即它所依赖的对象)的引用,这样会导致高度耦合和难以测试代码。
我们来看一个紧耦合的例子:
package com.springinaction.knight;
public class DamselRescuingKnight implements knight{
private RescueDameselQuest quest;
public DamselRescuingKnight(){
this.quest=new RescueDamselQuest();//与RescueDamselQuest紧耦合
}
public void embarkOnQuest(){
quest.embark();
}
}
这样的紧耦合有两个缺点,一方面DamselRescuingKnight与RescueDameselQuest耦合在一起,这样限制了这个骑士的能力。另一方面耦合的代码
难以测试,难以复用和理解。
这时DI可以很好的解决这两个问题,依赖注入会将所依赖的关系自动交给目标对象,而不是让对象自己去获取依赖。我们来看下面的例子:
package com.springinaction.knights;
public class BraveKnight implements Knight{
private Quest quest
public BraveKnight(Quest quest){//Quest被注入进来,BraveKnight没有自行创建探险任务,而是把它作为构造器参数注入,这就是构造器注入。
//这里的任务类型是Quest,这样所有探险任务都实现一个接口,BraveKnight就可以做RescueDamselQuest、SlayDragonQuest等任意Quest的实现。
this.quest=quest;
}
public void embarkOnQuest(){
quest.embark();
}
}
对依赖进行替换最常用的方法就是在测试的时候使用mock实现。测试代码就不详细说明了。
让我们来看如何把特定的探险任务传给骑士:
package com.springination.knights;
import java.io.printStream;
public class SlayDragonQuest implements Quest{
private printstream stream;
public SlayDragonQuest(printstream stream){
this.stream=stream;
}
public void embark(){
stream.println("Embarking on quest to slay the dragon!");
}
}
装配
创建应用组件之间协作的行为我们称为装配。Spring有多种装配bean的方式,采用XML是很常见的一种装配方式。
我们来看下面这个配置文件knights.xml,将BraveKnight、SlayDragonQuest和PrintsStream装配到一起。
<?xml version="1.0" encoding=='UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www-instance.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="knight" class="com.springinaction.knights.BraveKnight">
<constructor-arg ref="quest"/>//注入Quest bean
</bean>
<bean id="quest" class="com.springinaction.knights.SlayDragonQuest">//以下语句来创建SlayDragonQuest
<constructor-arg value="#{T(System).out}"/>
</bean>
</bean>
当然Spring还支持java来描述配置。
package com.spring.knights.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotion.Configuration;
import com.springinaction.knights.BraveKnight;
import com.springinaction.knights.eKnight;
import com.springinaction.knights.Quest;
import com.springinaction.knights.SlayDragonQuest;
@Configuration
public class knightConfig{
@Bean
public knight knight(){
return new Braveknight(quest());
}
@Bean
public Quest quest(){
return new SlayDragonQuest(System.out);
}
}
Spring通过上下文装载bean的定义并组装它们。
XML文件一般用ClassPathXmlApplicationContext作为上下文,我们看下面的例子:
package com.springinaction.knights;
import org.springframework.context.support.
ClassPathXmlApplicationContext;
public satic void main(String[]args)throws Exception{
ClassPathXmlApplicationContext context= //加载Spring上下文
new ClassPathXmlApplicationContext("META-INF/spring/knights.xml");
knight knight=context.getBean(knight.class);//获取knight bean
knight.embarkQuest();使用knight
context.close();
}
}
应用切面
DI能够让相互协作的软件组织保持松耦合,应用切面(AOP)可以把遍布应用各处的功能分离出来形成可重用的组件。
系统的组件除了实现自身核心的功能之外,这些组件还经常承担额外的职责。诸如日志、事务管理和安全这样的系统服务我们称为横切关注点。
AOP的作用就是能够将这些服务模块化,以声明的方式应用到所需的组件中去。
假设我们需要吟游诗人来记载骑士的所以事迹,我们使用Minstrel类。看下面的例子:
package com.springinaction.knights;
import java.io.printstream;
public class Minstrel{
private PrintStream stream;
public Minstrel(PrintStream stream){
this.stream=stream;
}
public void singBeforeQuest(){//探险之前被调用
stream.println("Tee hee hee,the brave knight is so brave!");
}
public void singAfterQuest(){//探险之后调用
stream.println("Tee hee hee,the brave knight"+"did embark on a quest!");
}
}
我们做一下适当调整让BraveKnight可以使用在Minstrel。
package com.springinaction.knights;
public class BraveKnight implements Knight{
private Quest quest;
private Minstrel minstrel;
public BraveKnight(Quest quest,Minstrel ministrel){
this.quest=quest;
this.ministrel=ministrel;
}
public void embarkOnQuest() throws QuestException{
minstrel.singBeforeQuest();
quest.embark();
ministrel.singAfterQuest();
}
}
这里的一个问题是吟游诗人的行为受到了骑士的影响,为何骑士还需要提醒吟游诗人做他份内的事?
现在我们将Minstrel抽象为一个切面,只需在Spring配置文件中声明它。
<?xml version="1.0" encoding=='UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www-instance.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://wwwspringframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/aop
http://www.springframework.org/schema/beans/spring-aop-3.2.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring/spring-beans.xsd">
<bean id="knight" class="com.springinaction.knights.BraveKnight">
<constructor-arg ref="quest"/>//注入Quest bean
</bean>
<bean id="quest" class="com.springinaction.knights.SlayDragonQuest">//以下语句来创建SlayDragonQuest
<constructor-arg value="#{T(System).out}"/>
</bean>
<bean id="ministrel" class="com.springinaction.knights.Minstrel">//声明Minstrel bean
<constructor-arg value="#{T(System).out}"/>
</bean>
<aop:config>
<aop:aspect ref="minstrel">
<aop:pointcut id="embark"
expression="execution(* *.embarkOnQuest(..))"/>//定义切点
<aop:before pointcut-ref="embark"//声明前置通知
method="singAfterQuest"/>
<aop:after pointcut-ref="embark"//声明前置通知
method="singAfterQuest"/>
</aop:aspect>
</aop:config>
</beans>
最后我们简单介绍几种应用上下文:
AnnotationConfigApplicationContext:从一个或多个基于Java的配置类中加载Spring应用上下文
AnnotationConfigWebApplicationContext:从一个或多个基于Java的配置类中加载Spring Web应用上下文
ClassPathXmlApplicationContext:从类路径下的一个或多个XML配置文件中加载上下文定义,把应用上下文的定义文件作为类资源
FileSystemXmlapplicationContext: 从文件系统下的一个或者多个XML配置文件中加载上下文定义
xmlWebApplicationContext:从web应用下的一个或者多个XML配置文件中加载上下文定义。