AOP与SpringBoot使用AOP实例

AOP:Aspect Oriented Programming(面向切面编程、面向方面编程),其实就是面向特定方法编程。

动态代理是面向切面编程最主流的实现。而SpringAOP是Spring框架的高级技术,旨在管理bean对象的过程中,主要通过底层的动态代理机制,对特定的方法进行编程。

使用场景:

        记录操作日志、权限控制、事务管理......

优势:

        代码无入侵、减少重复代码、提高开发效率、维护简单

 AOP的核心概念

JoinPoint(连接点):被拦截的点。Spring中指可以被动态代理拦截目标类的方法。

PointCat(切入点):指要对哪些JointPoint进行拦截,既被拦截的连接点。
Advice(通知):拦截JoinPoint后要做的事。既对切入点增强的内容。
Target(目标): 指代理的目标对象。
Weavìng(植入):指把增强代码应用到目标上,生成代理对象的过程。
Proxy (代理):生成的代理对象
Aspect(切面):切入点和通知的结合。

 AOP 的通知类型

  • @Around:环绕通知,此注解标注的通知方法在目标方法前、后都被执行
  • @Before:前置通知,此注解标注的通知方法在目标方法前被执行
  • @After :后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行
  • @AfterReturning : 返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行
  • @AfterThrowing : 异常后通知,此注解标注的通知方法发生异常后执行

@Around环绕通知需要自己调用 ProceedingJoinPoint.proceed() 来让原始方法执行,其他通知不需要考虑目标方法执行 @Around环绕通知方法的返回值,必须指定为Object,来接收原始方法的返回值

 AOP的通知顺序

当有多个切面的切入点都匹配到了目标方法,目标方法运行时,多个通知方法都会被执行。

1、不同切面类中,默认按照切面类的类名字母排序:

        目标方法前的通知方法:字母排名靠前的先执行

        目标方法后的通知方法:字母排名靠前的后执行

2、用 @Order(数字) 加在切面类上来控制顺序

        目标方法前的通知方法:数字小的先执行

        目标方法后的通知方法:数字小的后执行

 切入点表达式

切入点表达式:描述切入点方法的一种表达式

作用:主要用来决定项目中的哪些方法需要加入通知

常见形式:

        1、execution(……):根据方法的签名来匹配

        2、@annotation(注解名) :根据注解匹配

 切入点表达式-execultion

execution 主要根据方法的返回值、包名、类名、方法名、方法参数等信息来匹配,

语法为:execution(访问修饰符?  返回值  包名.类名.?方法名(方法参数) throws 异常?)

其中带 ? 的表示可以省略的部分

访问修饰符:可省略(比如: public、protected)

包名.类名: 可省略

throws 异常:可省略(注意是方法上声明抛出的异常,不是实际抛出的异常)

 根据业务需要,可以使用 且(&&)、或(||)、非(!) 来组合比较复杂的切入点表达式。

 书写建议:

         所有业务方法名在命名时尽量规范,方便切入点表达式快速匹配。如:查询类方法都是 find 开头,更新类方法都是 update开头。

        描述切入点方法通常基于接口描述,而不是直接描述实现类,增强拓展性。

        在满足业务需要的前提下,尽量缩小切入点的匹配范围。如:包名匹配尽量不使用 ..,使用 * 匹配单个包。       

  切入点表达式-@annotation


 AOP案例

将增、删、改 相关接口的操作日志记录到数据库表中。

日志信息包含:操作人、操作时间、执行方法的全类名、执行方法名、方法运行时参数、返回值、方法执行时长

需要对所有业务类中的增、删、改 方法添加统一功能,使用 AOP 技术最为方便

切入点表达式:利用exclution或@annotation都可,这里我用@annotation

通知类型:用@Around环绕通知就可

数据库设计

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for sys_log
-- ----------------------------
DROP TABLE IF EXISTS `sys_log`;
CREATE TABLE `sys_log`  (
  `id` int NOT NULL AUTO_INCREMENT,
  `create_user` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL,
  `create_time` datetime NOT NULL,
  `class_name` varchar(256) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,
  `method_name` varchar(256) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,
  `method_params` varchar(256) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,
  `return_value` varchar(256) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,
  `cost_time` mediumtext CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci COMMENT = '日志信息类' ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class OperatorLog {
    private Integer id; //ID
    private String createUser; //操作人ID
    private LocalDateTime createTime; //操作时间
    private String className; //操作类名
    private String methodName; //操作方法名
    private String methodParams; //操作方法参数
    private String returnValue; //操作方法返回值
    private Long costTime; //操作耗时

    public OperatorLog(String createUser, LocalDateTime createTime, String className, String methodName, String methodParams, String returnValue, Long costTime) {
        this.createUser = createUser;
        this.createTime = createTime;
        this.className = className;
        this.methodName = methodName;
        this.methodParams = methodParams;
        this.returnValue = returnValue;
        this.costTime = costTime;
    }
}

Mapper

@Mapper
public interface LogMapper {

    @Insert("insert into sys_log (create_user,create_time,class_name,method_name,method_params,return_value,cost_time)" +
            "values (#{createUser},#{createTime},#{className},#{methodName},#{methodParams},#{returnValue},#{costTime});")
    void addLog(OperatorLog log);
}

自定义注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface OperatorLog {
}

导入AOP的依赖

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

自定义AOP

@Slf4j
@Configuration
@Aspect
public class CustomizationAop {
    @Autowired
    HttpServletRequest request;

    @Autowired
    EmpMapper empMapper;

    @Autowired
    LogMapper logMapper;

    @Around("@annotation(com.huang.anno.OperatorLog)")
    public Object around(ProceedingJoinPoint p) throws Throwable{
        //获取token
        String token = request.getHeader("token");
        Claims claims = JwtUtils.parseJwt(token);
        //获取操作人名字
        Integer id = (Integer) claims.get("id");
        String operatorName = empMapper.getOneById(id).getName();
        //获取现在时间
        LocalDateTime now = LocalDateTime.now();
        //获取类名
        String className = p.getTarget().getClass().getName();
        //获取方法名
        String methodName = p.getSignature().getName();
        //获取参数名
        Object[] args = p.getArgs();
        String params = Arrays.toString(args);
        //方法执行前时间
        long begin = System.currentTimeMillis();
        //方法执行
        Object result = p.proceed();
        //方法执行后时间
        long end = System.currentTimeMillis();
        //获取执行时间
        long costTime = end - begin;
        //方法返回值
        String returnValue = JSONObject.toJSONString(result);
        if (returnValue.length()>255) {
            returnValue = returnValue.substring(0, 255);
        }

        OperatorLog operatorLog = new OperatorLog(operatorName, now, className, methodName, params, returnValue, costTime);
        logMapper.addLog(operatorLog);

        log.info("日志记录:{}",operatorLog);
        return result;
    }
}

实现

    @GetMapping
    @OperatorLog
    public Result page(@RequestParam(required = false) String name,
                       @RequestParam(required = false) Short gender,
                       @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin,
                       @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate end,
                       @RequestParam(defaultValue = "1") Integer page,
                       @RequestParam(defaultValue = "10") Integer pageSize){
        PageBean pageBean = empService.page(name,gender,begin,end,page,pageSize);
        return Result.success(pageBean);
    }

测试

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SpringSpring Cloud是两个相关的框架。Spring是一个开源的Java框架,用于构建Java应用程序。它提供了许多模块和工具,可以帮助开发者更轻松地构建企业级应用程序。Spring BootSpring框架的一部分,它是一个快速配置脚手架,可以帮助开发者快速搭建Spring应用程序。Spring Boot可以独立使用,也可以与Spring Cloud一起使用Spring Cloud是一个云端分布式架构解决方案,它是基于Spring Boot构建的。它提供了一系列的组件和工具,用于简化开发分布式系统的任务,比如服务发现、服务注册、负载均衡、配置管理等。Spring Cloud依赖于Spring Boot,因此无法独立使用,它扩展了Spring Boot的功能,使开发者可以更方便地构建和管理微服务架构。 总结起来,Spring Boot是一个快速配置脚手架,用于开发单个微服务,而Spring Cloud是基于Spring Boot的云应用开发工具,用于构建和管理分布式系统。Spring Cloud依赖于Spring Boot,因此需要在Spring Boot的基础上进行扩展。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [【微服务系列】Spring SpringMVC SpringBoot SpringCloud概念、关系及区别](https://blog.csdn.net/weixin_33700350/article/details/93831564)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值