文章目录

  • 一、前言
  • 二、正文开始
  • 1、项目结构:最简单的SpringBoot脚手架搭建的web项目
  • 2、自定义注解
  • 3.自定义注解切面类
  • 4、Controller层
  • 5、基本概念简述
  • 三、看运行结果,说结论
  • 1、正常执行
  • 2、产生异常时


一、前言

好久不见,大家还好吗?

最近开始学习若依框架:一个基于SpringBoot的后台管理系统,作为快速开发框架,网上评价不错,有时间的同学可以看看代码学习一下。

一开始打算以若依框架中关于Controller层的Log注解作为模板进行自定义注解的介绍,因为在切面类中涉及到了一些基础框架层的封装类及方法,如果深入去讲摊子会铺的很大,就脱离了自定义注解的主题,因此本文仅仅以最简单的程序来演示一下SpringBoot自定义注解的实现。仅做演示

二、正文开始

1、项目结构:最简单的SpringBoot脚手架搭建的web项目

springboot自定义日志框架 springboot自定义日志注解_System


pom依赖:

<dependencies>
        <!-- SpringBoot Web容器 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- SpringBoot 拦截器 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
    </dependencies>

application.yml:

server:
  port: 12345

2、自定义注解

package com.example.demoannotation.annotation;

import java.lang.annotation.*;

/**
 * @Author: zongshaofeng
 * @Description: 自定义的操作日志注解
 * @Date:Create:in 2021/10/22 8:25
 * @Modified By:
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperationLog {
	/**
	 * 模块名称
	 */
	String title() default "";
	
	/**
	 * 为了简单,省略后续内容
	 */
}

3.自定义注解切面类

package com.example.demoannotation.aspectj;

import com.example.demoannotation.annotation.OperationLog;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

/**
 * @Author: zongshaofeng
 * @Description: 操作日志记录的切面类
 * @Date:Create:in 2021/10/22 8:30
 * @Modified By:
 */
@Aspect
@Component
public class OperationLogAspect {
	
	/**
	 * @Author: zongshaofeng
	 * @Description: 返回增强,目标方法正常执行完毕时执行
	 * @Date: 2021/10/22 20:15 
	 * @param: joinPoint
	 * @param: operationLog
	 * @param: returnObj
	 * @return: void
	 * @Version: 1.0
	 */
	@AfterReturning(pointcut = "@annotation(operationLog)",returning = "returnObj")
	public void doAfterReturning(JoinPoint joinPoint, OperationLog operationLog, Object returnObj) {
		System.out.println("===============AfterReturning切入点开始执行......===============");
		Map<String,Object> map=new HashMap<>();
		map.put("title",operationLog.title());
		map.put("returnObj",returnObj);
		map.put("method",joinPoint.getTarget().getClass().getName()+"."+joinPoint.getSignature().getName());
		System.out.println(map);
		System.out.println("===============AfterReturning切入点执行完成。===============");
	}
	
	/**
	 * @Author: zongshaofeng
	 * @Description: 异常抛出增强,目标方法发生异常的时候执行
	 * @Date: 2021/10/22 20:16 
	 * @param: joinPoint
	 * @param: operationLog
	 * @param: e
	 * @return: void
	 * @Version: 1.0
	 */
	@AfterThrowing(pointcut = "@annotation(operationLog)",throwing = "e")
	public void doAfterThrowing(JoinPoint joinPoint, OperationLog operationLog, Exception e){
		System.out.println("===============AfterThrowing切入点开始执行......===============");
		Map<String,Object> map=new HashMap<>();
		map.put("title",operationLog.title());
		map.put("exceptionMsg",e.getMessage());
		map.put("method",joinPoint.getTarget().getClass().getName()+"."+joinPoint.getSignature().getName());
		System.out.println(map);
		System.out.println("===============AfterThrowing切入点执行完成。===============");
	}
	
	/**
	 * @Author: zongshaofeng
	 * @Description: 前置增强:在目标方法执行之前
	 * @Date: 2021/10/22 20:16
	 * @param: joinPoint
	 * @param: operationLog
	 * @return: void
	 * @Version: 1.0
	 */
	@Before(value = "@annotation(operationLog)")
	public void doBefore(JoinPoint joinPoint, OperationLog operationLog){
		System.out.println("===============Before切入点开始执行......===============");
		Map<String,Object> map=new HashMap<>();
		map.put("title",operationLog.title());
		map.put("method",joinPoint.getTarget().getClass().getName()+"."+joinPoint.getSignature().getName());
		System.out.println(map);
		System.out.println("===============Before切入点执行完成。===============");
	}
	
	/**
	 * @Author: zongshaofeng
	 * @Description: 后置增强,不管是抛出异常或者正常退出都会执行
	 * @Date: 2021/10/22 20:17 
	 * @param: joinPoint
	 * @param: operationLog
	 * @return: void
	 * @Version: 1.0
	 */
	@After(value = "@annotation(operationLog)")
	public void doAfter(JoinPoint joinPoint, OperationLog operationLog){
		System.out.println("===============After切入点开始执行......===============");
		Map<String,Object> map=new HashMap<>();
		map.put("title",operationLog.title());
		map.put("method",joinPoint.getTarget().getClass().getName()+"."+joinPoint.getSignature().getName());
		System.out.println(map);
		System.out.println("===============After切入点执行完成。===============");
	}
	
	/**
	 * @Author: zongshaofeng
	 * @Description:环绕通知:目标方法执行前后进行一些操作
	 * @Date: 2021/10/22 20:17
	 * @param: proceedingJoinPoint
	 * @param: operationLog
	 * @return: java.lang.Object
	 * @Version: 1.0
	 */
	@Around(value = "@annotation(operationLog)")
	public Object doAround(ProceedingJoinPoint proceedingJoinPoint, OperationLog operationLog) throws Throwable {
		System.out.println("===============Around切入点开始执行......===============");
		Map<String,Object> map=new HashMap<>();
		map.put("title",operationLog.title());
		map.put("method",proceedingJoinPoint.getTarget().getClass().getName()+"."+proceedingJoinPoint.getSignature().getName());
		System.out.println(map);
		System.out.println("---------Around中前置通知执行完成-------");
		Object proceed = proceedingJoinPoint.proceed();
		System.out.println("---------Around中目标方法返回结果:"+proceed);
		System.out.println("---------Around中目标方法执行完成-------");
		System.out.println("这是后置通知。。。");
		System.out.println("---------Around中后置通知执行完成-------");
		System.out.println("===============Around切入点执行完成。===============");
		return proceed;
	}
}

4、Controller层

package com.example.demoannotation.controller;

import com.example.demoannotation.annotation.OperationLog;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.Mapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Author: zongshaofeng
 * @Description:
 * @Date:Create:in 2021/10/22 8:16
 * @Modified By:
 */
@RestController
@RequestMapping("/demo")
public class SayHelloController {
	
	
	@GetMapping("/sayHello")
	@OperationLog(title = "自定义日志注解测试-说你好模块")
	public String sayHello(String name) {
		return "Hello " + name;
	}
}

5、基本概念简述

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

在Spring AOP中业务逻辑仅仅只关注业务本身,将日志记录、性能统计、安全控制、事务处理、异常处理等代码从业务逻辑代码中划分出来,从而在改变这些行为的时候不影响业务逻辑的代码。

AOP 涉及的相关注解主要有以下几个:

注解

作用

@Aspect

定义切面类

@Pointcut

Pointcut是织入Advice的触发条件。每个Pointcut的定义包括2部分,一是表达式,二是方法签名。方法签名必须是public及void型。可以将Pointcut中的方法看作是一个被Advice引用的助记符,因为表达式不直观,因此我们可以通过方法签名的方式为此表达式命名。因此Pointcut中的方法只需要方法签名,而不需要在方法体内编写实际代码。

@Around

环绕通知:目标方法执行前后进行一些操作

@Before

前置增强:在目标方法执行之前

@AfterReturning

返回增强,目标方法正常执行完毕时执行

@AfterThrowing

异常抛出增强,目标方法发生异常的时候执行

@After

后置增强,不管是抛出异常或者正常退出都会执行

三、看运行结果,说结论

文字看起来记不住,让我们运行一下,看看这几个增强通知的执行顺序是怎样的,这样在今后的实际开发中才能够得心应手,运用自如。

  • 首先启动项目,我们在Controller中写了一个sayHello方法,并且增加了我们自定义的@OperationLog注解进行了通知增强。
  • 打开浏览器访问说你好(http://localhost:12345/demo/sayHello?name=小宗)
  • 页面返回:

1、正常执行

让我们一起来看一下控制台打印:

springboot自定义日志框架 springboot自定义日志注解_java_02


所以说,整个AOP的增强通知的执行顺序是这样的:

@Around环绕通知中目标方法调用之前的代码—>@Before前置通知中代码—>@Around中执行调用目标方法—>@Around目标方法调用之后的代码—>@After后置通知—>@AfterReturning

2、产生异常时

让我们再来人为制造点麻烦吧:加点byZero异常

springboot自定义日志框架 springboot自定义日志注解_System_03


再来看看执行结果:

springboot自定义日志框架 springboot自定义日志注解_spring boot_04


我不是要强调@AfterThrowing的必然执行,而是要指出经过我们的验证:@After增强通知,无论是正常执行还是出现异常,它都会被执行。

好了,收工。

其实讲的非常简单,其中有很多东西并没有被提及,笔者还是想着能够起到一个抛砖引玉的作用吧。