您的位置:首页 > 编程语言 > Java开发

Java反射和动态代理详解和实例

2014-09-09 14:26 851 查看


一、反射

JAVA反射机制定义: JAVA反射机制是java程序在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

反射就是把Java类中的各种成分映射成相应的Java类。

Java反射机制主要提供了以下功能:

1、在运行时判断任意一个对象所属的类;

2、在运行时构造任意一个类的对象;

3、在运行时判断任意一个类所具有的成员变量和方法;

4、在运行时调用任意一个对象的方法;

5、生成动态代理。

我们了解了反射机制的功能,那么我们如何具体来实现它呢?

一个类中的每个成员都可以用相应的反射包中的一个类的实例对象来表示,通过Class类的方法可以得到这些实例对象;这个“成员”就包括:类的构造函数、成员变量、方法等一些信息。而他们所对应的反射包中的类分别是:Constructor类、Field类、Method类。

这里只要讲讲动态代理的知识。

二、动态代理
  Java动态代理类位于Java.lang.reflect包下,一般主要涉及到以下两个类:

    (1). Interface InvocationHandler:该接口中仅定义了一个方法Object:invoke(Object obj,Method method, Object[] args)。在实际使用时,第一个参数obj一般是指代理类,method是被代理的方法,如上例中的request(),args为该方法的参数数组。 这个抽象方法在代理类中动态实现。

    (2).Proxy:该类即为动态代理类,作用类似于上例中的ProxySubject,其中主要包含以下内容:

      Protected Proxy(InvocationHandler h):构造函数,估计用于给内部的h赋值。

      Static Class getProxyClass (ClassLoader loader, Class[] interfaces):获得一个代理类,其中loader是类装载器,interfaces是真实类所拥有的全部接口的数组。
      Static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h):返回代理类的一个实例,返回后的代理类可以当作被代理类使用(可使用被代理类的在Subject接口中声明过的方法)。

 实例:

1、定义一个HelloWorld接口

[java] view
plaincopy





/**

* 定义一个HelloWorld接口

*

* @author

*

*/

public interface HelloWorld {

public void sayHelloWorld();

}

2、类HelloWorldImpl是HelloWorld接口的实现

[java] view
plaincopy





/**

* 类HelloWorldImpl是HelloWorld接口的实现

*

* @author

*

*/

public class HelloWorldImpl implements HelloWorld{

public void sayHelloWorld() {

System.out.println("HelloWorld!");

}

}

3、HelloWorldHandler是 InvocationHandler接口实现

[java] view
plaincopy





import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Method;

/**

* 实现在方法调用前后向控制台输出两句字符串

*

* @author

*

*/

public class HelloWorldHandler implements InvocationHandler{

//要代理的原始对象

private Object obj;

public HelloWorldHandler(Object obj) {

super();

this.obj = obj;

}

/**

* 在代理实例上处理方法调用并返回结果

*

* @param proxy 代理类

* @param method 被代理的方法

* @param args 该方法的参数数组

*/

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

Object result = null;

//调用之前

doBefore();

//调用原始对象的方法

result=method.invoke(obj, args);

//调用之后

doAfter();

return result;

}

private void doBefore(){

System.out.println("before method invoke");

}

private void doAfter(){

System.out.println("after method invoke");

}

}

4、测试类

[java] view
plaincopy





import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Proxy;

public class HelloWorldTest {

public static void main(String[] args) {

HelloWorld helloWorld=new HelloWorldImpl();

InvocationHandler handler=new HelloWorldHandler(helloWorld);

//创建动态代理对象

HelloWorld proxy=(HelloWorld)Proxy.newProxyInstance(

helloWorld.getClass().getClassLoader(),

helloWorld.getClass().getInterfaces(),

handler);

proxy.sayHelloWorld();

}

}

运行结果为:

before method invoke

HelloWorld

after method invoke

总结:

一个典型的动态代理创建对象过程可分为以下四个步骤:

1、通过实现InvocationHandler接口创建自己的调用处理器 IvocationHandler handler = new InvocationHandlerImpl(...);

2、通过为Proxy类指定ClassLoader对象和一组interface创建动态代理类

Class clazz = Proxy.getProxyClass(classLoader,new Class[]{...});

3、通过反射机制获取动态代理类的构造函数,其参数类型是调用处理器接口类型

Constructor constructor = clazz.getConstructor(new Class[]{InvocationHandler.class});

4、通过构造函数创建代理类实例,此时需将调用处理器对象作为参数被传入Interface Proxy = (Interface)constructor.newInstance(new Object[] (handler));

为了简化对象创建过程,Proxy类中的newInstance方法封装了2~4,只需两步即可完成代理对象的创建。

生成的ProxySubject继承Proxy类实现Subject接口,实现的Subject的方法实际调用处理器的invoke方法,而invoke方法利用反射调用的是被代理对象的的方法(Object result=method.invoke(proxied,args))

综合上面所说的,作为一个Dynamic Proxy,它必须满足以下三个条件:

1、实现了InvocationHandler接口,实现接口中定义的invoke方法;

2、包含接口实现类的实例;

3、通过Proxy.newProxyInstance方法实现Proxy与接口之间的绑定

再来回顾一下定义

Dynamic Proxy是这样一种class:它是在运行时生成的class,在生成它时你必须提供一组interface给它,然后该class就宣称它实现了这些 interface。你当然可以把该class的实例当作这些interface中的任何一个来用。当然啦,这个Dynamic Proxy其实就是一个Proxy,它不会替你作实质性的工作,在生成它的实例时你必须提供一个handler,由它接管实际的工作 。


代理

一、代理的概念与作用

程序中的代理:要为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,例如:异常处理,日志,计算方法的运行时间,事务管理等等。

编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码。

如果采用工厂模式和配置文件的方法进行管理,则不需要修改客户端程序,在配置文件中配置是使用目标类,还是代理类,这样以后很容易切换,譬如,想要日志功能时就配置代理类,否则配置目标类,这样增加系统功能很容易,以后运行一段时间后,又想去掉系统功能也很容易。

代理框架图如下:



面向方面的编程(Aspect oriented program,简称AOP),AOP的目标就是要使交叉业务模块化,这与直接在方法中编写切面代码的运行效果是一样的,如下图所示:



代理技术正好解决这种问题,代理是实现AOP功能的核心和关键技术。

动态代理技术:

要为系统中的各种接口增加代理功能,那将需要太多的代理类,全部采用静态代理方式,将是一件非常麻烦的事情,写成千上百个代理类是不现实的。
JVM可以在运行期间动态生成类的字节码,这种动态生成的类往往被用作代理类。即动态代理。
JVM生成的动态类必须实现一个或多个接口,所以JVM生成的动态类只能用作具有相同接口的目标类的代理。
CGLIB库可以动态生成一个类的子类,一个类的子类也可以用做该类的代理,所以如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库。
代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下四个位置上加上系统功能代码:

1.在调用目标方法之前

2.在调用目标方法之后

3.在调用目标方法前后

4.在处理目标方法异常的catch块中。

二、代码示例

例1:演示创建动态类及查看其方法列表信息

[java] view
plaincopy

import java.lang.reflect.Constructor;

import java.lang.reflect.Method;

import java.lang.reflect.Proxy;

import java.util.Collection;

public class ProxyDemo {

public static void main(String[] args) {

// TODO Auto-generated method stub

Class proxy=Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);

System.out.println(proxy.getName());

System.out.println("-------Constructors List ---------");

//按照以下格式输出构造函数列表,带参数

//$Proxy0

//$Proxy0(参数列表)

Constructor[] constructors=proxy.getConstructors();

for(Constructor constructor:constructors){

String name=constructor.getName();

System.out.println(name);

StringBuilder stringBuilder=new StringBuilder();

Class[] parames=constructor.getParameterTypes();

stringBuilder.append(name);

stringBuilder.append('(');

for(Class parame:parames){

stringBuilder.append(parame.getName()).append(",");

}

if(parames!=null&¶mes.length!=0)//为什么只用null判断不行呢???

stringBuilder.deleteCharAt(stringBuilder.length()-1);

stringBuilder.append(')');

System.out.println(stringBuilder.toString());

}

System.out.println("-------Methods List ---------");

//和上面的格式相同

Method[] methods=proxy.getMethods();

for(Method method:methods){

String name=method.getName();

//System.out.println(name);

StringBuilder stringBuilder=new StringBuilder();

Class[] parames=method.getParameterTypes();

stringBuilder.append(name);

stringBuilder.append('(');

for(Class parame:parames){

stringBuilder.append(parame.getName());

stringBuilder.append(",");

}

if(parames!=null&¶mes.length!=0)//为什么只用null判断不行呢???

stringBuilder.deleteCharAt(stringBuilder.length()-1);

stringBuilder.append(')');

System.out.println(stringBuilder.toString());

}

}

}

输出结果是:



例2:创建动态类的实例对象

[java] view
plaincopy

import java.lang.reflect.Constructor;

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Method;

import java.lang.reflect.Proxy;

import java.util.ArrayList;

import java.util.Collection;

public class ProxyDemo2 {

public static void main(String[] args) throws Exception {

// TODO Auto-generated method stub

Class proxy=Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);

Constructor constructor=proxy.getConstructor(InvocationHandler.class);

//第一种创建实例对象的方法

class MyInvocationHandler implements InvocationHandler{

@Override

public Object invoke(Object proxy, Method method, Object[] args)

throws Throwable {

// TODO Auto-generated method stub

return null;

}

}

Collection collection1=(Collection)constructor.newInstance(new MyInvocationHandler());

//第二种创建实例对象的方法

Collection collection2=(Collection)constructor.newInstance(new InvocationHandler(){

@Override

public Object invoke(Object proxy, Method method, Object[] args)

throws Throwable {

// TODO Auto-generated method stub

return null;

}

});

//第三种创建实例对象的方法,直接用Proxy自带的方法,newProxyInstance()

Collection collection3=(Collection)Proxy.newProxyInstance(

Collection.class.getClassLoader(),//第一个参数:类加载器

new Class[]{Collection.class},//与目标相同的接口

new InvocationHandler() {//参数:一个对象

ArrayList target=new ArrayList();

@Override

public Object invoke(Object proxy, Method method, Object[] args)

throws Throwable {

// TODO Auto-generated method stub

Object retVal=method.invoke(target, args);//此处可以修改参数哟!

return retVal;

}

});

collection3.add("123");

collection3.add("123");

collection3.add("123");

System.out.println(collection3.size());

System.out.println(collection3.getClass().getName());//为什么返回的类名不是ArrayList?因为只有这三个hashCode,equals,toString从Object继承的方法交给Handler处理,其余的方法Proxy有处理的方式。

}

}

运行结果是:



关于以上代码的工作图:




动态代理机制

在学习Spring的时候,我们知道Spring主要有两大思想,一个是IoC,另一个就是AOP,对于IoC,依赖注入就不用多说了,而对于Spring的核心AOP来说,我们不但要知道怎么通过AOP来满足的我们的功能,我们更需要学习的是其底层是怎么样的一个原理,而AOP的原理就是java的动态代理机制,所以本篇随笔就是对java的动态机制进行一个回顾。

在java的动态代理机制中,有两个重要的类或接口,一个是 InvocationHandler(Interface)、另一个则是 Proxy(Class),这一个类和接口是实现我们动态代理所必须用到的。首先我们先来看看java的API帮助文档是怎么样对这两个类进行描述的:

InvocationHandler:

InvocationHandler is the interface implemented by the invocation handler of a proxy instance.

Each proxy instance has an associated invocation handler. When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to the invoke method of its invocation handler.


每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。我们来看看InvocationHandler这个接口的唯一一个方法 invoke 方法:

Object invoke(Object proxy, Method method, Object[] args) throws Throwable


我们看到这个方法一共接受三个参数,那么这三个参数分别代表什么呢?

Object invoke(Object proxy, Method method, Object[] args) throws Throwable

proxy:  指代我们所代理的那个真实对象
method:  指代的是我们所要调用真实对象的某个方法的Method对象
args:  指代的是调用真实对象某个方法时接受的参数


如果不是很明白,等下通过一个实例会对这几个参数进行更深的讲解。

接下来我们来看看Proxy这个类:

Proxy provides static methods for creating dynamic proxy classes and instances, and it is also the superclass of all dynamic proxy classes created by those methods.


Proxy这个类的作用就是用来动态创建一个代理对象的类,它提供了许多的方法,但是我们用的最多的就是 newProxyInstance 这个方法:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,  InvocationHandler h)  throws IllegalArgumentException


Returns an instance of a proxy class for the specified interfaces that dispatches method invocations to the specified invocation handler.


这个方法的作用就是得到一个动态的代理对象,其接收三个参数,我们来看看这三个参数所代表的含义:



public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException

loader:  一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载

interfaces:  一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了

h:  一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上




好了,在介绍完这两个接口(类)以后,我们来通过一个实例来看看我们的动态代理模式是什么样的:

首先我们定义了一个Subject类型的接口,为其声明了两个方法:

public interface Subject
{
public void rent();

public void hello(String str);
}


接着,定义了一个类来实现这个接口,这个类就是我们的真实对象,RealSubject类:



public class RealSubject implements Subject
{
@Override
public void rent()
{
System.out.println("I want to rent my house");
}

@Override
public void hello(String str)
{
System.out.println("hello: " + str);
}
}




下一步,我们就要定义一个动态代理类了,前面说个,每一个动态代理类都必须要实现 InvocationHandler 这个接口,因此我们这个动态代理类也不例外:



public class DynamicProxy implements InvocationHandler
{
// 这个就是我们要代理的真实对象
private Object subject;

//    构造方法,给我们要代理的真实对象赋初值
public DynamicProxy(Object subject)
{
this.subject = subject;
}

@Override
public Object invoke(Object object, Method method, Object[] args)
throws Throwable
{
//  在代理真实对象前我们可以添加一些自己的操作
System.out.println("before rent house");

System.out.println("Method:" + method);

//    当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
method.invoke(subject, args);

//  在代理真实对象后我们也可以添加一些自己的操作
System.out.println("after rent house");

return null;
}

}




最后,来看看我们的Client类:



public class Client
{
public static void main(String[] args)
{
//    我们要代理的真实对象
Subject realSubject = new RealSubject();

//    我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的
InvocationHandler handler = new DynamicProxy(realSubject);

/*
* 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数
* 第一个参数 handler.getClass().getClassLoader() ,我们这里使用handler这个类的ClassLoader对象来加载我们的代理对象
* 第二个参数realSubject.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实行的接口,表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了
* 第三个参数handler, 我们这里将这个代理对象关联到了上方的 InvocationHandler 这个对象上
*/
Subject subject = (Subject)Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject
.getClass().getInterfaces(), handler);

System.out.println(subject.getClass().getName());
subject.rent();
subject.hello("world");
}
}




我们先来看看控制台的输出:



$Proxy0

before rent house
Method:public abstract void com.xiaoluo.dynamicproxy.Subject.rent()
I want to rent my house
after rent house

before rent house
Method:public abstract void com.xiaoluo.dynamicproxy.Subject.hello(java.lang.String)
hello: world
after rent house




我们首先来看看 $Proxy0 这东西,我们看到,这个东西是由 System.out.println(subject.getClass().getName()); 这条语句打印出来的,那么为什么我们返回的这个代理对象的类名是这样的呢?

Subject subject = (Subject)Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject
.getClass().getInterfaces(), handler);


可能我以为返回的这个代理对象会是Subject类型的对象,或者是InvocationHandler的对象,结果却不是,首先我们解释一下为什么我们这里可以将其转化为Subject类型的对象?原因就是在newProxyInstance这个方法的第二个参数上,我们给这个代理对象提供了一组什么接口,那么我这个代理对象就会实现了这组接口,这个时候我们当然可以将这个代理对象强制类型转化为这组接口中的任意一个,因为这里的接口是Subject类型,所以就可以将其转化为Subject类型了。

同时我们一定要记住,通过 Proxy.newProxyInstance 创建的代理对象是在jvm运行时动态生成的一个对象,它并不是我们的InvocationHandler类型,也不是我们定义的那组接口的类型,而是在运行是动态生成的一个对象,并且命名方式都是这样的形式,以$开头,proxy为中,最后一个数字表示对象的标号。

接着我们来看看这两句

subject.rent();

subject.hello("world");

这里是通过代理对象来调用实现的那种接口中的方法,这个时候程序就会跳转到由这个代理对象关联到的 handler 中的invoke方法去执行,而我们的这个 handler 对象又接受了一个 RealSubject类型的参数,表示我要代理的就是这个真实对象,所以此时就会调用 handler 中的invoke方法去执行:



public Object invoke(Object object, Method method, Object[] args)
throws Throwable
{
//  在代理真实对象前我们可以添加一些自己的操作
System.out.println("before rent house");

System.out.println("Method:" + method);

//    当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
method.invoke(subject, args);

//  在代理真实对象后我们也可以添加一些自己的操作
System.out.println("after rent house");

return null;
}




我们看到,在真正通过代理对象来调用真实对象的方法的时候,我们可以在该方法前后添加自己的一些操作,同时我们看到我们的这个 method 对象是这样的:

public abstract void com.xiaoluo.dynamicproxy.Subject.rent()

public abstract void com.xiaoluo.dynamicproxy.Subject.hello(java.lang.String)


正好就是我们的Subject接口中的两个方法,这也就证明了当我通过代理对象来调用方法的时候,起实际就是委托由其关联到的 handler 对象的invoke方法中来调用,并不是自己来真实调用,而是通过代理的方式来调用的。

这就是我们的java动态代理机制

本篇随笔详细的讲解了java中的动态代理机制,这个知识点非常非常的重要,包括我们Spring的AOP其就是通过动态代理的机制实现的,所以我们必须要好好的理解动态代理的机制。


代理:设计模式

代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。

图 1. 代理模式



为了保持行为的一致性,代理类和委托类通常会实现相同的接口,所以在访问者看来两者没有丝毫的区别。通过代理类这中间一层,能有效控制对委托类对象的直接访问,也可以很好地隐藏和保护委托类对象,同时也为实施不同控制策略预留了空间,从而在设计上获得了更大的灵活性。Java 动态代理机制以巧妙的方式近乎完美地实践了代理模式的设计理念。

回页首

相关的类和接口

要了解 Java 动态代理的机制,首先需要了解以下相关的类或接口:

java.lang.reflect.Proxy:这是 Java 动态代理机制的主类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。

清单 1. Proxy 的静态方法

// 方法 1: 该方法用于获取指定代理对象所关联的调用处理器
static InvocationHandler getInvocationHandler(Object proxy)

// 方法 2:该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象
static Class getProxyClass(ClassLoader loader, Class[] interfaces)

// 方法 3:该方法用于判断指定类对象是否是一个动态代理类
static boolean isProxyClass(Class cl)

// 方法 4:该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
static Object newProxyInstance(ClassLoader loader, Class[] interfaces,
InvocationHandler h)
java.lang.reflect.InvocationHandler:这是调用处理器接口,它自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。

清单 2. InvocationHandler 的核心方法

// 该方法负责集中处理动态代理类上的所有方法调用。第一个参数既是代理类实例,第二个参数是被调用的方法对象
// 第三个方法是调用参数。调用处理器根据这三个参数进行预处理或分派到委托类实例上发射执行
Object invoke(Object proxy, Method method, Object[] args)
每次生成动态代理类对象时都需要指定一个实现了该接口的调用处理器对象(参见 Proxy 静态方法 4 的第三个参数)。

java.lang.ClassLoader:这是类装载器类,负责将类的字节码装载到 Java 虚拟机(JVM)中并为其定义类对象,然后该类才能被使用。Proxy 静态方法生成动态代理类同样需要通过类装载器来进行装载才能使用,它与普通类的唯一区别就是其字节码是由 JVM 在运行时动态生成的而非预存在于任何一个 .class 文件中。

每次生成动态代理类对象时都需要指定一个类装载器对象(参见 Proxy 静态方法 4 的第一个参数)

回页首

代理机制及其特点

首先让我们来了解一下如何使用 Java 动态代理。具体有如下四步骤:

通过实现 InvocationHandler 接口创建自己的调用处理器;

通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类;

通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;

通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。

清单 3. 动态代理对象创建过程

// InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
// 其内部通常包含指向委托类实例的引用,用于真正执行分派转发过来的方法调用
InvocationHandler handler = new InvocationHandlerImpl(..);

// 通过 Proxy 为包括 Interface 接口在内的一组接口动态创建代理类的类对象
Class clazz = Proxy.getProxyClass(classLoader, new Class[] { Interface.class, ... });

// 通过反射从生成的类对象获得构造函数对象
Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class });

// 通过构造函数对象创建动态代理类实例
Interface Proxy = (Interface)constructor.newInstance(new Object[] { handler });
实际使用过程更加简单,因为 Proxy 的静态方法 newProxyInstance 已经为我们封装了步骤 2 到步骤 4 的过程,所以简化后的过程如下

清单 4. 简化的动态代理对象创建过程

// InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
InvocationHandler handler = new InvocationHandlerImpl(..);

// 通过 Proxy 直接创建动态代理类实例
Interface proxy = (Interface)Proxy.newProxyInstance( classLoader,
new Class[] { Interface.class },
handler );
接下来让我们来了解一下 Java 动态代理机制的一些特点。

首先是动态生成的代理类本身的一些特点。1)包:如果所代理的接口都是 public 的,那么它将被定义在顶层包(即包路径为空),如果所代理的接口中有非 public 的接口(因为接口不能被定义为 protect 或 private,所以除 public 之外就是默认的 package 访问级别),那么它将被定义在该接口所在包(假设代理了 com.ibm.developerworks 包中的某非 public 接口 A,那么新生成的代理类所在的包就是 com.ibm.developerworks),这样设计的目的是为了最大程度的保证动态代理类不会因为包管理的问题而无法被成功定义并访问;2)类修饰符:该代理类具有
final 和 public 修饰符,意味着它可以被所有的类访问,但是不能被再度继承;3)类名:格式是“$ProxyN”,其中 N 是一个逐一递增的阿拉伯数字,代表 Proxy 类第 N 次生成的动态代理类,值得注意的一点是,并不是每次调用 Proxy 的静态方法创建动态代理类都会使得 N 值增加,原因是如果对同一组接口(包括接口排列的顺序相同)试图重复创建动态代理类,它会很聪明地返回先前已经创建好的代理类的类对象,而不会再尝试去创建一个全新的代理类,这样可以节省不必要的代码重复生成,提高了代理类的创建效率。4)类继承关系:该类的继承关系如图:

图 2. 动态代理类的继承图



由图可见,Proxy 类是它的父类,这个规则适用于所有由 Proxy 创建的动态代理类。而且该类还实现了其所代理的一组接口,这就是为什么它能够被安全地类型转换到其所代理的某接口的根本原因。

接下来让我们了解一下代理类实例的一些特点。每个实例都会关联一个调用处理器对象,可以通过 Proxy 提供的静态方法 getInvocationHandler 去获得代理类实例的调用处理器对象。在代理类实例上调用其代理的接口中所声明的方法时,这些方法最终都会由调用处理器的 invoke 方法执行,此外,值得注意的是,代理类的根类 java.lang.Object 中有三个方法也同样会被分派到调用处理器的 invoke 方法执行,它们是 hashCode,equals 和 toString,可能的原因有:一是因为这些方法为
public 且非 final 类型,能够被代理类覆盖;二是因为这些方法往往呈现出一个类的某种特征属性,具有一定的区分度,所以为了保证代理类与委托类对外的一致性,这三个方法也应该被分派到委托类执行。当代理的一组接口有重复声明的方法且该方法被调用时,代理类总是从排在最前面的接口中获取方法对象并分派给调用处理器,而无论代理类实例是否正在以该接口(或继承于该接口的某子接口)的形式被外部引用,因为在代理类内部无法区分其当前的被引用类型。

接着来了解一下被代理的一组接口有哪些特点。首先,要注意不能有重复的接口,以避免动态代理类代码生成时的编译错误。其次,这些接口对于类装载器必须可见,否则类装载器将无法链接它们,将会导致类定义失败。再次,需被代理的所有非 public 的接口必须在同一个包中,否则代理类生成也会失败。最后,接口的数目不能超过 65535,这是 JVM 设定的限制。

最后再来了解一下异常处理方面的特点。从调用处理器接口声明的方法中可以看到理论上它能够抛出任何类型的异常,因为所有的异常都继承于 Throwable 接口,但事实是否如此呢?答案是否定的,原因是我们必须遵守一个继承原则:即子类覆盖父类或实现父接口的方法时,抛出的异常必须在原方法支持的异常列表之内。所以虽然调用处理器理论上讲能够,但实际上往往受限制,除非父接口中的方法支持抛 Throwable 异常。那么如果在 invoke 方法中的确产生了接口方法声明中不支持的异常,那将如何呢?放心,Java 动态代理类已经为我们设计好了解决方法:它将会抛出
UndeclaredThrowableException 异常。这个异常是一个 RuntimeException 类型,所以不会引起编译错误。通过该异常的 getCause 方法,还可以获得原来那个不受支持的异常对象,以便于错误诊断。

回页首

代码是最好的老师

机制和特点都介绍过了,接下来让我们通过源代码来了解一下 Proxy 到底是如何实现的。

首先记住 Proxy 的几个重要的静态变量:

清单 5. Proxy 的重要静态变量

// 映射表:用于维护类装载器对象到其对应的代理类缓存
private static Map loaderToCache = new WeakHashMap();

// 标记:用于标记一个动态代理类正在被创建中
private static Object pendingGenerationMarker = new Object();

// 同步表:记录已经被创建的动态代理类类型,主要被方法 isProxyClass 进行相关的判断
private static Map proxyClasses = Collections.synchronizedMap(new WeakHashMap());

// 关联的调用处理器引用
protected InvocationHandler h;
然后,来看一下 Proxy 的构造方法:

清单 6. Proxy 构造方法

// 由于 Proxy 内部从不直接调用构造函数,所以 private 类型意味着禁止任何调用
private Proxy() {}

// 由于 Proxy 内部从不直接调用构造函数,所以 protected 意味着只有子类可以调用
protected Proxy(InvocationHandler h) {this.h = h;}
接着,可以快速浏览一下 newProxyInstance 方法,因为其相当简单:

清单 7. Proxy 静态方法 newProxyInstance

public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException {

// 检查 h 不为空,否则抛异常
if (h == null) {
throw new NullPointerException();
}

// 获得与制定类装载器和一组接口相关的代理类类型对象
Class cl = getProxyClass(loader, interfaces);

// 通过反射获取构造函数对象并生成代理类实例
try {
Constructor cons = cl.getConstructor(constructorParams);
return (Object) cons.newInstance(new Object[] { h });
} catch (NoSuchMethodException e) { throw new InternalError(e.toString());
} catch (IllegalAccessException e) { throw new InternalError(e.toString());
} catch (InstantiationException e) { throw new InternalError(e.toString());
} catch (InvocationTargetException e) { throw new InternalError(e.toString());
}
}
由此可见,动态代理真正的关键是在 getProxyClass 方法,该方法负责为一组接口动态地生成代理类类型对象。在该方法内部,您将能看到 Proxy 内的各路英雄(静态变量)悉数登场。有点迫不及待了么?那就让我们一起走进 Proxy 最最神秘的殿堂去欣赏一番吧。该方法总共可以分为四个步骤:

对这组接口进行一定程度的安全检查,包括检查接口类对象是否对类装载器可见并且与类装载器所能识别的接口类对象是完全相同的,还会检查确保是 interface 类型而不是 class 类型。这个步骤通过一个循环来完成,检查通过后将会得到一个包含所有接口名称的字符串数组,记为
String[] interfaceNames
。总体上这部分实现比较直观,所以略去大部分代码,仅保留留如何判断某类或接口是否对特定类装载器可见的相关代码。

清单 8. 通过 Class.forName 方法判接口的可见性

try {
// 指定接口名字、类装载器对象,同时制定 initializeBoolean 为 false 表示无须初始化类
// 如果方法返回正常这表示可见,否则会抛出 ClassNotFoundException 异常表示不可见
interfaceClass = Class.forName(interfaceName, false, loader);
} catch (ClassNotFoundException e) {
}
从 loaderToCache 映射表中获取以类装载器对象为关键字所对应的缓存表,如果不存在就创建一个新的缓存表并更新到 loaderToCache。缓存表是一个 HashMap 实例,正常情况下它将存放键值对(接口名字列表,动态生成的代理类的类对象引用)。当代理类正在被创建时它会临时保存(接口名字列表,pendingGenerationMarker)。标记 pendingGenerationMarke 的作用是通知后续的同类请求(接口数组相同且组内接口排列顺序也相同)代理类正在被创建,请保持等待直至创建完成。

清单 9. 缓存表的使用

do {
// 以接口名字列表作为关键字获得对应 cache 值
Object value = cache.get(key);
if (value instanceof Reference) {
proxyClass = (Class) ((Reference) value).get();
}
if (proxyClass != null) {
// 如果已经创建,直接返回
return proxyClass;
} else if (value == pendingGenerationMarker) {
// 代理类正在被创建,保持等待
try {
cache.wait();
} catch (InterruptedException e) {
}
// 等待被唤醒,继续循环并通过二次检查以确保创建完成,否则重新等待
continue;
} else {
// 标记代理类正在被创建
cache.put(key, pendingGenerationMarker);
// break 跳出循环已进入创建过程
break;
} while (true);
动态创建代理类的类对象。首先是确定代理类所在的包,其原则如前所述,如果都为 public 接口,则包名为空字符串表示顶层包;如果所有非 public 接口都在同一个包,则包名与这些接口的包名相同;如果有多个非 public 接口且不同包,则抛异常终止代理类的生成。确定了包后,就开始生成代理类的类名,同样如前所述按格式“$ProxyN”生成。类名也确定了,接下来就是见证奇迹的发生 —— 动态生成代理类:

清单 10. 动态生成代理类

// 动态地生成代理类的字节码数组
byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces);
try {
// 动态地定义新生成的代理类
proxyClass = defineClass0(loader, proxyName, proxyClassFile, 0,
proxyClassFile.length);
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}

// 把生成的代理类的类对象记录进 proxyClasses 表
proxyClasses.put(proxyClass, null);
由此可见,所有的代码生成的工作都由神秘的 ProxyGenerator 所完成了,当你尝试去探索这个类时,你所能获得的信息仅仅是它位于并未公开的 sun.misc 包,有若干常量、变量和方法以完成这个神奇的代码生成的过程,但是 sun 并没有提供源代码以供研读。至于动态类的定义,则由 Proxy 的 native 静态方法 defineClass0 执行。

代码生成过程进入结尾部分,根据结果更新缓存表,如果成功则将代理类的类对象引用更新进缓存表,否则清楚缓存表中对应关键值,最后唤醒所有可能的正在等待的线程。

走完了以上四个步骤后,至此,所有的代理类生成细节都已介绍完毕,剩下的静态方法如 getInvocationHandler 和 isProxyClass 就显得如此的直观,只需通过查询相关变量就可以完成,所以对其的代码分析就省略了。

回页首

代理类实现推演

分析了 Proxy 类的源代码,相信在读者的脑海中会对 Java 动态代理机制形成一个更加清晰的理解,但是,当探索之旅在 sun.misc.ProxyGenerator 类处嘎然而止,所有的神秘都汇聚于此时,相信不少读者也会对这个 ProxyGenerator 类产生有类似的疑惑:它到底做了什么呢?它是如何生成动态代理类的代码的呢?诚然,这里也无法给出确切的答案。还是让我们带着这些疑惑,一起开始探索之旅吧。

事物往往不像其看起来的复杂,需要的是我们能够化繁为简,这样也许就能有更多拨云见日的机会。抛开所有想象中的未知而复杂的神秘因素,如果让我们用最简单的方法去实现一个代理类,唯一的要求是同样结合调用处理器实施方法的分派转发,您的第一反应将是什么呢?“听起来似乎并不是很复杂”。的确,掐指算算所涉及的工作无非包括几个反射调用,以及对原始类型数据的装箱或拆箱过程,其他的似乎都已经水到渠成。非常地好,让我们整理一下思绪,一起来完成一次完整的推演过程吧。

清单 11. 代理类中方法调用的分派转发推演实现

// 假设需代理接口 Simulator
public interface Simulator {
short simulate(int arg1, long arg2, String arg3) throws ExceptionA, ExceptionB;
}

// 假设代理类为 SimulatorProxy, 其类声明将如下
final public class SimulatorProxy implements Simulator {

// 调用处理器对象的引用
protected InvocationHandler handler;

// 以调用处理器为参数的构造函数
public SimulatorProxy(InvocationHandler handler){
this.handler = handler;
}

// 实现接口方法 simulate
public short simulate(int arg1, long arg2, String arg3)
throws ExceptionA, ExceptionB {

// 第一步是获取 simulate 方法的 Method 对象
java.lang.reflect.Method method = null;
try{
method = Simulator.class.getMethod(
"simulate",
new Class[] {int.class, long.class, String.class} );
} catch(Exception e) {
// 异常处理 1(略)
}

// 第二步是调用 handler 的 invoke 方法分派转发方法调用
Object r = null;
try {
r = handler.invoke(this,
method,
// 对于原始类型参数需要进行装箱操作
new Object[] {new Integer(arg1), new Long(arg2), arg3});
}catch(Throwable e) {
// 异常处理 2(略)
}
// 第三步是返回结果(返回类型是原始类型则需要进行拆箱操作)
return ((Short)r).shortValue();
}
}
模拟推演为了突出通用逻辑所以更多地关注正常流程,而淡化了错误处理,但在实际中错误处理同样非常重要。从以上的推演中我们可以得出一个非常通用的结构化流程:第一步从代理接口获取被调用的方法对象,第二步分派方法到调用处理器执行,第三步返回结果。在这之中,所有的信息都是可以已知的,比如接口名、方法名、参数类型、返回类型以及所需的装箱和拆箱操作,那么既然我们手工编写是如此,那又有什么理由不相信 ProxyGenerator 不会做类似的实现呢?至少这是一种比较可能的实现。

接下来让我们把注意力重新回到先前被淡化的错误处理上来。在异常处理 1 处,由于我们有理由确保所有的信息如接口名、方法名和参数类型都准确无误,所以这部分异常发生的概率基本为零,所以基本可以忽略。而异常处理 2 处,我们需要思考得更多一些。回想一下,接口方法可能声明支持一个异常列表,而调用处理器 invoke 方法又可能抛出与接口方法不支持的异常,再回想一下先前提及的 Java 动态代理的关于异常处理的特点,对于不支持的异常,必须抛 UndeclaredThrowableException 运行时异常。所以通过再次推演,我们可以得出一个更加清晰的异常处理
2 的情况:

清单 12. 细化的异常处理 2

Object r = null;

try {
r = handler.invoke(this,
method,
new Object[] {new Integer(arg1), new Long(arg2), arg3});

} catch( ExceptionA e) {

// 接口方法支持 ExceptionA,可以抛出
throw e;

} catch( ExceptionB e ) {
// 接口方法支持 ExceptionB,可以抛出
throw e;

} catch(Throwable e) {
// 其他不支持的异常,一律抛 UndeclaredThrowableException
throw new UndeclaredThrowableException(e);
}
这样我们就完成了对动态代理类的推演实现。推演实现遵循了一个相对固定的模式,可以适用于任意定义的任何接口,而且代码生成所需的信息都是可知的,那么有理由相信即使是机器自动编写的代码也有可能延续这样的风格,至少可以保证这是可行的。

回页首

美中不足

诚然,Proxy 已经设计得非常优美,但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持 interface 代理的桎梏,因为它的设计注定了这个遗憾。回想一下那些动态生成的代理类的继承关系图,它们已经注定有一个共同的父类叫 Proxy。Java 的继承机制注定了这些动态代理类们无法实现对 class 的动态代理,原因是多继承在 Java 中本质上就行不通。

有很多条理由,人们可以否定对 class 代理的必要性,但是同样有一些理由,相信支持 class 动态代理会更美好。接口和类的划分,本就不是很明显,只是到了 Java 中才变得如此的细化。如果只从方法的声明及是否被定义来考量,有一种两者的混合体,它的名字叫抽象类。实现对抽象类的动态代理,相信也有其内在的价值。此外,还有一些历史遗留的类,它们将因为没有实现任何接口而从此与动态代理永世无缘。如此种种,不得不说是一个小小的遗憾。

但是,不完美并不等于不伟大,伟大是一种本质,Java 动态代理就是佐例


动态代理实例

[java] view
plaincopy





package cn.yws;

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Method;

import java.lang.reflect.Proxy;

/**

* 动态代理类使用到了一个接口InvocationHandler和一个代理类Proxy ,这两个类配合使用实现了动态代理的功能。

* 那么什么是动态代理呢?

* 我们平常说的代理类是指: 给每个具体类写一个代理类,以后要使用某个具体类时,只要创建它的代理类的对象,然后调用代理类的方法就可以了。

* 可是如果现在有许多的具体类,那就需要有许多的代理类才可以,这样很显然不合适。所以动态代理就应运而生了,我们只要写一个类实现

* InvocationHandler 并实现它的invoke方法,然后再用Proxy的工厂方法newProxyInstance()创建一个代理对象,这个对象同样可以实现对具体类的代理功能。

* 而且想代理哪个具体类,只要给Handler(以下代码中的Invoker)的构造器传入这个具体对象的实例就可以了。感觉是不是自己为该具体类造了一个代理类呢?呵呵~

*/

//接口类

interface AbstractClass {

public void show();

}

// 具体类A

class ClassA implements AbstractClass {

@Override

public void show() {

// TODO Auto-generated method stub

System.out.println("我是A类!");

}

}

// 具体类B

class ClassB implements AbstractClass {

@Override

public void show() {

// TODO Auto-generated method stub

System.out.println("我是B类!");

}

}

//动态代理类,实现InvocationHandler动态代理接口

class Invoker implements InvocationHandler {

AbstractClass ac;

public Invoker(AbstractClass ac) {

this.ac = ac;

}

@Override

public Object invoke(Object proxy, Method method, Object[] arg)

throws Throwable {

//调用之前可以做一些处理

//调用代理对象的方法

method.invoke(ac, arg);

//调用之后也可以做一些处理

return null;

}

}

/**

* 动态代理测试

*/

public class DynamicProxyTest {

public static void main(String[] args) {

//创建具体类ClassA的代理对象

Invoker invoker1=new Invoker(new ClassA());

//获得具体类ClassA的代理

AbstractClass ac1 = (AbstractClass) Proxy.newProxyInstance(

AbstractClass.class.getClassLoader(),

new Class[] { AbstractClass.class }, invoker1);

//调用ClassA的show方法。

ac1.show();

//创建具体类ClassB的代理对象

Invoker invoker2=new Invoker(new ClassB());

//获得具体类ClassB的代理

AbstractClass ac2 = (AbstractClass) Proxy.newProxyInstance(

AbstractClass.class.getClassLoader(),

new Class[] { AbstractClass.class }, invoker2);

//调用ClassB的show方法。

ac2.show();

}

}

Java架构基础之动态代理和cglib;

在以前的文章中,有提及到动态代理,它要解决的就是,当我们的某些代码前面或后面都需要一些处理的时候,如写日志、事务控制、做agent、自动化代码跟踪等,此时会给你带来无限的方便,这是JVM级别的提供的一种代理机制,不过在这种机制下调用方法在JVM7出来前还没有invokeDynamic的时候,调用的效率是很低的,此时方法调用都是通过method的invoke去实现。

其基本原理是基于实现JVM提供的一个:

InvocationHandler的接口,实现一个方法叫:public Object invoke(Object proxyed, Method method, Object[] args);

创建类的时候,通过实例化这个类(这个类就是实现InvocationHandler的类),再将实际要实现的类的class放进去,通过Proxy来实例化。

以下为一段简单动态代理的实现代码(以下代码放入一个文件:DynamicProxy.java):

[java] view
plaincopy

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.InvocationTargetException;

import java.lang.reflect.Method;

import java.lang.reflect.Proxy;

//定义了一个接口

interface Hello {

public String getInfos1();

public String getInfos2();

public void setInfo(String infos1, String infos2);

public void display();

}

//定义它的实现类

class HelloImplements implements Hello {

private volatile String infos1;

private volatile String infos2;

public String getInfos1() {

return infos1;

}

public String getInfos2() {

return infos2;

}

public void setInfo(String infos1, String infos2) {

this.infos1 = infos1;

this.infos2 = infos2;

}

public void display() {

System.out.println("\t\t" + infos1 + "\t" + infos2);

}

}

定义AOP的Agent

class AOPFactory implements InvocationHandler {

private Object proxyed;

public AOPFactory(Object proxyed) {

this.proxyed = proxyed;

}

public void printInfo(String info, Object... args) {

System.out.println(info);

if (args == null) {

System.out.println("\t空值。");

}else {

for(Object obj : args) {

System.out.println(obj);

}

}

}

public Object invoke(Object proxyed, Method method, Object[] args) throws IllegalArgumentException, IllegalAccessException,

InvocationTargetException {

System.out.println("\n\n====>调用方法名:" + method.getName());

Class<?>[] variables = method.getParameterTypes();

for(Class<?>typevariables: variables) {

System.out.println("=============>" + typevariables.getName());

}

printInfo("传入的参数为:", args);

Object result = method.invoke(this.proxyed, args);

printInfo("返回的参数为:", result);

printInfo("返回值类型为:", method.getReturnType());

return result;

}

}

//测试调用类

public class DynamicProxy {

public static Object getBean(String className) throws InstantiationException, IllegalAccessException,

ClassNotFoundException {

Object obj = Class.forName(className).newInstance();

InvocationHandler handler = new AOPFactory(obj);

return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj

.getClass().getInterfaces(), handler);

}

@SuppressWarnings("unchecked")

public static void main(String[] args) {

try {

Hello hello = (Hello) getBean("dynamic.HelloImplements");

hello.setInfo("xieyu1", "xieyu2");

hello.getInfos1();

hello.getInfos2();

hello.display();

} catch (Exception e) {

e.printStackTrace();

}

}

}

OK,可以看看输出结果,此时的输出结果不仅仅有自己的Hello实现类的中打印结果,还有proxy代理中的结果,它可以捕获方法信息和入参数,也可以捕获返回结果,也可以操作方法,所以这种非侵入式编程本身就是侵入式的,呵呵!

好了,你会发现都有接口,有些时候写太多接口很烦,而且上面的调用性能的确不怎么样,除了JVM提供的动态代理,还有什么办法吗?有的,org的asm包可以动态修改字节码信息,也就是可以动态在内存中创建class类和修改class类信息;但是听起来貌似很复杂,cglib为我们包装了对asm的操作,整个ASM包的操作非常小,但是代码很精炼,很容易看懂。那么cglib实现的时候,就是通过创建一个类的子类,然后在调用时,子类方法肯定覆盖父类方法,然后子类在完成相关动作后,进行super的回调;

我们来看个例子(首先下载asm包,和cglib包,各个版本不同而不同,我使用的是asm-all-3.1.jar和cglib-2.2.jar):

下面的程序只需创建文件:CglibIntereceptor.java

[java] view
plaincopy

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;

import net.sf.cglib.proxy.MethodInterceptor;

import net.sf.cglib.proxy.MethodProxy;

//创建一个类,用来做测试

class TestClass {

public void doSome() {

System.out.println("====>咿呀咿呀喂");

}

}

public class CglibIntereceptor {

static class MethodInterceptorImpl implements MethodInterceptor {

public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {

System.out.println(method);

proxy.invokeSuper(obj, args);

return null;

}

}

public static void main(String[] args) {

Enhancer enhancer = new Enhancer();

enhancer.setSuperclass(TestClass.class);

enhancer.setCallback( new MethodInterceptorImpl() );

TestClass my = (TestClass)enhancer.create();

my.doSome();

}

}

看看打印结果:

[java] view
plaincopy

public void dynamic.TestClass.doSome()

====>咿呀咿呀喂

//注意看黑色粗体标识出来的代码,首先要实现MethodInterceptor,然后实现方法intercept,内部使用invokeSuper来调用父类;下面的实例都是通过Enhancer 来完成的;细节的后续我们继续探讨,现在就知道这样可以使用,而spring的真正实现也是类似于此,只是spring对于cglib的使用做了其他的包装而已;大家可以去看看spring对事务管理器的源码即可了解真相。

下面问题来了,我们有些时候对某些方法不想去AOP,因为我认为只有需要包装的才去包装,就像事务管理器中切入的时候,我们一般会配置一个模式匹配,哪些类和那些方法才需要做AOP;那么cglib怎么实现的,它提供了一个CallbackFilter来实现这个机制。OK,我们来看一个CallbackFilter的实例:

以下代码创建文件:CglibCallBackFilter.java

[java] view
plaincopy

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Callback;

import net.sf.cglib.proxy.CallbackFilter;

import net.sf.cglib.proxy.Enhancer;

import net.sf.cglib.proxy.MethodInterceptor;

import net.sf.cglib.proxy.MethodProxy;

import net.sf.cglib.proxy.NoOp;

class CallBackFilterTest {

public void doOne() {

System.out.println("====>1");

}

public void doTwo() {

System.out.println("====>2");

}

}

public class CglibCallBackFilter {

static class MethodInterceptorImpl implements MethodInterceptor {

public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {

System.out.println(method);

return proxy.invokeSuper(obj, args);

}

}

static class CallbackFilterImpl implements CallbackFilter {

public int accept(Method method) {//返回1代表不会进行intercept的调用

return ("doTwo".equals(method.getName())) ? 1 : 0;

}

}

public static void main(String[] args) {

Callback[] callbacks =

new Callback[] { new MethodInterceptorImpl(), NoOp.INSTANCE };

Enhancer enhancer = new Enhancer();

enhancer.setSuperclass(CallBackFilterTest.class);

enhancer.setCallbacks( callbacks );

enhancer.setCallbackFilter( new CallbackFilterImpl());

CallBackFilterTest callBackFilterTest = (CallBackFilterTest)enhancer.create();

callBackFilterTest.doOne();

callBackFilterTest.doTwo();

}

}

看下打印结果:

[java] view
plaincopy

public void dynamic.CallBackFilterTest.doOne()

====>1

====>2

可以看到只有方法1打印出了方法名,方法2没有,也就是方法2调用时没有调用:intercept来做AOP操作,而是直接调用的;可以看出,他上层有一个默认值,而callbacks里面设置了NoOp.INSTANCE,就代表了不做任何操作的一个实例,你应该懂了吧,就是当不做AOP的时候调用那种实例来运行,当需要AOP的时候调用那个实例来运行;怎么对应上的,它自己不知道,accept返回的是一个数组的下标,callbacks是一个数组,那么你猜猜是不是数组的下标呢,你自己换下位置就知道了,呵呵,是的,没错就是数组下标,不相信可以翻翻他的源码就知道了。

其实你可以看出cglib就是在修改字节码,貌似很方面,spring、Hibernate等也大量使用它,但是并不代表你可以大量使用它,尤其是在写业务代码的时候,只有写框架才可以适当考虑使用这些东西,spring的反射等相关一般都是初始化决定的,一般都是单例的,前面谈及到JVM时,很多JVM优化原则都是基于VM的内存结构不会发生变化,如果发生了变化,那么优化就会存在很多的问题了,其次无限制使用这个东西可能会使得VM的Perm Gen内存溢出。

最后我们看个实际应用中没啥用途,但是cglib实现的一些东西,java在基于接口、抽象类的情况下,实现了很多特殊的机制,而cglib可以将两个根本不想管的接口和类合并到一起来操作,这也是字节码的一个功劳,呵呵,它的原理就是在接口下实现了子类,并把其他两个作为回调的方法,即可实现,但是实际应用中这种用法很诡异,cglib中是使用:Mixin来创建,而并非Enhancer了。例子如下:

创建文件:CglibMixin.java

[java] view
plaincopy

import net.sf.cglib.proxy.Mixin;

interface Interface1 {

public void doInterface1();

}

interface Interface2 {

public void doInterface2();

}

class ImpletmentClass1 implements Interface1 {

public void doInterface1() {

System.out.println("===========>方法1");

}

}

class ImpletmentClass2 implements Interface2 {

public void doInterface2() {

System.out.println("===========>方法2");

}

}

public class CglibMixin {

public static void main(String []args) {

Class<?>[] interfaces =

new Class[] { Interface1.class, Interface2.class };

Object[] implementObjs =

new Object[] { new ImpletmentClass1(), new ImpletmentClass2()};

Object obj = Mixin.create(interfaces,implementObjs);

Interface1 interface1 = (Interface1)obj;

Interface2 interface2 = (Interface2)obj;

interface1.doInterface1();

interface2.doInterface2();

}

}

结果就不用打印了,上面有描述,主要是两个接口、两个实例,最终用一个对象完成了,传递过程中只有一个,比起传统意义上的多态更加具有多态的效果,呵呵,不过还是建议少用。

本文只是简单阐述框架级别动态代理和cglib的实现,后续会深入探讨一些cglib的实现细节和功能,以及如何在框架中抽象出模型出来。

动态代理

http://hi.baidu.com/malecu/item/9e0edc115cb597a1feded5a0

http://www.iteye.com/topic/683613

/article/9362559.html

http://www.ibm.com/developerworks/cn/java/j-lo-hibernatelazy/index.html?ca=dat

通常情况下,适用代理模式的情况有两种:

1.创建对象开销很大,可以创建一个代理对象,推迟真正的对象创建。大家所熟悉的Hibernate延迟加载策略就是使用动态代理,当A实体关联B实体时,在获取A实体时不需要立即获得与A实体关联的B实体,因为有可能客户端根本不需要B实体数据,当客户端真正需要这部分数据时,再加载B实体的数据。这样就节省了资源。

2.所创建的对象不能满足客户端需求,比如说我们需要为某个方法运行前加入一些验证或者过滤,这时我们就需要创建一个代理对象,增强原来对象的功能。


代理模式

代理模式是一种应用非常广泛的设计模式,当客户端代码需要调用某个对象时,客户端实际上也不关心是否准确得到该对象,它只要一个能提供该功能的对象即可,此时我们就可返回该对象的代理(Proxy)。

在这种设计方式下,系统会为某个对象提供一个代理对象,并由代理对象控制对源对象的引用。代理就是一个 Java 对象代表另一个 Java 对象来采取行动。在某些情况下,客户端代码不想或不能够直接调用被调用者,代理对象可以在客户和目标对象之间起到中介的作用。

对客户端而言,它不能分辨出代理对象与真实对象的区别,它也无须分辨代理对象和真实对象的区别。客户端代码并不知道真正的被代理对象,客户端代码面向接口编程,它仅仅持有一个被代理对象的接口。

总而言之,只要客户端代码不能或不想直接访问被调用对象——这种情况有很多原因,比如需要创建一个系统开销很大的对象,或者被调用对象在远程主机上,或者目标对象的功能还不足以满足需求……,而是额外创建一个代理对象返回给客户端使用,那么这种设计方式就是代理模式。

下面示范一个简单的代理模式,程序首先提供了一个 Image 接口,代表大图片对象所实现的接口,该接口代码如下:

清单 3. Image.java


public interface Image
{
void show();
}


该接口提供了一个实现类,该实现类模拟了一个大图片对象,该实现类的构造器使用 Thread.sleep() 方法来暂停 3s。下面是该 BigImage 的程序代码。

清单 4. BigImage.java


// 使用该 BigImage 模拟一个很大图片
public class BigImage implements Image
{
public BigImage()
{
try
{
// 程序暂停 3s 模式模拟系统开销
Thread.sleep(3000);
System.out.println("图片装载成功 ...");
}
catch (InterruptedException ex)
{
ex.printStackTrace();
}
}
// 实现 Image 里的 show() 方法
public void show()
{
System.out.println("绘制实际的大图片");
}
}


上面的程序代码暂停了 3s,这表明创建一个 BigImage 对象需要 3s 的时间开销——程序使用这种延迟来模拟装载此图片所导致的系统开销。如果不采用代理模式,当程序中创建 BigImage 时,系统将会产生 3s 的延迟。为了避免这种延迟,程序为 BigImage 对象提供一个代理对象,BigImage 类的代理类如下所示。

清单 5. ImageProxy.java


public class ImageProxy implements Image
{
// 组合一个 image 实例,作为被代理的对象
private Image image;
// 使用抽象实体来初始化代理对象
public ImageProxy(Image image)
{
this.image = image;
}
/**
* 重写 Image 接口的 show() 方法
* 该方法用于控制对被代理对象的访问,
* 并根据需要负责创建和删除被代理对象
*/
public void show()
{
// 只有当真正需要调用 image 的 show 方法时才创建被代理对象
if (image == null)
{
image = new BigImage();
}
image.show();
}
}


上面的 ImageProxy 代理类实现了与 BigImage 相同的 show() 方法,这使得客户端代码获取到该代理对象之后,可以将该代理对象当成 BigImage 来使用。

在 ImageProxy 类的 show() 方法中增加了控制逻辑,这段控制逻辑用于控制当系统真正调用 image 的 show() 时,才会真正创建被代理的 BigImage 对象。下面程序需要使用 BigImage 对象,但程序并不是直接返回 BigImage 实例,而是先返回 BigImage 的代理对象,如下面程序所示。

清单 6. BigImageTest.java


public class BigImageTest
{
public static void main(String[] args)
{
long start = System.currentTimeMillis();
// 程序返回一个 Image 对象,该对象只是 BigImage 的代理对象
Image image = new ImageProxy(null);
System.out.println("系统得到 Image 对象的时间开销 :" +
(System.currentTimeMillis() - start));
// 只有当实际调用 image 代理的 show() 方法时,程序才会真正创建被代理对象。
image.show();
}
}


上面程序初始化 image 非常快,因为程序并未真正创建 BigImage 对象,只是得到了 ImageProxy 代理对象——直到程序调用 image.show() 方法时,程序需要真正调用 BigImage 对象的 show() 方法,程序此时才真正创建 BigImage 对象。运行上面程序,看到如图 6 所示的结果。

图 6. 使用代理模式提高性能




看到如图 6 所示的运行结果,读者应该能认同:使用代理模式提高了获取 Image 对象的系统性能。但可能有读者会提出疑问:程序调用 ImageProxy 对象的 show() 方法时一样需要创建 BigImage 对象啊,系统开销并未真正减少啊?只是这种系统开销延迟了而已啊?

我们可以从如下两个角度来回答这个问题:

把创建 BigImage 推迟到真正需要它时才创建,这样能保证前面程序运行的流畅性,而且能减少 BigImage 在内存中的存活时间,从宏观上节省了系统的内存开销。

有些情况下,也许程序永远不会真正调用 ImageProxy 对象的 show() 方法——意味着系统根本无须创建 BigImage 对象。在这种情形下,使用代理模式可以显著地提高系统运行性能。

与此完全类似的是,Hibernate 也是通过代理模式来“推迟”加载关联实体的时间,如果程序并不需要访问关联实体,那程序就不会去抓取关联实体了,这样既可以节省系统的内存开销,也可以缩短 Hibernate 加载实体的时间。

java 动态代理深度学习,

一.相关类及其方法:

java.lang.reflect.Proxy,

Proxy 提供用于创建动态代理类和实例的静态方法.

newProxyInstance()

返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序

(详见api文档)

java.lang.reflect.InvocationHandler,

InvocationHandler 是代理实例的调用处理程序 实现的接口。

invoke()

在代理实例上处理方法调用并返回结果。在与方法关联的代理实例上调用方法时,将在调用处理程序上调用此方法。

(详见api文档)

二.源代码:

被代理对象的接口及实现类:

package com.ml.test;

public interface Manager {

public void modify();

}

package com.ml.test;

public class ManagerImpl implements Manager {

@Override

public void modify() {

System.out.println("*******modify()方法被调用");

}

}

业务代理类:

package com.ml.test;

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Method;

public class BusinessHandler implements InvocationHandler {

private Object object = null;

public BusinessHandler(Object object) {

this.object = object;

}

@Override

public Object invoke(Object proxy, Method method, Object[] args)

throws Throwable {

System.out.println("do something before method");

Object ret = method.invoke(this.object, args);

System.out.println("do something after method");

return ret;

}

}

客户端类:

package com.ml.test;

import java.lang.reflect.Proxy;

public class Client {

public static void main(String[] args) {

// 元对象(被代理对象)

ManagerImpl managerImpl = new ManagerImpl();

// 业务代理类(在元对象的基础上增加内容,比如这个函数运行需要时间,调试信息等)

BusinessHandler securityHandler = new BusinessHandler(managerImpl);

// 获得代理类($Proxy0 extends Proxy implements Manager)的实例.

Manager managerProxy = (Manager) Proxy.newProxyInstance(managerImpl

.getClass().getClassLoader(), managerImpl.getClass()

.getInterfaces(), securityHandler);

managerProxy.modify();

}

}

managerImpl是被代理的类的对象,Manager是其接口类

securityHandler是代理managerImpl的对象,也就是说mnagerImpl是在securityHandler中以其成员变量的形式存在

Proxy.newProxyInstance(),传入securityHandler对象,生成一个新类$Proxy0, securityHandler对象作为其的一个成员变量,这个新类实现了Manager接口,对于接口的每一个方法都是调用securityHandler的invoke函数,在invoke函数中调用的是managerImpl的对应的方法。生成$Proxy0的对象,就是Mangager managerPorxy.

如果是RPC模式的,怎么用代理呢?

由newProxyInstance()生成的代理类,其中成员变量就是继承了InvocationHandler的类的对象,对于这个代理类,就可以看做是远程服务器类(被代理类)的在本地的一个实例,完全可以把本地实例就看做是远程服务器的实例,对于这个实例的每次函数的调用,就是把这次调用的函数名和参数传给invoke函数,由invoke函数内部实现网络通讯,就是把函数名和参数发给远程服务器,服务器接收之后,把运行结果通过网络返回

三.执行结果:

do something before method

*******modify()方法被调用

do something after method

四.机制分析:

Proxy.(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)做了以下几件事.

(1)根据参数loader和interfaces调用方法 getProxyClass(loader, interfaces)创建代理类$Proxy.

$Proxy0类实现了interfaces的接口,并继承了Proxy类.

(2)实例化$Proxy0并在构造方法中把BusinessHandler传过去,接着$Proxy0调用父类Proxy的构造器,为h赋值,如下:

class Proxy{

InvocationHandler h=null;

protected Proxy(InvocationHandler h) {

this.h = h;

}

...

}

下面是本例的$Proxy0类的源码(好不容易才把它提出来):

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Method;

import java.lang.reflect.Proxy;

import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements Manager {

private static Method m1;

private static Method m0;

private static Method m3;

private static Method m2;

static {

try {

m1 = Class.forName("java.lang.Object").getMethod("equals",

new Class[] { Class.forName("java.lang.Object") });

m0 = Class.forName("java.lang.Object").getMethod("hashCode",

new Class[0]);

m3 = Class.forName("com.ml.test.Manager").getMethod("modify",

new Class[0]);

m2 = Class.forName("java.lang.Object").getMethod("toString",

new Class[0]);

} catch (NoSuchMethodException nosuchmethodexception) {

throw new NoSuchMethodError(nosuchmethodexception.getMessage());

} catch (ClassNotFoundException classnotfoundexception) {

throw new NoClassDefFoundError(classnotfoundexception.getMessage());

}

}

public $Proxy0(InvocationHandler invocationhandler) {

super(invocationhandler);

}

@Override

public final boolean equals(Object obj) {

try {

return ((Boolean) super.h.invoke(this, m1, new Object[] { obj }))

.booleanValue();

} catch (Throwable throwable) {

throw new UndeclaredThrowableException(throwable);

}

}

@Override

public final int hashCode() {

try {

return ((Integer) super.h.invoke(this, m0, null)).intValue();

} catch (Throwable throwable) {

throw new UndeclaredThrowableException(throwable);

}

}

public final void modify() {

try {

super.h.invoke(this, m3, null);

return;

} catch (Error e) {

} catch (Throwable throwable) {

throw new UndeclaredThrowableException(throwable);

}

}

@Override

public final String toString() {

try {

return (String) super.h.invoke(this, m2, null);

} catch (Throwable throwable) {

throw new UndeclaredThrowableException(throwable);

}

}

}

接着把得到的$Proxy0实例强制转换成Manager.

当执行managerProxy.modify()方法时,就调用了$Proxy0类中的modify()方法.

在modify方法中,调用父类Proxy中的h的invoke()方法.

即InvocationHandler.invoke();

[java] view
plaincopy





/**

* 相亲接口

*

* @author zhengt

* @time Jun 3, 2095 3:13:03 PM

*/

public interface XiangQinInterface {

/**

* 相亲方法

*/

public void xiangQin();

}

/**

* 张三相亲实现类

*

* @author zhengt

* @time Jun 3, 2095 3:14:48 PM

*/

public class ZhangSanXiangQinInterfaceImpl implements XiangQinInterface {

public void xiangQin() {

System.out.println("张三去相亲,娶个漂亮老婆。");

}

}

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Method;

/**

* 相亲可是一辈子的大事,相亲前要准备一下,打扮得帅气些。

*

* @author zhengt

* @time Jun 3, 2095 3:15:48 PM

*/

public class ReadyInvocationHandler implements InvocationHandler {

//相亲接口的实现类,也就是张三相亲类

private Object zhangSan = null;

public ReadyInvocationHandler(Object realSubject) {

this.zhangSan = realSubject;

}

public Object invoke(Object proxy, Method m, Object[] args) {

Object result = null;

try {

/**

* 动态代理类$Proxy0调用xiangQin方法时会调用它自己的xiangQin方法,

* 而它自己的xiangQin方法里面调用的是super.h.invoke(this, , ),也就是父类Proxy的h的invoke方法,

* 也就是ReadyInvocationHandler类的invoke方法。

* 所以,invoke(Object proxy, Method m, Object[] args)种的proxy实际上就是动态代理类$Proxy0,

* 如果你将其强转成XiangQinInterface然后调用它的xiangQin方法,然后它就会调用super.h.invoke(this, , ),这样就会死循环。

*/

/**

* 网上关于这里最多问题就是Object proxy放在这里用来做什么呢?这个我也不知道,

* 不过至少我们知道它到底是个什么东西,具体做什么用嘛就不得而知了

*/

System.out.println(proxy.getClass().getSimpleName());

System.out.println("张三相亲前,代理人给他打扮了打扮。");

result = m.invoke(zhangSan, args);

} catch (Exception ex) {

System.exit(1);

}

return result;

}

}

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Method;

import java.lang.reflect.Proxy;

/**

* 张三来到了婚介所(相亲现场),开始相亲。

*

* @author zhengt

* @time Jun 3, 2095 3:17:16 PM

*/

public class HunJieSuo {

public static void main(String args[]) {

//先将张三相亲这个相亲的实现类实例化,也就是得到XiangQinInterface接口的一个实例对象

XiangQinInterface zhangSan = new ZhangSanXiangQinInterfaceImpl();

/**

* 得到ZhangSanXiangQinInterfaceImpl这个类的一个代理类,同时为代理类绑定了一个处理类ReadyInvocationHandler。

* 听着很绕口,其实就是每次调用ZhangSanXiangQinInterfaceImpl这个子类的xiangQin方法时,

* 不是zhangSan这个ZhangSanXiangQinInterfaceImpl类的实例去调用,

* 而是这个ZhangSanXiangQinInterfaceImpl的代理类ReadyInvocationHandler去调用它自己的invoke方法,

* 这个invoke方法里呢可以调用zhangSan这个实例的xiangQin方法

*/

/**

* 在java种怎样实现动态代理呢

* 第一步,我们要有一个接口,还要有一个接口的实现类,而这个实现类呢就是我们要代理的对象,

* 所谓代理呢也就是在调用实现类的方法时,可以在方法执行前后做额外的工作,这个就是代理。

* 第二步,我们要自己写一个在要代理类的方法执行时,能够做额外工作的类,而这个类必须继承InvocationHandler接口,

* 为什么要继承它呢?因为代理类的实例在调用实现类的方法的时候,不会调真正的实现类的这个方法,

* 而是转而调用这个类的invoke方法(继承时必须实现的方法),在这个方法中你可以调用真正的实现类的这个方法。

* 第三步,在要用代理类的实例去调用实现类的方法的时候,写出下面两段代码。

*/

XiangQinInterface proxy = (XiangQinInterface) Proxy.newProxyInstance(

zhangSan.getClass().getClassLoader(),

zhangSan.getClass().getInterfaces(),

new ReadyInvocationHandler(zhangSan));

proxy.xiangQin();

/**

* 这里要解释下中部那段长长的代码的意思,以及具体做了哪些工作?

* 第一,根据zhangSan.getClass().getClassLoader()这个要代理类的类加载器和

* zhangSan.getClass().getInterfaces()要代理类所实现的所有的接口

* 作为参数调用Proxy.getProxyClass(ClassLoader loader, Class<?>... interfaces)

* 的方法返回代理类的java.lang.Class对象,也就是得到了java动态生成的代理类$Proxy0的Class对象。

* 同时,java还让这个动态生成的$Proxy0类实现了要代理类的实现的所有接口,并继承了Proxy接口。

* 第二,实例化这个动态生成的$Proxy0类的一个实例,实例化代理类的构造函数为Proxy(InvocationHandler h),

* 也就是说要实例化这个动态生成的$Proxy0类,必须给它一个InvocationHandler参数,也就是我们自己实现的用来在代理类

* 方法执行前后做额外工作的类ReadyInvocationHandler。

* 这段代码Proxy.newProxyInstance(zhangSan.getClass().getClassLoader(),zhangSan.getClass().getInterfaces(),new ReadyInvocationHandler(zhangSan))

* 得到的其实是一个类名叫$Proxy0 extends Proxy implements XiangQinInterface的类。

* 第三,将这个$Proxy0类强制转型成XiangQinInterface类型,调用xiangQin方法。

*/

}

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: