“ 在各种业务场景中,我们可会有打印日志这种语句,通常为了方便我们直接写在业务逻辑的代码中。实际上和业务无关的代码我们也放入到业务逻辑中,会带来了较强的侵入性编码。通常来说,日志和业务代码应该是分离的,而Spring AOP能很好的实现日志和业务代码的分离,当然Spring AOP的作用不仅仅是用来打印日志的,还可以用来做权限控制,缓存等等......”
AOP也叫做面向切面编程(Aspect-Oriented Programming),它的核心是动态代理,了解过设计模式的小伙伴应该都知道代理模式,代理模式包含两大类:静态代理和动态代理。静态代理这里就不说了;动态代理实现方法我知道的有两种:JDK自带的动态代理和CGLIB的动态代理,JDK自带的动态代理基于反射,所以效率相对低,而且只能代理实现接口的对象。CGLIB的动态代理基于字节码实现(这比反射效率高)而且可以代理没有实现接口的对象,但是不能代理final方法。
这篇文章我们先简单实现利用AOP实现日志打印,然后再看相关注解含义。
相关文章: 代理模式
Spring Boot的搭建
01
—
利用AOP实现日志打印
项目中我们首先导入相关依赖:
代码语言:javascript复制<!--AOP-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
题外话:我是真的觉得现在SpringBoot集成其他框架很简单,我在公司项目上,想要集成AOP做自己开发代码的日志监控,先要导入六七个jar包,然后写(抄)Spring的配置文件,修改(抄)web.xml文件,然后还失败了,因为配置AOP之后,初始化容器报错说不支持XML Schema,只能用XML DTD,最后东改西改的成功启动了,但是AOP还没有集成成功,有这方面有经验的小伙伴请留言指导(我对配置文件基本是完全不懂)。
当我们导入完依赖之后,我们就可以写自己的Aspect类,用于监控方法。
代码语言:javascript复制package xcx.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/**
* @Auther: chenlong
* @Date: 2019/3/2 16:19
* @Description:
*/
@Aspect
@Component
public class MyAspect {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Pointcut("execution(* xcx.request.controller.*.*(..))")
public void logging() {
}
@Before(value = "logging() ")
public void logStart(JoinPoint joinPoint) {
logger.info(joinPoint.getSignature().getName() "运行,参数列表是: {" Arrays.asList(joinPoint.getArgs()) "}");
}
@After(value = "logging()")
public void logEnd(JoinPoint joinPoint) {
System.out.println(joinPoint.getSignature().getName() "结束...");
}
@AfterReturning(value = "logging()", returning = "result")
public void logReturn(JoinPoint joinPoint, Object result) {
System.out.println(joinPoint.getSignature().getName() "正常结束,结果是: {" result "}");
}
@AfterThrowing(value = "logging()", throwing = "e")
public void logException(JoinPoint joinPoint, Exception e) {
System.out.println(joinPoint.getSignature().getName() "异常,异常信息: {" e.getMessage() "}");
}
}
然后.....就没了,我们启动项目然后访问xcx.request.controller下的API就会打印相应日志了:是不是很简单,这样就成功的将业务逻辑和日志打印成功分离,我这里为了测试将execution配置范围比较大,大家可以指定到某个具体的方法,进行单独监控。
下面我们再来看看这些注解有什么含义
02
—
AOP的术语
通知(Advice):
Spring中的切面一共提供5种通知的类型:
前置通知(Before) 后置通知(After) 返回通知(After-Running) 异常通知(After-throwing) 环绕通知(Around)
前面4个较为容易理解,例如“前置通知”,我们通常在一个方法的第一句打印出传入的方法参数,此时就可以使用前置通知在方法调用前打印出传入的参数。对于“后置通知”实际是“返回通知”和“异常通知”的并集,返回通知表示程序正确运行返回后执行,异常通知表示程序不正常运行抛出异常时执行,而后置通知则不论程序是否正确运行,一旦离开方法就会执行。
环绕通知最为强大,它包裹了被通知的方法,可同时定义前置通知和后置通知。
切点(Pointcut):
通知定义了何时工作以及工作内容,切点则定义了在何处工作,也就是在哪个方法应用通知。要表达出在哪个方法中运用通知,这需要用到切点表达式。Spring AOP借助AspectJ(另一种AOP实现)的切点表达式来确定通知被应用的位置,虽然是借助但并不支持所有AspectJ的所有切点指示器而仅仅是其一个子集,这其中最为常用的就是execution切点指示器,表示执行。就如同上述代码:
代码语言:javascript复制 @Pointcut("execution(* xcx.request.controller.*.*(..))")
execution()是最常用的切点函数,其语法如下所示:
整个表达式可以分为五个部分:
1、execution(): 表达式主体。
2、第一个*号:表示返回类型,*号表示所有的类型。
3、包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,com.sample.service.impl包、子孙包下所有类的方法。
4、第二个*号:表示类名,*号表示所有的类。
5、*(..):最后这个星号表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。
到这里就引入Spring AOP到项目中,集成很简单,但是明白原理才是最重要的,在文章的开始我大致的说了AOP是如何实现的,但是很浅显,下篇文章我们再详细的看一下动态代理。