Java设计模式之代理模式的简介

代理模式

代理模式的定义:为另一个对象提供一个替身或占位符,以控制对这个对象的访问。(Head First设计模式给出的定义)

为什么要用代理模式

        动态代理技术是整个Java技术中最重要的一个环节,它是学习Java框架的基础,不会动态代理技术,学习Spring框架是学不明白的。
动态代理技术就是用来产生一个对象的代理对象的。在开发中为什么会需要一个代理对象呢?
我们可以举一个现实生活中的例子:
        每个明星都会有一个自己的经纪人,这个经纪人其实就是他们的代理人,当有剧组要找明星拍电影时,不能直接给这个明星打电话联系他,只能是联系到这个明星的代理人也就是经纪人。比如最近特别火的19版《倚天屠龙记》中赵敏的扮演者陈钰琪,她会唱歌、跳舞、会拍戏,在陈钰琪没有出名之前,我们可以直接找她唱歌、跳舞、拍戏,当她出名之后,她干的第一件事情就是找一个经纪人,这个经纪人就是陈钰琪的代理。当我们想要找陈钰琪表演时,不能直接找到陈钰琪了,陈钰琪说:“具体事宜你找我经纪人商谈吧!”,所以只能去找她的经纪人,因此陈钰琪的这个经纪人存在的价值就是拦截/阻止我们对陈钰琪的直接访问!!!
        这个现实中的例子和我们在开发中是一样的,我们在开发中之所以要产生一个对象的代理对象,主要用于拦截/阻止对真实业务对象的访问,代理对象应该具有和目标对象相同的方法

所以在这里我们必须要明白两个概念:

  1. 代理对象存在的价值主要用于拦截对真实业务对象的访问。
  2. 代理对象应该具有和目标对象(真实业务对象)相同的方法。

代理模式的关键特点是:代理对象是目标对象的扩展,并会调用目标对象


Java中的代理

一、静态代理

静态代理在使用时,需要定义接口或父类,目标对象(被代理对象,可以理解为明星)与代理对象一起实现相同的接口或者是继承相同的父类。
下面我们举个案例进行解释:
Subject接口,比如我们先定义一个唱歌的方法:

/**
 * @Auther: 马旭辉
 * @Date: 2019/4/29 14:48
 * @Description: 代理模式接口
 */
public interface Subject {
    //唱歌的方法
    void sing();
}

现在创建一个委托对象,它实现了Subject接口:

/**
 * @Auther: 马旭辉
 * @Date: 2019/4/29 14:47
 * @Description: 明星--委托对象
 */
public class Star implements Subject {

    @Override
    public void sing() {
        System.out.println("明星开始唱歌!");
    }
}

然后再创建一个代理对象:

/**
 * @Auther: 马旭辉
 * @Date: 2019/4/29 15:32
 * @Description: 明星的经纪人,代理对象
 */
public class Broker implements Subject {
    private Subject subject;

    public Broker(Subject subject) {
        this.subject = subject;
    }

    @Override
    public void sing() {
        //对目标对象的唱歌sing方法进行补充
        System.out.println("向大家问好!");
        this.subject.sing();
        System.out.println("谢谢大家,再见!");
    }
}

最后我们进行测试一下,编写测试类:

/**
 * 测试类
 */
public class Main {

    public static void main(String[] args) {
        //创建一个目标对象--明星
        Star star = new Star();
        //创建一个代理对象--经纪人
        Broker tony = new Broker(star);
        //执行代理对象的方法
        tony.sing();
    }
}

我们来看一下运行结果:
Java设计模式之代理模式的简介_第1张图片
通过以上代码及运行结果,我们可以看出代理模式的特点,代理类需要接收一个Subject接口对象,任何一个实现了该接口的对象,都可以通过代理类进行代理,增加了通用性。
但是!!!也有缺点,每一个代理类都必须实现一遍委托类的接口,如果接口增加方法,则代理类也必须跟着修改。代理类每一个接口对象对应一个委托对象,而且如果委托对象非常多,则静态代理类就非常臃肿,难以胜任。

二、动态代理

动态代理有以下特点:

  1. 代理对象,不需要实现接口。
  2. 代理对象的生成,是利用Java的API,动态的在内存中构建代理对象。(需要我们指定创建代理对象/目标对象实现的接口的类型)
  3. 动态代理也叫做JDK代理、接口代理。

JDK中生成代理对象的API:
代理类所在的包:java.lang.reflect.Proxy,这是JDK1.5以后才开始提供的类。
JDK实现代理只需要使用newProxyInstance()方法,但是该方法需要接收三个完整的参数,如下:

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)

该方法是在Proxy类中的静态方法,且接收的三个参数依次为:

  • ClassLoader loader:指定当前目标对象使用类加载器,获取加载器的方法是固定的。
  • Class[] interfaces:目标对象实现的接口类型,使用泛型方式确认类型。
  • InvocationHandler h:事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入。

现在我们来举个例子解释一下动态代理模式:
首先依然需要一个Subject接口和一个委托类:

public interface Subject {
    //唱歌的方法
    void sing();
}
public class Star implements Subject {

    @Override
    public void sing() {
        System.out.println("明星开始唱歌!");
    }
}

前两步跟静态代理模式是一样的步骤,下面就有区别了,创建一个动态代理类,实现InvocationHandler接口,并重写该类的invoke方法 :

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * @Auther: 马旭辉
 * @Date: 2019/4/29 16:02
 * @Description: 动态代理
 */
public class DynamicProxy implements InvocationHandler {
    private Object object;

    //构造方法
    public DynamicProxy(Object object) {
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //在代理真实对象前我们可以添加一些自己的操作
        System.out.println("先向大家问好!");
        //当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的 handler对象的 invoke方法来进行调用
        Object result = method.invoke(object, args);
        //在代理真实对象之后我们也可以添加一些自己的操作
        System.out.println("跟大家说再见!");
        return result;
    }
}

然后编写测试类:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

/**
 * 测试类
 */
public class Main {

    public static void main(String[] args) {
        //我们要代理的真实对象
        Subject subject = new Star();
        //要代理哪个对象就把哪个对象传进去,最后是通过该真实对象来调用其方法的
        InvocationHandler handler = new DynamicProxy(subject);
        /*
         * 通过Proxy的newProxyInstance方法来创建我们的代理对象,需要传入三个参数:
         * 1,handler.getClass().getClassLoader(),这里我们使用Handler这个类的ClassLoader对象来加载我们的代理对象。
         * 2,subject.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象实现的接口,表示我要代理的是这个真实对象,这样我就可以调用这组接口中的方法了。
         * 3,handler,这个参数的意思是将这个代理对象关联到了上方的InvocationHandler这个对象上。
         */
        Subject tony = (Subject) Proxy.newProxyInstance(handler.getClass().getClassLoader(), subject.getClass().getInterfaces(), handler);
        tony.sing();
    }
}

最后我们查看控制台运行结果:
Java设计模式之代理模式的简介_第2张图片
动态代理弥补了静态代理的不足,但是这个世界上没有十全十美的事物,我们可以看出静态代理和动态代理有一个共同的缺点,就是目标对象必须实现一个或多个接口,假如不想让他们必须实现接口的话,那就可以使用CGLIB代理。

三、CGLIB代理

上面的静态代理和动态代理模式都是要求目标对象实现一个接口,但是有的时候目标对象是一个单独的对象,并没有实现任何的接口,这个时候就可以使用以目标对象子类的方式实现代理(可以先理解为让这个明星的儿子/女儿当这个明星的经纪人),这种方法就叫做:CGLIB代理。

CGLIB代理,也叫做子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展。

  • JDK的动态代理有一个限制就是使用动态代理的对象必须实现一个或多个接口,如果想代理一些没有实现任何接口的类,就可以使用CGLIB实现。
  • CGLIB是一个强大的高性能的代码生成包,它可以在运行期间扩展Java类与实现Java接口,它广泛的被众多AOP框架所使用,比如最常用的Spring AOP和synaop,可以为他们提供方法的interception。(拦截)
  • CGLIB 包的底层是通过使用一个小而快的字节码处理框架ASM来转换字节码并生成新的类,不建议直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。

CGLIB子类代理需要注意的是,目标对象不能为final!
一定要确保你的项目有spring的依赖!!!否则不能使用。如果是个特别单纯的项目那就加一个spring-core-xxx.jar,下面进行代码实现CGLIB代理模式,先创建目标目标对象类:

/**
 * 
 * @author 马旭辉
 * @date: 2019年4月30日 下午2:11:57 
 * 没有实现任何接口
 */
public class Star {
	// 唱歌的方法
	public void sing() {
		System.out.println("====正在唱歌====");
	}
}

然后创建CGLIB子类代理工厂,对Star在内存中动态构建一个子类对象:

import java.lang.reflect.Method;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
/**
 * 
 * @author 马旭辉
 * @date:   2019年4月30日 下午2:32:47
 * CGLIB子类代理工厂
 */
public class ProxyFactory implements MethodInterceptor {
	// 维护目标对象
	private Object target;

	public ProxyFactory(Object target) {
		super();
		this.target = target;
	}

	// 给目标对象创建一个代理对象
	public Object getProxyInstance() {
		// 工具类
		Enhancer en = new Enhancer();
		// 设置父类
		en.setSuperclass(target.getClass());
		// 设置回调函数
		en.setCallback(this);
		// 创建子类,也就是代理对象,然后返回
		return en.create();
	}

	@Override
	public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
		System.out.println("向大家问好!");
		// 执行目标对象的方法
		Object returnValue = method.invoke(target, args);
		System.out.println("跟大家说再见!");
		return returnValue;
	}

}

然后编写测试类进行测试:

/**
 * 
 * @author 马旭辉
 * @date: 2019年4月30日 下午2:33:37 
 * 测试类
 */
public class Main {

	public static void main(String[] args) {
		// 目标对象
		Star target = new Star();
		// 代理对象
		Star proxy = (Star) new ProxyFactory(target).getProxyInstance();
		// 执行代理对象的方法
		proxy.sing();
	}
}

查看控制台输出结果:
Java设计模式之代理模式的简介_第3张图片
可以看到我们的CGLIB代理模式已经写成功了,达到我们想要的效果了。
这就是Java中的三种代理模式,如有文章有误,还望大家指正。

欢迎关注微信公众号"程序员小辉"

微信图片20190813101011.jpg

你可能感兴趣的:(Java设计模式)