代理模式
文章目录
一、静态代理
1、定义
就是在程序运行前就应经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
2、实例
王二狗公司(天津在线回声科技发展有限公司)老板突然在发工资的前一天带着小姨子跑路了,可怜二狗一身房贷,被迫提起劳动仲裁,劳动局就会为其指派一位代理律师全权负责二狗的仲裁事宜。那这里面就是使用了代理模式,因为在劳动仲裁这个活动中,代理律师会全权代理王二狗。
静态代理是指预先确定了代理与被代理者的关系,例如王二狗的代理律师方文镜是在开庭前就确定的了。那映射到编程领域的话,就是指代理类与被代理类的依赖关系在编译期间就确定了。下面就是王二狗劳动仲裁的代码实现:
/**
* 首先定义一个代表诉讼的接口
*/
public interface ILawSuit {
void submit(String proof);//提起诉讼
void defend();//法庭辩护
}
/**
* 王二狗诉讼类型,实现ILawSuit接口
*/
public class SecondDogWang 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还钱","马旭"));
}
}
/**
* 代理律师诉讼类,实现ILawSuit接口
*/
public class ProxyLawyer implements ILawSuit {
ILawSuit plaintiff;//持有要代理的那个对象
public ProxyLawyer(ILawSuit plaintiff) {
this.plaintiff=plaintiff;
}
@Override
public void submit(String proof) {
plaintiff.submit(proof);
}
@Override
public void defend() {
plaintiff.defend();
}
}
/**
* 产生代理对象的静态代理工厂类
*/
public class ProxyFactory {
public static ILawSuit getProxy(){
return new ProxyLawyer(new SecondDogWang());
}
}
/**
* 这样就基本构建了静态代理关系了,然后在客户端就可以使用代理对象来进行操作了
*/
public static void main(String[] args) {
ProxyFactory.getProxy().submit("工资流水在此");
ProxyFactory.getProxy().defend();
}
//输出结果如下:
//老板欠薪跑路,证据如下:工资流水在此
//铁证如山,马旭还钱
可以看到,代理律师全权代理了王二狗的本次诉讼活动。那使用这种代理模式有什么好处呢,我们为什么不直接让王二狗直接完成本次诉讼呢?现实中的情况比较复杂,但是我可以简单列出几条:这样代理律师就可以在提起诉讼等操作之前做一些校验工作,或者记录工作。例如二狗提供的资料,律师可以选择的移交给法庭而不是全部等等操作,就是说可以对代理的对做一些控制。例如二狗不能出席法庭,代理律师可以代为出席。。。
二、JDK动态代理
1、什么是动态代理
动态代理本质上仍然是代理,情况与上面介绍的完全一样,只是代理与被代理人的关系是动态确定的,例如王二狗的同事牛翠花开庭前没有确定她的代理律师,而是在开庭当天当庭选择了一个律师,映射到编程领域为这个关系是在运行时确定的。
那既然动态代理没有为我们增强代理方面的任何功能,那我们为什么还要用动态代理呢,静态代理不是挺好的吗?凡是动态确定的东西大概都具有灵活性,强扩展的优势。上面的例子中如果牛翠花也使用静态代理的话,那么就需要再添加两个类。一个是牛翠花诉讼类,一个是牛翠花的代理律师类,还的在代理静态工厂中添加一个方法。而如果使用动态代理的话,就只需要生成一个诉讼类就可以了,全程只需要一个代理律师类,因为我们可以动态的将很多人的案子交给这个律师来处理。
2、Jdk动态代理实现
在java的动态代理机制中,有两个重要的类或接口,一个是InvocationHandler接口、另一个则是 Proxy类,这个类和接口是实现我们动态代理所必须用到的。
InvocationHandler接口是给动态代理类实现的,负责处理被代理对象的操作的,而Proxy是用来创建动态代理类实例对象的,因为只有得到了这个对象我们才能调用那些需要代理的方法。
接下来我们看下实例,牛翠花动态指定代理律师是如何实现的。
/**
* 1.构建一个牛翠花诉讼类
*/
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还牛翠花血汗钱","马旭"));
}
}
/**
* 2.构建一个动态代理类
*/
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;
}
}
/**
* 3.修改静态工厂方法
*/
public class ProxyFactory {
...
public static Object getDynProxy(Object target) {
InvocationHandler handler = new DynProxyLawyer(target);
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), handler);
}
}
/**
* 4.客户端使用
*/
public static void main(String[] args) {
ILawSuit proxy= (ILawSuit) ProxyFactory.getDynProxy(new CuiHuaNiu());
proxy.submit("工资流水在此");
proxy.defend();
}
//输出结果:
//案件进展:submit
//老板欠薪跑路,证据如下:工资流水在此
//案件进展:defend
//铁证如山,马旭还牛翠花血汗钱
3、Jdk动态代理实现原理
InvocationHandler的接口描述:
package java.lang.reflect;
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
其中的参数:
- proxy: 代理对象本身,也就是 getProxy(UserService userServiceRef) 获取到的对象。
- method:代表正在执行的方法。
- args:method方法的参数。
再来看一下Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)方法参数:
- loader:类加载器,用来加载代理类的,即Proxy.newProxyInstance()的返回结果的类字节码。
- interfaces:代理对象所实现的接口。 这里接口是数组参数,通常被代理只实现一个接口。那实现多个接口时使用代理对象有什么问题?其实也没问题,就是调用不同接口的方法前需要先强转为对应的接口类,麻烦。
- h:实现InvocationHandler的类,也就是示例代码中的DynProxyLawyer 类 。
三、CGLIB动态代理
1、什么是CGLIB动态代理
由于JDK只能针对实现了接口的类做动态代理,而不能对没有实现接口的类做动态代理,所以cgLib横空出世!CGLib(Code Generation Library)是一个强大、高性能的Code生成类库,它可以在程序运行期间动态扩展类或接口,它的底层是使用java字节码操作框架ASM实现。
2、CGLIB动态代理实现
1 引入cgLib 库
cglib-nodep-3.2.6.jar:使用nodep包不需要关联asm的jar包,jar包内部包含asm的类.
/**
* 2 定义业务类,被代理的类没有实现任何接口
*/
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血汗钱","马旭"));
}
}
/**
* 3 定义拦截器,在调用目标方法时,CGLib会回调MethodInterceptor接口方法拦截,来实现你自己的代理逻辑,类似于JDK中的InvocationHandler接
* 口。
*/
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;
}
}
/**
* 4 定义动态代理工厂,生成动态代理
*/
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;
}
}
/**
* 5 客户端调用
*/
public static void main(String[] args) {
Frank cProxy= (Frank) ProxyFactory.getGcLibDynProxy(new Frank());
cProxy.submit("工资流水在此");
cProxy.defend();
}
//输出结果
//案件提交成功,证据如下:[工资流水在此]
//老板欠薪跑路,证据如下:工资流水在此
//铁证如山,马旭还Frank血汗钱
可见,通过cgLib对没有实现任何接口的类做了动态代理,达到了和前面一样的效果。这里只是简单的讲解了一些cgLib的使用方式,有兴趣的可以进一步了解其比较高级的功能,例如回调过滤器(CallbackFilter)等。
3、CGLIB动态代理实现原理
CGLIB原理:动态生成一个要代理类的子类,子类重写要代理的类的所有不是final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。它比使用java反射的JDK动态代理要快。
CGLIB底层:使用字节码处理框架ASM,来转换字节码并生成新的类。不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。
CGLIB缺点:对于final方法,无法进行代理。