设计模式之代理模式 (二)

这个设计模式也是算比较经典常用的了,在Spring的AOP实现中就用到了JAVA自带的动态代理与CGlib的动态代理。在我初学的时候,这个设计模式十分晦涩难懂,我也是看了很多次才大概能理解到这个设计模式的精髓。

画个图吧,我理解的代理模式是这样的,首先创建一个代理类Proxy,这个类拥有被代理类target的所有行为基础,然后改进被代理类中的某个方法(添加一些行为)。在调用target时实际上是调用了Proxy这个类,由Proxy这个类代替tartget执行各种方法,这个过程称为代理。

举个例子:

这个是动物的接口,每个动物都需要制作食物,然后开吃。

package test;

public interface Animal {

	void makeFood();

	void eat();

}

这个是人类,继承动物接口,实现每个方法。

package test;

public class Person implements Animal {

	public void makeFood() {
		System.out.println("自己在做饭");
	}

	public void eat() {
		System.out.println("吃!");
	}

}

这个是代理类,可以看作是饭馆,他代理人类makeFood的过程,但是吃还是使用的人类的吃方法。

package test;

public class Proxy implements Animal {

	public Animal animal;

	public Proxy(Animal animal) {
		this.animal = animal;
	}

	@Override
	public void makeFood() {
		System.out.println("代理类帮你做饭!");
	}

	@Override
	public void eat() {
		animal.eat();
	}

}

让我们看看客户端实现的样子

package test;

public class testA {
	public static void main(String[] args) {
		Animal A = new Person();
		System.out.println("无代理之前正常人吃饭的过程:");
		A.makeFood();
		A.eat();
		System.out.println("代理类介入之后正常人吃饭的过程:");
		Animal B = new Proxy(A);
		B.makeFood();
		B.eat();
	}
}

控制台输出:

可以看到,有代理类的介入之后,吃饭的过程makeFood由代理类帮你做好,具体怎么吃你自己可以决定。

从静态代理的使用上来看,一般是有这么几个步骤

1. 代理类一般要持有被代理类的对象的引用

2. 对于我们不想做的方法,可以交给代理类去帮你实现 (因为持有被代理类的引用,所以也可以在方法前后增加动作)

3. 被代理类可以自己处理自己的方法

这种代理是死的,在你编译之后,这种代理就已经创建好了,不可动态改变。静态代理对于这种需要代理的对象很固定,方法比较少的,可以使用,而且效果会比动态代理好,因为动态代理是在运行期间动态生成代理类,花费的时间会比较久一点。

下面开始介绍JDK自带的动态代理,代理类需要实现一个InvocationHandler接口,并且调用Proxy类的静态方法生成一个动态代理类。

package test;

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

public class DynamicProxy implements InvocationHandler {

	Object object;

	public DynamicProxy(Object animal) {
		this.object = animal;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		if(method.getName().equals("makeFood")) {
			System.out.println("动态代理类帮你做饭!");
			return null;
		} else {
			return method.invoke(object, args);
		}
	}

	public Object getAnimalProxy() {
		return Proxy.newProxyInstance(this.getClass().getClassLoader(), object.getClass().getInterfaces(), this);
	}
}

在动态代理类,我们只需要实现invoke方法,并在里面写对应的逻辑,方便又灵活,在调用的时候,实例化一个我们写的动态代理类,并调用getAnimalProxy的方法返回一个动态的代理类即可。如下:

package test;

public class testA {
	public static void main(String[] args) {
		DynamicProxy proxy = new DynamicProxy(new Person());
		Animal A = (Animal) proxy.getAnimalProxy();
		A.makeFood();
		A.eat();
	}
}

控制台打印

动态代理的原理是将被代理的每个方法都交给invoke这个方法里处理,这个代理类可以代理任何类,而不再是具体的固定类了。

但是这种JDK自带动态代理有个缺点,就是被代理的类必须实现了某个接口,或者本身就是接口。就像上面的Animal。

在看了源码后就知道为什么了,因为动态生成的代理类会实现你传入接口的所有方法。下面是我对源码的解读,由于源码量大,有些地方还是没有搞的太明白,所以我只摘录一些主要生成动态代理的过程。(需要看源码的可以自己查看,我只截取一部分)

public static Object newProxyInstance(ClassLoader loader,
                      Class<?>[] interfaces,
                      InvocationHandler h)
    throws IllegalArgumentException
    {
    if (h == null) {
        throw new NullPointerException();
    }

    /*
     * Look up or generate the designated proxy class.
     */
    Class cl = getProxyClass(loader, interfaces); //这里获取代理类的class信息

   

重点就在与getProxyClass这个方法,进去看看。

public static Class<?> getProxyClass(ClassLoader loader,
            Class<?>... interfaces) throws IllegalArgumentException {
            {
                /*
                 * Choose a name for the proxy class to generate.
                 */
                long num;
                synchronized (nextUniqueNumberLock) {
                    num = nextUniqueNumber++;
                }
                //生成一个随机代理类名
                String proxyName = proxyPkg + proxyClassNamePrefix + num;
                /*
                 * Verify that the class loader hasn't already defined a class
                 * with the chosen name.
                 */
                
                //这个方法是动态代理的重点,生成代理类的class文件,通过动态生成class文件来产生的代理类
                /*
                 * Generate the specified proxy class.
                 */
                byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                        proxyName, interfaces);
                try {
                    //得到class文件二进制流后,直接载入代理类
                    proxyClass = defineClass0(loader, proxyName,
                            proxyClassFile, 0, proxyClassFile.length);
                } catch (ClassFormatError e) {
                    /*
                     * A ClassFormatError here means that (barring bugs in the
                     * proxy class generation code) there was some other invalid
                     * aspect of the arguments supplied to the proxy class
                     * creation (such as virtual machine limitations exceeded).
                     */
                    throw new IllegalArgumentException(e.toString());
                }
            }
        return proxyClass;
    }

进入最关键的方法generateProxyClass里面看看。

public static byte[] generateProxyClass(String paramString, Class[] paramArrayOfClass) {
        //新建一个ProxyGenerator实例,传入类名和接口
        ProxyGenerator localProxyGenerator = new ProxyGenerator(paramString, paramArrayOfClass);
        //生成class文件
        final byte[] arrayOfByte = localProxyGenerator.generateClassFile();
        if (saveGeneratedFiles) {
            AccessController.doPrivileged(new PrivilegedAction() {
                public Object run() {
                    try {
                        FileOutputStream localFileOutputStream = new FileOutputStream( ProxyGenerator.dotToSlash(this.val$name) + ".class");
                        localFileOutputStream.write(arrayOfByte);
                        localFileOutputStream.close();
                        return null;
                    } catch (IOException localIOException) {
                        throw new InternalError( "I/O exception saving generated file: " + localIOException);
                    }
                }

            });
        }

        return arrayOfByte;
    }

在这里新建了一个ProxyGenerator的一个实例,并传入刚刚随机生成的动态类名与接口,然后调用它的一个方法generateClassFile生成Class文件。这里就不贴这个方法的源码了,里面的内容大致就是给这个动态生成的代理类实现所有被代理类接口的方法,到这里我们可以知道,代理类的生成是根据传入的被代理类的接口不同而生成不同的代理类,实现动态的效果,利用反射,对每一个方法的处理就是回调invoke方法,这也就解释了为什么创建动态代理类时需要传入被代理类的接口,所以在invoke方法里写具体代理逻辑即可实现动态代理的效果。

JDK自带的动态代理还是有缺点的,被代理类必须实现一个接口才可以使用,下面介绍另一个动态代理---CGlib动态代理

它可以为没有实现接口的类提供代理。这里我们把Person类改一改。

package test;

public class CGPerson {

	public void makeFood() {
		System.out.println("CG-自己在做饭");
	}

	public void eat() {
		System.out.println("CG-吃!");
	}

}

接下来写代理类,CGlib的代理类需要实现MethodInterceptor接口,(CGlib的代理需要导入jar包,我这里是用maven项目添加依赖)

<dependencies>
	<dependency>
	    <groupId>cglib</groupId>
	    <artifactId>cglib</artifactId>
	    <version>3.2.4</version>
	</dependency>  
  </dependencies>
package test;

import java.lang.reflect.Method;

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

public class CGProxy implements MethodInterceptor {

	CGPerson object;

	public CGProxy(CGPerson o) {
		this.object = o;
	}

	@Override
	public Object intercept(Object object, Method method, Object[] arg, MethodProxy proxy) throws Throwable {
		if(method.getName().equals("makeFood")) {
			System.out.println("CG代理为你做饭!");
			return null;
		} else {
			return proxy.invokeSuper(object, arg);
		}
	}

	public CGPerson getProxy() {
		Enhancer enhancer = new Enhancer();
		enhancer.setSuperclass(CGPerson.class);
		enhancer.setCallback(this);
		return (CGPerson) enhancer.create();
	}

}

类似JDK自带动态类的写法,不同的就是获取代理类的方法,CGlib获取动态代理类需要使用Enhancer,首先先设置被代理对象的父类,如果没有本身即可。然后设置回调函数(类似回调invoke方法)也就是本类实例,然后调用enhancer的create方法即可创建一个动态代理类。方便灵活!接下来看测试类。

package test;

public class testA {
	public static void main(String[] args) {

		CGProxy proxy = new CGProxy(new CGPerson());
		CGPerson A = proxy.getProxy();
		A.makeFood();
		A.eat();
	}
}

控制台输出

可以看到,被代理类CGPerson并没有实现任何一个接口,究其原理,CGLib的代理类是通过继承被代理类实现动态代理的,从生成代理方法里的一句,enhancer.setSuperclass(CGPerson.class); 就可以看出来,原理就是通过继承被代理对象实现。

在Spring的AOP中就分别使用了JDK自带动态代理和CGLib动态代理,但Spring会选择哪种代理模式,Spring这里用到了策略模式(即将写到)来决定。

以上有哪里写错的,写的不好的请大家指出。

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值