程序地带

Java中的代理


什么是代理模式?解决什么问题?


什么是静态代理?什么是动态代理?两者什么关系?具体如何实现?又是什么原理?


 


什么是代理模式?


定义:为其他对象提供一种代理以控制对这个对象的访问。


定义是抽象晦涩的,让我们回到生活中来个具体的例子。


示例:王二狗所在的公司,老板在发工资的前一天跑路了,可怜二狗一身房贷,被迫提起劳动仲裁,劳动局会为他指派代理律师全权负责二狗的仲裁事宜。这里使用的就是代理模式。因为在劳动仲裁整个活动中,代理律师会全权代理王二狗。


解决了什么问题?


下面是一些使用适用场景,听名字比较抽象,暂时可以不用在意,随着不断深入了解终究会明白。


(1)远程代理


(2)虚拟代理


(3)缓冲代理


(4)保护代理


(5)智能引用


 


什么是静态代理?


静态代理是预先指定了代理与被代理者的关系。


例如:王二狗的代理律师方唐镜是在开庭之前就确定了的。映射到编程领域的话,就是指代理类与被代理类的依赖关系是在编译期间就确定了。


首先定义一个诉讼的接口:


public interface ILawSuit{
void submit(String proof);//提起诉讼
void defend();//法庭辩护
}

王二狗的诉讼类,实现了ILawSuit接口


public class SecondDogWang implements ILawSuit{
@Override
public void submit(String proof){
System.out.println("老板拿钱跑路了");
}
@Override
public void defend(){
System.out.println("马上还钱");
}
}

代理律师诉讼类,实现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 ProxyLaywer(new SecondDogWwang());
}
}

这样就构建了静态代理关系,然后在客户端就可以使用代理对象进行操作了。


public static void main(String[] args){
ProxyFactory.getProxy.submit("工资流水在此");
ProxyFactory.getProxy.defend();
}

可以看到,代理律师全权代理了王二狗的本次诉讼活动。


使用这种代理模式有什么好处呢?为什么我们不直接让王二狗自己来完成本次诉讼呢?现实中的情况比较负责,但是这里可以列举一条:这样搭理律师就可以在提起诉讼等操作之前做一些校验工作或者记录工作。例如二狗提供的资源,律师可以根据实际情况选择在何时的时机移交给法庭等,就是说可以对代理对象做一些更加合理的控制。再比如二狗可以不用出席法庭,由代理律师代为出席。


什么是动态代理?


动态代理本质仍然是代理,情况与上述的静态代理完全一样,不同的是代理与被代理人之间的关系是动态确定的。例如王二狗的同时牛翠花也有诉讼请求,但是在开庭之前没有确定她的代理律师,而是在开庭当天选择了一个律师,这种场景映射到编程领域就是这个关系不是在编译时确定的,是在运行时确定的。


既然动态代理没有为我们增强代理方面的任何功能,娜我们为什么还要用动态代理呢?静态代理不是挺好么?


凡是动态确定的东西大概都具有灵活性、强扩展的优势。上面的例子中,如果牛翠花也使用静态代理的花,那么就需要再添加两个类,一个牛翠花诉讼类,一个牛翠花的代理律师诉讼类,还的在代理工厂中添加一个方法。而如果使用动态代理的话,就只需要生成一个诉讼类就可以了,全程指需要一个代理律师诉讼类,因为我们可以动态的讲很多人的案子让这个律师来处理。


动态代理的两种实现:


         方式一:JDK动态代理


         在Java的动态代理机制中,有两个非常重要的概念:一个是InvocationHandler接口,另一个是Proxy类,这个类和接口是实现我们实现动态代理必须用到的。


InvocationHandler接口是给动态代理类来实现的,负责处理被代理对象的操作的。Proxy是用来创建动态代理类实例对象的,因为只有得到了这个对象,我们才能调用那些需要代理的方法。


        接下来我们看下实例,牛翠花动态指定代理律师如何实现?


        (1)构建一个牛翠花诉讼类


public class CuiHuaNiu implements ILawSuit{
@Override
public void submit(){
System.out.println("老板拿钱跑路了");
}
@Override
public void defend(){
System.out.println("还钱");
}
}

         (2)构建一个动态代理类


public class DynProxyLaywer implements InvocationHandler{
private Object target;//被代理对象
public DynProxyLaywer(Object obj){
this.target = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args){
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 DynProxyLaywer(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();
}

        (5)输出结果


案件进展:submit
老板欠薪跑路,证据如下:工资流水在此
案件进展:defend
铁证如山,马旭还牛翠花血汗钱

        JDK动态代理实现的原理      


       首先JDK的动态代理实现方法是依赖于接口的,首先使用接口来定义好操作的规范。然后通过Proxy类产生的代理对象调用被代理对象的操作,而这个操作又被分发给InvocationHandler接口的invoke方法具体执行。


       public Object invoke(Object proxy, Method method, Object[] args) throws throwable;


       此方法的参数含义如下:


       proxy:代表动态代理对象


       method:代表正在执行的方法


       args:代表当前执行方法传入的实参


       返回值:表示当前执行方法的返回值


       例如上面牛翠花案例中,我们使用Proxy类的newProxyInstance()方法生成的代理对象proxy去调用了proxy.submit("工资流水在此");操作,那么系统就会将此方法分发给invoke().其中proxy对象的类是系统帮我们动态生产的,其实现了我们的业务接口ILawSuit。       


 


         方式二:cgLib动态代理


由于JDK只能针对实现了接口的类做动态代理,而不能对没有实现接口的类做动态代理,所以cgLib横空出世!CGLib(Code Generation Library)是一个强大、高性能的Code生成类库,它可以在程序运行期间动态扩展类或接口,它的底层是使用java字节码操作框架ASM实现。


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)等。


cgLib的动态代理原理

CGLIB原理:动态生成一个要代理类的子类,子类重写要代理的类的所有不是final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。它比使用java反射的JDK动态代理要快。


CGLIB底层:使用字节码处理框架ASM,来转换字节码并生成新的类。不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。


CGLIB缺点:对于final方法,无法进行代理。


动态代理在AOP中的应用

什么是AOP? 维基百科上如是说:


定义:In computing, aspect-oriented programming (AOP) is a programming paradigm that aims to increase modularity by allowing the separation of cross-cutting concerns. AOP是一种编程范式,其目标是通过隔离切面耦合来增加程序的模块化。


首先声明,AOP是OOP的补充,其地位及其重要性远不及OOP,总体来说OOP面向名词领域而AOP面向动词领域,例如对一个人的设计肯定是使用OOP,例如这个人有手,脚,眼睛瞪属性。而对这个人上厕所这个动作就会涉及到AOP,例如上厕所前的先确定一下拿没拿手纸等。要理解AOP就首先要理解什么是切面耦合(cross-cutting concerns)。例如有这样一个需求,要求为一个程序中所有方法名称以test开头的方法打印一句log,这个行为就是一个典型的cross-cutting场景。首先这个打印log和业务毫无关系,然后其处于分散在整个程序当中的各个模块,如果按照我们原始的方法开发,一旦后期需求变动将是及其繁琐的。所以我们就需要将这个切面耦合封装隔离,不要将其混入业务代码当中。


例如在王二狗的案子中,我们希望在案子起诉后打印一句成功的log,如果不使用代理的话,我们是需要将log写在相应的业务逻辑里面的,例如王二狗诉讼类SecondDogWang里面的submit()方法中。使用了动态代理后,我们只需要在InvocationHandler 里面的invoke()方法中写就可以了,不会侵入业务代码当中,在以后的维护过程中对业务毫无影响,这是我们希望看到的。


@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("submit"))
System.out.println("案件提交成功,证据如下:"+ Arrays.asList(args));
Object result=method.invoke(target,args);
return result;
}

输出结果为:


案件提交成功,证据如下:[工资流水在此]老板欠薪跑路,证据如下:工资流水在此铁证如山,马旭还牛翠花血汗钱


所以AOP主要可以用于:日志记录,性能统计,安全控制,事务处理,异常处理等场景下。


总结:静态代理比对台代理更复合OOP原则,在日常开发中使用比较多。动态代理在开发框架时使用较多,例如大名定鼎的Spring。


 


参考资料:https://blog.csdn.net/ShuSheng0007/article/details/80864854?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param


http://www.le.com/ptv/vplay/27404400.html


 


 


 


 


 


 


 


 


 


 


 


 


 


 


版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/u014365523/article/details/112689406

随机推荐

python函数操作大全_Python 字符串操作方法大全

python字符串操作实方法大合集,包括了几乎所有常用的python字符串操作,如字符串的替换、删除、截取、复制、连接、比较、查找、分割等,需要的朋友可以参考下1、去空格及...

weixin_39874881 阅读(848)