详谈Spring是否支持对静态方法进行Aop增强

 更新时间:2021年12月23日 17:27:44   作者:javartisan  
这篇文章主要介绍了Spring是否支持对静态方法进行Aop增强,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

Spring Aop是否对静态方法进行代理?不着急看结论,看完实现也就明白了细节。

1、JDK代理

JDK代理代码:

 
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
interface Echor {
    public void echo();
} 
 
class EchorImpl implements Echor { 
    @Override
    public void echo() {
        System.out.println("echo ~");
    }
}
 
class MethodInvoker<T> implements InvocationHandler { 
    private T invoker; 
    public MethodInvoker(T invoker) {
        this.invoker = invoker;
    }
 
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 
        System.out.println("start ~");
        Object result = method.invoke(invoker, args);
        System.out.println("end ~");
        return result;
    }
}
 
public class DebugJdkProxy { 
    public static void main(String[] args) {
        Echor proxy = (Echor) Proxy.newProxyInstance(DebugJdkProxy.class.getClassLoader(), new Class[]{Echor.class}, new MethodInvoker<Echor>(new EchorImpl()));
        proxy.echo();
    } 
}

JVM实现代理类比较重要的类sun.misc.ProxyGenerator,生成代理类的方法为generateClassFile源码:

private byte[] generateClassFile() {
        this.addProxyMethod(hashCodeMethod, Object.class);
        this.addProxyMethod(equalsMethod, Object.class);
        this.addProxyMethod(toStringMethod, Object.class);
        Class[] var1 = this.interfaces;
        int var2 = var1.length;
 
        int var3;
        Class var4;
        for(var3 = 0; var3 < var2; ++var3) {
            var4 = var1[var3];
             //重点:代理那些方法?实例方法
            Method[] var5 = var4.getMethods();
            int var6 = var5.length;
            
            for(int var7 = 0; var7 < var6; ++var7) {
                Method var8 = var5[var7];
                this.addProxyMethod(var8, var4);
            }
        }
 
        Iterator var11 = this.proxyMethods.values().iterator();
 
        List var12;
        while(var11.hasNext()) {
            var12 = (List)var11.next();
            checkReturnTypes(var12);
        }
 
        Iterator var15;
        try {
            this.methods.add(this.generateConstructor());
            var11 = this.proxyMethods.values().iterator();
 
            while(var11.hasNext()) {
                var12 = (List)var11.next();
                var15 = var12.iterator();
 
                while(var15.hasNext()) {
                    ProxyGenerator.ProxyMethod var16 = (ProxyGenerator.ProxyMethod)var15.next();
                    this.fields.add(new ProxyGenerator.FieldInfo(var16.methodFieldName, "Ljava/lang/reflect/Method;", 10));
                    this.methods.add(var16.generateMethod());
                }
            }
 
            this.methods.add(this.generateStaticInitializer());
        } catch (IOException var10) {
            throw new InternalError("unexpected I/O Exception", var10);
        }
 
        if (this.methods.size() > 65535) {
            throw new IllegalArgumentException("method limit exceeded");
        } else if (this.fields.size() > 65535) {
            throw new IllegalArgumentException("field limit exceeded");
        } else {
            this.cp.getClass(dotToSlash(this.className));
            this.cp.getClass("java/lang/reflect/Proxy");
            var1 = this.interfaces;
            var2 = var1.length;
 
            for(var3 = 0; var3 < var2; ++var3) {
                var4 = var1[var3];
                this.cp.getClass(dotToSlash(var4.getName()));
            }
 
            this.cp.setReadOnly();
            ByteArrayOutputStream var13 = new ByteArrayOutputStream();
            DataOutputStream var14 = new DataOutputStream(var13);
 
            try {
                var14.writeInt(-889275714);
                var14.writeShort(0);
                var14.writeShort(49);
                this.cp.write(var14);
                var14.writeShort(this.accessFlags);
                var14.writeShort(this.cp.getClass(dotToSlash(this.className)));
                var14.writeShort(this.cp.getClass("java/lang/reflect/Proxy"));
                var14.writeShort(this.interfaces.length);
                Class[] var17 = this.interfaces;
                int var18 = var17.length;
 
                for(int var19 = 0; var19 < var18; ++var19) {
                    Class var22 = var17[var19];
                    var14.writeShort(this.cp.getClass(dotToSlash(var22.getName())));
                }
 
                var14.writeShort(this.fields.size());
                var15 = this.fields.iterator();
 
                while(var15.hasNext()) {
                    ProxyGenerator.FieldInfo var20 = (ProxyGenerator.FieldInfo)var15.next();
                    var20.write(var14);
                }
 
                var14.writeShort(this.methods.size());
                var15 = this.methods.iterator();
 
                while(var15.hasNext()) {
                    ProxyGenerator.MethodInfo var21 = (ProxyGenerator.MethodInfo)var15.next();
                    var21.write(var14);
                }
 
                var14.writeShort(0);
                return var13.toByteArray();
            } catch (IOException var9) {
                throw new InternalError("unexpected I/O Exception", var9);
            }
        }
    }

上DEBUG截图:

到此处,已经清楚JDK底层生成代理类时代理哪些方法,其中反射getMethods是可以获取到Class中所有public方法,包括静态方法。

由于JDK代理是基于接口的,而接口里面又不允许有静态方法,所以是无法代理静态方法的。换个角度:基于接口的Jdk代理与基于继承Class的代理本质都是基于继承之后重写指定方法实现的代理,而static方法是属于class的,而不是类实例的,无法被重写所以static方法无法代理。除此之外,JDK代理类是基于接口实现生成的,因此对于子类的final方法是可以代理的。

需要注意:Jdk8中的default方式是实例方法,而静态方法。

2、CGLIB代理

 
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy; 
interface Echor {
    public void echo(); 
    public static void hello() {
        System.out.println("hello world!");
    }
} 
 
abstract class AbsEchor implements Echor { 
    public static void abs() {
        System.out.println("abs~~");
    } 
 
    public static void hello() {
        System.out.println("hello world!");
    }
}
 
class EchorImpl implements Echor { 
    public static void hello2() {
        System.out.println("hello world!");
    } 
    @Override
    public void echo() {
        System.out.println("echo ~");
    }
}  
 
class EchorMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("start ~");
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("end ~"); 
        return result; 
    }
}
 
class DebugCGlibProxy { 
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(AbsEchor.class);
        enhancer.setCallback(new EchorMethodInterceptor()); 
        AbsEchor hello = (AbsEchor) enhancer.create();
        hello.abs();
    } 
}

小结一下:基于JDK代理与基于CGLIB代理的代理类生成本质都是基于继承重写实现的(实现接口可以认为是一种特殊的继承);对于static成员方法是无法子类重写的,static是归属于class所属。

至此:由于Spring使用的是JDK与CGLIB这两种方式实现AOP,因此结论就是Spring无法支持static方法的代理增强。

Spring AOP静态代理

对于AOP 我们应该拿OOP来对比学习,它们之间的区别如下:

AOP中不得不提的就是代理

通俗理解就是:茅台公司生产出酒,而代理商拿出来销售并推出各种销售活动。这时茅台公司就是真实主题,也就是目标对象。而代理商就是代理。茅台酒就是目标对象中的方法。各种销售活动就是给目标对象中的方法的增强补充,比如对方法添加日志等等操作。

代理又分为静态代理和动态代理两种:像这样已知目标对象就是为茅台公司 就为静态代理,这时目标对象已确定

下面为一个静态代理的例子

先定义一个PersonBiz的接口:

再对这个接口进行实现

这是我们使用静态代理给这两个方法加上一个操作时间的功能,我就直接上代码了:

package com.yc.dao.impl;
import java.util.Date;
import com.yc.dao.PersonBiz;
//代理对象
public class PersonBizProxy implements PersonBiz {
    private PersonBiz personBiz;// 对真实主题的引用
    public PersonBizProxy(PersonBiz personBiz) {
        this.personBiz = personBiz;
    }
    @Override
    public void add(String name) {
        // 加入关注点-》增强的功能
        showLog();// 前置增强
        // 再调用真实主题的方法
        this.personBiz.add(name);
    }
    @Override
    public String find() {
        // 调用真实主题的方法
        personBiz.find();
        // 加入关注点-》增强的功能
        showLog();// 后置增强
        return null;
    }
    private void showLog() {
        Date d = new Date();
        System.out.println("-----------------");
        System.out.println("操作时间" + d);
        System.out.println("-----------------");
    }
}

最后就是测试类:

代理的优势很明显:当你不需要新增的操作时间的功能时,就将PersonBizProxy pbp=new PersonBizProxy(pb);去掉即可,后面改用pb调用方法,让代码很好的实现了可扩展性,也不用在原来已有的代码上修改。

静态代理的缺点:只能针对一个接口进行代理

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • java实现录音播放功能

    java实现录音播放功能

    这篇文章主要为大家详细介绍了java实现录音播放功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-08-08
  • Java过滤器Filter详解

    Java过滤器Filter详解

    这篇文章主要介绍了java过滤器中Filter,发送请求时,如果有不符合的信息将会被filter进行拦截,如果符合则会进行放行。如果感兴趣可以来学习一下
    2021-08-08
  • 一文带你深入了解Java8 Stream流式编程

    一文带你深入了解Java8 Stream流式编程

    在实际项目当中,若能熟练使用Java8 的Stream流特性进行开发,就比较容易写出简洁优雅的代码。本文主要就是基于实际项目常用的Stream Api流式处理总结,希望对大家有所帮助
    2023-04-04
  • Python单元测试_使用装饰器实现测试跳过和预期故障的方法

    Python单元测试_使用装饰器实现测试跳过和预期故障的方法

    下面小编就为大家带来一篇Python单元测试_使用装饰器实现测试跳过和预期故障的方法。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-06-06
  • java算法实现红黑树完整代码示例

    java算法实现红黑树完整代码示例

    这篇文章主要介绍了java算法实现红黑树完整代码示例,具有一定参考价值,需要的朋友可以了解下。
    2017-11-11
  • 详解在Spring Boot中使用数据库事务

    详解在Spring Boot中使用数据库事务

    本篇文章主要介绍了详解在Spring Boot中使用数据库事务,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-05-05
  • 详解Java SSM项目部署上线配置方法(阿里云服务器ECS + 云数据库RDS MySQL)(宝塔)

    详解Java SSM项目部署上线配置方法(阿里云服务器ECS + 云数据库RDS MySQL)(宝塔)

    这篇文章主要介绍了Java SSM项目部署上线(阿里云服务器ECS + 云数据库RDS MySQL)(宝塔)的图文教程,本文通过图文并茂的形式给大家介绍的非常详细,感兴趣的朋友一起看看吧
    2024-01-01
  • JAVA容器集合全面解析(Collection和Map)

    JAVA容器集合全面解析(Collection和Map)

    这篇文章主要介绍了JAVA容器集合全面解析(Collection和Map)本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-08-08
  • Spring Cloud Feign内部实现代码细节

    Spring Cloud Feign内部实现代码细节

    Feign 的英文表意为“假装,伪装,变形”, 是一个http请求调用的轻量级框架,可以以Java接口注解的方式调用Http请求,而不用像Java中通过封装HTTP请求报文的方式直接调用。接下来通过本文给大家分享Spring Cloud Feign内部实现代码细节,感兴趣的朋友一起看看吧
    2021-05-05
  • 如何通过SpringBoot实现商城秒杀系统

    如何通过SpringBoot实现商城秒杀系统

    这篇文章主要介绍了如何通过SpringBoot实现商城秒杀系统,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-11-11

最新评论