关于java:Spring中AOP的理解

40次阅读

共计 5335 个字符,预计需要花费 14 分钟才能阅读完成。

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);
 }

正文完
 0