理论
AOP为 Aspect Oriented Programming的缩写,意为面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术,是软件开发的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型,利用AOP可以对业务逻辑的各个部分进行隔离,从而使业务逻辑各部分之间的耦合度降低,提高程序的可重用性,提高开发效率.
-
Aspect (切面)
在web层级设计中,web层->网关层->服务层->数据层,每一层与每一层之间都是一个切面,编程中,对象与对象之间,方法与方法之间,模块与模块之间都是一个切面 -
Join Point(连接点)
所有可能的需要注入切面的地方,如方法前后,类初始化,属性初始化前后等 -
Poincut(切点)
需要做某些处理(如打印日志、处理缓存等等)的连接点。spring使用了AspectJ的切点表达式语言来定义Spring切面。
具象化理解
在业务场景中,日志的实现,简单实现可以在每个业务逻辑中添加日志的逻辑代码
对于重复的代码块我们可以抽取公共方法,避免如日志的插入等操作重复的copy,但是方法的调用还是要的,这个时候就可以使用切面注入的方式
Spring AOP
- Spring AOP的是用的动态代理模式
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.1.3.RELEASE</version>
</dependency>
- 常用注解
注解 | 含义 |
---|---|
@Aspect | 作用是把当前类标识为一个切面供容器读取 |
@Pointcut | Pointcut是植入Advice的触发条件。每个Pointcut的定义包括2部分,一是表达式,二是方法签名。方法签名必须是 public及void型。可以将Pointcut中的方法看作是一个被Advice引用的助记符,因为表达式不直观,因此我们可以通过方法签名的方式为 此表达式命名。因此Pointcut中的方法只需要方法签名,而不需要在方法体内编写实际代码。 |
@Around | 属于环绕增强,能控制切点执行前,执行后,用这个注解后,程序抛异常,会影响@AfterThrowing这个注解 |
@Before | 在切点方法之前执行 |
@After | 在切点方法之后执行,不管是抛出异常或者正常退出都会执行 |
@AfterReturning | 方法正常退出时执行 |
@AfterThrowing | 切点方法抛异常执行 |
@Component
@Aspect
public class AspectTest {
public static final Logger logger = LoggerFactory.getLogger(AspectTest.class);
@Pointcut("execution(* com.ufgov.ar.modules.test.*.*(..))")
public void pointCut() {
}
@Before("pointCut()")
public void before(){
logger.info("执行before");
}
@After("pointCut()")
public void after(){
logger.info("执行after");
}
@Around("pointCut()")
public void around(ProceedingJoinPoint pjp){
try{
logger.info("执行around");
Object o = pjp.proceed(pjp.getArgs());
logger.info("返回值"+o);
}catch (Throwable throwable) {
throwable.printStackTrace();
}
}
@AfterReturning("pointCut()")
public void afterReturning(){
logger.info("执行afterReturning");
}
@AfterThrowing(throwing = "e",pointcut = "pointCut()")
public void afterThrowing(Throwable e){
logger.info("执行afterThrowing");
}
}
/**
2019-08-26 10:42:10 INFO (AspectTest.java:29)- 执行around
2019-08-26 10:42:10 INFO (AspectTest.java:20)- 执行before
2019-08-26 10:42:10 INFO (AspectServiceImpl.java:13)- hello,shuge
2019-08-26 10:42:10 INFO (AspectTest.java:31)- 返回值goodBye
2019-08-26 10:42:10 INFO (AspectTest.java:24)- 执行after
2019-08-26 10:42:10 INFO (AspectTest.java:39)- 执行afterReturning
**/
日志实现
自定义日志注解
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ArOpLog {
String system() default "报销";
String module() default "";
String menu() default "";
String function() default "";
String content() default "";
}
注解使用
@ArOpLog(module = "报销管理/资金申请", menu = "我的报销单/我的请款单", content = "单据保存")
public Map<String, Object> saveArBill(@RequestBody ArBillVo arBillVo) {
}
日志代码
@Pointcut("@annotation(com.ufgov.ar.common.util.ArOpLog)")
public void logPointCut() {
}
@Around("logPointCut()")
public void around(ProceedingJoinPoint pjp){
try{
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
ArContextInfo arContextInfo = ArContext.getContextInfo();
SysOperationlog log = new SysOperationlog();
log.setUsercode(arContextInfo.getUserCode());
log.setUsername(arContextInfo.getUserName());
log.setAgencycode(arContextInfo.getCoCode());
log.setAgencyname(arContextInfo.getCoName());
log.setUrl(request.getServletPath());
log.setIpaddress(getIpAddress(request));
log.setFuncid(pjp.getSignature().getName());
Object[] args = pjp.getArgs();
String param;
if(args != null && args[0] instanceof ServletRequest){
param = new Gson().toJson(request.getParameterMap());
}else {
param = new Gson().toJson(args);
}
Object res = pjp.proceed(pjp.getArgs());
sysOpLogService.log(pjp,log,res,param);
}catch (Throwable throwable) {
throwable.printStackTrace();
}
}
全局异常捕获
@Pointcut("execution(* com.ufgov.ar.modules.*.serviceImpl.*.*(..))")
public void pointCut() {
}
@AfterThrowing(throwing = "e",pointcut = "pointCut()")
public void afterThrowing(Throwable e){
arBillFileService.writeLog(getExceptionToString(e));
}
public static String getExceptionToString(Throwable e) {
if (e == null){
return "";
}
StringWriter stringWriter = new StringWriter();
e.printStackTrace(new PrintWriter(stringWriter));
return stringWriter.toString();
}
异步处理
@EnableAsync
- 可以异步执行,就是开启多线程的意思。可以标注在方法、类上。
- 为了让@Async注解能够生效,还需要在Spring Boot的主程序中配置@EnableAsync
@Async
- @Async所修饰的函数不要定义为static类型,这样异步调用不会生效|
- 异步方法和调用异步方法的方法不能再同一个类