基础概念
首先让我们从一些重要的AOP概念和术语开始。这些术语不是Spring特有的。不过AOP术语并不是特别的直观,如果Spring使用自己的术语,将会变得更加令人困惑。
概念
- 切面(Aspect):一个关注点的模块化,这个关注点可能会横切多个对象。事务管理是J2EE应用中一个关于横切关注点的很好的例子。在Spring AOP中,切面可以使用基于模式或者基于@Aspect注解的方式来实现。
- 连接点(Joinpoint):在程序执行过程中某个特定的点,比如某方法调用的时候或者处理异常的时候。在Spring AOP中,一个连接点总是表示一个方法的执行。
- 通知(Advice):在切面的某个特定的连接点上执行的动作。其中包括了“around”、“before”和“after”等不同类型的通知(通知的类型将在后面部分进行讨论)。许多AOP框架(包括Spring)都是以拦截器做通知模型,并维护一个以连接点为中心的拦截器链。
- 切入点(Pointcut):匹配连接点的断言。通知和一个切入点表达式关联,并在满足这个切入点的连接点上运行(例如,当执行某个特定名称的方法时)。切入点表达式如何和连接点匹配是AOP的核心:Spring缺省使用AspectJ切入点语法。
- 引入(Introduction):用来给一个类型声明额外的方法或属性(也被称为连接类型声明(inter-type declaration))。Spring允许引入新的接口(以及一个对应的实现)到任何被代理的对象。例如,你可以使用引入来使一个bean实现IsModified接口,以便简化缓存机制。
- 目标对象(Target Object): 被一个或者多个切面所通知的对象。也被称做被通知(advised)对象。 既然Spring AOP是通过运行时代理实现的,这个对象永远是一个被代理(proxied)对象。
- AOP代理(AOP Proxy):AOP框架创建的对象,用来实现切面契约(例如通知方法执行等等)。在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。
- 织入(Weaving):把切面连接到其它的应用程序类型或者对象上,并创建一个被通知的对象。这些可以在编译时(例如使用AspectJ编译器),类加载时和运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。
通知类型
- 前置通知(Before advice):在某连接点之前执行的通知,但这个通知不能阻止连接点之前的执行流程(除非它抛出一个异常)。
- 后置通知(After returning advice):在某连接点正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。
- 异常通知(After throwing advice):在方法抛出异常退出时执行的通知。
- 最终通知(After (finally) advice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
- 环绕通知(Around Advice):包围一个连接点的通知,如方法调用。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它自己的返回值或抛出异常来结束执行。
环绕通知是最常用的通知类型。和AspectJ一样,Spring提供所有类型的通知,我们推荐你使用尽可能简单的通知类型来实现需要的功能。例如,如果你只是需要一个方法的返回值来更新缓存,最好使用后置通知而不是环绕通知,尽管环绕通知也能完成同样的事情。用最合适的通知类型可以使得编程模型变得简单,并且能够避免很多潜在的错误。比如,你不需要在JoinPoint上调用用于环绕通知的proceed()方法,就不会有调用的问题。
在Spring 2.0中,所有的通知参数都是静态类型,因此你可以使用合适的类型(例如一个方法执行后的返回值类型)作为通知的参数而不是使用Object数组。
通过切入点匹配连接点的概念是AOP的关键,这使得AOP不同于其它仅仅提供拦截功能的旧技术。 切入点使得通知可以独立对应到面向对象的层次结构中。例如,一个提供声明式事务管理 的环绕通知可以被应用到一组横跨多个对象的方法上(例如服务层的所有业务操作)。
引入依赖
<!-- aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
AOP开启
Spring Boot 场景下,不需要在 main Application 中单独配置@EnableAspectJAutoProxy
,也不需要再 application.properties 中配置spring.aop.auto=true
,Spring Boot引入 spring-boot-starter-aop 后默认开启 AOP 。
另外,当我们需要强制使用 CGLIB 来实现 AOP 的时候,需要配置 spring.aop.proxy-target-class=true
或 @EnableAspectJAutoProxy(proxyTargetClass = true)
日志配置
<!-- common biz log start -->
<appender name="biz-common" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/biz-common.log</file>
<encoder>
<pattern>${FILE_LOG_PATTERN}</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/biz-common.log.%d{yyyy-MM-dd}.%i</fileNamePattern>
<maxHistory>90</maxHistory>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>50MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
</appender>
<logger name="biz-common" level="INFO" additivity="false">
<appender-ref ref="biz-common"/>
</logger>
<!-- common biz log end -->
Aspect开发
import com.taobao.hsf.util.RequestCtxUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/**
* 监控旧服务的访问
* 打日志
* Created by duzhen on 2018/4/13.
*/
@Component
@Aspect
public class RemoteServiceCallAspect {
private static final Logger logger = LoggerFactory.getLogger("remote_service_call_aspect");
//com.aliexpress.seller.remote.service.impl.SellerAdmissionPostingRemoteQueryServiceImpl
@Pointcut("execution(* com.aliexpress.seller.remote.service.impl.SellerAdmissionPostingRemoteQueryServiceImpl.*(..))")
public void postCallLog(){}
//com.aliexpress.seller.remote.service.impl.InviteProcessRemoteServiceImpl
@Pointcut("execution(* com.aliexpress.seller.remote.service.impl.InviteProcessRemoteServiceImpl.*(..))")
public void inviteCallLog(){}
@Before("postCallLog() || inviteCallLog()")
public void doBefore(JoinPoint joinPoint) {
logger.error("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName() +
". ARGS : " + Arrays.toString(joinPoint.getArgs()) +
". CLIENT_IP : " + RequestCtxUtil.getClientIp() +
". CLIENT_APP : " + RequestCtxUtil.getAppNameOfClient());
}
}
这个Aspect的含义是,在 com.aliexpress.seller.remote.service.impl.SellerAdmissionPostingRemoteQueryServiceImpl
或 com.aliexpress.seller.remote.service.impl.InviteProcessRemoteServiceImpl
所有方法被调用时,会先调用 RemoteServiceCallAspect.doBefore()
进行日志打印。
参考文章
spring pointcut expressions
Chapter 6. 使用Spring进行面向切面编程(AOP)
Spring学习4:Spring AOP(面向切面编程)
Spring Boot中使用AOP统一处理Web请求日志