结构型-代理模式(Proxy)
1,代理模式
代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。简单的说就是,我们在访问实际对象时,是通过代理对象来访问的,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。比较合适的理解就是艺人和经纪人的关系。
代理模式又分为静态代理和动态代理。动态代理常用 JDK 代理和 CGLIB 代理。
2,静态代理
由程序员创建或特定工具自动生成源代码,也就是在编译时就已经将接口、被代理类、代理类等确定下来。在程序运行之前,代理类的.class文件就已经生成。
1. 定义一个 Person 接口确认具体行为。
//定义 Person 接口,有一个方法 giveMoney() public interface Person { void giveMoney(); }
2. 定义 Student 类,实现具体业务逻辑(被代理类)
public class Student implements Person { private String name; public Student(String name){ this.name = name; } @Override public void giveMoney() { System.out.println(name + "交学费!"); } }
3. 定义代理类 MonitorProxy ,持有一个 Student 类对象,外界可通过这个类来访问 Student 类的方法。
public class MonitorProxy implements Person { private Student student; public MonitorProxy(Student student){ this.student = student; } @Override public void giveMoney() { System.out.println("我是班长,大家学费交给我了!"); student.giveMoney(); } }
4. 通过 MonitorProxy 来代理执行 Student 操作。
public class ProxyDemoTest { public static void main(String[] args) { MonitorProxy monitor = new MonitorProxy(new Student("张三")); monitor.giveMoney(); } }
3,动态代理-JDK
动态代理:在编译期间无法确定需要代理的类。运行期间动态生成代理类。
JDK 动态代理:
在 java 的 java.lang.reflect 包下提供了一个 Proxy 类和一个 InvocationHandler 接口,通过这个类和这个接口可以生成 JDK 动态代理类和动态代理对象。
特点:
- 1)只能为接口实现类创建代理对象
- 2)创建出来的代理都是java.lang.reflect.Proxy的子类
1,JDK 动态代理 Demo
1. 定义一个接口规范。
public interface Person { void dance(); void sing(); }
2. 实现接口规范的业务类。
public class Actor implements Person { private String name; public Actor(String name){ this.name = name; } @Override public void dance() { System.out.println(name + "跳个舞!"); } @Override public void sing() { System.out.println(name + "唱个歌!"); } }
3. 定义处理器,实现 InvocationHandler 接口,invoke 方法定义了代理需要做的事情
public class ProxyHandle<T> implements InvocationHandler { T target; public ProxyHandle(T target){ this.target = target; } /** * proxy 表示代理对象 * method 表示正在执行的方法 * args 表示调用方法时的参数 * */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("代理执行:" + method.getName() + "方法"); //执行代理方法 Object invoke = method.invoke(target, args); return invoke; } }
4,动态生成代理对象
public class ProxyDemoTest { public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { Person p = new Actor("张三"); ProxyHandle<Person> personProxyHandle = new ProxyHandle<>(p); Person o = (Person)Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, personProxyHandle); o.dance(); o.sing(); //获取动态代理类 //Class<?> proxyClass = Proxy.getProxyClass(null, new Class<?>[]{Person.class}); //获取到代理类,将其输入到文件种 byte[] proxy0s = ProxyGenerator.generateProxyClass("$Proxy0", Actor.class.getInterfaces()); String path = "D:\\gaox\\project\\apz\\demo-base\\src\\main\\java\\com\\demo\\base\\design_partten\\proxy\\code\\com\\demo\\base\\design_partten\\proxy\\dynamics_proxy_jdk\\code\\jdkProxy.class"; try{ FileOutputStream fos = new FileOutputStream(path); fos.write(proxy0s); fos.flush(); }catch (Exception e){ } } }
ProxyGenerator.generateProxyClass(final String var0, Class<?>[] var1, int var2);
JDK-sun包下提供的用于生成动态代理类信息的类, 方法入参看,创建代理类信息需传入的参数包括代理类全限定名、代理类实现的接口数组,访问权限标识。第三个参数非必填。
byte[] proxy0s = ProxyGenerator.generateProxyClass("FMapper$proxy", new Class[]{FMapper.class}); 这个名字不知道怎么生成的,待研究。。。
2,JDK 代理原理。
1. 首先有一个被代理的实例(Actor)。这个实例必须实现了一个接口(Person)
2. 定义一个调用处理器,持有被代理实例,实现 InvocationHandler 接口,invoke() 方法是代理具体做的事情
public class ProxyHandle<T> implements InvocationHandler { T target; public ProxyHandle(T target){ this.target = target; } /** * proxy 表示代理对象 * method 表示正在执行的方法 * args 表示调用方法时的参数 * */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("代理执行:" + method.getName() + "方法"); //执行代理方法 Object invoke = method.invoke(target, args); return invoke; } }
3. 创建一个代理类对象。
/** * loader 一个类加载器 * interfaces 一个 class 对象数组,每个元素都是需要实现的接口 * hander 一个调用处理器,定义了想使用这个代理干什么事 */ Person o = (Person)Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, personProxyHandle);
newProxyInstance 方法内部创建代理类:
//生成一个代理类 Class<?> cl = getProxyClass0(loader, intfs); ... //利用反射使用构造方法返回一个代理类实例 final Constructor<?> cons = cl.getConstructor(constructorParams); return cons.newInstance(new Object[]{h});
此时生成一个默认叫做 $Proxy0 的代理类,这个代理类继承了 Proxy,实现了接口,并且重写了接口的方法,拥有 Person 接口的实例方法,访问 $Proxy0 的实例方法,其实是访问执行器的 invoke 方法。
jdk 代理无法代理非接口实现类?
因为 java 只允许单根的问题,代理类已经继承了 Proxy,就无法再次继承其他类,自然也就没有实现类的方法,就无法完成非接口实现类的功能。
4. 代理类执行对应的方法
通过代理类源码可以看到,其实是执行 ProxyHandle 的 invoke 方法。
3,Proxy 类其他方法。
Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler hander)
创建一个代理对象
- loader 一个类加载器
- interfaces 一个 class 对象数组,每个元素都是需要实现的接口
- hander 一个调用处理器,定义了想使用这个代理干什么事
Proxy.isProxyClass(Class<?> cl)
判断一个特定的 class 是否是一个代理类
Proxy.getProxyClass(ClassLoader loader, Class<?>... interfaces)
获取一个动态加载类 class
- loader 一个类加载器
- interfaces 一个 class 对象数组,每个元素都是需要实现的接口
ProxyGenerator.generateProxyClass("$Proxy0", Actor.class.getInterfaces())
可以获取到代理 class 的字节数组文件。
4,动态代理-CGLIB
CGLIB 一个强大的,高性能,高质量的 Code 生成类库,它可以在运行期扩展 Java 类与实现 Java 接口
1,CGLIB 动态代理 Demo
1. 定义一个被代理类。(实现不实现接口都无所谓)
public class Actor implements Person {- private String name; public Actor(){} public Actor(String name){ this.name = name; } @Override public void sing() { System.out.println(name + "唱个歌!"); } @Override public void dance() { System.out.println(name + "跳个舞!"); } }
2. 定义一个代理类。
public class MyInterceptor implements MethodInterceptor { private Object target; public MyInterceptor(Object object){ this.target = object; } /** * @Param o 代理对象 * @Param method 被代理对象的方法 * @Param objects 方法入参 * @Param methodProxy 代理方法 * */ @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("cglib before!"); Object invoke = methodProxy.invoke(target, objects); System.out.println("cglib after!"); return invoke; } }
3. CGLIB 动态代理
public class ProxyDemoTest { public static void main(String[] args) { //定义一个被代理实现类 Actor a = new Actor("藏三"); //定义方法拦截器 MyInterceptor interceptor = new MyInterceptor(a); // 代理类class文件存入本地磁盘方便我们反编译查看源码 System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\gaox\\project\\apz\\demo-base\\src\\main\\java\\com\\demo\\base\\design_partten\\proxy\\code"); //通过 CGLIB 动态代理获取代理对象的过程 Enhancer enhancer = new Enhancer(); //设置父类,因为 cglib 是针对指定的类生成一个子类,所以需要父类 enhancer.setSuperclass(Actor.class); //设置回调 enhancer.setCallback(interceptor); //生成代理对象 Actor actorProxy = (Actor)enhancer.create(); actorProxy.dance(); actorProxy.sing(); } }
2,CGLIB 实现原理
1. 通过 CGLIB 动态代理获取代理对象的过程
Enhancer enhancer = new Enhancer();
2. 设置父类,因为 cglib 是针对指定的类生成一个子类,所以需要父类,或者是接口
enhancer.setSuperclass(Actor.class);
enhancer.setInterfaces();
3. 设置回调,就是对方法的拦截
enhancer.setCallback(interceptor);
4. 生成代理对象
enhancer.create();
5. JDK 代理和 CGLIB 代理的区别
1. 实现原理
- JDK 代理类是针对被代理类实现的接口做一个实现,针对接口而不是实现类,所以被代理类必须实现接口,使用 JAVA 的反射技术实现。
- CBLIB 代理类是针对被代理类生成的一个子类,覆盖其中的方法,使用 asm 字节码框架实现。
2. 实现过程
- JDK代理是不需要第三方库支持,只需要JDK环境就可以进行代理,使用条件:实现InvocationHandler + 使用Proxy.newProxyInstance产生代理对象 + 被代理的对象必须要实现接口。
- CGLib必须依赖于CGLib的类库,但是它需要类来实现任何接口代理的是指定的类生成一个子类,覆盖其中的方法,是一种继承;
针对接口编程的环境下推荐使用JDK的代理
-
使用场景
-
mybatis Mapper 接口,使用 MapperProxy 代理,典型的 JDK 代理。
-
mybatis 的 SqlSessionTemplate 也是个 jdk 代理。
本文作者:primaryC
本文链接:https://www.cnblogs.com/cnff/p/17009123.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步