首页 > 其他分享 >注解版的springaop实操讲解(赋完整测试代码)

注解版的springaop实操讲解(赋完整测试代码)

时间:2024-01-20 22:03:26浏览次数:38  
标签:void 代理 springaop 实操 aop 测试代码 注解 com public


aop是个很强的东西,我们可以用来实现日志收集,鉴权,敏感词过滤等等功能。在说注解版的springaop使用之前,一些专业术语我用大白话来复述一遍,希望大家不要嫌弃。

  • 切面:切入点+通知
  • 连接点:目标对象中被增强的某个方法
  • 切点:连接点的集合
  • 目标对象:被增强的对象
  • 织入:把代理逻辑加入到目标对象的过程
    好了先来定义一个切面写法1:
@Component
@Aspect
public class aspect1 {
    @Pointcut("execution(* com.zzh.service.*.*(..))")
    public void pointCut() {
    }

    @Before("pointCut()")
    public void a(JoinPoint joinPoint) throws Throwable {
        System.out.println("前置通知");
    }
}

写法二:
这俩个写法无非就是一气呵成与分步骤写而已的区别了,我个人推荐写法一,排查起来方便,切点还可以复用

@Component
@Aspect
public class aspect1 {
    
    @Before("execution(* com.zzh.service.*.*(..))")
    public void a(JoinPoint joinPoint) throws Throwable {
        System.out.println("前置通知");
    }

  
}

execution里面是我们的切入点表达式,表示哪些类要被增强
@Pointcut就是切点了,

启动类

注意这里要加@EnableAspectJAutoProxy注解,作用是开启动态代理,默认是cglib还是jdk忘了,等本文说到这再来调试

@EnableAspectJAutoProxy
@Configuration
@ComponentScan({"com.zzh"})
public class DemoApplication {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext c = new 		AnnotationConfigApplicationContext(DemoApplication.class);
        IService bean = (IService)c.getBean("serviceImpl");
    
        bean.eat("肉");

    }

}

被增强的类

@Component
public class Service extends iService {
    public void eat(String food) {
        System.out.println("吃: " + food);
    }
}

这样一个基础的aop就搭建完成了。每次调用我们的service对象都会打印“前置通知这几个字了”

注解版的springaop实操讲解(赋完整测试代码)_proxy

基本的注解aop完了,接下来开始撸重点了。切面这里面有很大的学问呢,其中@before()这里面就可以加 this、whthin、@annotation、target,这些东西粗讲就是可以指定目标对象,细讲后文说。还可以加args、return等东西。而切面上面更是可以加@Aspect(“perthis(this(aop.a))”)、@Scope(“prototype”)。

this

作用:指定代理对象的类型,只有代理对象是我们指定的类型aop才会生效。在说这个之前先来把启动类改造一下,也就是把getBean的类型换成了ServiceImpl,诶这样一看好像是不是感觉没什么问题呢,拿的就是serviceImpl这个bean,类型转换成这样也没错吧。

@EnableAspectJAutoProxy
@Configuration
@ComponentScan({"com.zzh"})
public class DemoApplication {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext c = new AnnotationConfigApplicationContext(DemoApplication.class);
        IService bean = (IService)c.getBean("serviceImpl");
        //ServiceImpl bean = (ServiceImpl)c.getBean("serviceImpl");
        bean.eat("肉");

    }

}

但是结果确是不能,说到这里还得提一下jdk代理底层的逻辑。为什么jdk动态代理是通过接口来实现而不是用继承的呢?假设a实现了接口b,我获取名字为a的bean这个ben取名叫il吧,在jdk代理下,我们的il extends proxy implents b,代理对象il已经继承了proxy,java中是单继承的,所以只能通过接口来实现目标方法咯,而且这里还衍生出一个问题,那就是我们获取到的il不能转换成a,只能转换成b或者proxy类型的。

正确写法:

IService bean = (IService)c.getBean(“serviceImpl”);

错误写法:

ServiceImpl bean = (ServiceImpl)c.getBean(“serviceImpl”);

注解版的springaop实操讲解(赋完整测试代码)_proxy_02

this精讲

好了扯了这么多开始说this。一个demo带大家分析原因。启动类还是上面这个,下面这么写能不能使aop生效呢?

@Aspect
@Component
public class aspect2 {
    //代理对象为ServiceImpl类型aop才生效
    @Pointcut("this(com.zzh.service.bean1.ServiceImpl)")
    public void point() {
    }

    @Before("point()")
    public void test() {
        System.out.println("before");
    }
}

答案是不能,getBean(“serviceImpl”)这个代理对象只会等于IService与proxy,不会等于serviceImpl。但是我们的切面中是这么写的@Pointcut(“this(com.zzh.service.bean1.ServiceImpl)”)代理对象为ServiceImpl类型aop才生效

搞来搞去,头都晕了,那怎么才会aop生效呢,

方法一

那就是修改成这个咯,再看看this的作用,指定代理对象为***类型的aop才生效,是不是一下子就明白了this原来是这样用的啊。

@Aspect
@Component
public class aspect2 {
//    //代理对象为ServiceImpl类型aop才生效
//    @Pointcut("this(com.zzh.service.bean1.ServiceImpl)")
//    public void point() {
//    }
//    @Before("point()")
//    public void test() {
//        System.out.println("before");
//    }
    //代理对象为IService类型aop才生效
    @Pointcut("this(com.zzh.service.bean1.IService)")
    public void point2() {
    }

    @Before("point2()")
    public void test() {
        System.out.println("before");
    }
}

方法二开启cglib动态代理。

启动类改下这个标签@EnableAspectJAutoProxy(proxyTargetClass = true),就开启的是cglib代理了,因为cglib代理是通过继承目标对象来实现的。哦吼画画的baby,有的人修改后可能又会报下面这个错,这个错不详解,

注解版的springaop实操讲解(赋完整测试代码)_java_03

解决办法

加上这个参数-noverify,跳过字节码的检查,然后就不会报错了,

注解版的springaop实操讲解(赋完整测试代码)_spring_04

target

现在来分析target,作用:指定目标对象的类型,只有目标对象是我们 指定的类型aop才会生效。还是同一个启动类,毫无疑问下面的写法aop都会生效,目标对象是啥,getBean(“serviceImpl”);双引号里面的这个就是目标对象,而serviceImpl实现了Iservice接口,所以aop都会生效。

//    //目标对象为IService类型aop才生效
//    @Pointcut("target(com.zzh.service.bean1.IService)")
//    public void point2() {
//    }
//
//    @Before("point2()")
//    public void test() {
//        System.out.println("before");
//    }


    //目标对象为ServiceImpl类型aop才生效
    @Pointcut("target(com.zzh.service.bean1.ServiceImpl)")
    public void point2() {
    }

    @Before("point2()")
    public void test() {
        System.out.println("before");
    }

@annotation

说完了我认为最难的this和target,接下来来点容易的吧,这个的作用就是:为指定的注解赋予aop的属性,以后只要是被这个注解标注的方法都会aop生效。说这个之前先说下自定义注解的使用吧。下面这个就是一个简单的自定义注解。

//表明这个注解只会在运行时才会生效
@Retention(RetentionPolicy.RUNTIME)
//表明这个注解是作用在方法上的
@Target(value = ElementType.METHOD)
public @interface BeforeCustom {
}

好了有了注解,那就来使用它吧。下面为注解注入灵魂

//被此注解标注aop会生效
    @Pointcut("@annotation(com.zzh.annocation.BeforeCustom)")
    public void point2() {
    }

    @Before("point2()")
    public void test() {
        System.out.println("注解生效了");
    }

给注解分配工作的代码,给serviceImpl’中加个方法并且用我们的自定义注解标注

@Repository
public class ServiceImpl implements IService {

    public void eat(String food) {
        System.out.println("吃: " + food);
    }

    /**
     * @param
     * @method 自定义注解
     */
    @BeforeCustom
    public void methodA(String food) {
        System.out.println("吃: " + food);
    }
}

测试只需在启动类中调用bean.methodA(“肉”);

注解版的springaop实操讲解(赋完整测试代码)_编程语言_05

args

作用:就是精确级别到参数类型来匹配哪些方法能被aop作用。args中的映射类型从a()中的参数直接映射获取。

@Around("execution(* com.zzh.service.*.*(..))&&args(args1,args2)")
    public Object a( ProceedingJoinPoint proceedingJoinPoint,int args1,String args2) throws Throwable {
        Object proceed = proceedingJoinPoint.proceed();
        System.out.println("被代理的方法是:"+proceedingJoinPoint.getSignature().getName()+"的参数:" +proceedingJoinPoint.getArgs().toString()+"方法的返回值:"+proceed);
        return proceed;
    }

这段代码就是匹配com.zzh.service下的所有类的所有方法但是参数类型顺序依次为int,string类型的方法才会被aop生效。同时值得注意的是,我在测试中发现@Around只能仅与ProceedingJoinPoint配对才能使用否则报错。不太清除为什么。其实我感觉这些东西都和@Around有关,那干脆接着细聊下@Around好了

@Around

网上都是说环绕通知可以在方法的执行前后做点啥事情,那到底可以做啥事情呢?1:修改被代理方法的返回值。2:修改被代理方法的参数。3:一般被@Around这个通知标注的方法都带有返回值,且这个返回值就是被代理方法的返回值。如果你返回void,那么目标方法返回null,我们是搞代理,不是搞设计的,切记要加返回值。但是这里面还有需要注意的点,那就是我们的返回值的类型要>=目标方法的返回值的类型,不然类型转换不过来。4:就是在执行前后织入自己的逻辑。好了还是代码分析吧

/**
     * @param
     * @method 带返回值的环绕通知
     */
    @Around("execution(* com.zzh.service.*.*(..))&&args(args1,args2)")
    public Object a(ProceedingJoinPoint proceedingJoinPoint, int args1, String args2) throws Throwable {
        //修改执行参数
        //Object[] objects = {111, "222"};
        //Object proceed = proceedingJoinPoint.proceed(objects);
        Object proceed = proceedingJoinPoint.proceed();
        for (int i = 0; i < proceedingJoinPoint.getArgs().length; i++) {
            System.out.println("被代理的方法是:" + proceedingJoinPoint.getSignature().getName() + "的参数:" + proceedingJoinPoint.getArgs()[i] + "方法的返回值:" + proceed);

        }
        return proceed;
    }

这个就不分析了给大家开下效果图,这是不修改参数的

注解版的springaop实操讲解(赋完整测试代码)_编程语言_06

注解版的springaop实操讲解(赋完整测试代码)_proxy_07

这个是修改参数的

注解版的springaop实操讲解(赋完整测试代码)_aop_08

这个是修改返回值的

注解版的springaop实操讲解(赋完整测试代码)_proxy_09

到这@Around差不多应该差不多了吧,说到这还想提提@AfterReturning

@AfterReturning

一般与returning 连用字面意思就是方法执行后的通知,那么对应的连接点只能写JoinPoint而不是ProceedingJoinPoint,猜都猜到方法执行后的通知是不需要过程的,虽然看过aop源码但是不太清楚报错位置,可能看的不够仔细吧?直接代码分析

/**
     * @param
     * @method 带返回值的AfterReturning,MyReturn就是方法执行之后的结果
     */
    @AfterReturning(value = "execution(* com.zzh.service.*.*(..))&&args(args1,args2)", returning = "MyReturn")
    public Object a(JoinPoint joinPoint, int args1, String args2, Object MyReturn) throws Throwable {
        System.out.println(MyReturn);
        return "返回值被修改了,嘿嘿你气不气";
    }

注解版的springaop实操讲解(赋完整测试代码)_编程语言_10

都说是得到返回结果之后的通知,果然想试试修改目标方法的返回值还真的没用,但是与returning = "MyReturn"使用帅的一批,可以得到返回值。最后我好像还用过within,那就来说这个吧

within

我测试了一下,其实就是锁的粒度更大吧,直接到类aop都能生效,而execution锁的粒度更小,精确到参数级别,这个很简单就不再细讲了。接下来说下 perthis吧,

注解版的springaop实操讲解(赋完整测试代码)_aop_11

perthis

网上都说是啥生命周期啥的讲的不是很清楚,下面的话是我亲测出来的,可能与你以往的认知不同,管它呢本来有好多人写的博客就是有错的,但是我亲测的我就大胆的来说了,我就用大白话来说了,这个和this差不多,this是匹配符合条件代理对象使得通知生效,而这个就是匹配符合条件代理对象使得切面生效,且每次getbean都会生成一个全新的切面与之匹配。当然是这个bean是prototype的情况下,如果bean是单例的辣么无论get多少次这个bean,永远是同一个切面与之匹配。因而perthis与@Scope(“prototype”)是配套使用,不可拆散,下面直接代码来说

注解版的springaop实操讲解(赋完整测试代码)_java_12

注解版的springaop实操讲解(赋完整测试代码)_spring_13

注解版的springaop实操讲解(赋完整测试代码)_编程语言_14

注解版的springaop实操讲解(赋完整测试代码)_编程语言_15

在切面上加上perthis与@Scope(“prototype”),同时为service下的所有类开启aop,新建俩类MyPrototypea与MyPrototypeb俩个类差不多,我就只贴MyPrototypea的了,下面开始测试

注解版的springaop实操讲解(赋完整测试代码)_spring_16

由于我们是get MyPrototypea,且此时MyPrototypea是多例的,因此2次get都生成了一个全新的切面与之匹配,那么我们来接着试试get其他的bean吧,

注解版的springaop实操讲解(赋完整测试代码)_spring_17


我们发现切面压根都没有创建那么aop更就不会生效了,那如果把

MyPrototypea改成单例的呢?好的看下图

注解版的springaop实操讲解(赋完整测试代码)_proxy_18


我们可以看到切面只有创建一次,好的说到这其实perthis差不多就说透了,其实与pertarget都是差不多的,就不说了额到这,好像应该差不多了吧,额还有类似于这种的的@within表示匹配带有指定注解的类。其实这种加@大部分都是匹配对应的注解的,额好像还有。@DeclareParents的使用。

@DeclareParents

作用为指定类,增加属性或者方法。代码来说,如果我们要为ImplPerson类植入别的方法可以这么写,如下图,这样就为ImplPerson类植入了Iservice中的全部方法且默认实现类为ServiceImpl

注解版的springaop实操讲解(赋完整测试代码)_aop_19

效果

注解版的springaop实操讲解(赋完整测试代码)_编程语言_20

这里我摘抄它底层的一段源码,我也是研究aop源码时偶然发现的

DeclareParents declareParents = (DeclareParents)introductionField.getAnnotation(DeclareParents.class);
        if (declareParents == null) {
            return null;
        } else if (DeclareParents.class == declareParents.defaultImpl()) {
            throw new IllegalStateException("'defaultImpl' attribute must be set on DeclareParents");
        } else {
            return new DeclareParentsAdvisor(introductionField.getType(), declareParents.value(), declareParents.defaultImpl());
        }

好了大概的东西应该都有涉及到,下面是我测试用的代码完整链接https://github.com/zhangzihang3/zzhSpringAop.git觉得不错的点个赞吧


标签:void,代理,springaop,实操,aop,测试代码,注解,com,public
From: https://blog.51cto.com/u_16414043/9346319

相关文章

  • 将MySQL数据库数据转换为PGSQL数据库 --- 实操可以
    利用navicate,傻瓜操作即可。选中要迁移的数据库,用navicate上面的工具,数据传输,传输到要迁移的数据库(可以在不同的连接之间传输的)https://huaweicloud.csdn.net/63356c9ed3efff3090b5653e.html......
  • 实操开源版全栈测试工具RunnerGo安装(三)MacOS安装
    以Sonoma14.1.2系统为例视频教程:https://www.bilibili.com/video/BV1fG411e7h2/?spm_id_from=333.999.0.01、下载并安装docker​下载地址:https://docker.p2hp.com/下载后安装2、打开终端,准备docker和docker-compose环境​gitclonehttps://github.com/Runner-Go-T......
  • 实操开源版全栈测试工具RunnerGo安装(四)Windows安装
    以windows10系统为例视频教程:https://www.bilibili.com/video/BV14H4y1C71u/?spm_id_from=333.999.0.01、设置手动进入系统BIOS启用虚拟化技术​(展示型号是HUAWEIMateBook13),重启电脑按F2进入BIOS,然后启用虚拟化(Inter启动虚拟化)将VirtualizationTechnology设置为 <Enabl......
  • 实操开源版全栈测试工具RunnerGo安装(二)Linux安装
    手动安装(支持Linux、MacOS、Windows)Linux安装步骤以debian系统为例,其他linux系统参考官方文档:https://docs.docker.com/engine/install 进行docker安装视频教程:https://www.bilibili.com/video/BV1Mj41157db/?spm_id_from=333.999.0.01、安装docker​卸载所有冲突的程序包......
  • 实操开源版全栈测试工具RunnerGo安装(一)
    Docker版安装文档一、环境要求​1.1部署服务器要求​操作系统:任何支持Docker的Linuxx86CPU内存:最低要求4C8G,推荐8C16G网络要求:可访问互联网​ ❗并发量较大时,请关注您的带宽、服务器的cpu及内存使用率等,以免影响测试结果和业务运行。1.2网络端口要求​为保证R......
  • SpringAOP基于xml的五种通知
    <?xmlversion="1.0"encoding="UTF-8"?><beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springfra......
  • 用jacoco统计JAVA项目测试代码覆盖率
    一、概述Jacoco统计的是全量代码覆盖率。它不仅支持生成单元测试的覆盖率,也支持监控生成接口测试,功能测试的覆盖率。在新一代精准测试技术流的影响中,各大型单位对覆盖率的追求越来越迫切。作为一款开源产品,它主机面向Java语言,能够在字节码层面给出覆盖率,同时也能将字节码关联到......
  • Airtest-Selenium实操小课②:刷B站视频
    此文章来源于项目官方公众号:“AirtestProject”版权声明:允许转载,但转载必须保留原链接;请勿用作商业或者非法用途1.前言上一课我们讲到用Airtest-Selenium爬取网站上我们需要的信息数据,还没看的同学可以戳这里看看~那么今天的推文,我们就来说说看,怎么实现看b站、刷b站的日常......
  • 自定义快捷键实操与踩坑
    0.缘起要做一个自定义快捷键的功能,web端实现。这里分为两块逻辑,一部分是快捷键的应用,一部分是快捷键的定义。先从应用说起,快捷键实际上是对浏览器按键动作的监听,不过由于浏览器本身也有快捷键,就会有冲突的情况,自定义的要求应运而生。快捷键的定义,其实类似于设置的功能,也是存、......
  • #星计划#【坚果派】JS开源库适配OpenHarmony系列——第一期实操
    (目录)JS开源库适配OpenHarmony系列第一期实操1.为什么适配JS开源库由于OpenHarmony应用是基于ArkTS开发,而ArkTS是在保持TypeScript(简称TS)基础语法风格的基础上,对TS的动态类型特性施加更严格的约束,引入静态类型。因此在开发OpenHarmony三方库时,建议首选在成熟的JS/TS开源三方......