Java动态代理
代理简介
代理是基本的设计模式之一,它是你为了提供额外的或不同的操作,而插入的用来代替“实际”对象的对象。但是对于调用者,对此毫无察觉,就好像调用的方法是用传统方式实现的一般。
静态代理
静态代理的依赖关系在编译期就已经确定了。
静态代理类图如下:
一个静态代理的例子:
public interface TestInterface{
void doSomething();
}
public class TestOne implements TestInterface{
@Override
public void doSomething() {
System.out.println("TestOne on call");
}
}
public class ProxyDemo implements TestInterface{
private TestOne one;
public ProxyDemo(TestOne one){
this.one=one;
}
@Override
public void doSomething() {
System.out.println("Proxy on call");
one.doSomething();
}
}
public class Client {
public static void func(TestInterface ti){
ti.doSomething();
}
public static void main(String[] args){
func(new TestOne());
func(new ProxyDemo(one));
}
}
打印结果如下:
从代码中可以看出,project.func()方法执行之前,其实代理和被代理类的关系就已经确定了。
静态代理理解:
给我的感觉是代理类就像包裹在了被代理类外面一样。然后因为代理类和被代理类都实现了同一个接口,根据面向接口编程的特性(以及注入),不需要重写原来的方法就能实现代理类中添加的操作。
这里有几个值得注意的点:
- “实际”类和代理类都实现了同一个接口
- 调用的方法传递的参数是面向接口的
- 在代理类中,添加了一些“实际”类额外的操作,而且实现这些额外的操作不会对func方法本身的实现产生影响
静态代理本身存在以下缺点:
- 一旦接口增加方法,那么所有实现了该接口的类都要进行修改
- 一个代理类只能服务一种类型,如果要服务于多种类型,会创建过多的代理类
下面动态代理的例子会对上述的缺点进行进一步的阐述。
动态代理
动态代理不同于静态代理,代理和被代理的关系是直到运行时才能够确定。
引用秒懂Java代理与动态代理模式的例子:
public interface ILawSuit {
void submit(String proof);//提起诉讼
void defend();//法庭辩护
}
public class CuiHuaNiu implements ILawSuit {
@Override
public void submit(String proof) {
System.out.println(String.format("老板欠薪跑路,证据如下:%s",proof));
}
@Override
public void defend() {
System.out.println(String.format("铁证如山,%s还牛翠花血汗钱","马旭"));
}
}
public class DynProxyLawyer implements InvocationHandler {
private Object target;//被代理的对象
public DynProxyLawyer(Object obj){
this.target=obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("案件进展:"+method.getName());
Object result=method.invoke(target,args);
return result;
}
}
public class Client {
public static void main(String[] args) {
ILawSuit target=new CuiHuaNiu();
InvocationHandler handler = new DynProxyLawyer(target);
ILawSuit proxy = (ILawSuit) Proxy.
newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
handler);
proxy.submit("工资流水在此");
proxy.defend();
}
}
打印结果:
案件进展:submit
老板欠薪跑路,证据如下:工资流水在此
案件进展:defend
铁证如山,马旭还牛翠花血汗钱
试想,如果采用静态代理的方式,那么代理类中要实现被代理类中所有的方法,如果方法很多的话,那么会非常冗余,尤其是如果这些方法需要的额外操作还一样的话(此处是打印案件进展)。而且,如果存在另一个继承自BLawSuit的被代理人张小花,就算律师处理流程相同,那么也需要编写一个代理律师的类。
动态代理理解:
我的理解是动态代理是在保持当前方法不变的情况下,为需要添加相同额外操作的方法提供的统一的“接口”。这个实现了InvocationHandler的类相当于是一类操作的抽象,所有需要这个类中invoke方法中额外操作的类(前提是这个类实现了某个接口)在生成代理的时候,把这个抽象传递就去就好了(Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), handler))。
动态代理我认为存在如下值得注意的点:
- 需要有一个实现了接口的被代理类
- 需要有一个实现了InvocationHandler接口的类
- 在调用时使用Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), handler)获取实例
对动态代理中需要的静态类和函数解释的比较清楚的看这篇博文:Java的动态代理机制详解
静态代理比动态代理更符合OOP原则,在日常开发中使用也较多。动态代理在开发框架时使用较多,例如的Spring和MyBatis。
CGLIB代理
cglib特点:
- JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口。如果想代理没有实现接口的类,就可以使用CGLIB实现。
- CGLIB是一个强大的高性能的代码生成包,它可以在运行期扩展Java类与实现Java接口。它广泛的被许多AOP的框架使用,例如Spring AOP和dynaop,为他们提供方法的interception(拦截)。
- CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。不鼓励直接使用ASM,因为它需要你对JVM内部结构包括class文件的格式和指令集都很熟悉。
引用秒懂Java代理与动态代理模式的例子:
public class Frank {
public void submit(String proof) {
System.out.println(String.format("老板欠薪跑路,证据如下:%s",proof));
}
public void defend() {
System.out.println(String.format("铁证如山,%s还Frank血汗钱","马旭"));
}
}
public class cgLibDynProxyLawyer implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] params, MethodProxy methodProxy) throws Throwable {
if (method.getName().equals("submit"))
System.out.println("案件提交成功,证据如下:"+ Arrays.asList(params));
Object result = methodProxy.invokeSuper(o, params);
return result;
}
}
public class ProxyFactory {
public static Object getGcLibDynProxy(Object target){
Enhancer enhancer=new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(new cgLibDynProxyLawyer());
Object targetProxy= enhancer.create();
return targetProxy;
}
}
public static void main(String[] args) {
Frank cProxy= (Frank) ProxyFactory.getGcLibDynProxy(new Frank());
cProxy.submit("工资流水在此");
cProxy.defend();
}
打印如下:
案件提交成功,证据如下:[工资流水在此]
老板欠薪跑路,证据如下:工资流水在此
铁证如山,马旭还Frank血汗钱
在spring的AOP实现中,同时使用了jdk提供的动态代理和cglib动态代理。
Java三种代理模式:静态代理、动态代理和cglib代理的例子和上面不同,可以参考下
参考链接:
- Java的动态代理机制详解(各个参数解释的很清晰)
- 秒懂Java代理与动态代理模式(举的例子非常容易理解)
- Java 动态代理及 RPC 框架介绍(对动态代理的应用进行了讲解)
- Java三种代理模式:静态代理、动态代理和cglib代理
- AOP的底层实现-CGLIB动态代理和JDK动态代理
上一篇: JAVA SE Array(数组)
下一篇: 面试题收集