Java内存管理-掌握自定义类加载器的实现(七)
勿在流沙筑高台,出来混迟早要还的。
做一个积极的人
编码、改bug、提升自己
我有一个乐园,面向编程,春暖花开!
上一篇分析了ClassLoader的类加载相关的核心源码,也简单介绍了ClassLoader的设计思想,读源码相对来说是比较枯燥的,还是这个是必须要走的过程,学习源码中的一些思想,一些精髓,看一下大神级人物是怎么写出那么牛逼的代码。我们能够从中学到一点点东西,那也是一种进步和成长了。本文基于上一篇文章内容,手把手写一个自定义类加载器,并且通过一些简单的案例(场景)让我们更加细致和静距离的体验类加载器的神奇之处。
本文地图:

一、上文回顾扩展
上一篇介绍了ClassLoader中loadClass()内的一些源码,也介绍了一些核心的API,其中有一个getParent()是没有做说明的,这里简单说明一下,方便快速理解后续的内容。
// 返回委托的父类加载器。
ClassLoader getParent()
这个方法是获取父类加载器,那么父类加载器是怎么初始化的。上一文也提到了,虽然类加载器的加载模式为双亲委派模型,但是真正在实现上并不是使用继承方式。
看下面源码,sun.misc.Launcher类是java的入口,在启动java应用的时候会首先创建Launcher类,创建Launcher类的时候回准备应用程序运行中需要的类加载器。
/**
* 删除了一些其他代码,方便阅读
**/
public class Launcher {
// 可自行打印一下 bootClassPath ,看输入内容是什么?
private static String bootClassPath = System.getProperty("sun.boot.class.path");// ①
private static Launcher launcher = new Launcher(); // ②
private ClassLoader loader;
public Launcher() {
Launcher.ExtClassLoader var1;
try {
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
Thread.currentThread().setContextClassLoader(this.loader);// ③
// 省略其他代码....
}
}
第一:Launcher作为JAVA应用的入口,根据我们之前所学的双亲委派模型,Laucher是由JVM创建的,它类加载器应该是BootStrapClassLoader, 这是一个C++编写的类加载器,是Java应用体系中最顶层的类加载器,负责加载JVM需要的一些类库,classpath配置 (%JAVA_HOME%/jre/lib)。下面通过简单例子进行说明:
public class TestClassLoader {
public static void main(String[] args) {
// 可以获取是哪个类加载器加载 Launcher 这个类
ClassLoader classLoader = Launcher.class.getClassLoader();
System.out.println("classLoader : " + classLoader);
System.out.println("-------------------");
String bootClassPath = System.getProperty("sun.boot.class.path");
String[] split = bootClassPath.split(";");
for (int i = 0; i < split.length; i++) {
System.out.println(split[i]);
}
}
输出的结果,我本地机器上路径:
classLoader : null
-------------------
C:\Program Files\Java\jdk1.8.0_121\jre\lib\resources.jar
C:\Program Files\Java\jdk1.8.0_121\jre\lib\rt.jar
C:\Program Files\Java\jdk1.8.0_121\jre\lib\sunrsasign.jar
C:\Program Files\Java\jdk1.8.0_121\jre\lib\jsse.jar
C:\Program Files\Java\jdk1.8.0_121\jre\lib\jce.jar
C:\Program Files\Java\jdk1.8.0_121\jre\lib\charsets.jar
C:\Program Files\Java\jdk1.8.0_121\jre\lib\jfr.jar
C:\Program Files\Java\jdk1.8.0_121\jre\classes
第二:当初始化Launcher类的时候,遇到关键字new 进行初始化,调用构造方法。先获取到ExtClassLoader类加载器,然后在获取AppClassLoader类加载器器,然后设置ExtClassLoader做为它父类加载器。具体设置代码如下:
public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
//删掉其他代码...
return new Launcher.AppClassLoader(var1x, var0);
}
AppClassLoader(URL[] var1, ClassLoader var2) {
// 看 super的实现 ,var2 就是 ExtClassLoader
super(var1, var2, Launcher.factory);
this.ucp.initLookupCache(this);
}
/**
* 在AppClassLoader的父类中,java.net.URLClassLoader#URLClassLoader
* super(parent); 设置父类加载器,最后可以通过 getParent() 获取父类加载器
*/
public URLClassLoader(URL[] urls, ClassLoader parent,
URLStreamHandlerFactory factory) {
super(parent); // 这里就不在继续往上跟踪了,在往上代码相对简单,可自行查阅
SecurityManager security = System.getSecurityManager();
//删掉其他代码...
}
第三:设置上下文类加载器的思考?
在Java中为什么需要上下文类加载器呢,这个就是一个非常有意思的问题。 Java中有两种方式获取类加载器:
第一种:一个类被加载的时候使用哪个类加载器来加载,也就是类.class.getClassLoader()或者对象.getClass().getClassLoader()。
String str = new String();
ClassLoader classLoader1 = String.class.getClassLoader();
ClassLoader classLoader2 = str.getClass().getClassLoader();
System.out.println("String loader1 : " + classLoader1);
System.out.println("String loader2 : " + classLoader2);
-----输出为null,启动类加载器进行加载--------
String loader1 : null
String loader2 : null
第二种:通过Thread的上限文获取类加载器。为什么要通过上下文加载呢?
虽然我们都知道Java类加载的双亲委派模型,在加载一个类的时候,会优先委派给父类加载器,这样保证不会出现类被重复加载,也保证了Java一些基础类(如String类)可以稳定的存在,不会被用户自定义类顶替掉。
但是双亲委派模型并不是完美的,在一些场景下会出现一些比较难解决的问题,举个例子,在使用SPI的时候,java.util.ServiceLoader是通过BootStrapClassLoader类加载器加载的,在执行到加载用户编写的扩展类的时候,如果使用当前类的类加载器,是肯定无法加载到用户编写的类的,这个时候就无法继续执行了,所以这个时候就需要使用Thread的上下文类加载器,查看源码的时候我们就发现,在用户不主动传递ClassLoader的时候,会获取当前上下文类加载器,这样应用程序才能正常的执行。
public static <S> ServiceLoader<S> load(Class<S> var0) {
ClassLoader var1 = Thread.currentThread().getContextClassLoader();
return load(var0, var1);
}
小总结:
上面的内容是在上一篇的基础上继续的扩展,如果对还没有看过上一篇内容,请先阅读上一篇内容后,在来看这段内容。整个ClassLoader源码的的部分就分析这么多了,后面在自定义类加载器中有遇到需要分析源码的地方,还是会继续进行说明和讲解。
二、实现自定义类加载器
前面的两篇文章一直在为自定义加载器做铺垫,本文终于来引来这个神秘的嘉宾了,下面就我们用"热恋"的掌声欢迎它的出场(活跃一下气氛,因为刚才看源码太安静了)!
首先看一下JDK API中如何教我们实现从现从网络加载类的类加载器的简单示例。看下面代码:
例如,应用程序可以创建一个网络类加载器,从服务器中下载类文件。示例代码如下所示:
ClassLoader loader = new NetworkClassLoader(host, port);
Object main = loader.loadClass("Main", true).newInstance();
. . .
网络类加载器子类必须定义方法 findClass 和 loadClassData,以实现从网络加载类。下载组成该类的字节后,它应该使用方法 defineClass 来创建类实例。示例实现如下:
class NetworkClassLoader extends ClassLoader {
String host;
int port;
public Class findClass(String name) {
byte[] b = loadClassData(name);
return defineClass(name, b, 0, b.length);
}
private byte[] loadClassData(String name) {
// load the class data from the connection
. . .
}
}
下面就“手把手”一步步教你如何实现一个自定义类加载器!
第一步: 新建一个MyClassLoader 继承 ClassLoader
public class MyClassLoader extends ClassLoader {}
第二步:添加自定义的类加载器属性和 构造函数
public class MyClassLoader extends ClassLoader {
/**
* 类加载器名称
*/
private String name;
/**
* 自定义加载路径
*/
private String path;
/**
* 自定义加载文件后缀类型
*/
private final String fileType = ".class";
public MyClassLoader(String name,String path){
//让系统类加载器(AppClassLoader)成为该类加载器的父类加载器
super();
this.name = name;
this.path = path;
}
public MyClassLoader(ClassLoader parent,String name,String path){
//显示指定该类的父类加载器
super(parent);
this.name = name;
this.path = path;
}
@Override
public String toString() {
return this.name;
}
}
第三步:重写findClass方法,自定义我们自己的查询类的方式,然后通过defineClass方法将一个 byte 数组转换为 Class 类的实例。
/**
* 加载我们自己定义的类,通过我们自己定义的类加载器
* @param name 二进制的文件名称
* @return Class实例对象
* @throws ClassNotFoundException
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
//获取class文件的字节数组
byte[] resultData = this.loadByteClassData(name);
return super.defineClass(name, resultData, 0, resultData.length);
}
/**
* 加载指定路径下面的class文件的字节数组
* @param name 二进制文件名称 ,例如:com.learn.classloader.Demo
* @return 二进制字节数组
*/
private byte[] loadByteClassData(String name) {
byte[] classData = null;
InputStream in = null;
ByteArrayOutputStream os = null;
try {
// 比如 有包名 二进制文件名:com.learn.classloader.Demo
// 转换为本地路径 com/learnclassloader/Demo.class
name = this.path + name.replaceAll("\\.", "/") + fileType;
File file = new File(name);
os = new ByteArrayOutputStream();
in = new FileInputStream(file);
int tmp = 0;
while ((tmp = in.read()) != -1){
os.write(tmp);
}
// 文件流转为二进制字节流
classData = os.toByteArray();
}catch (Exception e){
e.printStackTrace();
}finally {
try {
// 关闭流
if(in != null){
in.close();
}
if(os != null){
os.close();
}
}catch (Exception e){
e.printStackTrace();
}
}
return classData;
}
上面三步整合在一起,就实现了一个简单的类加载! 查看自定义类加载器的全部代码。
/**
* 自定义类加载器
*
* @author:dufyun
* @version:1.0.0
* @date 2019/3/28
*/
public class MyClassLoader extends ClassLoader {
/**
* 类加载器名称
*/
private String name;
/**
* 自定义加载路径
*/
private String path;
/**
* 自定义加载文件后缀类型
*/
private final String fileType = ".class";
public MyClassLoader(String name,String path){
//让系统类加载器(AppClassLoader)成为该类加载器的父类加载器
super();
this.name = name;
this.path = path;
}
public MyClassLoader(ClassLoader parent,String name,String path){
//显示指定该类的父类加载器
super(parent);
this.name = name;
this.path = path;
}
/**
* 加载我们自己定义的类,通过我们自己定义的类加载器
* @param name 二进制的文件名称
* @return Class实例对象
* @throws ClassNotFoundException
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
//获取class文件的字节数组
byte[] resultData = this.loadByteClassData(name);
return super.defineClass(name, resultData, 0, resultData.length);
}
/**
* 加载指定路径下面的class文件的字节数组
* @param name 二进制文件名称 ,例如:com.learn.classloader.Demo
* @return 二进制字节数组
*/
private byte[] loadByteClassData(String name) {
byte[] classData = null;
InputStream in = null;
ByteArrayOutputStream os = null;
try {
// 比如 有包名 二进制文件名:com.learn.classloader.Demo
// 转换为本地路径 com/learn/classloader/Demo.class
name = this.path + name.replaceAll("\\.", "/") + fileType;
File file = new File(name);
os = new ByteArrayOutputStream();
in = new FileInputStream(file);
int tmp = 0;
while ((tmp = in.read()) != -1){
os.write(tmp);
}
// 文件流转为二进制字节流
classData = os.toByteArray();
}catch (Exception e){
e.printStackTrace();
}finally {
try {
// 关闭流
if(in != null){
in.close();
}
if(os != null){
os.close();
}
}catch (Exception e){
e.printStackTrace();
}
}
return classData;
}
@Override
public String toString() {
return this.name;
}
}
第五: 简单的测试自定义类加载器!
MyClassLoader myClassLoaderA = new MyClassLoader("aflyun", "F:\\tmp\\");
System.out.println("myClassLoaderA :" + myClassLoaderA.getParent().getClass().getName());
// 设置父类加载器 为null ,启动类加载器
MyClassLoader myClassLoaderB = new MyClassLoader(null, "aflyun", "F:\\tmp\\");
System.out.println("myClassLoaderB :" + myClassLoaderB.getParent());
------------
myClassLoaderA :sun.misc.Launcher$AppClassLoader
myClassLoaderB :null
看到这里,你如果对本篇第一小节理解的话,这里肯定会好奇,MyClassLoader是怎么设置AppClassLoader为父类加载器的,在代码中只写了一个super();系统类加载器(AppClassLoader)成为该类加载器的父类加载器。还是看源码 ,使用super();在初始化的时候调用CLassLoader的构造函数,在此构造函数中有一个getSystemClassLoader()获取的就是 AppClassLoader。
protected ClassLoader() {
this(checkCreateClassLoader(), getSystemClassLoader());
}
我们在看一下 getSystemClassLoader()的实现,其中有一个initSystemClassLoader()方法获取到Launcher初始化加载的ClassLoader,然后将此ClassLoader赋值给 ClassLoader类 中的 scl!具体源码如下:
// The class loader for the system
private static ClassLoader scl;
public static ClassLoader getSystemClassLoader() {
initSystemClassLoader(); // 获取系统初始化的ClassLoader
if (scl == null) {
return null;
}
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkClassLoaderPermission(scl, Reflection.getCallerClass());
}
return scl;
}
private static synchronized void initSystemClassLoader() {
if (!sclSet) {
// 省略其他代码.....
sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
if (l != null) {
Throwable oops = null;
//从Launcher 中获取ClassLoader也就是 AppClassLoader
scl = l.getClassLoader();
// 省略其他代码.....
}
sclSet = true;
}
}
最后this(checkCreateClassLoader(), getSystemClassLoader());调用了ClassLoader的另一个构造函数,具体看下面源码,比较简单,就不做太多说明了。
// 这段代码上一篇文章就介绍过
private ClassLoader(Void unused, ClassLoader parent) {
this.parent = parent; // 将获取的系统类加载器作为父类加载器,通过getParent()获取!
if (ParallelLoaders.isRegistered(this.getClass())) {
parallelLockMap = new ConcurrentHashMap<>();
// 省略其他代码.....
} else {
// no finer-grained lock; lock on the classloader instance
parallelLockMap = null;
// 省略其他代码.....
}
}
/**
* 获取父类加载器
*/
public final ClassLoader getParent() {
if (parent == null)
return null;
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkClassLoaderPermission(parent, Reflection.getCallerClass());
}
return parent;
}
一张图在此说明:当前的类加载器就是自定义类加载器MyClassLoader!
MyClassLoader myClassLoaderA = new MyClassLoader("aflyun", "F:\\tmp\\");

tips1 :自定义类加载说明
在实现自定义类加载器过程中可以重写findClass 也可以重写loadClass ,但一般建议重写findClass!
tips2 :自定义类加载器设置了加载路径path,其实之前介绍过的类加载器也有对应的加载路径。
// BootStrapClassLoader
String bootClassPath = System.getProperty("sun.boot.class.path");
// ExtClassLoader
String var0 = System.getProperty("java.ext.dirs");
// AppClassLoader
String var1 = System.getProperty("java.class.path");
三、多案例分析
1、相同两个类文件,名称一样,一个在磁盘F:\tmp\下(无包名)一个工程中(有包名)
F:盘中代码

public class HelloWorld {
public HelloWorld() {
System.out.println("--F: --HelloWorld--" + this.getClass().getClassLoader());
}
}
工程中代码:
package com.learn.classloader;
public class HelloWorld {
public HelloWorld() {
System.out.println("--IDEA --HelloWorld--" + this.getClass().getClassLoader());
}
}
执行类加载代码:
MyClassLoader myClassLoader = new MyClassLoader("myClassLoader","F:/tmp/");
Class<?> cls = null;
try {
//加载类文件
cls = myClassLoader.loadClass("HelloWorld");
cls.newInstance();//实例化类。调用构造方法
} catch (Exception e) {
e.printStackTrace();
}
思考一下打印的结果是什么呢?
答案:--F: --HelloWorld--myClassLoader
原因分析:HelloWorld类的class文件只有在F:/tmp/下存在,所以就加载的是F盘下的HelloWorld类文件!
2、相同两个类文件,名称和包名一样,一个在磁盘F:\tmp\,一个在工程中
F:盘中代码

package com.learn.classloader;
public class HelloWorld {
public HelloWorld() {
System.out.println("--F:com.learn.classloader --HelloWorld--" + this.getClass().getClassLoader());
}
}
工程中的代码:
package com.learn.classloader;
public class HelloWorld {
public HelloWorld() {
System.out.println("--IDEA --HelloWorld--" + this.getClass().getClassLoader());
}
}
执行类加载代码:
MyClassLoader myClassLoader = new MyClassLoader("myClassLoader","F:/tmp/");
Class<?> cls = null;
try {
cls = myClassLoader.loadClass("com.learn.classloader.HelloWorld");
cls.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
思考一下打印的结果是什么呢?
答案:--IDEA --HelloWorld--sun.misc.Launcher$AppClassLoader@18b4aac2
原因分析:F盘中HelloWorld和工程中的HelloWorld具有一样的包名,并且MyClassLoader没有设置父类加载器,那么默认的父类加载类就是AppClassLoader,根据之前所学的双亲委派模型,HelloWorld类文件会首先被父类加载器加载,也就是被AppClassLoader加载,只要父类加载器加载成功,子类加载器就不会在进行加载!
3、新增一个myClassLoaderB的实例设置父类加载器myClassLoader,加载MyHelloWorld
F: 盘 MyHelloWorld

package com.learn.classloader;
public class MyHelloWorld {
public MyHelloWorld() {
System.out.println("--F:com.learn.classloader --MyHelloWorld--" + this.getClass().getClassLoader());
}
}
D:盘 MyHelloWorld

package com.learn.classloader;
public class MyHelloWorld {
public MyHelloWorld() {
System.out.println("--D:com.learn.classloader --MyHelloWorld--" + this.getClass().getClassLoader());
}
}
此时新增一个myClassLoaderB,加载MyHelloWorld!
加载的代码如下:
MyClassLoader myClassLoader = new MyClassLoader("myClassLoader","F:/tmp/");
MyClassLoader myClassLoaderB = new MyClassLoader(myClassLoader,"myClassLoader","D:/tmp/");
Class<?> cls = null;
try {
cls = myClassLoaderB.loadClass("com.learn.classloader.MyHelloWorld");
cls.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
思考一下打印的结果是什么呢?
答案:--F:com.learn.classloader --MyHelloWorld--myClassLoader
原因分析:myClassLoaderB父类加载器是myClassLoader,此时在加载 MyHelloWorld,首先会被父类加载器myClassLoader 加载!
4、新增myClassLoaderC设置父类加载器为null(启动类加载器),加载MyHelloWorld!
代码示例和示例3一样!只是修改类加载器!代码如下:
MyClassLoader myClassLoaderC = new MyClassLoader(null,"myClassLoaderC","D:/tmp/");
Class<?> cls = null;
try {
cls = myClassLoaderC.loadClass("com.learn.classloader.MyHelloWorld");
cls.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
思考一下打印的结果是什么呢?
答案:--D:com.learn.classloader --MyHelloWorld--myClassLoaderC
原因分析:myClassLoaderC父类加载器是启动类加载器,在启动类加载器中找不到MyHelloWorld,转一圈回来还是需要myClassLoaderC自己去加载!
四、本文总结
本文实现了一个自定义的类加载器,并且通过简单的案例进行讲解和说明,让我们更加深入的了解类加载器的双亲委派模式和实现原理。
对类加载器有了比较深入的学习和思考之后,会对我们以后写Java代码会有一定帮助,并且在遇到一些Java的异常如ClassNotFoundException能够快速知道原因。 其实类加载的知识还有很多,在这里先抛出两个问题:
问题1、Java热部署如何实现 ? 修改一个Java文件后,不需要启动服务,就可以动态生效! (目前的主流开发工具都支持,如IDEA 在windows下 Ctrl+Shirt+F9,动态编译,动态加载!或者 SpringBoot通过配置devtools实现热部署的原理是什么?)
本质上是更新clas文件内容! 不需要重新启动服务!
问题2、之前一直提的类加载模式: 双亲模式模式!但是在Tomcat中你知道 WebappClassLoader 的加载机制吗?
说明:WebappClassLoader 会先加载自己的Class ,找不到在委托给parent,破坏双亲委派模式!
后续有时间会去整理,如果你对这两个问题感兴趣,也欢迎在文末留言,一起探讨!
五、参考资料
《JDK API 文档》
《深入理解Java虚拟机》
谢谢你的阅读,如果您觉得这篇博文对你有帮助,请点赞或者喜欢,让更多的人看到!祝你每天开心愉快!
不管做什么,只要坚持下去就会看到不一样!在路上,不卑不亢!
博客首页 : http://blog.csdn.net/u010648555
愿你我在人生的路上能都变成最好的自己,能够成为一个独挡一面的人
© 每天都在变得更好的阿飞云
Java内存管理-掌握自定义类加载器的实现(七)的更多相关文章
- Java内存管理-掌握虚拟机类加载器(五)
勿在流沙筑高台,出来混迟早要还的. 做一个积极的人 编码.改bug.提升自己 我有一个乐园,面向编程,春暖花开! 上一篇介绍虚拟机类加载机制,讲解了类加载机制中的三个阶段,分别是:加载.连接(验证.准 ...
- Java内存管理-掌握虚拟机类加载机制(四)
勿在流沙筑高台,出来混迟早要还的. 做一个积极的人 编码.改bug.提升自己 我有一个乐园,面向编程,春暖花开! 上一篇介绍了整个JVM运行时的区域,以及简单对比了JDK7和JDK8中JVM运行时区域 ...
- 【Java虚拟机8】自定义类加载器、类加载器命名空间、类的卸载
前言 学习类加载器就一定要自己实现一个类加载器,今天就从一个简单的自定义类加载器说起. 自定义类加载器 例1 一个简单的类加载器,从一个给定的二进制名字读取一个字节码文件的内容,然后生成对应的clas ...
- 【摘录】JAVA内存管理-自动选择垃圾收集器算法
在J2SE 5.0,垃圾收集的默认值:垃圾收集器.堆大小以及JVM的类型(客户端还是服务器)都会根据应用运行的硬件平台和操作系统自动选择.相比之前设置命令行参数的方式,自动选择很好的匹配了不同类型的应 ...
- JAVA基础知识之JVM-——自定义类加载器
JVM中除了根加载器之外其他加载器都是ClassLoader的子类实例, 可以通过扩展ClassLoader的子类,通过重写方法来实现自定义的类加载器. ClassLoader中有两个关键的方法如下, ...
- java虚拟机学习-JVM内存管理:深入垃圾收集器与内存分配策略(4)
Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来. 概述: 说起垃圾收集(Garbage Collection,下文简称GC),大部分人都把这项 ...
- Java自定义类加载器与双亲委派模型
其实,双亲委派模型并不复杂.自定义类加载器也不难!随便从网上搜一下就能搜出一大把结果,然后copy一下就能用.但是,如果每次想自定义类加载器就必须搜一遍别人的文章,然后复制,这样显然不行.可是自定义类 ...
- Java虚拟机JVM学习06 自定义类加载器 父委托机制和命名空间的再讨论
Java虚拟机JVM学习06 自定义类加载器 父委托机制和命名空间的再讨论 创建用户自定义的类加载器 要创建用户自定义的类加载器,只需要扩展java.lang.ClassLoader类,然后覆盖它的f ...
- java类加载器学习2——自定义类加载器和父类委托机制带来的问题
一.自定义类加载器的一般步骤 Java的类加载器自从JDK1.2开始便引入了一条机制叫做父类委托机制.一个类需要被加载的时候,JVM先会调用他的父类加载器进行加载,父类调用父类的父类,一直到顶级类加载 ...
随机推荐
- Confluence 6 关于统一插件管理器
所有的组件通过 统一插件管理器(Universal Plugin Manager)进行管理,这个也被称为 UPM.UPM 可以在几乎所有的 Atlassian 应用中找到,能够提供完整同意的插件安装管 ...
- input,select默认颜色修改
input::-webkit-input-placeholder{color: #7f7f7f;} select{color: #7f7f7f} option{color: #7f7f7f;}
- 【shell】两种字符串提取场景的实现
shell虽然比batch顺眼点儿,但还是老话,入门容易,精通难. 1.场景一是这样的,现有字符串的内容 name: tiger; age:18; location:china; 需求:提取每个属性的 ...
- css 选择器/table属性/type 属性
css style样式---要写单位px style=" width: 200px; height :300px;" ;是结束符
- 添加按钮 table增加一行 删减按钮 table去掉一行
需求描述:做的一个AA新增功能,同时可以为这个即将新增的AA添加内容,而且AA的内容默认展示一行列表,点击添加按钮后出现下一行列表 解决思路:页面首先展示一个表头和列表的一行,作为默认展示的一行列表, ...
- C++ lstrlen()
关于lstrlen function,参考:https://msdn.microsoft.com/en-us/library/windows/desktop/ms647492(v=vs.85).asp ...
- GetSystemInfo()
关于“GetSystemInfo()”的详细信息,参考:https://msdn.microsoft.com/en-us/library/windows/desktop/ms724381(v=vs.8 ...
- 论文阅读笔记三十五:R-FCN:Object Detection via Region-based Fully Convolutional Networks(CVPR2016)
论文源址:https://arxiv.org/abs/1605.06409 开源代码:https://github.com/PureDiors/pytorch_RFCN 摘要 提出了基于区域的全卷积网 ...
- Linux-server-sshd
Linux-server-sshd 1:安装 OpenSSH 服务需要4 个软件包 openssh-5.3p1-114.el6_7.x86_64:包含OpenSSH 服务器及客户端需要的核心文件 op ...
- 微信小程序如何自动弹出提示微信授权?
我想在一进入页面的时候就进行判断提示并且弹出提示授权!请问该如何处理比较合理 wx.authorize({}) //可以通过 wx.getSetting 先查询一下用户是否授权了 "scop ...