likes
comments
collection
share

Java | 动态代理及作用

作者站长头像
站长
· 阅读数 34

作者:Mars酱

声明:本文章由Mars酱编写,部分内容来源于网络,如有疑问请联系本人。

转载:欢迎转载,转载前先请联系我!

什么是代理

代理实际上是一种处理问题的方式。在现实世界中,你登录不同的社交app去撩漂亮小姐姐,经过长时间的努力和你撩力值的提升,最终有一位小姐姐的心被你捕获,也终于有天,你们决定线下见面完成线下###活动。于是,你俩一次又一次的完成线下###活动。久而久之,你和小姐姐产生了深厚的情感,你俩觉得很舒服很开心,应该持续下去,于是她决定成为你的女友,一起奋(tong)斗(ju)。既然要奋(tong)斗(ju)了,那你俩得有住处,于是你积极的找房,终于在跑断双腿之前看中了倒数第二套房,房子的隔音效果比较好啊,也正是你俩想要的,可没想到有几个和你相同经历的朋友和你抢着看房:

Java | 动态代理及作用

原来他们也是和你一样撩到妹之后,决定和妹子共同奋(tong)斗(ju)。但可惜,你们反复过去看房,搞得房东接待不过来,最后房东决定不租了!你只好重新去找房子,这次你学聪明了,不再去满大街跑,你把自己的租房要求告诉给了房屋中介,毕竟中介也是过来人,了解了你的需求,为了满足你和女友打(tong)拼(ju)生活,中介给你提供了隔音效果好的房子,正好这套房的房东年迈已高,就想躺着收租、和自己老婆看着小电影,于是,中介带着你去看房,房子隔音果然好啊,还有24小时热水,完全满足你的实际需求。中介带着你看房,还给你各种推销吹嘘,你看完之后,你和中介加了好友,中介告诉你其他租客也要找他看:

Java | 动态代理及作用

最后,你为了和你爱的女友的未来,你果断决定租下了这套房子!房东也很开心,总算实现了躺床上和老婆看着小电影,手机每个月收到房租到账短信的理想生活。

由此可见,代理是日常生活中常见的模式,故事中的中介就是一个代理,他代理房东和租客之间的需求。

在软件开发中,代理模式是常用的结构型设计模式中的一种,特征是代理类与委托类有同样的接口,代理类主要负责提交请求给委托类的前后进行事件处理,比如:预处理消息、过滤消息,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并没有真正实现,而是通过调用委托类的对象的相关方法,来提供特定的服务。

代理模式结构

以上故事在软件开发中结构如下:

Java | 动态代理及作用

你带着女友,把上面结构用编码语言重新记录了下来,首先你创建了一个房东接口LandlordInterface,房东可以出租,也可以接待:

package com.mars;

/**
 * Created by Mars酱
 */
public interface LandlordInterface {
	/** 出租房屋 */
    void rentHouse();

    /** 接待看房 */
    void reception();

}

然后创建了一个房东的具体实现类 LandlordImpl,同时实现 LandlordInterface 接口:

package com.mars;

/**
 * Created by Mars酱
 */
public class LandlordImpl implements LandlordInterface {

	/** 出租房屋 */
    @Override
    public void rentHouse(){
        System.out.println("出租:房子已租出。");
    }
        

    /** 接待看房 */
    @Override
    public void reception(){
        System.out.println("接待:欢迎参观");
    }
}

你租下的那套房,房东是给中介代理了接待和出租,所以,在接待和出租前后都加上中介的服务,如下:

package com.mars;

/**
 * Created by Mars酱
 */
public class LandlordProxy implements LandlordInterface {

    private LandlordInterface impl;

    public LandlordProxy(LandlordInterface impl) {
        this.impl = impl;
    }

	/** 出租房屋 */
    @Override
    public void rentHouse(){
        System.out.println("出租前:这是房子钥匙,请您拿好!");
        impl.rentHouse();
        System.out.println("出租后:祝您和女友过上自己想要的生活!");
    }
        

    /** 接待看房 */
    @Override
    public void reception(){
        System.out.println("接待前:您好,我是中介。我会带你去房子里面看看。");
        impl.reception();
        System.out.println("接待后:希望这套房子您能满意,毕竟隔音效果好。");
    }
}

最后 ,你去看房,而且决定租下来了。

/**
 * Created by Mars酱
 */
public class Renter {

    LandlordInterface landlordProxy = new LandlordProxy(new LandlordImpl());
    
    public void seeHouse(){
        // 中介接待你
        landlordProxy.reception();
    }

    public void iRentThisHouse(){
    	// 房子出租
        landlordProxy.rentHouse();
    }
} 

写上main函数:

/**
 * Created by Mars酱
 */
public static void main(String[] args) {
	Renter you = new Renter();
	you.seeHouse();
	System.out.println(">> 看完房子,回去和女友商量了一番,如是决定了!");
	you.iRentThisHouse();
}

运行结果:

Java | 动态代理及作用

静态代理

以上故事的代码实现,其实就是静态代理的实现过程,中介持有房东需求,然后通过房东的基本需求,在接待和出租前后加上自己的服务实现静态代理。 但这个代理方式存在着问题:

  • 当房东接口增加了需求,那么房东实现都要增加这个需求的实现。与此同时,中介代理也必须实现新增的房东接口的需求代理,这样对于房东和中介都不是很友好。
  • 中介代理目前只是对1个房东出租代理,假设添加一个厂房房东的实现,那么就要再增加对应的厂房中介代理,这样对代理类的维护也变得非常繁琐。

上面的问题在动态代理中就得到了比较好的解决。

动态代理

先看下网上搜索出来的定义:

动态代理类与静态代理类最主要不同的是,代理类的字节码不是在程序运行前生成的,而是在程序运行时在虚拟机中程序自动创建的。

其实动态代理与静态代理的本质一样,最终程序运行时都需要生成一个代理对象实例,通过它来完成相关增强以及业务逻辑,只不过静态代理需要硬编码的方式指定,而动态代理则是以动态方式生成代理(有编译时操作字节码生成的方式、运行时通过反射、字节码生成的式)。

由此可见,动态的好处很明显,代理逻辑与业务逻辑是互相独立的,没有耦合,代理1个类或100个类要做的事情没有任何区别。

在java中,常见的动态代理有两种:一种是JDK自带的动态代理,一种是CGLib的动态代理。

JDK动态代理

JDK动态代理是由JDK官方提供的代理方式,主要有java.lang.reflect.Proxyjava.lang.reflect.InvocationHandler两个类完成。

java.lang.reflect.Proxy:通过java反射,创建出代理类;

java.lang.reflect.InvocationHandler:负责接口方法的调用;

LandlordInterface、LandlordImpl都不用变,动态代理是改变中介和租客部分的逻辑,上面故事使用JDK动态代理的代码实现:

/** Created by Mars酱 */
/**
 * 代理服务
 *
 * @param landlordInterface LandlordInterface接口
 * @return InvocationHandler对象
 */
private InvocationHandler proxyService(LandlordInterface landlordInterface) {
    return (proxy, method, args1) -> {
            // 如果是接待服务
        if (method.getName().equalsIgnoreCase("reception")) {
            System.out.println("接待前:您好,我是中介。我会带你去房子里面看看。");
            method.invoke(landlordInterface, args1);
            System.out.println("接待后:希望这套房子您能满意,毕竟隔音效果好。");
        }
        return null;
	};
}

/**
 * 动态创建对象
 *
 * @param invocationHandler InvocationHandler对象
 * @return	LandlordInterface对象	
 */
private LandlordInterface getLandlord(InvocationHandler invocationHandler) {
	return (LandlordInterface) Proxy.newProxyInstance(LandlordInterface.class.getClassLoader(), new Class[]{LandlordInterface.class}, invocationHandler);
}

再创建一个只有单间的房东对象:

/**
 * Created by Mars酱
 */
public class LandlordSingleRoomImpl implements LandlordInterface {
    @Override
    public void rentHouse() {
        System.out.println("出租:单间房子已租出。");
    }

    @Override
    public void reception() {
        System.out.println("接待:欢迎参观,我们这里是单间,隔音效果很好");
    }
}

写上main函数:

/**
 * Created by Mars酱
 */
public static void main(String[] args) {
	RenterUseJDKProxy renterJDKProxy = new RenterUseJDKProxy();
    // 1. 代理看普通房东
    LandlordInterface normalLandlord = renterJDKProxy.getLandlord(renterJDKProxy.proxyService(new LandlordImpl()));
    normalLandlord.reception();
    System.out.println(">> 看完房子: 房子不太好,换一家");
    System.out.println();
	// 2. 代理看单间房东,听说隔音不错
    LandlordInterface singleRoomLandlord = renterJDKProxy.getLandlord(renterJDKProxy.proxyService(new LandlordSingleRoomImpl()));
    singleRoomLandlord.reception();
    System.out.println(">> 看完房子: 这房子不错,回去和女友商量下");

}

执行结果:

Java | 动态代理及作用

由此,用到JDK动态代理,需要实现的是InvocationHandler接口。那么,如果去实现InvocationHandler接口,是不是可以代理成功呢?试试看,我们创建一个不需要实现接口的房东类:

/**
 * Created by Mars酱
 */
public class LandlordImpl2 {

    public void rentHouse() {
        System.out.println("出租2:房子已租出。");
    }

    public void reception() {
        System.out.println("接待2:欢迎参观");
    }
}

再创建一个租客,再通过Proxy去调用:

/**
 * Created by Mars酱
 */
public class Renter2UseJDKProxy {

    /**
     * 代理服务
     *
     * @param landlordInterface LandlordInterface接口
     * @return 调用程序
     */
    private InvocationHandler proxyService(LandlordImpl2 landlordInterface) {
        return (proxy, method, args1) -> {
            // 如果是接待服务
            if (method.getName().equalsIgnoreCase("reception")) {
                System.out.println("接待前:您好,我是中介。我会带你去房子里面看看。");
                method.invoke(landlordInterface, args1);
                System.out.println("接待后:希望这套房子您能满意,毕竟隔音效果好。");
            }
            return null;
        };
    }

    /**
     * 动态创建中介对象
     *
     * @param invocationHandler
     * @return
     */
    private LandlordImpl2 getLandlord(InvocationHandler invocationHandler) {
        return (LandlordImpl2) Proxy.newProxyInstance(LandlordImpl2.class.getClassLoader(),
                new Class[]{LandlordImpl2.class},
                invocationHandler);
    }


    public static void main(String[] args) {
        Renter2UseJDKProxy renterJDKProxy = new Renter2UseJDKProxy();
        // 1. 代理看普通房东
        LandlordImpl2 landlord2 = renterJDKProxy.getLandlord(renterJDKProxy.proxyService(new LandlordImpl2()));
        landlord2.reception();
        System.out.println(">> 看完房子: 这房子不错,回去和女友商量下");
    }
}

运行main函数:

Java | 动态代理及作用

错误提示说我们的这个房东不是一个接口,那么看来使用JDK动态代理是必须要实现业务接口的。这样看来就不是太灵活了,有些房东想着轻松,会把房子代理给中介出租,有些人就想自己出租不喜欢中介,JDK动态代理就无法满足这种情况了。

CGLib动态代理

CGLib的定义,先看网上搜到的说明:

CGLib (Code Generation Library) 是一个开源项目。是一个强大的、高性能、高质量的 Code 生成类库。它可以在运行期扩展 Java 类与实现 Java 接口。CGLib 比 Java 的 java.lang.reflect.Proxy 类更强的在于它不仅可以接管接口类的方法还可以接管普通类的方法

CGLib 的底层是 Java 字节码操作框架:ASM

CGLib的优势它自己写了,根据官方说明,只需要用到两个类:net.sf.cglib.proxy.MethodInterceptornet.sf.cglib.proxy.Enhancer,前者用来在被代理业务的前后添加自己的逻辑,后者负责生成动态代理类。那么我们来试下吧!兄弟们,上车~

/**
 * Created by Mars酱
 */
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;

public class RenterUseCglibProxy {

    public static void main(String[] args) {
        LandlordInterface normalLandlord = (LandlordInterface) cglibProxy(new LandlordImpl()).create();
        normalLandlord.reception();
        System.out.println(">>> 房子一般,我再看看!");
        System.out.println();
        LandlordInterface singleLandlord = (LandlordInterface) cglibProxy(new LandlordSingleRoomImpl()).create();
        singleLandlord.reception();
    }

    private static Enhancer cglibProxy(LandlordInterface landlordInterface) {
        Enhancer enhancer = new Enhancer();
        // 1.设置类加载
        enhancer.setClassLoader(landlordInterface.getClass().getClassLoader());
        // 2.设置被代理的类
        enhancer.setSuperclass(landlordInterface.getClass());
        // 3.设置好回调
        enhancer.setCallback((MethodInterceptor) (obj, method, params, methodProxy) -> {
            System.out.println("接待前:您好,我是中介。我会带你去房子里面看看。");
            Object result = methodProxy.invokeSuper(obj, params);
            System.out.println("接待后:希望这套房子您能满意。");
            return result;
        });
        // 4.创建代理类
        return enhancer;
    }
}

运行之后的结果:

Java | 动态代理及作用

效果符合。但是这里cglibProxy()传递的是LandlordInterface接口,那么cglib不是说好的可以代理普通类吗?前面的LandlordImpl2怎么处理呢?我们把cglib这里改造下吧

/**
 * Created by Mars酱
 */
public class RenterUseCglibProxy {

    public static void main(String[] args) {
        LandlordInterface normalLandlord = (LandlordInterface) cglibProxy(LandlordImpl.class).create();
        normalLandlord.reception();
        System.out.println(">>> 房子一般,我再看看!");
        System.out.println();
        LandlordInterface singleLandlord = (LandlordInterface) cglibProxy(LandlordSingleRoomImpl.class).create();
        singleLandlord.reception();
        System.out.println(">>> 房子不错,但是我想再看看!");
        System.out.println();
        // 自己房子的房东返回的就是自己
        LandlordImpl2 singleLandlordSelf = (LandlordImpl2) cglibProxy(LandlordImpl2.class).create();
        // 然后自己做接待
        singleLandlordSelf.reception();
        System.out.println(">>> 房子不错,我想想再决定!");
    }

    /**
     * cglibProxy函数接受Class对象
     */
    private static Enhancer cglibProxy(Class clazz) {
        Enhancer enhancer = new Enhancer();
        enhancer.setClassLoader(clazz.getClassLoader());
        enhancer.setSuperclass(clazz);
        enhancer.setCallback((MethodInterceptor) (obj, method, params, methodProxy) -> {
            System.out.println("接待前:您好,我是中介。我会带你去房子里面看看。");
            Object result = methodProxy.invokeSuper(obj, params);
            System.out.println("接待后:希望这套房子您能满意。");
            return result;
        });
        return enhancer;
    }
}

运行之后的结果:

Java | 动态代理及作用

细心的朋友发现,最后那个接待语怎么还是用的中介的呢?那是因为自住房房东的接待方法和中介的一致,为了区别,我们可以再改造一下,cglib中有个回调过滤器net.sf.cglib.proxy.CallbackFilter,我们用这个过滤器处理一下自住房的房东接待语

/**
 * Created by Mars酱
 */
private static Enhancer cglibProxy(Class clazz) {
	Enhancer enhancer = new Enhancer();
	enhancer.setClassLoader(clazz.getClassLoader());
	enhancer.setSuperclass(clazz);
	// 1. 创建两个回调,一个用来适用于中介,一个适用于自住房,数组索引从0开始
	enhancer.setCallbacks(new Callback[]{(MethodInterceptor) (obj, method, params, methodProxy) -> {
		System.out.println("接待前:您好,我是中介。我会带你去房子里面看看。");
		Object result = methodProxy.invokeSuper(obj, params);
        System.out.println("接待后:希望这套房子您能满意。");
	    return result;
	}, (MethodInterceptor) (obj, method, params, methodProxy) -> {
		System.out.println("接待前:您好,我不是中介。今天我带你去我自己房子里面看看。");
		Object result = methodProxy.invokeSuper(obj, params);
		System.out.println("接待后:你什么时候有空就给我个答复。");
		return result;
    }});
	// 2. 创建函数过滤器,如果是自己房子的函数,使用第二个回调函数,回调数组中索引为1
	enhancer.setCallbackFilter(new CallbackFilter() {
		@Override
		public int accept(Method method) {
			return (method.getName().equalsIgnoreCase("selfReception")) ? 1 : 0;
		}

        @Override
		public boolean equals(Object o) {
			return false;
        }
    });
    return enhancer;
}

同样的,自住房的房东接待函数名也改个名为:

/**
 * Created by Mars酱
 */
// 原函数名为:reception()
public void selfReception() {
	System.out.println("接待2:欢迎参观,这是我自己的房子,没有中介。");
}

执行之后的结果:

Java | 动态代理及作用

好了,cglib确实可以代理接口对象和普通对象。

总结

Java动态代理,介绍了两种方式:JDK动态代理和开源Cglib动态代理,它们的实现机制不同:

  1. JDK 动态代理是通过实现目标类的接口,然后将目标类在构造动态代理时作为参数传入,使代理对象持有目标对象,再通过代理对象的 InvocationHandler 实现动态代理的操作。
  2. CGLIB 动态代理是通过配置目标类信息,然后利用 ASM 字节码框架进行生成目标类的子类。当调用代理方法时,通过拦截方法的方式实现代理的操作。

共同的部分,底层其实都是用到了通过反射完成,

总的来说,JDK 动态代理利用接口实现代理,Cglib 动态代理利用继承的方式实现代理。

而动态代理的作用就在于:

做方法的增强,在不改变源代码的情况下给原来的方法前后增加一些其他方法。比如:日志记录,事务管理等等

动态代理在 Java 开发中是非常常见的,在日志,监控,事务,技术框架中都有着广泛使用。动态代理的底层原理下次再讲。

转载自:https://juejin.cn/post/7215963267490627644
评论
请登录