📜  Spring框架中的面向切面编程和AOP

📅  最后修改于: 2022-05-13 01:55:45.134000             🧑  作者: Mango

Spring框架中的面向切面编程和AOP

顾名思义,面向方面的编程(AOP)在编程中使用方面。它可以定义为将代码分解为不同的模块,也称为模块化,其中方面是模块化的关键单元。方面支持横切关注点的实现,例如事务、日志记录,这些不是业务逻辑的核心,而不会将代码核心与其功能混为一谈。它通过添加作为现有代码建议的附加行为来实现。例如,安全性是一个横切关注点,在应用程序中的许多方法中都可以应用安全规则,因此在每个方法中重复代码,在公共类中定义功能,并控制在整个应用程序中应用该功能。

AOP 中的主要框架:
AOP包括支持和实现代码模块化的编程方法和框架。让我们看一下AOP 中的三个主要框架

  • AspectJ:它是PARC 研究中心创建的Java编程扩展。它使用类似Java的语法并包含用于显示横切结构的 IDE 集成。它有自己的编译器和编织器,使用它可以使用完整的 AspectJ 语言。
  • JBoss:是JBoss开发的一款开源Java应用服务器,用于Java开发。
  • Spring:它使用基于 XML 的配置来实现 AOP,还使用注释,这些注释通过使用 AspectJ 提供的库进行解析和匹配来解释。

目前市场上以 Spring 框架为主的 AspectJ 库占主导地位,因此让我们了解一下面向 Aspect 的编程如何与 Spring 配合使用。

面向方面的编程如何与 Spring 一起工作:
有人可能认为调用一种方法会自动实现横切关注点,但事实并非如此。仅调用方法不会调用建议(本应完成的工作)。 Spring 使用基于代理的机制,即它创建一个代理对象,该对象将环绕原始对象并接受与方法调用相关的建议。代理对象可以通过代理工厂 bean 手动创建,也可以通过 XML 文件中的自动代理配置创建,并在执行完成时被销毁。代理对象用于丰富真实对象的原始行为。

AOP 中的常用术语:

  1. 方面:实现 JEE 应用程序横切关注点(事务、记录器等)的类称为方面。它可以是通过 XML 配置或通过使用 @Aspect 注释的常规类配置的普通类。
  2. 编织:将方面与建议对象联系起来的过程。它可以在加载时、编译时或运行时完成。 Spring AOP 在运行时进行编织。

    让我们编写我们的第一个方面类,但在此之前看看所需的 jars 和 AOP 的 Bean 配置文件。

    package com.aspect
      
        import org.aspectj.lang.annotation.Aspect;
    import Java.lang.RuntimeException;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
      
    // Logging class is anotated with @Aspect
    // and will contain advices.
    @Aspect
    class Logging {
    }
      
    // The class ImplementAspect
    // contains method Aspectcall
    // and the advices will be applied
    // on that method for demo.
    public class ImplementAspect {
        public static void main(String args[])
        {
      
            Scanner sc = new Scanner(System.in);
            System.out.println("my first aspect");
      
            // **Add beanconfiguration file
            // in your programme when executing.**
            ApplicationContext ctx
                = new ClassPathXmlApplicationContext("beanconfigfile.XML");
      
            ImplementAspect call
                = (ImplementAspect)ctx.getbean("aspect");
      
            System.out.println("enter an integer");
            int a = sc.nextInt();
            if (a == 1) {
                throw new RuntimeException(msg);
            }
            else {
                call.aspectCall();
            }
            call.myMethod();
        }
      
        public void aspectCall()
        {
            System.out.println("Applying advices"
                               + " for the first time");
        }
      
        public void myMethod()
        {
            System.out.println("This is an"
                               + " extra method");
        }
    }
    
  3. 建议:本应由 Aspect 完成的工作,或者可以定义为 Aspect 在特定点采取的行动。 Advice 有五种类型,即:Before、After、Around、AfterThrowing 和 AfterReturning。让我们对所有五种类型进行简要讨论。

    建议类型:

    1. 之前:在调用建议的方法之前运行。它由@Before注释表示。
    2. After:无论结果如何,无论成功与否,都在建议的方法完成后运行。它由@After注释表示。
    3. AfterReturning:在建议的方法成功完成后运行,即没有任何运行时异常。它由@AfterReturning注释表示。
    4. Around:这是所有建议中最强的建议,因为它环绕并在建议方法之前和之后运行。这种类型的建议用于我们需要频繁访问方法或数据库(如缓存)的地方。它由@Around注释表示。
    5. AfterThrowing:在建议的方法引发运行时异常后运行。它由@AfterThrowing注解表示。

    让我们在 Aspect 类 Logger 中实现所有 5 条建议

    // Program to show types of Advices
      
    @Aspect
    class Logging {
      
        // Implementing all the five pieces of advice
        // to execute AfterThrowing advice enter integer value as 1.
      
        // **Before**
        @Before("execution(public void com.aspect.ImplementAspect.aspectCall())")
        public void loggingAdvice1()
        {
            System.out.println("Before advice is executed");
        }
      
        // **After**
        @After("execution(public void com.aspect.ImplementAspect.aspectCall())")
        public void loggingAdvice2()
        {
            System.out.println("Running After Advice.");
        }
      
        // **Around**
        @Around("execution(public void com.aspect.ImplementAspect.myMethod())")
        public void loggingAdvice3()
        {
            System.out.println("Before and After invoking method myMethod");
        }
      
        // **AfterThrowing**
        @AfterThrowing("execution(" public void com.aspect.ImplementAspect.aspectCall())
        ")
            public void
            loggingAdvice4()
        {
            System.out.println("Exception thrown in method");
        }
      
        // **AfterRunning**
        @AfterReturning("execution(public void com.aspect.ImplementAspect.myMethod())")
        public void loggingAdvice5()
        {
            System.out.println("AfterReturning advice is run");
        }
    }
    
  4. JoinPoints:一个应用程序有数以千计的机会或点来应用 Advice。这些点称为连接点。例如,可以在每次调用方法或抛出异常时或在其他各个点应用建议。但是 Spring AOP 目前只支持方法执行连接点(建议在 Spring bean 上执行方法)。

    让我们看看连接点在我们的@Aspect 类(Logger)中做了什么

    // Program to show JoinPoints
      
    @Aspect
    class Logging {
      
        // Passing a JoinPoint Object
        // into parameters of the method
        // with the annotated advice
        // enables to print the information
        /// when the advice is executed
        // with the help of toString() method
        // present in it.
      
        @Before("execution(public void com.aspect.ImplementAspect.aspectCall())")
        public void loggingAdvice1(JoinPoint joinpoint)
        {
            System.out.println("Before advice is executed");
            System.out.println(joinpoint.toString());
        }
    }
    
  5. 切入点:由于在代码的每个点都应用建议是不可行的,因此,最终应用建议的选定连接点称为切入点。通常,您使用显式的类和方法名称或通过定义匹配的类和方法名称模式的正则表达式来指定这些切入点。它有助于通过编写一次并在多个点使用来减少重复代码,让我们看看如何。
    // Program to shgw PointCuts
      
    @Aspect
    class Logging {
      
        // pointcut() is a dummy method
        // required to hold @Pointcut annotation
        // pointcut() can be used instead of writing line 1
        // whenever required, as done in line 4.
        // This prevents a repetition of code.
      
        @Pointcut("execution(public void com.aspect.ImplementAspect.aspectCall())") // line 1
        public void pointCut()
        {
        }
      
        // pointcut() is used to avoid repetition of code
        @Before("pointcut()")
        public void loggingAdvice1()
        {
            System.out.println("Before advice is executed");
        }
    }