使用 Java AOP API 完成动态代理的一些注意事项

代码示例

Java原生API中,动态代理常用的API有两个:InvocationHandler接口和Proxy类

首先上代码StaffLoggerAspect.java

public class StaffLoggerAspect implements InvocationHandler {

    Object target;

    public Object getObject(Object object) {
        target = object;
        return Proxy.newProxyInstance(Staff.class.getClassLoader(), Staff.class.getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(proxy.getClass().getName());
        return method.invoke(target, args);
    }
}

Main类的main方法

public static void main(String[] args) {
    StaffLoggerAspect aspect = new StaffLoggerAspect();
    Staff staff1 = new Staff();
    staff1.setName("");
    System.out.println(staff1);
    Object staff2 = aspect.getObject(staff1);
    System.out.println(staff2);
}

输出结果

Staff{name=‘‘, age=0}
com.sun.proxy.$Proxy0
Staff{name=‘‘, age=0}

注意事项

先来解释getObject方法,很多网上的教程都有某种函数用来做类似的事情,这个函数用来返回一个与被代理对象实现了相同接口的代理对象

注意它返回的是代理对象,而不是原对象。代理对象是Proxy类的子类(API文档),实现了被代理对象的所有接口,所以对这个函数结果进行强制转换的话必须转换成对应的接口类型而不是对象类型,对于没有接口的对象怎么办呢?只能转换成Object对象了,当然你能够通过代理使用的方法也只有Object自带的equals, toString之类的了,一些Object方法不会触发invoke方法,详见后边Proxy对象特征最后一条。代理对象的类名是预留类名详见Java API对于Proxy类的解释

Java API Proxy 部分原文及解释

Proxy类的特征

A proxy class has the following properties:

  • Proxy classes are public, final, and not abstract. (即不可继承)
  • The unqualified name of a proxy class is unspecified. The space of class names that begin with the string “$Proxy” should be, however, reserved for proxy classes. (代理对象类名是没有明确定义的,但是以$Proxy开头的类名要给代理对象保留着,所以标准情况下如果发现某个class的类名以$Proxy开头,那它肯定是代理对象,具体可见输出结果)
  • A proxy class extends java.lang.reflect.Proxy. (代理对象全是这个类的子类,所以可以用instanceof来判断是否是代理对象)
  • A proxy class implements exactly the interfaces specified at its creation, in the same order.(代理对象和被代理对象所实现接口完全一致,连顺序也一致,顺序是做什么用的呢?原文档后边有一节标题是Methods Duplicated in Multiple Proxy Interfaces,这个顺序即是用来处理多接口具有相同函数声明的情况的,这里不详述)
  • If a proxy class implements a non-public interface, then it will be defined in the same package as that interface. Otherwise, the package of a proxy class is also unspecified. Note that package sealing will not prevent a proxy class from being successfully defined in a particular package at runtime, and neither will classes already defined by the same class loader and the same package with particular signers.(好长一串,意思是:如果公共接口,那么包名不确定,即使是包的内部接口,其它包访问不了,如果进行了代理,那么包里也会多出来这么一个代理类。后面说的跟代理无关了,说的是使用反射动态创建对象的话,包密闭是防不住色狼类的,完全阻止不了)
  • Since a proxy class implements all of the interfaces specified at its creation, invoking getInterfaces on its Class object will return an array containing the same list of interfaces (in the order specified at its creation), invoking getMethods on its Class object will return an array of Method objects that include all of the methods in those interfaces, and invoking getMethod will find methods in the proxy interfaces as would be expected.(因为之前说的一些原理,所以代理对象的getInterfaces返回被代理对象所有接口,顺序一致,代理对象的getMethod返回那些接口的方法)
  • The Proxy.isProxyClass method will return true if it is passed a proxy class– a class returned by Proxy.getProxyClass or the class of an object returned by Proxy.newProxyInstance– and false otherwise.(代理对象两种获取方式:Proxy.getProxyClass或者Proxy.newProxyInstance其它方式获取的Proxy.isProxyClass会返回false)
  • The java.security.ProtectionDomain of a proxy class is the same as that of system classes loaded by the bootstrap class loader, such as java.lang.Object, because the code for a proxy class is generated by trusted system code. This protection domain will typically be granted java.security.AllPermission.(代理对象权限极高,具有java.security.AllPermission,和java.lang.Object及其它的启动对象一致)
  • Each proxy class has one public constructor that takes one argument, an implementation of the interface InvocationHandler, to set the invocation handler for a proxy instance. Rather than having to use the reflection API to access the public constructor, a proxy instance can be also be created by calling the Proxy.newProxyInstance method, which combines the actions of calling Proxy.getProxyClass with invoking the constructor with an invocation handler.(代理类具有一个public单参数构造函数,需要InvocationHandler对象作为输入参数)

Proxy对象的特征

  • Given a proxy instance proxy and one of the interfaces implemented by its proxy class Foo, the following expression will return true:

    proxy instanceof Foo

and the following cast operation will succeed (rather than throwing a ClassCastException):

(Foo) proxy

(首先要注意断句,看英文的话implemented by后边很容易搞错,应该是its proxy class / Foo, 整体上是这样的: Given a proxy instance proxy and one of its interfaces, for example Foo, … Foo是接口,不是代理类的对象!!!根据之前proxy对象的说明,这一点本身没什么难理解的,它一加说明反而容易弄错)

  • Each proxy instance has an associated invocation handler, the one that was passed to its constructor. The static Proxy.getInvocationHandler method will return the invocation handler associated with the proxy instance passed as its argument.(废话,就是说想要拿到某个代理对象的InvocationHandler的话调用Proxy.getInvocationHandler方法)
  • An interface method invocation on a proxy instance will be encoded and dispatched to the invocation handler‘s invoke method as described in the documentation for that method. (代理对象上方法调用会发送到与之关联的InvocationHandler对象的invoke方法,注意所有方法调用都会被转发,需要在invoke里判断是不是你要弄的那个方法)
  • An invocation of the hashCode, equals, or toString methods declared in java.lang.Object on a proxy instance will be encoded and dispatched to the invocation handler‘s invoke method in the same manner as interface method invocations are encoded and dispatched, as described above. The declaring class of the Method object passed to invoke will be java.lang.Object. Other public methods of a proxy instance inherited from java.lang.Object are not overridden by a proxy class, so invocations of those methods behave like they do for instances of java.lang.Object.(hashCode, equals, toString方法也会转发到proxy对象InvocationHandler的invoke方法,其它没有转发!!!重要!!!要注意!!!)

使用说明

文档重要部分解释完了,这里补充一些实际使用时候用的上的包括思考方式等。

最开始我看到那个getObject里target设置的不是Proxy对象有点转不过来,其实要想清楚,过程是这样的:

  1. 你在外边用代理获得的是proxy对象,而不是原来类型的对象了,所以return的是代理对象,这样在调用接口的方法时才能转发过来
  2. 这里存储一个target是很有必要的,为什么呢,因为要保存被代理对象的信息。像代理对象创建的时候完全不需要被代理对象的实体,只需要其类的信息,而没有记录对象本身,示例代码里Staff的name的值有很多可能,我们对于不同staff对象做代理的话,希望被代理对象对应的getName不会出问题,那么我们需要一个原来对象的target来调用对应的方法,所以要记录一下,因此,通常情况下这个target在getObject被赋值,在invoke方法中被使用
  3. invoke方法第一个参数是代理对象,而且不要在invoke里调用代理对象上可以被转发的方法,比如你对toString进行了处理,然后你在invoke方法里还调用了proxy对象的toString方法(比如隐式的:System.out.println(proxy)这样),这种是绝对禁止的!因为你在代理对象上调用转发方法的话会引起再次调用invoke函数,从而导致像递归一样的结果,而大部分操作系统对递归层数是有限制的,会引发严重后果,所以如果没必要不要在invoke里调用代理对象的方法,但是你调用System.out.println(target)是安全的,因为会进行转发的只有代理对象的对应方法,被代理对象的直接方法调用是不经过转发的!!!

使用Java API动态代理的注意事项就简单说到这里,对于Method对象的使用和注意事项请自行查找相关API或教程,CGLib动态代理实现原理有所不同,相似性肯定有,但是请注意和Java原生API动态代理的区别(Java原生通过反射直接生成getInterfaces()得到的那些接口的一个对象,并不管被代理对象extends的部分,所以构造非常快,但是执行的时候性能低,因为需要各种转发;CGLib通过反射直接生成被代理对象的子类,所以不可用于final类的代理,因为这种类不可被继承,同时会把invoke的内容写入被生成的代理对象里,所以生成的时候会很慢,但是这种对象直接就是被代理对象相同类型的对象,毕竟是生成被代理类的子类,所以使用方便,被代理对象可以不需要接口,而且执行方法速度很快。因为原理区别比较大,CGLib肯定还有其它与Java API不同的特征,笔者暂时没有时间研究CGLib的特点,暂不详述)。在选择上,对于单例模式的对象,或者说相比于对象方法调用的次数,构造次数很少的对象建议用CGLib,对于需要大量构造(比如数据库的一条记录,网络服务的一个Request对象),相对而言每个对象方法调用次数不是很多的对象,建议使用Java原生API。

发现文中有不当之处,希望指正,尤其CGLib和Java API比较部分,是从原理出发的一些推断,并不确定,建议大家去看CGLib的相关文档。

笔者撰文不易,转载请注明出处,拜谢

时间: 2024-10-21 23:44:21

使用 Java AOP API 完成动态代理的一些注意事项的相关文章

Spring之AOP原理_动态代理

面向方面编程(Aspect Oriented Programming,简称AOP)是一种声明式编程(Declarative Programming).声明式编程是和命令式编程(Imperative Programming)相对的概念.我们平时使用的编程语言,比如C++.Java.Ruby.Python等,都属命令式编程.命令式编程的意思是,程序员需要一步步写清楚程序需要如何做什么(How to do What).声明式编程的意思是,程序员不需要一步步告诉程序如何做,只需要告诉程序在哪些地方做什么

简谈Java 反射机制,动态代理

谈谈 Java 反射机制,动态代理是基于什么原理?小编整理了一些java进阶学习资料和面试题,需要资料的请加JAVA高阶学习Q群:701136382 这是小编创建的java高阶学习交流群,加群一起交流学习深造.群里也有小编整理的2019年最新最全的java高阶学习资料! 反射机制 Java 语言提供的一种基础功能,赋予程序在运行时自省(introspect,官方用语)的能力.可以在运行时通过提供完整的"包名+类名.class"得到某个对象的类型. 功能 在运行时能判断任意一个对象所属的

Java语言中反射动态代理接口的解释与演示

Java语言中反射动态代理接口的解释与演示 Java在JDK1.3的时候引入了动态代理机制.可以运用在框架编程与平台编程时候捕获事件.审核数据.日志等功能实现,首先看一下设计模式的UML图解: 当你调用一个接口API时候,实际实现类继承该接口,调用时候经过proxy实现. 在Java中动态代理实现的两个关键接口类与class类分别如下: java.lang.reflect.Proxy java.lang.reflect.InvocationHandler 我们下面就通过InvocationHan

Spring AOP中的动态代理

0  前言 1  动态代理 1.1 JDK动态代理 1.2 CGLIB动态代理 1.2.1 CGLIB的代理用法 1.2.2 CGLIB的过滤功能 2  Spring AOP中的动态代理机制 2.1 JdkDynamicAopProxy 2.2 CglibAopProxy 3 总结 0  前言 前一个季度旅游TDC的Thames服务有几次宕机,根据组内原因认真查找发现是数据库事务造成的,后来把服务中的事务配置全部去掉,服务恢复正常.根据这次教训,虽然现在还是很难确定是哪一个方面的真正原因,但是激

学习Spring必学的Java基础知识(2)----动态代理

学习Spring必学的Java基础知识(2)----动态代理 引述要学习Spring框架的技术内幕,必须事先掌握一些基本的Java知识,正所谓“登高必自卑,涉远必自迩”.以下几项Java知识和Spring框架息息相关,不可不学(我将通过一个系列分别介绍这些Java基础知识,希望对大家有所帮助.): [1] Java反射知识-->Spring IoC :http://www.iteye.com/topic/1123081 [2] Java动态代理-->Spring AOP :http://www

java反射机制与动态代理

在学习HadoopRPC时,用到了函数调用,函数调用都是采用的java的反射机制和动态代理来实现的,所以现在回顾下java的反射和动态代理的相关知识. 一.反射 JAVA反射机制定义: JAVA反射机制是java程序在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法:这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制. 反射就是把Java类中的各种成分映射成相应的Java类. Java反射机制主要提供了以下功能: 1

Java中两种动态代理的实现

本文介绍了java中两种动态代理的实现方法,Spring的动态代理也是基于这两种方法的.直接附上源码: 1.JDK实现 使用JDK实现动态代理必须使用接口 接口Work.java public interface Work { public void work(); } 实现类WorkImpl.java public class WorkImpl implements Work { @Override public void work() { System.out.println("我在工作&q

[Java]Spring AOP基础知识-动态代理

Spring AOP使用动态代理技术在运行期织入增强的代码,为了揭示Spring AOP底层的工作机理,有必要对涉及到的Java知识进行学习.Spring AOP使用了两种代理机制:一种是基于JDK的动态代理:另一种是基于CGLib的动态代理.之所以需要两种代理机制,很大程度上是因为JDK本身只提供接口的代理,而不支持类的代理. 带有横切逻辑的实例 我们通过具体化代码实现上一节所介绍例子的性能监视横切逻辑,并通过动态代理技术对此进行改造.在调用每一个目标类方法时启动方法的性能监视,在目标类方法调

Java实现AOP切面(动态代理)

Java.lang.reflect包下,提供了实现代理机制的接口和类: public interface InvocationHandler InvocationHandler 是代理实例的调用处理程序 实现的接口. public class Proxy extends Object implements Serializable Proxy 提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类. Java的动态代理依赖于接口,虽然在生成效率上较高,但是执行效率比较