AOP实战⼩案例
AOP实战⼩案例
前⾔
AOP技术是指⾯向切⾯编程技术,主要⽤于在具有横切逻辑的场景中,将横切逻辑提取出来,形成独⽴的模块,然后通过特殊的技术,如Java中的动态代理技术,动态地将横切逻辑织⼊到具体的应⽤场景中
⼤概在去年的这个时候,学习过AOP技术,对AOP中的⼀些概念也有⼀些了解,不过基本都是理论上的内容,缺乏实战经验,所以,对AOP的理解并不是很充分,加上最近刚好有个项⽬需要⽤到,⽼⼤说让我通过AOP来实现,加深对AOP的理解,所以有了这篇⽂章
环境准备
这篇⽂章中采⽤的是SpringBoot 1.5.14.RELEASE版本,当然,其实哪个版本都可以。不⽤SpringBoot其实也可以,主要是SpringBoot 中配置起来⽐较简单,可以将主要精⼒集中在AOP上⽽不是环境的配置上、
需要引⼊aop依赖以及测试环境(主要是为了⽅便测试)
<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>
⽇志记录及性能监控
有时候,为了对程序有更好的控制以及分析,我们需要记录⽅法的调⽤情况,调⽤结果,以及执⾏时间等信息,这种情况下,基本上就是很多的⽅法都需要记录,⽽记录的这些操作⼜跟业务情况⽆关,也就是符合横切逻辑的概念,所以,通过AOP来实现是⽐较理想⽽且可维护性⽐较恰当的。
在本案例中,为了回顾之前学习到的注解以及反射等技术,这⾥通过为⽅法加上注解的形式来实现⽇志记录开关,当然,通过切⾯表达式直接匹配⽅法实现起来更加简单,不过,作为学习的案例,能在⼀个⼩案例中将尽量多的知识⽤上,也是⼀个不错的选择
⾸先是注解的设计以及实现
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LogInfo {
String value();
}
这⾥我们的注解设计⽐较简单,只包含⼀个基本的元素,⽤于记录⽅法的⽤途。这⾥需要注意的是注解的保留策略,由于我们需要在运⾏时获取注解的内容,所以注解的保留策略需要为RetentionPolicy.RUNTIME
接下来是切⾯的设计
切⾯,其实就是对应的操作逻辑(增强)以及注⼊点(切点)的结合,通过切点来定位,增强来实现功能增强
我们知道,SpringAOP中有多种⽅式来实现,⽐如通过环绕来实现,或者通过前置以及后置来实现,这两种⽅式理论上都是可⾏的,不过实现细节上有所区别
先来看下环绕实现
@Aspect
public class LogAspect {
@Pointcut("@annotation(cn.xuhuanfeng.annnotation.LogInfo)")
public void logPointCut(){}
@Around(value ="logPointCut()")
public Object logAround(ProceedingJoinPoint joinPoint){
// 获取执⾏⽅法签名,这⾥强转为MethodSignature
MethodSignature signature =(MethodSignature) Signature();
Object[] args = Args();
// 通过反射来获取注解内容
LogInfo loginfo = Method().getAnnotation(LogInfo.class);
String description = loginfo.value();
System.out.println("****** "+ description +" Before: "+ signature +" args "+ String(args));
Object result = null;
long start = System.currentTimeMillis();
try{
// 调⽤原来的⽅法
result = joinPoint.proceed();
}catch(Throwable throwable){
throwable.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("****** "+ description +" After: "+ signature +" "+ result +"total time: "+(end - start)+" ms");
return result;
}
}
编写⼀个简单的操作类
@Service
public class BlogService {
@LogInfo("获取博客")
public String getBlog(){
return"Blog info";
}
@LogInfo("增加博客")
public boolean addBlog(String blog){
System.out.println("adding blog");
return true;
spring aop应用场景}
}
对应的测试类如下
@RunWith(SpringRunner.class)
public class BlogServiceTest {
@Autowired
private BlogService blogService;
@Test
public void getBlog(){
}
@Test
public void addBlog(){
blogService.addBlog("blog");
}
}
运⾏结果
****** 增加博客 Before: boolean cn.xuhuanfeng.blog.BlogService.addBlog(String) args [blog]
adding blog
****** 增加博客 After: boolean cn.xuhuanfeng.blog.BlogService.addBlog(String) truetotal time: 4 ms
****** 获取博客 Before: String cn.xuhuanfeng.Blog() args []
****** 获取博客 After: String cn.xuhuanfeng.Blog() Blog infototal time: 0 ms
可以看到,我们所需要的功能已经成功通过AOP中的环绕实现了,既保持了代码的整洁性,以及模块性,⼜基本实现了功能前置以及后置实现
除了上⾯环绕试下外,还可以通过前置以及后置增强来实现,如下所⽰
@Component
@Aspect
public class LogAspect {
@Pointcut("@annotation(cn.xuhuanfeng.annnotation.LogInfo)")
public void logPointCut(){}
// 通过ThreadLocal来记录进⼊⽅法的时间
private ThreadLocal<Long> logRecorder =new ThreadLocal<>();
private String getDescription(JoinPoint joinPoint){
MethodSignature methodSignature =(MethodSignature) Signature();
Method().getAnnotation(LogInfo.class).value();
}
private long calculateExecutionTime(){
long end = System.currentTimeMillis();
long duration = end - ();
return duration;
}
@Before("logPointCut()")
public void beforeMethod(JoinPoint joinPoint){
logRecorder.set(System.currentTimeMillis());
String description =getDescription(joinPoint);
System.out.println("****** "+ description +
" Before: "+ Signature()+
" args "+ Args()));
}
@AfterReturning(value ="logPointCut()", returning ="result")
public void afterReturning(JoinPoint joinPoint, Object result){
String description =getDescription(joinPoint);
System.out.println("****** "+ description +
" After: "+ Signature()+
" "+ result +
" total time: "+calculateExecutionTime()+" ms");
}
}
测试类以及执⾏结果同上,这⾥不重复,结果肯定是⼀样的啦
从上⾯的代码中可以看到,通过AOP的两种不同的操作机制,均能够实现⽇志记录以及⽅法性能记录
然⽽,上⾯第⼆种操作中存在⼀个问题,就是当⽅法抛出异常的时候,@AfterReturing是不会执⾏的,原因在于,@AfterReturing是在⽅法调⽤结束,返回之前进⾏织⼊的,所以⼀旦抛出异常,就⽆法处理了。这时候有两种解决⽅案,第⼀种是使⽤@After,@After是在⽅法调⽤结束之后织⼊的,所以可以正常记录,另⼀种⽅案就是使⽤@AfterThrowing在异常抛出的时候进⾏处理,这两种⽅案均能实现我们所需要的功能,具体的就根据个⼈习惯来处理了。
总结
上⾯通过⼀个简单的⽇志记录案例,使⽤了AOP技术来实现,从⽽使得代码更加清晰,具有更好的维护性,在实现的过程中,顺便回顾了反射技术以及注解技术,将这三个技术整合起来,该案例确实是⼀个不错的尝试,感谢⽼⼤给的这个练⼿机会以及练⼿案例。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论