本文实现的是使用自定义注解作为切入点。

1、创建springboot工程,引入依赖

本次任务实例主要引入以下两个依赖即可。

 <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency></dependencies>

2、自定义注解 WebLog

package com.qqxhb.mybatis.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/*** Web 日志注解**/
//运行时使用
@Retention(RetentionPolicy.RUNTIME)
//注解用于方法
@Target({ ElementType.METHOD })
//注解包含在JavaDoc中
@Documented
public @interface WebLog {String value() default "";
}

知识补充

  • Retention注解
    Reteniton的作用是定义被它所注解的注解保留多久,一共有三种策略,定义在RetentionPolicy枚举中。
public enum RetentionPolicy {/***被编译器忽略*/SOURCE,/*** 注解将会被保留在Class文件中,但在运行时并不会被VM保留。这是默认行为,所有没有      * 用Retention注解的注解,都会采用这种策略。*/CLASS,/*** 保留至运行时。所以我们可以通过反射去获取注解信息*/RUNTIME
}
  • Target注解
    说明了Annotation所修饰的对象范围,常用的是TYPE、FIELD、METHOD,具体策略在枚举ElementType中定义。
public enum ElementType {/** 类, 接口 (包括注释类型), 或 枚举 声明 */TYPE,/** 字段声明(包括枚举常量) */FIELD,/** 方法声明(Method declaration) */METHOD,/** 正式的参数声明 */PARAMETER,/** 构造函数声明 */CONSTRUCTOR,/** 局部变量声明 */LOCAL_VARIABLE,/** 注释类型声明 */ANNOTATION_TYPE,/** 包声明 */PACKAGE,/*** 类型参数声明** @since 1.8*/TYPE_PARAMETER,/*** 使用的类型** @since 1.8*/TYPE_USE
}

3、自定义切面

package com.qqxhb.mybatis.aspect;import java.lang.reflect.Method;
import java.util.Arrays;import javax.servlet.http.HttpServletRequest;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import com.qqxhb.mybatis.annotation.WebLog;/*** web日志切面**/
//标识这是一个切面
@Aspect
//交给spring容器管理
@Component
public class WebLogAspect {private final static Logger logger = LoggerFactory.getLogger(WebLogAspect.class);// 以自定义 @WebLog 注解为切点@Pointcut("@annotation(com.qqxhb.mybatis.annotation.WebLog)")public void webLog() {}/*** 切点之前* * @param joinPoint* @throws Throwable*/@Before("webLog()")public void before(JoinPoint joinPoint) throws Throwable {// 得到 HttpServletRequestServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = attributes.getRequest();logger.info("============ before ==========");// 获取WebLog注解信息String info = getWebLogInfo(joinPoint);logger.info("Point Info    : {}", info);// 请求地址URLlogger.info("URL   : {}", request.getRequestURL().toString());// 请求方法logger.info("HTTP Method : {}", request.getMethod());// 具体切入执行方法logger.info("Class Method : {}.{}", joinPoint.getSignature().getDeclaringTypeName(),joinPoint.getSignature().getName());// 请求IPlogger.info("IP  : {}", request.getRemoteAddr());// 打印描述信息// 请求参数logger.info("Input Parameter : {}", Arrays.asList(joinPoint.getArgs()));}/*** 切点之后* * @throws Throwable*/@After("webLog()")public void after() throws Throwable {logger.info("============ after ==========");}/*** 切点返回内容后* * @throws Throwable*/@AfterReturning("webLog()")public void afterReturning() throws Throwable {logger.info("============ afterReturning ==========");}/*** 切点抛出异常后* * @throws Throwable*/@AfterThrowing("webLog()")public void afterThrowing() throws Throwable {logger.info("============ afterThrowing ==========");}/*** 环绕* * @param proceedingJoinPoint* @return* @throws Throwable*/@Around("webLog()")public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {logger.info("============ doAround ==========");long startTime = System.currentTimeMillis();Object result = proceedingJoinPoint.proceed();// 打印出参logger.info("Output Parameter : {}", result);// 执行时间logger.info("Execution Time : {} ms", System.currentTimeMillis() - startTime);return result;}/*** 获取web日志注解信息* * @param joinPoint* @return* @throws Exception*/public String getWebLogInfo(JoinPoint joinPoint) throws Exception {// 获取切入点的目标类String targetName = joinPoint.getTarget().getClass().getName();Class<?> targetClass = Class.forName(targetName);// 获取切入方法名String methodName = joinPoint.getSignature().getName();// 获取切入方法参数Object[] arguments = joinPoint.getArgs();// 获取目标类的所有方法Method[] methods = targetClass.getMethods();for (Method method : methods) {// 方法名相同、包含目标注解、方法参数个数相同(避免有重载)if (method.getName().equals(methodName) && method.isAnnotationPresent(WebLog.class)&& method.getParameterTypes().length == arguments.length) {return method.getAnnotation(WebLog.class).value();}}return "";}
}

知识点补充

  • 指定切面有效环境
    可以在切面类上加Profile注解指定运行环境。
    spring在确定那个profile处于激活状态的时,需要依赖两个独立的属性:spring.profiles.active和spring.profile.default。如果设置了spring.profiles.active属性,那么它的值就会用来确定那个profile是激活的。如果没有设置spring.profiles.active属性的话,那spring将会查找spring.profiles.default的值。
//指定在开发、测试环境使用
@Profile({ "dev", "test" })//此时需要设置spring.profiles.active的值为dev或者test 切面才会生效。
  • 切点指示符
    切点指示符是切点定义的关键字,切点表达式以切点指示符开始。开发人员使切点指示符来告诉切点将要匹配什么,有以下9种切点指示符:execution、within、this、target、args、@target、@args、@within、@annotation。
    execution
    execution是一种使用频率比较高比较主要的一种切点指示符,用来匹配方法签名,方法签名使用全限定名,包括访问修饰符(public/private/protected)、返回类型,包名、类名、方法名、参数,其中返回类型,包名,类名,方法,参数是必须的,如下面代码片段所示:
@Pointcut("execution(public String org.baeldung.dao.FooDao.findById(Long))")

上面的代码片段里的表达式精确地匹配到FooDao类里的findById(Long)方法,但是这看起来不是很灵活。假设我们要匹配FooDao类的所有方法,这些方法可能会有不同的方法名,不同的返回值,不同的参数列表,为了达到这种效果,我们可以使用通配符。如下代码片段所示:

@Pointcut("execution(* org.baeldung.dao.FooDao.*(..))")

第一个通配符匹配所有返回值类型,第二个匹配这个类里的所有方法,()括号表示参数列表,括号里的用两个点号表示匹配任意个参数,包括0个
within
使用within切点批示符可以达到上面例子一样的效果,within用来限定连接点属于某个确定类型的类。如下面代码的效果与上面的例子是一样的:

@Pointcut("within(org.baeldung.dao.FooDao)")

我们也可以使用within指示符来匹配某个包下面所有类的方法(包括子包下面的所有类方法),如下代码所示:

@Pointcut("within(org.baeldung..*)")

this 和 target
this用来匹配的连接点所属的对象引用是某个特定类型的实例,target用来匹配的连接点所属目标对象必须是指定类型的实例;那么这两个有什么区别呢?原来AspectJ在实现代理时有两种方式:
1、如果当前对象引用的类型没有实现自接口时,spring aop使用生成一个基于CGLIB的代理类实现切面编程
2、如果当前对象引用实现了某个接口时,Spring aop使用JDK的动态代理机制来实现切面编程
this指示符就是用来匹配基于CGLIB的代理类,通俗的来讲就是,如果当前要代理的类对象没有实现某个接口的话,则使用this;target指示符用于基于JDK动态代理的代理类,通俗的来讲就是如果当前要代理的目标对象有实现了某个接口的话,则使用target.:

public class FooDao implements BarDao {...
}

比如在上面这段代码示例中,spring aop将使用jdk的动态代理来实现切面编程,在编写匹配这类型的目标对象的连接点表达式时要使用target指示符, 如下所示:

@Pointcut("target(org.baeldung.dao.BarDao)")

如果FooDao类没有实现任何接口,或者在spring aop配置属性:proxyTargetClass设为true时,Spring Aop会使用基于CGLIB的动态字节码技为目标对象生成一个子类将为代理类,这时应该使用this指示器:

@Pointcut("this(org.baeldung.dao.FooDao)")

@Target
这个指示器匹配指定连接点,这个连接点所属的目标对象的类有一个指定的注解:

@Pointcut("@target(org.springframework.stereotype.Repository)")

@args
这个指示符是用来匹配连接点的参数的,@args指出连接点在运行时传过来的参数的类必须要有指定的注解,假设我们希望切入所有在运行时接受实@Entity注解的bean对象的方法:

@Pointcut("@args(org.baeldung.aop.annotations.Entity)")

@within
这个指示器,指定匹配必须包括某个注解的的类里的所有连接点:

@Pointcut("@within(org.springframework.stereotype.Repository)")

上面的切点跟以下这个切点是等效的:

@Pointcut("within(@org.springframework.stereotype.Repository *)")

@annotation
这个指示器匹配那些有指定注解的连接点,比如,我们可以新建一个这样的注解@Loggable:

@Pointcut("@annotation(org.baeldung.aop.annotations.Loggable)")

切点表达式 可以使用&&、||、!、三种运算符来组合切点表达式,表示与或非的关系。

@Pointcut("@target(org.springframework.stereotype.Repository)")
public void repositoryMethods() {}@Pointcut("execution(* *..create*(Long,..))")
public void firstLongParamMethods() {}@Pointcut("repositoryMethods() && firstLongParamMethods()")
public void entityCreationMethods() {}

4、使用注解切面

在需要使用该切面的方法上添加自定义的注解即可。

/*** 查询新闻* * @param title* @return*/@GetMapping@WebLog("查询新闻列表接口")public List<News> getNews(String title, @RequestParam(defaultValue = "1") int pageIndex,@RequestParam(defaultValue = "5") int pageSize) {return newsServiceImpl.selectNews(title, pageIndex, pageSize);}

5、访问接口测试效果


源码地址:https://github.com/qqxhb/springboot-mybatis-demo
中的springboot-aspect

Springboot 自定义注解、切面相关推荐

  1. @retention注解作用_分分钟带你玩转SpringBoot自定义注解

    在工作中,我们有时候需要将一些公共的功能封装,比如操作日志的存储,防重复提交等等.这些功能有些接口会用到,为了便于其他接口和方法的使用,做成自定义注解,侵入性更低一点.别人用的话直接注解就好.下面就来 ...

  2. 元旦加班写SpringBoot自定义注解

    写在前面 这个点我们公司的人走的已经差不多了,原因很简单呀,明天元旦嘛,放假前可是不加班的,很nice,实习生的我,今天给大家分享一篇springboot自定义注解的技术文章. 很牛逼的注解 开发过程 ...

  3. Springboot自定义注解实现用户登录状态校验(一)

    Springboot自定义注解实现用户登录状态校验(一) 拦截器方式 定义注解类 import java.lang.annotation.*;/*** @author:小飞猪* @date:2020/ ...

  4. springboot 自定义注解拦截器

    springboot 自定义注解拦截器 最近在工作中,发现自定义注解拦截使用起来特别方便,现在来写出来给大家看看 环境springboot 首先写一个自定义注解 package com.study.c ...

  5. springboot+自定义注解实现灵活的切面配置

    利用aop我们可以实现业务代码与系统级服务例如日志记录.事务及安全相关业务的解耦,使我们的业务代码更加干净整洁. 最近在做数据权限方面的东西,考虑使用切面对用户访问进行拦截,进而确认用户是否对当前数据 ...

  6. SpringBoot 自定义注解+AOP+Redis 防止接口重复提交表单数据

    SpringBoot结合Redis处理重复提交 数据重复提交导致多次请求服务.入库,产生脏数据.冗余数据等情况.禁止重复提交使我们保证数据准确性及安全性的必要操作. 实际上,造成这种情况的场景不少: ...

  7. SpringBoot自定义注解接收json参数

    SpringBoot如果接受json参数的话需要定义实体类然后使用@RequestBody注解,但是如果每个接口都创建一个实体类的话太麻烦,因此可以使用自定义注解的方法接收.从网上发现了这篇博客,解决 ...

  8. 自定义注解+切面处理+全局异常处理

    可以实现接口调用验证签名.参数判断等扩展. 1.注解方法 /*** 自定义注解签名参数验证* xuxx*/@Retention(RetentionPolicy.RUNTIME) @Target(Ele ...

  9. SpringBoot自定义注解+AOP+redis实现防接口幂等性重复提交,从概念到实战

    一.前言 在面试中,经常会有一道经典面试题,那就是:怎么防止接口重复提交? 小编也是背过的,好几种方式,但是一直没有实战过,做多了管理系统,发现这个事情真的没有过多的重视. 最近在测试过程中,发现了多 ...

最新文章

  1. Splunk 会议回想: 大数据的关键是机器学习
  2. ITIL应用系列之服务台
  3. 又见灵异事件,li中的span右浮动遇到的问题
  4. 基于SSM实现公交路线管理系统
  5. magic_quotes_gpc和magic_quotes_runtime的区别和用法详解
  6. Shell(6)——array的删改unset
  7. Fastboot Normal + Recovery
  8. mongodb 启动_精心总结--mongodb分片集群启动与关闭
  9. Eclipse公共许可证
  10. Apple System: Error: ENFILE: file table overflow
  11. vs2008 jQuery 智能提示失败可能是Jquery版本问题
  12. android的opengl教程,android opengl 教程
  13. TCPClient代码
  14. 关于 移动 电信 联通 运营商数据抓取 通话记录 京东淘宝学信网数据抓取 失信报告,网贷黑名单,央行征信报告数据抓取
  15. 春季养生食谱 灵芝孢子粉牛排汤补中益气
  16. web前端学习-第二天
  17. android2.3.5中阿拉伯文字符显示顺序不是从右至左显示
  18. java opencv 更换图片背景色(基于ROI)
  19. 视频 | 20分钟出结果!有了这个,在家也能做新冠病毒检测
  20. 面向智慧教室物联网关键技术的研究与运用(待完成)

热门文章

  1. VMware SDS之五 - VMware刚公布第四代VSAN - 超融合软件VSAN 6.2新增了哪九大特性?
  2. 视频怎样转换为GIF表情包
  3. flutter usb串口_在Windows上搭建Flutter开发环境
  4. 在ubuntu中使用jupyter
  5. 史上最全 App功能测试点分析
  6. 【友盟+】O2O报告:租用车类App最受上海网民追捧!
  7. 基于 SpringBoot+Vue+Java 的智慧外贸系统(数据库,附源码,教程)
  8. C++多线程-无锁链表
  9. 其他消息中间件及场景应用(下1)
  10. python列表去除none_从Python列表中删除None