1.认识aop
1.何为aop
- aop全称 Aspect Oriented Programming ,面向切面,Aop的主要实现就是针对业务处理过程的切面进行,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果
- 通俗的来比喻,你可以使用aop来帮你完成一些繁杂的工作,而你只需要专注于业务逻辑,好比你吃饭,穿衣服都可以雇一个人来帮你,你只需要去做自己要处理的事情。
2.aop相关名词
- 切面**(Aspect)😗* 一个关注点的模块化,以@Aspect的形式放在类上面声明一个切面
- 链接点**(Joinpoint)😗* 在程序执行过程中某个特定的点,比如方法调用前执行的操作或者是处理异常都可以算作是链接点
- 通知**(Advice): **需要完成的工作叫做通知及,就是写的业务逻辑中,比如事务,日志等先定义好再拿去用
- 主要包括五个注解:Before,After,AfterReturning,AfterThrowing,Around
- @Before: 在切点方法前执行
- @After: 在切点方法后执行
- @AfterReturning: 切点方法返回后执行
- @AfterThrowing: 在切点方法抛出异常后执行
- @Around: 环绕增强,执行后使用此注解会影响@AfterThrowing这个注解
- 切点**(Pointcut): **筛选出的链接点,如果说通知定义了切面的动作或者执行时机的话,切点则定义了执行的地点,切入点表达式如何和连接点匹配是AOP的核心
- 引入**(Introduction) 😗* 在不改变一个现有类的代码的情况下,为该类添加熟悉和方法
- 目标对象**(Target Object) 😗*被一个或者多个切面通知的对象,也被称为adviced被通知对象
- AOP代理**(AOP Proxy) 😗*用来实现切面契约,如通知方法执行等
- 织入**(Weaving) 😗*把切面连接到其它的应用程序类型或者对象上,并创建一个被通知的对象
3.实战
- 添加aop依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.1.6.RELEASE</version>
</dependency>
- 创建LogAspect 日志记录类
@Aspect
@Component
public class LogAspect {
@Pointcut("execution(public * com.yang.respringboot.re.controller.*.*(..))")
public void webLog(){}
@Before("webLog()")
public void deBefore(JoinPoint joinPoint) throws Throwable {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
System.out.println("URL : " + request.getRequestURL().toString());
System.out.println("HTTP_METHOD : " + request.getMethod());
System.out.println("IP : " + request.getRemoteAddr());
System.out.println("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
System.out.println("ARGS : " + Arrays.toString(joinPoint.getArgs()));
}
@AfterReturning(returning = "ret", pointcut = "webLog()")
public void doAfterReturning(Object ret) throws Throwable {
System.out.println("方法的返回值 : " + ret);
}
@AfterThrowing("webLog()")
public void throwss(JoinPoint jp){
System.out.println("方法异常时执行.....");
}
@After("webLog()")
public void after(JoinPoint jp){
System.out.println("方法最后执行.....");
}
@Around("webLog()")
public Object arround(ProceedingJoinPoint pjp) {
System.out.println("方法环绕start.....");
try {
Object o = pjp.proceed();
System.out.println("方法环绕proceed,结果是 :" + o);
return o;
} catch (Throwable e) {
e.printStackTrace();
return null;
}
}
}
- Controller类
@RestController
public class AopController {
@RequestMapping("/first")
public Object first() {
return "first controller";
}
@RequestMapping("/doError")
public Object error() {
return 1 / 0;
}
}
- 运行结果
URL : http://localhost:8085/first
HTTP_METHOD : GET
IP : 127.0.0.1
CLASS_METHOD : com.yang.respringboot.re.controller.AopController.first
ARGS : []
方法的返回值 : first controller
方法最后执行.....
方法环绕proceed,结果是 :first controller
//doError
方法环绕start.....
URL : http://localhost:8085/doError
HTTP_METHOD : GET
IP : 127.0.0.1
CLASS_METHOD : com.yang.respringboot.re.controller.AopController.error
ARGS : []
方法异常时执行.....
方法最后执行.....
2.真正的实战
1.我们来看一下ruoyi的日志功能
1.定义一个日志注解
@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log
{
public String title() default "";
public BusinessType businessType() default BusinessType.OTHER;
public OperatorType operatorType() default OperatorType.MANAGE;
public boolean isSaveRequestData() default true;
public boolean isSaveResponseData() default true;
}
2.定义操作类型enum类
- 业务操作类型
public enum BusinessType
{
OTHER,
INSERT,
UPDATE,
DELETE,
GRANT,
EXPORT,
IMPORT,
FORCE,
GENCODE,
CLEAN,
}
- 操作人 OperatorType
public enum OperatorType
{
OTHER,
MANAGE,
MOBILE
}
3.定义切面类
@Aspect
@Component
public class LogAspect
{
private static final Logger log = LoggerFactory.getLogger(LogAspect.class);
@AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult)
{
handleLog(joinPoint, controllerLog, null, jsonResult);
}
@AfterThrowing(value = "@annotation(controllerLog)", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e)
{
handleLog(joinPoint, controllerLog, e, null);
}
protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult)
{
try
{
LoginUser loginUser = SecurityUtils.getLoginUser();
SysOperLog operLog = new SysOperLog();
operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
operLog.setOperIp(ip);
operLog.setOperUrl(ServletUtils.getRequest().getRequestURI());
if (loginUser != null)
{
operLog.setOperName(loginUser.getUsername());
}
if (e != null)
{
operLog.setStatus(BusinessStatus.FAIL.ordinal());
operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
}
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
operLog.setMethod(className + "." + methodName + "()");
operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult);
AsyncManager.me().execute(AsyncFactory.recordOper(operLog));
}
catch (Exception exp)
{
log.error("==前置通知异常==");
log.error("异常信息:{}", exp.getMessage());
exp.printStackTrace();
}
}
public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperLog operLog, Object jsonResult) throws Exception
{
operLog.setBusinessType(log.businessType().ordinal());
operLog.setTitle(log.title());
operLog.setOperatorType(log.operatorType().ordinal());
if (log.isSaveRequestData())
{
setRequestValue(joinPoint, operLog);
}
if (log.isSaveResponseData() && StringUtils.isNotNull(jsonResult))
{
operLog.setJsonResult(StringUtils.substring(JSON.toJSONString(jsonResult), 0, 2000));
}
}
private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog) throws Exception
{
String requestMethod = operLog.getRequestMethod();
if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod))
{
String params = argsArrayToString(joinPoint.getArgs());
operLog.setOperParam(StringUtils.substring(params, 0, 2000));
}
else
{
Map<?, ?> paramsMap = (Map<?, ?>) ServletUtils.getRequest().getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
operLog.setOperParam(StringUtils.substring(paramsMap.toString(), 0, 2000));
}
}
private String argsArrayToString(Object[] paramsArray)
{
String params = "";
if (paramsArray != null && paramsArray.length > 0)
{
for (Object o : paramsArray)
{
if (StringUtils.isNotNull(o) && !isFilterObject(o))
{
try
{
Object jsonObj = JSON.toJSON(o);
params += jsonObj.toString() + " ";
}
catch (Exception e)
{
}
}
}
}
return params.trim();
}
@SuppressWarnings("rawtypes")
public boolean isFilterObject(final Object o)
{
Class<?> clazz = o.getClass();
if (clazz.isArray())
{
return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
}
else if (Collection.class.isAssignableFrom(clazz))
{
Collection collection = (Collection) o;
for (Object value : collection)
{
return value instanceof MultipartFile;
}
}
else if (Map.class.isAssignableFrom(clazz))
{
Map map = (Map) o;
for (Object value : map.entrySet())
{
Map.Entry entry = (Map.Entry) value;
return entry.getValue() instanceof MultipartFile;
}
}
return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
|| o instanceof BindingResult;
}
}