精华内容
下载资源
问答
  • 精馏塔动态过程模拟研究对于确定精馏塔系统从开工过程达到系统稳态运行时间上限具有重要意义,开工过程动态模拟研究可以为系统产品质量控制方案和控制策略提供理论指导。针对氢同位素精馏分离过程,基于严格热力学...
  • 动态模拟CPU源码.rar

    2019-08-29 19:20:37
    一、源码特点 采用WPF进行开发,实现动态模拟CPU曲线图,可拖拽、放大缩小 ... 本源码是一个动态模拟CPU曲线图源码,适合初学者,欢迎下载 三、注意事项 1、开发环境为Visual Studio 2010,使用.net 4.0开发。
  • 根据单晶硅各向异性腐蚀的特点,以晶格内部原子键密度为主要因素,温度、腐蚀液浓度等环境因素为校正因子,建立了一个新颖的硅各向
  • 本文将介绍其特点、工作原理,及其在70dB大动态范围控制系统中应用,给出了两种级联控制电路连接方法并对二者性能做出了比较分析。AD8367芯片介绍AD8367是基于AD公司X-AMP结构可变增益中频放大器,能够实现...
  • 针对布料动态模拟中快速稳定求解瓶颈问题, 提出了一种局部自适应混合积分方法。在每一时间步长, 网格中质点利用自身模拟参数求解一稳定判断准则, 据此自适应判定该质点相连弹簧不同弹性力部分引起运动方程...
  • 为了探讨高频振动筛横梁应力分布特点,以与实际工况相似为原则,采用数值模拟方法对其进行分析,结果表明,其应力数值较小,远低于其屈服极限,主要集中于横梁中部及两侧法兰处,为该筛机今后设计改进及动态强度试验...
  • 在此动态数据采集系统设计充分借鉴无线传感器网络设计思想,探讨一种基于ZigBee协议无线传榆设计方案,具有低功耗、灵活性强、可扩展好、体积小、成本低等特点,解决了传统采集系统设计瓶颈,并具有新优异...
  • 针对这个问题,提出一种结合Qsplat算法与IFS(迭代函数系统)算法粒子系统积云模拟方法,并考虑了积云结构特点。基于Qsplat算法细节层次树形结构建立树型结构粒子系统,以加速粒子系统搜索速度;对距离视点...
  • 数值模拟模型在商丘地下水动态预报中应用,李平,樊向阳,本文针对商丘实验区水文地质特点,概述了地下水位预报模型建立过程,并结合典型井点地下水位预报结果,经与实际资料对比,其结
  • 该电路具有节省输入晶体管数目、偏置晶体管和偏置电路,以及性能指标优良的特点。其主要参数指标达到:一、三次谐波差值40 dB,输出信号频带宽度375 MHz,平均电源电流约30μA,动态功耗约36μW。可直接应用于低功耗...
  •  新一代逻辑器件已经出现,其特点是工作电压低,可以和其他低电压器件,例如采用领先65纳米和45纳米工艺FPGA、存储卡以及微型控制器等,直接连接。内核电压可低至1.2V,而输入输出电压一般为3.3伏、2.5伏或...
  • MAX12557型高速A/D转换器的特点及应用 罗勇,王莹,周资伟 1 引言 MAX12557是Maxim公司开发的一款高速、低功耗、高性能的14位模/数转换器。该模/数转换器具有完全差分的双通道带宽采样保持(T/H)输入端,采样速率为...
  • 根据激光星间链路技术特点, 并考虑导航卫星星间链路需兼顾通信、高精度测量与自主定轨多重要求, 研究了全球导航卫星系统(GNSS)激光星间链路拓扑的动态优化问题。采用有限状态自动机(FSA)思想建立了一种导航卫星...
  • 主要通过筛机与实际工况相似原则简化高频振动筛模型,而后对其进行动力学分析,研究其内部应力分布规律,研究结果可作为今后筛机动态强度校核和结构改进依据。
  • 针对矿用防爆胶轮车井下作业时受力复杂的特点,分别建立了车体多体动力学模型和车架有限元模型。应用虚拟样机技术模拟真实的工况,提取搓板路面工况下随机激励的峰值,进而通过有限元法对车架进行动态应力分析。有限元...
  • 基于BP网络和模拟退火算法的多品种、小批量车间作业动态调度模型研究,阳岁红,鄢萍,针对多品种、小批量生产企业车间作业调度的复杂性、不确定性、多约束性和多目标性的特点,综合BP网络自适应、自学习和高度容错的...
  • matplotlib包下下的animation模块的FuncAnimation方法可以称的上matplotlib功能...核心要导入: from matplotlib.animation import FuncAnimation它的特点就是可以用一个给定的时间间隔不断的重复执行某个绘制函数,...

    matplotlib包下下的animation模块的FuncAnimation方法可以称的上matplotlib功能最强大的方法之一了,使用它可以创建很多丰富美丽的数据图像,而且可以随着时间变动而变动。

    比如可以进行硬件的仿真等等。

    核心要导入: from matplotlib.animation import FuncAnimation

    它的特点就是可以用一个给定的时间间隔不断的重复执行某个绘制函数, 从而实现动态数据的绘制。

    但是要注意一点就是在每次绘制的时候如果仅仅改变要绘制的数据是不行的,看上去特别不美观,因为每次在当前axes上调用新的plot时会使用不同的颜色,尺寸等,会像这样:

    image.png

    image.png

    可以看到颜色在不停的变化, 如果数据集动态改变, 那么尺寸也会动态改变(因为从新的axes上进行绘制了),这样达不到我们要的效果。

    其根本原因就是,当前绘制图形的实例: axes对象没有重置, 那么在绘制新的图象时, 它的各种参数都会采用系统默认的参数进行变动,比如:最初颜色是蓝色, 当第二次绘制时,蓝色在当前axes实例下已经绘制过了,系统则会绘制上不同的颜色(有规律)。

    知道根本原因后我们可以这样更改:

    每次重新绘制的时候,先清除当前axes对象(绘图对象),让它重置,然后再用新的数据集绘制,这样每次绘制出来的数据从颜色上来说每次都是蓝色了,从其他属性来说也是一样的, 这样看上去效果会很棒。

    动态生成数据:

    image.png

    生成数据源码:

    import csv

    import random

    import time

    # 3个属性下的初始值

    x_value = 0

    total_1 = 1000

    total_2 = 1000

    # 生成的csv数据的头部标签

    fieldnames = ["x_value", "total_1", "total_2"]

    # 写入文件头部标签, 因文件打开方式是w, 故而会覆盖掉前面的内容从头开始写

    with open('data_6.csv', 'w') as csv_file:

    # 以字典的方式写入文件

    csv_writer = csv.DictWriter(csv_file, fieldnames=fieldnames)

    # 写入头部信息: x_vale, total_1, total_2的标签信息

    csv_writer.writeheader()

    # 永真循环, 程序每隔一秒(time.sleep(1))就写入一次数据

    while True:

    with open('data_6.csv', 'a') as csv_file:

    csv_writer = csv.DictWriter(csv_file, fieldnames=fieldnames)

    # 因为创建的是DictWriter(以字典的方式写入文件), 因此创建的数据也是字典格式的, 键是列的标签, 值是对应标签上的数据

    info = {

    "x_value": x_value,

    "total_1": total_1,

    "total_2": total_2

    }

    # 写入一行信息

    csv_writer.writerow(info)

    # 打印刚刚写入的信息

    print(x_value, total_1, total_2)

    # 时间往后移一天

    x_value += 1

    # 两个value值随机变动

    total_1 = total_1 + random.randint(-6, 8)

    total_2 = total_2 + random.randint(-5, 6)

    # 程序等待1秒钟

    time.sleep(1)

    动态展示数据:

    image.png

    image.png

    image.png

    展示数据源码:

    # coding=utf-8

    import random

    from itertools import count

    import pandas as pd

    import matplotlib.pyplot as plt

    from matplotlib.animation import FuncAnimation

    plt.style.use('fivethirtyeight')

    # 利用itertools里的count创建一个迭代器对象,默认从0开始计数, 是一个"无限大"的等差数列

    index = count()

    x_vals = []

    y_vals = []

    def animate(i):

    # i表示的是经历过的"时间", 即每调用一次animate函数, i的值会自动加一

    # 我们从一个动态生成数据的csv文件中获取数据来模拟动态数据

    data = pd.read_csv('data_6.csv')

    # 获得该动态数据的所有列的所有数据(当前生成的), 到下一次运行时该数据如果变动了, 绘出来的图形自然也变动了

    x = data['x_value']

    y1 = data['total_1']

    y2 = data['total_2']

    # plt对象的cla方法: clear axes: 清除当前轴线(前面说过axes对象表示的是plt整个figure对象下面的一个绘图对象, 一个figure可以有多个axes, 其实就是当前正在绘图的实例).

    # 我们可以不清除当前的axes而沿用前面的axes, 但这样会产生每次绘出来的图形都有很大的变化(原因是重新绘制的时候,颜色,坐标等都重新绘制,可能不在同一个地方了,所以看上去会时刻变化).

    # 因此必须要清除当前axes对象,来重新绘制.

    plt.cla()

    plt.plot(x, y1, label='Channel 1')

    plt.plot(x, y2, label='Channel 2')

    plt.legend(loc='upper left')

    plt.tight_layout()

    # FuncAnimation可以根据给定的interval(时间间隔, ms为单位)一直重复调用某个函数来进行绘制, 从而模拟出实时数据的效果.

    ani = FuncAnimation(plt.gcf(), animate, interval=1000)

    plt.show()

    再介绍一个不需要每次都重置axes轴来绘制的方法(效果是一样的,只是稍微复杂点):

    axes对象的lines方法可以获取到当前绘制图象中所有不同数据集绘制出图象的实例:

    image.png

    在上面这个图里面,每个line对象代表了”图中的一根线“。然后根据它们动态调整数据,源码稍微有点复杂,但最终获得的效果和前面的例子是一样的。

    源码:

    # coding=utf-8

    # Another way to do it without clearing the Axis

    from itertools import count

    import pandas as pd

    import matplotlib.pyplot as plt

    from matplotlib.animation import FuncAnimation

    plt.style.use('fivethirtyeight')

    x_vals = []

    y_vals = []

    plt.plot([], [], label='Channel 1')

    plt.plot([], [], label='Channel 2')

    def animate(i):

    data = pd.read_csv('data_6.csv')

    x = data['x_value']

    y1 = data['total_1']

    y2 = data['total_2']

    # plt对象的get current axex方法, 返回当前绘制对象的axes轴(也相当于一个plt,它有plt对象几乎所有方法,可以直接像plt对象一样使用它)

    ax = plt.gca()

    # 获得当前绘制的两条曲线对象

    line1, line2 = ax.lines

    # 分别给两条曲线设置其对应的数据(动态刷新)

    line1.set_data(x, y1)

    line2.set_data(x, y2)

    # 分别求出两条曲线在x,y方向上的最小值和最大值

    xlim_low, xlim_high = ax.get_xlim()

    ylim_low, ylim_high = ax.get_ylim()

    # 重新设置当前最小的x和最大的x(沿着横轴最左端和最右端的数据)--> 横轴最左端(起点)不动,而将横轴最右端的数据增加5也就是实现动态向后移动

    ax.set_xlim(xlim_low, (x.max() + 5))

    # 获得动态生成数据集数据的当前最大值

    y1max = y1.max()

    y2max = y2.max()

    # 用一个临时变量保存动态生成数据集两列下所有数据的最大值

    current_ymax = y1max if (y1max > y2max) else y2max

    # 获得动态生成数据集数据的当前最小值

    y1min = y1.min()

    y2min = y2.min()

    # 用一个临时变量保存动态生成数据集两列下所有数据的最小值

    current_ymin = y1min if (y1min < y2min) else y2min

    # 重新设置当前最小的y和最大的y(沿着竖轴最下端和最上端的数据)--> 分别将竖轴最下端的数据和最上端的数据设置成动态数据集中当前最大的数据+5和最小的数据-5(有点难理解....)

    ax.set_ylim((current_ymin - 5), (current_ymax + 5))

    # FuncAnimation方法可以在间隔interval时间(ms)后重复运行某个函数(该函数必须要有一个参数,表示的是当前运行该函数第几次了)

    ani = FuncAnimation(plt.gcf(), animate, interval=1000)

    plt.legend()

    plt.tight_layout()

    plt.show()

    展开全文
  • 冲击载荷下柔性储液罐动态响应数值模拟及规律分析,曹源,金先龙,本文通过数值方法研究了柔性储液罐冲击响应规律。针对柔性材料大变形、非线性等特点,依据实际物理实验,采用ALE有限元方法模拟
  • 数据库连接池:说白了就是在一个池子中(容器)中放了很多的...这个集合的特点是增删快,查询慢。自定义一个数据库连接池的步骤:1.自定义一个类实现DataSource接口。2.定义一个List&lt;Connection&gt; li...

    数据库连接池:说白了就是在一个池子中(容器)中放了很多的数据库连接,当用户需要的时候就从中取出来一个用,用完了就放回连接池中。

    优点:极大的提高了数据库的效率。

    对于自定义的数据库连接池我们使用一个LinkedList做数据库连接池.这个集合的特点是增删快,查询慢。

    自定义一个数据库连接池的步骤:

    1.自定义一个类实现DataSource接口。

    2.定义一个List<Connection> list=new LinkedList<Connection>();存放数据库连接。

    3.初始化数据库连接池。就是在静态代码块static块中添加数据库的连接若干到list中(连接池)。

    4.写一个方法returnPool(Connection con)还回到数据库连接池。如果数据库连接池中没有数据连接可用,就添加若干个带数据库连接池list中。

    5.在getConnection()方法中动态代理重新改造Connection的close方法,因为我们不能够用完了就关闭,而是应该用完了还回数据库连接池中。这样可以循环使用。

    案例:

    自定义自己的数据库连接池MyPool

    package com.itheima.mypools;
    
    import java.io.PrintWriter;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.SQLException;
    import java.sql.SQLFeatureNotSupportedException;
    import java.util.LinkedList;
    import java.util.List;
    import java.util.logging.Logger;
    
    import javax.sql.DataSource;
    
    public class MyPool implements DataSource{
    	//1.需要一个容器存放连接
    	private static List<Connection> list=new LinkedList<Connection>();
    	//初始化加载类的时候就在连接池中添加了5个连接
    	static{
    		try {
    			//2.加载驱动
    			Class.forName("com.mysql.jdbc.Driver");
    			for(int i=0;i<5;i++){
    				//3.获取连接
    				Connection con=DriverManager.getConnection("jdbc:mysql://localhost:3306/day11", "root", "169500");
    				list.add(con);
    			}
    		}catch (Exception e) {
    			e.printStackTrace();
    			throw new RuntimeException();
    		}
    	}
    	@Override
    	public Connection getConnection() throws SQLException {
    		//如果连接池的数据没有了就添加3个连接到连接池中
    		if(list.size()==0){
    			for(int i=0;i<3;i++){
    				Connection con=DriverManager.getConnection("jdbc:mysql://localhost:3306/day11", "root", "169500");
    				list.add(con);
    			}
    		}
    		//获取连接,就是把连接池中的连接remove不是get...
    		final Connection con = list.remove(0);
    		
    		//需要重写连接的close()方法,我们使用动态代理
    		Connection proxy=(Connection) Proxy.newProxyInstance(con.getClass().getClassLoader(), con.getClass().getInterfaces(), new InvocationHandler() {
    			@Override
    			public Object invoke(Object proxy, Method method, Object[] args)//proxy代理类对象 method被代理类的方法,args被代理类方法的参数
    					throws Throwable {
    				if("close".equals(method.getName())){
    					//要改造的方法就还回连接池
    					returnPool(con);
    					return null;
    				}else{
    					//不想改造的方法就使用被代理者对象的方法
    					return method.invoke(con, args);
    				}
    			}
    		});
    		System.out.println("获取了一个连接,连接池中还有:"+list.size()+"个连接");
    		return proxy;
    	}
    	public void returnPool(Connection con){
    		try {
    			if(con!=null&&!con.isClosed()){
    				list.add(con);
    			}
    		} catch (SQLException e) {
    			e.printStackTrace();
    		}
    		System.out.println("还回了一个连接,池里还剩余"+list.size()+"个连接");
    	}
    	@Override
    	public PrintWriter getLogWriter() throws SQLException {
    		// TODO Auto-generated method stub
    		return null;
    	}
    
    	@Override
    	public void setLogWriter(PrintWriter out) throws SQLException {
    		// TODO Auto-generated method stub
    		
    	}
    
    	@Override
    	public void setLoginTimeout(int seconds) throws SQLException {
    		// TODO Auto-generated method stub
    		
    	}
    
    	@Override
    	public int getLoginTimeout() throws SQLException {
    		// TODO Auto-generated method stub
    		return 0;
    	}
    
    	@Override
    	public Logger getParentLogger() throws SQLFeatureNotSupportedException {
    		// TODO Auto-generated method stub
    		return null;
    	}
    
    	@Override
    	public <T> T unwrap(Class<T> iface) throws SQLException {
    		// TODO Auto-generated method stub
    		return null;
    	}
    
    	@Override
    	public boolean isWrapperFor(Class<?> iface) throws SQLException {
    		// TODO Auto-generated method stub
    		return false;
    	}
    
    	@Override
    	public Connection getConnection(String username, String password)
    			throws SQLException {
    		// TODO Auto-generated method stub
    		return null;
    	}
    	
    }
    
    使用这个自己定义的数据库连接池:

    package com.itheima.mypools;
    
    import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    
    import cn.itheima.utils.JDBCUtils;
    
    public class JDBCDemo1 {
    	public static void main(String[] args) {
    		Connection con=null;
    		PreparedStatement ps=null;
    		ResultSet rs=null;
    		try {
    			//使用数据库连接池
    			MyPool pool=new MyPool();
    			//获取连接
    			con=pool.getConnection();
    			ps=con.prepareStatement("select * from account");
    			rs=ps.executeQuery();
    			while(rs.next()){
    				String name=rs.getString("name");
    				System.out.println(name);
    			}
    		} catch (SQLException e) {
    			e.printStackTrace();
    			throw new RuntimeException();
    		}finally{
    			//这里的close已经被更该过了
    			if(rs!=null){
    				try {
    					rs.close();
    				} catch (SQLException e) {
    					e.printStackTrace();
    				}finally{
    					rs=null;
    				}
    			}
    			if(ps!=null){
    				try {
    					ps.close();
    				} catch (SQLException e) {
    					e.printStackTrace();
    				}finally{
    					ps=null;
    				}
    			}
    			if(con!=null){
    				try {
    					con.close();
    				} catch (SQLException e) {
    					e.printStackTrace();
    				}finally{
    					con=null;
    				}
    			}
    		}
    		
    	}
    }
    
    运行结果:






    展开全文
  • 该四VGA以及10bit和12bit的四ADC还具有串行的低电压差分信号(LVDS)数据输出的特点,因为能在给定的印制电路板(PCB)面积中实现更多的数据转换通道,从而简化了PCB布线并且进一步提高了图像质量。 高级超声设备必需...
  • :dashing_away: 抽烟 具有记录功能简单但功能强大基于文件模拟服务器 只需将一堆(JSON)文件放入一个... 但是,它几乎在任何情况下都支持许多高级功能和动态模拟: 通过记录来自现有服务器响应来快速生成模
  •  录音模拟唱片时,如图1所示S/N、动态范围,强调高频域频率。此时强调特性由RIAA(Recording Industry Associ-at1on of America)规格决定,唱片再生时由于强调信号已复原,所以可通过均衡电路使
  • java的动态代理

    2016-08-25 12:29:31
    本文通过分析 Java 动态代理机制和特点,解读动态代理类源代码,并且模拟推演了动态代理类可能实现,向读者阐述了一个完整 Java 动态代理运作过程,希望能帮助读者加深对 Java 动态代理理解和应用。...

    Java 动态代理机制分析及扩展,第 1 部分

    本文通过分析 Java 动态代理的机制和特点,解读动态代理类的源代码,并且模拟推演了动态代理类的可能实现,向读者阐述了一个完整的 Java 动态代理运作过程,希望能帮助读者加深对 Java 动态代理的理解和应用。

    王 忠平, 软件工程师, IBM

    何 平, 软件工程师, IBM

    2010 年 1 月 21 日

    • expand内容

    引言

    Java 动态代理机制的出现,使得 Java 开发人员不用手工编写代理类,只要简单地指定一组接口及委托类对象,便能动态地获得代理类。代理类会负责将所有的方法调用分派到委托对象上反射执行,在分派执行的过程中,开发人员还可以按需调整委托类对象及其功能,这是一套非常灵活有弹性的代理框架。通过阅读本文,读者将会对 Java 动态代理机制有更加深入的理解。本文首先从 Java 动态代理的运行机制和特点出发,对其代码进行了分析,推演了动态生成类的内部实现。

    代理:设计模式

    代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。

    图 1. 代理模式
    图 1. 代理模式

    为了保持行为的一致性,代理类和委托类通常会实现相同的接口,所以在访问者看来两者没有丝毫的区别。通过代理类这中间一层,能有效控制对委托类对象的直接访问,也可以很好地隐藏和保护委托类对象,同时也为实施不同控制策略预留了空间,从而在设计上获得了更大的灵活性。Java 动态代理机制以巧妙的方式近乎完美地实践了代理模式的设计理念。

    相关的类和接口

    要了解 Java 动态代理的机制,首先需要了解以下相关的类或接口:

    • java.lang.reflect.Proxy:这是 Java 动态代理机制的主类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。
      清单 1. Proxy 的静态方法
      // 方法 1: 该方法用于获取指定代理对象所关联的调用处理器
      static InvocationHandler getInvocationHandler(Object proxy) 
      
      // 方法 2:该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象
      static Class getProxyClass(ClassLoader loader, Class[] interfaces) 
      
      // 方法 3:该方法用于判断指定类对象是否是一个动态代理类
      static boolean isProxyClass(Class cl) 
      
      // 方法 4:该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
      static Object newProxyInstance(ClassLoader loader, Class[] interfaces, 
          InvocationHandler h)
    • java.lang.reflect.InvocationHandler:这是调用处理器接口,它自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。
      清单 2. InvocationHandler 的核心方法
      // 该方法负责集中处理动态代理类上的所有方法调用。第一个参数既是代理类实例,第二个参数是被调用的方法对象
      // 第三个方法是调用参数。调用处理器根据这三个参数进行预处理或分派到委托类实例上发射执行
      Object invoke(Object proxy, Method method, Object[] args)

      每次生成动态代理类对象时都需要指定一个实现了该接口的调用处理器对象(参见 Proxy 静态方法 4 的第三个参数)。

    • java.lang.ClassLoader:这是类装载器类,负责将类的字节码装载到 Java 虚拟机(JVM)中并为其定义类对象,然后该类才能被使用。Proxy 静态方法生成动态代理类同样需要通过类装载器来进行装载才能使用,它与普通类的唯一区别就是其字节码是由 JVM 在运行时动态生成的而非预存在于任何一个 .class 文件中。

      每次生成动态代理类对象时都需要指定一个类装载器对象(参见 Proxy 静态方法 4 的第一个参数)

    代理机制及其特点

    首先让我们来了解一下如何使用 Java 动态代理。具体有如下四步骤:

    1. 通过实现 InvocationHandler 接口创建自己的调用处理器;
    2. 通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类;
    3. 通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;
    4. 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。
    清单 3. 动态代理对象创建过程
    // InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
    // 其内部通常包含指向委托类实例的引用,用于真正执行分派转发过来的方法调用
    InvocationHandler handler = new InvocationHandlerImpl(..); 
    
    // 通过 Proxy 为包括 Interface 接口在内的一组接口动态创建代理类的类对象
    Class clazz = Proxy.getProxyClass(classLoader, new Class[] { Interface.class, ... }); 
    
    // 通过反射从生成的类对象获得构造函数对象
    Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class }); 
    
    // 通过构造函数对象创建动态代理类实例
    Interface Proxy = (Interface)constructor.newInstance(new Object[] { handler });

    实际使用过程更加简单,因为 Proxy 的静态方法 newProxyInstance 已经为我们封装了步骤 2 到步骤 4 的过程,所以简化后的过程如下

    清单 4. 简化的动态代理对象创建过程
    // InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
    InvocationHandler handler = new InvocationHandlerImpl(..); 
    
    // 通过 Proxy 直接创建动态代理类实例
    Interface proxy = (Interface)Proxy.newProxyInstance( classLoader, 
    	 new Class[] { Interface.class }, 
    	 handler );

    接下来让我们来了解一下 Java 动态代理机制的一些特点。

    首先是动态生成的代理类本身的一些特点。1)包:如果所代理的接口都是 public 的,那么它将被定义在顶层包(即包路径为空),如果所代理的接口中有非 public 的接口(因为接口不能被定义为 protect 或 private,所以除 public 之外就是默认的 package 访问级别),那么它将被定义在该接口所在包(假设代理了 com.ibm.developerworks 包中的某非 public 接口 A,那么新生成的代理类所在的包就是 com.ibm.developerworks),这样设计的目的是为了最大程度的保证动态代理类不会因为包管理的问题而无法被成功定义并访问;2)类修饰符:该代理类具有 final 和 public 修饰符,意味着它可以被所有的类访问,但是不能被再度继承;3)类名:格式是“$ProxyN”,其中 N 是一个逐一递增的阿拉伯数字,代表 Proxy 类第 N 次生成的动态代理类,值得注意的一点是,并不是每次调用 Proxy 的静态方法创建动态代理类都会使得 N 值增加,原因是如果对同一组接口(包括接口排列的顺序相同)试图重复创建动态代理类,它会很聪明地返回先前已经创建好的代理类的类对象,而不会再尝试去创建一个全新的代理类,这样可以节省不必要的代码重复生成,提高了代理类的创建效率。4)类继承关系:该类的继承关系如图:

    图 2. 动态代理类的继承图
    图 2. 动态代理类的继承图

    由图可见,Proxy 类是它的父类,这个规则适用于所有由 Proxy 创建的动态代理类。而且该类还实现了其所代理的一组接口,这就是为什么它能够被安全地类型转换到其所代理的某接口的根本原因。

    接下来让我们了解一下代理类实例的一些特点。每个实例都会关联一个调用处理器对象,可以通过 Proxy 提供的静态方法 getInvocationHandler 去获得代理类实例的调用处理器对象。在代理类实例上调用其代理的接口中所声明的方法时,这些方法最终都会由调用处理器的 invoke 方法执行,此外,值得注意的是,代理类的根类 java.lang.Object 中有三个方法也同样会被分派到调用处理器的 invoke 方法执行,它们是 hashCode,equals 和 toString,可能的原因有:一是因为这些方法为 public 且非 final 类型,能够被代理类覆盖;二是因为这些方法往往呈现出一个类的某种特征属性,具有一定的区分度,所以为了保证代理类与委托类对外的一致性,这三个方法也应该被分派到委托类执行。当代理的一组接口有重复声明的方法且该方法被调用时,代理类总是从排在最前面的接口中获取方法对象并分派给调用处理器,而无论代理类实例是否正在以该接口(或继承于该接口的某子接口)的形式被外部引用,因为在代理类内部无法区分其当前的被引用类型。

    接着来了解一下被代理的一组接口有哪些特点。首先,要注意不能有重复的接口,以避免动态代理类代码生成时的编译错误。其次,这些接口对于类装载器必须可见,否则类装载器将无法链接它们,将会导致类定义失败。再次,需被代理的所有非 public 的接口必须在同一个包中,否则代理类生成也会失败。最后,接口的数目不能超过 65535,这是 JVM 设定的限制。

    最后再来了解一下异常处理方面的特点。从调用处理器接口声明的方法中可以看到理论上它能够抛出任何类型的异常,因为所有的异常都继承于 Throwable 接口,但事实是否如此呢?答案是否定的,原因是我们必须遵守一个继承原则:即子类覆盖父类或实现父接口的方法时,抛出的异常必须在原方法支持的异常列表之内。所以虽然调用处理器理论上讲能够,但实际上往往受限制,除非父接口中的方法支持抛 Throwable 异常。那么如果在 invoke 方法中的确产生了接口方法声明中不支持的异常,那将如何呢?放心,Java 动态代理类已经为我们设计好了解决方法:它将会抛出 UndeclaredThrowableException 异常。这个异常是一个 RuntimeException 类型,所以不会引起编译错误。通过该异常的 getCause 方法,还可以获得原来那个不受支持的异常对象,以便于错误诊断。

    代码是最好的老师

    机制和特点都介绍过了,接下来让我们通过源代码来了解一下 Proxy 到底是如何实现的。

    首先记住 Proxy 的几个重要的静态变量:

    清单 5. Proxy 的重要静态变量
    // 映射表:用于维护类装载器对象到其对应的代理类缓存
    private static Map loaderToCache = new WeakHashMap(); 
    
    // 标记:用于标记一个动态代理类正在被创建中
    private static Object pendingGenerationMarker = new Object(); 
    
    // 同步表:记录已经被创建的动态代理类类型,主要被方法 isProxyClass 进行相关的判断
    private static Map proxyClasses = Collections.synchronizedMap(new WeakHashMap()); 
    
    // 关联的调用处理器引用
    protected InvocationHandler h;

    然后,来看一下 Proxy 的构造方法:

    清单 6. Proxy 构造方法
    // 由于 Proxy 内部从不直接调用构造函数,所以 private 类型意味着禁止任何调用
    private Proxy() {} 
    
    // 由于 Proxy 内部从不直接调用构造函数,所以 protected 意味着只有子类可以调用
    protected Proxy(InvocationHandler h) {this.h = h;}

    接着,可以快速浏览一下 newProxyInstance 方法,因为其相当简单:

    清单 7. Proxy 静态方法 newProxyInstance
    public static Object newProxyInstance(ClassLoader loader, 
                Class<?>[] interfaces, 
                InvocationHandler h) 
                throws IllegalArgumentException { 
        
        // 检查 h 不为空,否则抛异常
        if (h == null) { 
            throw new NullPointerException(); 
        } 
    
        // 获得与制定类装载器和一组接口相关的代理类类型对象
        Class cl = getProxyClass(loader, interfaces); 
    
        // 通过反射获取构造函数对象并生成代理类实例
        try { 
            Constructor cons = cl.getConstructor(constructorParams); 
            return (Object) cons.newInstance(new Object[] { h }); 
        } catch (NoSuchMethodException e) { throw new InternalError(e.toString()); 
        } catch (IllegalAccessException e) { throw new InternalError(e.toString()); 
        } catch (InstantiationException e) { throw new InternalError(e.toString()); 
        } catch (InvocationTargetException e) { throw new InternalError(e.toString()); 
        } 
    }

    由此可见,动态代理真正的关键是在 getProxyClass 方法,该方法负责为一组接口动态地生成代理类类型对象。在该方法内部,您将能看到 Proxy 内的各路英雄(静态变量)悉数登场。有点迫不及待了么?那就让我们一起走进 Proxy 最最神秘的殿堂去欣赏一番吧。该方法总共可以分为四个步骤:

    1. 对这组接口进行一定程度的安全检查,包括检查接口类对象是否对类装载器可见并且与类装载器所能识别的接口类对象是完全相同的,还会检查确保是 interface 类型而不是 class 类型。这个步骤通过一个循环来完成,检查通过后将会得到一个包含所有接口名称的字符串数组,记为 String[] interfaceNames。总体上这部分实现比较直观,所以略去大部分代码,仅保留留如何判断某类或接口是否对特定类装载器可见的相关代码。
      清单 8. 通过 Class.forName 方法判接口的可见性
      try { 
          // 指定接口名字、类装载器对象,同时制定 initializeBoolean 为 false 表示无须初始化类
          // 如果方法返回正常这表示可见,否则会抛出 ClassNotFoundException 异常表示不可见
          interfaceClass = Class.forName(interfaceName, false, loader); 
      } catch (ClassNotFoundException e) { 
      }
    2. 从 loaderToCache 映射表中获取以类装载器对象为关键字所对应的缓存表,如果不存在就创建一个新的缓存表并更新到 loaderToCache。缓存表是一个 HashMap 实例,正常情况下它将存放键值对(接口名字列表,动态生成的代理类的类对象引用)。当代理类正在被创建时它会临时保存(接口名字列表,pendingGenerationMarker)。标记 pendingGenerationMarke 的作用是通知后续的同类请求(接口数组相同且组内接口排列顺序也相同)代理类正在被创建,请保持等待直至创建完成。
      清单 9. 缓存表的使用
      do { 
          // 以接口名字列表作为关键字获得对应 cache 值
          Object value = cache.get(key); 
          if (value instanceof Reference) { 
              proxyClass = (Class) ((Reference) value).get(); 
          } 
          if (proxyClass != null) { 
              // 如果已经创建,直接返回
              return proxyClass; 
          } else if (value == pendingGenerationMarker) { 
              // 代理类正在被创建,保持等待
              try { 
                  cache.wait(); 
              } catch (InterruptedException e) { 
              } 
              // 等待被唤醒,继续循环并通过二次检查以确保创建完成,否则重新等待
              continue; 
          } else { 
              // 标记代理类正在被创建
              cache.put(key, pendingGenerationMarker); 
              // break 跳出循环已进入创建过程
              break; 
      } while (true);
    3. 动态创建代理类的类对象。首先是确定代理类所在的包,其原则如前所述,如果都为 public 接口,则包名为空字符串表示顶层包;如果所有非 public 接口都在同一个包,则包名与这些接口的包名相同;如果有多个非 public 接口且不同包,则抛异常终止代理类的生成。确定了包后,就开始生成代理类的类名,同样如前所述按格式“$ProxyN”生成。类名也确定了,接下来就是见证奇迹的发生 —— 动态生成代理类:
      清单 10. 动态生成代理类
      // 动态地生成代理类的字节码数组
      byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces); 
      try { 
          // 动态地定义新生成的代理类
          proxyClass = defineClass0(loader, proxyName, proxyClassFile, 0, 
              proxyClassFile.length); 
      } catch (ClassFormatError e) { 
          throw new IllegalArgumentException(e.toString()); 
      } 
      
      // 把生成的代理类的类对象记录进 proxyClasses 表
      proxyClasses.put(proxyClass, null);

      由此可见,所有的代码生成的工作都由神秘的 ProxyGenerator 所完成了,当你尝试去探索这个类时,你所能获得的信息仅仅是它位于并未公开的 sun.misc 包,有若干常量、变量和方法以完成这个神奇的代码生成的过程,但是 sun 并没有提供源代码以供研读。至于动态类的定义,则由 Proxy 的 native 静态方法 defineClass0 执行。

    4. 代码生成过程进入结尾部分,根据结果更新缓存表,如果成功则将代理类的类对象引用更新进缓存表,否则清楚缓存表中对应关键值,最后唤醒所有可能的正在等待的线程。

    走完了以上四个步骤后,至此,所有的代理类生成细节都已介绍完毕,剩下的静态方法如 getInvocationHandler 和 isProxyClass 就显得如此的直观,只需通过查询相关变量就可以完成,所以对其的代码分析就省略了。

    代理类实现推演

    分析了 Proxy 类的源代码,相信在读者的脑海中会对 Java 动态代理机制形成一个更加清晰的理解,但是,当探索之旅在 sun.misc.ProxyGenerator 类处嘎然而止,所有的神秘都汇聚于此时,相信不少读者也会对这个 ProxyGenerator 类产生有类似的疑惑:它到底做了什么呢?它是如何生成动态代理类的代码的呢?诚然,这里也无法给出确切的答案。还是让我们带着这些疑惑,一起开始探索之旅吧。

    事物往往不像其看起来的复杂,需要的是我们能够化繁为简,这样也许就能有更多拨云见日的机会。抛开所有想象中的未知而复杂的神秘因素,如果让我们用最简单的方法去实现一个代理类,唯一的要求是同样结合调用处理器实施方法的分派转发,您的第一反应将是什么呢?“听起来似乎并不是很复杂”。的确,掐指算算所涉及的工作无非包括几个反射调用,以及对原始类型数据的装箱或拆箱过程,其他的似乎都已经水到渠成。非常地好,让我们整理一下思绪,一起来完成一次完整的推演过程吧。

    清单 11. 代理类中方法调用的分派转发推演实现
    // 假设需代理接口 Simulator 
    public interface Simulator { 
        short simulate(int arg1, long arg2, String arg3) throws ExceptionA, ExceptionB;
    } 
    
    // 假设代理类为 SimulatorProxy, 其类声明将如下
    final public class SimulatorProxy implements Simulator { 
        
        // 调用处理器对象的引用
        protected InvocationHandler handler; 
        
        // 以调用处理器为参数的构造函数
        public SimulatorProxy(InvocationHandler handler){ 
            this.handler = handler; 
        } 
        
        // 实现接口方法 simulate 
        public short simulate(int arg1, long arg2, String arg3) 
            throws ExceptionA, ExceptionB {
    
            // 第一步是获取 simulate 方法的 Method 对象
            java.lang.reflect.Method method = null; 
            try{ 
                method = Simulator.class.getMethod( 
                    "simulate", 
                    new Class[] {int.class, long.class, String.class} );
            } catch(Exception e) { 
                // 异常处理 1(略)
            } 
            
            // 第二步是调用 handler 的 invoke 方法分派转发方法调用
            Object r = null; 
            try { 
                r = handler.invoke(this, 
                    method, 
                    // 对于原始类型参数需要进行装箱操作
                    new Object[] {new Integer(arg1), new Long(arg2), arg3});
            }catch(Throwable e) { 
                // 异常处理 2(略)
            } 
            // 第三步是返回结果(返回类型是原始类型则需要进行拆箱操作)
            return ((Short)r).shortValue();
        } 
    }

    模拟推演为了突出通用逻辑所以更多地关注正常流程,而淡化了错误处理,但在实际中错误处理同样非常重要。从以上的推演中我们可以得出一个非常通用的结构化流程:第一步从代理接口获取被调用的方法对象,第二步分派方法到调用处理器执行,第三步返回结果。在这之中,所有的信息都是可以已知的,比如接口名、方法名、参数类型、返回类型以及所需的装箱和拆箱操作,那么既然我们手工编写是如此,那又有什么理由不相信 ProxyGenerator 不会做类似的实现呢?至少这是一种比较可能的实现。

    接下来让我们把注意力重新回到先前被淡化的错误处理上来。在异常处理 1 处,由于我们有理由确保所有的信息如接口名、方法名和参数类型都准确无误,所以这部分异常发生的概率基本为零,所以基本可以忽略。而异常处理 2 处,我们需要思考得更多一些。回想一下,接口方法可能声明支持一个异常列表,而调用处理器 invoke 方法又可能抛出与接口方法不支持的异常,再回想一下先前提及的 Java 动态代理的关于异常处理的特点,对于不支持的异常,必须抛 UndeclaredThrowableException 运行时异常。所以通过再次推演,我们可以得出一个更加清晰的异常处理 2 的情况:

    清单 12. 细化的异常处理 2
    Object r = null; 
    
    try { 
        r = handler.invoke(this, 
            method, 
            new Object[] {new Integer(arg1), new Long(arg2), arg3}); 
    
    } catch( ExceptionA e) { 
    
        // 接口方法支持 ExceptionA,可以抛出
        throw e; 
    
    } catch( ExceptionB e ) { 
        // 接口方法支持 ExceptionB,可以抛出
        throw e; 
    
    } catch(Throwable e) { 
        // 其他不支持的异常,一律抛 UndeclaredThrowableException 
        throw new UndeclaredThrowableException(e); 
    }

    这样我们就完成了对动态代理类的推演实现。推演实现遵循了一个相对固定的模式,可以适用于任意定义的任何接口,而且代码生成所需的信息都是可知的,那么有理由相信即使是机器自动编写的代码也有可能延续这样的风格,至少可以保证这是可行的。

    美中不足

    诚然,Proxy 已经设计得非常优美,但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持 interface 代理的桎梏,因为它的设计注定了这个遗憾。回想一下那些动态生成的代理类的继承关系图,它们已经注定有一个共同的父类叫 Proxy。Java 的继承机制注定了这些动态代理类们无法实现对 class 的动态代理,原因是多继承在 Java 中本质上就行不通。

    有很多条理由,人们可以否定对 class 代理的必要性,但是同样有一些理由,相信支持 class 动态代理会更美好。接口和类的划分,本就不是很明显,只是到了 Java 中才变得如此的细化。如果只从方法的声明及是否被定义来考量,有一种两者的混合体,它的名字叫抽象类。实现对抽象类的动态代理,相信也有其内在的价值。此外,还有一些历史遗留的类,它们将因为没有实现任何接口而从此与动态代理永世无缘。如此种种,不得不说是一个小小的遗憾。

    但是,不完美并不等于不伟大,伟大是一种本质,Java 动态代理就是佐例。

    展开全文
  • 模拟信号自动追踪控制器是空间科学实验载荷研制过程中必不可少的地面测试器.在地面检测、匹配实验、验收测试各项试验中,发挥着至关重要的作用...实际应用表明,该系统具有操作简便、测试准确的特点,达到了设计要求。
  • 以树叶凋落的生理学原理为依据,提出了一种树叶凋落快速模拟的方法。该方法首先采用交互式编辑确定叶凋落节律,由气象要素进行局部调整得到叶凋落动态。此外,考虑叶龄和风力对落叶的激励诱导作用,显著标识了树体上...
  • 面向特定领域构建适应底层计算资源特点的工具库,抽取针对Julia语言计算原语;程序员通过Julia语言实现原语,动态选取领域工具,适应运行时限约束。结合图像对象识别案例,构建Colored Petri Net模型,借助CPN Tools工具...
  • 根据基于DMD的红外景象模拟系统的要求,分析了红外投影光学系统的特点,确定了其技术指标。从初始结构选取入手,利用像差平衡理论和非球面技术,针对DLP5500 DMD(1024 pixel×768 pixel),设计出一种成像质量高、性...
  • 模拟一个消费者 public class Client { public static void main(String[] args) { final Producer producer = new Producer(); /** * 动态代理: * 特点:字节码随用随创建,随用随加载 * 作用:不修改源码...

    模拟一个消费者
    public class Client {

    public static void main(String[] args) {
        final Producer producer = new Producer();
    
        /**
         * 动态代理:
         *  特点:字节码随用随创建,随用随加载
         *  作用:不修改源码的基础上对方法增强
         *  分类:
         *      基于接口的动态代理
         *      基于子类的动态代理
         *  基于接口的动态代理:
         *      涉及的类:Proxy
         *      提供者:JDK官方
         *  如何创建代理对象:
         *      使用Proxy类中的newProxyInstance方法
         *  创建代理对象的要求:
         *      被代理类最少实现一个接口,如果没有则不能使用
         *  newProxyInstance方法的参数:
         *      ClassLoader:类加载器
         *          它是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法。
         *      Class[]:字节码数组
         *          它是用于让代理对象和被代理对象有相同方法。固定写法。
         *      InvocationHandler:用于提供增强的代码
         *          它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
         *          此接口的实现类都是谁用谁写。
         */
       IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),
                producer.getClass().getInterfaces(),
                new InvocationHandler() {
                    /**
                     * 作用:执行被代理对象的任何接口方法都会经过该方法
                     * 方法参数的含义
                     * @param proxy   代理对象的引用
                     * @param method  当前执行的方法
                     * @param args    当前执行方法所需的参数
                     * @return        和被代理对象方法有相同的返回值
                     * @throws Throwable
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //提供增强的代码
                        Object returnValue = null;
    
                        //1.获取方法执行的参数
                        Float money = (Float)args[0];
                        //2.判断当前方法是不是销售
                        if("saleProduct".equals(method.getName())) {
                            returnValue = method.invoke(producer, money*0.8f);
                        }
                        return returnValue;
                    }
                });
        proxyProducer.saleProduct(10000f);
    }
    

    }

    展开全文
  • /** 模拟一个消费者 public class Client { public static void main(String[] args) { final Producer producer = new Producer(); /** * 动态代理: * 特点:字节码随用随创建,随用随加载 * 作用:不修改...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 804
精华内容 321
关键字:

动态模拟的特点