《Java基础知识》动态代理(InvocationHandler)详解
1. 什么是动态代理
对象的执行方法,交给代理来负责。比如user.get() 方法,是User对象亲自去执行。而使用代理则是由proxy去执行get方法。
举例:投资商找明星拍广告,投资商是通过经纪人联系的,经纪人可以帮明星接这个广告,也可以拒绝。做不做,怎么做都叫给经纪人和投资商谈。
2. 实际场景应用
2.1 校验用户权限,每一个菜单请求,都要判断一下请求的用户是否有该菜单权限。菜单多了,代码冗余,且容易遗漏。
通过动态代理就可以实现为:每一个用户,每一个菜单的请求,都经过代理(proxy),由他判断是否有权限,调用者只需要调用,实现自己的逻辑,不关心权限问题。
3. 动态代理完整案例:
/**
* 创建用户接口
*/
public interface UserBean {
String getUser();
}
import demo.knowledgepoints.section.inf.UserBean;
public class UserBeanImpl implements UserBean {
private String user = null;
//flag:0 无权限,1有权限。
private String flag = null;
public String getFlag() {
return flag;
}
public void setFlag(String flag) {
this.flag = flag;
}
public UserBeanImpl(String user,String flag)
{
this.user = user;
this.flag = flag;
}
public String getUserName()
{
return user;
}
public String getUser()
{
System.out.println("this is getUser() method!");
return user;
}
public void setUser(String user)
{
this.user = user;
System.out.println("this is setUser() method!");
}
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method; public class UserBeanProxy implements InvocationHandler { private Object targetObject; public UserBeanProxy(Object targetObject)
{
this.targetObject = targetObject;
} @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
UserBeanImpl userBean = (UserBeanImpl) targetObject;
String flag = userBean.getFlag();
Object result = null; //权限判断
if("1".equals(flag) ){
result = method.invoke(targetObject, args);
}else{
System.out.println("sorry , You don't have permission");
}
return result;
}
}
import demo.knowledgepoints.section.inf.UserBean;
import java.lang.reflect.Proxy;
public class TestSection {
public static void main(String[] args) {
UserBeanImpl targetObject = new UserBeanImpl("蕾蕾","1");
UserBeanProxy proxy = new UserBeanProxy(targetObject);
//生成代理对象
UserBean object = (UserBean) Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),
targetObject.getClass().getInterfaces(), proxy);
String userName = object.getUser();
System.out.println("userName: " + userName);
}
}
运行结果:

模拟动态代理自己实现一个动态代理的功能:https://www.cnblogs.com/jssj/p/12499086.html。这篇文章应该可以更好的让人理解动态代理。
代理代理核心代码
UserBean object = (UserBean) Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),
targetObject.getClass().getInterfaces(), proxy);
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
接口:InvocationHandler,代理需要实现该接口,并且实现方法:invoke。
方法:newProxyInstance原理分析:
先看源码
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) throws IllegalArgumentException {
//验证传入的InvocationHandler不能为空
Objects.requireNonNull(h);
//复制代理类实现的所有接口
final Class<?>[] intfs = interfaces.clone();
//获取安全管理器
final SecurityManager sm = System.getSecurityManager();
//进行一些权限检验
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
//该方法先从缓存获取代理类, 如果没有再去生成一个代理类
Class<?> cl = getProxyClass0(loader, intfs);
try {
//进行一些权限检验
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
//获取参数类型是InvocationHandler.class的代理类构造器
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
//如果代理类是不可访问的, 就使用特权将它的构造器设置为可访问
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
//传入InvocationHandler实例去构造一个代理类的实例
//所有代理类都继承自Proxy, 因此这里会调用Proxy的构造器将InvocationHandler引用传入
return cons.newInstance(new Object[]{h});
} catch (Exception e) {
//统一用Exception捕获了所有异常
throw new InternalError(e.toString(), e);
}
}
可以看到,newProxyInstance方法首先是对参数进行一些权限校验,之后通过调用getProxyClass0方法生成了代理类的类对象,然后获取参数类型是InvocationHandler.class的代理类构造器。检验构造器是否可以访问,最后传入InvocationHandler实例的引用去构造出一个代理类实例,InvocationHandler实例的引用其实是Proxy持有着,因为生成的代理类默认继承自Proxy,所以最后会调用Proxy的构造器将引用传入。在这里我们重点关注getProxyClass0这个方法,看看代理类的Class对象是怎样来的,下面贴上该方法的代码
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
//目标类实现的接口不能大于65535
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
//获取代理类使用了缓存机制
return proxyClassCache.get(loader, interfaces);
}
可以看到getProxyClass0方法内部没有多少内容,首先是检查目标代理类实现的接口不能大于65535这个数,之后是通过类加载器和接口集合去缓存里面获取,如果能找到代理类就直接返回,否则就会调用ProxyClassFactory这个工厂去生成一个代理类。
//代理类生成工厂
private static final class ProxyClassFactory
implements BiFunction<ClassLoader, Class<?>[], Class<?>> {
//代理类名称前缀
private static final String proxyClassNamePrefix = "$Proxy";
//用原子类来生成代理类的序号, 以此来确定唯一的代理类
private static final AtomicLong nextUniqueNumber = new AtomicLong();
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
for (Class<?> intf : interfaces) {
//这里遍历interfaces数组进行验证, 主要做三件事情
//1.intf是否可以由指定的类加载进行加载
//2.intf是否是一个接口
//3.intf在数组中是否有重复
}
//生成代理类的包名
String proxyPkg = null;
//生成代理类的访问标志, 默认是public final的
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
for (Class<?> intf : interfaces) {
//获取接口的访问标志
int flags = intf.getModifiers();
//如果接口的访问标志不是public, 那么生成代理类的包名和接口包名相同
if (!Modifier.isPublic(flags)) {
//生成的代理类的访问标志设置为final
accessFlags = Modifier.FINAL;
//获取接口全限定名, 例如:java.util.Collection
String name = intf.getName();
int n = name.lastIndexOf('.');
//剪裁后得到包名:java.util
String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
//生成的代理类的包名和接口包名是一样的
if (proxyPkg == null) {
proxyPkg = pkg;
} else if (!pkg.equals(proxyPkg)) {
//代理类如果实现不同包的接口, 并且接口都不是public的, 那么就会在这里报错
throw new IllegalArgumentException(
"non-public interfaces from different packages");
}
}
}
//如果接口访问标志都是public的话, 那生成的代理类都放到默认的包下:com.sun.proxy
if (proxyPkg == null) {
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}
//生成代理类的序号
long num = nextUniqueNumber.getAndIncrement();
//生成代理类的全限定名, 包名+前缀+序号, 例如:com.sun.proxy.$Proxy0
String proxyName = proxyPkg + proxyClassNamePrefix + num;
//这里是核心, 用ProxyGenerator来生成字节码, 该类放在sun.misc包下
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName,
interfaces, accessFlags);
try {
//根据二进制文件生成相应的Class实例
return defineClass0(loader, proxyName, proxyClassFile,
0, proxyClassFile.length);
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}
}
}
该工厂的apply方法会被调用用来生成代理类的Class对象,由于代码的注释比较详细,我们只挑关键点进行阐述,其他的就不反复赘述了。
1. 在代码中可以看到JDK生成的代理类的类名是“$Proxy”+序号。
2. 如果接口是public的,代理类默认是public final的,并且生成的代理类默认放到com.sun.proxy这个包下。
3. 如果接口是非public的,那么代理类也是非public的,并且生成的代理类会放在对应接口所在的包下。
4. 如果接口是非public的,并且这些接口不在同一个包下,那么就会报错。
参考:
https://www.cnblogs.com/liuyun1995/p/8157098.html
《Java基础知识》动态代理(InvocationHandler)详解的更多相关文章
- Java 反射 设计模式 动态代理机制详解 [ 转载 ]
Java 反射 设计模式 动态代理机制详解 [ 转载 ] @author 亦山 原文链接:http://blog.csdn.net/luanlouis/article/details/24589193 ...
- Java进阶 | Proxy动态代理机制详解
一.Jvm加载对象 在说Java动态代理之前,还是要说一下Jvm加载对象的过程,这个依旧是理解动态代理的基础性原理: Java类即源代码程序.java类型文件,经过编译器编译之后就被转换成字节代码.c ...
- 【转】java的动态代理机制详解
java的动态代理机制详解 在学习Spring的时候,我们知道Spring主要有两大思想,一个是IoC,另一个就是AOP,对于IoC,依赖注入就不用多说了,而对于Spring的核心AOP来说,我们 ...
- java的动态代理机制详解-----https://www.cnblogs.com/xiaoluo501395377/p/3383130.html
java的动态代理机制详解-----https://www.cnblogs.com/xiaoluo501395377/p/3383130.html
- 《Java基础——break与continue用法详解》
Java基础--break与continue用法详解 1. break语句: 规则: 1. 仅用于循环语句和switch语句当中,用于跳出循环. 2. 当只有一层循环时,则直接跳出循环,不 ...
- Java 动态代理机制详解
在学习Spring的时候,我们知道Spring主要有两大思想,一个是IoC,另一个就是AOP,对于IoC,依赖注入就不用多说了,而对于Spring的核心AOP来说,我们不但要知道怎么通过AOP来满足的 ...
- java的动态代理机制详解
在学习Spring的时候,我们知道Spring主要有两大思想,一个是IoC,另一个就是AOP,对于IoC,依赖注入就不用多说了,而对于Spring的核心AOP来说,我们不但要知道怎么通过AOP来满足的 ...
- Java动态代理机制详解(JDK 和CGLIB,Javassist,ASM)
class文件简介及加载 Java编译器编译好Java文件之后,产生.class 文件在磁盘中.这种class文件是二进制文件,内容是只有JVM虚拟机能够识别的机器码.JVM虚拟机读取字节码文件,取出 ...
- Java 动态代理机制详解(JDK 和CGLIB,Javassist,ASM)
class文件简介及加载 Java编译器编译好Java文件之后,产生.class 文件在磁盘中.这种class文件是二进制文件,内容是只有JVM虚拟机能够识别的机器码.JVM虚拟机读取字节码文件,取出 ...
- Java的动态代理机制详解(转)
在学习Spring的时候,我们知道Spring主要有两大思想,一个是IoC,另一个就是AOP,对于IoC,依赖注入就不用多说了,而对于Spring的核心AOP来说,我们不但要知道怎么通过AOP来满足的 ...
随机推荐
- Java——内部类详解
说起内部类,大家肯定感觉熟悉又陌生,因为一定在很多框架源码中有看到别人使用过,但又感觉自己使用的比较少,今天我就带你具体来看看内部类. 内部类基础 所谓内部类就是在类的内部继续定义其他内部结构类. 在 ...
- 论文阅读:Face Recognition: From Traditional to Deep Learning Methods 《人脸识别综述:从传统方法到深度学习》
论文阅读:Face Recognition: From Traditional to Deep Learning Methods <人脸识别综述:从传统方法到深度学习> 一.引 ...
- 【2018寒假集训 Day2】【动态规划】挖地雷
挖地雷(Mine) 在一个地图上有N 个地窖(N<=200),每个地窖中埋有一定数量的地雷.同时,给出地窖之间的连接路径,并规定路径都是单向的,且从编号小的地窖通向编号大的地窖.某人可以从任一处 ...
- 【集训Day1 测试】装饰
装饰(decorate) [题目描述] 一个图有 N 个结点,编号 1 至 N,有 M 条无向边,第 i 条边连接的两个结点是 Ai 和Bi,其中 Ai 和 Bi 是不同的结点.可能有多条边连接的是同 ...
- 【Android - IPC】之Serializable和Parcelable序列化
1.序列化的目的 (1)永久的保存对象数据(将对象数据保存到文件或磁盘中): (2)通过序列化操作将对象数据在网络上进行传输(由于网络传输是以字节流的方式对数据进行传输的,因此序列化的目的是将对象数据 ...
- linux下信号量可设值的函数操作
#include <sys/types.h>#include <sys/ipc.h>#include <sys/sem.h>#include <errno.h ...
- while(cin)?
#include<iostream> #include<utility> using namespace std; int main() { int i; do { cout& ...
- 第三章 学习Shader所需的数学基础(2)
目录 1.坐标空间 1.2 坐标空间的变换 @ 1.坐标空间 我们在以前渲染流水线中就接触了坐标空间的变换.例如,在学习顶点着色器流水线阶段时,我们说过,顶点着色器的最基本功能就是把模型的顶点坐标从模 ...
- 机器学习笔记(六) ---- 支持向量机(SVM)【华为云技术分享】
版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/devcloud/article/detai ...
- 下一代容器架构已出,Docker何去何处?看看这里的6问6答!!
我猜很多人一看这个标题已经感觉很懵逼了,什么?下一代容器都出来了,我还没学Docker呢!!! 咳咳~~在这里我给大家做一个保证,下一代容器目前也只是各个公司在测试阶段,Github上面也有很多Iss ...