第4章 面向切面的Spring
4.1 什麼是面向切面編程
4.1.4 定義AOP術語
- 橫切關注點(cross-cutting concern)
-
通知(advice)
- 前置通知(Before)
- 後置通知(After)
- 返回通知(After-returning)
- 異常通知(After-throwing)
- 環繞通知(Around)
- 切點(pointcut)
-
連接點(joinpoint)
-
切面(Aspcet)
-
引入(Introduction)
-
織入(Weaving)
- 編譯期織入
- 類加載期織入
- 運行期織入
4.1.2 Spring對AOP的支持
Spring提供的4中類型的AOP支持
- 基於代理的經典Spring AOP(這種不介紹,因爲非常笨重和過於複雜,太老舊)
- 純POJO切面
- @AspectJ註解驅動的切面
- 注入式AspectJ切面(適用於Spring各版本)
介紹的AOP方式
- 聲明式AOP
- 基於註解的AOP
Spring通知是Java編寫的
AspectJ是以JAVA語言擴展的方式實現的
Spring在運行時通知對象
SpringAOP實現的實質是代理類
Spring只支持方法級別的連接點
4.2 通過切點來選擇連接點
SpringAOP中,使用AspectJ的切點表達式語言來定義切點。 Spring僅支持AspectJ的切點指示器(pointcut designator)的一個子集。 總結:SpringAOP 與 AspectJ相比弱爆了。
4.2.1 編寫切點
public interface Performance {
void perform();
}
4.2.2 在切點中選擇bean
execution(* concert.Performance.perform(..)) and bean('woodstock')
排除!
execution(* concert.Performance.perform(..)) and !bean('woodstock')
4.3 使用註解創建切面
4.3.1 定義切面
@Aspect
public class Audience {
@Before("execution(** concert.Performance.perform(..))")
public void silenceCellPhones() {
System.out.println("Silencing cell phones");
}
@Before("execution(** concert.Performance.perform(..))")
public void takeSeats() {
System.out.println("Taking seats");
}
@After("execution(** concert.Performance.perform(..))")
public void applause() {
System.out.println("CLAP CLAP CLAP!!!");
}
@AfterThrowing("execution(** concert.Performance.perform(..))")
public void demandRefund() {
System.out.println("Demanding a refund");
}
}
定義切點,添加@Aspect註解
@Aspect
public class Audience {
@Pointcut("execution(** concert.Performance.perform(..))")
public void performance() {}
@Before("performance()")
public void silenceCellPhones() {
System.out.println("Silencing cell phones");
}
@Before("performance()")
public void takeSeats() {
System.out.println("Taking seats");
}
@Before("performance()")
public void applause() {
System.out.println("CLAP CLAP CLAP!!!");
}
@AfterThrowing("performance()")
public void demandRefund() {
System.out.println("Demanding a refund");
}
}
啓用自動代理
@Configuration
@EnableAspectJAutoProxy
@ComponentScan
public class ConcertConfig {
@Bean
public Audience audience() {
return new Audience();
}
}
XML啓動
<context:conponent-scan base-package="concert" />
<aop:aspectj-autoproxy />
<bean class="concert.Audience" />
4.3.2 創建環繞通知
@Aspect
public class Audience {
@Pointcut("execution(** concert.Performance.perform(..))")
public void performance() {}
@Around("performance()")
public void watchPerformance(ProceedingJoinPoint jp) {
try {
System.out.println("Silencing cell phones");
System.out.println("Taking seats");
jp.proceed();
System.out.println("CLAP CLAP CLAP!!!");
} catch (Throwable e) {
System.out.println("Demanding a refund");
}
}
}
4.3.3 處理通知中的參數
public interface CompactDisc {
void play();
void playTrack(int trackNumber);
}
public class BlackDisc implements CompactDisc {
private String title = "";
private String artist = "";
public void setTitle(String title) {
this.title = title;
}
public void setArtist(String artist) {
this.artist = artist;
}
@Override
public void playTrack(int trackNumber) {
System.out.println("track " + trackNumber + " play.");
}
@Override
public void play() {
System.out.println("Playing " + title + " by " + artist);
}
}
切面
@Aspect
public class TrackCounter {
private Map<Integer, Integer> trackCounts = new HashMap<Integer, Integer>();
@Pointcut("execution(* soundsystem.CompactDisc.playTrack(int)) "
+ "&& args(trackNumber)")
public void trackPlayed(int trackNumber) {}
@Before("trackPlayed(trackNumber)")
public void countTrack(int trackNumber) {
int currentCount = getPlayCount(trackNumber);
trackCounts.put(trackNumber, currentCount+1);
}
@Before("execution(* soundsystem.CompactDisc.play(..))")
public void countPlay() {
System.out.println("countPlay");
}
public int getPlayCount(int trackNumber) {
return trackCounts.containsKey(trackNumber) ? trackCounts.get(trackNumber) : 0;
}
}
配置
@Configuration
@EnableAspectJAutoProxy
public class TrackCounterConfig {
@Bean
public CompactDisc sgtPeppers() {
BlackDisc cd = new BlackDisc();
cd.setTitle("Sgt. pepper's Lonely Hearts Club Band");
cd.setArtist("The Beatles");
return cd;
}
@Bean
public TrackCounter trackCounter() {
return new TrackCounter();
}
}
測試
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=TrackCounterConfig.class)
public class TrackCounterTest {
@Autowired
private CompactDisc cd;
@Autowired
private TrackCounter counter;
@Test
public void testTrackCounter() {
cd.playTrack(1);
cd.playTrack(2);
cd.playTrack(3);
cd.playTrack(3);
cd.playTrack(3);
cd.playTrack(3);
cd.playTrack(7);
cd.playTrack(7);
cd.play();
cd.play();
for (int i=1; i<=7; ++i) {
int count = counter.getPlayCount(i);
System.out.println(i + " : " + count);
}
}
}
4.3.4 通過註解引入新功能
新接口
public interface Encoreable {
void performEncore();
}
切面
@Aspect
public class EncoreableIntroducer {
@DeclareParents(value="concert.Performance+",
defaultImpl=DefaultEncoreable.class)
public static Encoreable encoreable;
}
4.4 在XML中聲明切面
public class Audience {
public void silenceCellPhones() {
System.out.println("Silencing cell phones");
}
public void takeSeats() {
System.out.println("Taking seats");
}
public void applause() {
System.out.println("CLAP CLAP CLAP!!!");
}
public void demandRefund() {
System.out.println("Demanding a refund");
}
}
4.4.1 聲明前置和後置通知
<aop:config>
<aop:aspect ref="audience">
<aop:before
pointcut="execution(** concert.Performance.perform(..))"
method="silenceCellPhones" />
<aop:before
pointcut="execution(** concert.Performance.perform(..))"
method="takeSeats" />
<aop:after-returning
pointcut="execution(** concert.Performance.perform(..))"
method="applause" />
<aop:after-throwing
pointcut="execution(** concert.Performance.perform(..))"
method="demandRefund" />
</aop:aspect>
</aop:config>
定義切點
<aop:config>
<aop:aspect ref="audience">
<aop:pointcut
expression="execution(** concert.Performance.perform(..))"
id="performance"/>
<aop:before
pointcut-ref="performance"
method="silenceCellPhones" />
<aop:before
pointcut-ref="performance"
method="takeSeats" />
<aop:after-returning
pointcut-ref="performance"
method="applause" />
<aop:after-throwing
pointcut-ref="performance"
method="demandRefund" />
</aop:aspect>
</aop:config>
4.4.2 聲明環繞通知
public class Audience {
public void watchPerformance(ProceedingJoinPoint jp) {
try {
System.out.println("Silencing cell phones");
System.out.println("Taking seats");
jp.proceed();
System.out.println("CLAP CLAP CLAP!!!");
} catch (Throwable e) {
System.out.println("Demanding a refund");
}
}
}
<aop:config>
<aop:aspect ref="audience">
<aop:pointcut
expression="execution(** concert.Performance.perform(..))"
id="performance"/>
<aop:around
pointcut-ref="performance"
method="watchPerformance" />
</aop:aspect>
</aop:config>
4.4.3 爲通知傳遞參數
public interface CompactDisc {
void playTrack(int trackNumber);
}
public class BlackDisc implements CompactDisc {
private String title = "";
private String artist = "";
@Override
public void playTrack(int trackNumber) {
System.out.println("track " + trackNumber + " play.");
}
public void setTitle(String title) {
this.title = title;
}
public void setArtist(String artist) {
this.artist = artist;
}
public String getTitle() {
return title;
}
public String getArtist() {
return artist;
}
}
TrackCounter
public class TrackCounter {
private Map<Integer, Integer> trackCounts = new HashMap<Integer, Integer>();
public void countTrack(int trackNumber) {
int currentCount = getPlayCount(trackNumber);
trackCounts.put(trackNumber, currentCount+1);
}
public int getPlayCount(int trackNumber) {
return trackCounts.containsKey(trackNumber) ? trackCounts.get(trackNumber) : 0;
}
}
XML
<bean id="trackCounter" class="soundsystem4_4_3.TrackCounter" />
<bean id="cd" class="soundsystem4_4_3.BlackDisc">
<property name="title" value="Sgt. pepper's Lonely Hearts Club Band" />
<property name="artist" value="The Beatles" />
</bean>
<aop:config>
<aop:aspect ref="trackCounter">
<aop:pointcut
expression="execution(* soundsystem4_4_3.CompactDisc.playTrack(int)) and args(trackNumber)"
id="trackPlayed"/>
<aop:before pointcut-ref="trackPlayed" method="countTrack" />
</aop:aspect>
</aop:config>
test
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations= {"classpath:soundsystem4_4_3/trackconfig.xml"})
public class TrackCounterTest {
@Autowired
private CompactDisc cd;
@Autowired
private TrackCounter counter;
@Test
public void testTrackCounter() {
cd.playTrack(1);
cd.playTrack(2);
cd.playTrack(3);
cd.playTrack(3);
cd.playTrack(3);
cd.playTrack(3);
cd.playTrack(7);
cd.playTrack(7);
for (int i=1; i<=7; ++i) {
int count = counter.getPlayCount(i);
System.out.println(i + " : " + count);
}
}
}
4.4 通過切面引入新的功能
<aop:config>
<aop:aspect>
<aop:declare-parents
types-matching="concert.Performance+"
implement-interface="concert.Encoreable"
default-impl="concert.DefaultEncoreable" />
</aop:aspect>
</aop:config>
方法2:delegate-ref
<bean id="encoreableDelegate" class="concert.DefaultEncoreable" />
<aop:config>
<aop:aspect>
<aop:declare-parents
types-matching="concert.Performance+"
implement-interface="concert.Encoreable"
delegate-ref="encoreableDelegate" />
</aop:aspect>
</aop:config>
4.5 注入AspectJ切面
public aspect CriticAspect {
public CriticAspect() {}
pointcut performance() : execution(* perform(..));
before():performance() {
System.out.println(criticismEngine.getCriticism());
}
private CriticismEngine criticismEngine;
public void setCriticismEngine(CriticismEngine criticismEngine) {
this.criticismEngine = criticismEngine;
}
}
public interface CriticismEngine {
String getCriticism();
}
public class CriticismengineImpl implements CriticismEngine {
@Override
public String getCriticism() {
int i = (int)(Math.random() * criticismPool.length);
return criticismPool[i];
}
private String[] criticismPool;
public void setCriticismPool(String[] criticismPool) {
this.criticismPool = criticismPool;
}
}
XML 注意 factory-method="aspectOf"
<bean id="criticismEngine" class="concert.CriticismengineImpl">
<property name="criticismPool">
<list>
<value>Wrost perforamance ever.</value>
<value>Wrost perforamance ever.</value>
<value>Wrost perforamance ever.</value>
</list>
</property>
</bean>
<bean class="concert.CriticAspect" factory-method="aspectOf">
<property name="criticismEngine" ref="criticismEngine"></property>
</bean>