生活中处处能遇到代理,代理使得复杂的事情变得简单,在我们日常开发的过程中也常常使用到代理,例如spring的AOP,hibernate的事物等等,这些优秀的代理,让我们更专注于自己的业务本身,使开发变得简单。如果想要自己实现一个代理,进行权限验证、参数检验、事务处理等,有哪些方式呢?
下面介绍三种代理,以及他们的优缺点:
静态代理:
public class Proxy implements Foo{
public Foo foo;//需要代理的接口
public Proxy(Foo foo){
this.foo = foo;
}
@Override
public void func(String args){//代理处理的方法
System.out.println("before method");
this.foo.func(args);//执行原方法调用
System.out.println("before method");
}
}
优点:业务类只需要关注业务逻辑本身,保证了业务类的重用性。这是代理的共有优点。
缺点:代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。
如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
JDK原生的动态代理:
与静态代理类对照的是动态代理类,动态代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员手工编写它的源代码。动态代理类不仅简化了编程工作,而且提高了软件系统的可扩展性,因为Java 反射机制可以生成任意类型的动态代理类。
在运行时生成class,提供一组interface,则动态代理类就宣称它实现了这些interface。当然,动态代理类只是充当一个代理,你不要企图它会帮你干实质性的工作,在生成它的实例时你必须提供一个handler,由它接管实际的工作。
public class MyInvocationHandler implements InvocationHandler{
private Object target;//代理对象
MyInvocationHandler(Object target){
this.target = target;
}
//代理的处理集中在这里
@Override
public Object invoke(Object proxy,Method method,Objects[] args)throws Throwable{
System.out.println("before method");
Object result = method.invoke(target,args);
System.out.println("after method");
return result;
}
}
public class Test{
public static void main(String[] args)throws Exception{
Foo impl = new FooImpl;//需要被代理的对象
//hander
InvocationHander hander = new MyInvocationHandler(impl);
//创建代理对象
Foo f = (Foo)Proxy.newProxyInstance(FooImpl.class.getClassLoader(),FooImpl.class.getInterFaces(),hander);
f.func("sum");
}
}
优点:动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器方法中(InvocationHandler.invoke)。这样在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。实际中可以类似Spring AOP那样配置业务。
缺点:诚然,Proxy 已经设计得非常优美,但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持 interface 代理的桎梏,因为它的设计注定了这个遗憾。动态生成的代理类,注定有一个共同的父类。
修改编译字节码:
有很多条理由,人们可以否定对 class 代理的必要性,但是同样有一些理由,相信支持 class 动态代理会更美好。接口和类的划分,本就不是很明显,只是到了 Java 中才变得如此的细化。如果只从方法的声明及是否被定义来考量,有一种两者的混合体,它的名字叫抽象类。实现对抽象类的动态代理,相信也有其内在的价值。此外,还有一些历史遗留的类,它们将因为没有实现任何接口而从此与动态代理永世无缘。
public calss Test{
public static void main(String[] args){
//方法拦截器
MethodInterceptor callback = new MethodInterceptor(){
@Override
public Object intercept(Object target,Method method,Object[] args,MethodProxy methodProxy)throws Throwable{
System.out.println("before method");
Object result = methodProxy.invokeSuper(target,args);
System.out.println("after method");
return result;
}
};
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(FooImpl.class);
enhancer.setCallback(callback);
FooImpl foo = (FooImpl)enhancer.create();
foo.func("sum");
}
}
修改编译字节码方式有一些开源的项目,比如CGLib、ASM等,CGLib是一个强大的高性能的代码生成包。它广泛的被许多AOP的框架使用,例如Spring AOP和dynaop,为他们提供方法的interception(拦截)。最流行的OR Mapping工具hibernate也使用CGLIB来代理单端single-ended(多对一和一对一)关联(对集合的延迟抓取,是采用其他机制实现的)。EasyMock和jMock是通过使用模仿(mock)对象来测试java代码的包。它们都通过使用CGLib来为那些没有接口的类创建模仿(mock)对象。CGLib包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。除了CGLib包,脚本语言例如[Groovy和BeanShell],也是使用ASM来生成java的字节码。当然不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。
最后说明一点,CGLib所创建的动态代理对象的性能比JDK所创建的代理对象性能高不少,大概10倍,但CGLib在创建代理对象时所花费的时间却比JDK动态代理多大概8倍,所以对于singleton的代理对象或者具有实例池的代理,因为无需频繁的创建新的实例,所以比较适合CGLib动态代理技术,反之则适用于JDK动态代理技术。另外,由于CGLib采用动态创建子类的方式生成代理对象,所以不能对目标类中的final,private等方法进行处理。所以,大家需要根据实际的情况选择使用什么样的代理了。