曹操自剿黄巾,讨董作,擒吕布,灭袁绍后,队伍达到鼎盛期,拥有兵将100余万,为了统一全国,派手下将领夏候淳领兵十万攻打新野。时当秋月,秋风徐起,夏侯惇引兵至博望坡,新野危在旦夕。
刘备请军师诸葛亮对策。诸葛亮给出的计策是引诱曹军到达博望坡南处后实施火攻。命赵云在北部与曹兵对敌,只输不赢,刘备引本部人马兵援赵云诱敌深入,将曹兵引入峡谷。命关羽领兵1000在博望坡之左玉山右边的安林脚下伏兵,不可动,等曹兵经过看见南面火起出兵围之。命张飞领1000往安林背后山谷埋伏,见南面火起向博望坡存粮处纵火出击。命关平、刘封领500兵马预备引火之物背靠博望坡南处埋伏,曹兵到时放火烧之。
当然,这场战的最后结果是,诸葛亮只用几千兵马在峡谷内火烧夏候淳十万兵马,夏候淳大败而归。
浅析博望坡一役火攻为何可行
曹操发起战争,刘备的决定火拼。诸葛亮想,既然要战,那行,先来个火烧曹军,然后再打。诸葛亮为何想出火烧曹军,并非随便想想而已,这也是有根据的。诸葛亮熟读兵书,而孙子兵法上说:
“凡火攻有五:一曰火人,二曰火积,三曰火辎,四曰火库,五曰火队。行火必有因,烟火必素具。发火有时,起火有日。时者,天之燥也;日者,月在箕、壁、翼、轸也。凡此四宿者,风起之日也。”
这段话就是说:火烧的目标就是敌军人马、粮草辎重、仓库设施。那么火攻的条件就是天气干燥有风以及引火材料。而曹军到达离新野不远的博望坡时,正好满足天气干燥有风的条件,因此火攻是可行的。
Spring AOP实现博望坡一役
纵观博望坡一役,诸葛亮实质上是一个拥有丰富经验的程序员,而孙子兵法是开发大牛孙武编写的一个包含有计策(Advice)和计策运用条件(Pointcut)的一个xml配置文件。在孙子兵法中,火攻篇就是一个Advisor,其中的计策火攻就是一个Advice,实施火攻的条件干燥有风就是一个Pointcut。诸葛亮开发博望坡一役项目时,正因为根据该项目所处具体情况在曹刘双方火拼前(Joinpoint)使用了孙子兵法火攻篇(Advisor),才成功完成了对曹军的截杀,为最后刘备军团获胜提供了条件。下面我们用代码来实现博望坡一役。
(1)定义一个WarCamp 类来表示作战双方阵营,代码如下。
public class WarCamp {
//战斗力
private float effectiveness = 100;
public float getEffectiveness() {
return effectiveness;
}
/**
* 添加战斗力
* @param effectiveness
*/
public void addEffectiveness(float effectiveness){
this.effectiveness += effectiveness;
}
}
(2)定义战争接口
public interface IWar {
/**
* 打战
* @param enemy 敌方阵营
* @param myCamp 我方阵营
* @return true表示我方阵营赢得胜利,false表示敌方胜利,null表示平手
*/
Boolean fight(WarCamp enemy, WarCamp myCamp);
}
(3)在com.chyohn.war.dry.wind包中实现战争IWar接口
public class WarAtBoWangPo implements IWar {
@Override
public Boolean fight(WarCamp enemy, WarCamp myCamp) {
System.out.println("作战双方正在火拼");
if (myCamp.getEffectiveness() == enemy.getEffectiveness()) {
// 平手
return null;
}
// 作战最后比较双方军队实力
return myCamp.getEffectiveness() > enemy.getEffectiveness();
}
}
(4)配置xml文件spring-aop.xml,定义战争bean
<!--定义战争Bean-->
<bean id="war" class="com.chyohn.war.dry.wind.WarAtBoWangPo" />
(5)编写main方法发起博望坡之战
public static void main(String[] args) throws Exception{
ApplicationContext cxa = new ClassPathXmlApplicationContext("classpath:spring-aop.xml");
// 曹操阵营,战斗力为100
WarCamp caocao = new WarCamp();
// 刘备阵营,战斗力为40
WarCamp liubei = new WarCamp();
liubei.addEffectiveness(-60);
// 发起战争
IWar war = cxa.getBean(IWar.class);
Boolean win = war.fight(caocao, liubei);
// 战后结果
System.out.println("曹操军战斗力:" + caocao.getEffectiveness());
System.out.println("刘备军战斗力:" + liubei.getEffectiveness());
if (win == null) {
System.out.println("双方平手");
} else {
String winner = win ? "刘备" : "曹操";
System.out.println(winner + "胜");
}
}
运行结果
作战双方正在火拼
曹操军战斗力:100.0
刘备军战斗力:40.0
曹操胜
这种曹操胜刘备败的结果是因为刘备军团在敌我悬殊非常大的情况下与曹操军团正面交锋造成的。下面我们分别通过Spring的AOP的5种使用方式来引入孙子兵法中的火攻一策来帮助刘备军团获取胜利。
1. 配置ProxyFactoryBean实现通过火攻发起作战
(1)实现火烧敌军的计策(Advice),代码如下。
public class FireCampOfEnemy implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("火烧敌军阵营, 敌军战斗力降低40个点");
WarCamp enemy = (WarCamp)args[0];
enemy.addEffectiveness(-40);
System.out.println("我军阵营战斗力提升低40个点");
WarCamp myCamp = (WarCamp)args[1];
myCamp.addEffectiveness(40);
}
}
(2)重新配置spring-aop.xml文件
<!--定义Advisor:孙子兵法.火攻篇-->
<bean id="fireAttachAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice">
<bean class="com.chyohn.aop.service.war.FireCampOfEnemy"/>
</property>
<!--定义切点:火攻的条件-->
<property name="pattern" value=".*war\.dry\.wind.*"/>
</bean>
<!--通过ProxyFactoryBean定义战争对象-->
<bean id="war" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyTargetClass" value="true"/>
<property name="target">
<!--战争对象-->
<bean class="com.chyohn.aop.service.war.dry.wind.WarAtBoWangPo" />
</property>
<property name="interceptorNames">
<array>
<value>fireAttachAdvisor</value>
</array>
</property>
</bean>
运行main方法后的结果如下:
火烧敌军阵营, 敌军战斗力降低40个点
我军阵营战斗力提升低40个点
作战双方正在火拼
曹操军战斗力:60.0
刘备军战斗力:80.0
刘备胜
2. AOP自动代理实现通过火攻发起作战
修改spring-aop.xml配置
<!--定义Advisor:孙子兵法.火攻篇-->
<bean id="fireAttachAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice">
<bean class="com.chyohn.aop.service.war.FireCampOfEnemy"/>
</property>
<!--定义切点:火攻的条件-->
<property name="pattern" value=".*war\.dry\.wind.*"/>
</bean>
<!--战争对象-->
<bean class="com.chyohn.aop.service.war.dry.wind.WarAtBoWangPo" />
<!--向容器中添加一个自动代理构造器-->
<!--这种方式可以自动匹配需要增强的方法和类-->
<bean class="org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator"/>
运行结果如下:
火烧敌军阵营, 敌军战斗力降低40个点
我军阵营战斗力提升低40个点
作战双方正在火拼
曹操军战斗力:60.0
刘备军战斗力:80.0
刘备胜
3. 使用<aop:config>标签的子标签配置AOP
使用<aop:config>标签有两种方式配置AOP,其一是和前面两种方式一样实现Advice接口并用<aop:advisor>标签实现;其二是定义一个POJO类,并用<aop:aspect>标签的子标签指定增强方法。下面分别介绍这里中。
使用<aop:advisor>实现通过火攻发起作战
使用这种方式有两步,首先实现Advice接口,然后在xml文件中配置Advisor。
这里沿用前面两种方式的Advice,那么这里只需修改spring-aop.xml配置,修改后内容如下。
<!--战争对象-->
<bean class="com.chyohn.aop.service.war.dry.wind.WarAtBoWangPo" />
<!--定义Advice:火攻-->
<bean id="fireCampOfEnemy" class="com.chyohn.aop.service.war.FireCampOfEnemy"/>
<aop:config>
<!--定义Pointcut:干燥有风-->
<aop:pointcut id="firePointcut" expression="within(*..war.dry.wind.*)"/>
<!--定义Advisor:孙子兵法.火攻篇-->
<aop:advisor advice-ref="fireCampOfEnemy" pointcut-ref="firePointcut"/>
</aop:config>
运行结果如下
火烧敌军阵营, 敌军战斗力降低40个点
我军阵营战斗力提升低40个点
作战双方正在火拼
曹操军战斗力:60.0
刘备军战斗力:80.0
刘备胜
使用<aop:aspect>标签实现通过火攻发起作战
这种方式也是简单两步:首先自定义一个含有增强逻辑的POJO,然后使用<aop:aspect>标签下的子标签指定增强方法。
(1)自定义POJO指定增强方法。
定义一个POJO类,名称为SunZiBingFa如下。
public class SunZiBingFa {
/**
* 火攻
* @param joinPoint
*/
public void fire(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
System.out.println("火烧敌军阵营, 敌军战斗力降低40个点");
WarCamp enemy = (WarCamp)args[0];
enemy.addEffectiveness(-40);
System.out.println("我军阵营战斗力提升低40个点");
WarCamp myCamp = (WarCamp)args[1];
myCamp.addEffectiveness(40);
}
}
fire方法的内容和FireCampOfEnemy内容差不多。
(2)下面修改spring-aop.xml文件,代码如下。
<!--战争对象-->
<bean class="com.chyohn.aop.service.war.dry.wind.WarAtBoWangPo" />
<!--孙子兵法-->
<bean id="sunZiBingFa" class="com.chyohn.aop.service.war.SunZiBingFa"/>
<aop:config>
<!--定义Pointcut:干燥有风-->
<aop:pointcut id="firePointcut" expression="within(*..war.dry.wind.*)"/>
<!--定义Advisor:孙子兵法.火攻篇-->
<aop:aspect ref="sunZiBingFa">
<aop:before method="fire" pointcut-ref="firePointcut"/>
</aop:aspect>
</aop:config>
运行结果如下。
火烧敌军阵营, 敌军战斗力降低40个点
我军阵营战斗力提升低40个点
作战双方正在火拼
曹操军战斗力:60.0
刘备军战斗力:80.0
刘备胜
4. 使用<aop: aspectj-autoproxy>配置实现通过火攻发起作战
这种方式则全基于注解来定义一个AOP,和<aop:config>的第二种方式一样,定义一个POJO类,并在这个类中使用AspectJ的注解。
(1)修改SunZiBingFa 类,代码如下。
@Aspect
public class SunZiBingFa {
/**
* 火攻
* @param joinPoint
*/
@Before("within(*..war.dry.wind.*)")
public void fire(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
System.out.println("火烧敌军阵营, 敌军战斗力降低40个点");
WarCamp enemy = (WarCamp)args[0];
enemy.addEffectiveness(-40);
System.out.println("我军阵营战斗力提升低40个点");
WarCamp myCamp = (WarCamp)args[1];
myCamp.addEffectiveness(40);
}
}
(2)修改spring-aop.xml文件,代码如下。
<!--战争对象-->
<bean class="com.chyohn.aop.service.war.dry.wind.WarAtBoWangPo" />
<!--孙子兵法-->
<bean id="sunZiBingFa" class="com.chyohn.aop.service.war.SunZiBingFa"/>
<aop:aspectj-autoproxy/>
运行结果如下。
火烧敌军阵营, 敌军战斗力降低40个点
我军阵营战斗力提升低40个点
作战双方正在火拼
曹操军战斗力:60.0
刘备军战斗力:80.0
刘备胜
总结
(一)使用Spring AOP代理只需要开发者只需做如下3三件事情:
1. 定义和配置增强处理类(Advice或者自定义POJO)。
2. 在配置中定义切点(Pointcut)。
3. 在配置中定义切面 (Advisor)。
(二)5中使用方式代码量从少到多