AOP:Aspect Oriented Programming,面向切面编程。
OOP:Object Oriented Programming,面向对象编程。
面向切面编程:基于OOP基础之上的新的编程思想,指在程序运行期间,将某段代码(日志)动态的切入(不把日志代码写死在业务逻辑方法中)到指定方法的指定位置(方法的开始、结束、异常位置)进行运行的这种编程方式。
可以用动态代理来实现面向切面编程,但是有缺点:
写起来比较麻烦,每次都要通过反射来创建代理对象;jdk默认的动态代理如果目标对象没有实现任何接口,是无法为他创建代理对象的。因为代理对象和被代理对象的纽带就是接口类型。
AOP专业术语:
抽取可重用的切入点表达式:
随便声明一个没有实现的返回void
的空方法
给方法上标注@Pointcut
注解
括号内写上切入点表达式
切入点表达式结构:execution(访问权限符 返回值类型 方法全类名(参数列表))
*
:只能匹配一层路径/一个参数
execution(public int com.vickkkyz.aoppractice.math.MyMathCalculator.*(*,*))
..
:用在参数表,表示匹配任意多个参数,任意参数类型
用在路径上,表示多层路径,下面的表示aoppractice包下的多层路径下名字叫MyMathCalculator的类
execution(public int com.vickkkyz.aoppractice..MyMathCalculator.*(*,*))
1 2 3
| @Pointcut("execution(public int com.vickkkyz.aoppractice.math.MyMathCalculator.*(*,*))") public void myPoint(){}
|
写一次,之后全引用即可。
通知方法的执行顺序
1 2 3 4 5 6 7 8 9
| try{ @Before method.invoke(obj,args); @AfterReturning }catch(){ @AfterThrowing }finally{ @After }
|
方法正常执行:@Beofre -> @After -> @AfterReturning
方法异常执行:@Before -> @After -> @AfterThrowing
使用参数JoinPoint
可以拿到运行的方法的相信信息。需要在通知方法的参数列表上写上
JoinPoint joinPoint
在通知方法注解上写上throwing = "exception"
,参数列表上加上这个参数Exception exception
,通知方法就可以接收到方法出现的异常信息,注解上加上returning = "result"
,参数上写上Object result
,就可以获得方法的返回值。
Spring对增强方法要求不严格,因为通过注解标记了方法,只要能正常调用就完成了增强的目的;所以唯一要求严格的就是参数表,反射调用的时候得给参数赋值;JoinPoint是预定义的;如果有其他参数(接返回值的,接异常的),得声明出来哪个参数接什么;
@Around
环绕通知,是Spring中最强大的通知,环绕通知是上面四个通知的整合,四合一。
有一个强大的回调参数,可以拿到底层动态代理的invoke权,
如果使用环绕通知注解,那么目标方法的调用就是在@Around
注解修饰的方法中,需要我们显示去利用动态代理调用目标方法,其实就相当于之前的Spring帮我们调用的method.invoke(obj,args)
,变为我们自己调用point.proceed(args);
最后将方法的返回值,即proceed返回出去。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| @Component @Aspect public class LogUtils {
@Pointcut("execution(public int com.vickkkyz.aoppractice.math.MyMathCalculator.*(int,int))") public void myPoint(){}
@Before("myPoint()") public void logBefore(JoinPoint joinPoint){ Object[] args = joinPoint.getArgs(); Signature signature = joinPoint.getSignature(); String name = signature.getName(); System.out.println("[前置通知]方法执行前,参数为"+ Arrays.asList(args)+"方法名为:" + name); }
@After("myPoint()") public void logAfter(){ System.out.println("[后置通知]finally"); }
@AfterReturning(value = "myPoint()",returning = "result") public void logAfterReturning(Object result){ System.out.println("[正常返回]try语句块结束,结果为" + result); }
@AfterThrowing(value = "myPoint()", throwing = "exception") public void logAfterThrowing(Exception exception) { System.out.println("[方法异常]throwing,异常信息是"); exception.printStackTrace(); }
@Around("myPoint()") public Object logAround(ProceedingJoinPoint point) throws Throwable { Object[] args = point.getArgs(); Object proceed = null; try{ System.out.println("环绕前置"); proceed = point.proceed(args); System.out.println("环绕正常返回"); }catch (Exception e){ System.out.println("环绕异常"); }finally { System.out.println("环绕后置"); } return proceed;
}
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| @Component public class MyMathCalculator {
public int add(int a, int b) { return a + b; }
public int sub(int a, int b) { return a - b; }
public int mul(int a, int b) { return a * b; }
public int dev(int a, int b) { return a / b; }
}
|
AOP使用场景:
1)、AOP加日志保存到数据库;
2)、AOP做权限验证;
3)、AOP做安全检查;
4)、AOP做事务控制;
https://www.jianshu.com/p/9093e6ca3378
对切入点表达式格式的说明:(即@Point注解内的表达式)
within:匹配包/类型
1 2 3 4 5
| @Poincut("within(com.hhu.service.ProductService)")
@Pointcut("within(com.hhu..*)")
|
this、target、bean:匹配对象
1 2 3 4 5 6 7 8
| @Pointcut("this(com.hhu.DemaoDao)")
@Pointcut("target(com.hhu.Idao)")
@Pointcut("bean(*Service)")
|
@annotation:匹配注解
1 2 3 4 5 6 7 8 9 10 11
| @Pointcut("annotation(com.hhu.demo.security.AdminOnly)")
@Pointcut("@within(com.google.common.annotations.Beta)")
@Pointcut("@target(org.springframework.stereotype.Repository)")
@Pointcut("@args(org.springframework.stereotype.Repository)")
|
https://blog.csdn.net/jaryle/article/details/88764751