java设计模式————代理模式,了解jdk动态代理和cglib动态代理的区别

代理模式:

指为其它对象提供一种代理,以控制对这个对象的访问。

代理对象在客服端和目标对象之间起到中介作用。

属于结构性的设计模式。

代理模式的分类:

静态代理。

动态代理。

静态代理

好,那么我们首先从静态代理开始:

角色分析:

1.抽象角色:一般使用接口或者抽象类来解决

2.真实角色:被代理的角色

3.代理角色:代理真实角色,代理真实角色后,我们一般会做一些附属操作

4.客户:访问代理对象的人

代码步骤:

1、接口

/**
 * @Author Darker
 * @Descrption 租房
 * @Date : Created in 10:00 2020-3-11
 */
public interface Rent {
    public void rent();
}

2、真实角色

/**
 * @Author Darker
 * @Descrption
 * @Date : Created in 10:01 2020-3-11
 */
public class Host implements Rent {
    @Override
    public void rent() {
        System.out.println("房东出租房子");
    }
}

3、代理角色

/**
 * @Author Darker
 * @Descrption 代理,中介
 * @Date : Created in 10:02 2020-3-11
 */
public class Proxy{

    private Host host;

    public Proxy(){}

    public Proxy(Host host){
        this.host = host;
    }

    public void rent(){
        seeHouse();
        host.rent();
        signContract();
        fare();
    }

    public void seeHouse(){
        System.out.println("中介带你看房子");
    }

    public void signContract(){
        System.out.println("签租房合同");
    }

    public void fare(){
        System.out.println("收中介费");
    }
}

4、客户端访问

/**
 * @Author Darker
 * @Descrption 客户端租房
 * @Date : Created in 10:02 2020-3-11
 */
public class Client {
    public static void main(String[] args) {
        Host host = new Host();
        Proxy proxy = new Proxy(host);
        proxy.rent();
    }
}

静态代理模式的好处:

1.可以使真实角色的操作更加存粹!不用去关注一些公共的业务。

2.公共角色交给代理角色!实现了业务的分工!

3.公共业务发生扩展的时候,方便集中管理。

缺点:

一个真实角色就会产生一个代理角色,代码量会翻倍。

当然,上面的例子可能在我们的实际开发中不常见,但主要是来理解代理模式的意义,现在我们举一个代码中的应用,就拿curd来讲讲。

1、首先我们公司有个业务是对用户的curd操作;

/**
 * @Author Darker
 * @Descrption
 * @Date : Created in 10:19 2020-3-11
 */
public interface UserService {
    public void add();

    public void delete();
}

/**
 * @Author Darker
 * @Descrption
 * @Date : Created in 10:20 2020-3-11
 */
public class UserServiceImpl implements UserService {
    @Override
    public void add() {
        System.out.println("增加一个角色");
    }

    @Override
    public void delete() {
        System.out.println("删除一个角色");
    }
}

2、好,现在问题来了,我们公司要求给每一步都加上日志。

首先思考,给每一个userImpl里面的方法加上日志操作,太傻了吧,还不要累死人,而且也不符合开闭原则。

那么,我们想到了代理模式,我们用代理来完成。

/**
 * @Author Darker
 * @Descrption 静态代理模式
 * @Date : Created in 10:26 2020-3-11
 */
public class Proxy implements UserService{
    
    private UserServiceImpl userService;

    public void setUserService(UserServiceImpl userService) {
        this.userService = userService;
    }

    @Override
    public void add() {
        log("add");
        userService.add();
    }

    @Override
    public void delete() {
        log("delete");
        userService.delete();
    }

    public void log(String msg){
        System.out.println("调用了"+msg+"方法");
    }
    
}

/**
 * @Author Darker
 * @Descrption 客户端
 * @Date : Created in 10:20 2020-3-11
 */
public class Client {
    public static void main(String[] args) {
        UserServiceImpl userService = new UserServiceImpl();
        Proxy proxy = new Proxy();
        proxy.setUserService(userService);
        proxy.add();
    }
}

这样,我们就完成了一个添加日志的功能了,而且符合了开闭原则,同时降低了耦合性,这就是静态代理模式的应用了。

但是,这样每个真实角色都要一个代理,就像上面的每个service都要一个proxy,还是太麻烦了,怎么来解决呢,作为程序员肯定会想到利用反射,就像简单工厂一样,我不想传字符串进去区分哪个子类了,我要直接传class,然后利用反射自动生成那个类的工厂,那么,我们是不是也可以直接利用反射自动生成我们要代理的代理类呢,思想是有了,具体怎么实现?

动态代理

1、角色分析:静态代理和动态代理角色一样

2、动态代理类是自动动态生成的,不是我们直接写好的

3、动态代理分为两大类:基于接口的动态代理,基于类的动态代理

    *基于接口 ----jdk动态代理

    *基于类:cglib

    *java字节码实现:javasist(稍微了解下就成)

jdk动态代理

这里我们重点来讲下jdk的动态代理,首先我们要先了解两个类:Proxy(代理),invocationHandler(调用处理)

先看看InvocationHandler这个类的api,我们可以发现它是反射包下的一个接口,那么我们可以知道它确实利用了反射技术。

我们点进去看看发现它只有一个方法

 好,那么我们再来看看下一个类proxy

同样,它也是反射包下面的类,同时它告诉了你两种方法来创建代理 

方法1是先调用了方法getProxyClass先获得了Constructor对象,然后用这个构造器对象newInstance来构造这个对象,同时把你传的handler传进来,最后返回一个你要的代理类。

方法2更加简单,直接调用newProxcyInstance方法来直接返回该对象的代理类。 

首先我们的目的是获得一个动态代理,那就意味着我传什么类进来都要能给我返回一个代理对象,同时我们的这个对象需要有一个操作方法来对你要代理的类做一点操作,所以从刚刚的api可以看出,这个invocationHadler就相当于我们需要实现的抽象接口,就像我们第一个静态代理中的租房行为(一个代理类和真实类共通的方法)。

1.万能代理生成者,可以代理任意接口,返回你要的代理类对象。

/**
 * @Author Darker
 * @Descrption
 * @Date : Created in 11:42 2020-3-11
 */
public class DynamicProxyProvider implements InvocationHandler {
    //被代理的接口
    private Object target;

    public void setObject(Object target) {
        this.target = target;
    }

    //第一个参数是为了加载类在哪个位置classLoader
    //代理类的接口
    //自己本身
    public Object getProxy(){
      return  Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
    }


    //从参数可以看出我们需要一个代理类,所以我们来一个方法生成代理类
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        log(method.getName());
        Object invoke = method.invoke(target, args);
        return invoke;
    }

    public void log(String msg){
        System.out.println("调用了"+msg+"方法");
    }
}

2.需要被代理的接口和实体。

/**
 * @Author Darker
 * @Descrption 租房
 * @Date : Created in 10:00 2020-3-11
 */
public interface Rent {
    public void rent();
}


/**
 * @Author Darker
 * @Descrption
 * @Date : Created in 10:01 2020-3-11
 */
public class Host implements Rent {
    @Override
    public void rent() {
        System.out.println("房东出租房子");
    }
}

/**
 * @Author Darker
 * @Descrption
 * @Date : Created in 10:19 2020-3-11
 */
public interface UserService {
    public void add();

    public void delete();
}

/**
 * @Author Darker
 * @Descrption
 * @Date : Created in 10:20 2020-3-11
 */
public class UserServiceImpl implements UserService {
    @Override
    public void add() {
        System.out.println("增加一个角色");
    }

    @Override
    public void delete() {
        System.out.println("删除一个角色");
    }
}

3.客户端根据代理需求来调用

/**
 * @Author Darker
 * @Descrption
 * @Date : Created in 11:50 2020-3-11
 */
public class Client {
    public static void main(String[] args) {
        Rent rent = new Host();
        DynamicProxyProvider proxyProvider = new DynamicProxyProvider();
        proxyProvider.setObject(rent);
        Rent proxyRent =(Rent) proxyProvider.getProxy();
        proxyRent.rent();

        UserServiceImpl userService = new UserServiceImpl();
        proxyProvider.setObject(userService);
        UserService proxyUserService = (UserService) proxyProvider.getProxy();
        proxyUserService.add();
    }
}

 

 很神奇不是,利用了代理,可以给你想要接口来增强,比如添加日志,添加操作记录等。

优点:

1.拥有静态代理的所有优点

2.一个动态代理类代理的是一个接口,一般就是对应一类的业务

3.一个动态代理类可以代理多个类,只要实现了同一个接口

但是!但是!但是!!!!这两个类它到底是怎么做到动态生成代理的呢,为什么我在invoke方法里面加了方法后,我每个代理类方法都会改变呢?

好,我们现在来分析下动态代理的实现原理。

1.拿到被代理类的引用,并且获取它的所有接口(反射获取)。注明:为什么不能直接代理类,非要以接口的形式来代理,因为它代码中是反射获得interfce的所有方法。

2.jdk Proxy类重新生成一个新的类,实现了被代理类的所有接口的方法。

3.动态生成java代码,把增强逻辑加入到新生成代码中。

4.编译生成新的java代码的class文件。

5.加载并重新运行新的class,得到类就是全新的类。

为什么说我们得到了一个全新的类呢,我们来输出一下这个类看看,我们会发现它的类型变成了$Proxy1而不是UserServiceImpl。

那jdk到底是不是如我们所想如此做的呢,我们进入它的方法中去看看。

 

果然,它是重新生成了一个类,这也就可以解释它为什么可以增强我们的方法了,它把所有方法都重写了一遍,如果去看它反编译出来的class文件,你还会发现它的方法都是final的,意味着它只能被代理一次(还有,上面为什么实现接口的长度要小于65535呢,我从其它人博客中找到了答案)。

 cglib动态代理

上面聊了jdk的动态代理,发现了一个问题,那就是只能代理有一个总接口的所有子类,通过扫描这个接口里面的所有方法来重写一个新的class,那么问题来了,我们如果没有接口怎么办,我就只有一个类,当然,这也有解决办法的,那就是cglib动态代理来实现。

1.先写一个cglib的代理类(cglib实现的是它包下的一个拦截器MethodInterceptor,通过把你要代理的类设置成它的父类的形式来代理,也就是利用继承的原理,子类可以继承父类并且重写父类的方法)

/**
 * @Author Darker
 * @Descrption 实现一个cglib的拦截器
 * @Date : Created in 16:32 2020-3-11
 */
public class CGlibProxyPriveder implements MethodInterceptor {

    public Object getProxy(Class<?> clazz){
        //相当于cglib给你写了个生成代理的工具类,你只要把class传进来就可以了
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) {
        log(method.getName());
        try {
            methodProxy.invokeSuper(o,objects);
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return null;
    }

    public void log(String msg){
        System.out.println("调用了"+msg+"方法");
    }
}

2.被代理的类,不实现接口,当然实现也可以,主要是为了和jdk动态代理进行下区分。

/**
 * @Author Darker
 * @Descrption
 * @Date : Created in 17:34 2020-3-11
 */
public class UserImpl {
    public void add(){
        System.out.println("添加一个用户");
    }
}

3.运行

/**
 * @Author Darker
 * @Descrption
 * @Date : Created in 17:36 2020-3-11
 */
public class CGlibTest {
    public static void main(String[] args) {
        CGlibProxyPriveder cGlibProxyPriveder = new CGlibProxyPriveder();
        UserImpl cGlibProxy = (UserImpl)cGlibProxyPriveder.getProxy(UserImpl.class);
        cGlibProxy.add();
    }
}

ok,成功了,看来cglib也完成了我们的代理功能。

总结:

1.jdk动态代理是利用反射机制实现的,它是面向接口的;而cglib动态代理是基于字节码底层继承代理类来实现的,CGLib是一个强大、高性能的Code生产类库,可以实现运行期动态扩展java类(因为是继承实现,所以如果被代理类如果由final关键字修饰,那么会失败)。

2.关于两者性能问题:

CGLib所创建的动态代理对象在实际运行时候的性能要比JDK动态代理高不少,因为它会生成一个包含所有逻辑的FastClass,里面包含了被调用的方法,不需要再通过反射来调用,有研究表明,大概速度要高10倍;

但是CGLib在创建对象的时候所花费的时间却比JDK动态代理要多很多,因为cglib生成代理的逻辑更加复杂,有研究表明,大概有8倍的差距;

因此,对于singleton的代理对象或者具有实例池的代理,因为无需频繁的创建代理对象,所以比较适合采用CGLib动态代理,反正,则比较适用JDK动态代理。

代理模式和spring

好,我们一直都说springAop的实现是用了代理模式,那么你知道它是用了哪一种动态代理来实现的嘛,现在我们来找到spring-aop下面的ProxyFactoryBean来看看,找到里面的getObject方法。

你会看到它这里有个判断,如果你要的代理类是单例,就返回一个单例式的代理,如果式多例,就返回一个多例的代理,我们就看看这个返回单例的。

 

从上面的判断可以知道,但bean有实现接口,同时配置文件中没有配置强行使用cglib代理的时候,它就会自动使用jdk动态代理,否则如果这个bean没有接口,那么它就会使用cglib来动态代理。

附:

配置cglib的配置开启(可以看出,配置其实都来源于源码,这里和mybatis扫描类的四种方式有异曲同工之妙):

发布了27 篇原创文章 · 获赞 1 · 访问量 3644

猜你喜欢

转载自blog.csdn.net/qq_40111437/article/details/104789444
今日推荐