详谈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 { @Overridepublic void echo() {System.out.println("echo ~"); }} class MethodInvoker implements InvocationHandler { private T invoker; public MethodInvoker(T invoker) {this.invoker = invoker; } @Overridepublic 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(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截图:
详谈Spring是否支持对静态方法进行Aop增强
文章图片

到此处,已经清楚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!"); } @Overridepublic void echo() {System.out.println("echo ~"); }} class EchorMethodInterceptor implements MethodInterceptor {@Overridepublic 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来对比学习,它们之间的区别如下:
详谈Spring是否支持对静态方法进行Aop增强
文章图片


AOP中不得不提的就是代理
详谈Spring是否支持对静态方法进行Aop增强
文章图片

通俗理解就是:茅台公司生产出酒,而代理商拿出来销售并推出各种销售活动。这时茅台公司就是真实主题,也就是目标对象。而代理商就是代理。茅台酒就是目标对象中的方法。各种销售活动就是给目标对象中的方法的增强补充,比如对方法添加日志等等操作。
代理又分为静态代理和动态代理两种:像这样已知目标对象就是为茅台公司 就为静态代理,这时目标对象已确定
【详谈Spring是否支持对静态方法进行Aop增强】
下面为一个静态代理的例子
先定义一个PersonBiz的接口:
详谈Spring是否支持对静态方法进行Aop增强
文章图片

再对这个接口进行实现
详谈Spring是否支持对静态方法进行Aop增强
文章图片

这是我们使用静态代理给这两个方法加上一个操作时间的功能,我就直接上代码了:
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; }@Overridepublic void add(String name) {// 加入关注点-》增强的功能showLog(); // 前置增强// 再调用真实主题的方法this.personBiz.add(name); }@Overridepublic String find() {// 调用真实主题的方法personBiz.find(); // 加入关注点-》增强的功能showLog(); // 后置增强return null; }private void showLog() {Date d = new Date(); System.out.println("-----------------"); System.out.println("操作时间" + d); System.out.println("-----------------"); }}

最后就是测试类:
详谈Spring是否支持对静态方法进行Aop增强
文章图片

代理的优势很明显:当你不需要新增的操作时间的功能时,就将PersonBizProxy pbp=new PersonBizProxy(pb); 去掉即可,后面改用pb调用方法,让代码很好的实现了可扩展性,也不用在原来已有的代码上修改。
静态代理的缺点:只能针对一个接口进行代理
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

    推荐阅读