SpringFramework-AOP

发布 | 2024-09-06 | JAVA

Spring AOP

Spring AOP使用动态代理来创建代理对象,并在代理对象上织入横切关注点

横切关注点通常指与业务逻辑无关,需要在多个地方重复出现,如日志记录、事务管理、安全检查等

核心概念包括切面(Aspect)、连接点(Join Point)、通知(Advice)和切入点(Pointcut)。

  • 切面(Aspect):切面是横切关注点的模块化,它包含了通知和切入点。
  • 连接点(Join Point):连接点是程序执行的一个点,如方法调用或异常抛出。
  • 通知(Advice):通知是切面在连接点执行的操作,如方法调用前后执行某些操作。
  • 切入点(Pointcut):切入点定义了通知应该被应用在哪些连接点上。

原生动态代理

动态代理是Java反射机制的一部分,它允许程序在运行时创建接口的代理对象。动态代理主要用于实现一些通用的功能,如日志记录、事务管理、权限检查等,而不需要修改源代码。

Hello.java

public interface Hello {
  void sayHello();
}

HelloImpl.java

public class HelloImpl implements Hello {
  @Override
  public void sayHello() {
      System.out.println("Hello, world!");
  }
}

HelloProxy.java

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
 
public class HelloProxy implements InvocationHandler {
  private Object target;
 
  public HelloProxy(Object target) {
      this.target = target;
  }
 
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      System.out.println("Before method: " + method.getName());
      Object result = method.invoke(target, args);
      System.out.println("After method: " + method.getName());
      return result;
  }
}

Main.java

public class Main {
  public static void main(String[] args) {
      Hello hello = new HelloImpl();
      Hello proxy = (Hello) Proxy.newProxyInstance(
              hello.getClass().getClassLoader(),
              hello.getClass().getInterfaces(),
              new HelloProxy(hello)
      );
      proxy.sayHello();
  }
}

AOP流程

  1. 引入依赖:在pom.xml中添加Spring AOP的依赖。
  2. 定义切面:创建一个类,使用@Aspect注解标记,定义切点和通知。
  3. 配置AOP:在Spring配置文件中启用AOP,或者使用Java配置类启用AOP。
  4. 编写业务逻辑:定义一个普通的业务逻辑类,并使用切面进行增强。
  5. 运行程序:测试AOP功能是否生效。
引入依赖

pom.xml中添加Spring AOP的依赖:

<dependencies>
  <!-- Spring Core -->
  <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
  </dependency>
  <!-- Spring AOP -->
  <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aop</artifactId>
  </dependency>
  <!-- AspectJ Weaver -->
  <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
  </dependency>
</dependencies>
定义切面

创建一个切面类LoggingAspect,并使用@Aspect注解标记:

    import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.JoinPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
 
@Aspect // 定义切面
@Component // 将该类声明为Spring组件
public class LoggingAspect {
 
   private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class); // 日志记录器
 
   // 使用切点表达式匹配com.example.service包下的所有方法
   @Before("execution(* com.example.service.*.*(..))")
   public void logBefore(JoinPoint joinPoint) {
       logger.info("Before method: " + joinPoint.getSignature().getName()); // 在方法执行前记录日志
   }
 
   @After("execution(* com.example.service.*.*(..))")
   public void logAfter(JoinPoint joinPoint) {
       logger.info("After method: " + joinPoint.getSignature().getName()); // 在方法执行后记录日志
   }
 
   // 匹配com.example.service包下的所有方法,并在方法成功返回后记录日志
   @AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result")
   public void logAfterReturning(JoinPoint joinPoint, Object result) {
       logger.info("Method returned: " + result); // 记录方法返回值
   }
 
   // 匹配com.example.service包下的所有方法,并在方法抛出异常后记录日志
   @AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "error")
   public void logAfterThrowing(JoinPoint joinPoint, Throwable error) {
       logger.error("Exception in method: " + joinPoint.getSignature().getName() + ", Exception: " + error); // 记录异常信息
   }
}
配置AOP

创建一个配置类AopConfig,并使用@EnableAspectJAutoProxy注解启用AOP:

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
 
@Configuration
@EnableAspectJAutoProxy
public class AopConfig {
}
编写业务逻辑

创建一个业务逻辑类MyService

import org.springframework.stereotype.Service;
 
@Service
public class MyService {
 
  public void performTask() {
      System.out.println("Performing task...");
  }
 
  public void performTaskWithError() {
      System.out.println("Performing task with error...");
      throw new RuntimeException("Task failed!");
  }
}
运行程序

创建一个主程序类App来测试AOP功能:

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.example.service.MyService;
 
public class App {
  public static void main(String[] args) {
      ApplicationContext context = new AnnotationConfigApplicationContext(AopConfig.class);
      MyService myService = context.getBean(MyService.class);
 
      myService.performTask();
      myService.performTaskWithError();
  }
}
运行结果

运行上述程序,你将看到类似以下的日志输出:

INFO  c.e.LoggingAspect - Before method: performTask
INFO  c.e.LoggingAspect - After method: performTask
INFO  c.e.LoggingAspect - Method returned: null
 
INFO  c.e.LoggingAspect - Before method: performTaskWithError
INFO  c.e.LoggingAspect - After method: performTaskWithError
ERROR c.e.LoggingAspect - Exception in method: performTaskWithError, Exception: java.lang.RuntimeException: Task failed!

AOP术语

  • 切面(Aspect):一个模块化的关注点,它横切多个类,通常包含横切关注点的代码。
  • 连接点(Join Point):程序执行的一个点,例如方法调用或异常抛出。
  • 通知(Advice):在连接点执行的操作,例如前置通知、后置通知、返回通知和异常通知。
  • 切点(Pointcut):匹配连接点的表达式。
  • 引入(Introduction):允许向现有的类添加新的方法或属性。
  • 织入(Weaving):将切面应用到目标对象,创建一个被通知的对象。

常用注解

  • @Aspect:标记一个类为切面。
  • @Pointcut:定义一个切点表达式。
  • @Before:在连接点之前执行的通知。
  • @After:在连接点之后执行的通知。
  • @AfterReturning:在连接点正常返回后执行的通知。
  • @AfterThrowing:在连接点抛出异常后执行的通知。
  • @Around:环绕通知,可以在方法执行前后执行逻辑。可以控制目标方法是否执行,修改目标方法参数执行结果等。
  • @EnableAspectJAutoProxy:启用Spring AOP自动代理。

切点表达式

  • execution:匹配方法执行连接点,例如execution(* com.example.service.*.*(..))
  • within:匹配特定类型的连接点,例如within(com.example.service.*)
  • this:匹配当前AOP代理对象类型的连接点,例如this(com.example.service.MyService)
  • target:匹配目标对象类型的连接点,例如target(com.example.service.MyService)
  • args:匹配参数列表的连接点,例如args(java.lang.String)
  • @annotation:匹配带有特定注解的连接点,例如@annotation(com.example.annotation.MyAnnotation)

常用工具类

常用工具类可以帮助我们处理类型、反射、注解和类等操作

TypeUtils

位于org.springframework.core包中,提供了一些与类型相关的实用方法,例如获取泛型类型、判断类型是否为某个类的子类等。

ReflectionUtils

位于org.springframework.util包中,提供了一些与反射相关的实用方法,例如获取类的所有方法、调用方法等。

AnnotationUtils

位于org.springframework.core.annotation包中,提供了一些与注解相关的实用方法,例如获取类或方法上的注解、判断类或方法是否包含某个注解等。

ClassUtils

位于org.springframework.util包中,提供了一些与类相关的实用方法,例如获取类的全限定名、判断类是否为某个类的子类等。

在使用这些工具类时,需要导入相应的包,并使用静态导入来简化代码。例如:

import static org.springframework.core.annotation.AnnotationUtils.findAnnotation;
import static org.springframework.util.ClassUtils.getAllInterfacesForClass;
import static org.springframework.util.ReflectionUtils.findMethod;

然后,就可以直接使用这些工具类的方法,而不需要每次都写出完整的类名。例如:

Annotation annotation = findAnnotation(MyClass.class, MyAnnotation.class);
Class[] interfaces = getAllInterfacesForClass(MyClass.class);
Method method = findMethod(MyClass.class, "myMethod");

这些工具类是Spring框架中的一部分,可以在任何Spring应用程序中使用。

代码

package xin.links.framework.springaop.operation;

/**
 * ClassName: SpringFramework
 * Package: xin.links.framework.springaop.operation
 * Description:
 *
 * @author Jane
 * @version 1.0
 * @since 2024/9/3 18:37
 */
public interface MathOperation {

//    定义四则运算
    int add(int a, int b);
    int sub(int a, int b);
    int mul(int a, int b);
    int div(int a, int b);
}

package xin.links.framework.springaop.operation;

import org.springframework.stereotype.Component;

/**
 * ClassName: SpringFramework
 * Package: xin.links.framework.springaop.operation
 * Description:
 *
 * @author Jane
 * @version 1.0
 * @since 2024/9/3 18:39
 */
@Component
public class MathOperationImpl implements MathOperation {

    @Override
    public int add(int a, int b) {
        System.out.println(a+"+"+b+"="+(a+b));
        return a+b;}

    @Override
    public int sub(int a, int b) {
        System.out.println(a+"-"+b+"="+(a-b));
        return a-b;
    }

    @Override
    public int mul(int a, int b) {
        System.out.println(a+"*"+b+"="+(a*b));
        return a*b;
    }

    @Override
    public int div(int a, int b) {
        System.out.println(a+"/"+b+"="+(a/b));
        return a/b;
    }

}
package xin.links.framework.springaop.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * ClassName: SpringFramework
 * Package: xin.links.framework.springaop.aspect
 * Description:
 *
 * @author Jane
 * @version 1.0
 * @since 2024/9/4 15:52
 */
@Aspect
@Component
public class LogAspect {


    /**切入点表达式
     * execution(访问修饰符 返回值类型 方法名(参数类型)) 包名.类名.方法名(参数类型)
     *
     */

    /**简单通知
     * @AfterReturning
     * @Before
     * @AfterThrowing
     * @After
     */

    @Before("execution(int xin.links.framework.springaop.operation.MathOperation.*(..))")
    public void logStart(){
        System.out.println("【开始】log日志");
    }

    @After("execution(int xin.links.framework.springaop.operation.MathOperation.*(..))")
    public void logEnd(){
        System.out.println("【结束】log日志");
    }

    @AfterThrowing("execution(int *xin.links.framework.springaop.operation.MathOperation.*(..))")
    public void logException(){
        System.out.println("【异常】log日志");
    }

    @AfterReturning("execution(int xin.links.framework.springaop.operation.MathOperation.*(..))")
    public void logReturn(){
        System.out.println("【返回】log日志");
    }


    /**
     * 定义切入点
     */
    @Pointcut("execution(int xin.links.framework.springaop.operation.MathOperation.*(..))")
    public void logPointcut(){}



    /**
     * 环绕通知
     * @param joinPoint
     * @return
     */
    @Around("logPointcut()")
    public Object logAround(ProceedingJoinPoint joinPoint){
        System.out.println("【环绕开始】log日志");
        Object result = null;
        try {
            result = joinPoint.proceed();
            System.out.println("【环绕返回】log日志");
        } catch (Throwable e) {
            e.printStackTrace();
            System.out.println("【环绕异常】log日志");
        }
        System.out.println("【环绕结束】log日志");
        return result;
    }



}
package xin.links.framework.springaop;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cglib.proxy.Proxy;
import xin.links.framework.springaop.operation.MathOperation;
import xin.links.framework.springaop.operation.MathOperationImpl;

import java.lang.reflect.Array;
import java.util.Arrays;


@SpringBootTest
class SpringAopApplicationTests {


    @Autowired
    MathOperation mathOperation;

    @Test
    public void Test01 () {

        System.out.println("mathOperation.getClass() = " + mathOperation.getClass());
        System.out.println("mathOperation.add(a, b) = " + mathOperation.add(1, 2));



    }

    @Test
    void contextLoads() {

//        原生计算对象
//        MathOperationImpl calculator = new MathOperationImpl();
//        System.out.println("calculator.add(1, 2) = " + calculator.add(1, 2));


        /**动态代理对象
         * 1. 代理对象和被代理对象实现相同的接口
         * 2. 代理对象内部持有一个被代理对象的引用
         * 3. 代理对象和被代理对象有相同的父类或父接口
         */
//        Spring动态代理对象(Java原生支持)
        //创建一个MathOperation类型的代理对象
//        MathOperation mathOperation = (MathOperation)Proxy.newProxyInstance(
//                calculator.getClass().getClassLoader(),
//                calculator.getClass().getInterfaces(),
//                (proxy, method, args) -> {
//                    //执行前打印日志
//                    System.out.println("执行前");
//                    //打印参数
//                    System.out.println("Arrays.asList(args) = " + Arrays.asList(args));
//                    //将参数2的值改为1
//                    args[1]=1;
//                    //执行方法
//                    Object result = method.invoke(calculator, args);
//                    //执行后打印日志
//                    System.out.println("执行后");
//                    //打印参数
//                    System.out.println("Arrays.asList(args) = " + Arrays.asList(args));
//                    //返回结果
//                    return result;
//                });
//        //调用add方法并打印结果
//        System.out.println("mathOperation.add(1, 2) = " + mathOperation.add(1, 2));
//

    }



}

标签
没有标签

© 著作权归作者所有

本文由 趣代码Blog 创作,采用 知识共享署名4.0 国际许可协议进行许可,本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名。

评论关闭