胡豆秆

个人博客

欢迎来到我的个人博客~


Java代理模式


代理模式

​ 代理(Proxy)是一种设计模式,提供了间接对目标对象进行访问的方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的功能上,增加额外的功能补充,即扩展目标对象的功能.这就符合了设计模式的开闭原则,在不修改原有代码的情况下,对功能进行拓展。

​ 代理模式分为两种:静态代理和动态代理。动态代理又分为:JDK代理和CGLIB代理。

静态代理

​ 在使用静态代理时,被代理对象与代理对象需要一起实现相同的接口或者是继承相同父类,因此要定义一个接口或抽象类.

​ 原理其实比较简单:代理类和被代理类都实现了相同的接口,在代理类中需有一个变量用于接收被代理类的实例;在代理类的构造方法中将传入的被代理类的实例赋值给自己的变量;在调用代理类实现的方法时,调用该变量(即传入的被代理类)的方法;这样就可以在代理类的这个方法中做更多的功能拓展。这样可能还是比较绕,难以理解。那么就看一下例子,结合代码理解。这里我们使用实现相同接口作为案例:

public interface Communicate {
    //代理类和被代理类都需实现的方法
	void talk();
    void greet();
}

被代理类,需实现该接口:

public class Boy implements Communicate {
	@Override
	public void talk() {
        //被代理类的该方法只是简单的打印输出
		System.out.println("做我女朋友吧!");
	}
    @Override
	public void greet() {
		System.out.println("Hello~");
	}
}
public class Girl implements Communicate {
	@Override
	public void talk() {
        //被代理类的该方法只是简单的打印输出
		System.out.println("你很好,但我们不合适!");
	}
    @Override
	public void greet() {
		// TODO Auto-generated method stub
		System.out.println("Hi!");
	}
}

代理类,需实现该接口:

public class StaticProxy implements Communicate{
    //该变量用来接收传入的被代理类实例
	private Communicate person;
	public StaticProxy(Communicate person) {
        //在构造方法中,将传入的被代理实例传给自己类中的变量
		this.person = person;
	}
    //代理类实现的方法中,可以做更多的功能拓展。
	@Override
	public void talk() {
		if("Boy".equals(person.getClass().getSimpleName())) {
			System.out.println("这个男生说:");
            //代理类调用被代理类的方法,这样被代理类的方法会得到执行
			person.talk();
			System.out.println("对!他是这么说的。");
		}
		if("Girl".equals(person.getClass().getSimpleName())) {
			System.out.println("这个女生说:");
            //代理类调用被代理类的方法,这样被代理类的方法会得到执行
			person.talk();
			System.out.println("她说她只想和你做朋友。");
		}
	}
    @Override
	public void greet() {
		if("Boy".equals(person.getClass().getSimpleName())) {
			System.out.println("这个男生打招呼!");
			person.talk();
		}
		if("Girl".equals(person.getClass().getSimpleName())) {
			System.out.println("这个女生打招呼!");
			person.talk();
		}
	}
}

测试:

public class ProxyTest {
	public static void main(String[] args) {
        // 创建代理类对象,并传入被代理类的实例
		StaticProxy proxyOfBoy = new StaticProxy(new Boy());
		// 调用代理类的talk方法,其内部会调用Boy的talk方法
		proxyOfBoy.talk();
		proxyOfBoy.greet();
		System.out.println("============================");
		// 同理
		StaticProxy proxyOfGirl = new StaticProxy(new Girl());
		proxyOfGirl.talk();
		proxyOfGirl.greet();
	}
}

​ 从运行结果可以看到,除了被代理类(Boy和Girl)的方法打印,还打印了别的。那么在实际的项目中,就可以做更多实质的功能拓展。如打印日志、权限认证等。

动态代理

​ 动态代理的主要特点就是能够在程序运行时JVM才为被代理对象生成代理对象。动态代理有两种:JDK和CGLIB,其内部都是使用了反射。

JDK

​ JDK动态代理的实现是在运行时,根据一组接口定义,使用Proxy、InvocationHandler等工具类去生成一个代理类和代理类实例。

​ JDK动态代理的类关系模型和静态代理看起来差不多。也是需要一个或一组接口来定义行为规范。需要一个代理类来实现接口。区别是没有真实类,因为动态代理就是要解决在不知道真实类的情况下依然能够使用代理模式的问题。

​ JDK动态代理需要实现InvocationHandler接口,该接口是JDK自带的。和静态代理有点类似,该代理类内部也有一个用于接收被代理类实例的变量。此外还需要自定义一个用于获取被代理类实例的方法(getJDKProxy),传入被代理类实例。InvocationHandler接口有一个invoke方法,该方法有三个参数:proxy表示在其上调用方法的代理实例;method代表对应于在代理实例上调用的接口方法的 Method 实例(Method 对象的声明类将是在其中声明方法的接口,该接口可以是代理类赖以继承方法的代理接口的超接口);args表示包含传入代理实例上方法调用的参数值的对象数组,如果接口方法不使用参数,则为 null。基本类型的参数被包装在适当基本包装器类(如 java.lang.Integer 或 java.lang.Boolean)的实例中。下面来看例子:

public class JDKProxy implements InvocationHandler{
	Object target;
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("==========================");
        //该method就是被代理的方法
		if("talk".equals(method.getName())) {
			System.out.println("他们在交流!");
            //java反射调用方法,需传入目标类和参数
			method.invoke(target, args);
		}
		if("greet".equals(method.getName())) {
			System.out.println("他们在打招呼!");
            //可以在调用方法前面接收返回值,并返回出去
           	//Object result = method.invoke(target, args);
			method.invoke(target, args);
		}
        //return result;
        //因为被代理的两个方法不需要返回值,所以直接返回null
		return null;
	}
	public Object getJDKProxy(Object targetObject) {
		this.target = targetObject;
        //获取被代理类的代理对象
		return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(), this);
	}
}

测试:

public static void main(String[] args) {
    JDKProxy jdkProxyOfBoy = new JDKProxy();
    JDKProxy jdkProxyOfGirl = new JDKProxy();
    Communicate boy = (Communicate) jdkProxyOfBoy.getJDKProxy(new Boy());
    Communicate girl = (Communicate) jdkProxyOfGirl.getJDKProxy(new Girl());
    boy.talk();
    girl.talk();
    boy.greet();
    girl.greet();
}

运行结果:

​ 看起来其实和静态代理差不了多少,从代码也可以看出:静态代理需明确给出代理类,并实现接口中的方法;即使什么都不做,也必须在代理类实现的方法中调用一次被代理类的方法,从而实现代理。而JDK代理的代理类是由java反射生成的,并不需要我们手动去写。我们只需在invoke方法中,通过传入的method判断方法,并执行所需的功能拓展。不用每个方法都去实现一遍,大大减少了代码量。

​ 一般被代理类有实现接口,都推荐使用JDK动态代理。但JDK动态代理只能对实现了接口的类生成代理,而不能针对类。那如果需要被代理的类没有实现接口呢?如SpringMVC中的Controller类,某些url需要指定的权限才能访问,但Controller类又没有实现接口。这时就需要使用CGLIB动态代理。

CGLIB

​ CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法。

​ CGLIB是一个强大的高性能的代码生成包,它可以在运行期扩展Java类与实现Java接口。它广泛的被许多AOP的框架使用,例如Spring AOP和dynaop,为他们提供方法的interception(拦截)。CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。

​ 使用CGLIB动态代理需要加入两个jar包:asm-7.2.jarcglib-3.3.0.jar 。将两个jar包放到src目录导入,右键-Build Path-Add to Build Path 。

public class CGLIBProxy implements MethodInterceptor{
	private Object target;
	public CGLIBProxy(Object target) {
		this.target = target;
	}
	public Object getCGLIBProxy() {
        //实例化一个增强器,也就是cglib中的一个class generator
		Enhancer enhancer = new Enhancer();
        //设置目标类 
		enhancer.setSuperclass(target.getClass());
        //设置拦截对象(该对象需实现MethodInterceptor,并调用他的intercept方法)
		enhancer.setCallback(this);
        //返回代理对象
		return enhancer.create();
	}
	@Override
	public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodproxy) throws Throwable {
		System.out.println("==========================");
		if("talk".equals(method.getName())) {
			System.out.println("他们在交流!");
			method.invoke(target, args);
		}
		if("greet".equals(method.getName())) {
			System.out.println("他们在打招呼!");
			method.invoke(target, args);
		}
		return null;
	}
}

测试:

public static void main(String[] args) {
    CGLIBProxy cglibProxyOfBoy = new CGLIBProxy(new Boy());
    CGLIBProxy cglibProxyOfGirl = new CGLIBProxy(new Girl());
    Boy boy = (Boy) cglibProxyOfBoy.getCGLIBProxy();
    Girl girl = (Girl) cglibProxyOfGirl.getCGLIBProxy();
    boy.greet();
    girl.greet();
    boy.talk();
    girl.talk();
}

运行结果:

​ CGLIB动态代理的intercept方法相比于JDK动态代理的invoke方法,多了一个MethodProxy参数。该参数表示与Method的区别在于:MethodProxy除了调用代理方法invoke之外,还可以调用invokeSupper方法。invoke方法调用的对象没有增强过,invokeSuper方法调用的对象已经是增强了的,所以会再走一遍 MyMethodInterceptor的 interceptor方法,如果是个拦截器链条,就会重新在走一次拦截器链。如果不做任何必要处理(如逻辑判断)就调用invokeSupper的话,会造成无限循环调用,最终导致栈深操作过大范围,出现内存溢出。

总结

静态代理:

优点:可以做到在符合开闭原则的情况下对目标对象进行功能扩展。

缺点:我们得为每一个服务都得创建代理类,工作量太大,不易管理。同时接口一旦发生改变,代理类也得相应修改。

动态代理:分为JDK和CGLIB。

JDK:JDK动态代理只能对实现了接口的类生成代理,而不能针对类。

CGLIB:CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法。因为是继承,所以该类或方法最好不要声明成final。

​ 从 jdk6 到 jdk7、jdk8 ,动态代理的性能得到了显著的提升,而 cglib 的表现并未跟上,甚至可能会略微下降。

打赏一个呗

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦