目录
一、代理模式简介
1、代理模式的定义
2、组成
3、优缺点
二、静态代理
1、静态代理的实例
2、静态代理的优缺点
三、动态代理
(一)jdk动态代理模式
>1)模式要点分析
>2)反射实例
>3)动态代理源码分析
>4)动态代理实例
>5)InvocationHandler分析
>6)总结下动态代理的流程:
(二)cglib的动态代理
实例1:
实例2:
最后,作为一个搬砖者,再次感谢各位大神的分享,好人一生平安!
为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
抽象角色:通过接口或抽象类声明真实角色实现的业务方法。
代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。
真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。
优点: 1、职责清晰。 2、高扩展性。 3、智能化。
缺点: 1、由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。 2、实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
使用场景:按职责来划分,通常有以下使用场景: 1、远程代理。 2、虚拟代理。 3、Copy-on-Write 代理。 4、保护(Protect or Access)代理。 5、Cache代理。 6、防火墙(Firewall)代理。 7、同步化(Synchronization)代理。 8、智能引用(Smart Reference)代理。
注意事项: 1、和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。 2、和装饰者模式的区别:装饰者模式为了增强功能,而代理模式是为了加以控制。
静态代理是由程序员创建或工具生成代理类的源码,再编译代理类。
所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
一句话,自己手写代理类就是静态代理。
--. 静态代理模式类图如下:
被代理对象RealSubject负责执行相关业务逻辑,不同的被代理对象处理不同的业务,而代理对象只需要访问这些业务方法就行,不需要处理具体业务逻辑,但是针对某一类共同问题的的处理,就可以移到代理对象里来实现对方法的增强,由于代理模式功能增强这一块在实际开发中尤为突出,所以后面的例子也会稍微针对一下。
public interface Moveable {
void move();
}
public class Car implements Moveable {
@Override
public void move() {
System.out.println("汽车行驶中....");
}
}
public class CarProxy implements Moveable{
private Moveable move;
@Override
public void move() {
if(move==null){
move = new Car();
}
System.out.println("开始记录日志:");
move.move();
System.out.println("记录日志结束!");
}
}
public static void main(String[] args) throws Exception {
Moveable m =new CarProxy();
m.move();
}
优点:
业务类只需要关注业务逻辑本身,保证了业务类的重用性。这是代理模式的共有优点。
缺点:
1)代理对象的一个接口只服务于一种类型的对象,如果要代理的类型很多,势必要为每一种类型的方法都进行代理,静态代理在程序规模稍大时就无法胜任了。
2)如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。显而易见,增加了代码维护的复杂度。
动态代理有jdk动态代理模式和cglib代理模式。JDK的动态代理只能代理实现了接口的类, 没有实现接口的类不能实现动态代理;而cglib的动态代理是通过继承实现的,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
(如果读者只想知道动态代理如何使用,可直接阅读 >4)动态代理实例)
》备注:源码分析版:https://www.jianshu.com/p/5478f170d9ee
》简单实例版:https://www.cnblogs.com/yueshutong/p/9500632.html
动态代理:在程序运行时,通过反射机制动态地创建一个代理类。
1,动态的体现:程序开始执行时是没有代理类的,在程序运行时,java利用反射机制动态生成代理类的实例
2,jdk技术支持:java在java.lang.reflect包里提供两个类/接口帮助我们创建和使用代理类实例,一个是Proxy类,一个是InvocationHandler接口。
3,两种代理类的模式区别:静态模式要求为程序中所有需要被访问的目标创建代理类,如果有10种代理目标,我们就得创建10个代理类,让代理类直接访问目标对象,下面是其简易关系图。
动态代理则是在程序调用某个目标类时才通过反射创建代理类的实例,创建过程并不是在开发层面可见的,又加之常常需要在代理类中进行一些额外操作来加强目标类的功能,如果代理类不可见我们就不能达到这个目的,所以这时在代理类和被代理类之间就需要一个第三者,帮助我们完成功能增强,它就是InvocationHandler。下面给出动态代理简易关系图。
对动态代理有初步了解后,我们下面通过源码和实例来分析创建和使用动态代理类的过程。
学习动态代理前,我们来先看一个反射的例子,对动态代理的过程理解很有帮助
public class Student {
public String name;
public int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
}
在MainActivity里使用反射获取Student对象的实例,并调用getName方法
public void reflect() throws Exception {
//通过反射获取类
Class studentClass = Student.class;
//获取构造时,需要传入的该构造的参数类型
Class[] classes = new Class[]{String.class, int.class};
//获取类的参数为String和int类型的public构造函数
Constructor constructor = studentClass.getConstructor(classes);
//通过构造参数创建一个Student类的实例
Student zhangsan = (Student) constructor.newInstance("张三", 18);
//获取Student对象里名字为“getName”的方法
Method method = studentClass.getMethod("getName", new Class[]{});
//执行方法 实例zhangsan的"getName"方法
String name = (String) method.invoke(zhangsan, new Object[]{});
//打印我们创建实例对象时传入的name,看是否能通过"getName"方法获取到
Log.e("miss08", "name = " + name);
}
04-27 10:37:15.796 31008-31008/com.example.pver.proxydesign E/miss08: name = 张三
public class Proxy implements Serializable {
//被代理类构造函数的参数类型,即InvocationHandler
private static final Class>[] constructorParams =
{ InvocationHandler.class };
//代理对象的创建方法,通过Proxy.newProxyInstance(...)生成代理对象
public static Object newProxyInstance(ClassLoader loader,
Class>[] interfaces,
InvocationHandler h) {
...省去一些方法,只显示核心代码
final Class>[] intfs = interfaces.clone();
//1,通过类加载器和接口创建代理类
Class> cl = getProxyClass0(loader, intfs);
//2,通过反射获取代理类中public类型的构造,且参数类型为InvocationHandler
final Constructor> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
//3,通过构造,创建代理类的实例对象,并返回
return cons.newInstance(new Object[]{h});
}
//通过类加载器和接口,动态创建代理类。
public static Class> getProxyClass0(ClassLoader loader, Class... interfaces)
{...}
}
通过代理类的创建方法newProxyInstance,我们可以看到一个代理对象的创建需要三个步骤和三个核心参数,它们分别为:
三个步骤为:
1,通过类加载器和接口创建代理类,方法为getProxyClass0(loader,interfaces)
2,通过反射获取代理类的构造,且构参为InvocationHandler类型。
3,通过构造函数生成代理对象的实例。三个参数为:
1,类加载器:ClassLoader 将代理类的字节码转换成class类的对象
2,被代理类的接口列表:Class>[] interfaces,通过接口动态生成代理类字节码
3,InvocationHandler接口:被代理类的直接调用者
代理类实例创建的步骤最重要的是第一步代理类的创建,后面两步是最基本的反射,我们就结合后面实例来分析,下面我们先来分析第一步代理类的创建,在分析的过程中顺带说说三个参数。
1、代理类Proxy的创建
public abstract class ClassLoader {
//类加载器作用就是加载字节码,并转换为java.lang.Class类的一个实例,即一个java类
protected final Class> defineClass(byte[] b, int off, int len) ...{
throw new RuntimeException("Stub!");
}
protected final Class> defineClass(String name, byte[] b, int off, int len){
throw new RuntimeException("Stub!");
}
}
/**
* Generate a proxy class. Must call the checkProxyAccess method
* to perform permission checks before calling this.
*/
private static Class> getProxyClass0(ClassLoader loader,
Class>... interfaces) {
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
//从缓存获取代理类,如果缓存没有,就通过ProxyClassFactory去创建
return proxyClassCache.get(loader, interfaces);
}
getProxyClass0方法里首先做接口长度验证,接着会用类加载器和接口从proxyClassCache缓存里去取代理类,如果缓存里有代理类的class对象,就直接返回,不用再做后续操作,提高了创建效率。如果缓存里没有就用ProxyClassFactory去创建代理类。我们先看看proxyClassCache,它是一个WeakCache缓存类。
/**
* a cache of proxy classes
* KeyFactory:key和parameters映射sub-key
* ProxyClassFactory:key和parameters映射proxy
*/
private static final WeakCache[], Class>>
proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
WeakCache ---> 分别代表ClassLoader,interfaces 和 proxy
WeakCache对象在创建时,会传入两个映射接口,一个是key和parameters映射sub-key的KeyFactory,另一个是以key和parameters映射proxy的valueFactory,下面就看看这两个映射在proxyClassCache的get方法里的体现。
public V get(K key, P parameter) {
//使用WeakReference引用key,当key为null时能立马回收
Object cacheKey = CacheKey.valueOf(key, refQueue);
// map是一个ConcurrentMap,用cacheKey为key来缓存代理类,cacheKey支持为null
ConcurrentMap
上面分析proxyClassCache.get(...)方法,知道了如何从缓存里取到代理类,但是当缓存里没有值时,会通过ProxyClassFactory创建代理类,我们找到ProxyClassFactory,它是一个静态内部类,分析请看里面的注释:
//只展示核心代码
private static final class ProxyClassFactory
implements BiFunction[], Class>> {
//所有创建的代理类的名称的前缀必须是"$Proxy"
private static final String proxyClassNamePrefix = "$Proxy"
//完成一些验证工作,如类加载器可用于加载接口类型等
@override
public Class> apply(ClassLoader loader, Class>[] interfaces) {
Map, Boolean> interfaceSet
= new IdentityHashMap<>(interfaces.length);
for (Class> intf : interfaces) {
/*
* Verify that the class loader resolves the name of this
* interface to the same Class object.
*/
Class> interfaceClass = null;
try {
interfaceClass = Class.forName(intf.getName(), false, loader);
} catch (ClassNotFoundException e) {
}
if (interfaceClass != intf) {
throw new IllegalArgumentException(
intf + " is not visible from class loader");
}
/*
* Verify that the Class object actually represents an
* interface.
*/
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException(
interfaceClass.getName() + " is not an interface");
}
/*
* Verify that this interface is not a duplicate.
*/
if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
throw new IllegalArgumentException(
"repeated interface: " + interfaceClass.getName());
}
}
//通过代理类名称和接口动态生成生成代理类字节码
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
//返回代理类,这里没找到本地实现
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}
}
从上面方法里我们找到了生成代理类字节码和生成代理类的方法:
1,通过代理类名字和接口生成代理类字节码 : ProxyGenerator.generateProxyClass(...)
2,使用类加载器和代理类字节码等,生成代理类:defineClass0(...)
我们先看看生成Prox字节码的方法,这个类ProxyGenerator存在sun包里,这个包不是开源包,在文章结尾我会上传这个反编译的包,下面我们就来看看这个方法:
public static byte[] generateProxyClass(final String name,Class[] interfaces) {
//通过代理类类名和接口生成字节码。
ProxyGenerator gen = new ProxyGenerator(name, interfaces);
final byte[] classFile = gen.generateClassFile();
//saveGeneratedFiles标记位,判断是否把这个字节码文件写入到某个class文件
if (saveGeneratedFiles) {
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction() {
public Object run() {
try {
FileOutputStream file =
new FileOutputStream(dotToSlash(name) + ".class");
file.write(classFile);
file.close();
return null;
} catch (IOException e) {
throw new InternalError(
"I/O exception saving generated file: " + e);
}
}
});
}
}
generateProxyClass方法生成字节码并且会把字节码文件存储,这里我们可以在本地创建一个代理类文件用来存储字节码,反编译查看字节码里的信息。defineClass0生成代理类的方法这里就不分析了。接下来看看第三个参数InvocationHandler。
public interface InvocationHandler {
/**
* proxy:代理类
* method:调用被代理对象的某个方法的Method对象
* args:调用被代理对象的方法需要的参数
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
InvocationHandler里只用一个invoke方法,从上面分析我们知道,通过反射获取代理类的构造时会传入构造参数InvocationHandler,InvocationHandler作为实际的被代理类的控制者,不管用户想要调用哪个被代理类的哪个方法,我们只需要通过代理类实例调用InvocationHandler的invoke方法,传入方法名和参数即可,要分析InvocationHandler在动态代理里起的作用,就需要先拿到代理类的字节码,下面就通过具体案例来说明。
JDK动态代理实现步骤:
- 创建被代理的类以及实现的接口;
- 创建一个实现接口InvocationHandler的类,它必须实现invoke方法;
- 调用Proxy的newProxyInstance静态方法,创建一个代理类。
- 通过代理对象调用目标方法。
public interface Subject {
String speak();
}
public class TargetSubject implements Subject {
@Override
public String speak() {
System.out.println("speak方法执行");
return "结束";
}
}
public class DynamicInvocationHandler implements InvocationHandler {
private Subject subject;
public DynamicInvocationHandler(Subject subject) {
this.subject = subject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
//增强处理
System.out.println("增强处理--前");
//通过反射获取到接口的method,执行subject目标对象中带参数args的method方法
String response = (String) method.invoke(subject, args);
//增强处理
System.out.println("增强处理--后");
return response;
}
}
Subject targetSubject = new TargetSubject();
InvocationHandler handler = new DynamicInvocationHandler(targetSubject);
//创建代理类的实例
Subject proxySubject = (Subject) Proxy.newProxyInstance(targetSubject.getClass()
.getClassLoader()
, targetSubject.getClass().getInterfaces()
, handler);
//调用被代理类的方法
proxySubject.speak();
接下来看看代理类的字节码文件里的信息,看看代理类是如何通过InvocationHandler来控制目标对象的。
public final class $Proxy0
extends Proxy
implements Subject {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
// 反射时获取这个构参的构造函数来创建代理类的实例对象
public $Proxy0(InvocationHandler invocationHandler) {
super(invocationHandler);
}
//代理类实现方法,会执行InvocationHandler的invoke方法
public final String getResponse(String string) {
try {
return (String)this.h.invoke((Object)this, m3, new Object[]{string});
}
catch (Error | RuntimeException v0) {
throw v0;
}
catch (Throwable var2_2) {
throw new UndeclaredThrowableException(var2_2);
}
}
//静态代码块,反射得到我通过代理类调用的方法
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName
("java.lang.Object"));
m3 = Class.forName("com.example.pver.proxydesign.dynamicproxy.Subject")
.getMethod("getResponse", Class.forName("java.lang.String"));
m2 = Class.forName("java.lang.Object")
.getMethod("toString", new Class[0]);
m0 = Class.forName("java.lang.Object")
.getMethod("hashCode", new Class[0]);
return;
}
catch (NoSuchMethodException var1) {
throw new NoSuchMethodError(var1.getMessage());
}
catch (ClassNotFoundException var1_1) {
throw new NoClassDefFoundError(var1_1.getMessage());
}
}
字节码文件分析:
1,通过Proxy.newProxyInstance生成的代理类,执行接口里的方法,实际上是调用了InvocationHandler 的
invoke方法(参考上面直接码里的getResponse方法),参数分别为当前代理类、用户执行的方法和我们调用该方法时传入的参数。
2,InvocationHandler需要用户自定义,在invoke方法里执行method.invoke(subject, args),用来执行目标对象里参数为args的方法method,故InvocationHandler才是真正的被代理类的直接访问者。
3,代理类继承了Proxy,由于对象的单继承性质,所以代理类不能再继承其他类,所以JDK的动态代理只支持接口的方式代理,并不能支持具体实现类的代理。
引用cglib的依赖包
cglib
cglib-nodep
2.2
为了方便思考它的原理,我把执行步骤按顺序写下
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
//设置父类,被代理类(这里是Car.class)
enhancer.setSuperclass(Car.class);
//设置回调函数
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
//增强处理...
Object o= proxy.invokeSuper(obj, args);//代理类调用父类的方法
//增强处理...
return o;
}
});
//创建代理类并使用回调(用父类Car去引用)
Car car = (Car) enhancer.create();
//执行目标方法
System.out.println(car.move());
}
方法拦截器
实现MethodInterceptor接口的intercept方法后,所有生成的代理方法都调用这个方法。
intercept方法的具体参数有
该方法的返回值就是目标方法的返回值。
特点
静态代理和动态代理模式都是要求目标对象是实现一个接口的目标对象,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候就可以使用以目标对象子类的方式类实现代理,这种方法就叫做:Cglib代理
Cglib代理,也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展.
- JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口,如果想代理没有实现接口的类,就可以使用Cglib实现.
- Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口.它广泛的被许多AOP的框架使用,例如Spring AOP和synaop,为他们提供方法的interception(拦截)
- Cglib包的底层是通过使用一个小而块的字节码处理框架ASM来转换字节码并生成新的类.不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉.
Cglib子类代理实现方法:
1.需要引入cglib的jar文件,但是Spring的核心包中已经包括了Cglib功能,所以直接引入
pring-core-3.2.5.jar
即可.
2.引入功能包后,就可以在内存中动态构建子类
3.代理的类不能为final,否则报错
4.目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法.
代码示例:
目标对象类:UserDao.java
/**
* 目标对象,没有实现任何接口
*/
public class UserDao {
public void save() {
System.out.println("----已经保存数据!----");
}
}
Cglib代理工厂:ProxyFactory.java
/**
* Cglib子类代理工厂
* 对UserDao在内存中动态构建一个子类对象
*/
public class ProxyFactory implements MethodInterceptor{
//维护目标对象
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
//给目标对象创建一个代理对象
public Object getProxyInstance(){
//1.工具类
Enhancer en = new Enhancer();
//2.设置父类
en.setSuperclass(target.getClass());
//3.设置回调函数
en.setCallback(this);
//4.创建子类(代理对象)
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;
}
}
测试类:
/**
* 测试类
*/
public class App {
@Test
public void test(){
//目标对象
UserDao target = new UserDao();
//代理对象
UserDao proxy = (UserDao)new ProxyFactory(target).getProxyInstance();
//执行代理对象的方法
proxy.save();
}
}
在Spring的AOP编程中:
如果加入容器的目标对象有实现接口,用JDK代理
如果目标对象没有实现接口,用Cglib代理
参考文档1:https://www.jianshu.com/p/5478f170d9ee(重要参考文献)
参考文档2:https://www.cnblogs.com/yueshutong/p/9500632.html(次重点)
参考文档3:https://www.cnblogs.com/cenyu/p/6289209.html(cglib参考文献)