面向切面编程(Aspect-Oriented Programming,简称AOP)是一种编程范式,它允许开发者将横切关注点(如日志记录、事务管理等)与业务逻辑分离,从而提高代码的模块化和可维护性。AOP 通过引入“切面”的概念来实现这一点,切面可以看作是封装了横切关注点的模块。
面向切面编程(AOP)的一个经典例子是日志记录。在一个应用程序中,你可能有多个服务层方法需要记录每次调用的日志。如果使用传统的面向对象编程(OOP),你需要在每个服务层方法中手动添加日志代码,这会导致代码重复和难以维护。
通过AOP,你可以定义一个日志切面,它将日志记录逻辑封装在一个单独的模块中。这个切面可以配置为在特定方法调用的前后自动执行日志记录,而不需要修改这些方法的代码。下面是一个简化的示例来说明这个过程:
@Aspect
注解标记它。@Aspect
public class LoggingAspect {
@Before("execution(* com.example.service.*.*(..))")
public void logBefore(JoinPoint joinPoint) {
// 获取被调用的方法名和参数
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
// 打印日志
System.out.println("Before method: " + methodName + ", with args: " + Arrays.toString(args));
}
@AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result")
public void logAfterReturning(JoinPoint joinPoint, Object result) {
// 打印返回结果
System.out.println("After method returned: " + result);
}
}
<aop:aspectj-autoproxy/>
@Service
public class UserService {
public User findUserById(Long id) {
// 业务逻辑
return userRepository.findById(id);
}
}
在这个例子中,无论何时调用UserService
的findUserById
方法,LoggingAspect
中的日志记录逻辑都会自动执行,无需在UserService
中添加任何日志代码。这样,日志记录就作为一个横切关注点被模块化了,符合AOP的原则。
Spring框架中的AOP是一个重要的组成部分,它提供了声明式企业服务和自定义切面的支持。Spring AOP使用纯Java实现,不需要特殊的编译过程,适用于J2EE web容器或应用服务器。
Spring AOP支持两种配置风格:基于注解的@AspectJ风格和基于XML的schema风格。@AspectJ风格使用Java 5的注解,可以声明切面为普通的Java类,而XML风格则使用aop命名空间来定义切面和通知。
@AspectJ
风格首先,需要引入必要的Spring依赖,确保项目中有Spring AOP相关的库。
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aopartifactId>
<version>5.3.9version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.3.9version>
dependency>
dependencies>
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingAspect {
// 定义一个切入点,匹配service包下的所有方法
@Before("execution(* com.example.service.*.*(..))")
public void logBefore() {
System.out.println("A method in the service package is being called.");
}
}
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackages = "com.example")
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AppConfig {
}
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
MyService service = context.getBean(MyService.class);
service.doSomething();
}
}
@Component
public class MyService {
public void doSomething() {
System.out.println("Executing service method...");
}
}
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<aop:config>
<aop:aspect ref="loggingAspect">
<aop:pointcut id="serviceMethods" expression="execution(* com.example.service.*.*(..))"/>
<aop:before pointcut-ref="serviceMethods" method="logBefore"/>
aop:aspect>
aop:config>
<bean id="loggingAspect" class="com.example.aspect.LoggingAspect"/>
<bean id="myService" class="com.example.service.MyService"/>
beans>
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
MyService service = context.getBean(MyService.class);
service.doSomething();
}
}
Spring AOP的代理机制可以是基于JDK动态代理或CGLIB代理。如果目标对象实现了至少一个接口,则使用JDK动态代理;如果没有实现任何接口,则使用CGLIB代理。
使用JDK动态代理(假设MyService实现了一个接口)
import org.springframework.aop.framework.ProxyFactory;
public class Main {
public static void main(String[] args) {
ProxyFactory factory = new ProxyFactory(new MyServiceImpl());
factory.addAdvice(new LoggingAdvice());
MyService proxy = (MyService) factory.getProxy();
proxy.doSomething();
}
}
使用CGLIB代理(MyService不实现任何接口)
import org.springframework.aop.framework.ProxyFactory;
public class Main {
public static void main(String[] args) {
ProxyFactory factory = new ProxyFactory(new MyService());
factory.setProxyTargetClass(true); // 强制使用CGLIB代理
factory.addAdvice(new LoggingAdvice());
MyService proxy = (MyService) factory.getProxy();
proxy.doSomething();
}
}
此外,Spring AOP还支持以编程方式创建代理,可以使用AspectJProxyFactory类来为一个或多个@AspectJ切面通知的目标对象创建代理。
AspectJProxyFactory
import org.springframework.aop.aspectj.annotation.AspectJProxyFactory;
public class Main {
public static void main(String[] args) {
MyService target = new MyService();
AspectJProxyFactory factory = new AspectJProxyFactory(target);
factory.addAspect(LoggingAspect.class);
MyService proxy = factory.getProxy();
proxy.doSomething();
}
}
最后,Spring框架还支持使用AspectJ的加载时织入(LTW),这允许在虚拟机载入字节码文件时动态织入AspectJ切面,提供了更细粒度的控制。
在META-INF/aop.xml
中配置:
<aspectj>
<weaver>
<include within="com.example..*"/>
weaver>
<aspects>
<aspect name="com.example.aspect.LoggingAspect"/>
aspects>
aspectj>
在Spring配置中启用LTW:
<beans>
<context:load-time-weaver/>
beans>
还在学习中,内容有误请联系作者,本内容仅供各位大佬了解与讨论。