Java | 动态代理及作用

时间:2023-04-03 12:02:56

作者: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 开发中是非常常见的,在日志,监控,事务,技术框架中都有着广泛使用。动态代理的底层原理下次再讲。