AOP概念
AOP(Aspect Oriented Programming),即面向切面编程(也叫面向方面编程,面向方法编程)。其主要作用是,在不修改源代码的情况下给某个或者一组操作添加额外的功能。像日志记录,事务处理,权限控制等功能,都可以用AOP来“优雅”地实现,使这些额外功能和真正的业务逻辑分离开来,软件的结构将更加清晰。AOP是OOP的一个强有力的补充。
AOP术语
AOP的术语不太直观,Spring文档中也没有给一个确切的定义,所以重在理解。
-
Join Point: Spring AOP中,join point就是一个方法。(通俗来讲就是起作用的那个方法)。
-
Pointcut: 用来指定join point(通俗来讲就是描述的一组符合某个条件的join point)。通常使用pointcut表达式来限定joint point,Spring默认使用AspectJ pointcut expression language。
-
Advice: 在join point上特定的时刻执行的操作,Advice有几种不同类型,下文将会讨论(通俗地来讲就是起作用的内容和时间点)。
-
Introduction:给对象增加方法或者属性。
-
Target object: Advice起作用的那个对象。
-
AOP proxy: 为实现AOP所生成的代理。在Spring中有两种方式生成代理:JDK代理和CGLIB代理。
-
Aspect: 组合了Pointcut与Advice,在Spring中有时候也称为Advisor。某些资料说Advisor是一种特殊的Aspect,其区别是Advisor只能包含一对pointcut和advice,但是aspect可以包含多对。AOP中的aspect可以类比于OOP中的class。
-
Weaving:将Advice织入join point的这个过程。
Advice的类型
-
Before advice: 执行在join point之前的advice,但是它不能阻止joint point的执行流程,除非抛出了一个异常(exception)。
-
After returning advice: 执行在join point这个方法返回之后的advice。
-
After throwing advice: 执行在join point抛出异常之后的advice。
-
After(finally) advice: 执行在join point返回之后或者抛出异常之后的advice,通常用来释放所使用的资源。
-
Around advice: 执行在join point这个方法执行之前与之后的advice。
实现机制
Spring AOP是基于代理机制的,通过JDK Proxy和CGLIB Proxy两种方法实现代理。
如果target object没有实现任何接口,那么Spring将使用CGLIB来实现代理。CGLIB是一个开源项目,它是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。
如果target object实现了一个以上的接口,那么Spring将使用JDK Proxy来实现代理,因为Spring默认使用的就是JDK Proxy,并且JDK Proxy是基于接口的。这也是Spring提倡的面向接口编程。当然,你也可以强制使用CGLIB来进行代理,但是这样可能会造成性能上的下降。
使用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: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/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--开启切面注解-->
<aop:aspectj-autoproxy/>
<bean id="audience" class="com.zc.demo.aop.Audience"/>
<bean id="magician" class="com.zc.demo.aop.Magician"></bean>
<aop:config>
<!--定义切面 引用audience Bean 将audience定义成一个切面-->
<aop:aspect ref="audience">
<!--用于匹配连接点的执行方法 execution在方法执行时触发
以*开始代表我们不关心方法的返回类型,可以返回任意类型。
com.zc.demo.di.interfaces.Performer.perform() 指定全限定类名和方法名
(..)标识切点选择任意的play()方法,无论改方法的入参是什么。
-->
<!--可以单独定义一个命名切点,也可在每个通知元素中定义-->
<aop:pointcut id="performance" expression="execution(* com.zc.demo.di.interfaces.Performer.perform(..))"/>
<!--Performer.perform()做为切点的执行方法 在执行perform()方法之前先调用audience切面的前置通知方法takeSeats()和turnOffcellPhones()-->
<aop:before pointcut-ref="performance" method="takeSeats"/>
<aop:before pointcut-ref="performance" method="turnOffcellPhones"/>
<!--在切点方法成功执行之后调用后置通知applaud()方法-->
<aop:after-returning pointcut="execution(* com.zc.demo.di.interfaces.Performer.perform(..))"
method="applaud"/>
<!--在切点方法执行时并抛出异常后调用后置通知demandRefund()方法-->
<aop:after-throwing pointcut="execution(* com.zc.demo.di.interfaces.Performer.perform(..))"
method="demandRefund"/>
<!--环绕式通知-->
<aop:around method="watchPerformance" pointcut-ref="performance"/>
</aop:aspect>
<!--定义切面 传递参数-->
<aop:aspect ref="magician">
<aop:pointcut id="thinking" expression="execution(* com.zc.demo.aop.Thinker.thinkOfSomething(String)) and args(thoughts)"/>
<aop:before method="interceptThoughts" pointcut-ref="thinking" arg-names="thoughts"/>
</aop:aspect>
</aop:config>
</beans>
使用注解annotation实现aop
package com.zc.demo.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* 通过注解定义切面
* Created by zhangchi9 on 2016/11/7.
*/
@Component
@Aspect
public class AudienceAnnotation {
/**
* 定义切点
*/
@Pointcut("execution(* com.zc.demo.di.interfaces.Performer.perform(..))")
public void performance(){}
@Before("performance()")
public void takeSeats(){
System.out.println("表演之_前获得座位");
}
@Before("performance()")
public void turnOffCellPhones(){
System.out.println("表演之_前关闭手机");
}
@After("performance()")
public void after(){
System.out.println("无论成功失败表演之后都调用");
}
@AfterReturning("performance()")
public void applaud(){
System.out.println("表演成功之后要鼓掌");
}
@AfterThrowing("performance()")
public void demandRefund(){
System.out.println("表演失败之后要退票");
}
@Around("performance()")
public void watchPerformance(ProceedingJoinPoint joinPoint){
try {
/**表演之前**/
System.out.println("环绕通知——表演之前获得座位!");
System.out.println("环绕通知——表演之前关闭手机!");
long start = System.currentTimeMillis();
/** 执行被通知的方法 **/
joinPoint.proceed();
/**表演之后*/
long end = System.currentTimeMillis();
System.out.println("环绕通知——表演之后要鼓掌!");
System.out.println("环绕通知——"+(end-start));
} catch (Throwable throwable) {
System.out.println("环绕通知——表演失败后我们要求退票!");
throwable.printStackTrace();
}
}
}
源码demo可在https://git.oschina.net/ciyuan/ssm.git获取