【Java技术专题】「攻破技术盲区」攻破Java技术盲点之unsafe类的使用指南(打破Java的安全管控— sun.misc.unsafe)
Java后门机制 — sun.misc.unsafe
- 打破Java的安全管控
- 关于Unsafe的编程建议
- 实例化Unsafe后门对象
- 使用sun.misc.Unsafe
- 创建实例
- 单例模式处理
- 实现浅克隆(直接获取内存的方式)
- 直接使用copyMemory
- 原理分析
- 密码安全
- 使用`Unsafe`类—示例代码
- 运行时动态创建类
- 超大数组
- 总结概括
打破Java的安全管控
Java是一种安全而强大的开发工具,它能有效地防止许多低级错误,特别是与内存管理相关的错误。然而,在某些情况下,Unsafe类可以被用于一些高级开发需求,例如在底层内存操作和性能优化方面。Unsafe类确实具有许多强大的功能,如下图所示:
Unsafe类属于sun. API,但并不是J2SE的官方一部分,所以你可能很难找到官方文档进行参考,在开发过程中,我们建议开发者谨慎使用Unsafe类,并遵循Java的最佳实践。尽可能地使用官方支持的API和框架来完成开发任务。这样可以确保代码的安全性和可维护性,并降低潜在错误的风险。*。
关于Unsafe的编程建议
通过使用Unsafe类,开发人员可以直接操作内存,从而实现一些高级功能和性能优化。但是,使用Unsafe类需要非常谨慎,因为它可以绕过Java语言的安全机制,可能导致严重的安全漏洞和内存错误。为了确保安全性和可靠性,开发人员应该遵循Java的最佳实践,并尽量避免使用Unsafe类。
实例化Unsafe后门对象
我们来看一下sun.misc.Unsafe类的源码,如下图所示。
如果尝试创建sun.misc.Unsafe类的实例,是不被允许的,主要基于以下两个原因:
- Unsafe类的构造函数是私有的,无法直接实例化;
- 虽然Unsafe类提供了静态的getUnsafe()方法,但如果尝试调用Unsafe.getUnsafe(),会导致SecurityException异常。这是因为只有由JDK信任的类才能实例化Unsafe类。
然而,总会存在一些变通的解决办法,其中一个简单的方式是利用反射进行实例化,具体示例代码如下所示:
Field f = Unsafe.class.getDeclaredField("theUnsafe"); //Internal reference
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);
需要注意的是,IDE(如Eclipse)对于这样的用法可能会报错。不过,不用担心,你可以直接运行代码,它们应该可以正常执行,现在进入主题,使用这个对象我们可以做如下“有趣的”事情。
使用sun.misc.Unsafe
首先,让我们创建一个User类作为我们测试Unsafe操作的目标实体。
public class User {private String name;private int age;public User(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public int getAge() {return age;}public void setName(String name) {this.name = name;}public void setAge(int age) {this.age = age;}
}
以上是一个简单的User类,包含一个name属性和一个age属性,以及相应的getter和setter方法。这将作为我们接下来进行Unsafe操作的测试实体类。
创建实例
通过使用Unsafe类的allocateInstance()方法,我们可以创建一个类的实例,而无需调用其构造函数、初始化代码、JVM安全检查等底层操作。即使构造函数是私有的,我们也可以使用这个方法来创建实例。
public class UnsafeTest { public static Unsafe getUnsafe(){Field f = Unsafe.class.getDeclaredField("theUnsafe"); // Internal reference f.setAccessible(true); Unsafe unsafe = (Unsafe) f.get(null); }public static void main(String[] args) throws NoSuchFieldException, SecurityException, Illegal ArgumentException, IllegalAccessException, InstantiationException { User user = (User ) getUnsafe().allocateInstance(User.class); System.out.println(user.getAge()); // Print 0 user.setAge(45); // Let's now set age 45 to un-initialized object System.out.println(user.getAge()); // Print 45 }
}
在上述示例中,通过调用Unsafe类的allocateInstance()方法实例化了User类的对象。注意,我们并没有直接调用User类的构造函数,而是绕过了它。
注意,虽然使用allocateInstance()方法可以绕过构造函数的限制,但这意味着我们无法执行构造函数中的初始化逻辑。因此,必须谨慎使用此方法,并确保正确地初始化创建的对象。
单例模式处理
对于喜欢使用单例模式的程序员来说,这种方式可能会让他们感到头疼,因为它绕过了阻止此类调用的机制。让我们看一个实例。
public class Singleton {// 私有化构造函数,强制使用getInstance()方法获取实例private Singleton() {// 构造函数逻辑}private static Singleton instance;// 获取单例实例的方法public static Singleton getInstance() {if (instance == null) {try {// 使用Unsafe类的allocateInstance()方法创建实例Unsafe unsafe = Unsafe.getUnsafe();instance = (Singleton) unsafe.allocateInstance(Singleton.class);} catch (InstantiationException e) {e.printStackTrace();}}return instance;}// 其他方法和属性...
}
在上述示例中,展示了如何使用Unsafe类的allocateInstance()方法来创建单例模式的实例。请注意,我们绕过了私有构造函数,通过allocateInstance()方法创建了实例。
实现浅克隆(直接获取内存的方式)
对于浅克隆的实现方法,通常是在clone()
方法中调用super.clone()
来完成。然而,这种方式要求对象必须实现Cloneable
接口,并且在需要进行浅克隆的所有对象中都要实现clone()
方法。对于一些开发者来说,这可能会带来一定的工作量和复杂性。
直接使用copyMemory
copyMemory函数是一种低级别的内存复制方法,它可以按字节进行复制。
- 确定对象的大小:首先,你需要确定要克隆的对象的大小。这可以通过计算对象的字节数来完成。
- 创建目标对象:使用目标对象的构造函数创建一个新的对象。这个对象将是克隆对象的副本。
- 使用copyMemory进行复制:使用copyMemory函数将原始对象的内存数据复制到新创建的对象中。
public class TestCloneable {private static Unsafe getUnsafeInstance() throws IllegalAccessException, NoSuchFieldException {Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");theUnsafeField.setAccessible(true);return (Unsafe) theUnsafeField.get(null);}public static void main(String[] args) throws Exception {// 使用Unsafe类进行浅克隆User originalPerson = new User("name",12);Unsafe unsafe = getUnsafeInstance();User clonedPersonUnsafe = (User) unsafe.allocateInstance(User.class);// 获取对象的起始地址long srcAddress = unsafe.objectFieldOffset(User.class.getDeclaredField("name"));// 获取对象的大小 int类型4个字节。long objectSize = srcAddress + 4; // 分配新的内存空间 long clonedObjectAddress = unsafe.allocateMemory(objectSize);// 执行内存复制操作unsafe.copyMemory(originalPerson , srcAddress, clonedPersonUnsafe , clonedObjectAddress, objectSize ); System.out.println("Cloned User (Unsafe): " + clonedPersonUnsafe);}
}
原理分析
unsafe.copyMemory() 方法是 sun.misc.Unsafe 类中用于在内存中复制数据的方法。它的参数如下:
这个方法用于在内存中直接复制数据,可以用于将一个对象的字节数据复制到另一个对象的内存位置,然后将这个对象转换为需要被克隆的对象类型。
注意,在使用Unsafe类进行对象克隆时,需要特别谨慎,并确保了解其带来的潜在风险。而在实际开发中,为了代码的可读性和可维护性,我们通常建议使用传统的
clone()
方法或者其他官方支持的克隆方式。
密码安全
开发人员通常会将密码存储在字符串中,并在应用程序中使用这些密码。使用完成后,一些聪明的程序员会将字符串引用设为null
,以使其不再被引用,从而容易被垃圾收集器回收。
-
问题分析:在将引用设为
null
到垃圾收集器实际回收之间的时间段内,该字符串可能仍存在于字符串池中。在这段时间内,虽然机会很小,但仍有可能通过复杂的攻击方式读取到内存区域并获取密码。 -
解决方案:为了解决这个问题,建议使用
char[]
数组来存储密码。使用完毕后,你可以迭代处理当前数组,修改/清空这些字符,从而防止密码被泄露。
使用Unsafe
类—示例代码
当处理敏感数据如密码时,使用char[]数组是一种更安全的方式。以下是一个完善的案例,演示了如何使用char[]数组来存储和处理密码:
public static void main(String[] args) throws Exception {String password = "l00k@myHor$e";String fake = password.replaceAll(".", "?");System.out.println("Original password: " + password);System.out.println("Fake password: " + fake);Unsafe unsafe = getUnsafeInstance();unsafe.copyMemory(fake, 0L, null, toAddress(password), sizeOf(password));System.out.println("Password after overwrite: " + password);System.out.println("Fake password after overwrite: " + fake);
}private static long toAddress(Object object) throws NoSuchFieldException, IllegalAccessException {Unsafe unsafe = getUnsafeInstance();Object[] array = new Object[] { object };long offset = unsafe.arrayBaseOffset(Object[].class);return unsafe.getLong(array, offset);
}private static int sizeOf(Object object) throws NoSuchFieldException, IllegalAccessException {Unsafe unsafe = getUnsafeInstance();return (int) (unsafe.getAddress(toAddress(object) + 8));
}private static Unsafe getUnsafeInstance() throws NoSuchFieldException, IllegalAccessException {Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");theUnsafeField.setAccessible(true);return (Unsafe) theUnsafeField.get(null);
}
运行时动态创建类
通过使用sun.misc.Unsafe类的defineClass()方法,可以在运行时动态地创建类。这种方式允许我们将一个字节数组(如编译后的.class文件)转换为一个Java类的实例。
下面是一个简单的示例,演示了如何通过sun.misc.Unsafe类动态加载和创建类:
import sun.misc.Unsafe;
import java.lang.reflect.Field;
public class DynamicClassCreationExample {public static void main(String[] args) throws Exception {// 获取Unsafe实例Unsafe unsafe = getUnsafeInstance();// 读取.class文件并保存为字节数组byte[] classBytes = readClassBytes("DynamicClassToBeCreated.class");// 动态创建类Class<?> dynamicClass = unsafe.defineClass(null, classBytes, 0, classBytes.length,DynamicClassCreationExample.class.getClassLoader(), null);// 使用动态创建的类Object instance = dynamicClass.getDeclaredConstructor().newInstance();System.out.println(instance.getClass().getName()); // 输出:DynamicClassToBeCreated}private static byte[] readClassBytes(String className) throws IOException {InputStream inputStream = DynamicClassCreationExample.class.getClassLoader().getResourceAsStream(className);ByteArrayOutputStream outputStream = new ByteArrayOutputStream();byte[] buffer = new byte[1024];int length;while ((length = inputStream.read(buffer)) != -1) {outputStream.write(buffer, 0, length);}return outputStream.toByteArray();}private static Unsafe getUnsafeInstance() throws IllegalAccessException, NoSuchFieldException {Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");theUnsafeField.setAccessible(true);return (Unsafe) theUnsafeField.get(null);}
}
在上述示例中,我们首先获取sun.misc.Unsafe实例,并将编译后的.class文件读取为字节数组。然后,使用defineClass()方法创建一个新的类。通过调用该方法,我们可以指定类加载器、字节数组的偏移量和长度等信息来创建类。
超大数组
在Java中,常量Integer.MAX_VALUE表示数组长度的最大值。如果你想创建一个非常大的数组,可以通过直接分配内存来实现。以下示例演示了如何创建一个分配了连续内存(数组)的示例,其容量为最大容量的两倍:
import sun.misc.Unsafe;
import java.lang.reflect.Field;
public class LargeArrayExample {private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {// 获取Unsafe实例Unsafe unsafe = getUnsafeInstance();// 计算数组长度long arrayLength = (long) MAX_ARRAY_SIZE * 2;// 分配内存long arrayAddress = unsafe.allocateMemory(arrayLength);System.out.println("Array allocated at address: " + arrayAddress);for (int i = 0; i < 12 ; i += blockSize) {// 获取当前块的地址long blockAddress = arrayAddress + (i * Integer.BYTES);// 计算当前块的实际大小long currentBlockSize = Math.min(12 - i, blockSize);// 循环添加元素到当前块for (int j = 0; j < currentBlockSize; j++) {unsafe.putInt(blockAddress + (j * Integer.BYTES), i + j);}} }private static Unsafe getUnsafeInstance() throws NoSuchFieldException, IllegalAccessException {Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");theUnsafeField.setAccessible(true);return (Unsafe) theUnsafeField.get(null);}
}
总结概括
sun.misc.Unsafe提供了可以随意查看及修改JVM中运行时的数据结构,尽管这些功能在JAVA开发本身是不适用的。Unsafe是一个用于研究学习HotSpot虚拟机非常棒的工具,因为它不需要调用C++代码,或者需要创建即时分析的工具。然而,使用Unsafe类进行直接内存分配是一种非常底层和不安全的操作,绕过了Java内存管理系统,需要谨慎处理,并且仅在特定的情况下才应使用。
相关文章:
【Java技术专题】「攻破技术盲区」攻破Java技术盲点之unsafe类的使用指南(打破Java的安全管控— sun.misc.unsafe)
Java后门机制 — sun.misc.unsafe 打破Java的安全管控关于Unsafe的编程建议实例化Unsafe后门对象使用sun.misc.Unsafe创建实例单例模式处理实现浅克隆(直接获取内存的方式)直接使用copyMemory原理分析 密码安全使用Unsafe类—示例代码 运行时动态创建类超…...
私有云平台搭建openstack和ceph结合搭建手册
OpenStack与云计算 什么是云? 如何正确理解云,可以从以下几个方面。 云的构成。 用户:对用户而言是透明无感知的,不用关心底层构成,只需要知道利用云完成自己任务即可。 云提供商:对云资产管理和运维。 云…...
debug mccl 02 —— 环境搭建及初步调试
1, 搭建nccl 调试环境 下载 nccl 源代码 git clone --recursive https://github.com/NVIDIA/nccl.git 只debug host代码,故将设备代码的编译标志改成 -O3 (base) hipperhipper-G21:~/let_debug_nccl/nccl$ git diff diff --git a/makefiles/common.mk b/makefiles/…...
ros python 接收GPS RTK 串口消息再转发 ros 主题消息
代码是一个ROS(Robot Operating System)节点,用于从GPS设备读取RTK(实时动态)数据并通过ROS主题发布。 步骤: 导入必要的模块: rospy 是ROS的Python库,用于ROS的节点、发布者和订阅者。serial 用于串行通信。NavSatFix 和 NavSatStatus 是从GPS接收的NMEA 0183标准消息…...
2024年网络安全竞赛-页面信息发现任务解析
页面信息发现任务说明: 服务器场景:win20230305(关闭链接)在渗透机中对服务器信息收集,将获取到的服务器网站端口作为Flag值提交;访问服务器网站页面,找到主页面中的Flag值信息,将Flag值提交;访问服务器网站页面,找到主页面中的脚本信息,并将Flag值提交;访问服务器…...
ARM DMA使用整理
Direct Memory Access, 直接存储访问。同SPI,IIC,USART等一样,属于MCU的一个外设,用于在不需要MCU介入的情况下进行数据传输。可以将数据从外设传输到flash,也可以将数据从flash传输到外设,或者flash内部数据移动。 它…...
移动通信原理与关键技术学习(第四代蜂窝移动通信系统)
前言:LTE 标准于2008 年底完成了第一个版本3GPP Release 8的制定工作。另一方面,ITU 于2007 年召开了世界无线电会议WRC07,开始了B3G 频谱的分配,并于2008 年完成了IMT-2000(即3G)系统的演进——IMT-Advanc…...
光明源@智慧厕所技术:优化生活,提升卫生舒适度
在当今数字科技飞速发展的时代,我们的日常生活正在经历一场革命,而这场革命的其中一个前沿领域就是智慧厕所技术。这项技术不仅仅是对传统卫生间的一次升级,更是对我们生活品质的全方位提升。从智能感应到数据分析,从环保设计到舒…...
【Bootstrap学习 day13】
Bootstrap5 下拉菜单 下拉菜单通常用于导航标题内,在用户鼠标悬停或单击触发元素时显示相关链接列表。 基础的下拉列表 <div class"dropdown"><button type"button" class"btn btn-primary dropdown-toggle" data-bs-toggl…...
Shell:常用命令之dirname与basename
一、介绍 1、dirname命令用于去除文件名中的非目录部分,删除最后一个“\”后面的路径,显示父目录。 语法:dirname [选项] 参数 2、basename命令用于打印目录或者文件的基本名称,显示最后的目录名或文件名。 语法:basen…...
Linux-v4l2框架
框架图 从上图不难看出,v4l2_device作为顶层管理者,一方面通过嵌入到一个video_device中,暴露video设备节点给用户空间进行控制;另一方面,video_device内部会创建一个media_entity作为在media controller中的抽象体&a…...
VPC网络架构下的网络上数据采集
介绍 想象这样一个场景,一开始在公司里,所有的部门的物理机、POD都在一个经典网络内,它们可以通过 IP 访问彼此,没有任何限制。因此有很多系统基于此设计了很多点对点 IP 直连的访问,比如中心控制服务器 S 会主动访问物…...
模拟算法(模拟算法 == 依葫芦画瓢)万字
模拟算法 基本思想引入算法题替换所有的问号提莫攻击Z字形变换外观数列数青蛙 基本思想 模拟算法 依葫芦画瓢解题思维要么通俗易懂,要么就是找规律,主要难度在于将思路转换为代码。 特点:相对于其他算法思维,思路比较简单&#x…...
QtApplets-SystemInfo
QtApplets-SystemInfo 今天是2024年1月3日09:18:44,这也是2024年的第一篇博客,今天我们主要两件事,第一件,获取系统CPU使用率,第二件,获取系统内存使用情况。 这里因为写博客的这个本本的环境配置不…...
vue3防抖函数封装与使用,以指令的形式使用
utils/debounce.js /*** 防抖函数* param {*} fn 函数* param {*} delay 暂停时间* returns */ export function debounce(fn, delay 500) {let timer nullreturn function (...args) {// console.log(arguments);// const args Array.from(arguments)if (timer) {clearTim…...
Hive学习(13)lag和lead函数取偏移量
hive里面lag函数 在数据处理和分析中,窗口函数是一种重要的技术,用于在数据集中执行聚合和分析操作。Hive作为一种大数据处理框架,也提供了窗口函数的支持。在Hive中,Lag函数是一种常用的窗口函数,可以用于计算前一行…...
Centos Unable to verify the graphical display setup
ERROR: Unable to verify the graphical display setup. 在Linux下安装Oracle时 运行 ./runInstaller 报错 ERROR: Unable to verify the graphical display setup. This application requires X display. Make sure that xdpyinfo exist under PATH variable. No X11 DISPL…...
Java 说一下 synchronized 底层实现原理?
Java 说一下 synchronized 底层实现原理? synchronized 是 Java 中用于实现同步的关键字,它保证了多个线程对共享资源的互斥访问。底层实现涉及到对象头的 Mark Word 和锁升级过程。 synchronized 可以用于方法上或代码块上,分别对应于方法…...
nginx访问路径匹配方法
目录 一:匹配方法 二:location使用: 三:rewrite使用 一:匹配方法 location和rewrite是两个用于处理请求的重要模块,它们都可以根据请求的路径进行匹配和处理。 二:location使用: 1:简单匹配…...
偌依 项目部署及上线步骤
准备实验环境,准备3台机器 1.作为前端服务器,mysql,redis服务器--同时临时作为代码打包服务器 192.168.2.65 nginx-server 2.作为后端服务器 192.168.2.66 java-server-1 192.168.2.67 java-server-2 安装nginx/mysql #安装nginx [rootweb-nginx ~]…...
PHP特性知识点扫盲 - 上篇
概述 之前在分析thinkphp源码的时候,对依赖注入等等php高级的特性一直想做一个梳理和总结,一直没有时间,好不容易抽一点时间对技术的盲点做一个扫盲和总结。 特性 1.命名空间 命名空间是在PHP5.3中引入,是一个很重要的工具&am…...
Docker一键极速安装Nacos,并配置数据库!
1 部署方式 1.1 DockerHub javaedgeJavaEdgedeMac-mini ~ % docker run --name nacos \ -e MODEstandalone \ -e JVM_XMS128m \ -e JVM_XMX128m \ -e JVM_XMN64m \ -e JVM_MS64m \ -e JVM_MMS64m \ -p 8848:8848 \ -d nacos/nacos-server:v2.2.3 a624c64a1a25ad2d15908a67316d…...
交换机04_远程连接
通过远程管理方式连接交换机 1、telnet简介 telnet 是应用层协议 基于传输层TCP协议的,默认端口:23 采用的是明文密码方式 不是很安全,一般用于内网管理。 2、ssh协议简介 ssh 是应用层的协议,基于传输层的TCP协议&#x…...
ES6定义一个类(函数内部定义属性,,原型定义方法 ), 实现继承?
ES6中使用class关键字定义一个类,使用extends关键字实现继承。下面是一个示例: class Animal {constructor(name) {this.name name;}sayHello() {console.log(Hello, my name is ${this.name});} }class Dog extends Animal {constructor(name, breed)…...
使用 Process Explorer 和 Windbg 排查软件线程堵塞案例分享
目录 1、问题说明 2、线程堵塞的可能原因分析 3、使用Windbg和Process Explorer确定线程中发生了死循环 4、根据Windbg中显示的函数调用堆栈去查看源码,找到问题 4.1、在Windbg定位发生死循环的函数的方法 4.2、在Windbg中查看变量的值去辅助分析 4.3、是循环…...
“智慧”千里眼助力水泵站
泵站是为水提供势能和压能,解决无自流条件下的排灌、供水和水资源调配问题的唯一动力来源,在工农业用水、防洪、排涝和抗旱减灾等方面发挥着重要作用。一旦出现异常,对经济生产将造成难以估量的损失,给水利安全管理造成负担。因此…...
C++多态性——(5)运算符重载(第二节)
归纳编程学习的感悟, 记录奋斗路上的点滴, 希望能帮到一样刻苦的你! 如有不足欢迎指正! 共同学习交流! 🌎欢迎各位→点赞 👍 收藏⭐ 留言📝 身先才能率人,律己才能服人…...
ES -极客学习
Elasticsearch 简介及其发展历史 起源 Lucene 于 Java 语言开发的搜索引擎库类创建于 1999 年,2005 年成为 Apache 顶级开源项目Lucene 具有高性能、易扩展的优点Lucene 的局限性 只能基于 Java 语言开发类库的接口学习曲线陡峭原生并不支持水平扩展原生并不支持水…...
【大厂秘籍】系列 - Java多线程面试题
Java多线程面试题 友情提示,看完此文,在Java多线程这块,基本上可以吊打面试官了 线程和进程的区别 进程是资源分配的最小单位,线程是CPU调度的最小单位 线程是进程的子集,一个进程可以有很多线程,每条线…...
vue实现画笔回放,canvas转视频播放功能
示例图: 一、vue2版本 <template><div class"canvas-video"><canvasref"myCanvasByVideo"class"myCanvas"id"myCanvasByVideo":width"width":height"height"></canvas><d…...
wordpress sidebar.php/万维网域名注册查询
MongoDB 进程控制 进程控制db.currentOp() # 查看活动进程 db.$cmd.sys.inprog.findOne() # 查看活动进程 与上面一样 opid # 操作进程号 op # 操作类型(查询\更新) ns # 命名空间,指操作的是哪个对象 query # 如果操作类型是查询,这里将显示具体的查询内容 lockType # 锁的类型…...
广州网站备案/百度云引擎搜索
这几天经常与OEM 顶上,前几天刚解决一个OEM问题,今天又遇到了。Oracle 安装OEM 报错: 无法对所有EM 相关账户解锁 解决方法http://blog.csdn.net/tianlesoftware/article/details/6816938原打算安装一下Oracle 11gR2. 然后升级到11.2..0.3的,…...
网站实现多模板切换/网站有吗免费的
这是bootCDN上引用的bootstrap前端框架套件,由多个框架组合而成,方便平时学习和测试使用。生产环境要仔细琢磨一下,不要用开发版,而要用生产版。bootCDN的地址是:https://www.bootcdn.cn/ ,是由bootstrap中…...
做宾馆网站/seo搜索引擎优化怎么优化
# -*- coding: utf-8 -*- #python 27 #xiaodeng #python之函数用法iter()#iter() #说明:对一个对象调用 iter() 就可以得到它的迭代器iter(...)iter(collection) -> iteratorcollection:容器iterator:可迭代对象iter(callable, sentinel) -> itera…...
怎么做英文版网站/seo课程培训视频
ScottGu在其最新的博文中推荐了Simone Chiaretta的文章13 ASP.NET MVC extensibility points you have to know,该文章为我们简单介绍了ASP.NET MVC中的13个扩展点。Keyvan Nayyeri(与Simone合著了Beginning ASP.NET MVC 1.0一书)又陆续发表了一些文章,对…...