Spring Aop 面向切面编程 入门实战

目录

1. 定义: AOP(Aspect Orient Programming)

2. 实现原理:  代理(CGLIB代理和JDK代理)

3. 相关术语: 

4. 实战 (执行一个方法后查看执行时间)

5. 自定义注解

6. 导入依赖 Aop 

7. 创建切面对象 (创建切面类)

8. 按照最上面4方法里的controller 进行测试

---------> test01

 -----------> test02

===> 结束了  最最简单的aop入门 around方法   

****  思维拓展: ****

两个拓展思维

1. 写一个接口限制(单体项目的熔断策略)

2. aop+redis实现缓存=> springboot整合的@EnableCaching

2.1 查询商品列表 查询后存入redis 下次看缓存中是否存在

2.2 列表更新修改 缓存也需要修改 如何实现!!!


1. 定义: AOP(Aspect Orient Programming)

是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程(OOP)的一种补充和完善。实际项目中我们通常将面向对象理解为一个静态过程(例如一个系统有多少个模块,一个模块有哪些对象,对象有哪些属性),面向切面理解为一个动态过程(在对象运行时动态织入一些扩展功能或控制对象执行)

AOP面向切面的运行期代理方式,理解为一个动态过程,可以在对象运行时动态织入一些扩展功能或控制对象执行。

2. 实现原理:  代理(CGLIB代理和JDK代理)

AOP可以在系统启动时为目标类型创建子类或兄弟类型对象,这样的对象我们通常会称之为动态代理对象.

其中,为目标类型(XxxServiceImpl)创建其代理对象方式有两种(先了解):

第一种方式:借助JDK官方API为目标对象类型创建其兄弟类型对象,但是目标对象类型需要实现相应接口.
第二种方式:借助CGLIB库为目标对象类型创建其子类类型对象,但是目标对象类型不能使用final修饰.

3. 相关术语: 

切面(aspect): 横切面对象,一般为一个具体类对象。
切入点(pointcut):定义了切入扩展业务逻辑的位置(哪些方法运行时切入扩展业务),一般会通过表达式进行相关定义,一个切面中可以定义多个切入点。
通知(Advice): 内部封装扩展业务逻辑的具体方法对象,一个切面中可以有多个通知(在切面的某个特定位置上执行的动作(扩展功能)。
连接点(joinpoint):程序执行过程中,封装了某个正在执行的目标方法信息的对象,可以通过此对象获取具体的目标方法信息,甚至去调用目标方法。连接点与切入点定义如图所示:

4. 实战 (执行一个方法后查看执行时间)

随便写一个controller  写两个不同的方法 区分是否使用aop

详细解释在代码中  基本每行我都写注释了

package cn.pingzhuyan.testAop.controller;

import cn.pingzhuyan.testAop.annotation.TimeCount;
import cn.pingzhuyan.testAop.aspect.TimeCountLogAspect;
import lombok.SneakyThrows;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
//import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 简易aop 模拟
 * 每个接口 我现在都需要 请求时间(ms)
 * 有一百个接口 难道我要写一百次这个方法吗 当然是不能的
 * 根据封装思想  把公共的地方抽出去
 * <p>
 * 这个时候 继承就可以实现了 但是java是单继承
 * 而这个业务 是重要 但是不能影响其他业务, 也就是锦上添花的业务(暂时这么理解)
 * <p>
 * 使用新技术 aop 面向切面编程
 * 定义: 切面编程  切面是一个类  切入点是一个方法  切面类里面有一个切入点方法  切入点方法里面有业务逻辑
 * <p>
 * around()   最常用(优先级最高) 讲的也是这种 //@Order(2) 注解代表执行优先级 可以自定义一下
 * before()
 * after()
 * afterReturning()
 * afterThrowing()
 * afterFinally()
 *
 * @Author pzy
 * @Version 0.1.0
 * @CreateTime 2022/08/14
 */

//@RefreshScope
@RestController
@RequestMapping("/test0101")
public class AopTestController {
    private static final Logger log = LoggerFactory.getLogger(TimeCountLogAspect.class);

    /**
     * 不加aop (java代码实现)
     *
     * @param
     * @return
     */
//    @SneakyThrows
    @GetMapping("/test01")
    public String test01() throws InterruptedException {
        long startTime = System.currentTimeMillis();
//        log.debug("不加aop的测试->{}","true");
        System.out.println("开始执行~~~");
        //模拟耗时操作
        Thread.sleep(3000);

        long endTime = System.currentTimeMillis();
        System.out.println("不加aop的测试--->" + (endTime - startTime) + "ms");

        System.out.println("程序结束~~~");
        return "不加aop的测试!!!";
    }

    /**
     * 加aop(注解实现)
     *
     * @param
     * @return
     */
    @SneakyThrows
    @TimeCount(value = "pzy随意写的 看到这个值就行", operation = "加aop的测试 看到我是在哪写的")
    @GetMapping("/test02")
    public String test02() {

        Thread.sleep(3000);

        return "加aop的测试!!!";
    }


}

注意:  里面爆红的地方 (先注释掉, 写最简淡的controller 返回一个字符串)

5. 自定义注解

创建注解类型,应用于切入点表达式的定义

详细解释在代码中  基本每行我都写注释了

package cn.pingzhuyan.testAop.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @Author pzy
 * @Version 0.1.0
 * @CreateTime 2022/08/14
 * 自定义注解  时间输出(简单)
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface TimeCount {
    /**
     * 操作信息
     * @return
     */
    String operation();

    /**
     * value 默认值 可以不写
     * @return
     */
    String value() default "";

}

6. 导入依赖 Aop 

spring-boot-starter-aop 跟springboot统一版本一致

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

7. 创建切面对象 (创建切面类)

里面的反射可以先不看, 但是只要使用注解内的对象类型 就需要反射 
(例如次数 过期秒数 熔断策略等)

详细解释在代码中  基本每行我都写注释了

package cn.pingzhuyan.testAop.aspect;

import cn.pingzhuyan.testAop.annotation.TimeCount;
import org.aspectj.lang.ProceedingJoinPoint;
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.stereotype.Component;

import java.lang.reflect.Method;

/**
 * @Author pzy
 * @Description: TODO
 * @Version 0.1.0
 * @CreateTime 2022/08/14
 */
@Aspect //切面类 就是声明我是一个切面
@Component //交给 spring bean容器管理 看看我之前总结的注解 没了我博客都有
public class TimeCountLogAspect {

    //这个等效于 @Slf4j ->  写了这个 不用写 @Slf4j 知道即可 设计模式: 简单工厂模式
    private static final Logger log = LoggerFactory.getLogger(TimeCountLogAspect.class);

    /**
     * @Pointcut注解用于定义切入点
     * @annotation(注解)为切入点表达式,后续由此注解描述的方法为切入 点方法
     */
    @Pointcut("@annotation(cn.pingzhuyan.testAop.annotation.TimeCount)")
    public void doLog() {
    }//此方法只负责承载切入点的定义

    /**
     * @param jp 连接点对象,此对象封装了要执行的目标方法信息.
     *           可以通过连接点对象调用目标方法.
     * @return 目标方法的执行结果
     * @throws Throwable
     * @Around注解描述的方法,可以在切入点执行之前和之后进行业务拓展,
     */
    @Around("doLog()")
    public Object doAround(ProceedingJoinPoint jp) throws Throwable {
        log.error("========= 区分主动pzy(警示非异常) ========");

        long t1 = System.currentTimeMillis();
        System.out.println("aop程序开始~~~" + t1);

        /*反射获取目标方法的参数 可以不会 之前讲过反射和暴力反射 需要操作类或者方法时使用*/
        //获取目标方法的类对象
        Class<?> targetCls = jp.getTarget().getClass();
        //获取目标方法的反射对象
        MethodSignature ms = (MethodSignature) jp.getSignature();
        //获取目标方法
        Method targetMethod = targetCls.getMethod(ms.getName(), ms.getParameterTypes());
        //获取目标方法的注解
        TimeCount annotation = targetMethod.getAnnotation(TimeCount.class);

        String operation = annotation.operation();
        String value = annotation.value();
        try {
            //执行目标方法(切点方法中的某个方法)
            Object result = jp.proceed();
            long t2 = System.currentTimeMillis();
            log.info("opertime:{}", t2 - t1);

            System.out.println("正确执行 -> " + operation + ":" + value + ":" + (t2 - t1));


            System.out.println("aop程序执行结束~~~" + t2);
            return result;//目标业务方法的执行结果
        } catch (Throwable e) {
            e.printStackTrace();
            long t2 = System.currentTimeMillis();
//            log.info("exception:{}", e.getMessage());
            System.out.println("错误执行 -> " + operation + ":" + value + ":" + (t2 - t1));
            throw e;
        }
    }
}

8. 按照最上面4方法里的controller 进行测试

---------> test01

http://localhost:9000/test0101/test01

 -----------> test02

http://localhost:9000/test0101/test02


===> 结束了  最最简单的aop入门 around方法   

****  思维拓展: ****

两个拓展思维

1. 写一个接口限制(单体项目的熔断策略)

需求:  (60秒内 一个ip 可以访问10次 , 超过返回error 提示: 稍后重试!!!)aop配合redis  



2. aop+redis实现缓存=> springboot整合的@EnableCaching

2.1 查询商品列表 查询后存入redis 下次看缓存中是否存在

->  存在显示

-> 不存在插库然后存redis      循环

2.2 列表更新修改 缓存也需要修改 如何实现!!!

pzy 20220814

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

pingzhuyan

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值