通知定义了切面是什么以及何时使用。Spring切面可以应用5种类型的通知:
连接点是应用执行过程中能够插入切面的一个点。
切点的定义会匹配通知所要织入的一个或者多个连接点。通常使用明确的类和方法名称,或者利用正则表达式定义所匹配的类和方法名称来指定这些切点。
切面是通知和切点的结合。
引入允许我们向现有的类添加新的方法或属性。
织入是把切面应用到目标对象并创建新的代理对象的过程。切面在指定的连接点被织入到目标对象中。在目标对象的生命周期里有多个点可以进行织入:
非常重要一点:切点定义了哪些连接点会得到通知。
Spring提供了4种类型的AOP支持:
Spring对AOP的支持局限于方法拦截。
通过在代理类中包裹切面,Spring在运行期把切面织入到Spring管理的bean中。Spring的切面由包裹了目标对象的代理类实现。代理类处理方法的调用,执行额外的切面逻辑,并调用方法。
execution(* com.up.vincent.Performance.perform(..)) and within(com.up.vincent.*)
任意返回类型,方法所属的类,方法,任意参数,指定包下。
execution(* com.up.vincent.Performance.perform(..)) and !bean('woodstock')
切面的通知会被编织到所有ID不为woodstock的bean中。
切面样例代码:
@Aspect
public class Audience {
@Pointcut("execution(* com.soundsystem.Performance.perform(..))")
public void performance() {
}
@Before("performance()")
public void silencePhone() {
System.out.println("xx xx");
}
@Before("performance()")
public void takeSeates() {
System.out.println("xx xx");
}
@AfterReturning("performance()")
public void applause() {
System.out.println("xx xx");
}
@AfterThrowing("performance()")
public void demandRefund() {
System.out.println("xx xx");
}
}
在配置类的类级别上通过使用@EnableAspectJAutoProxy注解启动自动代理功能。
@EnableAspectJAutoProxy
@Configuration
@ComponentScan
public class ConcertConfig {
@Bean
public Audience audience() {
return new Audience();
}
}
如果使用XML配置的方式:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.soundsystem"/>
<!-- 启动AspectJ自动代理 -->
<aop:aspectj-autoproxy/>
<!-- 声明Audience这个bean -->
<bean id="audience" class="com.soundsystem.Audience"/>
</beans>
注意:不管使用JavaConfig还是XML,Aspect自动代理都会为使用@Aspect注解的bean创建一个代理,这个代理会围绕着所有该切面的切点所匹配的bean。
@Aspect
public class Audience {
@Pointcut("execution(* com.soundsystem.Performance.perform(..))")
public void performance() {
}
@Around("performance()")
public void watchPerformance(ProceedingJoinPoint joinPoint) {
try {
System.out.println("xx xx");
joinPoint.proceed();
System.out.println("yy yy");
} catch (Throwable throwable) {
System.out.println("zz zz");
}
}
}
@Aspect
public class TrackCounter {
private Map<Integer, Integer> trackCounts = new HashMap<>();
@Pointcut("execution(* com.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);
}
public int getPlayCount(int trackNumber) {
return trackCounts.containsKey(trackNumber) ? trackCounts.get(trackNumber) : 0;
}
}
注意:参数的名称trackNumber也与切点方法签名中的参数想匹配。从命名切点到通知方法的参数转移。
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareParents;
@Aspect
public class EncoreableIntroducer {
@DeclareParents(value="concert.Performance+", defaultImpl=DefaultEncoreable.class)
public static Encoreable encoreable;
}
@DeclareParents解释说明:
Spring中AOP配置元素能够以非侵入性的方式声明切面
AOP配置元素 | 用途 |
---|---|
< aop:advisor > | 定义AOP通知器 |
< aop:after> | 定义AOP后置通知(不管被通知的方法是否执行成功) |
< aop:after-returning> | 定义AOP返回通知 |
< aop:after-throwing> | 定义AOP异常通知 |
< aop:around> | 定义AOP环绕通知 |
< aop:aspect> | 定义一个切面 |
< aop:aspectj-autoproxy> | 启用@AspectJ注解驱动的切面 |
< aop:before> | 定义一个AOP前置通知 |
< aop:config> | 顶层的AOP配置元素,大多数< aop:*>元素都必须包含在此元素之内 |
< aop:declare-parents> | 以透明的方式为被通知的对象引入额外的接口 |
< aop:pointcut> | 定义一个切点 |
代码样例:
<bean id="audience" class="com.springinaction.springidol.Audience"/>
<aop:config>
<aop:pointcut id="performance" expression="execution(* *.perform(..))"/>
<aop:aspect ref="audience">
<!-- 注意在这些通知中引用方法的时候,只需要写方法名,不需要() -->
<aop:before method="takeSeats" pointcut-ref="performance"/>
<aop:before method="turnOffCellPhones" pointcut-ref="performance"/>
<aop:after-returning method="applaud" pointcut-ref="performance"/>
<aop:after-throwing method="demandRefund" pointcut-ref="performance"/>
</aop:aspect>
</aop:config>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="trackCounter" class="soundsystem.TrackCounter"/>
<bean id="cd" class="soundsystem.BlankDisc">
<property name="title" value="Sgt. Pepper's Lonely Hearts Club Band"/>
<property name="artist" value="The Beatles"/>
<property name="tracks">
<list>
<value>Sgt. Pepper's Lonely Hearts Club Band</value>
<value>With a Little Help from My Friends</value>
<value>Lucy in the Sky with Diamonds</value>
<value>Getting Better</value>
<value>Fixing a Hole</value>
</list>
</property>
</bean>
<aop:config>
<aop:aspect ref="trackCounter">
<aop:pointcut id="trackPlayed" expression="execution(* soundsystem.CompactDisc.playTrack(int)) and args(trackNumber)"/>
<aop:before pointcut-ref="trackPlayed" method="countTrack"/>
</aop:aspect>
</aop:config>
</beans>
<aop:aspect>
<aop:declare-parents types-matching="concert.Performance+" implement-interface="concert.Encoreable" default-impl="concert.DefaultEncoreable" />
</aop:aspect>
注意:default-impl必须使用全限定类名来显示指定Encoreable的实现。除此之外,还可以使用delegate-ref属性来标识。
<aop:aspect>
<aop:declare-parents types-matching="concert.Performance+" implement-interface="concert.Encoreable" delegate-ref="encoreableDelegate" />
</aop:aspect>
<bean id="encoreableDelegate" class="concert.DefaultEncoreable" />
代码JudgeAspect文件的后缀为.aj
public aspect JudgeAspect {
public JudgeAspect() {
}
pointcut performance(): execution(* perform(..));
after() returning(): performance() {
System.out.println(criticismEngine.getCriticism());
}
private CriticismEngine criticismEngine;
public void setCriticismEngine(CriticismEngine criticismEngine) {
this.criticismEngine = criticismEngine;
}
}
public class CriticismEngineImpl implements CriticismEngine {
public CriticismEngineImpl() {}
public String getCriticism() {
int i = (int) (Math.random() * criticismPool.length);
return criticismPool[i];
}
// injected
private String[] criticismPool;
public void setCriticismPool(String[] criticismPool) {
this.criticismPool = criticismPool;
}
}
<bean id="criticismEngine" class="com.springinaction.springidol.CriticismEngineImpl">
<property name="criticisms">
<list>
<value>Worst performance ever!</value>
<value>I laughed, I cried, then I realized I was at the wrong show.</value>
<value>A must see show!</value>
</list>
</property>
</bean>
<bean class="com.springinaction.springidol.CriticAspect" factory-method="aspectOf">
<property name="criticismEngine" ref="criticismEngine" />
</bean>
通常情况下,Spring bean由Spring容器初始化,但是AspectJ切面是由AspectJ在运行期创建的。
等到Spring为CriticAspect注入CriticismEngine时,CriticAspect已经被实例化了。Spring需要通过aspectOf()工厂方法获得切面的引用。