• SpringAOP的应用实例与总结


      一:AOP的背景

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

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

    @Service
    public class myService{
    
    @Resource
    private CoreService coreService;
    
    @Resource
    private LogService logService;
    @Resource
    private PropertyService propertyService;
     
    // 权限校验代码
    
    //核心业务层代码
    
    //记录日志的代码  

    // 异常的处理
    }

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

    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)。

    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:环绕通知,在目标方法执行前和执行后,都需要执行通知定义的任务。

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

    正常情况下的执行顺序:

    @Around:进入方法---环绕通知--order=3 @Before:开始添加--order=3 ============执行业务方法findUser,查找的用户是:张三============= @Around:退出方法---环绕通知--order=3 @After:最终通知--order=3 @AfterReturning:后置通知--order=3 ---张三---
    异常情况下的执行顺序: @Around:进入方法---环绕通知--order=3 @Before:开始添加--order=3 ============执行业务方法addUser============= @After:最终通知--order=3 @AfterThrowing:例外通知--order=3 null

    三:切点表达式。

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

    3.1:方法签名表达式
     
    execution(<修饰符模式>?<返回类型模式><方法所在类的完全限定名称模式>(<参数模式>)<异常模式>?)
    execution(modifiers-pattern? ret-type-pattern fully-qualified-class-name (param-pattern) throws-pattern?)   
     
    其实如果单纯的给定这个表达式还是不容易记忆,下面对比方法的定义来记忆,一个java方法的全部定义方式可以表示成下面的方式:
        
    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:&&,||,!

      @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,执行的结果是:

    @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

  • 相关阅读:
    Linux如何重复执行命令每隔几秒或者每隔几分钟
    Linux中如何在系统重启时执行命令或脚本
    如何重启Linux服务器
    Linux中30个有用的命令
    Linux中如何同时执行多个命令
    Linux中查看主机名的10种方法
    Linux中创建文件的10种方法
    Linux中cat命令的使用
    K8S最佳安装教程
    使用yum安装Golang
  • 原文地址:https://www.cnblogs.com/boywwj/p/7502185.html
Copyright © 2020-2023  润新知