zoukankan      html  css  js  c++  java
  • 深入了解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>
  • 相关阅读:
    Dynamics AX 2012 R2 配置E-Mail模板
    Dynamics AX 2012 R2 设置E-Mail
    Dynamics AX 2012 R2 为运行失败的批处理任务设置预警
    Dynamics AX 2012 R2 耗尽用户
    Dynamics AX 2012 R2 创建一个专用的批处理服务器
    Dynamics AX 2012 R2 创建一个带有负载均衡的服务器集群
    Dynamics AX 2012 R2 安装额外的AOS
    Dynamics AX 2012 R2 将系统用户账号连接到工作人员记录
    Dynamics AX 2012 R2 从代码中调用SSRS Report
    Dynamics AX 2012 R2 IIS WebSite Unauthorized 401
  • 原文地址:https://www.cnblogs.com/binwenhome/p/12997196.html
Copyright © 2011-2022 走看看