动态代理双剑客--JDK Proxy与CGLIB
背景:
研究过设计模式的同胞们都知道代理模式可以有两种实现方案:
1.接口实现(或继承抽象类)
核心代码片段
ProxySubject-->>doOperation()
//dosomething before realSubject.doOperation() //dosomething after
2.继承父类
核心代码片段
ProxySubject-->>doOperation()
//dosomething before super.doOperation() //dosomething after
总结:
相同点
都可以通过Proxy控制对Target的访问
不同点
1.灵活性
“接口实现”的方式更加灵活,代理类可以代理所有实现了ISubject接口的类;
“继承父类”的方式代理类只能代理它的父类,因为java中只支持单继承
2.可行性
如果Target有直接接口,那么这两种方式都可以;
如果没有实现任何接口,那只能采取“继承父类”的方式了
正文
Java中动态代理对应着也有两种实现方式
1.“接口实现"---JDK Proxy
用到JDK提供的InvocationHandler接口和Proxy类
类之间的关系如下
InvocationHandler接口
用于处理方法请求
Proxy类
用于生成代理对象
代码演示
ISubject接口
public interface ISubject { public void showName(String name); }
RealSubject类
public class RealSubject implements ISubject { @Override public void showName(String name) { System.out.println(name+"闪亮登场"); } }
LogHandler类
为了更明确的说明动态代理的工作原理,将代理的创建过程放到了LogHandler的外部,即main方法中
public class LogHandler implements InvocationHandler { Object target=null; public Object getTarget() { return target; } public void setTarget(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result=null; //调用目标对象方法前的逻辑 System.out.println("下面有一个大人物要出现"); //调用目标对象的方法,这句代码将代理与目标类联系了起来 method.invoke(target, args); //调用目标对象方法后的逻辑 System.out.println("大家鼓掌欢迎"); return result; } }
public class Client { /** * @param args */ public static void main(String[] args) { LogHandler logHandler=new LogHandler(); logHandler.setTarget(new RealSubject()); //创建代理对象 ISubject proxySubject=(ISubject)Proxy.newProxyInstance(RealSubject.class.getClassLoader(), RealSubject.class.getInterfaces(), logHandler); System.out.println("-------JDK Proxy-------------"); proxySubject.showName("委座"); } }
执行结果
调用过程时序图
2.“继承父类”---CGLIB
用到了CBLIB提供的Enhancer类和MethodInterceptor接口
类之间的关系如下
需要引入第三方jar包
- cglib-2.2.jar
- asm-3.1.jar
Enhancer类
用于创建代理对象
MethodInterceptor接口
用于处理方法请求
代码演示
ISubject接口,RealSubject类同上
LogIntercept类
public class LogIntercept implements MethodInterceptor { Object target=null; public Object getTarget() { return target; } public void setTarget(Object target) { this.target = target; } @Override public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable { Object result=null; //调用目标对象方法前的逻辑 System.out.println("下面有一个大人物要出现"); //调用目标对象的方法,这句代码将代理与目标类联系了起来 arg3.invoke(target, arg2); //调用目标对象方法后的逻辑 System.out.println("大家鼓掌欢迎"); return result; } }
客户端类Client
public class Client { /** * @param args */ public static void main(String[] args) { LogIntercept logIntercept=new LogIntercept(); logIntercept.setTarget(new RealSubject()); Enhancer enhancer=new Enhancer(); enhancer.setSuperclass(RealSubject.class); enhancer.setCallback(logIntercept); ISubject proxySubject=(ISubject)enhancer.create(); System.out.println("-------CBLIB-------------"); proxySubject.showName("委座"); } }
调用过程时序图
总结
大家可以看到JDK Proxy和CGLIB这两种动态代理的实现过程是非常相似的,但也有区别
相同点:
- 都用到了一个接口一个类;
- 接口用于处理方法调用,类用于创建代理对象
JDK Proxy
InvocationHandler接口
Proxy类
CGLIB
MethodIntercept接口
Enhancer类
不同点:
JDK Proxy
使用目标类的接口创建动态代理
CBLIB
使用目标类的子类创建动态代理
最后
JDK Proxy和CGLIB两种动态代理各有千秋,具体用哪个方案要看具体情况。如果目标类实现了对应接口,两种方案都可以;如果没有实现任何接口则要使用CBLIB。比如Hibernate中的实体类是POJO类,没有实现任何接口,那么要通过代理实现延迟加载就只能采用CGLIB方案了。