AOP 简介
AOP(Aspect Orient Programming)是一种设计思维,是软件设计畛域中的面向切面编程,它是面向对象编程 (OOP) 的一种补充和欠缺。理论我的项目中咱们通常将面向对象了解
为一个动态过程 (例如一个零碎有多少个模块,一个模块有哪些对象,对象有哪些属性),面向切面了解为一个动静过程(在对象运行时动静织入一些扩大性能或管制对象执行)。
AOP 与 OOP 字面意思相近,但其实两者齐全是面向不同畛域的设计思维。理论我的项目中咱们通常将面向对象了解为一个动态过程(例如一个零碎有多少个模块,一个模块有哪些
对象,对象有哪些属性),面向切面的运行期代理形式,了解为一个动静过程,能够在对象运行时动静织入一些扩大性能或管制对象执行。
实现原理
AOP 能够在系统启动时为指标类型创立子类或兄弟类型对象, 这样的对象咱们通常会称之为动静代理对象. 如图所示:
其中,为指标类型 (XxxServiceImpl) 创立其代理对象形式有两种:
第一种形式: 借助 JDK 官网 API 为指标对象类型创立其兄弟类型对象, 然而指标对象类型须要实现相应接口.
第二种形式: 借助 CGLIB 库为指标对象类型创立其子类类型对象, 然而指标对象类型不能应用 final 润饰.
相干术语剖析
切面 (aspect): 横切面对象,个别为一个具体类对象
切入点 (pointcut): 定义了切入扩大业务逻辑的地位(哪些办法运行时切入扩大业务),个别会通过表达式进行相干定义,一个切面中能够定义多个切入点。
告诉(Advice): 外部封装扩大业务逻辑的具体方法对象,一个切面中能够有多个告诉(在切面的某个地位上执行的动作(扩大性能))
连接点(joinpoint): 程序执行过程中,封装了某个正在执行的指标办法信息的对象,能够通过此对象获取具体的指标办法信息,甚至去调用指标办法。连接点与切入点定义如图:
案例
增加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
业务切面对象设计
通过设计切面对象,为指标业务办法做性能加强,关键步骤如下:
第一步:创立注解类型,利用于切入点表达式的定义,要害代码如下:
@Target 用于形容定义的注解可能润饰的对象,@Retention 用于形容定义的注解何时无效。
package com.cy.pj.sys.commen.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequiredLog{String operation();// 在指标业务上应用 @RequiredLog 时须要为 operation 赋值
// String operation() default "";// 在指标业务上应用 @RequiredLog 时不须要为 operation 赋值}
第二步:创立切面对象,用于做日志业务加强,要害代码如下:
package com.cy.pj.sys.service.aspect;
import com.cy.pj.sys.commen.annotation.RequiredLog;
import com.cy.pj.sys.dao.SysLogDao;
import com.cy.pj.sys.pojo.SysLog;
import com.cy.pj.sys.service.SysLogService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.List;
/*
在 spring aop 利用中,基于 @Aspect 注解形容的
类型为一个切面类型,此类中要封装切入点及告诉办法的定义
1. 切入点:要切入扩大业务逻辑的一些指标办法汇合
2. 告诉:封装了扩大业务逻辑的一个办法
* 创立切面对象,用于做日志业务加强
* */
@Aspect
@Component
public class SysLogAspect {
private static final Logger log=
LoggerFactory.getLogger(SysLogAspect.class);
/* 定义切入点,基于 @pointcut 注解定义,这里的 @annotation 为一种切入点表达式
* 示意由 RequiredLog 注解形容的办法为一个切入点办法,咱们在这样的办法上增加业务扩大
* */ @Pointcut("@annotation(com.cy.pj.sys.commen.annotation.RequiredLog)")
public void doLog(){}// 此办法只负责承载切入点的定义
/*
* @Around 注解形容的办法,能够在切入点执行之前和之后进行业务拓展
* @param jp 连接点对象,在此对象封装了要执行的指标办法信息
* ProceedingJoinPoint 此类型的注解只能定义在 @Around 中
* 能够通过连接点对象调用指标办法
* @return 指标办法的执行后果
* @throws Throwable * */ @Around("doLog()")
//@Around("@annotation(com.cy.pj.sys.commen.annotation.RequiredLog)")
public Object doAround(ProceedingJoinPoint jp) throws Throwable
{log.info("method start {}", System.currentTimeMillis());
long t1=System.currentTimeMillis();
try {Object result=jp.proceed();// 调用指标办法
System.out.println(jp.getTarget().getClass().getName());
log.info("method after {}", System.currentTimeMillis());
long t2=System.currentTimeMillis();
doLogInfo(jp,(t2-t1),null);
return result;
}catch (Throwable e){log.error("exception {}", e.getMessage());
long t3=System.currentTimeMillis();
doLogInfo(jp,(t3-t1),e);
throw e;
}
}
// 记录用户行为日志
@Autowired
private SysLogService sysLogService;
private void doLogInfo(ProceedingJoinPoint jp,long time,Throwable e) throws NoSuchMethodException, JsonProcessingException {
// 获取行为日志
String username="tony";// 登录用户
String ip="192.168.100.11";// 登录用户的 IP 借助三方工具类
// 获取用户操作
// 获取办法所在类的字节码对象(指标对象对应的字节码对象)Class<?>cls=jp.getTarget().getClass();
// 获取注解形容的办法对象(字节码对象,办法名,参数列表)//System.out.println("cls="+cls);
Signature signature=jp.getSignature();
//System.out.println("signature="+signature.getClass().getName());//MethodSignatureImpl
MethodSignature methodSignature= (MethodSignature) signature;
// Method targetMethod=methodSignature.getMethod();//cglib
Method targetMethod=//cglib,jdk
cls.getDeclaredMethod(methodSignature.getName(),
methodSignature.getParameterTypes());
// System.out.println("targetMethod="+targetMethod);
// 获取 RequiredLog 注解
RequiredLog requiredLog=targetMethod.getAnnotation(RequiredLog.class);
String operation= requiredLog.operation();
//String operation=null;
// 获取办法申明信息
// System.out.println(cls.getName());//com.cy.pj.sys.service.serviceImpl.SysLogServiceImpl
// System.out.println(targetMethod.getName());//findLogs
String method=cls.getName()+"."+targetMethod.getName();
// 获取办法执行时传入的理论参数
String params=new ObjectMapper().writeValueAsString(jp.getArgs());
// 获取状态信息
Integer status=e==null?1:0;
String error=e==null?null:e.getMessage();
// 封装用户行为日志
SysLog sysLog=new SysLog();
sysLog.setUsername(username);
sysLog.setIp(ip);
sysLog.setCreatedTime(new Date());
sysLog.setOperation(operation);
sysLog.setParams(params);
sysLog.setStatus(status);
sysLog.setError(error);
sysLog.setTime(time);
// 打印日志
log.info("user log {}", new ObjectMapper().writeValueAsString(sysLog));
// 将日志写入到数据库
sysLogService.saveLog(sysLog);
}
}
第三步:通过注解 RequiredLog 注解形容日志查问或删除业务相干办法,此时这个办法为日志切入点办法,例如:
@RequiredLog(operation = "布告查问")
@Override
public List<SysLog> findLogs(SysLog sysLog) {return sysLogDao.selectLogs(sysLog);
}
@RequiredLog(operation = "删除日志")
// @RequiredLog
@Override
public int deleteById(long... id) {return sysLogDao.deleteLogs(id);
}