一、面向切面的编程需求的产生
- 代码混乱:越来越多的非业务需求(日志和验证等)加入后,原有的业务方法急剧膨胀。每个方法在处理核心逻辑的同时还必须兼顾其他多个关注点。
- 代码分散: 以日志需求为例,只是为了满足这个单一需求,就不得不在多个模块(方法)里多次重复相同的日志代码。如果日志需求发生变化,必须修改所有模块。
二、实现面向切面的编程
- 将需要实现AOP的类注入到Spring容器中,例如:
1 package com.neuedu.aop; 2 3 import org.springframework.stereotype.Component; 4 5 @Component 6 public class RawCaculator implements MathCaculator{ 7 8 @Override 9 public int add(int i, int j) {10 int rs=i+j;11 System.out.println(i+"+"+j+"="+rs);12 return rs;13 }14 15 @Override16 public int sub(int i, int j) {17 int rs=i-j;18 System.out.println(i+"-"+j+"="+rs);19 return rs;20 }21 22 @Override23 public int mul(int i, int j) {24 int rs=i*j;25 System.out.println(i+"*"+j+"="+rs);26 return rs;27 }28 29 @Override30 public int div(int i, int j) {31 int rs=i/j;32 System.out.println(i+"/"+j+"="+rs);33 return rs;34 }35 36 }
要实现AOP的计算方法类
- 实现切面类
- 使用前置通知、后置通知、返回通知、异常通知实现切面类,注入到Spring容器中
1 package com.neuedu.aop; 2 3 import static org.hamcrest.CoreMatchers.nullValue; 4 5 import java.util.Arrays; 6 import java.util.List; 7 8 import org.aspectj.lang.JoinPoint; 9 import org.aspectj.lang.ProceedingJoinPoint;10 import org.aspectj.lang.Signature;11 import org.aspectj.lang.annotation.After;12 import org.aspectj.lang.annotation.AfterReturning;13 import org.aspectj.lang.annotation.AfterThrowing;14 import org.aspectj.lang.annotation.Around;15 import org.aspectj.lang.annotation.Aspect;16 import org.aspectj.lang.annotation.Before;17 import org.springframework.core.annotation.Order;18 import org.springframework.stereotype.Component;19 20 @Component21 //@Aspect表明当前类是一个切面类22 @Aspect23 //@Order表示切面执行顺序,value值越小优先级越高24 @Order(value=50)25 public class CaculatorAspect {26 @Before(value = "execution(public int com.neuedu.aop.RawCaculator.*(int, int))")27 public void showBeginLog(JoinPoint point){28 //System.out.println("【日志】【前置通知】");29 //获得参数列表:30 Object[] args = point.getArgs();31 List<Object> asList = Arrays.asList(args);32 //获得方法名:33 Signature signature = point.getSignature();34 String name = signature.getName();35 System.out.println("【日志】【前置通知】 目标方法名为:"+name+"参数为:"+asList);36 }37 @AfterThrowing(value = "execution(public int com.neuedu.aop.RawCaculator.*(..))" ,throwing="ex")38 public void showThrowing(JoinPoint point,Exception ex){39 //System.out.println("【日志】【异常通知】");40 System.out.println("【日志】【异常通知】 异常信息:"+ex);41 }42 @After(value = "execution(public int com.neuedu.aop.RawCaculator.*(..))")43 public void showAfter(){44 System.out.println("【日志】【后置通知】");45 }46 @AfterReturning(value = "execution(* * .*(..))",returning="result")47 public void showAfterReturning(JoinPoint point,Object result){48 //System.out.println("【日志】【返回通知】");49 System.out.println("【日志】【返回通知】 目标方法的返回值为"+result);50 }51 }
使用黄色背景标注的代码是声明的通知,其中包括通知类型和切入点表达式,以下就是对通知类型的介绍,切入点表达式在最后
@Before 前置通知:在方法执行之前执行的通知
@After 后置通知:后置通知是在连接点完成之后执行的,即连接点返回结果或者抛出异常的时候
@AfterReturning 返回通知:
- 无论连接点是正常返回还是抛出异常,后置通知都会执行。如果只想在连接点返回的时候记录日志,应使用返回通知代替后置通知。
- 在返回通知中访问连接点的返回值:
- 在返回通知中,只要将returning属性添加到@AfterReturning注解中,就可以访问连接点的返回值。该属性的值即为用来传入返回值的参数名称
- 必须在通知方法的签名中添加一个同名参数。在运行时Spring AOP会通过这个参数传递返回值
- 原始的切点表达式需要出现在pointcut属性中
@AfterThrowing 异常通知:只在连接点抛出异常时才执行异常通知.:
- 将throwing属性添加到@AfterThrowing注解中,也可以访问连接点抛出的异常。Throwable是所有错误和异常类的顶级父类,所以在异常通知方法可以捕获到任何错误和异常。
- 如果只对某种特殊的异常类型感兴趣,可以将参数声明为其他异常的参数类型。然后通知就只在抛出这个类型及其子类的异常时才被执行
2.使用环绕通知实现切面类,注入到Spring容器中
1 package com.neuedu.aop; 2 3 import java.util.Arrays; 4 import java.util.List; 5 6 import org.aspectj.lang.ProceedingJoinPoint; 7 import org.aspectj.lang.Signature; 8 import org.aspectj.lang.annotation.Around; 9 import org.aspectj.lang.annotation.Aspect;10 import org.springframework.core.annotation.Order;11 import org.springframework.stereotype.Component;12 13 @Component14 //@Aspect表明当前类是一个切面类15 @Aspect16 @Order(value=40)17 public class secondAspect {18 @Around(value = "execution(* * .*(..))")19 public Object Around(ProceedingJoinPoint point){20 Object result=null;21 Object[] args = point.getArgs();22 List<Object> asList = Arrays.asList(args);23 Signature signature = point.getSignature();24 String name = signature.getName();25 try{26 System.out.println("【事物日志】【前置通知】 目标方法名为:"+name+"参数为:"+asList);27 try {28 result=point.proceed(args);29 } finally{30 System.out.println("【事物日志】【后置通知】");31 } 32 System.out.println("【事物日志】【返回通知】 目标方法的返回值为"+result); 33 }catch (Throwable e) {34 System.out.println("【事物日志】【异常通知】 异常信息:"+e);35 }36 return result;37 }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.ClassPathimport com.neuedu.aop.MathCaculator;import com.neuedu.aop.RawCaculator;public class TestCaculator { @Test public void test() { ApplicationContext ioc = new ClassPath); //MathCaculator bean = ioc.getBean(RawCaculator.class); 不可用 (代表类 类型不一样) //rawCaculator即使使用注解时代表类的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)
2)示例代码:
1 @Component 2 //@Aspect表明当前类是一个切面类 3 @Aspect 4 //@Order表示切面执行顺序,value值越小优先级越高 5 @Order(value=50) 6 public class CaculatorAspect { 7 //重用切入点 8 @Pointcut("execution(* * .*(..))") 9 private void LoggingOperation(){10 11 }12 @Before(value = "LoggingOperation()")13 public void showBeginLog(JoinPoint point){14 //获得参数列表:15 Object[] args = point.getArgs();16 List<Object> asList = Arrays.asList(args);17 //获得方法名:18 Signature signature = point.getSignature();19 String name = signature.getName();20 System.out.println("【日志】【前置通知】 目标方法名为:"+name+"参数为:"+asList);21 }22 @AfterThrowing(value = "LoggingOperation()" ,throwing="ex")23 public void showThrowing(JoinPoint point,Exception ex){24 System.out.println("【日志】【异常通知】 异常信息:"+ex);25 }26 @After(value = "LoggingOperation()")27 public void showAfter(){28 System.out.println("【日志】【后置通知】");29 }30 @AfterReturning(value = "LoggingOperation()",returning="result")31 public void showAfterReturning(JoinPoint point,Object result){32 System.out.println("【日志】【返回通知】 目标方法的返回值为"+result);33 }34 }
原标题:AOP 面向切面的编程
关键词:aop