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

目录
  • 1、JDK代理
  • 2、CGLIB代理
  • Spring AOP静态代理
    • 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调用方法,让代码很好的实现了可扩展性,也不用在原来已有的代码上修改。

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

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

(0)

相关推荐

  • Spring-AOP 静态正则表达式方法如何匹配切面

    概述 在Spring-AOP 静态普通方法名匹配切面案例中 StaticMethodMatcherPointcutAdvisor中,仅能通过方法名定义切点,这种描述方式不够灵活,假设目标类中有多个方法,切满足一定的命名规范,使用正则表达式进行匹配就灵活多了. RegexpMethodPointcutAdvisor是正则表达式方法匹配的切面实现类,该类已经是功能齐全的实现类,一般情况下无需扩展该类. 实例 代码已托管到Github-> https://github.com/yangshangwei

  • spring AOP的After增强实现方法实例分析

    本文实例讲述了spring AOP的After增强实现方法.分享给大家供大家参考,具体如下: 一 配置 <?xml version="1.0" encoding="GBK"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmln

  • spring AOP的Around增强实现方法分析

    本文实例讲述了spring AOP的Around增强实现方法.分享给大家供大家参考,具体如下: 一 配置 <?xml version="1.0" encoding="GBK"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xml

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

    目录 1.JDK代理 2.CGLIB代理 Spring AOP静态代理 AOP中不得不提的就是代理 下面为一个静态代理的例子 Spring Aop是否对静态方法进行代理?不着急看结论,看完实现也就明白了细节. 1.JDK代理 JDK代理代码: import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; interface Echor { p

  • 详谈Spring框架之事务管理

    一.编程式事务 二.声明式事务 1.基于XML的事务 1.1 Spring配置文件 <!-- 配置c3p0数据源,只是进行了最简单的配置 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="user" value="root"></property>

  • 详谈Spring对IOC的理解(推荐篇)

    一.IOC控制反转和DI依赖注入 1.控制反转,字面可以理解为:主动权的转移,原来一个应用程序内的对象是类通过new去主动创建并实例化的,对对像创建的主动权在程序代码中.程序不仅要管理业务逻辑也要管理对的象创建和依赖关系.这是很累的,也跟软件工程 "低耦合高内聚" 的概念不十分符合. 有了spring的ioc容器之后,对象的实例化和依赖关系管理都由IOC容器进行统一管理,主体类只要依赖ioc容器就够了,需要啥,容器会给他注入进去,也就是只要声明对象不用再主动去new,ioc容器帮忙把相

  • Spring Boot中自定义注解结合AOP实现主备库切换问题

    摘要:本篇文章的场景是做调度中心和监控中心时的需求,后端使用TDDL实现分表分库,需求:实现关键业务的查询监控,当用Mybatis查询数据时需要从主库切换到备库或者直接连到备库上查询,从而减小主库的压力,在本篇文章中主要记录在Spring Boot中通过自定义注解结合AOP实现直接连接备库查询. 一.通过AOP 自定义注解实现主库到备库的切换 1.1 自定义注解 自定义注解如下代码所示 import java.lang.annotation.ElementType; import java.la

  • Spring 4 支持的 Java 8 特性

    Spring 框架 4 支持 Java 8 语言和 API 功能.在本文中,我们将重点放在 Spring 4 支持新的 Java 8 的功能.最重要的是 Lambda 表达式,方法引用,JSR-310的日期和时间,和可重复注释. Lambda 表达式 Spring 的代码库使用了 Java 8 大量的函数式接口,Lambda 表达式可以用来编写更干净和紧凑的代码.每当出现函数式接口的对象的预期时我们便可以提供一个 Lambda 表达式.让我们进一步继续之前首先学习函数式接口. 函数式接口 有单一

  • Spring重试支持Spring Retry的方法

    本文介绍了Spring重试支持Spring Retry的方法,分享给大家,具体如下: 第一步.引入maven依赖 <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.3.RELEASE</version> </parent> &l

  • Spring Boot支持Crontab任务改造的方法

    在以往的 Tomcat 项目中,一直习惯用 Ant 打包,使用 build.xml 配置,通过 ant -buildfile 的方式在机器上执行定时任务.虽然 Spring 本身支持定时任务,但都是服务一直运行时支持.其实在项目中,大多数定时任务,还是借助 Linux Crontab 来支持,需要时运行即可,不需要一直占用机器资源.但 Spring Boot 项目或者普通的 jar 项目,就没这么方便了. Spring Boot 提供了类似 CommandLineRunner 的方式,很好的执行

  • Spring MVC项目中log4J和AOP使用详解

    前言 日志处理是每个项目当中一个非常重要的内容.没有了日志,也就失去了对系统的可控性.没有日志,系统出现任何问题,都会没有踪迹可寻,这对一个信息系统而言是非常危险的. 项目中需要将service中的类方法的调用过程,使用log4j日志记录. service中的类和方法都很多,不可能在每个类中单独添加log4j日志记录的功能,因此我们在这里使用AOP的思想进行横向切面. 以service类中的方法为切入点,通过AOP在方法调用前后使用log4j输出日志,内容包括正在调用的类和方法名. 在配置过程中

  • Spring Boot支持HTTPS步骤详解

    Spring Boot中启动HTTPS 如果你使用Spring Boot,并且想在内嵌tomcat中添加HTTPS,需要有一个证书. 两种方式 自己通过keytool生成 通过证书授权机构购买 这里采用第一种方式,采用keytool生成. -genkey 生成秘钥 -alias 别名 -keyalg 秘钥算法 -keysize 秘钥长度 -validity 有效期 -keystore 生成秘钥库的存储路径和名称 -keypass 秘钥口令 -storepass 秘钥库口令 -dname 拥有者信

  • 详谈spring boot中几种常见的依赖注入问题

    目录 @Autowired依赖注入问题–逻辑使用先于@Autowired注解处理 测试用例 BeanFactory.getBean问题–getBean调用先于BeanDefinition信息注册 在Configuration中使用@Autowired注解 spring 实例化Bean过程 @Bean内部使用配置类@Autowired注解引入依赖 InitializingBean#afterPropertiesSet内部使用依赖 总结 最近有空总结一下之前在使用spring boot时遇到过的几种

随机推荐