java代理模式详解

什么是代理

代理就是代理方从被代理方获取某些权限,从而为被代理方服务,例如:

我们大家都知道微商代理,简单地说就是代替厂家卖商品,厂家“委托”代理为其销售商品。关于微商代理,首先我们从他们那里买东西时通常不知道背后的厂家究竟是谁,也就是说,“委托者”对我们来说是不可见的;其次,微商代理主要以朋友圈的人为目标客户,这就相当于为厂家做了一次对客户群体的“过滤”。我们把微商代理和厂家进一步抽象,前者可抽象为代理类,后者可抽象为委托类(被代理类)。通过使用代理,通常有两个优点,并且能够分别与我们提到的微商代理的两个特点对应起来:

  • 优点一:可以隐藏委托类的实现;
  • 优点二:可以实现客户与委托类间的解耦,在不修改委托类代码的情况下能够做一些额外的处理。

有时候我们需要对一个类添加相应的功能或对某些结果进行修改,但是直接修改源码会对维护等提高困难,
所以我们才会有代理的思想,就是说,我不修改你源码了,我让一个“中间类”来办这件事,来调用你的方法,然后我在中间类中添加或修改相应的功能,我想怎么实现,想怎么添加都行。这样极大的保护了源码,而且下次你功能优点变化,我修改修改代理类,实在不行,我重新定义一个代理类,这样的维护成本比你直接修改源码要低?这就是AOP(面向切面编程)。

在java中代理又分为静态代理动态代理,一般而言我们都是使用动态代理,静态代理的局限性太大,不过也可以让我们对代理的概念有更好的了解,下面我们来了解一下java中的代理。

静态代理

静态代理实例

Car接口

package prox;

public interface Car {
     public void run();  
}

被代理类SUVCar

package prox;

public class SUVCar implements Car {

    @Override
    public void run() {
        System.out.println("SUV is running");
    }

}

代理类CarProx

package prox;

public class CarProx extends SUVCar{
    private SUVCar suvcar;
    public CarProx(){
        super();
        suvcar=new SUVCar();
    }
    @Override
    public void run() {
        befor();
        super.run();
        after();
    }
    private void after() {
        System.out.println("after running");
    }
    private void befor() {
        System.out.println("befor running");
    }
}

测试用例

/**
     * 静态代理,代理类继续被代理类,在初始化时生成被代理类对象
     * 实现被代理对象的方法,客户端调用的代理的方法,代理对象调用被代理对象同样的方法
     * 在调用的前后可以对被代理对象进行操作
     */
    @Test
    public void prox(){
        Car car = new CarProx();
        car.run();
    }

静态代理一般结构:
1、通用接口
2、被代理对象实现接口
3、代理对象继承被代理对象或者接口
4、客户端调用代理对象

通过以上代码,我们可以看到我们在代理类初始化时生成被代理对象的实例对象,重写和被代理对象相同的方法,从而可以将代理对象当被代理对象使用,在调用代理对象的方法run()时,调用被代理对象的run()方法,实现代理功能,并且在代理的方法前后调用了after()和befor()方法,实现了对被代理对象功能的拓展。

静态代理的缺点

1)代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法。这样就出现了大量的代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。

2)代理对象只服务于一种类型的对象,如果要服务多类型的对象。势必要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了。

举例说明:代理可以对实现类进行统一的管理,如在调用具体实现类之前,需要打印日志等信息,这样我们只需要添加一个代理类,在代理类中添加打印日志的功能,然后调用实现类,这样就避免了修改具体实现类。满足我们所说的开闭原则。但是如果想让每个实现类都添加打印日志的功能的话,就需要添加多个代理类,以及代理类中各个方法都需要添加打印日志功能(如上的代理方法中删除,修改,以及查询都需要添加上打印日志的功能)

即静态代理类只能为特定的接口(Service)服务。如想要为多个接口服务则需要建立很多个代理类。

动态代理

根据如上的介绍,你会发现每个代理类只能为一个接口服务,这样程序开发中必然会产生许多的代理类,所以我们就会想办法可以通过一个代理类完成全部的代理功能,那么我们就需要用动态代理

在上面的示例中,一个代理只能代理一种类型,而且是在编译器就已经确定被代理的对象。而动态代理是在运行时,通过反射机制实现动态代理,并且能够代理各种类型的对象

实现动态代理有两种方法,一种是JDK动态代理,一种是CGLIB动态代理。下面我们来看看这两种动态大理的实现方法。

一、JDK动态代理

在Java中要想实现JDK动态代理机制,需要java.lang.reflect.InvocationHandler接口和 java.lang.reflect.Proxy 类的支持

java.lang.reflect.InvocationHandler接口的定义如下:

    //Object proxy:被代理的对象  
    //Method method:要调用的方法  
    //Object[] args:方法调用时所需要参数  
    public interface InvocationHandler {  
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;  
    }  

java.lang.reflect.Proxy类的定义如下:

    //CLassLoader loader:类的加载器  
    //Class<?> interfaces:得到全部的接口  
    //InvocationHandler h:得到InvocationHandler接口的子类的实例  
    public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException  

1.1、普通的代理模式

代理对象

package prox;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/*动态代理类只能代理接口(不支持抽象类),代理类都需要实现InvocationHandler类,
实现invoke方法。该invoke方法就是调用被代理接口的所有方法时需要调用的,该invoke方法返回的值是被代理接口的一个实现类  
*/
public class LogSuvCar implements InvocationHandler{
    private Object targetObject;

     //绑定关系,也就是关联到哪个接口(与具体的实现类绑定)的哪些方法将被调用时,执行invoke方法。       
    public Object newProxyInstance(Object targetObject){  
        //该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例    
        //第一个参数指定产生代理对象的类加载器,需要将其指定为和目标对象同一个类加载器  
        //第二个参数要实现和目标对象一样的接口,所以只需要拿到目标对象的实现接口  
        //第三个参数表明这些被拦截的方法在被拦截时需要执行哪个InvocationHandler的invoke方法  
        //根据传入的目标返回一个代理对象  
        this.targetObject = targetObject;
        return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), 
                targetObject.getClass().getInterfaces(), this);
    }
    @Override
    /*InvocationHandler接口的方法,proxy表示代理,method表示原对象被调用的方法,args表示方法的参数*/  
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
         System.out.println("start------------>>");
            if(args!=null){
                for(int i=0;i<args.length;i++){  
                    System.out.println(args[i]);  
                }  
            }
            Object ret=null;  
            try{  
                /*原对象方法调用前处理日志信息*/  
                System.out.println("satrt-->>");  

                //调用目标方法  
                ret=method.invoke(targetObject, args);  
                /*原对象方法调用后处理日志信息*/  
                System.out.println("success-->>");  
            }catch(Exception e){  
                e.printStackTrace();  
                System.out.println("error-->>");  
                throw e;  
            }  
            return ret;  
        }  


}

测试用例

    LogSuvCar logHandler=new LogSuvCar();  
    Car suvcar=(Car)logHandler.newProxyInstance(new SUVCar());  
    suvcar.run();  

代理实现类LogSuvCar实现InvocationHandler接口,重写invoke方法,在invoke方法中进行代理操作
LogSuvCar的newProxyInstance传入被代理对象赋值从而交给invoke方法代理,并且通过Proxy.newProxyInstance方法根据被代理对象的加载器,接口和代理类生成动态代理对象
调用动态代理对象的方法即可进行代理

1.2、匿名代理模式

@Test
    public void movePorxy2(){
        SUVCar suvcar=new SUVCar();
        Car car=(Car)Proxy.newProxyInstance(suvcar.getClass().getClassLoader(), suvcar.getClass().getInterfaces()
                , new InvocationHandler(){
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                         System.out.println("start------------>>");
                            if(args!=null){
                                for(int i=0;i<args.length;i++){  
                                    System.out.println(args[i]);  
                                }  
                            }
                            Object ret=null;  
                            try{  
                                /*原对象方法调用前处理日志信息*/  
                                System.out.println("satrt-->>");  

                                //调用目标方法  
                                ret=method.invoke(suvcar, args);  
                                /*原对象方法调用后处理日志信息*/  
                                System.out.println("success-->>");  
                            }catch(Exception e){  
                                e.printStackTrace();  
                                System.out.println("error-->>");  
                                throw e;  
                            }  
                            return ret;  
                    }

                });
        car.run();  
    }

1、首先生成被代理对象SUVCar
2、通过Proxy.newProxyInstance方法生成动态代理对象
3、newProxyInstance方法的第单个参数通过匿名方法生成实现接口InvocationHandle的对象
4、在匿名类中实现invoke方法实现代理

二、CGLIB动态代理

Cglib是一个强大的,高性能,高质量的代码生成类库。它可以在运行期扩展JAVA类与实现JAVA接口。其底层实现是通过ASM字节码处理框架来转换字节码并生成新的类。大部分功能实际上是ASM所提供的,Cglib只是封装了ASM,简化了ASM操作,实现了运行期生成新的class。

运行时动态的生成一个被代理类的子类(通过ASM字节码处理框架实现),子类重写了被代理类中所有非final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势植入横切逻辑。

2.1、Cglib的拦截器和过滤器

拦截器:实现MethodInterceptor接口的类,在intercept方法中实现对代理目标类的方法拦截。但同时Cglib为简化和提高性能提供了一些专门的回调类型如FixedValue(可以在实现的方法loadObject中指定返回固定值,而不调用目标类函数)、NoOp(把对回调方法的调用直接委派到这个方法的父类,即不进行拦截)

过滤器:实现CallbackFilter接口的类,通过accept方法返回一个下标值,用于指定调用哪个拦截器进行拦截处理

2.2、cglib实例

使用cglib需要引入两个jar包:cglib.jar,asm.jar,在marven中添加依赖

<dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.2.10</version>
</dependency>

1、被代理类

/**
 * 
 */
package prox.cglib;

/**
 * @author Administrator
 *cglib被代理类
 */
public class TDao {
    /**
     * 
     */
    public void add(){
    System.out.println("添加动作被执行");
    }
}

2、代理对象生成器

/**
 * 
 */
package prox.cglib;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

/**
 * @author Administrator
 *cglib代理类
 */
public class CglibProxyFactory {
    private Object obj;

    public CglibProxyFactory(Object obj) {
        this.obj = obj;
    }

    public Object getProxyFactory(){
        //Enhancer类是cglib中的一个字节码增强器,它可以方便的为你所要处理的类进行扩展
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(obj.getClass());//将目标对象所在的类作为Enhaner类的父类
        enhancer.setCallback(new MethodInterceptor() {
            //通过实现MethodInterceptor实现方法回调
            @Override
            public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                System.out.println("事务开启...");
                method.invoke(obj, args);
                System.out.println("事务结束...");

                return proxy;
            }
        });

        return enhancer.create();//生成目标对象并返回
    }
}

3、测试类

/**
 * 
 */
package prox.cglib;

/**
 * @author Administrator
 *
 */
public class TestCglibProxy {
    public static void main(String[] args) {
    TDao userDao = new TDao();
    TDao userDaoProxy = (TDao)new CglibProxyFactory(userDao).getProxyFactory();
    userDaoProxy.add();
    System.out.println("目标对象类型:"+userDao.getClass());
    System.out.println("代理对象类型:"+userDaoProxy.getClass());
    }
}

结果

事务开启...
添加动作被执行
事务结束...
目标对象类型:class prox.cglib.TDao
代理对象类型:class prox.cglib.TDao$$EnhancerByCGLIB$$f50b341e

JDK代理与cglib代理的区别

1)JDK动态代理只能对实现了接口的类生成代理,而不能针对类。

2)CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,并覆盖其中方法实现增强,但是因为采用的是继承,所以该类或方法最好不要声明成final,对于final类或方法,是无法继承的。

JDK代理与cglib代理的效率

1)使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,

在jdk6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的方法进行代理,

因为CGLib原理是动态生成被代理类的子类。

2)在jdk6、jdk7、jdk8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLIB代理效率,

只有当进行大量调用的时候,jdk6和jdk7比CGLIB代理效率低一点,但是到jdk8的时候,jdk代理效率高于CGLIB代理,

总之,每一次jdk版本升级,jdk代理效率都得到提升,而CGLIB代理消息确有点跟不上步伐。

spring对动态代理的选择

Spring如何选择用JDK还是CGLiB?

1)当Bean实现接口时,Spring就会用JDK的动态代理。

2)当Bean没有实现接口时,Spring使用CGlib是实现。

3)可以强制使用CGlib(在spring配置中加入)。

参考

动态代理之Cglib浅析
JDK和CGLIB动态代理区别

点赞

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注