• 深入了解Spring(三)


    在学习AOP前可以先了解一下Java动态代理

    https://www.cnblogs.com/binwenhome/p/13025480.html

    AOP概述

    • 简介
    1. AOP(Aspect-Oriented Programming, 面向切面编程): 是一种新的方法论, 对OOP(面向对象编程)的补充.
      • 面向对象: 纵向继承机制, 在该类中写代码.
      • 面向切面: 横向抽取机制, 把某些代码抽取到另外一个类中, 再作用到该类上.
    2. AOP编程操作的主要对象主要是切面, 而切面用于模块化横切关注点(公共功能)
      • 来存储公共功能的类就叫切面.
    3. AOP的好处
      • 每个事务逻辑位于一个位置, 代码不分散, 便于维护和升级.
      • 业务模块更简洁, 只包含核心代码.
    • AOP术语
    1. 横切关注点
      • 从每个方法中抽取出来的同一类非核心业务.
    2. 切面(Aspect)
      • 封装横切关注点信息的类, 每个关注点体现为一个通知方法.
    3. 通知(Advice)
      • 切面必须要完成的各个具体工作.
    4. 目标(Target)
      • 被通知的对象
    5. 代理(Proxy)
      • 向目标对象应用通知后创建的代理对象.
    6. 连接点(JoinPoint)
      • 横切关注点在程序代码中的具体体现, 对应程序执行的某个特定位置. 例如: 类某个方法调用前、调用后、方法捕获到异常后等
      • 在应用程序中可以使用横纵两个坐标来定位一个具体的连接点.
    7. 切入点(pointcut)
      • 要把切面作用到目标对象的一种方式, 即为使用切面的条件, 是个表达式.
      • AOP可以通过切入点定位到特定的连接点. 切点通过org.springframework.aop.Pointcut接口进行描述, 它使用类和方法作为连接点的查询条件. 
    8. 图解

    • AspectJ(切面)
    1. 简介
      • 是Java社区里最完整, 最流行的AOP框架.
      • Spring2.0以上, 可用基于AspectJ注解或XML配置的AOP.
    2. 在Spring中启用AspectJ注解支持.
      • 导入依赖
                <dependency>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-aop</artifactId>
                    <version>${spring.version}</version>
                </dependency>
        
                <dependency>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-aspects</artifactId>
                    <version>${spring.version}</version>
                </dependency>
      • 在配置文件中开启AspectJ的自动代理功能.
        <aop:aspectj-autoproxy />
      • 在Spring5中, 默认使用AspectJ动态代理, 所以不用加相关的jar了.
    • 用AspectJ注解声明切面
    1. 要在Spring中声明AspectJ切面, 只需要在IOC容器中将切面声明为bean实例.
    2. 当在SpringIOC容器中初始化AspectJ切面之后, SpringIOC容器就会为那些与AspectJ切面相匹配的bean创建代理.
    3. 在AspectJ注解中, 切面只是一个带有@Aspect注解的Java类.
    4. 通知是标注有某种注解的简单的Java方法.
    5. AspectJ支持5种类型的通知注解
      • @Before: 前置通知, 在方法执行前执行.
      • @After: 后置通知, 在方法执行之后执行.
      • @AfterRunning: 返回通知, 在方法返回结果之后执行.
      • @AfterThrowing: 异常通知, 在方法抛出异常之后执行.
      • @Around: 环绕通知, 围绕着方法执行.
    • 示例代码
    1. 注意: AOP也是基于IOC实现的, 所以需要IOC完成自动装配工作, 故仍需加入@Component注解.
    2. 使用配置类
      • 在配置类中加入@EnableAspecctJAutoProxy, 开启aspectj的自动代理功能.
        @EnableAspectJAutoProxy
        @Configuration
        @ComponentScan({"test"})
        public class MainConfigOfAOP {
        
            //...
        }
    3. aop.xml
          <!-- 开启aspectj的自动代理功能 -->
          <aop:aspectj-autoproxy />
      
          <!-- 扫描 -->
          <context:component-scan base-package="aop" />
    4. 结构
      @Component
      public class MathImpl implements MathI {
      
          //各个可执行方法
          //横切关注点在方法中.
      }
      
      //切面里封装横切关注点 @Component @Aspect
      //标注当前类为切面 public class MyLoggerAspect { //在切面中写各种通知, 切入点表达式加到通知上. }

    AOP细节

    • 切入点表达式
    1. 作用: 通过表达式的方式定位一个或多个具体的连接点.
    2. 语法细节
      •  execution(权限修饰符 返回类型 全类名 方法名 参数列表)
        @Before(value = "execution(public int aop.MathImpl.add(int, int))")
      • 几种特殊形式
        execution(* aop.Calculator.* (..))
        Calculator接口中的所有方法
        第一个"*"表示任意修饰符, 任意返回值.
        第二个"*"表示任意方法
        ".."匹配任意数量, 任意类型的参数.
        
        execution(public * aop.Calculator.*(..))
        Calculator接口的所有公有方法
        
        execution(public double aop.Calculator.*(..))
        Calculator接口的中返回值double类型的方法
        
        execution(public * aop.Calculator.*(double, ..))
        Calculator接口中的公有方法且第一个参数为double类型
        
        execution(public * Calculator.*(double, double))
        参数为double, double的方法
      • 还可以通过"&&", "||", "!"等操作符结合起来
        execution(* *.add(int, ..)) || execution(* *.sub(int, ..))
        
        !execution(* *.add(int, ..))
    • 当前连接点细节
    1. 切入点表达式通常都会是从宏观上定位一组方法, 和具体某个通知的注解结合起来就能够确定对应的连接点, 那么就一个具体的连接点而言, 我们可能会关心这个连接点的一些具体信息. 
    2. JoinPoint
    • 通知
    1. 概述
      • 通知: 在具体的连接点上要执行的操作.
      • 一个切面可以包括多个通知.
      • 通知所使用的注解的值往往是切入点表达式.
    2. 前置通知
      • 方法执行前执行的通知.
      • 使用@Before注解
            /**
             * 切面里定义了通知, 而通知要想作用于连接点, 需要通过切入点表达式(只能定位到要作用的方法)
             *  而写的各种通知代表要作用的位置.
             */
            @Before(value = "execution(* aop.MathImpl.*(..))")
            public void beforeMeethod(JoinPoint joinPoint) {
                Object[] args = joinPoint.getArgs(); //获取方法参数
                String name = joinPoint.getSignature().getName(); //获取方法名
                System.out.println("method: " + name + ", arguments: " + Arrays.toString(args));
            }
    3. 后置通知
      • 连接点完成之后执行的, 作用于方法的finally语句块, 即有无异常都会执行.
      • 使用@After注解
            @After(value = "execution(* aop.MathImpl.*(..))")
            public void afterMethod() {
                System.out.println("后置通知");
            }
    4. 返回通知
      • 如果只想在连接点返回的时候记录日志, 应使用返回通知代替后置通知.
      • 使用@AfterReturning注解, 在返回通知中访问连接点的返回值
        • 在返回通知中, 只要将returning属性添加到@AfterReturning注解中, 就可以访问连接点的返回值. 该属性的值即为用来传入返回值的参数名称.
        • 必须在通知方法的签名中添加一个同名参数. 在运行时Spring AOP会通过这个参数传递返回值.
              /**
               * @AfterReturning: 将方法标注为返回通知, 作用于方法执行之后.
               * 可通过returning设置接受方法返回值的变量名.
               * 要想在方法中使用, 必须在方法的形参中设置和变量名相同的参数名的参数, 类型为Object
               */
              @AfterReturning(value = "execution(* aop.MathImpl.*(..))", returning = "result")
              public void afterReturning(JoinPoint joinPoint, Object result) {
                  String name = joinPoint.getSignature().getName();
                  System.out.println("method: " + name + ", result: " + result);
              }
    5. 异常通知
      • 只在连接点抛出异常时才执行异常通知.
      • 将throwing属性添加到@AfterThrowing注解中, 才可以访问连接点抛出的异常. Throwable是所有错误和异常类的顶级父类, 所以在异常通知方法可以捕获到任何错误和异常.
      • 如果只对某种特殊的异常类型感兴趣, 可以将参数声明为其他异常的参数类
        型. 然后通知就只在抛出这个类型及其子类的异常时才被执行.
            /**
             * @AfterThrowing: 将方法标注为异常通知.
             * 异常通知: 当方法抛出异常时作用.
             * 通过throwing设置接受方法返回的异常信息.
             * 在参数列表中可通过具体的异常类型, 来对指定的一场进行操作
             */
            @AfterThrowing(value = "execution(* aop.MathImpl.*(..))", throwing = "e")
            public void afterThrowing(Exception e) {
                System.out.println("有异常了, message: " + e.getMessage());
            }
    6. 环绕通知
      • 环绕通知是所有通知类型中功能最为强大的, 能够全面地控制连接点,甚至可以控制是否执行连接点.
      • 对于环绕通知来说, 连接点的参数类型必须是ProceedingJoinPoint. 它是JoinPoint的子接口, 允许控制何时执行, 是否执行连接.
      • 在环绕通知中需要明确调用ProceedingJoinPoint的proceed()方法来执行被代理的方法, 如果忘记这样做就会导致通知被执行了, 但目标方法没有被执行.
      • 注意: 环绕通知的方法需要返回目标方法执行之后的结果, 即调用point.proceed(); 的返回值, 否则会出现空指针异常.
            @Around(value = "execution(* aop.MathImpl.*(..))")
            public Object arounMethod(ProceedingJoinPoint point) {
                Object result = null;
        
                try {
                    //前置通知
                    System.out.println("method: " + point.getSignature().getName() + ", arguments: " + Arrays.toString(point.getArgs()));
                    result = point.proceed();
                    
                    //返回通知
                    System.out.println("method: " + point.getSignature().getName() + ", result: " + result);
                } catch (Throwable throwable) {
                    //异常通知
                    System.out.println("有异常了, message: " + throwable.getMessage());
                } finally {
                    //后置通知
                    System.out.println("后置通知");
                }
                return result;
            }
    • 重用切入点定义
    1. 同一个切入点表达式可能在多个通知中重复出现, 可通过@Pointcut注解将切入点声明为简单的方法.
    2. 其他通知就可以通过方法名称引入该切入点.
          @Pointcut(value = "execution(* aop.MathImpl.*(..))")
          public void test() {
          }
      
          @Before(value = "test()")
          public void beforeMethod(JoinPoint joinPoint) {
              //...
          }
    • 指定切面的优先级
    1. 在同一个连接点上应用多个切面时, 除非明确指定, 否则优先级不确定.
    2. 切面的优先级可以通过@Order注解指定.
    3. @Order注解中值(非负数)越小, 优先级越高. 默认值为int的最大值.
      @Component
      @Order(0)
      @Aspect //标注当前类为切面
      public class MyLoggerAspect {
      
          //...
      }
    • 使用举例
      //核心类
      @Component
      public class MathImpl implements MathI { @Override public int add(int i, int j) {
      //先执行前置通知
      int result = i + j; return result;
      //后置通知
      //返回通知 } @Override
      public int sub(int i, int j) { int result = i - j; return result; } @Override public int mul(int i, int j) { int result = i * j; return result; } @Override public int div(int i, int j) { int result = i / j; return result; } } //测试 public class TestBySpring { public static void main(String[] args) { //初始化容器 ClassPathXmlApplicationContext cac = new ClassPathXmlApplicationContext("conf/aop.xml");
      //注意, 这里不要自己创建对象, 要从容器中取, 否则无法启用AOP功能. MathI math
      = cac.getBean("mathImpl", MathI.class); System.out.println(math.getClass().getName()); int div = math.div(4, 1); System.out.println(div); cac.close(); } }

    以XML方式配置切面

    1.  除了使用AspectJ注解声明切面, Spring也支持在bean配置文件中声明切面, 通过aop名称空间中的xml元素完成的.
    2. 配置细节
      • 在bean配置文件中, 所有的SpringAOP配置都必须定义在<aop:config>元素内部, 对于每个切面而言, 都要创建一个<aop:aspect>元素来为具体的切面实现引用后端bean实例.
      • <aop:aspect>中以ref属性标明具体的切面.
      • <aop:before>等, 以method属性标明方法, 以pointcut属性标明切入点表达式.
            <aop:config>
                <aop:aspect ref="myLoggerAspect">
                    <aop:before method="beforeMethod" pointcut="execution(* aop.MathImpl.*(..))" />
                </aop:aspect>
            </aop:config>
    3. 声明切入点
      • 切入点使用<aop:pointcut>元素声明.
      • 切入点可定义在<aop:aspect>或<aop:config>下.
        • 定义在<aop:aspect>下, 只对当前切面有效.
        • 定义在<aop:config>下, 对所有切面有效.
      • 通知元素需要使用<pointcut-ref>来引用切入.
      • 基于XML的AOP配置不允许在切入点表达式中用名称引用其他切入点.
            <aop:config>
                <aop:aspect ref="myLoggerAspect">
                    <aop:pointcut id="cut" expression="execution(* aop.MathImpl.*(..))"/>
                    <aop:before method="beforeMethod" pointcut-ref="cut" />
                </aop:aspect>
            </aop:config>
  • 相关阅读:
    July 08th. 2018, Week 28th. Sunday
    July 07th. 2018, Week 27th. Saturday
    兄弟组件bus传值
    vue 父子组件传值
    路由传值的三种方式
    jQuery 操作表格
    原生js实现开关功能
    跨域解决方法
    正则判断密码难度
    cookie封装函数
  • 原文地址:https://www.cnblogs.com/binwenhome/p/12997196.html
Copyright © 2020-2023  润新知