
正文
AOP 面向切面的编程
提示:扫一扫查出行【扫一扫了解最新限行尾号】
复制提示
一、面向切面的编程需求的产生
- 代码混乱:越来越多的非业务需求(日志和验证等)加入后,原有的业务方法急剧膨胀。每个方法在处理核心逻辑的同时还必须兼顾其他多个关注点。
- 代码分散: 以日志需求为例,只是为了满足这个单一需求,就不得不在多个模块(方法)里多次重复相同的日志代码。如果日志需求发生变化,必须修改所有模块。
二、实现面向切面的编程
- 将需要实现AOP的类注入到Spring容器中,例如:
package com.neuedu.aop; import org.springframework.stereotype.Component; @Component
public class RawCaculator implements MathCaculator{ @Override
public int add(int i, int j) {
int rs=i+j;
System.out.println(i+"+"+j+"="+rs);
return rs;
} @Override
public int sub(int i, int j) {
int rs=i-j;
System.out.println(i+"-"+j+"="+rs);
return rs;
} @Override
public int mul(int i, int j) {
int rs=i*j;
System.out.println(i+"*"+j+"="+rs);
return rs;
} @Override
public int div(int i, int j) {
int rs=i/j;
System.out.println(i+"/"+j+"="+rs);
return rs;
} }要实现AOP的计算方法类
- 实现切面类
- 使用前置通知、后置通知、返回通知、异常通知实现切面类,注入到Spring容器中
package com.neuedu.aop; import static org.hamcrest.CoreMatchers.nullValue; import java.util.Arrays;
import java.util.List; import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component; @Component
//@Aspect表明当前类是一个切面类
@Aspect
//@Order表示切面执行顺序,value值越小优先级越高
@Order(value=50)
public class CaculatorAspect {
@Before(value = "execution(public int com.neuedu.aop.RawCaculator.*(int, int))")
public void showBeginLog(JoinPoint point){
//System.out.println("【日志】【前置通知】");
//获得参数列表:
Object[] args = point.getArgs();
List<Object> asList = Arrays.asList(args);
//获得方法名:
Signature signature = point.getSignature();
String name = signature.getName();
System.out.println("【日志】【前置通知】 目标方法名为:"+name+"参数为:"+asList);
}
@AfterThrowing(value = "execution(public int com.neuedu.aop.RawCaculator.*(..))" ,throwing="ex")
public void showThrowing(JoinPoint point,Exception ex){
//System.out.println("【日志】【异常通知】");
System.out.println("【日志】【异常通知】 异常信息:"+ex);
}
@After(value = "execution(public int com.neuedu.aop.RawCaculator.*(..))")
public void showAfter(){
System.out.println("【日志】【后置通知】");
}
@AfterReturning(value = "execution(* * .*(..))",returning="result")
public void showAfterReturning(JoinPoint point,Object result){
//System.out.println("【日志】【返回通知】");
System.out.println("【日志】【返回通知】 目标方法的返回值为"+result);
}
}
- 使用前置通知、后置通知、返回通知、异常通知实现切面类,注入到Spring容器中
- 将需要实现AOP的类注入到Spring容器中,例如:
使用黄色背景标注的代码是声明的通知,其中包括通知类型和切入点表达式,以下就是对通知类型的介绍,切入点表达式在最后
@Before 前置通知:在方法执行之前执行的通知
@After后置通知:后置通知是在连接点完成之后执行的,即连接点返回结果或者抛出异常的时候
@AfterReturning 返回通知:
- 无论连接点是正常返回还是抛出异常,后置通知都会执行。如果只想在连接点返回的时候记录日志,应使用返回通知代替后置通知。
- 在返回通知中访问连接点的返回值:
- 在返回通知中,只要将returning属性添加到@AfterReturning注解中,就可以访问连接点的返回值。该属性的值即为用来传入返回值的参数名称
- 必须在通知方法的签名中添加一个同名参数。在运行时Spring AOP会通过这个参数传递返回值
- 原始的切点表达式需要出现在pointcut属性中
@AfterThrowing异常通知:只在连接点抛出异常时才执行异常通知.:
- 将throwing属性添加到@AfterThrowing注解中,也可以访问连接点抛出的异常。Throwable是所有错误和异常类的顶级父类,所以在异常通知方法可以捕获到任何错误和异常。
- 如果只对某种特殊的异常类型感兴趣,可以将参数声明为其他异常的参数类型。然后通知就只在抛出这个类型及其子类的异常时才被执行
- 将throwing属性添加到@AfterThrowing注解中,也可以访问连接点抛出的异常。Throwable是所有错误和异常类的顶级父类,所以在异常通知方法可以捕获到任何错误和异常。
2.使用环绕通知实现切面类,注入到Spring容器中
1 package com.neuedu.aop; import java.util.Arrays;
import java.util.List; import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component; @Component
//@Aspect表明当前类是一个切面类
@Aspect
@Order(value=40)
public class secondAspect {
@Around(value = "execution(* * .*(..))")
public Object Around(ProceedingJoinPoint point){
Object result=null;
Object[] args = point.getArgs();
List<Object> asList = Arrays.asList(args);
Signature signature = point.getSignature();
String name = signature.getName();
try{
System.out.println("【事物日志】【前置通知】 目标方法名为:"+name+"参数为:"+asList);
try {
result=point.proceed(args);
} finally{
System.out.println("【事物日志】【后置通知】");
}
System.out.println("【事物日志】【返回通知】 目标方法的返回值为"+result);
}catch (Throwable e) {
System.out.println("【事物日志】【异常通知】 异常信息:"+e);
}
return result;
}
38 }
@Around 环绕通知:
- 环绕通知是所有通知类型中功能最为强大的,能够全面地控制连接点,甚至可以控制是否执行连接点。
对于环绕通知来说,连接点的参数类型必须是ProceedingJoinPoint。它是 JoinPoint的子接口,允许控制何时执行,是否执行连接点。
在环绕通知中需要明确调用ProceedingJoinPoint的proceed()方法来执行被代理的方法。如果忘记这样做就会导致通知被执行了,但目标方法没有被执行。
注意:环绕通知的方法需要返回目标方法执行之后的结果,即调用 joinPoint.proceed();的返回值,否则会出现空指针异常。
3.实现测试类
package junit.test;import static org.junit.Assert.*;import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;import com.neuedu.aop.MathCaculator;
import com.neuedu.aop.RawCaculator;public class TestCaculator { @Test
public void test() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
//MathCaculator bean = ioc.getBean(RawCaculator.class); 不可用 (代表类 类型不一样)
//rawCaculator即使使用注解时代表类的id;又是xml配置文件里的id
MathCaculator bean = (MathCaculator) ioc.getBean("rawCaculator");
bean.add(10, 5);
System.out.println();
bean.sub(14, 5);
System.out.println();
bean.mul(8, 7);
System.out.println();
bean.div(10, 0);
}}
测试类
三、关于切入点表达式
1.作用:
通过表达式的方式定位一个或多个具体的连接点。
2.语法细节:
1)切入点表达式的语法格式:execution([权限修饰符] [返回值类型] [简单类名/全类名] [方法名]([参数列表]))
2)举例说明:
表达式 | execution(* com.atguigu.spring.ArithmeticCalculator.*(..)) |
含义 | ArithmeticCalculator接口中声明的所有方法。 第一个“*”代表任意修饰符及任意返回值。 第二个“*”代表任意方法。 “..”匹配任意数量、任意类型的参数。 若目标类、接口与该切面类在同一个包中可以省略包名。 |
表达式 | execution(public * ArithmeticCalculator.*(..)) |
含义 | ArithmeticCalculator接口的所有公有方法 |
表达式 | execution(public double ArithmeticCalculator.*(..)) |
含义 | ArithmeticCalculator接口中返回double类型数值的方法 |
表达式 | execution(public double ArithmeticCalculator.*(double, ..)) |
含义 | 第一个参数为double类型的方法。 “..” 匹配任意数量、任意类型的参数。 |
表达式 | execution(public double ArithmeticCalculator.*(double, double)) |
含义 | 参数类型为double,double类型的方法 |
3.重用切入点:
1)
在编写AspectJ切面时,可以直接在通知注解中书写切入点表达式。但同一个切点表达式可能会在多个通知中重复出现
- 在Aspect切面中,可以通过@Pointcut注解将一个切入点声明成简单的方法。切入点的方法体通常是空的,因为将切入点定义与应用程序逻辑混在一起是不合理的。
- 切入点方法的访问控制符同时也控制着这个切入点的可见性。如果切入点要在多个切面中共用,最好将它们集中在一个公共的类中。在这种情况下,它们必须被声明为public。在引入这个切入点时,必须将类名也包括在内。如果类没有与这个切面放在同一个包中,还必须包含包名。
2)示例代码:
1 @Component
2 //@Aspect表明当前类是一个切面类
@Aspect
4 //@Order表示切面执行顺序,value值越小优先级越高
5 @Order(value=50)
6 public class CaculatorAspect {
//重用切入点
@Pointcut("execution(* * .*(..))")
private void LoggingOperation(){
}
@Before(value = "LoggingOperation()")
public void showBeginLog(JoinPoint point){
//获得参数列表:
Object[] args = point.getArgs();
List<Object> asList = Arrays.asList(args);
//获得方法名:
Signature signature = point.getSignature();
String name = signature.getName();
System.out.println("【日志】【前置通知】 目标方法名为:"+name+"参数为:"+asList);
}
@AfterThrowing(value = "LoggingOperation()" ,throwing="ex")
public void showThrowing(JoinPoint point,Exception ex){
System.out.println("【日志】【异常通知】 异常信息:"+ex);
}
@After(value = "LoggingOperation()")
public void showAfter(){
System.out.println("【日志】【后置通知】");
29 }
@AfterReturning(value = "LoggingOperation()",returning="result")
public void showAfterReturning(JoinPoint point,Object result){
System.out.println("【日志】【返回通知】 目标方法的返回值为"+result);
}
}







