目录
- 简介
- 核心概念
- AOP 的实现方式
- AOP 的应用场景
- AOP 的优点
- AOP 的缺点
- AOP 的实现框架
- 代码实战
- 总结
简介
AOP(ASPect-Oriented Programming,面向切面编程)是一种编程范式,旨在通过分离横切关注点(cross-cutting concerns)来提高代码的模块化。
横切关注点是指那些跨越多个模块的功能,例如日志记录、事务管理、安全性检查等。
AOP 通过将这些功能从核心业务逻辑中分离出来,使代码更易于维护和扩展。
核心概念
1、切面(Aspect):
- 切面是横切关注点的模块化实现。它包含通知(Advice)和切点(Pointcut)。
- 例如,日志记录功能可以作为一个切面。
2、连接点(Join Point):
- 连接点是程序执行过程中的特定点,例如方法调用、异常抛出等。
- AOP 可以在这些点上插入额外的行为。
3、通知(Advice):
- 通知是切面在特定连接点执行的动作。常见的通知类型包括:
- 前置通知(Before Advice):在目标方法执行之前执行。
- 后置通知(After Advice):在目标方法执行之后执行。
- 返回通知(After Returning Advice):在目标方法成功返回后执行。
- 异常通知(After Throwing Advice):在目标方法抛出异常后执行。
- 环绕通知(Around Advice):在目标方法执行前后都执行,可以控制是否执行目标方法。
4、切点(Pointcut):
- 切点用于定义哪些连接点会触发通知。它通过表达式或规则匹配连接点。
- 例如,匹配所有以 save 开头的方法。
5、引入(Introduction):
- 引入允许向现有类添加新的方法或属性,而无需修改类的源代码。
6、目标对象(Target Object):
目标对象是被一个或多个切面通知的对象,通常是核心业务逻辑的类。
7、代理(Proxy):
- AOP 框架通过创建代理对象来实现切面功能。
- 代理对象在目标对象的方法调用前后插入通知。
AOP 的实现方式
1、静态代理:
在编译时生成代理类,例如 AspectJ 的编译时织入。
2、动态代理:
在运行时生成代理类,例如 Spring AOP 使用的 JDK 动态代理或 CGLIB。
AOP 的应用场景
1、日志记录:
在方法调用前后记录日志,而不需要在每个方法中手动添加日志代码。
2、事务管理:
在方法执行前后管理事务的开启、提交或回滚。
3、安全性检查:
在方法调用前检查用户权限。
4、性能监控:
在方法执行前后记录时间,用于性能分析。
5、异常处理:
在方法抛出异常时执行特定的处理逻辑。
AOP 的优点
1、模块化:
将横切关注点从核心业务逻辑中分离出来,使代码更清晰、更易于维护。
2、代码复用:
通过切面实现通用功能,避免重复代码。
3、灵活性:
可以动态地添加或移除切面,而不需要修改核心业务代码。
AOP 的缺点
1、学习曲线:
对于初学者来说,AOP 的概念和实现方式可能较难理解。
2、调试困难:
由于切面逻辑是动态插入的,调试时可能难以追踪问题。
3、性能开销:
动态代理和运行时织入可能带来一定的性能开销。
AOP 的实现框架
1、Spring AOP:
Spring 框架提供的 AOP 实现,基于动态代理,易于与 Spring 集成。
2、AspectJ:
功能强大的 AOP 框架,支持编译时织入和运行时织入。
3、JBoss AOP:
JBoss 提供的 AOP 实现,支持复杂的切面逻辑。
代码实战
- maven依赖
<?XML version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <artifactId>spring-boot-demo-log-aop</artifactId> <version>1.0.0-SNAPSHOT</version> <packaging>jar</packaging> <name>spring-boot-demo-log-aop</name> <description>Demo project for Spring Boot</description> <parent> <groupId>com.xiaoma</groupId> <artifactId>spring-boot-demo</artifactId> <version>1.0.0-SNAPSHOT</version> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <Java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <编程;scope>test</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> </dependency> <!-- 解析 UserAgent 信息 --> <dependency> <groupId>eu.bitwalker</groupId> <artifactId>UserAgentUtils</artifactId> </dependency> </dependencies> <build> <finalName>spring-boot-demo-log-aop</finalName> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
- AopLog.java
@Aspect @Component @Slf4j public class AopLog { private static final String START_TIME = "request-start"; /** * 切入点 */ @Pointcut("execution(public * com.xiaoma.log.aop.controller.*Controller.*(..))") public void log() { } /** * 前置操作 * * @param point 切入点 */ @Before("log()")编程 public void beforeLog(JoinPoint point) { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = Objects.requireNonNull(attributes).getRequest(); log.info("【请求 URL】:{}", request.getRequestURL()); log.info("【请求 IP】:{}", request.getRemoteAddr()); log.info("【请求类名】:{},【请求方法名】:{}", point.getSignature().getDeclaringTypeNampythone(), point.getSignature().getName()); Map<String, String[]> parameterMap = request.getParameterMap(); log.info("【请求参数】:{},", jsONUtil.toJsonStr(parameterMap)); Long start = System.currentTimeMillis(); request.setAttribute(START_TIME, start); } /** * 环绕操作 * * @param point 切入点 * @return 原方法返回值 * @throws Throwable 异常信息 */ @Around("log()") public Object aroundLog(ProceedingJoinPoint point) throws Throwable { Object result = point.proceed(); log.info("【返回值】:{}", JSONUtiandroidl.toJsonStr(result)); return result; } /** * 后置操作 */ @AfterReturning("log()") public void afterReturning() { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = Objects.requireNonNull(attributes).getRequest(); Long start = (Long) request.getAttribute(START_TIME); Long end = System.currentTimeMillis(); log.info("【请求耗时】:{}毫秒", end - start); String header = request.getHeader("User-Agent"); UserAgent userAgent = UserAgent.parseUserAgentString(header); log.info("【浏览器类型】:{},【操作系统】:{},【原始User-Agent】:{}", userAgent.getBrowser().toString(), userAgent.getOperatingSystem().toString(), header); } }
- TestController.java
@RestController public class TestController { /** * 测试方法 * * @param who 测试参数 * @return {@link Dict} */ @GetMapping("/http://www.devze.comtest") public Dict test(String who) { return Dict.create().set("who", StrUtil.isBlank(who) ? "me" : who); } }
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程客栈(www.devze.com)。
精彩评论