本文介绍代理模式核心思想,静态代理、jdk动态代理、cglib动态代理的分别实现与对比选用,代理模式与装饰模式的对比
- 简介
- 应用场景
- 结构图
- 角色和职责
- 静态代理
- 静态代理实现
- JDK动态代理
- jdk动态代理实现
- CGLIB动态代理
- cglib动态代理实现
- 静态代理、jdk动态代理、cglib动态代理对比
- 静态代理
- jdk动态代理
- CGLIB动态代理
- 静态代理、jdk动态代理、cglib动态代理对比
- 代理模式与装饰模式对比
Proxy代理模式,是构造型的设计模式之一
代理模式为其他对象提供代理以控制这个对象的访问。
所谓代理,是指具有与代理元(被代理对象)具有相同接口的类。client需要通过代理与被代理的目标类交互,代理类就是在交互的过程中(前后)进行特殊处理。
注:这里讨论的代理都是对接口方法的代理。目前实现代理的方式:静态代理和jdk动态代理都是对接口方法的增强。而对于没有接口的类的方法,增强就需要使用CGLIB的实现方式。
成熟框架正在使用:
代理模式的核心是对目标方法/类进行功能增强!无论哪种代理方式他的核心思想来源都是于此结构
package com.mym.designmodel.proxy.staticProxy;
/**
* 职责: Subject 真实对象与代理对象的共同接口
*/
public interface Subject {
public void eat();
}
真实实现类
package com.mym.designmodel.proxy.staticProxy;
/**
* 职责:RealSubject 真实对象的代理类
*/
public class RealSubject implements Subject{
@Override
public void eat() {
System.out.println("吃饭!");
}
}
代理实现类
package com.mym.designmodel.proxy.staticProxy;
/**
* 职责 ProxySubject 代理对象的实现类
*/
public class ProxySubject implements Subject{
RealSubject realSubject = null;
@Override
public void eat() {
if(realSubject == null){
realSubject = new RealSubject();
}
prepare();
realSubject.eat();
clean();
}
private void prepare(){
System.out.println("准备碗筷!");
}
private void clean(){
System.out.println("清洗碗筷!");
}
}
测试
package com.mym.designmodel.proxy.staticProxy;
/**
* 测试静态代理
*/
public class MainClass {
public static void main(String[] args) {
ProxySubject proxySubject = new ProxySubject();
proxySubject.eat();
}
}
结果:
准备碗筷!
吃饭!
清洗碗筷!
jdk 有动态代理支持.参看jdk文档中的Proxy类
jdk动态代理的原理我先一句话概括下:通过类装载器拿到真实实现类
和真实实现类的接口
的字节码
文件,然后构造生成一个代理类(是有真实类字节码文件的)。生成完后此时又回归待静态代理的uml结构上了。
jdk动态代理深层源码剖析可以参考:http://rejoy.iteye.com/blog/1627405
代理接口
package com.mym.designmodel.proxy.dynamicProxy;
/**
* 职责: Subject 真实对象与代理对象的共同接口
*/
public interface Subject {
public void eat();
}
真实实现类
package com.mym.designmodel.proxy.dynamicProxy;
/**
* 职责:RealSubject 真实对象的代理类
*/
public class RealSubject implements Subject {
@Override
public void eat() {
System.out.println("吃饭!");
}
}
动态代理 方法处理类
package com.mym.designmodel.proxy.dynamicProxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* proxy 处理
*/
public class ProxyHandler implements InvocationHandler{
private RealSubject realSubject = null;
public void setRealSubject(RealSubject realSubject) {
this.realSubject = realSubject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
prepare();
Object result = method.invoke(realSubject, args);
clean();
return result;
}
private void prepare(){
System.out.println("dynamic 准备碗筷!");
}
private void clean(){
System.out.println("dynamic 清洗碗筷!");
}
}
测试
package com.mym.designmodel.proxy.dynamicProxy;
import java.lang.reflect.Proxy;
/**
* 测试动态代理
*/
public class MainClass {
public static void main(String[] args) {
ProxyHandler proxyHandler = new ProxyHandler();
proxyHandler.setRealSubject(new RealSubject());
Subject subject = (Subject) Proxy.newProxyInstance(RealSubject.class.getClassLoader(), RealSubject.class.getInterfaces(), proxyHandler);
subject.eat();
}
}
结果
dynamic 准备碗筷!
吃饭!
dynamic 清洗碗筷!
cglib是一个框架,需要额外导入包。使用的是与jdk动态代理不同的技术实现,即asm的字节码技术。
cglib动态代理也是字节码层面的代理实现方式。原理概括一下:
CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。
若使用maven等版本控制来管理依赖则去maven仓库搜索cglib引入依赖即可。
若没有依赖管理则需要把cglib、asm的jar包引入到工程classpath下才可以进行测试使用。jar包同样可以上maven仓库搜索然后下载jar并加入到工程path中就可以。
笔者测试使用的版本是:
- cglib-3.2.4.jar
- asm-5.1.jar
被代理类
package com.mym.designmodel.proxy.cglib;
/**
* 职责:RealSubject 真实对象
*/
public class RealSubject{
public void eat() {
System.out.println("吃饭!");
}
}
代理实现
package com.mym.designmodel.proxy.cglib;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* cglib实现动态代理
*/
public class CglibProxy implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();
/*
* getProxy(SuperClass.class)方法通过入参即父类的字节码,通过扩展父类的class来创建代理对象。
* intercept()方法拦截所有目标类方法的调用,
* obj表示目标类的实例,
* method为目标类方法的反射对象,
* args为方法的动态入参,
* proxy为代理类实例。
* proxy.invokeSuper(obj, args)通过代理类调用父类中的方法
* */
public Object getProxy(Class clazz){
//设置需要创建子类的类
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
//通过字节码技术动态创建子类实例
return enhancer.create();
}
//实现MethodInterceptor接口方法
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
prepare();
Object result = proxy.invokeSuper(obj, args);//通过代理类调用父类中的方法
clean();
return result;
}
private void prepare(){
System.out.println("CGLIB 准备碗筷!");
}
private void clean(){
System.out.println("CGLIB 清洗碗筷!");
}
}
测试:
package com.mym.designmodel.proxy.cglib;
/**
* 测试CGLIB代理
*/
public class MainClass {
public static void main(String[] args) {
CglibProxy cglibProxy = new CglibProxy();
RealSubject proxy = (RealSubject) cglibProxy.getProxy(RealSubject.class);
proxy.eat();
}
}
执行:
CGLIB 准备碗筷!
吃饭!
CGLIB 清洗碗筷!
缺点:
静态代理实现简单,但只能为一个目标对象服务。如果目标对象过多,则会产生很多代理类
JDK动态代理需要目标对象实现业务接口,代理类只需实现InvocationHandler接口
而JDK动态代理须实现InvocationHandler接口,通过反射代理方法,比较消耗系统性能,但可以减少代理类的数量,使用更灵活。
cglib代理无需实现接口,通过生成类字节码实现代理,比反射稍快,不存在性能问题,但cglib会继承目标对象,需要重写方法,所以目标对象不能为final类
代理模式和装饰模式都是对 目标 方法/类 进行功能增强。
详细对比请参看笔者对装饰模式的介绍,这里就不赘述
设计模式与应用:装饰模式