概念术语
通知:切面可能有很多个工作,每个具体的工作成为通知。通知定义了切面做什么以及何时做。
Spring切面可以应用5种类型的通知:
- 前置通知(Before):在目标方法被调用之前调用通知功能;
- 后置通知(After):在目标方法完成之后调用通知,此时不会关
- 心方法的输出是什么;
- 返回通知(After-returning):在目标方法成功执行之后调用通
知; - 异常通知(After-throwing):在目标方法抛出异常后调用通知;
- 环绕通知(Around):通知包裹了被通知的方法,在被通知的方
法调用之前和调用之后执行自定义的行为。
连接点
连接点是在应用执行过程中能够插入切面的一个点。这个
点可以是调用方法时、抛出异常时、甚至修改一个字段时。切面代码
可以利用这些点插入到应用的正常流程之中,并添加新的行为。
切点(Poincut)
切点定义了在什么地方执行切面功能。一个切面并不需要通知应用的所有连接点。切点有助于缩小切面所通知的连接点的范围。切点的定义会匹配通知所要织入的一个或多个连接点。
切面(Aspect)
切面是通知和切点的结合。通知和切点共同定义了切面的全部内容
——它是什么,在何时和何处完成其功能。
引入(Introduction)
引入允许我们向现有的类添加新方法或属性。
织入(Weaving)
织入是把切面应用到目标对象并创建新的代理对象的过程。
切面在指
定的连接点被织入到目标对象中。在目标对象的生命周期里有多个点
可以进行织入:
- 编译期:切面在目标类编译时被织入。这种方式需要特殊的编译
器。AspectJ的织入编译器就是以这种方式织入切面的。 - 类加载期:切面在目标类加载到JVM时被织入。这种方式需要特
殊的类加载器(ClassLoader),它可以在目标类被引入应用
之前增强该目标类的字节码。AspectJ 5的加载时织入(load-time
weaving,LTW)就支持以这种方式织入切面。 - 运行期:切面在应用运行的某个时刻被织入。一般情况下,在织
入切面时,AOP容器会为目标对象动态地创建一个代理对象。
Spring AOP就是以这种方式织入切面的。
spring aop
Spring提供了4种类型的AOP支持:
- 基于代理的经典Spring AOP;
- 纯POJO切面;
- @AspectJ注解驱动的切面;
- 注入式AspectJ切面(适用于Spring各版本)。
Spring AOP构建在动态代理基础
之上,因此,Spring对AOP的支持局限于方法拦截。
spring在运行时通知对象
通过在代理类中包裹切面,spring在运行期把切面织入到spring管理的bean中。代理类封装了目标类,并拦截被通知方法的调用,再把调用转发给真正的目标bean。当代理类拦截到方法调用时,在调动目标bean方法之前,会执行切面逻辑。
因为spring在运行时才创建代理对象,所以我们不需要特殊的编译器来织人spring AOP的切面。
通过切点来选择连接点
切点用于准确定位应该在什么地方应用切面的通知。通知和切点是切面的最基本元素。
spring aop支持的AspectJ切点指示器:
- arg():限制连接点匹配参数为指定类型的执行方法
- @args():限制连接点匹配参数由指定注解标注的执行方法。
- execution():用于匹配是连接点的执行方法
- this():限制连接点匹配AOP代理的bean引用为指定类型的类。
- target:限制连接点匹配目标对象为指定类型的类。
- @target():限制连接点匹配特定的连接对象。这些对象对应的的类要具有指定类型的注解。
- within():限制连接点为指定的类型
- @within():限制连接点匹配指定注解所标注的类型
- @annotaiton:限定匹配带有指定注解的连接点。
以上只有execution是实际执行匹配的,其他都是限制匹配的。
编写切点
需要有个主题来定义切面的切点。为此我们定义了一个performance接口。
package concert;
public interface Performance{
public void perform();
}
编写perform()方法触发的通知:
execution(* concert.Performance.perform(..))&&within(concert.*)
within()指定在concert包下的类。也可以用and or not || ! 等操作符。
execution(* concert.Performance.perform(..)) and bean(‘mybeanid’)
bean()限定bean的id为mybeanid.
也可以用!表示除了特定ID以外的其它bean应用通知。
execution(* concert.Performance.perform(..)) and !bean('mybeanid')
使用注解创建切面
我们已经定义了Performance接口,它是切面中切点的目标对象。将观众定义为一个个切面并应用到演出上。
@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");
}
@AfterReturning("execution(** concert.Performance.perform(..))")
public void applause(){
System.out.println("applause");
}
@AfterThrowing("execution(** concert.Performance.perform(..))")
public void demandRefund(){
System.out.println("demanding a refund");
}
}
AspectJ的五个通知注解:
1. @After
2. @Before
3. AfterReturning
4. AfterThrowing
5. Around
@Pointcut可以定义一个切点,然后就可以把切点表达式替换为切点引用。
@Pointcut("execution(** concert.Performance.perform(..))")
public void performance(){}
@Before("performance()")
public void silenceCellPhones(){
System.out.println("silencing cell phones");
}
@Around("performance()")
public void watchPerformance(ProceedingJoinPoint jp){
System.out.println("silencing cell phones");
System.out.println("taking seats");
try {
jp.proceed();
} catch (Throwable e) {
System.out.println("demanding a refund");
}
}
在JavaConfig上启动自动代理功能。
@Configuration
@EnableAspectJAutoProxy
@ComponentScan
public class ConcertConfig {
@Bean
public Audience audience(){
return new Audience();
}
}
xml的就要用
AspectJ自动代理会为使用@AspectJ注解的bean创建一个代理。这个代理会围绕着所有该切面的切点所匹配的bean。
环绕通知
@Around("performance()")
public void watch(ProceedingJoinPoint jp){
//调用前业务
jp.proceed();
//调用后业务
}
处理通知中的参数
@Aspect
public class bean1{
@PointCut("execution(* x.x.x.method1(int) && args(parametername)")
public void play(int parametername){}
@Before("play(parametername")
public void play2(int parametername){
//这样可以取到parametername
}
}
通过注解引入新功能
利用被称为引入的AOP概念,切面可以为spring bean添加新方法。在spring中,切面只是实现了他们所包装bean相同接口的代理。如果代理也能暴露新接口的话那么看起来就像是切面所通知的bean实现了新的接口。
当引入接口的方法被调用时,代理会把次调用委托给实现了新接口的某个其它对象。
package concert;
public interface Encoreale{
void performance();
}
@Aspect
public class EncorealeIntroducer{
@DeclareParents(value="concert.Performance+",defaultImpl=DefaultEncoreable.class)
public static Encoreable encoreable;
}
@DeclareParents注解由三部分组成:
1. value属性指定了那种类型的bean要引入新接口。
2. defaultImpl属性指定了为引入功能提供实现的类。
3. 注解所标注的静态属性指明了要引入的接口。
和其它切面一样要将它声明为一个bean.这样就可以将一个相同的bean转换为两个不同接口的实现。例如:
ApplicationContext cxt=new ClassPathXmlApplicationContext(
"classpath:/spring/spring.xml");
Performer performer =(Performer)cxt.getBean("juggler");
performer.perform();
Encoreale contestant =(Encoreale)cxt.getBean("juggler");
contestant.performance();
使用xml声明切面
<aop:config>
<aop:aspect ref="audience">
<aop:pointcut expression="execution(** concert.Performance.perform(..))" id="performance"/>
<aop:before method="silenceCellPhones"
pointcut-ref="performance"/>
<aop:after-returning method="applause"
pointcut="execution(** concert.Performance.perform(..))"/>
<aop:after-throwing method="demandfund"
pointcut="execution(** concert.Performance.perform(..))"/>
</aop:aspect>
</aop:config>
引入新功能:
<aop:aspect>
<aop:declare-parents
types-matching="concert.Performance+"
implement-interface="concert.Encoreable"
default-impl="concert.DefaultEncoreable"/>
</aop:aspect>
//可以把上面的default-impl替换成delegate-ref,引用spring bean作为引入的委托。
注入Aspectj切面
AspectJ切面不需要Spring就可以织入到应用中,如果向用Spring的依赖注入为AspectJ切面注入协作者。就需要使用Spring把切面声明为一个bean.如下:
<bean class="aop.CriticAspect" factory-method="aspectOf">
<property name="criticismEngine" ref="criticismEngine"/>
</bean>
spring bean由spring容器初始化,但是AspectJ切面是由AspectJ在运行期创建的。等到spring有机会为切面类注入协作者时,切面类已经被实例化了。
因此这种情况下spring不能负责创建切面类,而是引用AspectJ创建好的切面类实例。然后为它注入。所有的AspectJ都有一个静态的aspectOf()方法,该方法返回切面的一个单例。所以必须调用factory-method来调用aspectOf()而不是切面类的构造方法。