曹操自剿黃巾,討董作,擒呂布,滅袁紹後,隊伍達到鼎盛期,擁有兵將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中使用方式代碼量從少到多