设计模式--代理模式


    代理模式(ProxyPattern)的定义也非常简单,是指为其他对象提供一种代理,以控制对这个对象的访问。代理对象在客服端和目标对象之间起到中介作用,代理模式属于结构型设计模式。使用代理模式主要有两个目的:一保护目标对象,二增强目标对象。

    代理模式分为静态代理和动态代理,动态代理有2种实现方式,JDK动态代理和Cglib动态代理。


1、静态代理

public interface ISubject {
	public void sayHello() ;
}
public class RealSubject implements ISubject{

	@Override
	public void sayHello() {
		System.out.println("RealSubject.sayHello ... ");
	}

}
public class ProxySubject implements ISubject {

	private ISubject subject;

	public ProxySubject(ISubject subject) {
		this.subject = subject;
	}

	@Override
	public void sayHello() {
		System.out.println("before realSubject sayHello ...");
		subject.sayHello();
		System.out.println("after realSubject sayHello ...");
	}

}
public class ProxyDemo {
	public static void main(String[] args) {
		ProxySubject proxy = new ProxySubject(new RealSubject()) ;
		proxy.sayHello();
	}
}

打印:

before realSubject sayHello ...

RealSubject.sayHello ... 

after realSubject sayHello ...



2、JDK动态代理

动态代理和静态对比基本思路是一致的,只不过动态代理功能更加强大,随着业务的扩展适应性更强。


JDK Proxy 生成对象的步骤如下:

    1、拿到被代理对象的引用,并且获取到它的所有的接口,反射获取。

    2、JDK Proxy 类重新生成一个新的类、同时新的类要实现被代理类所有实现的所有的接口。

    3、动态生成 Java 代码,把新加的业务逻辑方法由一定的逻辑代码去调用(在代码中体现)。重写接口的方法,在对应方法中利用反射调用目标方法

    4、编译新生成的 Java 代码.class。

    5、再重新加载到 JVM 中运行。


    以上这个过程就叫字节码重组。JDK 中有一个规范,在 ClassPath 下只要是$开头的 class文件一般都是自动生成的。

    从以上生成对象的步骤可以知道,被代理类需要继承至少一个接口 , 生成的类继承会这些接口。被代理的方法必须在接口中定义好。

public interface ISubject {
	public void sayHello() ;
}
public class RealSubject implements ISubject{
	@Override
	public void sayHello() {
		System.out.println("RealSubject.sayHello ... ");
	}
}
public class DynamicProxy implements InvocationHandler {

	private Object target;

	public Object getInstance(Object target) {
		this.target = target;
		Class<? extends Object> clazz = target.getClass();
		return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this) ;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

		System.out.println("before doing something ...");
		Object invoke = method.invoke(target, args);
		System.out.println("after doing something ...");
		return invoke;
	}
}
public class ProxyDemo {
	public static void main(String[] args) {

		ISubject subject = (ISubject) new DynamicProxy().getInstance(new RealSubject());
		subject.sayHello();

//通过反编译工具可以查看源代码
//byte [] bytes = ProxyGenerator.generateProxyClass("$Proxy0",new Class[]{ISubject.class});
//FileOutputStream os = new FileOutputStream("E://$Proxy0.class");
//os.write(bytes);
//os.close();
	}
}

    运行之后,能在 E://盘下找到一个$Proxy0.class 文件。使用 Jad 反编译,得到$Proxy0.jad 文件。

    发现$Proxy0 继承了Proxy类,同时还实现了我们的 ISubject接口,而且重写了sayHello()等方法。而且在静态块中用反射查找到了目标对象的所有方法,而且保存了所有方法的引用,在重写的方法用反射调用目标对象的方法。


打印:

before doing something ...

RealSubject.sayHello ... 

after doing something ...



3、Cglib动态代理

    CGLib 代理的目标对象不需要实现任何接口,它是通过动态继承目标对象实现的动态代理。Cglib代理会生成一个类,来继承被代理的类,然后来完成相关操作。所以被代理的类不能被定义为final  class 。

<dependency>
	<groupId>cglib</groupId>
	<artifactId>cglib</artifactId>
	<version>3.3.0</version>
</dependency>


Cglib会依赖asm包
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>7.1</version>
public class RealSubject {
	public void sayHello() {
		System.out.println("RealSubject.sayHello ... ");
	}
}
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 CglibProxy implements MethodInterceptor {

	public Object getInstance(Class<?> clazz) {
		
		Enhancer enhancer = new Enhancer();
		enhancer.setSuperclass(clazz);
		enhancer.setCallback(this);
		return enhancer.create();
	}

	@Override
	public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
		System.out.println("before doing something ...");
		Object invoke = proxy.invokeSuper(obj, args);
		System.out.println("after doing something ...");
		return invoke;
	}

}
public class CglibTest {

	public static void main(String[] args) {
      //利用 cglib 的代理类可以将内存中的 class 文件写入本地磁盘
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,
"E://cglib_proxy_class/");

		RealSubject subject = (RealSubject)new CglibProxy().getInstance(RealSubject.class);
		subject.sayHello();
	}

}

    通过代理类的源码可以看到,代理类会获得所有在 父 类 继 承 来 的 方 法 , 并 且 会 有 MethodProxy 与 之 对 应 , 比 如 MethodCGLIB$findLove$0$Method、MethodProxy CGLIB$findLove$0$Proxy;

这些方法在代理类的 sayHello()中都有调用。


注意:proxy.invokeSuper(obj, args); 

    必须是调用invokeSuper ,  因为生成的类继承了代理类,也会重写目标方法,如果是使用invoke(obj,args) 方法,则调用的是自己的方法,不是父类的方法,然后就进入调用循环了。

at com.meihaocloud.proxy.cglibproxy.RealSubject$$FastClassByCGLIB$$62ce64d8.invoke(<generated>)
at net.sf.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)

    CGLib动态代理执行代理方法效率之所以比JDK的高是因为Cglib采用了FastClass机制,它的原理简单来说就是:为代理类和被代理类各生成一个Class,这个Class会为代理类或被代理类的方法分配一个index(int类型)。这个index当做一个入参,FastClass就可以直接定位要调用的方法直接进行调用,这样省去了反射调用,所以调用效率比JDK动态代理通过反射调用高。



4、CGLib和JDK动态代理对比

    1.JDK动态代理是实现了被代理对象的接口,CGLib是继承了被代理对象。

    2.JDK和CGLib都是在运行期生成字节码,JDK是直接写Class字节码,CGLib使用ASM框架写Class字节码,Cglib代理实现更复杂,生成代理类比JDK效率低。

    3.JDK调用代理方法,是通过反射机制调用,CGLib是通过FastClass机制直接调用方法,CGLib执行效率更高。



5、代理模式与 Spring

先看 ProxyFactoryBean 核心的方法就是 getObject()方法。

Spring代理模式


    在getObject()方法中,主要调用getSingletonInstance()和newPrototypeInstance();在Spring的配置中,如果不做任何设置,那么Spring代理生成的Bean都是单例对象。

    如果修改scope则每次创建一个新的原型对象。newPrototypeInstance()里面的逻辑比较复杂,我们后面的课程再做深入研究,这里我们先做简单的了解。

Spring利用动态代理实现AOP有两个非常重要的类,一个是JdkDynamicAopProxy类和CglibAopProxy类


Spring中的代理选择原则

    1、当Bean有实现接口时,Spring就会用JDK的动态代理

    2、当Bean没有实现接口时,Spring选择CGLib。

    3、Spring可以通过配置强制使用CGLib,只需在Spring的配置文件中加入如下代码:

        <aop:aspectj-autoproxyproxy-target-class="true"/>




设计模式 代理模式

2020.11.29 22:19

https://www.meihaocloud.com.com/1011.html , 欢迎转载,请在文章页标出原文连接 !


Copyright © 2020 千夕网 联系站长

粤公网安备 44030302001408号 粤ICP备19099833号-1