一、AOP的基本概念
连接点(Jointpoint):连接点可以是类初始化,方法执行,方法调用,字段调用等。
切入点(Pointcut):切入到目标对象的连接点集合,在Spring中只支持切入方法。,通常是一个表达式。
通知(Advice):在目标对象的连接点执行的行为。
包括前置通知,后置通知,环绕通知。
切面(Aspect):通知、引入和切入点的集合。
引入(inter-type declaration):为目标对象添加一个实现接口,间接地添加新的字段或方法。
AOP代理(AOP Proxy):AOP的实现就是使用代理模式来为目标对象应用切面。
通知类型:
前置通知(before):在目标方法执行前执行的方法
后置返回通知(after-returning):在目标方法正常执行完毕后,将返回结果作为该后置返回通知的实现方法的参数。
后置异常通知(after-throwing):如果在目标方法执行过程中出现异常,则将异常信息作为后置异常通知的实现方法的方法参数。
后置最终通知(after-finally):不管目标是否正常执行,还是抛出异常,都会执行的通知方法。
环绕通知(around):可以实现上述四个通知的功能;方法的第一个参数必须为ProceedingJoinPoint类型。
二、Schema配置显示AOP
1.定义一个目标接口的实现类,定义几个方法作为连接点。
2.定义一个通知实现类,实现通知方法。
3.xml中配置aop命名空间
<!-- 配置目标类 -->
<bean id="helloTargetImpl" class="aop.target.HelloTargetImpl">
</bean>
<!-- 配置 通知实现类 -->
<bean id="firstAspect" class="aop.aspect.FirstAspect"/>
<!-- <aop:config proxy-target-class="true"> 使用cglib代理 -->
<aop:config>
<!-- <aop:pointcut> 用来定义切入点,该切入点可以重用 -->
<aop:pointcut expression="execution(* aop.target..say*(..))" id="helloPointcut"/>
<aop:aspect ref="firstAspect"> <!-- 引入切面通知的实现类,配置通知 -->
<aop:before pointcut-ref="helloPointcut" method="beforeAdvice"/>
<!-- 前置通知配置:切入点,method=通知实现类中方法名,于是在执行切入点中的目标方法时,会先执行该前置通知的实现方法 -->
<aop:after-returning pointcut-ref="helloPointcut" method="afterReturnAdvice"/>
<!-- 使用throwing可以指定被传递的异常通知类型:只有目标方法抛出的异常类型与通知实现方法的参数异常类型相匹配时,该通知才会执行,如果通知中参数类型为Throwable则匹配任何类型 -->
<aop:after-throwing pointcut-ref="helloPointcut" method="afterExceptionAdvice"/>
<aop:after pointcut-ref="helloPointcut" method="afterFinallyAdvice"/>
<aop:around pointcut="execution(* aop.target.HelloTargetImpl.sayMessage(String,int)) and args(name,age)" method="arroundAdvice" />
</aop:aspect>
</aop:config>
三、注解实现AOP
@Aspect:将一个类声明为切面
@Pointcut(value=”切入点表达式”,argNames=”参数名列表”)
@Before(value=”切入点表达式或命名切入点”,argNames=”参数名列表”)
@AfterReturning(pointcut=”切入点表达式或命名切入点”, returning=”返回结果参数名”)
@AfterThrowing(pointcut=”切入点表达式或命名切入点”, throwing=”抛出异常参数名”)
@After(“切入点表达式或命名切入点”)
@Around(“切入点表达式或命名切入点”)
代码例子:
@Component
@Aspect
public class AspectJAnnotation {
@Pointcut(value="execution(* me.enzo.aop.aspectj.annotation.AspectJTarget.sayTarget(..)) ") //指定方法匹配,可以不用指定参数
public void pointcut1(){}
//@Before(value = "pointcut1()") //方法1:使用已声明的切入点
@Before("execution(* me.enzo.aop.aspectj.annotation.AspectJTarget.sayTarget(..))") //方法2:使用切入点表达式
public void before1(){
System.out.println("before1.");
}
@Pointcut(value="execution(* me.enzo.aop.aspectj.annotation.AspectJTarget.sayHello(..)) && args(param)") // args(param...)表示切入点会传入参数
public void pointcut2(String param){} // 让支持方法去接收参数
@Before("pointcut2(String) && args(param)") //第一种方式
//@Before(value="pointcut2(param)",argNames="param") //第二种方式 这两种方式都表明了,会传入参数名为param,所以Advice方法必须接收它,且参数必须一致
public void before2(String param){
System.out.println("before2参数:"+param);
}
@Pointcut("execution(* me.enzo.aop.aspectj.annotation.AspectJTarget.sayYes(..)) && args(name,age))")
public void pointcut3(String name,int age){}
@Before("pointcut3(String,int) && args(name,age)")
public void before3(String name,int age){
System.out.println("before3参数:"+name+" and "+age);
}
//@AfterReturning(pointcut="pointcut2(String) && args(param)") //这种后置通知并不会返回修改后的参数
@AfterReturning(pointcut="pointcut2(String) ",returning="ss") // 返回参数名只需要与后置返回通知的方法参数一样就行了,不需要与目标方法的返回参数名一样(这里目标方法参数名为param)
public void afterReturning(String ss){
System.out.println("afterReturning = "+ss);
}
@After("pointcut1()")
public void after(){
//通常用于释放资源,且必须准备处理正常返回通知或者异常返回通知
System.out.println("后置最终方法");
}
@Pointcut("execution(* me.enzo.aop.aspectj.annotation.AspectJTarget.throwAdviceTest(..))")
public void pointcut4(){}
@AfterThrowing(value="pointcut4()",throwing="e") //异常信息参数名可以随便
public void afterThrowing(RuntimeException e){
System.out.println("the exception is: "+e.getMessage());
}
@Around("pointcut1() && args(param)") //表明切入点会传入参数,方法必须接收它且参数名必须一致
public Object around(ProceedingJoinPoint pjp,String param) throws Throwable{ //第一个参数必须为ProceedingJoinPoint类型,用于环绕通知,使用proceed()方法来执行目标方法:
System.out.println("ProceedingJoinPoint 执行preceed()方法前");
Object obj = pjp.proceed(new Object[]{"replace the real returned value"});
//即将obj代替了目标方法的返回值 pjp.proceed()代表此时才会执行目标方法,所以最先执行的是环绕通知的proceed()方法前的代码
// Object[]数组中的值作为目标方法参数传入进去
System.out.println("ProceedingJoinPoint 执行preceed()方法后");
System.out.println("Around obj: "+obj);
return obj;
}
@Before("execution(* me.enzo.aop.aspectj.annotation.AspectJTarget.*(..)) && @annotation(myannotation)")
public void beforeMyannotation(Myannotation myannotation){
System.out.println("自定义注解给Advice注入参数,这注解有一个String类型的值:"+myannotation.value());
}
}