Aop的基本概念和用Aop实现日志管理模块

 

一.什么是AOP

  官方介绍:面向切面编程(AOP,Aspect Oriented Programming),通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
  举例子:(来自:Spring 之 AOP): 

    要理解切面编程,就需要先理解什么是切面。用刀把一个西瓜分成两瓣,切开的切口就是切面;炒菜,锅与炉子共同来完成炒菜,锅与炉子就是切面。web层级设计中,web层->网关层->服务层->数据层,每一层之间也是一个切面。编程中,对象与对象之间,方法与方法之间,模块与模块之间都是一个个切面。
  

  我们一般做活动的时候,一般对每一个接口都会做活动的有效性校验(是否开始、是否结束等等)、以及这个接口是不是需要用户登录。

按照正常的逻辑,我们可以这么做。

 这有个问题就是,有多少接口,就要多少次代码copy。对于一个“懒人”,这是不可容忍的。好,提出一个公共方法,每个接口都来调用这个接口。这里有点切面的味道了。

 同样有个问题,我虽然不用每次都copy代码了,但是,每个接口总得要调用这个方法吧。于是就有了切面的概念,我将方法注入到接口调用的某个地方(切点)。

 这样接口只需要关心具体的业务,而不需要关注其他非该接口关注的逻辑或处理。

红框处,就是面向切面编程

二.AOP的相关概念
 1.Aspect(切面):表示切面。切入业务流程的一个独立模块。在实际开发中通常是一个存放共有功能实现的标准Java类。当Java类使用了@Aspect注解修饰时,就能被AOP容器识别为切面。
  2.Joint point(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。一个类的所有方法前、后、抛出异常时等都是连接点。
  3.Pointcut(切点):表示切入点。用于定义通知应该切入到哪些连接点上,不同的通知通常需要切入到不同的连接点上。切入点就是来定义哪些类里面的哪些方法会得到通知。
  4.Advice(通知):表示通知。是切面的具体实现方法。可分为前置通知(Before)、后置通知(AfterReturning)、异常通知(AfterThrowing)、最终通知(After)和环绕通知(Around)五种。实现方法具体属于哪类通知,是在配置文件和注解中指定的。在实际开发中通常是切面类中的一个方法,具体属于哪类通知,通过方法上的注解区分。
  5.Target(目标对象):表示目标对象。那些即将切入切面的对象,也就是那些被通知的对象
     6.Proxy:表示代理对象。将通知应用到目标对象之后被动态创建的对象。可以简单地理解为,代理对象为目标对象的业务逻辑功能加上被切入的切面所形成的对象。
  7.Weaving:表示切入,也称为织入。将切面应用到目标对象从而创建一个新的代理对象的过程。这个过程可以发生在编译期、类装载期及运行期。
举一个容易理解的例子:
    

   从前有一个叫爪哇的小县城, 在一个月黑风高的晚上, 这个县城中发生了命案. 作案的凶手十分狡猾, 现场没有留下什么有价值的线索. 不过万幸的是, 刚从隔壁回来的老王恰好在这时候无意中发现了凶手行凶的过程, 但是由于天色已晚, 加上凶手蒙着面, 老王并没有看清凶手的面目, 只知道凶手是个男性, 身高约七尺五寸. 爪哇县的县令根据老王的描述, 对守门的士兵下命令说: 凡是发现有身高七尺五寸的男性, 都要抓过来审问. 士兵当然不敢违背县令的命令, 只好把进出城的所有符合条件的人都抓了起来.

   在 Spring AOP 中 Joint point 指代的是所有方法的执行点, 而 point cut 是一个描述信息, 它修饰的是 Joint point, 通过 point cut, 我们就可以确定哪些 Joint point 可以被织入 Advice. 对应到我们在上面举的例子, 我们可以做一个简单的类比, Joint point 就相当于 爪哇的小县城里的百姓,pointcut 就相当于 老王所做的指控, 即凶手是个男性, 身高约七尺五寸, 而 Advice 则是施加在符合老王所描述的嫌疑人的动作: 抓过来审问.
  

  Joint point : 爪哇的小县城里的百姓: 因为根据定义, Joint point 是所有可能被织入 Advice 的候选的点, 在 Spring AOP中, 则可以认为所有方法执行点都是 Joint point. 而在我们上面的例子中, 命案发生在小县城中, 按理说在此县城中的所有人都有可能是嫌疑人.

  Pointcut :男性, 身高约七尺五寸: 我们知道, 所有的方法(joint point) 都可以织入 Advice, 但是我们并不希望在所有方法上都织入 Advice, 而 Pointcut 的作用就是提供一组规则来匹配joinpoint, 给满足规则的 joinpoint 添加 Advice. 同理, 对于县令来说, 他再昏庸, 也知道不能把县城中的所有百姓都抓起来审问, 而是根据凶手是个男性, 身高约七尺五寸, 把符合条件的人抓起来. 在这里 凶手是个男性, 身高约七尺五寸 就是一个修饰谓语, 它限定了凶手的范围, 满足此修饰规则的百姓都是嫌疑人, 都需要抓起来审问.

  Advice :抓过来审问, Advice 是一个动作, 即一段 Java 代码, 这段 Java 代码是作用于 point cut 所限定的那些 Joint point 上的. 同理, 对比到我们的例子中, 抓过来审问 这个动作就是对作用于那些满足 男性, 身高约七尺五寸 的爪哇的小县城里的百姓.

  Aspect::Aspect 是 point cut 与 Advice 的组合, 因此在这里我们就可以类比: “根据老王的线索, 凡是发现有身高七尺五寸的男性, 都要抓过来审问” 这一整个动作可以被认为是一个 Aspect.

 

三.Aop实现日志管理模块

  1.准备:日志表

    

   2.定义切入點(Pointcut ),该地方定义了控制层和业务层,

  

  @Retention(RetentionPolicy.RUNTIME)
  @Target({ElementType.PARAMETER, ElementType.METHOD})
  @Documented
  public @interface SystemControllerLog {
  String module() default "" ;//系统功能
  String actionType() default "" ;//操作类型 0登录 1增加 2编辑 3删除 4其它
  int categoryId() default 2;//分类Id 1-登陆2-访问3-操作4-异常
  }
  
  @Retention(RetentionPolicy.RUNTIME)
  @Target({ElementType.PARAMETER, ElementType.METHOD})
  @Documented
  public @interface SystemServiceLog {
  String module() default "" ;//系统功能
  String actionType() default "" ;//操作类型 0登录 1增加 2编辑 3删除 4其它
  int categoryId() default 2;//分类Id 1-登陆2-访问3-操作4-异常
  }

 3.切面类

  SignAop类使用了@Aspect注解,则该类可以被AOP容器识别为切面。
@Aspect
@Component
@Slf4j
public class LogAop {


@Autowired
private SnowFlake snowFlake;
@Autowired
private SchoolLogMapper schoolLogMapper;
@Autowired
private SchoolHumanMapper schoolHumanMapper;
    //Pointcut声明一个切入点,
    //注:作为切入点签名的方法必须返回void类型
@Pointcut("@annotation( com.woke.project.camp.entity.SystemControllerLog) ||" +
" @annotation( com.woke.project.camp.entity.SystemServiceLog)")
public void logPoinCut() {

}

// 切面 配置通知 后置通知
@AfterReturning(value = "logPoinCut()", returning = "res")
@Transactional
public void saveSysLog(JoinPoint joinPoint, Object res) throws Exception {
log.info(">>>>>>>>>>>>日志AOP开始<<<<<<<<<<<<");
// 保存日志
SchoolLog schoollog = new SchoolLog();
SchoolHuman schoolHuman=new SchoolHuman();
// 从切面织入点处通过反射机制获取织入点处的方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 获取切入点所在的方法
Method method = signature.getMethod();
// 获取操作
SystemControllerLog systemControllerLog = method.getAnnotation(SystemControllerLog.class);
SystemControllerLog systemServiceLog = method.getAnnotation(SystemControllerLog.class);
if (systemControllerLog != null) {
String actionType = systemControllerLog.actionType();
String module = systemControllerLog.module();
schoollog.setOperatetype(actionType);//操作类型 0登录 1增加 2编辑 3删除 4其它
schoollog.setModulename(module);//系统功能
schoollog.setCategoryid(systemControllerLog.categoryId());//分类 1-登陆2-访问3-操作4-异常
}
if (systemServiceLog != null) {
String actionType = systemServiceLog.actionType();
String module = systemServiceLog.module();
schoollog.setOperatetype(actionType);//操作类型 0登录 1增加 2编辑 3删除 4其它
schoollog.setModulename(module);//备注
schoollog.setCategoryid(systemServiceLog.categoryId());//分类 1-登陆2-访问3-操作4-异常
}
Map<String, Object> map = new HashMap<>();
for (int j = 0; j < ((MethodSignature) joinPoint.getSignature()).getParameterNames().length; j++) {
for (int i = 0; i < joinPoint.getArgs().length; i++) {
map.put((((MethodSignature) joinPoint.getSignature()).getParameterNames())[j], (joinPoint.getArgs())[i]);
}
}

HttpServletRequest request = (HttpServletRequest) map.get("request");
String humanId = request.getHeader("humanId");

String ip = getIpAddr(request);
String urlAddress = request.getHeader("urlAddress");
if (StringUtils.isNotEmpty(urlAddress) && urlAddress.contains("?")) {
urlAddress = urlAddress.substring(0, urlAddress.lastIndexOf("?"));
}
String path = request.getRequestURI();
// log.info("获得前端参数:userid={},urlAddress={},path={}", userid, urlAddress, path);
if ((path.contains("/ymt/login"))) {
String account = request.getParameter("account");
schoolHuman = schoolHumanMapper.getAccountM(account);
} else {
//忘记密码
if (path.contains("/ymt/updatePassword")) {
String account = request.getParameter("account");
schoolHuman = schoolHumanMapper.getAccountM(account);
} else {
// swagger 调用时它是空的 正常情况下权限接口会挡住
if (StringUtils.isEmpty(humanId)) {
schoolHuman = new SchoolHuman();
} else {
schoolHuman = schoolHumanMapper.selectByPrimaryKeyM(Long.valueOf(humanId));
}
}
}
// 获取浏览器信息
Browser browser = UserAgent.parseUserAgentString(request.getHeader("User-Agent")).getBrowser();
// 获取浏览器版本号
Version version = browser.getVersion(request.getHeader("User-Agent"));
String info = browser.getName() + "-" + version.getVersion();
try {
schoollog.setBrowser(info);
schoollog.setLogid(String.valueOf(snowFlake.nextId()));//id
schoollog.setIpaddress(ip);
schoollog.setOperatetime(new Date());
if (!ObjectUtils.isEmpty(schoolHuman)) {
schoollog.setOperateuserid(String.valueOf(schoolHuman.getHumanId()));//用户id
schoollog.setOperateaccount(schoolHuman.getUsername());//账号
schoollog.setOperateusername(schoolHuman.getHumanName());//用户姓名
schoollog.setIpaddress(ip);
schoollog.setIpaddressname(urlAddress);
if (((Result) res).getCode() == 200) {
schoollog.setExecuteresult(0);
schoollog.setDescription("成功");
} else {
schoollog.setExecuteresult(1);

}
// 调用service保存SysLog实体类到数据库
int i = schoolLogMapper.insertSelective(schoollog);
log.info(">>>>>>>>>>>>日志AOP结束<<<<<<<<<<<<");
}

} catch (Exception e) {
schoollog.setBrowser(info);
schoollog.setLogid(String.valueOf(snowFlake.nextId()));//id
schoollog.setIpaddress(ip);
schoollog.setOperatetime(new Date());//操作时间
if (!ObjectUtils.isEmpty(schoolHuman)) {
schoollog.setOperateuserid(String.valueOf(schoolHuman.getHumanId()));//用户id
schoollog.setOperateaccount(schoolHuman.getUsername());//账号
schoollog.setOperateusername(schoolHuman.getHumanName());//用户姓名
}
schoollog.setIpaddress(ip);
schoollog.setIpaddressname(urlAddress);
schoollog.setExecuteresult(1);
schoollog.setDescription("异常");
int i = schoolLogMapper.insertSelective(schoollog);
log.info(">>>>>>>>>>>>日志AOP异常<<<<<<<<<<<<");
}
}

private String getIpAddr(HttpServletRequest request) {

String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
}
4.查询日志

 5,给需要在系统管理模块展示的操作的类上加注解


@SystemControllerLog(module="停用帐号",actionType="2",categoryId=3)

   注意:一定要加

 6.结果

 

 
原文地址:https://www.cnblogs.com/yangxiaoli/p/12667466.html