AOP & 拦截器

 

https://www.cnblogs.com/boywwj/p/7502185.html

 

spring aop中@after-returning和@after,@afterThrowing,@Around,@Before注解执行顺序
try{
try{
//@Around
//@Before
method.invoke(..);
//@Around
}catch(){
throw.....;
}finally{
//@After
}
//@AfterReturning
}catch(){
//@AfterThrowing
}

正常情况 @Around @Before 目标方法 @Around @After @AfterReturning;
异常情况 @Around @Before 目标方法 @After @AfterThrowing;

 


____________________________________________________________________________________________________________
 

 

  一:AOP的背景

  面试的时候面试官让我解释一下什么是AOP,当时不懂,在路上就查了,AOP:面向切面的编程技术,困惑了,JAVA是OOP:面向对象的编程技术。那么自己就立刻查了几个为题:1、什么是面向切面的编程技术;2、为什么要面向切面的编程技术;3、与OOP是什么关系?

首先解释第二个问题:在我们平时的开发过程中,你肯定会遇到下面几个面:1)权限校验;2)业务的核心代码;3)记录日志。那么在@Service层采用代码累加的方法,那么结构就会如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Service
public  class  myService{
 
@Resource
private  CoreService coreService;
 
@Resource
private  LogService logService;
@Resource
private  PropertyService propertyService;
  
// 权限校验代码
 
//核心业务层代码
 
//记录日志的代码  <br><br>// 异常的处理<br>
}

从上面的代码结构中我们可以看出以下几个问题:

1.1、代码混乱:核心业务模块与其他非核心的代码交织在一起,大大影响了代码的模块独立性能,不利于代码的维护,而且分工不明确造成代码混乱。

1.2、冗余代码:其实权限的校验,异常的处理,日志的记录可以独立在一个模块给所有的服务公用,写在一起导致代码的分散和冗余。

因此面向切面的编程技术应运而生。

解释第一个问题:什么是面向切面的编程技术。切面与切点是几何上面的术语,用在这里可以这样理解:将核心业务代码过程比作一个柱体,其他的日志记录,权限校验等就像是横切核心业务的面,这些面需要完成一些非核心的业务。如下图:

                 

从图中可以看出我们定义了多个切面,每个切面都完成各自的非核心的业务,一个切面上还可以完成多个非核心的业务。 

1.3、第三个问题:与OOP是什么关系?

AOP的实现技术有多种,其中与Java无缝对接的是一种称为AspectJ的技术,Spring AOP 与AspectJ 实现原理上并不完全一致,但功能上是相似的。AOP的出现确实解决外围业务代码与核心业务代码分离的问题,但它并不会替代OOP,如果说OOP的出现是把编码问题进行模块化,那么AOP就是把涉及到众多模块的某一类问题进行统一管理(参考:关于 Spring AOP (AspectJ) 你该知晓的一切;其实我也想到了他们的关系,但是感觉没有这篇博客总结的很好)

 二:AOP的核心概念

从上右图可以可以很好看到切面(Aspect)包含了切点(PointCut)、连接点(JoinPoint);额外还有通知(Advice),织入(Weaving),引入(Introduce)。

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
56
57
58
59
60
61
62
63
64
65
66
67
68
package  springMVCmybatis.com.my.aop;
import  org.aspectj.lang.annotation.Aspect; 
import  org.aspectj.lang.annotation.Around; 
import  org.aspectj.lang.annotation.Before; 
import  org.aspectj.lang.annotation.Pointcut; 
import  org.aspectj.lang.JoinPoint;
import  org.aspectj.lang.ProceedingJoinPoint; 
import  org.aspectj.lang.annotation.After; 
import  org.aspectj.lang.annotation.AfterReturning; 
import  org.aspectj.lang.annotation.AfterThrowing; 
import  org.springframework.core.annotation.Order;
@Aspect
// 切面执行顺序
@Order ( 3 )
public  class  MyAopTest {
       @Pointcut ( "execution(* springMVCmybatis..addController.addEmp(..))"
         private  void  pointCutMethod() { 
        
       
       @Pointcut ( "execution(* springMVCmybatis.com.my.aop.UserServiceImp.*(..))"
         private  void  testAOP() { 
         }
       /*
        *  声明前置通知 ,JoinPont是srpring提供的静态变量,
        *  通过joinPoint参数可以获得目标方法的类名,方法参数,方法名等信息,这个参数可有可无。
        */
        
         @Before ( "pointCutMethod() || testAOP()"
         public  void  doBefore(JoinPoint joinPoint) { 
             System.out.println( "@Before:开始添加--order=3" ); 
        
       
         //声明后置通知 ,如果result的类型与proceed执行的方法返回的参数类型不匹配那么就不会执行这个方法
         @AfterReturning (pointcut =  "pointCutMethod()  || testAOP()" , returning =  "result"
         public  void  doAfterReturning(String result) { 
             System.out.println( "@AfterReturning:后置通知--order=3" ); 
             System.out.println( "---"  + result +  "---" ); 
        
       
         //声明例外通知 
         @AfterThrowing (pointcut =  "pointCutMethod() || testAOP()" , throwing =  "e"
         public  void  doAfterThrowing(Exception e) { 
             System.out.println( "@AfterThrowing:例外通知--order=3" ); 
             System.out.println(e.getMessage()); 
        
       
         //声明最终通知 
         @After ( "pointCutMethod() || testAOP()"
         public  void  doAfter() { 
             System.out.println( "@After:最终通知--order=3" ); 
        
       /*
        * 声明环绕通知
        * 参数必须是ProceedingJoinPoint,通过该对象的proceed()方法来执行目标函数,
        * proceed()的返回值就是环绕通知的返回值,proceedingJoinPoint是个接口,
        * implement JoinPoint,所以也可以获得目标函数的类名,方法名等参数。
        */
         
         @Around ( "pointCutMethod() || testAOP()"
         public  Object doAround(ProceedingJoinPoint pjp)  throws  Throwable { 
             System.out.println( "@Around:进入方法---环绕通知--order=3" ); 
             Object o = pjp.proceed(); 
             System.out.println( "@Around:退出方法---环绕通知--order=3" ); 
             return  o; 
         }
 
        
}

  

 上面是我写的一个例子,结合例子我们来看看这些核心的概念:

2.1、切面(Aspect):是一个类,里面定义了通知与切点。

2.2、切点(PointCut):表达式。就是告诉程序要在执行哪些核心业务的时候,执行非核心的业务。

2.3、通知(advice):五种通知方式:

  • @Before:前置通知,在调用目标方法之前执行通知定义的任务
  • @After:后置通知,在目标方法执行结束后,无论执行结果如何都执行通知定义的任务
  • @After-returning:后置通知,在目标方法执行结束后,如果执行成功,则执行通知定义的任务
  • @After-throwing:异常通知,如果目标方法执行过程中抛出异常,则执行通知定义的任务
  • @Around:环绕通知,在目标方法执行前和执行后,都需要执行通知定义的任务。

五种通知方式的执行顺序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
正常情况下的执行顺序:<br><br> @Around :进入方法---环绕通知--order= 3
@Before :开始添加--order= 3
============执行业务方法findUser,查找的用户是:张三=============
@Around :退出方法---环绕通知--order= 3
@After :最终通知--order= 3
@AfterReturning :后置通知--order= 3
---张三---
<br>异常情况下的执行顺序:
 
@Around :进入方法---环绕通知--order= 3
@Before :开始添加--order= 3
============执行业务方法addUser=============
@After :最终通知--order= 3
@AfterThrowing :例外通知--order= 3
null

三:切点表达式。

这个表达式有很多种,如方法签名表达式,类型签名表达式,还有其他的表达式,我只用过前面两个,其他的没用过,也不做介绍,如果这两种表达式不能解决问题的可以参考我参考的博客。

3.1:方法签名表达式
 
1
2
execution(<修饰符模式>?<返回类型模式><方法所在类的完全限定名称模式>(<参数模式>)<异常模式>?)
execution(modifiers-pattern? ret-type-pattern fully-qualified- class -name (param-pattern)  throws -pattern?)   
 
其实如果单纯的给定这个表达式还是不容易记忆,下面对比方法的定义来记忆,一个java方法的全部定义方式可以表示成下面的方式:
    
1
2
public  String springMVCmybatic.com.my.aop.UserServiceImp(String a,  int  b)  throw  Exception{
}
  • modifier-pattern?:表示方法的修饰符,可有可无;对应的就是 public
  • ret-type-pattern:表示方法的返回值;对应的就是 String
  • fully-qualified-class-name  方法所在类的完全限定名称;对应的就是 springMVCmybatic.com.my.aop.UserServiceImp
  • param-pattern:表示方法的参数;对应的就是 String a, int b
  • throws-pattern:表示方法抛出的异常,可有可无;对应的就是 throw Exception

3.2:&&,||,!

1
2
3
4
5
6
7
@Around ( "pointCutMethod() || testAOP()"
       public  Object doAround(ProceedingJoinPoint pjp)  throws  Throwable { 
           System.out.println( "@Around:进入方法---环绕通知" ); 
           Object o = pjp.proceed(); 
           System.out.println( "@Around:退出方法---环绕通知" ); 
           return  o; 
       }

表达式之间可以采用与,或,非的方式来过滤。

四:多个切点的执行顺序

 上面的例子中,定义了order=3,重新创建一个切面,定义order=6,执行的结果是:

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
@Around :进入方法---环绕通知--order= 3
@Before :开始添加--order= 3
@Around :进入方法---环绕通知--order= 6
@Before :开始添加--order= 6
============执行业务方法findUser,查找的用户是:张三=============
@Around :退出方法---环绕通知--order= 6
@After :最终通知--order= 6
@AfterReturning :后置通知--order= 6
---张三---
@Around :退出方法---环绕通知--order= 3
@After :最终通知--order= 3
@AfterReturning :后置通知--order= 3
---张三---
 
 
@Around :进入方法---环绕通知--order= 3
@Before :开始添加--order= 3
@Around :进入方法---环绕通知--order= 6
@Before :开始添加--order= 6
============执行业务方法addUser=============
@After :最终通知--order= 6
@AfterThrowing :例外通知--order= 6
null
@After :最终通知--order= 3
@AfterThrowing :例外通知--order= 3
null

 从结果中可以看出order越小越先执行,执行完了之后就order越小就越后推出。总结为下面的图:

 

【参考博客】

1、http://blog.csdn.net/javazejian/article/details/56267036/

2、http://blog.csdn.net/qqxhwwqwq/article/details/51678595

 

————————————————————————————————————————————————————————

https://blog.csdn.net/u011974797/article/details/80591986

 

@Around是可以同时在所拦截方法的前后执行一段逻辑。

@Before是在所拦截方法执行之前执行一段逻辑。

@After 是在所拦截方法执行之后执行一段逻辑。

注意:不能拦截静态(static)方法

示例:

  1.  
    package com.abc.advice;
  2.  
     
  3.  
    import java.util.Arrays;
  4.  
     
  5.  
    import org.aspectj.lang.JoinPoint;
  6.  
    import org.aspectj.lang.ProceedingJoinPoint;
  7.  
    import org.aspectj.lang.annotation.After;
  8.  
    import org.aspectj.lang.annotation.AfterReturning;
  9.  
    import org.aspectj.lang.annotation.Around;
  10.  
    import org.aspectj.lang.annotation.Aspect;
  11.  
    import org.aspectj.lang.annotation.Before;
  12.  
     
  13.  
    @Aspect
  14.  
    public class AdviceTest {
  15.  
    @Around("execution(* com.abc.service.*.many*(..))")
  16.  
    public Object process(ProceedingJoinPoint point) throws Throwable {
  17.  
    System. out.println("@Around:执行目标方法之前...");
  18.  
    //访问目标方法的参数:
  19.  
    Object[] args = point.getArgs();
  20.  
    if (args != null && args.length > 0 && args[0].getClass() == String.class) {
  21.  
    args[ 0] = "改变后的参数1";
  22.  
    }
  23.  
    //用改变后的参数执行目标方法
  24.  
    Object returnValue = point.proceed(args);
  25.  
    System. out.println("@Around:执行目标方法之后...");
  26.  
    System. out.println("@Around:被织入的目标对象为:" + point.getTarget());
  27.  
    return "原返回值:" + returnValue + ",这是返回结果的后缀";
  28.  
    }
  29.  
     
  30.  
    @Before("execution(* com.abc.service.*.many*(..))")
  31.  
    public void permissionCheck(JoinPoint point) {
  32.  
    System. out.println("@Before:模拟权限检查...");
  33.  
    System. out.println("@Before:目标方法为:" +
  34.  
    point.getSignature().getDeclaringTypeName() +
  35.  
    "." + point.getSignature().getName());
  36.  
    System. out.println("@Before:参数为:" + Arrays.toString(point.getArgs()));
  37.  
    System. out.println("@Before:被织入的目标对象为:" + point.getTarget());
  38.  
    }
  39.  
     
  40.  
    @AfterReturning(pointcut="execution(* com.abc.service.*.many*(..))",
  41.  
    returning="returnValue")
  42.  
    public void log(JoinPoint point, Object returnValue) {
  43.  
    System. out.println("@AfterReturning:模拟日志记录功能...");
  44.  
    System. out.println("@AfterReturning:目标方法为:" +
  45.  
    point.getSignature().getDeclaringTypeName() +
  46.  
    "." + point.getSignature().getName());
  47.  
    System. out.println("@AfterReturning:参数为:" +
  48.  
    Arrays.toString(point.getArgs()));
  49.  
    System. out.println("@AfterReturning:返回值为:" + returnValue);
  50.  
    System. out.println("@AfterReturning:被织入的目标对象为:" + point.getTarget());
  51.  
     
  52.  
    }
  53.  
     
  54.  
    @After("execution(* com.abc.service.*.many*(..))")
  55.  
    public void releaseResource(JoinPoint point) {
  56.  
    System. out.println("@After:模拟释放资源...");
  57.  
    System. out.println("@After:目标方法为:" +
  58.  
    point.getSignature().getDeclaringTypeName() +
  59.  
    "." + point.getSignature().getName());
  60.  
    System. out.println("@After:参数为:" + Arrays.toString(point.getArgs()));
  61.  
    System. out.println("@After:被织入的目标对象为:" + point.getTarget());
  62.  
    }
  63.  
    }

被拦截的方法

  1.  
    //将被AdviceTest的各种方法匹配
  2.  
    public String manyAdvices(String param1, String param2) {
  3.  
    System.out.println( "方法:manyAdvices");
  4.  
    return param1 + " 、" + param2;
  5.  
    }

运行方法

  1.  
    String result = manager.manyAdvices("aa", "bb");
  2.  
    System.out.println( "Test方法中调用切点方法的返回值:" + result);

结果

  1.  
    @Around:执行目标方法之前...
  2.  
    @Before:模拟权限检查...
  3.  
    @Before:目标方法为:com.abc.service.AdviceManager.manyAdvices
  4.  
    @Before:参数为:[改变后的参数 1, bb]
  5.  
    @Before:被织入的目标对象为:com.abc.service.AdviceManager@1dfc617e
  6.  
    方法:manyAdvices
  7.  
    @Around:执行目标方法之后...
  8.  
    @Around:被织入的目标对象为:com.abc.service.AdviceManager@1dfc617e
  9.  
    @After:模拟释放资源...
  10.  
    @After:目标方法为:com.abc.service.AdviceManager.manyAdvices
  11.  
    @After:参数为:[改变后的参数 1, bb]
  12.  
    @After:被织入的目标对象为:com.abc.service.AdviceManager@1dfc617e
  13.  
    @AfterReturning:模拟日志记录功能...
  14.  
    @AfterReturning:目标方法为:com.abc.service.AdviceManager.manyAdvices
  15.  
    @AfterReturning:参数为:[改变后的参数 1, bb]
  16.  
    @AfterReturning:返回值为:原返回值:改变后的参数 1 、 bb,这是返回结果的后缀
  17.  
    @AfterReturning:被织入的目标对象为:com.abc.service.AdviceManager@1dfc617e
  18.  
    Test方法中调用切点方法的返回值:原返回值:改变后的参数 1 、bb,这是返回结果的后缀

 

转载于:https://www.cnblogs.com/kelelipeng/p/11358216.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值