spring的核心两个东西:依赖注入(Dependency Injection,DI)和面向切面编程(Aspect-Oriented Programming,AOP)。
一、DI功能是如何实现的
任何一个有实际应用的程序(肯定比helloword复杂)都会有两个或者更多的类组成。传统做法就是每个对象负责管理与自己相互协作的对象的引用,这会导致
高度耦合和难以测试。
public class RescueKnight implements Knight{
private RescueQuest quest;
public RescueKnight(){
this.quest = new RescueQuest();--->高度耦合
}
public void excuteQuest(){
quest.excute();
}
}
这样一个类,就有很大的局限性,RescueKnight就只能执行rescue任务,不能执行其他任务,比如杀一只巨龙。
public class BraveKnight implements Knight{
private Quest quest;
public BraveKnight(Quest quest){
this.quest=quest;
}
public void excuteQuest(){
quest.excute();
}
}
这个类就厉害了,它通过构造器传入一个Quest类型的任务,这个骑士除了很brave以外,还能执行救援,杀龙等任务。这就是DI实现的最大收益,松耦合。如果一个对象只通过接口来表明依赖,而不是具体的实现,那么就能在毫不知情的情况下用具体实现进行替换。
bean装配
spring有多种bean装配方式,xml文件是比较常见的例子
<bean id="knight" class="com.qunhe.hengli.springtest.BraveKnight">
<constructor-arg ref="quest"/>
</bean>
<bean id="quest" class="com.qunhe.hengli.springtest.KillDragonQuest">
<constructor-arg />
</bean>
等同于java代码
@Configuration
public void KnightConfig{
@Bean
public Knight knight(){
return new BraveKnight(quest());
}
@Bean
public Quest quest(){
return new KillDragonQuest();
}
}
再看上面代码,尽管BraveKnight依赖于Quest,但是并不知道这个Quest传过来的是什么类型的参数,也不知道来自哪里。你只有通过xml配置才知道这些组件这么装配
起来,这样也就能在不改变依赖的情况下修改依赖。实现代码的时候你只要加载xml配置文件,就能获得对象的引用。
方法的调用
public class KnightMain {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new
ClassPathXmlApplicationContext(".xml");
Knight knight = context.getBean(Knight.class);
knight.excuteQuest();
}
}
这个类完全不知道这个骑士执行了什么任务,且不知道是哪个骑士来执行的。
二、面向切面编程
面向切面编程是为了促使软件系统实现关注点的一项技术。比如一个系统由多个组件组成,各个组件负责一块特定的功能,但是往往它需要负责功能之外的一些事情。比如打日志功能,这些经常要融入到有核心业务的组件里面去,如果在有核心业务的组件里面去实现打日志功能那么代码会变的很混乱,切如果打日志代码的逻辑需要修改,那么需要到各个组件中修改,即使把打日志写成一个模块,各个组件里面还是会调用这个模块的方法。这样组件不仅仅关注自己的核心功能,还需要知道各个对象需要记日志,还要亲自执行这些功能。
总之使用AOP能使这些组件具有更高的内聚性,更关注自己的核心业务,不需要涉及系统服务所带来的复杂性。
比如我们需要一个诗人来对骑士进行歌颂,这是一个记载骑士事迹的服务类。
public class Poet {
public Poet(){
}
public void beforeQuest(){
System.out.println("骑士你太勇敢了!");
}
public void afterQuest(){
System.out.println("骑士你在此次任务中表现的真出色!");
}
}
程序中poet类只有两个方法,一个是在骑士执行任务之前调用的,一个是在执行之后。下面让一个knight可以使用poet:
public class BraveKnight implements Knight{
private Quest quest;
private Poet poet;
public BraveKnight(Quest quest,Poet poet){
this.quest=quest;
this.poet=poet;
}
public void excuteQuest(){
poet.beforeQuest();
quest.excute();
poet.afterQuest();
}
}
然后我们只要把poet这个bean注入到knight中就行了,但是为什么诗人的事需要骑士去提醒他做?应该要他自主来完成啊。并且knight的代码变复杂了,而且如果一个knight不需要poet来歌颂事迹呢?
<bean id="knight" class="com.qunhe.hengli.springtest.BraveKnight">
<constructor-arg ref="quest"/>
</bean>
<bean id="quest" class="com.qunhe.hengli.springtest.KillDragonQuest">
<constructor-arg />
</bean>
<bean id="poet" class="com.qunhe.hengli.springtest.Poet">
<constructor-arg />
</bean>
<aop:config>
<aop:aspect ref="poet">
<aop:pointcut id="excuteQuest"
expression="execution(* com.qunhe.hengli.springtest.Knight.excuteQuest( . . ))
and within(com.qunhe.hengli.springtest.*)) "/>--定义切点
<aop:before pointcut-ref="excuteQuest"--声明前置通知
method="beforeQuest"/>
<aop:after pointcut-ref="excuteQuest"--声明后置通知
method="afterQuest"/>
</aop:aspect>
</aop:config>
首先声明了一个bean在<aop:aspect ref="poet">引用这个bean,<aop:before/>声明了一个前置通知,<aop:after/>声明了一个后置通知,在<aop:pointcut/>
的expression里选择要应用通知的位置,语法用的是aspectj的切点表达式语言。
execution指示器
1.第一个*表示返回值为任意值,我们不用关心方法的返回值
2.com.qunhe.hengli.springtest表示切点仅匹配springtest这个包,可以写*表示匹配任意的包,就不用关心在哪个包下了
3. .Knight表示匹配Knight这个接口,就不用关心类名了
4. .excuteQuest( . . )指定了方法,并且(. .),表示我们不用关心方法传入的参数类型
and表示切点需要匹配所有指示器,当然也可以用or,not
within指示器
within(com.qunhe.hengli.springtest.*)表示在springtest包下的任意方法被调用时