


















AOP 全称 Aspect Oriented Programming(面向切面),AOP主要实现的目的是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。其与设计模式完成的任务差不多,是提供另一种角度来思考程序的结构,来弥补面向对象编程的不足。
通俗点讲就是提供一个为一个业务实现提供切面注入的机制,通过这种方式,在业务运行中将定义好的切面通过切入点绑定到业务中,以实现将一些特殊的逻辑绑定到此业务中。
比如,若是需要一个记录日志的功能,首先想到的是在方法中通过日志框架来进行记录日志,但写下来发现一个问题,在整个业务中其实核心的业务代码并没有多少,都是一些记录日志或其他辅助性的一些代码。而且很多业务有需要相同的功能,比如都需要记录日志,这时候又需要将这些记录日志的功能复制一遍,即使是封装成框架,也是需要调用之类的。
AOP 的基本概念
快速入门
我们通过实现记录方法执行时间的跟踪日志来演示 Spring AOP,示例如下:
创建项目
创建 Spring Boot 项目,命名为 spring-aop,增加 spring-boot-starter-aop 依赖开启 AOP 支持,pom.xml 文件内容如下:
<?xmlversion="1.0"encoding="UTF-8"?>
<projectxmlns="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.0http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.lixue</groupId>
<artifactId>spring-aop</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>spring-aop</name>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.12.RELEASE</version>
<relativePath/><!--lookupparentfromrepository-->
</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-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
创建注解
我们通过注解来标注切面,因此我们需要创建注解类,使用 @Target 注解使用在方法中,使用 @Retention 注解用来声明注解的保留策略,代码如下:
package org.lixue;
import java.lang.annotation.*;
@Target(value={ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EanbleLogTrace{
String name() default "";
}
创建切面类
创建切面类 TraceLogAspect,用于定义跟踪日志的切入点(被 org.lixue.EnableLogTrace 注解标注的方法和类为切入点),和通知方法,代码如下:
package org.lixue;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class TraceLogAspect{
private ThreadLocal<Long> startTimeMillis;
public TraceLogAspect(){
startTimeMillis=new ThreadLocal<>();
}
/**
*定义切入点,使用注解方式,声明了被org.lixue.EnableLogTrace注解标注的方法和类为切入点
*/
@Pointcut("@annotation(org.lixue.EnableLogTrace) || within(@org.lixue.EnableLogTrace *)")
private void pointcut(){
}
/**
*定义通知before(方法执行前),切面使用pointcut()定义的
*
*@param joinPoint
*/
@Before("pointcut()")
public void before(JoinPointjoinPoint){
Object[] args=joinPoint.getArgs();
String methodName=joinPoint.getSignature().getName();
startTimeMillis.set(System.currentTimeMillis());
StringBuilder stringBuilder=new StringBuilder();
for(inti=0;i<args.length;i++){
stringBuilder.append(args[i]+"");
}
System.out.println("beforename="+methodName+"\targs="+stringBuilder.toString()
+"startTimeMillis="+startTimeMillis.get()+"\tThread="+Thread.currentThread().getId());
}
/**
*定义通知After(方法执行后),切面使用pointcut()定义的
*
*@param joinPoint
*/
@After("pointcut()")
public void after(JoinPointjoinPoint){
Object[] args=joinPoint.getArgs();
String methodName=joinPoint.getSignature().getName();
try{
long executingTimeMillis=System.currentTimeMillis()-startTimeMillis.get();
StringBuilder stringBuilder=newStringBuilder();
for(inti=0;i<args.length;i++){
stringBuilder.append(args[i]+"");
}
System.out.println("aftername="+methodName+"\targs="+stringBuilder.toString()
+"executingTimeMillis="+executingTimeMillis+"\tThread="+Thread.currentThread().getId());
}finally{
startTimeMillis.remove();
}
}
}
创建示例类
创建示例类,使用注解 @EnableLogTrace 标注类或者方法
package org.lixue;
import org.springframework.stereotype.Service;
import java.util.Random;
@EnableLogTrace(name="trace")
@Service
public class AccountService{
private Random random;
publicAccountService(){
random = new Random();
}
public boolean login(Stringname,Stringpassword){
int sleep;
try{
sleep=random.nextInt(100);
Thread.sleep(sleep);
System.out.println("loginname="+name+"\tpassword="+password+"\tsleep="+sleep+"\tThread="
+Thread.currentThread().getId());
return true;
}catch(InterruptedExceptione){
e.printStackTrace();
return false;
}
}
}
测试验证
编写单元测试类,通过自动注入 AccountService 类,来调用 login 方法,看日志输入:
package org.lixue;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import static org.junit.Assert.*;
@RunWith(SpringRunner.class)
@SpringBootTest
public class AccountServiceTest{
@Autowired
private AccountService accountService;
@Test
public void login() throws Exception{
accountService.login("lixue","123123");
}
}
执行单元测试输出如下:
before name=login args=lixue 123123 startTimeMillis=1525590400031 Thread=1
login name=lixue password=123123 sleep=86 Thread=1
after name=login args=lixue 123123 executingTimeMillis=114 Thread=1
本文版权归作者 李雪(博客地址:https://www.cnblogs.wiki)所有,欢迎转载和商用,请在文章页面明显位置给出原文链接并保留此段声明,否则保留追究法律责任的权利,其他事项,可留言咨询。
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。