Spring AOP使用动态代理来创建代理对象,并在代理对象上织入横切关注点
横切关注点通常指与业务逻辑无关,需要在多个地方重复出现,如日志记录、事务管理、安全检查等
核心概念包括切面(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();
}
}
pom.xml
中添加Spring AOP的依赖。@Aspect
注解标记,定义切点和通知。在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); // 记录异常信息
}
}
创建一个配置类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!
execution(* com.example.service.*.*(..))
。within(com.example.service.*)
。this(com.example.service.MyService)
。target(com.example.service.MyService)
。args(java.lang.String)
。@annotation(com.example.annotation.MyAnnotation)
。常用工具类可以帮助我们处理类型、反射、注解和类等操作
位于org.springframework.core
包中,提供了一些与类型相关的实用方法,例如获取泛型类型、判断类型是否为某个类的子类等。
位于org.springframework.util
包中,提供了一些与反射相关的实用方法,例如获取类的所有方法、调用方法等。
位于org.springframework.core.annotation
包中,提供了一些与注解相关的实用方法,例如获取类或方法上的注解、判断类或方法是否包含某个注解等。
位于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 国际许可协议进行许可,本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名。