在Java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过使用这个类和接口可以生成JDK动态代理类或动态代理对象。
- 使用Proxy和InvocationHandler创建动态代理
- 动态代理和AOP
1.使用Proxy和InvocationHandler创建动态代理
Proxy提供了用于创建动态代理类和代理对象的静态方法,它也是所有动态代理类的父类。如果在程序中为一个或多个接口动态地生成实现类,就可以使用Proxy来创建动态代理类;
如果需要为一个或多个接口动态地创建实例,也可以使用Proxy来创建动态代理实例。
Proxy提供了如下两个方法来创建动态代理类和动态代理实例。
static Class<?> getProxyClass(ClassLoader loader,Class<?>...interfaces):创建一个动态代理类所对应的Class对象,该代理类将实现interfaces所指定的多个接口。第一个ClassLoader参数指定生成动态代理类的类加载器。
static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h):直接创建一个动态代理对象,该代理对象的实现类实现了interfaces制定的系列接口,执行代理对象的每个方法时都会被替换执行InvocationHandler对象的invoke方法。
实际上,即使采用第一个方法生成动态代理类之后,如果程序需要通过该代理类来创建对象,依然需要传入一个InvocationHandler对象。也就是说,系统生成的每个代理对象都有一个与之关联的InvocationHandler对象。
程序中可以采用先生成一个动态代理类,然后通过动态代理类来创建代理对象的方式生成一个动态代理对象:
下面程序示范了使用Proxy和InvocationHandler来生成动态代理对象:
interface Person
{
void walk();
void sayHello(String name);
}
class MyInvokationHandler implements InvocationHandler
{
/*
执行动态代理对象的所有方法时,都会被替换成执行如下的invoke方法
其中:
proxy:代表动态代理对象
method:代表正在执行的方法
args:代表调用目标方法时传入的实参。
*/
public Object invoke(Object proxy, Method method, Object[] args)
{
System.out.println("----正在执行的方法:" + method);
if (args != null)
{
System.out.println("下面是执行该方法时传入的实参为:");
for (Object val : args)
{
System.out.println(val);
}
}
else
{
System.out.println("调用该方法没有实参!");
}
return null;
}
}
public class ProxyTest
{
public static void main(String[] args)
throws Exception
{
// 创建一个InvocationHandler对象
InvocationHandler handler = new MyInvokationHandler();
// 使用指定的InvocationHandler来生成一个动态代理对象
Person p = (Person)Proxy.newProxyInstance(Person.class.getClassLoader(), new Class[]{Person.class}, handler);
// 调用动态代理对象的walk()和sayHello()方法
p.walk();
p.sayHello("孙悟空");
}
}
实际上,在普通编程过程中,确实无须使用动态代理,但在编写框架或底层基础代码时,动态代理的作用就非常大。
2.动态代理和AOP
下面介绍一种更实用的动态代理机制。
开发实际应用的软件系统时,通常会存在相同代码段重复出现的情况,在这种情况下,对于许多刚开始从事软件开发的人而言,他们的做法是:选中那些代码,一路"复制"、"粘贴",立即实现了系统的功能,如果仅仅从软件功能上来看,他们确实已经完成了软件开发。
通过这种“复制”、“粘贴”方式开发出来的软件如图1所示。
采用上述图片的方式实现具体的软件系统,在软件开发期间可能会觉得无所谓,但如果有一天需要修改程序的深色代码的实现,则意味着打开三分源代码进行修改。如果有100个地方甚至1000个地方使用了这段深色代码段,那么修改、维护这段代码的动作量将变成噩梦。
在这种情况下,大部分稍有经验的开发者都会讲这段深色代码段定义成一个方法,然后让另外三段代码段直接调用该方法即可。在这种方式下,软件系统的结构如图2所示。
采用上图所示的软件系统,如果需要修改深色部分的代码,则只要修改一个地方即可,而调用该方法的代码段,不管有多少个地方调用个该方法,都完全无须任何修改,只要被调用方法别修改了,所有调用该方法的地方就会自然改变——通过这种方式,大大降低了软件后期维护的复杂度。
但采用这种方式实现代码复用依然产生了一个重要的问题:代码段1、代码段2、代码段3和深色diamante段分离开了,但代码段1、代码段2和代码段3又和一个特定方式耦合了。最理想的效果是:代码块1、代码块2和代码块3既可以执行深色代码部分,又无须在程序中以硬编码方式直接调用深色代码的方法,这是就可以通过动态代理来达到这种效果。
public interface Dog
{
// info方法声明
void info();
// run方法声明
void run();
}
public class GunDog implements Dog
{
// 实现info()方法,仅仅打印一个字符串
public void info()
{
System.out.println("我是一只猎狗");
}
// 实现run()方法,仅仅打印一个字符串
public void run()
{
System.out.println("我奔跑迅速");
}
}
public class DogUtil
{
// 第一个拦截器方法
public void method1()
{
System.out.println("=====模拟第一个通用方法=====");
}
// 第二个拦截器方法
public void method2()
{
System.out.println("=====模拟通用方法二=====");
}
}
public class MyInvokationHandler implements InvocationHandler
{
// 需要被代理的对象
private Object target;
public void setTarget(Object target)
{
this.target = target;
}
// 执行动态代理对象的所有方法时,都会被替换成执行如下的invoke方法
public Object invoke(Object proxy, Method method, Object[] args)throws Exception
{
DogUtil du = new DogUtil();
// 执行DogUtil对象中的method1。
du.method1();
// 以target作为主调来执行method方法
Object result = method.invoke(target , args);
// 执行DogUtil对象中的method2。
du.method2();
return result;
}
}
public class MyProxyFactory
{
// 为指定target生成动态代理对象
public static Object getProxy(Object target)throws Exception
{
// 创建一个MyInvokationHandler对象
MyInvokationHandler handler =
new MyInvokationHandler();
// 为MyInvokationHandler设置target对象
handler.setTarget(target);
// 创建、并返回一个动态代理
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces() , handler);
}
}
public class Test
{
public static void main(String[] args)throws Exception
{
// 创建一个原始的GunDog对象,作为target
Dog target = new GunDog();
// 以指定的target来创建动态代理
Dog dog = (Dog)MyProxyFactory.getProxy(target);
dog.info();
dog.run();
}
}
通过上面的例子可以非常灵活地实现解耦。通常而言,使用Proxy生成一个动态代理时,往往并不会凭空产生一个动态代理,这样没有太大的实际意义。通常都是为指定的目标对象生成动态代理。
这种动态代理在AOP(Aspect Orient Programming,面向切面编程)中被称为AOP代理,AOP代理可替代目标对象,AOP代理包含了目标对象的全部方法。但AOP代理中的方法与目标对象的方法存在差异:AOP代理里的方法可以在执行目标方法之前、之后插入一些通用处理。
AOP代理包含的方法与目标对象包含的方法示意图如图3所示: