Java实现JDK * 的原理详解
作者:??下岗码农大飞???? 发布时间:2021-09-19 08:17:43
概念
代理:为控制A对象,而创建出新B对象,由B对象代替执行A对象所有操作,称之为代理。一个代理体系建立涉及到3个参与角色:真实对象(A),代理对象(B),客户端。
其中的代理对象(B)起到中介作用,连通真实对象(A)与客户端,如果进一步拓展,代理对象可以实现更加复杂逻辑,比如对真实对象进行访问控制。
案例
需求:员工业务层接口调用save需要admin权限,调用list不需要权限,没权限调用时抛出异常提示。
静态代理
/**
* 代理接口
*/
public interface IEmployeeService {
void save();
void list();
}
/**
* 真实对象
*/
public class EmployeeServiceImpl implements IEmployeeService {
@Override
public void save() {
System.out.println("EmployeeServiceImpl-正常的save....");
}
@Override
public void list() {
System.out.println("EmployeeServiceImpl-正常的list....");
}
}
/**
* 模拟当前登录用户对象
*/
public class SessionHolder {
private static String currentUser;
public static String getCurrentUser(){
return currentUser;
}
public static void setCurrentUser(String currentUser){
SessionHolder.currentUser = currentUser;
}
}
/**
* 代理对象
*/
public class EmployeeProxy implements IEmployeeService {
//真实对象
private EmployeeServiceImpl employeeService;
public EmployeeProxy(EmployeeServiceImpl employeeService){
this.employeeService = employeeService;
}
@Override
public void save() {
//权限判断
if("admin".equals(SessionHolder.getCurrentUser())){
employeeService.save();
}else{
throw new RuntimeException("当前非admin用户,不能执行save操作");
}
}
@Override
public void list() {
employeeService.list();
}
}
public class App {
public static void main(String[] args) {
System.out.println("----------------真实对象--------------------");
EmployeeServiceImpl employeeService = new EmployeeServiceImpl();
employeeService.list();
employeeService.save();
System.out.println("----------------代理对象--------------------");
SessionHolder.setCurrentUser("dafei"); //设置权限(当前登录用户)
EmployeeProxy employeeProxy = new EmployeeProxy(employeeService);
employeeProxy.list();
employeeProxy.save();
}
}
----------------真实对象--------------------
EmployeeServiceImpl-正常的list....
EmployeeServiceImpl-正常的save....
----------------代理对象--------------------
EmployeeServiceImpl-正常的list....
Exception in thread "main" java.lang.RuntimeException: 当前非admin用户,不能执行save操作
at com.langfeiyes.pattern.proxy.demo.EmployeeProxy.save(EmployeeProxy.java:20)
at com.langfeiyes.pattern.proxy.demo.App.main(App.java:16)
使用真实对象EmployeeServiceImpl 直接调用时,不管是list 还是save都能直接访问,但不符合需求上的admin权限限制。如果使用代理对象EmployeeProxy,可以完成需求实现。
通过直接创建新类新类代理对象方式完成代理逻辑,这种方式称之为静态代理模式。
JDK * 模式
Java常用的 * 模式有JDK * ,也有cglib * ,此处重点讲解JDK的 *
还是原来的需求,前面的IEmployeeService EmployeeServiceImpl SessionHolder 都没变,新加一个JDK代理控制器-EmployeeInvocationHandler
/**
* jdk * 控制类,由它牵头代理类获取,代理方法的执行
*/
public class EmployeeInvocationHandler implements InvocationHandler {
//真实对象-EmployeeServiceImpl
private Object target;
public EmployeeInvocationHandler(Object target){
this.target = target;
}
//获取jvm在内存中生成代理对象
public Object getProxy(){
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this);
}
//代理对象控制执行方法
//参数1:代理对象
//参数2:真实对象的方法(使用方式得到方法对象)
//参数3:真实对象方法参数列表
//此处是代理对象对外暴露的可编辑的方法处理场所,代理对象每调用一个次方法,就会执行一次invoke
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String name = method.getName();
if("save".equals(name) && !"admin".equals(SessionHolder.getCurrentUser())){
throw new RuntimeException("当前非admin用户,不能执行save操作");
}
return method.invoke(target, args);
}
}
测试App类稍微改动下:
public class App {
public static void main(String[] args) {
System.out.println("----------------真实对象--------------------");
EmployeeServiceImpl employeeService = new EmployeeServiceImpl();
employeeService.list();
employeeService.save();
System.out.println("----------------代理对象--------------------");
SessionHolder.setCurrentUser("dafei");
EmployeeInvocationHandler handler =
new EmployeeInvocationHandler(employeeService);
IEmployeeService proxy = (IEmployeeService) handler.getProxy();
proxy.list();
proxy.save();
}
}
上面代码一样可以实现需求,跟静态代理区别就在于少创建了代理对象。此时存在疑问点,没有创建代理对象,为啥可以实现代理类调用呢??
原理分析
先抛出结论JDK * 底层实现原理:使用接口实现方式,运行时,在内存中动态构建出一个类,然后编译,执行。这个类是一次性的,JVM停止,代理类就消失。
参与角色 要理解JDK * 原理,首先得了解JDK * 涉及到的类
InvocationHandler:真实对象方法调用处理器,内置invoke方法,其功能:为真实对象定制代理逻辑
EmployeeInvocationHandler:员工服务真实对象方法调用处理器,此类有3个用途: 1>设置真实对象
//真实对象-EmployeeServiceImpl
private Object target;
public EmployeeInvocationHandler(Object target){
this.target = target;
}
2>定制代理方法实现逻辑
为真实对象save方法添加了权限校验逻辑
//代理对象控制执行方法
//参数1:代理对象
//参数2:真实对象的方法(使用方式得到方法对象)
//参数3:真实对象方法参数列表
//此处是代理对象对外暴露的可编辑的方法处理场所,代理对象每调用一个次方法,就会执行一次invoke
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String name = method.getName();
if("save".equals(name) && !"admin".equals(SessionHolder.getCurrentUser())){
throw new RuntimeException("当前非admin用户,不能执行save操作");
}
return method.invoke(target, args);
}
3>返回代理对象
方法执行完之后,返回一个名为:$ProxyX的代理类(其中的X是序号,一般默认为0),这代理类由JDK动态构建出来。
//获取jvm在内存中生成代理对象
public Object getProxy(){
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this);
}
Proxy: * 控制类,是JDK动态生成的$ProxyX类的父类,它作用如下:
1>通过调用ProxyBuilder 类builder方法构建代理对象类
private static Constructor<?> getProxyConstructor(Class<?> caller,
ClassLoader loader,
Class<?>... interfaces){
return proxyCache.sub(intf).computeIfAbsent(
loader,
(ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
);
}
2>通过newProxyInstance方法返回$ProxyX类的实例
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) {
//...
}
$Proxy0:App类运行时,JDK动态构建出来的代理类,继承至Proxy类
public class App {
public static void main(String[] args) {
//System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
System.out.println("----------------真实对象--------------------");
EmployeeServiceImpl employeeService = new EmployeeServiceImpl();
employeeService.list();
employeeService.save();
System.out.println("----------------代理对象--------------------");
SessionHolder.setCurrentUser("dafei");
EmployeeInvocationHandler handler =
new EmployeeInvocationHandler(employeeService);
IEmployeeService proxy = (IEmployeeService) handler.getProxy();
proxy.list();
proxy.save();
}
}
默认情况下JVM是不保存动态创建代理类字节码对象的,可以在main方法中配置代理参数让字节码保留
//JDK8之前
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
//JDK8之后
System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
执行完之后,会在项目根目录生成代理类字节码对象。
为了方便解读,将一些不需要的方法剔除之后
$Proxy0类
public class $Proxy0 extends Proxy implements IEmployeeService {
private static Method m4;
private static Method m3;
static {
try {
m4 = Class.forName("com.langfeiyes.proxy.demo.IEmployeeService")
.getMethod("save");
m3 = Class.forName("com.langfeiyes.proxy.demo.IEmployeeService")
.getMethod("list");
} catch (Exception e) {
e.printStackTrace();
}
}
public $Proxy0(InvocationHandler var1) throws Throwable {
super(var1);
}
public final void save() throws Throwable {
super.h.invoke(this, m4, (Object[])null);
}
public final void list() throws Throwable{
super.h.invoke(this, m3, (Object[])null);
}
}
从源码上看,$Proxy0的特点:
1>继承了Proxy类,实现了IEmployeeService 接口
2>通过静态块的方式反射IEmployeeService接口save与list方法,得到他们的方法对象Method
3>调用父类构造器,需要传入InvocationHandler 参数
4>重写IEmployeeService接口的save list方法靠的是父类Proxy的h属性.invoke方法
真相大白
下图所有参与 * 的类:
下图是上图的操作时序图,跟着走就对了
到这,JDK * 就ok了。
来源:https://juejin.cn/post/7114228397613514765


猜你喜欢
- 先看效果:输入内容,点击生成二维码:点击logo图案:代码:QRCodeUtil:package com.example.administr
- 背景客户使用我们系统的时候,查询不带任何查询条件,查询就返回全部数据,500多万条数据啊,然后直接导出,数据量庞大,接口超时,这可苦了我们这
- 本文实例讲述了C#实现简单的Login窗口。分享给大家供大家参考。具体实现方法如下:C# 制作登录窗体,登录成功之后正确的做法是关闭(clo
- 对 Debug 的好奇初学 Java 时,我对 IDEA 的 Debug 非常好奇,不止是它能查看断点的上下文环境,更神奇的是我可以在断点处
- 本文实例讲述了Android开发之imageView图片按比例缩放的实现方法。分享给大家供大家参考,具体如下:android:scaleTy
- 生成excel并导出到对应位置package tech.BurtonPratice; import org.apache.poi.hssf.
- 我们经常看到使用了ViewPager的App,在每页上面都会有一个滑块来标志当前处于哪一页。在PagerView包里有android.sup
- 通常我们在看一些源码时,发现全是T、?,晕乎乎的:sob:。于是,把泛型掌握好十分重要!什么是泛型Java 泛型(generics)是 JD
- 匿名内部类:先举个例子吧,给大家看一下什么是匿名内部类,Endeavor刚刚接触的时候,觉得哇哦,好奇怪的样子,这也太别扭了吧,不知道大家是
- 随着微信的到来,二维码越来越火爆,随处能看到二维码,比如商城里面,肯德基,餐厅等等,对于二维码扫描我们使用的是google的开源框架Zxin
- 简介本文介绍Idea如何根据maven依赖名查找它是哪个pom.xml引入的。有时候会有这样的问题:我们知道项目里用了某个依赖,想知道它是项
- System.getProperty(user.dir)定位问题前言随着学习java web 的深入学习,为了巩固自己的学习成果,练习了一个
- 1.替换符的使用(1)在 app-android-defaultConfig (或者多渠道打包)下面可以这样使用android { &nbs
- 前言:由于项目需求,短信验证码的接口需要换成阿里大于的,但是尴尬的发现阿里大于的jar包没有maven版本的,于是便开始了一上午的 * 引包之
- 1.包1.包的三大作用区分相同名字的类当类很多时,可方便管理控制访问范围2.包的基本语法package abc.www;3.包的本质实际上就
- 之前由于项目需要比较详细地学习了Spring Security的相关知识,并打算实现一个较为通用的权限管理模块。由于项目是前后端分离的,所以
- 关于Maven的使用就不再啰嗦了,网上很多,并且这么多年变化也不大,这里仅介绍怎么搭建Hadoop的开发环境。1. 首先创建工程mvn ar
- tasks下面的代码展示了三个Gradle task,稍后会讲解这三者的不同。 task myTask { println "He
- 此问题的产生,主要是数据库的字段名一样导致三张表 DOCTOR JOB OBJECT有问题的查询语句和查询结果是:SELECT d.*,j.
- 本文实例为大家分享了C#实现图片切割、切图的具体代码,供大家参考,具体内容如下前台准备两个Image控件。上面是显示原图,下面显示切割后的效