Proxy 代理模式 动态代理 CGLIB
代理的基本概念
proxy [ˈprɒksi] n. 代理服务器;代表权;代理人,代替物;委托书;
invoke [ɪnˈvəʊk] vt. 乞灵,祈求;提出或授引…以支持或证明;召鬼;借助;
invocation [ˌɪnvəˈkeɪʃn] n. 祈祷;乞求;乞灵;乞求神助;
subject [ˈsʌbdʒɪkt] n. 主题,话题;学科,科目;[哲] 主观;
adj. 须服从…的;(在君主等)统治下的;
v. 提供,提出;使…隶属;
- 可以隐藏委托类的实现
- 可以实现客户与委托类间的解耦,在不修改委托类代码的情况下能够做一些额外的处理。
按照代理的创建时期,可以分为两种:
- 静态代理:若代理类在程序运行前就已经存在,那么这种代理方式被成为静态代理 。静态代理通常是由程序员在Java代码中定义的, 且代理类和委托类会实现同一接口或是派生自相同的父类。
- 动态代理:在程序运行时运用反射机制动态创建而成。
静态代理
- 首先创建一个接口
- 然后创建具体实现类来实现这个接口,具体实现类中需要将接口中定义的方法的业务逻辑功能实现
- 再创建一个代理类同样实现这个接口,代理类中接口的方法只要调用具体类中的对应方法即可
这样,我们在需要使用接口中的某个方法时,直接调用代理类的方法即可,而具体的实现隐藏在了底层。
/**第一步:定义一个接口*/
interface Iuser {
void eat(String s);
}
/**第二步:创建具体实现类,即被代理类或委托类*/
class UserImpl implements Iuser {
@Override
public void eat(String s) {
System.out.println("我要吃" + s);
}
}
/**第三步:创建代理类*/
class UserProxy implements Iuser {
private Iuser user;//代理类通常只实现一个接口,因为代理类中持有的通常只是接口的引用,而不是某个委托类的直接引用
public UserProxy() {
this.user = new UserImpl();
}
public UserProxy(Iuser user) {
this.user = user;
}
@Override
public void eat(String s) {
System.out.println("静态代理前置内容");
user.eat(s);//利用的就是多态的特性,也是面向接口编程的一种典型的体现
System.out.println("静态代理后置内容");
}
}
/**静态代理访问演示*/
public class Test {
public static void main(String[] args) {
UserProxy proxy = new UserProxy();//或 UserProxy proxy = new UserProxy(new UserImpl())
proxy.eat("苹果");
}
}
- 代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法,但是因为代理类中接口的方法往往是没什么逻辑的,它通常只是调用了委托类的同名方法而已,所以这就出现了大量重复、冗余的代码。
- 如果接口中增加或修改了某个方法,除了所有委托类需要修改代码外,所有代理类也需要修改代码,增加了代码维护的复杂度。
- 代理对象只服务于一种类型的对象,即静态代理类只能为特定的接口服务,如想要为多个接口服务则需要建立多个代理类,这在大型系统中大大增加了复杂度。
动态代理
/**第一步:定义一个接口*/
interface Iuser {
void eat(String s);
}
/**再定义一个接口*/
interface Irun {
String run(int length);
}
/**第二步:创建具体实现类,即被代理类或委托类*/
class UserImpl implements Iuser, Irun {
@Override
public void eat(String s) {
System.out.println("我要吃" + s);
}
@Override
public String run(int length) {
System.out.println("我跑了 " + length + " 米");
return "跑步很欢乐";
}
}
/**第三步:定义代理实例的【调用处理器】,这是一个位于代理类与委托类之间的【中介类】,它需要实现【InvocationHandler】接口*/
class UserHandler implements InvocationHandler {
private Object object;//调用处理器持有委托类的引用,但并不限定委托类必须是某一接口或某一类
public UserHandler(Object object) {//这里是用Object接收的,所以可以传递任何类型的对象
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object returnObj = method.invoke(object, args); //【核心点】通过反射执行某个类的某方法
System.out.println("【方法】" + method.getName() + "【参数】" + Arrays.toString(args) + "【返回值】" + returnObj);
return returnObj;
}
}
/**第四步:在使用时动态创建动态代理类*/
public class Test {
public static void main(String[] args) {
test1();
System.out.println("----------------------2---------------------");
test2();
System.out.println("----------------------3---------------------");
test3();
}
private static void test1() {//完全基于接口Iuser的用法
Iuser user = new UserImpl();
System.out.println("【委托类】" + user.getClass().getName());//UserImpl
System.out.println("【委托类实现的接口】" + Arrays.toString(user.getClass().getInterfaces()));//[interface Iuser, interface Irun]
InvocationHandler handler = new UserHandler(user);
Iuser proxy = (Iuser) Proxy.newProxyInstance(Iuser.class.getClassLoader(), new Class[] { Iuser.class }, handler);//可以直接强转为Iuser
System.out.println("【代理类实现的接口】" + Arrays.toString(proxy.getClass().getInterfaces()));//[interface Iuser]。因为你只指定了Iuser接口
proxy.eat("苹果");
}
private static void test2() {//精简形式
Iuser proxy = (Iuser) Proxy.newProxyInstance(Iuser.class.getClassLoader(), new Class[] { Iuser.class }, new UserHandler(new UserImpl()));
System.out.println("【代理类实现的接口】" + Arrays.toString(proxy.getClass().getInterfaces()));//[interface Iuser, interface Irun]
proxy.eat("香蕉你个巴拉");
}
private static void test3() {//完全基于委托类UserImpl,之所以采用这种方式,是因为委托类实现了多个接口,且我们需要调用不同接口中的方法
Object proxy = Proxy.newProxyInstance(UserImpl.class.getClassLoader(), UserImpl.class.getInterfaces(), new UserHandler(new UserImpl()));
((Iuser) proxy).eat("你妹");//可以强转为Iuser
String returnObj = ((Irun) proxy).run(99);//也可以强转为Irun
System.out.println(returnObj);
}
}
接口 InvocationHandler 调用处理器
java.lang.reflect.InvocationHandler
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; //在代理实例上处理方法调用并返回结果。
- proxy - 在其上调用方法的代理实例
- method - 对应于在代理实例上调用的接口方法的 Method 实例。 Method 对象的声明类将是在其中声明方法的接口,该接口可以是代理类赖以继承方法的代理接口的超接口。
- args - 包含传入代理实例上方法调用的参数值的对象数组,如果接口方法不使用参数,则为 null。基本类型的参数被包装在适当基本包装器类的实例中。
- 从代理实例的方法调用返回的值。
- 如果接口方法的声明返回类型是基本类型,则此方法返回的值一定是相应基本包装对象类的实例;否则,它一定是可分配到声明返回类型的类型。
- 如果此方法返回的值为 null 并且接口方法的返回类型是基本类型,则代理实例上的方法调用将抛出 NullPointerException。否则,如果此方法返回的值与上述接口方法的声明返回类型不兼容,则代理实例上的方法调用将抛出 ClassCastException。
Proxy 动态代理类
public class java.lang.reflect.Proxy extends Object implements java.io.Serializable
基本简介
InvocationHandler handler = new MyInvocationHandler(new FooImpl());//FooImpl是实现 Foo 接口的某一委托类
Foo f = (Foo) Proxy.getProxyClass(Foo.class.getClassLoader(), new Class[] { Foo.class })
.getConstructor(new Class[] { InvocationHandler.class })
.newInstance(new Object[] { handler });
Foo proxy = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),//定义了由哪个ClassLoader对象来对生成的代理对象进行加载
new Class[] { Foo.class },//表示的是我将要给我需要代理的对象提供一组什么接口,之后我这个代理对象就会实现了这组接口
handler);//表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上
代理类具有的属性(properties)
- 代理类是公共的、最终的,而不是抽象的。Proxy classes are public, final, and not abstract.
//这句话我觉得是有问题的,如下:
System.out.println("【代理类的修饰符】" + Modifier.toString(proxy.getClass().getModifiers()));//final,并不是public的
- 未指定代理类的非限定名称 The unqualified name of a proxy class is unspecified。但是,以字符串 "$Proxy" 开头的类名空间应该为代理类保留。
System.out.println("【代理类】" + proxy.getClass().getName());//【$Proxy0】
- 代理类的超类为(A proxy class extends) java.lang.reflect.Proxy。
System.out.println("【代理类的超类】" + proxy.getClass().getSuperclass());//【class java.lang.reflect.Proxy】
- 代理类会按同一顺序准确地实现其创建时指定的接口。
- 如果代理类实现了非公共接口,那么它将在与该接口相同的包中定义。If a proxy class implements a non-public interface, then it will be defined in the same package as that interface. 否则,代理类的包也是未指定的 unspecified。注意,包密封 package sealing 将不阻止代理类在运行时在特定包中的成功定义,也不会阻止相同类加载器和带有特定签名的包所定义的类。
- 由于代理类将实现所有在其创建时指定的接口,所以对其 Class 对象调用 getInterfaces 将返回一个包含相同接口列表的数组(按其创建时指定的顺序),对其 Class 对象调用 getMethods 将返回一个包括这些接口中所有方法的 Method 对象的数组,并且调用 getMethod 将会在代理接口中找到期望的 as would be expected 一些方法。
System.out.println("【代理类实现的接口】" + Arrays.toString(proxy.getClass().getInterfaces()));//[interface Iuser, interface Irun]
- 如果 Proxy.isProxyClass 方法传递代理类(由 Proxy.getProxyClass 返回的类,或由 Proxy.newProxyInstance 返回的对象的类),则该方法返回 true,否则返回 false。
System.out.println("【是否是代理类】" +Proxy.isProxyClass(proxy.getClass()));//true
- 代理类的 java.security.ProtectionDomain 与由引导类加载器(如 java.lang.Object)加载的系统类相同,原因是代理类的代码由受信任的系统代码生成。此保护域通常被授予 java.security.AllPermission。
- 每个代理类都有一个可以带一个参数(接口 InvocationHandler 的实现)的公共构造方法,用于设置代理实例的调用处理程序。并非必须使用反射 API 才能访问公共构造方法,通过调用 Proxy.newInstance 方法(将调用 Proxy.getProxyClass 的操作和调用带有调用处理程序的构造方法结合在一起)也可以创建代理实例。
代理实例具有的属性
- 提供代理实例 proxy 和一个由其代理类 Foo 实现的接口,表达式 proxy instanceof Foo 将返回 true,并且 (Foo) proxy 的强制转换操作将会成功(而不抛出 ClassCastException):
//指定代理类实现的接口为:new Class[] { Iuser.class, Irun.class }
((Iuser) proxy).eat("苹果");//可以强转为Iuser
((Irun) proxy).run(99);//也可以强转为Irun
- 每个代理实例都有一个关联的调用处理程序,它会被传递到其构造方法中。静态 Proxy.getInvocationHandler 方法将返回与作为其参数传递的代理实例相关的调用处理程序。
Iuser proxy = (Iuser) Proxy.newProxyInstance(Iuser.class.getClassLoader(), new Class[] { Iuser.class }, handler);
System.out.println(Proxy.getInvocationHandler(proxy) == handler);//true
- 代理实例上的接口方法调用将按照该方法的文档描述进行编码,并被指派到调用处理程序的 Invoke 方法。
- 在代理实例上的 java.lang.Object 中声明的 hashCode、equals 或 toString 方法的调用将按照与编码和指派接口方法调用相同的方式进行编码,并被指派到调用处理程序的 invoke 方法,如上所述。传递到 invoke 的 Method 对象的声明类是 java.lang.Object。代理类不重写从 java.lang.Object 继承的代理实例的其他公共方法,所以这些方法的调用行为与其对 java.lang.Object 实例的操作一样。
在多代理接口中重复的方法
API
- protected InvocationHandler h 此代理实例的调用处理程序。
- protected Proxy(InvocationHandler h) 使用其调用处理程序的指定值从子类(通常为动态代理类)构建新的 Proxy 实例。
- static InvocationHandler getInvocationHandler(Object proxy) 返回指定代理实例的调用处理程序。
- static boolean isProxyClass(Class<?> cl) 当且仅当指定的类通过 getProxyClass 方法或 newProxyInstance 方法动态生成为代理类时,返回 true。
- static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces) 返回代理类的 java.lang.Class 对象,并向其提供类加载器和接口数组。
- static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。
- 返回值:一个带有代理类的指定调用处理程序的代理实例,它由指定的类加载器定义,并实现指定的接口
- 参数:
- loader - 定义代理类的类加载器。定义了由哪个ClassLoader对象来对生成的代理对象进行加载
- interfaces - 代理类要实现的接口列表。表示的是我将要给我需要代理的对象提供一组什么接口,之后我这个代理对象就会实现了这组接口
- h - 指派方法调用的调用处理程序。表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上
关于 newProxyInstance 方法
((Iuser) proxy).eat("苹果");
((Irun) proxy).run(99);
CGLIB代码生成库简介
Byte Code Generation Library is high level API to generate and transform JAVA byte code. It is used by AOP, testing, data access frameworks to generate dynamic proxy objects and intercept field access.
字节码生成库是用于生成和转换JAVA字节码的高级API。它被AOP,测试,数据访问框架用于生成动态代理对象并拦截字段访问。
- net.sf.cglib.core:底层字节码操作类;大部分与ASP相关。
- net.sf.cglib.transform:编译期、运行期的class文件转换类。
- net.sf.cglib.proxy:代理创建类、方法拦截类。
- net.sf.cglib.reflect:更快的反射类、C#风格的代理类。
- net.sf.cglib.util:集合排序工具类
- net.sf.cglib.beans:JavaBean相关的工具类
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
interface LoginService {
public boolean checkUser();
}
class LoginServiceImpl implements LoginService {
@Override
public boolean checkUser() {
System.out.println("LoginServiceImpl checkUser");
return false;
}
}
interface UserService {
public String getUserName();
}
class UserServiceImpl implements UserService {
@Override
public String getUserName() {
System.out.println("UserServiceImpl getUserName");
return null;
}
}
class CglibProxy implements MethodInterceptor {
@Override
public Object intercept(Object proxy, Method method, Object[] params, MethodProxy methodProxy) throws Throwable {
System.out.println("*********代理方法执行前************");
Object retObj = methodProxy.invokeSuper(proxy, params);
System.out.println("*********代理方法执行后************");
return retObj;
}
//返回目标对象的代理对象
public Object newProxy(Object target) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(this);
enhancer.setClassLoader(target.getClass().getClassLoader());
return enhancer.create();
}
}
public class Test {
public static void main(String[] args) {
//创建目标对象
LoginService loninService = new LoginServiceImpl();
UserService userService = new UserServiceImpl();
CglibProxy proxy = new CglibProxy();
//创建代理对象
LoginService loninService$Proxy = (LoginService) proxy.newProxy(loninService);
UserService userService$Proxy = (UserService) proxy.newProxy(userService);
loninService$Proxy.checkUser();
userService$Proxy.getUserName();
}
}
Proxy 代理模式 动态代理 CGLIB的更多相关文章
- 黑马程序员:Java基础总结----静态代理模式&动态代理
黑马程序员:Java基础总结 静态代理模式&动态代理 ASP.Net+Android+IO开发 . .Net培训 .期待与您交流! 静态代理模式 public class Ts { ...
- Proxy 代理模式 动态代理 cglib MD
Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...
- Java设计模式--代理模式+动态代理+CGLib代理
静态代理 抽象主题角色:声明真实主题和代理主题的共同接口. 代理主题角色:代理主题内部含有对真实主题的引用,从而在任何时候操作真实主题对象:代理主题提供一个与真实主题相同的接口,以便在任何时候都可以代 ...
- Java-马士兵设计模式学习笔记-代理模式-动态代理 调用Proxy.newProxyInstance()
一.概述 1.目标:不自己写代理类,利用Proxy.newProxyInstance()动态生成 2.用到的知识点: (1)//编译源码,生成class,注意编译环境要换成jdk才有compiler, ...
- java23种设计模式之一: 代理模式(动态代理)
在学习Spring的时候,我们知道Spring主要有两大思想,一个是IoC,另一个就是AOP,对于IoC,依赖注入就不用多说了,而对于Spring的核心AOP来说,我们不但要知道怎么通过AOP来满足的 ...
- Java-马士兵设计模式学习笔记-代理模式-动态代理 修改成可以任意修改代理逻辑
一.概述 1.目标:动态代理的代理逻辑可以任意修改 2.思路: (1)要把代理逻辑抽离,站在jvm的角度思考,应独立出InvocationHandler接口,并接收被代理的对象及方法作为参数invok ...
- spring——AOP(静态代理、动态代理、AOP)
一.代理模式 代理模式的分类: 静态代理 动态代理 从租房子开始讲起:中介与房东有同一的目标在于租房 1.静态代理 静态代理角色分析: 抽象角色:一般使用接口或者抽象类来实现(这里为租房接口) pub ...
- Spring框架_代理模式(静态代理,动态代理,cglib代理)
共性问题: 1. 服务器启动报错,什么原因? * jar包缺少.jar包冲突 1) 先检查项目中是否缺少jar包引用 2) 服务器: 检查jar包有没有发布到服务器下: ...
- 代理模式(静态代理、JDK动态代理原理分析、CGLIB动态代理)
代理模式 代理模式是设计模式之一,为一个对象提供一个替身或者占位符以控制对这个对象的访问,它给目标对象提供一个代理对象,由代理对象控制对目标对象的访问. 那么为什么要使用代理模式呢? 1.隔离,客户端 ...
随机推荐
- kolla部署all-in-one环境(N版)
简单介绍: Kolla 是 OpenStack 大帐篷模式下的官方子项目之一,其主要目标是通过利用 Docker 容器以及 Ansible 自动化部署工具,来为 OpenStack 云平台提 供一个简 ...
- SPOJ6717 Two Paths 树形dp
首先有朴素的\(O(n^2)\)想法 首先枚举断边,之后对于断边之后的两棵子树求出直径 考虑优化这个朴素的想法 考虑换根\(dp\) 具体而言,首先求出\(f[i], fs[i]\)表示\(i\)号点 ...
- BZOJ 4289: PA2012 Tax 差分建图 最短路
https://www.lydsy.com/JudgeOnline/problem.php?id=4289 https://www.cnblogs.com/clrs97/p/5046933.html ...
- python编程之socket编程基础
python socket编程,首先需要import socket模块 首先创建一个socket对象 expl = socket.socket(socket.AF_INET,socket.SOCK ...
- [BZOJ2683]简单题/[BZOJ1176][BalkanOI2007]Mokia
[BZOJ2683]简单题 题目大意: 一个\(n\times n(n\le5\times10^5)\)的矩阵,初始时每个格子里的数全为\(0\).\(m(m\le2\times10^5)\)次操作, ...
- hdu 1514 记忆化搜索
题意是给4堆(堆的高度小于等于40)有颜色(颜色的种类小于等于20)的物品,你有一个篮子最多能装5件物品,每次从这4堆物品里面任取一件物品放进篮子里,但是取每堆物品时,必须先取上面的物品,才能取下面的 ...
- 【JavaScript代码实现一】数组去重
function arrayNoDupulate(array) { var hash = {}; var result = []; for(var i=0;i<array.length;i++) ...
- Elasticsearch中document的基础知识
写在前面的话:读书破万卷,编码如有神-------------------------------------------------------------------- 参考内容: <Ela ...
- Codeforces Round #351 (VK Cup 2016 Round 3, Div. 2 Edition) D. Bear and Two Paths 构造
D. Bear and Two Paths 题目连接: http://www.codeforces.com/contest/673/problem/D Description Bearland has ...
- 体感设备:因特尔 Intel RealSense R200,乐视LeTV Pro Xtion和Orb奥比中光bec Astra比较
最近调试三个个厂家的体感设备,第一个是Intel的RealSense R200(参数规格:分辨率:1080p,深度有效距离:0.51-4,USB3.0),第二个是乐视LeTV Pro Xtion(参数 ...