余晖落尽暮晚霞,黄昏迟暮远山寻
本站
当前位置:网站首页 > 编程知识 > 正文

盘点Java框架常用的3大底层技术

xiyangw 2023-09-28 15:03 14 浏览 0 评论



本文所介绍的三个Java底层技术,有着逐渐递进的特点,Java注解中使用了JDK动态代理,而JDK动态代理中运用了Java反射。

Java注解

当我们阅读框架源码时,会看到其中包含着大量的注解,注解被广泛使用的原因在于,可以生成一些通用的“模板化”代码,来避免重复性的工作。使用注解的工作模式是,通过注解来描述我们的意图,然后用注解解析工具对注解进行解析。

【一】实验:自定义注解

首先我们通过 @interface关键字定义一个注解@Tree,定义注解时,需要定义两个内容:元注解,注解属性。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Tree {
 String name() default "tree";
}

元注解:

可以看到我在上面还添加了@Target和@Retention,这个是元注解,也就是添加到注解之上的注解,元注解有5种:

  • @Retention:声明注解的的存活时间
RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中。RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。
  • @Target:声明注解运用的地方
ElementType.ANNOTATION_TYPE 应用到注解ElementType.CONSTRUCTOR 应用到构造方法ElementType.FIELD 应用到属性ElementType.LOCAL_VARIABLE 应用到局部变量ElementType.METHOD 应用到方法ElementType.PACKAGE 应用到包ElementType.PARAMETER 应用到方法内的参数ElementType.TYPE 应用到类型(类、接口、枚举)
  • @Documented:将注解中的元素包含到 Javadoc 中
  • @Inherited:使用了这个注解的子类,就继承了该注解
  • @Repeatable:Java1.8新增特性,应用于注解的值可以取多个的场景

注解属性:

可以类比为普通类中的成员变量,注解中只有成员变量没有方法,在使用该注解时为该属性赋值,也可以在定义时赋默认值。

【注解处理器】

在注解处理器中,我们可以为注解定义逻辑,例如在下面的例子中,就是调用AnnotationClient类中所有方法,把@Tree中name属性值注入到方法中。

public class TreeProcessor {
 public void parseMethod(final Class<?> clazz) throws Exception {
 final Object obj = clazz.getConstructor(new Class[] {}).newInstance(new Object[] {});
 final Method[] methods = clazz.getDeclaredMethods();
 for (final Method method : methods) {
 final Tree my = method.getAnnotation(Tree.class);
 if (null != my) {
 method.invoke(obj, my.name());
 }
 }
 }
}

接下来做一下测试:

public class AnnotationClient {
 @Tree
 public static void sayHello(final String name) {
 System.out.println("==>> Hi, " + name + " [sayHello]");
 }
 @Tree(name = "Someone")
 public static void sayHelloToSomeone(final String name) {
 System.out.println("==>> Hi, " + name + " [sayHelloToSomeone]");
 }
 public static void main(final String[] args) throws Exception {
 final TreeProcessor treeProcessor = new TreeProcessor();
 treeProcessor.parseMethod(AnnotationClient.class);
 }
}



【二】深入理解注解

如果换一个角度理解注解:它的本质是一个继承了Annotation接口的接口

当我们通过getAnnotation()方法获取一个注解的时候,JDK会通过动态代理生成注解的代理类$Proxy1,这个代理类代理了Tree中的所有方法,其实本质上还是通过反射来实现的,但是我们逐步递进的分析,先研究注解,下一步研究JDK动态代理,最后才能到达反射。

JAVA 中专门用于处理注解的 Handler:

sun.reflect.annotation.AnnotationInvocationHandler

AnnotationInvocationHandler有如下几个属性:

private final Class<? extends Annotation> type;
private final Map<String, Object> memberValues;
private transient volatile Method[] memberMethods = null;

其中memberValues在初始化后,key是注解的属性值,value是我们为属性的赋值,可能你已经忘了我们在程序中是怎么做的了 : @Tree(name = "Someone")



所有动态代理类生成的方法,都会走这个invoke()方法。

而这个invoke方法做的事情,概括起来就是:通过方法名获取属性值。

public Object invoke(Object var1, Method var2, Object[] var3) {
 String var4 = var2.getName();
 Class[] var5 = var2.getParameterTypes();
 if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
 return this.equalsImpl(var3[0]);
 } else if (var5.length != 0) {
 throw new AssertionError("Too many parameters for an annotation method");
 } else {
 byte var7 = -1;
 switch(var4.hashCode()) {
 case -1776922004:
 if (var4.equals("toString")) {
 var7 = 0;
 }
 break;
 case 147696667:
 if (var4.equals("hashCode")) {
 var7 = 1;
 }
 break;
 case 1444986633:
 if (var4.equals("annotationType")) {
 var7 = 2;
 }
 }
 switch(var7) {
 case 0:
 return this.toStringImpl();
 case 1:
 return this.hashCodeImpl();
 case 2:
 return this.type;
 default:
 Object var6 = this.memberValues.get(var4);
 if (var6 == null) {
 throw new IncompleteAnnotationException(this.type, var4);
 } else if (var6 instanceof ExceptionProxy) {
 throw ((ExceptionProxy)var6).generateException();
 } else {
 if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
 var6 = this.cloneArray(var6);
 }
 return var6;
 }
 }
 }
 }

从代码中我们可以看到,如果匹配为toString(),hashCode(),annotationType,会专门返回相应的实现,否则就返回memberValues对应key中相应的value,这样看起来还是比较清晰的。

于是,在分析完注解的实现后,我们同时也有了下一步的研究目标:JDK动态代理的实现。

【三】servlet 3.0引入的新注解

此篇文章的缘起,是由于在阅读SpringBoot源码时看到了@HandlesTypes注解,这是Tomcat的SCI机制用到的一个注解,被它标明的类需要作为参数值传入到onStartup方法。这是Servlet3.0新增的特性,所以在这里也列举一下Servlet3.0中新增的一些注解:

  • HandlesTypes –该注解用来表示一组传递给ServletContainerInitializer的应用类。
  • HttpConstraint – 该注解代表所有HTTP方法的应用请求的安全约束,和ServletSecurity注释中定义的HttpMethodConstraint安全约束不同。
  • HttpMethodConstraint – 指明不同类型请求的安全约束,和ServletSecurity 注解中描述HTTP协议方法类型的注释不同。
  • MultipartConfig –该注解标注在Servlet上面,表示该Servlet希望处理的请求的 MIME 类型是 multipart/form-data。
  • ServletSecurity 该注解标注在Servlet继承类上面,强制该HTTP协议请求遵循安全约束。
  • WebFilter – 该注解用来声明一个Server过滤器;
  • WebInitParam – 该注解用来声明Servlet或是过滤器的中的初始化参数,通常配合 @WebServlet 或者 @WebFilter 使用。
  • WebListener –该注解为Web应用程序上下文中不同类型的事件声明监听器。
  • WebServlet –该注解用来声明一个Servlet的配置。

JDK动态代理

【一】实验:实现基于JDK的动态代理

【第一步】定义一个接口和实现类

public interface IPerson {
 void sayHello();
}
@Service
public class PersonServiceImpl implements IPerson {
 @Override
 public void sayHello() {
 System.out.println("Hello~~~");
 }
}
public class PersonInvocationHandler implements InvocationHandler {
 private Object target;
 public PersonInvocationHandler(Object target){
 this.target = target;
 }
 @Override
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 System.out.println("------前置逻辑-------------");
 // 执行相应的目标方法
 Object result = method.invoke(target,args);
 System.out.println("------后置逻辑-------------");
 return result;
 }
}

【第二步】两种使用JDK动态代理的方式

方式一:分为五个步骤
  • 实现InvocationHandler接口
  • 获得动态代理类:Proxy.getProxyClass
  • 获得代理类的构造方法:getConstructor(InvocationHandler.class)
  • 获得代理对象,传入自定义的InvocationHandler
  • 通过代理对象调用目标方法
方式二 - 提供了一种封装好的方法:
java.lang.reflect.Proxy#newProxyInstance(ClassLoader loader,
 Class<?>[] interfaces,
 InvocationHandler h)
三个参数代表的含义分别是:

ClassLoader loader:接口的类加载器

Class<?>[] interfaces:接口(可以是一组接口)

InvocationHandler h:自定义的InvocationHandler

下面是两种方式的示例:

public class DynamicProxyClient {
 public static void main(String[] args) throws Exception{
 // 方式一
 System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
 Class proxyClazz = Proxy.getProxyClass(IPerson.class.getClassLoader(),IPerson.class);
 Constructor constructor = proxyClazz.getConstructor(InvocationHandler.class);
 IPerson iPerson = (IPerson) constructor.newInstance(new PersonInvocationHandler(new PersonServiceImpl()));
 iPerson.sayHello();
 // 方式二
 IPerson proxyInstance = (IPerson) Proxy.newProxyInstance(IPerson.class.getClassLoader(), new Class[]{IPerson.class}, new PersonInvocationHandler(new PersonServiceImpl())); proxyInstance.sayHello();
}
}

综上所述,基于JDK的动态代理的实现方式:实现InvocationHandler接口,重写invoke()方法。

【二】源码分析

首先要说明一点,JDK动态代理只能代理接口,因为代理类本身已经继承了Proxy,而Java不允许多重继承,但是允许实现多个接口。

我们先来看那个封装好的newProxyInstance()方法源码:

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);
 }
 // 生成代理类
 Class<?> cl = getProxyClass0(loader, intfs);
 try {
 if (sm != null) {
 checkNewProxyPermission(Reflection.getCallerClass(), cl);
 }
 // 获取代理类的构造方法,其中constructorParams是InvocationHandler.class
 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) {
 throw new InternalError(e.toString(), e);
 } catch (InvocationTargetException e) {
 Throwable t = e.getCause();
 if (t instanceof RuntimeException) {
 throw (RuntimeException) t;
 } else {
 throw new InternalError(t.toString(), t);
 }
 } catch (NoSuchMethodException e) {
 throw new InternalError(e.toString(), e);
 }
 }
 private static final class ProxyClassFactory
 implements BiFunction<ClassLoader, Class<?>[], Class<?>>
 {
 // 组合成为代理类名字
 private static final String proxyClassNamePrefix = "$Proxy";
 private static final AtomicLong nextUniqueNumber = new AtomicLong();
 @Override
 public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
 Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
 for (Class<?> intf : interfaces) {
 
 Class<?> interfaceClass = null;
 try {
 interfaceClass = Class.forName(intf.getName(), false, loader);
 } catch (ClassNotFoundException e) {
 }
 if (interfaceClass != intf) {
 throw new IllegalArgumentException(
 intf + " is not visible from class loader");
 }
 
 if (!interfaceClass.isInterface()) {
 throw new IllegalArgumentException(
 interfaceClass.getName() + " is not an interface");
 }
 /*
 * Verify that this interface is not a duplicate.
 */
 if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
 throw new IllegalArgumentException(
 "repeated interface: " + interfaceClass.getName());
 }
 }
 String proxyPkg = null; // package to define proxy class in
 int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
 
 for (Class<?> intf : interfaces) {
 int flags = intf.getModifiers();
 if (!Modifier.isPublic(flags)) {
 accessFlags = Modifier.FINAL;
 String name = intf.getName();
 int n = name.lastIndexOf('.');
 String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
 if (proxyPkg == null) {
 proxyPkg = pkg;
 } else if (!pkg.equals(proxyPkg)) {
 throw new IllegalArgumentException(
 "non-public interfaces from different packages");
 }
 }
 }
 if (proxyPkg == null) {
 proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
 }
 long num = nextUniqueNumber.getAndIncrement();
 String proxyName = proxyPkg + proxyClassNamePrefix + num;
 // 生成代理类文件
 byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
 proxyName, interfaces, accessFlags);
 try {
 // native方法,返回代理类
 return defineClass0(loader, proxyName,
 proxyClassFile, 0, proxyClassFile.length);
 } catch (ClassFormatError e) {
 
 throw new IllegalArgumentException(e.toString());
 }
 }
 }

依稀记得,当年看《深入理解Java虚拟机时》,第七章有这样一段话,当时懵懵懂懂的我只是把它背了下来,

在类加载的加载阶段,虚拟机完成三件事情:

  • 通过一个类的全限定名来获取定义此类的二进制字节流
  • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
  • 在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据访问入口

由于虚拟机规范对这3点要求并不具体,所以实际的实现是非常灵活的,关于第1点,获取类的二进制字节流(class字节码)就有很多途径:

  • 从ZIP包获取,这是JAR、EAR、WAR等格式的基础
  • 从网络中获取,典型的应用是 Applet
  • 运行时计算生成,这种场景使用最多的是动态代理技术,在 java.lang.reflect.Proxy 类中,就是用了 ProxyGenerator.generateProxyClass 来为特定接口生成形式为 *$Proxy 的代理类的二进制字节流
  • 由其它文件生成,典型应用是JSP,即由JSP文件生成对应的Class类
  • 从数据库中获取等等

其中的“运行时计算生成 - ProxyGenerator.generateProxyClass”不就在这里吗~~

 public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {
 ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
 final byte[] var4 = var3.generateClassFile();
 if (saveGeneratedFiles) {
 AccessController.doPrivileged(new PrivilegedAction<Void>() {
 public Void run() {
 try {
 int var1 = var0.lastIndexOf(46);
 Path var2;
 if (var1 > 0) {
 Path var3 = Paths.get(var0.substring(0, var1).replace('.', File.separatorChar));
 Files.createDirectories(var3);
 var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class");
 } else {
 var2 = Paths.get(var0 + ".class");
 }
 Files.write(var2, var4, new OpenOption[0]);
 return null;
 } catch (IOException var4x) {
 throw new InternalError("I/O exception saving generated file: " + var4x);
 }
 }
 });
 }
 return var4;
 }

如果继续深入研究,下面的这些方法,基本都是一些IO文件写入的操作,就不粘贴源码了。

sun.misc.ProxyGenerator#generateClassFile
sun.misc.ProxyGenerator#addProxyMethod
......

至此,基于JDK的动态代理的分析就到此为止,其本质上是基于反射机制,在运行时创建代理类。所以下一步的研究对象,理所当然的就是Java的反射机制了。

Java反射

【一】简介

  • 允许运行时的Java程序获取自身信息,同时操作类或对象的内部属性,最通俗易懂的解释,就是让你根据一个String来得到你要的实体对象
  • 功能:
  1. 运行时判断对象所属类
  2. 运行时构造类的对象
  3. 运行时判断类的属性和方法
  4. 运行时调用任意一个类的方法

下面是相关的四个类,包含了我们定义一个类所能用到的所有属性和方法。

  • Class
  • Field
  • Method
  • Constructor

【二】常用方法

  1. 获取Class对象
  • Class类中的静态方法forName
  • 直接获取某一个对象的 class
  • 调用某个对象的 getClass() 方法
  1. 反射获取类的属性
  • getFields():Field[] - 只能获取public的字段,包括父类的
  • **getDeclaredFields():Field[] **- 只能获取自己声明的各种字段,包括public,protected,private
  1. 获取方法:
  • getMethod(String,Class<?>...):Method - 根据方法名和参数获取方法
  • getDeclaredMethods():Method[]
  1. invoke():
  2. 调用包装在当前Method对象中的方法
  3. Method和NativeMethodAccessorImpl类中的invoke()方法:
public Object invoke(Object obj, Object... args)
 throws IllegalAccessException, IllegalArgumentException,
 InvocationTargetException
 {
 if (!override) {
 if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
 Class<?> caller = Reflection.getCallerClass();
 checkAccess(caller, clazz, obj, modifiers);
 }
 }
 MethodAccessor ma = methodAccessor;
 if (ma == null) {
 ma = acquireMethodAccessor();
 }
 return ma.invoke(obj, args);
 }
public Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException {
 if (++this.numInvocations > ReflectionFactory.inflationThreshold() && !ReflectUtil.isVMAnonymousClass(this.method.getDeclaringClass())) {
 MethodAccessorImpl var3 = (MethodAccessorImpl)(new MethodAccessorGenerator()).generateMethod(this.method.getDeclaringClass(), this.method.getName(), this.method.getParameterTypes(), this.method.getReturnType(), this.method.getExceptionTypes(), this.method.getModifiers());
 this.parent.setDelegate(var3);
 }
 return invoke0(this.method, var1, var2);
 }

在上述方法中,有两点是比较重要的:

  1. 根据Class对象获取Method方法时,参数:方法名和参数的CLass类型
  2. 调用method.invoke(obj, args)

【三】原理探究

如果想要理解反射,就首先要对Java类加载的方式有了解。

我们知道,我们写的Java文件会被编译成.class文件(字节码文件),才能在JVM中执行,是通过类加载器进行加载,

java.lang.ClassLoader#loadClass(java.lang.String, boolean)是加载一个类的核心方法,

 protected Class<?> loadClass(String name, boolean resolve)
 throws ClassNotFoundException
 {
 synchronized (getClassLoadingLock(name)) {
 // First, check if the class has already been loaded
 Class<?> c = findLoadedClass(name);
 if (c == null) {
 long t0 = System.nanoTime();
 try {
 if (parent != null) {
 c = parent.loadClass(name, false);
 } else {
 c = findBootstrapClassOrNull(name);
 }
 } catch (ClassNotFoundException e) {
 // ClassNotFoundException thrown if class not found
 // from the non-null parent class loader
 }
 if (c == null) {
 // If still not found, then invoke findClass in order
 // to find the class.
 long t1 = System.nanoTime();
 c = findClass(name);
 // this is the defining class loader; record the stats
 sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
 sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
 sun.misc.PerfCounter.getFindClasses().increment();
 }
 }
 if (resolve) {
 resolveClass(c);
 }
 return c;
 }
 }

主要逻辑分为3步

  1. 检查是否已加载,如果已加载则直接返回
  2. 遵循双亲委派模式,先调用父类加载器加载器
  3. 如果父类加载器加载失败,调用findClass加载

当我们打开findClass方法时,会不会有些诧异,直接抛了个异常,这是因为ClassLoader是一个抽象类,是无法通过new创建一个对象的,也就是说,你无法通过ClassLoader类直接加载.class文件,只能写一个子类继承ClassLoader,然后覆盖findClass方法,定义自己的加载逻辑。

 protected Class<?> findClass(String name) throws ClassNotFoundException {
 throw new ClassNotFoundException(name);
 }

在自定义加载逻辑的时候,通常的做法是,读取一个字节数组byte[] b,然后调用ClassLoader的defineClass()方法,于是返回了一个Class对象

 private native Class<?> defineClass0(String name, byte[] b, int off, int len,
 ProtectionDomain pd);
 private native Class<?> defineClass1(String name, byte[] b, int off, int len,
 ProtectionDomain pd, String source);
 private native Class<?> defineClass2(String name, java.nio.ByteBuffer b,
 int off, int len, ProtectionDomain pd,
 String source);

Class类的内部类 ReflectionData,里面的内容与xxx.class文件中的内容映射,作用就是缓存从JVM中读取类的如下属性数据。同时由于一个类中包含的内容信息量过大,所以被拆成了四个类,也就是上面提到的- Class、Field、Method、Constructor

 private static class ReflectionData<T> {
 volatile Field[] declaredFields;
 volatile Field[] publicFields;
 volatile Method[] declaredMethods;
 volatile Method[] publicMethods;
 volatile Constructor<T>[] declaredConstructors;
 volatile Constructor<T>[] publicConstructors;
 // Intermediate results for getFields and getMethods
 volatile Field[] declaredPublicFields;
 volatile Method[] declaredPublicMethods;
 volatile Class<?>[] interfaces;
 // Value of classRedefinedCount when we created this ReflectionData instance
 final int redefinedCount;
 ReflectionData(int redefinedCount) {
 this.redefinedCount = redefinedCount;
 }
 }

关注小编,小编会每天为你分享有趣的技术文章哦。偷偷告诉你私信小编“学习”会有意想不到的惊喜哟~~!

相关推荐

华为交换机配置命令总结

1、配置文件相关命令[Quidway]displaycurrent-configuration显示当前生效的配置[Quidway]displaysaved-configuration显示fla...

解决账户无法登录的故障
解决账户无法登录的故障

在优化系统时错误地根据网上的提示,将唯一的Administrator账户设置为禁用,导致重启后无法进入系统。类似的故障还有使用组策略限制本地账户登录,导致重启后...

2023-10-11 17:16 xiyangw

S5720交换机登录提示初始密码存在安全风险
S5720交换机登录提示初始密码存在安全风险

问题描述客户每次登录输密码时,提示初始密码不安全,现在客户嫌麻烦想要去掉:Username:huaweiPassword:Warning:Theinitia...

2023-10-11 17:15 xiyangw

Springboot,Mybatis修改登录用户的密码
Springboot,Mybatis修改登录用户的密码

一、Mybatis.xml<updateid="changePassword"parameterType="string...

2023-10-11 17:15 xiyangw

PHP理论知识之沐浴更衣重看PHP基础(二)
PHP理论知识之沐浴更衣重看PHP基础(二)

接上篇,咱们继续讲解PHP基础八、标准PHP组件和框架的数量很多,随之产生的问题就是:单独开发的框架没有考虑到与其他框架的通信。这样对开发者和框架本身都是不利的...

2023-10-11 17:15 xiyangw

新鲜出炉UCloud云主机“数据方舟”评测报告(5)— — 关其城
新鲜出炉UCloud云主机“数据方舟”评测报告(5)— — 关其城

2015年10月29日,UCloud云主机黑科技——“数据方舟”功能正式上线,首轮内测随即开放。截止至2015年12月6日,我们共收到了534位用户的评测申...

2023-10-11 17:14 xiyangw

业余无线电Q简语及英文缩语
业余无线电Q简语及英文缩语

Q简语:语音通信及CW通信通用(加粗为常用)QRA电台何台QRB电台间之距离QRG告之正确频率QRH频率是否变动QRI发送音调QRJ能否收到QRK信号之可...

2023-10-11 17:14 xiyangw

非常详细!如何理解表格存储的多版本、生命周期和有效版本偏差
非常详细!如何理解表格存储的多版本、生命周期和有效版本偏差

表格存储在8月份推出了容量型实例,直接支持了表级别最大版本号和生命周期,高性能实例也将会在9月中旬支持这两个特性。那么,最大版本号和生命周期以及特有的...

2023-10-11 17:14 xiyangw

H3C交换机恢复出厂和各种基本配置,这20个要点你知道吗?
H3C交换机恢复出厂和各种基本配置,这20个要点你知道吗?

私信“干货”二字,即可领取138G伺服与机器人专属及电控资料!H3C交换机不知道密码如何恢复出厂设置1、开机启动,Ctrl+B进入bootrom菜单,选择恢复出...

2023-10-11 17:13 xiyangw

在使用移动支付系统的时候如何保护信息安全?

移动支付的方式近年来不断被更新,使得Venmo(据嘉丰瑞德理财师了解,此为美国的“支付宝”)之类的支付方式已经可以某种意义上代替随身携带现金了。但是你必须防范那些第三方应用程序轻松地获取你的银行卡以及...

界面控件DevExpress WinForms MVVM入门指南——登录表单(下)

从本文档中,您将了解如何向应用程序添加登录表单。在本节教程中着重讨论了如何实现此任务,这基本上是附加应用程序功能的一部分。DevExpressUniversalSubscription官方最新版免...

linux基础命令(一)
linux基础命令(一)

为啥要学linux?您可能熟悉WindowsXP、Windows7、Windows10和MacOSX等操作系统。Linux就是这样一种强大的操...

2023-10-11 17:13 xiyangw

MySQL数据库密码忘记了,怎么办?

#头条创作挑战赛#MySQL数据库密码忘记了且没有其他可以修改账号密码的账户时怎么办呢?登录MySQL,密码输入错误/*密码错误,报如下错误*/[root@TESTDB~]#mysql-u...

MobaXterm忘记Session密码,如何查看已保存的密码
MobaXterm忘记Session密码,如何查看已保存的密码

MobaXterm工具登录过SSH终端后,如果存储了Session(存储后再连接ssh的时候只需要输入账号不需要输入密码就可以直接连接上ssh),则可以...

2023-10-11 17:12 xiyangw

华为交换机密码丢失修改方法
华为交换机密码丢失修改方法

华为S2300交换机找回密码设置一、目的交换机的console和telnet密码丢失,无法登录设备。交换机已进行过数据配置,要把密码恢复而数据配置不能丢失。二、...

2023-10-11 17:12 xiyangw

取消回复欢迎 发表评论: