JDK 动态代理的实现
JDK 动态代理的实现
虽然在常用的 Java 框架(Spring、MyBaits 等)中,经常见到 JDK 动态代理的使用,也知道了怎么去写一个 JDK 动态代理的 Demo,但是并不清楚实现原理。
之前略微知道是先生成一个代理对象,然后外部调用代理对象的接口方法,接着代理又会调用原始对象的方法,仅此而已。现在想稍微深入一丢,了解具体一点的实现方式。
Java 程序是运行在 JVM 中的,源码首先编译成字节码,然后加载到虚拟机中,最后被虚拟机解析然后执行(注意这里的措辞不一定严谨准确,会其意即可)。
JDK 动态代理实际上也就是在代码运行的过程中动态的生成了实现指定接口(被代理对象实现的接口)的类的字节码,对应的也就是代理类,然后创建这个代理类的对象,也就是代理对象。关于这一部分内容,看这篇博文更好,写的很详细。其实本文关注的点其实是 JDK 是如何创建代理类的,进而找到为什么调用代理对象的方法,就能增强原始对象的方法。
先上简单的 Demo 代码:
package jvmlearn;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
*
* @author xi
* @date 2018/08/23 12:04
*/
public class DynamicProxyTest {
interface IHello {
void sayHello();
}
static class Hello implements IHello {
@Override
public void sayHello() {
System.out.println("hello world");
}
}
static class DynamicProxy implements InvocationHandler {
Object originalObj;
Object bind(Object originalObj) {
this.originalObj = originalObj;
return Proxy.newProxyInstance(
originalObj.getClass().getClassLoader(),
originalObj.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("welcome");
return method.invoke(originalObj, args);
}
}
public static void main(String[] args) {
IHello hello = (IHello) new DynamicProxy().bind(new Hello());
hello.sayHello();
}
}
这个例子是『深入理解 Java 虚拟机』书上的例子,感觉例子中的DynamicProxy
取名不太好,容易误导像我这样的初学者,容易把它看成代理类,实则不然,它只是个InvocationHandler
接口的实现类,不要搞混了。
其实本次想要关注的重点是代理类和代理对象是如何产生的,所以要看看java.lang.reflect.Proxy#newProxyInstance
,因为机器上用的是 JDK 1.8,代码与之前有点小区别,不过问题不大。
@CallerSensitive// 这个注解不懂是啥,Google 一下,还是没懂,先看看文末的参考吧,有懂的同学,麻烦指点一二
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* 生成代理类的地方,需要进去看一看
* Look up or generate the designated proxy class.
*/
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
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;
}
});
}
// 创建代理对象
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
//...省略部分不看的内容
}
}
主要想看的是java.lang.reflect.Proxy#getProxyClass0
是如何生成代理类的:
/**
* Generate a proxy class. Must call the checkProxyAccess method
* to perform permission checks before calling this.
*/
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
// If the proxy class defined by the given loader implementing
// the given interfaces exists, this will simply return the cached copy;
// otherwise, it will create the proxy class via the ProxyClassFactory
return proxyClassCache.get(loader, interfaces);
}
proxyClassCache
是一个静态变量,缓存了代理类:
/**
* a cache of proxy classes
*/
private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
这里需要注意一下创建缓存的构造方法的两个参数,两个参数的类都实现了BiFunction
接口,Java8 的新特性:函数式接口。简单理解就是接口中定义了一个方法,接收两个参数,一顿操作之后,返回一个结果。先看缓存的构造方法:
public WeakCache(BiFunction<K, P, ?> subKeyFactory,
BiFunction<K, P, V> valueFactory) {
this.subKeyFactory = Objects.requireNonNull(subKeyFactory);
this.valueFactory = Objects.requireNonNull(valueFactory);
}
记住subKeyFactory
和valueFactory
这两个变量是啥,马上就会用到。
先看缓存的java.lang.reflect.WeakCache#get
方法,方法比较长,不用全部看,关注几个注释的地方即可,具体细节可自行 Debug:
public V get(K key, P parameter) {
Objects.requireNonNull(parameter);
expungeStaleEntries();
Object cacheKey = CacheKey.valueOf(key, refQueue);
// lazily install the 2nd level valuesMap for the particular cacheKey
ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
if (valuesMap == null) {
ConcurrentMap<Object, Supplier<V>> oldValuesMap
= map.putIfAbsent(cacheKey,
valuesMap = new ConcurrentHashMap<>());
if (oldValuesMap != null) {
valuesMap = oldValuesMap;
}
}
// create subKey and retrieve the possible Supplier<V> stored by that
// subKey from valuesMap 生成 key,subKeyFactory 就是前面提到的KeyFactory
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
Supplier<V> supplier = valuesMap.get(subKey);
Factory factory = null;
while (true) {
if (supplier != null) {
// supplier might be a Factory or a CacheValue<V> instance
// 上面的注释都写明白了,获取出来的对象的类都实现了 Supplier 接口
V value = supplier.get();
if (value != null) {
return value;
}
}
// else no supplier in cache
// or a supplier that returned null (could be a cleared CacheValue
// or a Factory that wasn't successful in installing the CacheValue)
// lazily construct a Factory
if (factory == null) {// 创建工厂
factory = new Factory(key, parameter, subKey, valuesMap);
}
if (supplier == null) {
supplier = valuesMap.putIfAbsent(subKey, factory);
if (supplier == null) {
// successfully installed Factory
supplier = factory;
}
// else retry with winning supplier
} else {// 用 factory 替换 supplier
if (valuesMap.replace(subKey, supplier, factory)) {
// successfully replaced
// cleared CacheEntry / unsuccessful Factory
// with our Factory
supplier = factory;
} else {
// retry with current supplier
supplier = valuesMap.get(subKey);
}
}
}
}
生成 key 就不看了,直接看V value = supplier.get();
这行代码,初次调用时,肯定没有缓存,所以先看java.lang.reflect.WeakCache.Factory#get
:
@Override
public synchronized V get() { // serialize access
// re-check
Supplier<V> supplier = valuesMap.get(subKey);
if (supplier != this) {
// something changed while we were waiting:
// might be that we were replaced by a CacheValue
// or were removed because of failure ->
// return null to signal WeakCache.get() to retry
// the loop
return null;
}
// else still us (supplier == this)
// create new value
V value = null;
try {// 这个地方的 valueFactory 就是前面说到的 ProxyClassFactory 对象
value = Objects.requireNonNull(valueFactory.apply(key, parameter));
} finally {
if (value == null) { // remove us on failure
valuesMap.remove(subKey, this);
}
}
// the only path to reach here is with non-null value
assert value != null;
// wrap value with CacheValue (WeakReference)
CacheValue<V> cacheValue = new CacheValue<>(value);
// put into reverseMap
reverseMap.put(cacheValue, Boolean.TRUE);
// try replacing us with CacheValue (this should always succeed)
if (!valuesMap.replace(subKey, this, cacheValue)) {// 添加缓存
throw new AssertionError("Should not reach here");
}
// successfully replaced us with new CacheValue -> return the value
// wrapped by it
return value;
}
接下来要看的是java.lang.reflect.Proxy.ProxyClassFactory#apply
方法,主要这一行:
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);
这行代码生成了代理类的字节码数组。
proxyName
是代理类接口的包名加上java.lang.reflect.Proxy.ProxyClassFactory#proxyClassNamePrefix
属性的值$Proxy
,再加上java.lang.reflect.Proxy.ProxyClassFactory#nextUniqueNumber
自增一次的值。
上面 Demo 中DynamicProxyTest
的包名为jvmlearn
,在main
方法中追加以下代码,看一下输出的类的名称:
System.out.println(DynamicProxyTest.class.getName());
System.out.println(IHello.class.getName());
System.out.println(hello.getClass().getName());
输出如下:
jvmlearn.DynamicProxyTest
jvmlearn.DynamicProxyTest$IHello
jvmlearn.$Proxy0
ProxyGenerator
是 JDK 提供的生成代理类的工具,源码可以看 OpenJDK 中的:ProxyGenerator.java,现截取部分关键代码如下:
public static byte[] generateProxyClass(String name,
Class<?>[] interfaces,
int accessFlags)
{
ProxyGenerator gen = new ProxyGenerator(name, interfaces, accessFlags);
// 生成代理类的字节码数组,稍后再看
final byte[] classFile = gen.generateClassFile();
if (saveGeneratedFiles) {// 是否保存生成的字节码到文件中
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<Void>() {
public Void run() {
try {
int i = name.lastIndexOf('.');
Path path;
if (i > 0) {// 创建字节码文件路径
Path dir = Paths.get(name.substring(0, i).replace('.', File.separatorChar));
Files.createDirectories(dir);
path = dir.resolve(name.substring(i+1, name.length()) + ".class");
} else {
path = Paths.get(name + ".class");// 字节码文件
}
Files.write(path, classFile);// 写入到文件中
return null;
} catch (IOException e) {
throw new InternalError(
"I/O exception saving generated file: " + e);
}
}
});
}
return classFile;
}
注释基本说明了这个方法的作用,现在来看一下字节码是如何生成的。JDK 的这个工具是手工生成的字节码,代码虽然有点多,不过注释写的很清楚。但是因为不熟悉字节码,读起来仍旧很吃力,那就采用反推的方式,先看生成的代理类,再看生成字节码的代码。
先生成代理类的 Class 文件,具体操作就是将sun.misc.ProxyGenerator.saveGeneratedFiles
设置为true
,两种方式:
- IDEA 设置 VM Options:
-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
main
方法开头添加如下代码:System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
最后生成的 Class 文件路径,以本人使用的 IDEA 为例,在项目根目录下:jvmlearn/$Proxy0.class
。先上反编译后的代码:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package jvmlearn;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import jvmlearn.DynamicProxyTest.IHello;
final class $Proxy0 extends Proxy implements IHello {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final void sayHello() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
// 省略了 equals、toString、hashCode 方法
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("jvmlearn.DynamicProxyTest$IHello").getMethod("sayHello");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
从反编译的代码就已经可以看出来,代理类的接口方法调用的是InvocationHandler
的invoke
方法。
接下来用javap -v jvmlearn.\$Proxy0
来反汇编 Class 文件:
Classfile /Users/xi/***/DEMO/jvmlearn/$Proxy0.class
Last modified 2019-10-30; size 1939 bytes
MD5 checksum 0191ebc19d7bb8b925d6d6a84d3b777a
final class jvmlearn.$Proxy0 extends java.lang.reflect.Proxy implements jvmlearn.DynamicProxyTest$IHello
minor version: 0
major version: 49
flags: ACC_FINAL
Constant pool:
// 常量池太长了,文末参考附了图
// 同样省略了 equals、toString、hashCode 方法
{
public jvmlearn.$Proxy0(java.lang.reflect.InvocationHandler) throws ;
descriptor: (Ljava/lang/reflect/InvocationHandler;)V
flags: ACC_PUBLIC
Code:
stack=10, locals=2, args_size=2
0: aload_0
1: aload_1
2: invokespecial #8 // Method java/lang/reflect/Proxy."<init>":(Ljava/lang/reflect/InvocationHandler;)V
5: return
Exceptions:
throws
public final void sayHello() throws ;
descriptor: ()V
flags: ACC_PUBLIC, ACC_FINAL
Code:
stack=10, locals=2, args_size=1
0: aload_0
1: getfield #16 // Field java/lang/reflect/Proxy.h:Ljava/lang/reflect/InvocationHandler;
4: aload_0
5: getstatic #50 // Field m3:Ljava/lang/reflect/Method;
8: aconst_null
9: invokeinterface #28, 4 // InterfaceMethod java/lang/reflect/InvocationHandler.invoke:(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;
14: pop
15: return
16: athrow
17: astore_1
18: new #42 // class java/lang/reflect/UndeclaredThrowableException
21: dup
22: aload_1
23: invokespecial #45 // Method java/lang/reflect/UndeclaredThrowableException."<init>":(Ljava/lang/Throwable;)V
26: athrow
Exception table:
from to target type
0 16 16 Class java/lang/Error
0 16 16 Class java/lang/RuntimeException
0 16 17 Class java/lang/Throwable
Exceptions:
throws
static {} throws ;
descriptor: ()V
flags: ACC_STATIC
Code:
stack=10, locals=2, args_size=0
0: ldc #70 // String java.lang.Object
2: invokestatic #76 // Method java/lang/Class.forName:(Ljava/lang/String;)Ljava/lang/Class;
5: ldc #77 // String equals
7: iconst_1
8: anewarray #72 // class java/lang/Class
11: dup
12: iconst_0
13: ldc #70 // String java.lang.Object
15: invokestatic #76 // Method java/lang/Class.forName:(Ljava/lang/String;)Ljava/lang/Class;
18: aastore
19: invokevirtual #81 // Method java/lang/Class.getMethod:(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;
22: putstatic #20 // Field m1:Ljava/lang/reflect/Method;
25: ldc #83 // String jvmlearn.DynamicProxyTest$IHello
27: invokestatic #76 // Method java/lang/Class.forName:(Ljava/lang/String;)Ljava/lang/Class;
30: ldc #84 // String sayHello
32: iconst_0
33: anewarray #72 // class java/lang/Class
36: invokevirtual #81 // Method java/lang/Class.getMethod:(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;
39: putstatic #50 // Field m3:Ljava/lang/reflect/Method;
42: ldc #70 // String java.lang.Object
44: invokestatic #76 // Method java/lang/Class.forName:(Ljava/lang/String;)Ljava/lang/Class;
47: ldc #85 // String toString
49: iconst_0
50: anewarray #72 // class java/lang/Class
53: invokevirtual #81 // Method java/lang/Class.getMethod:(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;
56: putstatic #55 // Field m2:Ljava/lang/reflect/Method;
59: ldc #70 // String java.lang.Object
61: invokestatic #76 // Method java/lang/Class.forName:(Ljava/lang/String;)Ljava/lang/Class;
64: ldc #86 // String hashCode
66: iconst_0
67: anewarray #72 // class java/lang/Class
70: invokevirtual #81 // Method java/lang/Class.getMethod:(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;
73: putstatic #62 // Field m0:Ljava/lang/reflect/Method;
76: return
77: astore_1
78: new #90 // class java/lang/NoSuchMethodError
81: dup
82: aload_1
83: invokevirtual #93 // Method java/lang/Throwable.getMessage:()Ljava/lang/String;
86: invokespecial #96 // Method java/lang/NoSuchMethodError."<init>":(Ljava/lang/String;)V
89: athrow
90: astore_1
91: new #100 // class java/lang/NoClassDefFoundError
94: dup
95: aload_1
96: invokevirtual #93 // Method java/lang/Throwable.getMessage:()Ljava/lang/String;
99: invokespecial #101 // Method java/lang/NoClassDefFoundError."<init>":(Ljava/lang/String;)V
102: athrow
Exception table:
from to target type
0 77 77 Class java/lang/NoSuchMethodException
0 77 90 Class java/lang/ClassNotFoundException
Exceptions:
throws
}
主要看代理类的反编译代码即可,对照以上两个文件,再看 JDK 生成字节码的代码,可能会好一点点,截取sun.misc.ProxyGenerator#generateClassFile
方法中几个重要步骤:
jvmlearn.ProxyGenerator#generateConstructor
这是代理类的构造方法/**
* Generate the constructor method for the proxy class.
*/
private MethodInfo generateConstructor() throws IOException {
MethodInfo minfo = new MethodInfo(// 方法信息
"<init>", "(Ljava/lang/reflect/InvocationHandler;)V",
ACC_PUBLIC); DataOutputStream out = new DataOutputStream(minfo.code); code_aload(0, out); // aload0 0x2a code_aload(1, out); // aload1 0x2b out.writeByte(opc_invokespecial);// 调用超类构造方法 0xb7
out.writeShort(cp.getMethodRef( // 方法参数就是 InvocationHandler 对象,对应常量池里面的 #8
superclassName,
"<init>", "(Ljava/lang/reflect/InvocationHandler;)V")); // L 表示对象 out.writeByte(opc_return); // 0xb1 从当前方法返回 void minfo.maxStack = 10; // 操作数栈最大深度
minfo.maxLocals = 2; // 局部变量的存储空间 2 个 Slot
minfo.declaredExceptions = new short[0]; return minfo;
}
对照源码和字节码看这个构造方法的生成。
jvmlearn.ProxyGenerator.ProxyMethod#generateMethod
生成代理方法的字节码主要关注以下两个部分,对照字节码看:
- 拿到代理类父类中的
InvocationHandler
对象h
out.writeByte(opc_getfield);// 0xb4 获取指定类的实例域,并压入栈顶,这里就是拿到 handler
out.writeShort(cp.getFieldRef(// #50 Field m3:Ljava/lang/reflect/Method;
superclassName,
handlerFieldName, "Ljava/lang/reflect/InvocationHandler;"));// handlerFieldName = 'h' code_aload(0, out); out.writeByte(opc_getstatic); // 拿到静态变量,这里是 Method 类型,主要指的是代理方法
out.writeShort(cp.getFieldRef(
dotToSlash(className),
methodFieldName, "Ljava/lang/reflect/Method;"));
- 调用接口方法,也就是
invoke
方法
out.writeByte(opc_invokeinterface);
out.writeShort(cp.getInterfaceMethodRef(
"java/lang/reflect/InvocationHandler",
"invoke", // invoke 方法
"(Ljava/lang/Object;Ljava/lang/reflect/Method;" +
"[Ljava/lang/Object;)Ljava/lang/Object;"));
- 拿到代理类父类中的
上面两个部分的作用其实就是super.h.invoke(this, m3, (Object[])null);
这行代码的作用。
至此,JDK 动态代理的原理也基本清楚了
另外
因为 JDK 动态代理是基于接口实现的,所以要求代理类和被代理类实现了相同接口
缓存的使用,ConcurrentHashMap 正确的使用方式
优点:
相对于静态代理来说,动态代理的优点并不是省去了编写代理类的工作量,而是实现了可以在原始类(被代理类)和接口还未知的时候,就可以确定代理类的代理行为,当代理类和原始类脱离直接联系之后,就可以很灵活的被重用于不同的应用场景中。
对于 JDK 动态代理来说,就是可以先定义InvocationHandler
来确定代理行为,创建代理对象时,再将其传入。
参考:
- 『深入理解 Java 虚拟机』:第二版,9.2.3 字节码生成技术与动态代理的实现 P-282
- Java动态代理机制详解(JDK 和CGLIB,Javassist,ASM) - 我的程序人生 - CSDN博客
- JVM注解@CallSensitive - HEL_WOR的博客 - CSDN博客
JDK 动态代理的实现的更多相关文章
- JDK动态代理
一.基本概念 1.什么是代理? 在阐述JDK动态代理之前,我们很有必要先来弄明白代理的概念.代理这个词本身并不是计算机专用术语,它是生活中一个常用的概念.这里引用维基百科上的一句话对代理进行定义: A ...
- 静态代理和利用反射形成的动态代理(JDK动态代理)
代理模式 代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问.在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用. 静态代理 1.新建 ...
- Spring中的JDK动态代理
Spring中的JDK动态代理 在JDK1.3以后提供了动态代理的技术,允许开发者在运行期创建接口的代理实例.在Sun刚推出动态代理时,还很难想象它有多大的实际用途,现在动态代理是实现AOP的绝好底层 ...
- AOP学习心得&jdk动态代理与cglib比较
什么是AOP AOP(Aspect-OrientedProgramming,面向方面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善.OOP引入 ...
- JDK动态代理的实现原理
学习JDK动态代理,从源码层次来理解其实现原理参考:http://blog.csdn.net/jiankunking/article/details/52143504
- Java中的JDK动态代理
所谓代理,其实就是相当于一个中间人,当客户端需要服务端的服务时,不是客户直接去找服务,而是客户先去找代理,告诉代理需要什么服务,然后代理再去服务端找服务,最后将结果返回给客户. 在日常生活中,就拿买火 ...
- JDK动态代理与CGLib动态代理
1.JDK动态代理 JDK1.3以后java提供了动态代理技术,允许开发者在运行期创建接口的代理实例,动态代理是实现AOP的绝好底层技术. JDK的动态代理主要涉及到java.lang.reflect ...
- jdk动态代理实现
1.jdk动态代理的简单实现类 package com.proxy; import java.lang.reflect.InvocationHandler; import java.lang.refl ...
- java jdk动态代理
在面试的时候面试题里有一道jdk的动态代理是原理,并给一个事例直接写代码出来,现在再整理一下 jdk动态代理主要是想动态在代码中增加一些功能,不影响现有代码,实现动态代理需要做如下几个操作 1.首先必 ...
- JDK动态代理的实现及原理
Proxy.newProxyInstance(classloader,Class,invocationHandler) 调用getProxyClass0(loader, interfaces)生成代理 ...
随机推荐
- 北京智和信通IT运维管理系统二次开发服务提供商
随着云计算.大数据.物联网.移动互联网.人工智能.5G等高新技术的快速发展,数据中心及网络基础设施呈现出井喷式的增长模式,对设备商来说,多.快.好.省的实现定制化网络管理开发,可极大的扩充设备适用范围 ...
- MySql的命令介绍
1,连接数据库服务器命令 mysql -u 用户名 -p 密码 mysql是连接MySql数据库的命令,-u后跟用户名,-p后跟密码,如果登陆后展示"mysql",则表示登录成功. ...
- 传智播客C++视频学习笔记(5)
#include <iostream> using namespace std; void swapInt(int& a, int& b) { int temp = a; ...
- K8S 容器挂载Windows 文件共享目录,访问出现Permission denied的解决办法
K8S Pod,Docker 容器挂载Windows主机目录,访问相应的文件出现Premission denied的权限访问问题 Windows远程共享目录为//192.168.61.115/huas ...
- day1 对java的认识
对java的认识 1.java是一门跨平台的语言,由jvm进行预编译,转换成类似伪代码一样的东西,最后再转换成机器语言. 2.程序是由数据结构和算法构成,其他所有的工具类,方法都是为数据结构或者算法服 ...
- MFC/QT 学习笔记(一)——认识windows基础库
MFC (微软基础类库),Windows系统平台做GUI尚且OK,但不支持跨平台. //Windows消息机制: //1.SDK与API Software Development Kit,为特定软件框 ...
- springboot + mybatis 支持oracle和mysql切换含源码
1.springboot 启动类加入bean 如下 // DatabaseIdProvider元素主要是为了支持不同的数据库@Beanpublic DatabaseIdProvider getData ...
- 简单的说说tippyjs的使用
我们会接触到很多插件的使用,但是我们该如何的去使用呢,本人建议多学习英语,会对开发很有帮助的 为什么说是多去学习它,接下来我们就来说说: 当你没学习英语看到下面的官网是这样子的 当你会英语了,你就会觉 ...
- [.NET][C#] C#中的时间戳
Unix时间戳(Unix timestamp),或称Unix时间(Unix time).POSIX时间(POSIX time),是一种时间表示方式,定义为从格林威治时间1970年1月1日0时0分0秒( ...
- Docker 网络原理
引言 学习docker网络,可以带着下面两个问题来探讨 容器之间可以相互访问的原理 容器暴露端口后,通过宿主机访问到容器内应用,并且对于访问端而言不用感知容器存在的原理 Docker 本身的技术依赖L ...