首页 文章 精选 留言 我的

精选列表

搜索[自动化研究],共10001篇文章
优秀的个人博客,低调大师

Java动态代理研究

浅说动态代理 关于java的代理模式,此处不过多讲解。所谓代理模式是指客户端并不直接调用实际的对象,而是通过调用代理,来间接的调用实际的对象。动态代理指被代理者委托代理者完成相应的功能,是拦截器的一种实现方式,其用于拦截类或接口,内部可通过判断实现对某个方法的拦截。 日常使用中可能经常需要在方法调用时进行拦截,如调用前记录一下调用开始时间,调用结束后记录结束时间,就可以很方便的计算出调用方法的业务逻辑处理耗时。 动态代理使用 简单的看下最简单的使用: 编写一个接口: package my.java.reflect.test; public interface Animal { void sayHello(); } 委托类 public class Dog implements Animal { public void sayHello() { System.out.println("wang wang!"); } } 拦截器 package my.java.reflect.test; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class MyInvocationHandler implements InvocationHandler { private Object target; public Object bind(Object realObj) { this.target = realObj; Class<?>[] interfaces = target.getClass().getInterfaces(); ClassLoader classLoader = this.getClass().getClassLoader(); return Proxy.newProxyInstance(classLoader, interfaces, this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("proxy method"); return method.invoke(target, args); } } 测试类 package my.java.reflect.test; import org.junit.Test; public class ProxyTest { @Test public void testNewProxyInstance() { Dog dog = new Dog(); Animal proxy = (Animal) new MyInvocationHandler().bind(dog); proxy.sayHello(); } } 输出 proxy method wang wang! 动态代理原理总结 之所以将原理先总结了,因为希望把原理先用最简洁的语言说清楚,再来深入分析,否则在深入分析阶段粘贴过多的源码可能会导致阅读兴趣下降。 通过Proxy#newProxyInstance方法得到代理对象实例; 这个代理对象有着和委托类一模一样的方法; 当调用代理对象实例的方法时,这个实例会调用你实现InvocationHandler里的invoke方法。 而这里,最复杂的显然是得到代理对象实例了,怎么得到的呢?来,看看源码! 动态代理原理 要了解java动态代理的原理只要从Proxy#newProxyInstance入手即可。 先看newProxyInstance方法,关注有中文注释的地方即可 @CallerSensitive public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { if (h == null) {// 没有实现InvocationHandler,直接失败 throw new NullPointerException(); } 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对象 */ Class<?> cl = getProxyClass0(loader, intfs); /* * Invoke its constructor with the designated invocation handler. */ try { // 拿到代理对象的构造方法 final Constructor<?> cons = cl.getConstructor(constructorParams); final InvocationHandler ih = h; if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) { // create proxy instance with doPrivilege as the proxy class may // implement non-public interfaces that requires a special permission return AccessController.doPrivileged(new PrivilegedAction<Object>() { public Object run() { return newInstance(cons, ih); } }); } else { // 构造出代理对象实例 return newInstance(cons, ih); } } catch (NoSuchMethodException e) { throw new InternalError(e.toString()); } } // 利用反射调用构造方法,产生代理实例,没什么好说的 private static Object newInstance(Constructor<?> cons, InvocationHandler h) { try { return cons.newInstance(new Object[] {h} ); } catch (IllegalAccessException | InstantiationException e) { throw new InternalError(e.toString()); } catch (InvocationTargetException e) { Throwable t = e.getCause(); if (t instanceof RuntimeException) { throw (RuntimeException) t; } else { throw new InternalError(t.toString()); } } } 以上这段代码的核心在 /* * Look up or generate the designated proxy class. * 查找或者生成代理类的Class对象 */ Class<?> cl = getProxyClass0(loader, intfs); 查看getProxyClass0方法 private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) { if (interfaces.length > 65535) {// 你的类如果实现了超过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 // 如果委托类的接口已经存在于缓存中,则返回,否则利用ProxyClassFactory产生一个新的代理类的Class对象 return proxyClassCache.get(loader, interfaces); } 这段话的核心很显然就是proxyClassCache.get(loader, interfaces),其中proxyClassCache是一个缓存: /** * a cache of proxy classes */ private static final WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory()); 继续看proxyClassCache.get(loader, interfaces),对应的是java.lang.reflect.WeakCache<K, P, V>#get,一大段代码就为了返回一个代理类的Class对象,关注中文注释处即可: 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 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 // 关键点在这里 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 { 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); } } } } 这段代码的核心就在V value = supplier.get();,通过这段代码,代理类的Class的对象就出来了。 查看subKeyFactory.apply(key, parameter),这段代码还是在WeakCache里: @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 { // 核心点在这里 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); // try replacing us with CacheValue (this should always succeed) if (valuesMap.replace(subKey, this, cacheValue)) { // put also in reverseMap reverseMap.put(cacheValue, Boolean.TRUE); } else { throw new AssertionError("Should not reach here"); } // successfully replaced us with new CacheValue -> return the value // wrapped by it return value; } 回看proxyClassCache可以知道valueFacotry对应的是ProxyClassFactory类,这是java.reflect.Proxy的内部类: @Override public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) { Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length); for (Class<?> intf : interfaces) { /* * Verify that the class loader resolves the name of this * interface to the same Class object. */ Class<?> interfaceClass = null; try { interfaceClass = Class.forName(intf.getName(), false, loader);// 看下接口对应的类文件有没有被ClassLoader加载 } catch (ClassNotFoundException e) { } if (interfaceClass != intf) { throw new IllegalArgumentException( intf + " is not visible from class loader"); } /* * Verify that the Class object actually represents an * interface. */ 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 /* * Record the package of a non-public proxy interface so that the * proxy class will be defined in the same package. Verify that * all non-public proxy interfaces are in the same package. */ for (Class<?> intf : interfaces) { int flags = intf.getModifiers(); if (!Modifier.isPublic(flags)) {// 接口不是public的,截取包名,保证代理类跟委托类同一个包下 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) {// 是public的接口,拼接成com.sun.proxy$Proxy,再拼接一个数字 // if no non-public proxy interfaces, use com.sun.proxy package proxyPkg = ReflectUtil.PROXY_PACKAGE + "."; } /* * Choose a name for the proxy class to generate. */ long num = nextUniqueNumber.getAndIncrement(); String proxyName = proxyPkg + proxyClassNamePrefix + num; /* * Generate the specified proxy class. */ // 核心,生成代理类的字节码 byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces); try { // 根据字节码生成代理类Class对象,native方法,看过类加载器的童鞋应该不陌生 return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); } catch (ClassFormatError e) { /* * A ClassFormatError here means that (barring bugs in the * proxy class generation code) there was some other * invalid aspect of the arguments supplied to the proxy * class creation (such as virtual machine limitations * exceeded). */ throw new IllegalArgumentException(e.toString()); } } 这段代码前面一大段就是检查、校验接口,然后利用ProxyGenerator生成代理类的字节码数组,接着将字节码封装成Class对象,就是代理类的Class对象了。生成字节码数组的代码就不详细说了。 将字节码数组写到文件里查看一下: byte[] data = ProxyGenerator.generateProxyClass(“Animal$Proxy”, new Class[] { Animal.class }); FileOutputStream out = new FileOutputStream("Animal$Proxy.class"); out.write(data); 利用jd-gui反编译工具可以看看代理类的源码: import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; public final class Animal$Proxy extends Proxy implements my.java.reflect.test.Animal { private static Method m3; private static Method m1; private static Method m0; private static Method m2; public Animal$Proxy(InvocationHandler paramInvocationHandler) { super(paramInvocationHandler); } public final void sayHello() { try { this.h.invoke(this, m3, null); return; } catch (Error | RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final boolean equals(Object paramObject) { try { return ((Boolean) this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue(); } catch (Error | RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final int hashCode() { try { return ((Integer) this.h.invoke(this, m0, null)).intValue(); } catch (Error | RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final String toString() { try { return (String) this.h.invoke(this, m2, null); } catch (Error | RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } static { try { m3 = Class.forName("my.java.reflect.test.Animal").getMethod("sayHello", new Class[0]); m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") }); m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); return; } catch (NoSuchMethodException localNoSuchMethodException) { throw new NoSuchMethodError(localNoSuchMethodException.getMessage()); } catch (ClassNotFoundException localClassNotFoundException) { throw new NoClassDefFoundError(localClassNotFoundException.getMessage()); } } } 可以看到生成的代理类有一个构造方法,参数是InvocationHandler,然后回看我们第一步分析的时候,得到代理类的Class对象后,会通过反射得到代理类的构造方法,接着调用构造方法,参数就是InvocationHandler。 在代理类的源码里,最值得注意的就是m3,这个对应的是Animal的sayHello方法,当我们通过MyInvocationHandler#bind方法得到代理对象实例后,调用代理对象Animal$Proxy的sayHello方法,就会执行: public final void sayHello() { try { this.h.invoke(this, m3, null); return; } catch (Error | RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } 正好是我们在MyInvocationHandler实现的invoke方法,这样就完成了代理的功能。 用一个总结收尾 之所以将原理先总结了,因为希望把原理先用最简洁的语言说清楚,再来深入分析,否则在深入分析阶段粘贴过多的源码可能会导致阅读兴趣下降。 通过Proxy#newProxyInstance方法得到代理对象实例; 这个代理对象有着和委托类一模一样的方法; 当调用代理对象实例的方法时,这个实例会调用你实现InvocationHandler里的invoke方法。 这里面无疑第一步是最复杂的,这里大概经历了: 利用参数中的接口,通过缓存或者利用ProxyGenerator生成字节码并生成代理类的Class对象; 通过反射得到代理对象的构造方法; 通过构造方法和InvocationHandler参数通过反射实例化出代理对象实例。

优秀的个人博客,低调大师

Java技术专题-JVM研究系列(1)Class文件分析和研究

背景介绍 Java源代码被编译为Class文件之后,Class文件结构是JVM加载Class,实例化对象,和进行方法调用的重要依据. 每个 Class 文件都是由 8 字节为单位的字节流组成,所有的 16 位、32 位和 64 位长度的数 据将被构造成 2 个、4 个和 8 个 8 字节单位来表示。多字节数据项总是按照 Big-Endian1的顺 序进行存储。在Java SDK中,访问这种格式的数据可以使用java.io.DataInput、 java.io.DataOutput 等接口和 java.io.DataInputStream 和 java.io.DataOutputStream 等类来实现。 Class 文件的内容可用一组私有数据类型来表示,它们包括 u1,u2 和 u4,分别代 表了1、2和4个字节的无符号数。在 Java SDK 中这些类型的数据可以通过实现接口 java.io.DataInput 中的 readUnsignedByte、readUnsignedShort 和 readInt 方法进 行读取。 ClassFile 结构 每一个 Class 文件对应于一个如下所示的 ClassFile 结构体,其包含的属性如下表: 几个问题 运行时常量池和静态常量池有什么区别? Class文件里面都有什么内容? Class文件反汇编之后的格式里面分别有什么,尝试解读里面方法中的汇编指令 本地变量表和操作数栈是如何工作? 查看Class文件 以十六进制查看Class文件 技巧: vim + xxd = 十六进制编辑器vim + xxd = 十六进制编辑器 vim -b xxx.class 可以以二进制将class文件打开; vim内调用::%!xxd 以十六进制显示当前文件; 修改完成之后,如果想保存,执行以下命令把十六进制转换回二进制- :%!xxd -r 输出包括行号,本地变量反汇编等信息 javap -v -verbose:输出附加信息(包括行号、本地变量表、反汇编等信息) -c:对代码进行反汇编 如: javap -c xxx.class javap -verbose Test.class 更多关于javap的介绍:https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javap.html 关于反汇编: 反汇编(Disassembly):把目标代码转为汇编代码的过程,也可以说是把机器语言转换为汇编语言代码、低级转高级的意思。软件一切神秘的运行机制全在反汇编代码里面。 Class文件解读 JVM规范中的Class文件解读 ClassFile { u4 magic; // 魔数 u2 minor_version; // 副版本号 u2 major_version; // 主版本号 u2 constant_pool_count; // 常量池计数器 cp_info constant_pool[constant_pool_count-1]; // 常量池数据区 u2 access_flags; // 访问标志 u2 this_class; // 类索引 u2 super_class; // 父类索引 u2 interfaces_count; // 接口计数器 u2 interfaces[interfaces_count]; // 接口表 u2 fields_count; // 字段计数器 field_info fields[fields_count]; // 字段表 u2 methods_count; // 方法计数器 method_info methods[methods_count]; // 方法表 u2 attributes_count; // 属性计数器 attribute_info attributes[attributes_count]; // 属性表 } Class文件是一组以8位字节为基础单位的二进制流。类结构有两种数据类型: 无符号数:无符号数属于基本属性类型,用u1, u2, u4, u8分别代表1个字节,2个字节,4个字节和8个字节的无符号数,可以用它描述数字、索引引用、数量值或者utf8编码的字符串值; 表:由多个无符号数或者其他表作为数据项构成的复合数据类型,以命名_info结尾。 cafe babe 0000 0034 001d 0a00 0600 0f09 0010 0011 0800 120a 0013 0014 0700 1507 0016 0100 063c 696e 6974 3e01 0003 2829 5601 0004 436f 6465 0100 0f4c 696e 654e 756d 6265 7254 6162 6c65 0100 046d 6169 6e01 0016 285b 4c6a 6176 612f 6c61 6e67 2f53 7472 696e 673b 2956 0100 0a53 6f75 7263 6546 696c 6501 0008 4c6f 672e 6a61 7661 0c00 0700 0807 0017 0c00 1800 1901 000c 6865 6c6c 6f20 776f 726c 6421 0700 1a0c 001b 001c 0100 1263 6f6d 2f68 656c 6c6f 2f74 6573 742f 4c6f 6701 0010 6a61 7661 2f6c 616e 672f 4f62 6a65 6374 0100 106a 6176 612f 6c61 6e67 2f53 7973 7465 6d01 0003 6f75 7401 0015 4c6a 6176 612f 696f 2f50 7269 6e74 5374 7265 616d 3b01 0013 6a61 7661 2f69 6f2f 5072 696e 7453 7472 6561 6d01 0007 7072 696e 746c 6e01 0015 284c 6a61 7661 2f6c 616e 672f 5374 7269 6e67 3b29 5600 2100 0500 0600 0000 0000 0200 0100 0700 0800 0100 0900 0000 1d00 0100 0100 0000 052a b700 01b1 0000 0001 000a 0000 0006 0001 0000 0003 0009 000b 000c 0001 0009 0000 0025 0002 0001 0000 0009 b200 0212 03b6 0004 b100 0000 0100 0a00 0000 0a00 0200 0000 0500 0800 0600 0100 0d00 0000 0200 0e 根据以上的Class文件结构,我们可以梳理出以下的Class文件结构图: 魔数 magic 用于标识这个文件的格式,Class文件格式的魔数为 0xCAFEBABE。 Class 文件的第 1 - 4 个字节代表了该文件的魔数。 魔数的唯一作用是确定这个文件是否为一个能被虚拟机所接受的 Class 文件。魔数值固定为 0xCAFEBABE,不会改变。魔数的唯一作用是确定这个文件是否为一个能被虚拟机所接受的 Class 文件。魔数值固定为 0xCAFEBABE,不会改变。 副版本号 minor_version,主版本号 major_version Class 文件的第 5 - 6 个字节代表了 Class 文件的副版本号。 Class 文件的第 7 - 8 个字节代表了 Class 文件的主版本号。 主版本号和次版本号共同决定了类文件格式的版本。 如果类文件的主版本号为M,次版本号为m,则将其类文件格式的版本表示为M.m。 因此,可以按字典顺序对类文件格式版本进行排序,例如1.5 <2.0 <2.1。minor_version和major_version项目的值是此类文件的次要版本号和主要版本号。 Java 虚拟机实例只能支持特定范围内的主版本号(Mi 至 Mj)和 0 至特定范围 内(0 至 m)的副版本号。 假设一个 Class 文件的格式版本号为 V,仅当 Mi.0 ≤ v ≤ Mj.m 成立时,这个 Class 文件才可以被此 Java 虚拟机支持。不同版本的 Java 虚拟机实现 支持的版本号也不同,高版本号的 Java 虚拟机实现可以支持低版本号的 Class 文件。 下表列出了各个版本 JDK 的十六进制版本号信息: 上述 class 文件 0000 0034 对应的就是表格中的 JDK1.8。 常量池计数器 constant_pool_count 紧跟版本信息之后的是常量池信息,其中前 2 个字节表示常量池计数器,其后的不定长数据则表示常量池的具体信息。constant_pool表的索引[1,constant_pool_count-1] 常量池描述着整个Class文件中所有的字面量信息。常量池计数器(constant_pool_count)的值等于常量池(constant_pool)表中的条目数加一。 如果constant_pool索引大于零且小于constant_pool_count,则该索引被视为有效。对于 long 和 double 类型有例外情况。 在 Class 文件的常量池中,所有的 8 字节的常量都占两个表成员(项)的空间。如果一个 CONSTANT_Long_info 或 CONSTANT_Double_info 结构的项在常量池中的索引为 n,则常量池中下一个有效的项的索引为 n+2,此时常量池中索引为 n+1 的项有效但必须被认为不可用。 class 文件字节码对应的内容是:001d,其值为 29,表示一共有 29 - 1 = 28 个常量。 常量池表 constant_pool[] 紧跟着常量池计数器后面就是 28 个常量了,因为每个常量都对应不同的类型,需要一个个具体分析。 constant_pool[]是一个结构表,表示各种字符串常量,类和接口名称,字段名称以及在ClassFile结构及其子结构中引用的其他常量。 每个constant_pool表条目的格式由其第一个“标签”字节指示。 所有类型的常量池表项目有以下通用的格式: cp_info { u1 tag; u1 info[]; } 常量池中,每个 cp_info 项的格式必须相同,它们都以一个表示 cp_info 类型的单字节 “tag”项开头。后面 info[]项的内容 tag 由的类型所决定。tag 有效的类型和对应的取值在下图表示。每个 tag 项必须跟随 2 个或更多的字节,这些字节用于给定这个常量的信息,附加字节的信息格式由 tag 的值决定。 常量池中的14种常量结构 这些 cp_info 表结构又有不同的数据结构,其对应的数据结构如下图所示。 接下来我们开始分析上述 Log.class 文件每个字节的含义,前面第一句话已经说了,紧跟着常量池计数器后面的就是常量池了。下面开始分析: 第 1 个常量 紧接着 001d 的后一个字节为 0A,为十进制数字 10,查表可知其为方法引用类型(CONSTANT_Methodref_info)的常量。在 cp_info 中结构如下所示: 查找的方式是先确定 tag 值,tag 值判断当前属于哪一个常量。这里 tag 为 10。 然后看其结构显示还有两个 U2 的index,说明后面 4 个字节都是属于第一个常量,其中第 2 - 3 个字节表示类信息,第 4 - 5 个字节表示名称及类描述符。 接下来我们取出这部分的数据:0a 0600 000f : 该常量项: 第 2 - 3 个字节,其值为 00 06,表示指向常量池第 6 个常量所表示的信息。根据后面我们分析的结果知道第 6 个常量是 java/lang/Object。 第 4 - 5 个字节,其值为 000f,表示指向常量池第 15 个常量所表示的信息,根据 javap 反编译出来的信息可知第 15 个常量是 <init>:()V。 将两者组合起来:java/lang/Object.<init>:V,即 Object 的 init 初始化方法。 javap -v Log.class Classfile /Users/xxx/Desktop/Log.class Last modified 2020-1-8; size 427 bytes MD5 checksum 745be5a6df4d9554e783dbbcecaf9b6d Compiled from "Log.java" public class com.hello.test.Log minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #6.#15 // java/lang/Object."<init>":()V #2 = Fieldref #16.#17 // java/lang/System.out:Ljava/io/PrintStream; #3 = String #18 // hello world! #4 = Methodref #19.#20 // java/io/PrintStream.println:(Ljava/lang/String;)V #5 = Class #21 // com/hello/test/Log #6 = Class #22 // java/lang/Object #7 = Utf8 <init> #8 = Utf8 ()V #9 = Utf8 Code #10 = Utf8 LineNumberTable #11 = Utf8 main #12 = Utf8 ([Ljava/lang/String;)V #13 = Utf8 SourceFile #14 = Utf8 Log.java #15 = NameAndType #7:#8 // "<init>":()V #16 = Class #23 // java/lang/System #17 = NameAndType #24:#25 // out:Ljava/io/PrintStream; #18 = Utf8 hello world! #19 = Class #26 // java/io/PrintStream #20 = NameAndType #27:#28 // println:(Ljava/lang/String;)V #21 = Utf8 com/hello/test/Log #22 = Utf8 java/lang/Object #23 = Utf8 java/lang/System #24 = Utf8 out #25 = Utf8 Ljava/io/PrintStream; #26 = Utf8 java/io/PrintStream #27 = Utf8 println #28 = Utf8 (Ljava/lang/String;)V { public com.hello.test.Log(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 3: 0 public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #3 // String hello world! 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return LineNumberTable: line 5: 0 line 6: 8 } SourceFile: "Log.java" 其实从上面的结果也可以看出来,第一个常量对应的是第6,15个常量,组合起来的含义后面注释也写着了。 其他很多常量都是类似的,接下来我们看看字符串是怎么来得。 第 21 个常量 第 21 个常量,数据为 0100 1263 6f6d 2f68 656c 6c6f 2f74 6573 742f 4c6f 67 这里 tag 值是 01,对应的结构如下: length 是 u2,对应着 0012,说明后面跟着 18 个字节:63 6f6d 2f68 656c 6c6f 2f74 6573 742f 4c6f 67;查 ASCII 表可得 63-c, 6f-o, 6d-m, 2f-/ ··· 4c-L,6f-o, 67-g, 组合起来就是:com/hello/test/Log 。 相信通过上面两个例子,大家就知道如何去分析常量池里面的索引了。但很多时候我们可以借助 JDK 提供的 javap 命令直接查看 Class 文件的常量池信息,但是手动分析能够让你更加了解结果为啥是这样的。其实 javap 出来的就是人家分析总结好的。 access_flags 访问标志 在常量池结束之后,紧接着的两个字节代表类或接口的访问标(access_flags)。这里的数据为 00 21。 access_flags 是一种掩码标志,用于表示某个类或者接口的访问权限及基础属性。access_flags 的取值范围和相应含义见下表: 第一列是标记名; 第二列是对应的值; 第三列是对应的说明。 带有 ACC_SYNTHETIC 标志的类,意味着它是由编译器自己产生的而不是由程序员 编写的源代码生成的。 带有 ACC_ENUM 标志的类,意味着它或它的父类被声明为枚举类型。 带有 ACC_INTERFACE 标志的类,意味着它是接口而不是类,反之是类而不是接口。 如果一个 Class 文件被设置了 ACC_INTERFACE 标志,那么同时也得设置 ACC_ABSTRACT 标志。同时它不能再设置ACC_FINAL、 ACC_SUPER 和 ACC_ENUM 标志。 ANNOTATION注解类型必定带有 ACC_ANNOTATION 标记,如果设置了 ANNOTATION 标记, ACC_INTERFACE 也必须被同时设置。如果没有同时设置 ACC_INTERFACE 标记, 那么这个 Class 文件可以具有表 4.1 中的除 ACC_ANNOTATION 外的所有其它标记。 当然 ACC_FINAL 和 ACC_ABSTRACT 这类互斥的标记除外。 ACC_SUPER 标志用于确定该 Class 文件里面的 invokespecial 指令使用的是哪 一种执行语义。目前 Java 虚拟机的编译器都应当设置这个标志。ACC_SUPER 标记 是为了向后兼容旧编译器编译的 Class 文件而存在的,在 JDK1.0.2 版本以前的编 译器产生的 Class 文件中,access_flag 里面没有 ACC_SUPER 标志。同时, JDK1.0.2 前的 Java 虚拟机遇到 ACC_SUPER 标记会自动忽略它。 在表中没有使用的 access_flags 标志位是为未来扩充而预留的,这些预留的标志为在编译器中会被设置为 0, Java 虚拟机实现也会自动忽略它们。 类索引、父类索引、接口索引 在访问标记后,则是类索引、父类索引、接口索引的数据,这里数据为:00 05 、00 06 、00 00。 类索引和父类索引都是一个 u2 类型的数据,而接口索引集合是一组 u2 类型的数据的集合,这个可以由前面 Class 文件的构成可以得到。 Class 文件中由这三项数据来确定这个类的继承关系。 this_class 类索引 类索引,this_class 的值必须是对 constant_pool 表中项目的一个有效索引值。 constant_pool 表在这个索引处的项必须为 CONSTANT_Class_info 类型常量,表示这个 Class 文件所定义的类或接口。这里的类索引是 00 05 表示其指向了常量池中第 5 个常量,通过我们之前的分析,我们知道第 5 个常量其最终的信息是 Log 类。 super_class 父类索引 super_class 值必须为 0 或者对 constant_pool 表中项目的一个有效索引值。 如果它的值不为 0,那 constant_pool 表在这个索引处的项 必须为 CONSTANT_Class_info 类型常量,表示这个 Class 文件所定义的 类的直接父类。 当前类的直接父类,以及它所有间接父类的 access_flag 中都不能有 ACC_FINAL 标记。对于接口来说,它的 Class 文件的 super_class 项的值必须是 对 constant_pool 表中项目的一个有效索引值。 constant_pool 表在这个索引处的 项必须为代表 java.lang.Object 的 CONSTANT_Class_info 类型常量。 如果 Class 文件的 super_class 的值为 0,那这个 Class 文件只可能是定义的是 java.lang.Object 类,只有它是唯一没有父类的类。这里的父类索引是 00 06 表示其指向了常量池中第 6 个常量,通过我们之前的分析,我们知道第 6 个常量其最终的信息是 Object 类。 因为其并没有继承任何类,所以 Demo 类的父类就是默认的 Object 类。 interfaces_count 接口计数器 interfaces_count 的值表示当前类或接口的直接父接口数量。 interfaces[] 接口表 interfaces[] 数组中的每个成员的值必须是一个对 constant_pool 表中项 目的一个有效索引值,它的长度为 interfaces_count。每个成员 interfaces[i] 必 须为CONSTANT_Class_info类型常量,其中0 ≤ i < interfaces_count。 在 interfaces[]数组中,成员所表示的接口顺序和对应的源 代码中给定的接口顺序(从左至右)一样,即 interfaces[0]对应的是源代码中最左 边的接口。 这里 Log 类的字节码文件中,因为并没有实现任何接口,所以紧跟着父类索引后的两个字节是0x0000,这表示该类没有实现任何接口。因此后面的接口索引表为空。 未完待续......

优秀的个人博客,低调大师

Java技术专题-JVM研究系列(9)JVM深入研究挖掘课题

JVM执行引擎的效率提升 JVM是如何在保证可移植性的前提下提供高执行效率的? Java跨平台性 Java程序最为常见的执行方式,是预先编译为一种名为 Java 字节码的中间代码格式。这种代码格式无法直接运行在 CPU 之上,而是需要借助 JVM 来执行。换句话说,只要某个平台提供了合乎JVM 规范的实现,它便能执行这份Java 字节码。这也就是我们经常说的“一次编写,到处运行”。 Java执行方式 主流的 OpenJDK/OracleJDK 中所提供的 JVM 叫做HotSpot。它同时采用了解释执行和即时编译。 解释执行就好比同声传译,JVM一边理解输入的字节码一边解释字节码生成机器码向CPU发出指令序列; 即时编译则是“磨刀不误砍柴工”,JVM 会在运行过程中将 热点代码编译成为可直接执行二进制代码。 这种混合执行模式是建立在程序符合二八定律的假设上,即百分之二十的代码占据了百分之八十的计算资源。对于不常用代码,我们无需耗费时间将其编译成二进制代码,而是采取解释执行的方式运行;另一方面,对于仅占据小部分的热点代码,JVM 则会花费时间将其编译为二进制代码,以达到理想的运行效率。 Java异常捕获 编译生成的Java字节码中,每个方法都附带一个异常表。异常表中的每一行均定义了一条异常执行路径,其中包括规定捕获范围的起始字节码索引、终止(不包含)字节码索引,异常处理代码的起始字节码索引,以及所捕获的异常类型。 当程序触发异常时,JVM会从上至下遍历异常表中的所有条目。当触发异常的字节码的索引值在某行异常表条目的捕获范围内,JVM 会判断所抛出的异常和该条目想要捕获的异常是否匹配。如果匹配,JVM 会将控制流转移至该条目所指向的异常处理代码。 上述异常捕获机制还被用于 finally 从句的实现。通常,Java程序的编译器javac会复制多份 finally 代码块,放置于生成的 Java 字节码之中,然后通过生成多行异常表条目,来实现完整的 finally 逻辑。 Java反射调用 默认情况下,反射调用首先会被委派给native方法来进行。可想而知,其运行效率低下。当某个反射调用的调用次数达到 15 之后,JDK 代码断定该调用属于热点调用。 继而,JDK 将动态生成直接调用目标方法的字节码,并将反射调用的委派对象由原本的 native 方法实现切换至该动态生成的实现。这种方式的运行效率相对于 native 方法来说要高很多。 之所以 JDK 不从一开始便采用动态生成字节码的方式,主要是因为生成过程需要耗费一定的时间。对于那些整个生命周期中仅执行数次的反射调用,动态生成字节码将得不偿失。 然而,即便是直接调用目标方法的动态实现,其峰值性能也无法跟真正的直接调用相媲美。这背后涉及到即时编译中的虚方法内联。 JVM垃圾回收 目前 JVM 的主流垃圾回收器采取的都是可达性分析算法。 该算法的实质是将一系列被称为GC Roots的对象作为初始的存活对象合集,然后从该合集出发探索所有能够被该集合引用到的对象,并标记为存活对象。当标记阶段结束之后,未被标记到的对象便是可以清除的。 传统的垃圾回收算法在标记、清除过程中需要中止其他应用线程,即所谓的 Stop-The-World。新型的垃圾回收算法,如 CMS、G1 以及 ZGC,尽可能地实现并发标记、清除,从而让Stop-The-World 的时间长度可控。 垃圾回收的另一基础思想则是分代回收。JVM 会将新生成的对象划为新生代,而将在多次垃圾回收中存活下来的对象划为老年代。JVM 会为不同的分代设置不同的回收算法,从而达到新生代多收集、快收集,老年代少收集、全收集的目标。 Java内存模型 现代计算机多为对称多处理器的体系架构。每个处理器均有独立的寄存器组和缓存(这在 Java 内存模型中被抽象为工作内存);多个处理器可同时执行同一进程中的不同线程。 不同线程可能访问同一变量或对象。如果任由编译器或处理器对这些访问进行优化,则很可能出现在单线程执行思维下无法想象的问题。因此,Java语言规范引入了Java内存模型,通过定义多项规则对编译器和处理器进行限制。 这些规则所体现的最为重要的属性便是可见性,即对某一变量的访问能否被同一线程的其他操作,或者不同线程所观测到。 Java内存模型引入了多种 happens-before 关系,以实现上述可见性。以volatile字段为例,对其的写操作 happens before这之后的读操作,也就是说,我们总能读到volatile字段的最新值。 JVM对象锁 重量级锁是最为基础、最为低效的对象锁实现。JVM会阻塞加锁失败的线程,并且在目标锁被释放的时候,唤醒这些线程。我们用等红灯作类比。Java线程进入阻塞状态相当于熄火停车,再次点火启动必然耗费时间。JVM 会在进入阻塞状态之前进行自旋,也就是怠速停车。如果目标锁能够在短时间内被释放出来,该线程便能够不进入阻塞状态,直接获取该锁。 重量级锁针对的是多个线程同时竞争同一把锁的场景。在现实中,多个线程可能在不同时间段持有同一把锁。为了应对这种没有锁竞争的情况,JVM 采用了轻量级锁机制。在加锁时,JVM 将在锁对象处做标记,指向当前线程的栈上;在解锁时,上述标记会被清除。如果某线程在请求锁时,发现该锁为轻量级锁,并且指向另一线程所对应的栈,那么它会将该锁膨胀为重量级锁。 偏向锁所应对的场景则更为乐观:至始至终只有一个线程请求某把锁。JVM 采取的做法是在第一次加锁时为锁对象做标记,使其指向当前线程的地址;在解锁时则不做任何操作。如果下一次请求该锁的仍是同一线程,便直接跳过标记过程;否则,JVM 会将该锁膨胀为轻量级锁。

优秀的个人博客,低调大师

python自动化巡检

#!/usr/bin/envpython #-*-coding:utf-8-*- #__author__="chenhao" #Email:312779641@qq.com #Date:2017/8/22 importparamiko defsftp_exec_command(host,port,username,password,command): ssh=paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) ssh.connect(hostname=host,port=port,username=username,password=password) stdin,stdout,stderr=ssh.exec_command(command) list=[] foriteminstdout.readlines(): list.append(item.strip()) returnlist ssh.close() defsftp_down_file(host,port,username,password,server_path,local_path): try: t=paramiko.Transport((host,port)) t.connect(username=username,password=password) sftp=paramiko.SFTPClient.from_transport(t) sftp.get(server_path,local_path) t.close() exceptExceptionase: print(e) if__name__=='__main__': hosts_file=open('./hosts/host.info','r') forlineinhosts_file.readlines(): ifline[0:1]=='#':continue line=line.strip('\n') items=line.split() port=22 host=items[0] username=items[1] password=items[2] sftp_exec_command(host,port,username,password,"sh/root/xunjian.sh") n=sftp_exec_command(host,port,username,password,"ls-t/root/log/|head-1") filename="/root/log/%s"%(n[0]) print(filename) sftp_down_file(host,port,username,password,filename,"D:/大数据数据/%s"%(n[0])) 执行后自动巡检python脚本 shell运维脚本 #!/bin/bash #主机信息每日巡检 IPADDR=$(ifconfigeth0|grep'inetaddr'|awk-F'[:]''{print$13}') #环境变量PATH没设好,在cron里执行时有很多命令会找不到 exportPATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin source/etc/profile [$(id-u)-gt0]&&echo"请用root用户执行此脚本!"&&exit1 centosVersion=$(awk'{print$(NF-1)}'/etc/redhat-release) VERSION="2017.08.22" #日志相关 PROGPATH=`echo$0|sed-e's,[\\/][^\\/][^\\/]*$,,'` [-f$PROGPATH]&&PROGPATH="." LOGPATH="$PROGPATH/log" [-e$LOGPATH]||mkdir$LOGPATH RESULTFILE="$LOGPATH/HostDailyCheck-$IPADDR-`date+%Y%m%d`.txt" #定义报表的全局变量 report_DateTime=""#日期ok report_Hostname=""#主机名ok report_OSRelease=""#发行版本ok report_Kernel=""#内核ok report_Language=""#语言/编码ok report_LastReboot=""#最近启动时间ok report_Uptime=""#运行时间(天)ok report_CPUs=""#CPU数量ok report_CPUType=""#CPU类型ok report_Arch=""#CPU架构ok report_MemTotal=""#内存总容量(MB)ok report_MemFree=""#内存剩余(MB)ok report_MemUsedPercent=""#内存使用率%ok report_DiskTotal=""#硬盘总容量(GB)ok report_DiskFree=""#硬盘剩余(GB)ok report_DiskUsedPercent=""#硬盘使用率%ok report_InodeTotal=""#Inode总量ok report_InodeFree=""#Inode剩余ok report_InodeUsedPercent=""#Inode使用率ok report_IP=""#IP地址ok report_MAC=""#MAC地址ok report_Gateway=""#默认网关ok report_DNS=""#DNSok report_Listen=""#监听ok report_Selinux=""#Selinuxok report_Firewall=""#防火墙ok report_USERs=""#用户ok report_USEREmptyPassword=""#空密码用户ok report_USERTheSameUID=""#相同ID的用户ok report_PasswordExpiry=""#密码过期(天)ok report_RootUser=""#root用户ok report_Sudoers=""#sudo授权ok report_SSHAuthorized=""#SSH信任主机ok report_SSHDProtocolVersion=""#SSH协议版本ok report_SSHDPermitRootLogin=""#允许root远程登录ok report_DefunctProsess=""#僵尸进程数量ok report_SelfInitiatedService=""#自启动服务数量ok report_SelfInitiatedProgram=""#自启动程序数量ok report_RuningService=""#运行中服务数ok report_Crontab=""#计划任务数ok report_Syslog=""#日志服务ok report_SNMP=""#SNMPOK report_NTP=""#NTPok report_JDK=""#JDK版本ok functionversion(){ echo"" echo"" echo"系统巡检脚本:Version$VERSION" } functiongetCpuStatus(){ echo"" echo"" echo"############################CPU检查#############################" Physical_CPUs=$(grep"physicalid"/proc/cpuinfo|sort|uniq|wc-l) Virt_CPUs=$(grep"processor"/proc/cpuinfo|wc-l) CPU_Kernels=$(grep"cores"/proc/cpuinfo|uniq|awk-F':''{print$2}') CPU_Type=$(grep"modelname"/proc/cpuinfo|awk-F':''{print$2}'|sort|uniq) CPU_Arch=$(uname-m) echo"物理CPU个数:$Physical_CPUs" echo"逻辑CPU个数:$Virt_CPUs" echo"每CPU核心数:$CPU_Kernels" echo"CPU型号:$CPU_Type" echo"CPU架构:$CPU_Arch" #报表信息 report_CPUs=$Virt_CPUs#CPU数量 report_CPUType=$CPU_Type#CPU类型 report_Arch=$CPU_Arch#CPU架构 } functiongetMemStatus(){ echo"" echo"" echo"############################内存检查############################" if[[$centosVersion<7]];then free-mo else free-h fi #报表信息 MemTotal=$(grepMemTotal/proc/meminfo|awk'{print$2}')#KB MemFree=$(grepMemFree/proc/meminfo|awk'{print$2}')#KB letMemUsed=MemTotal-MemFree MemPercent=$(awk"BEGIN{if($MemTotal==0){printf100}else{printf\"%.2f\",$MemUsed*100/$MemTotal}}") report_MemTotal="$((MemTotal/1024))""MB"#内存总容量(MB) report_MemFree="$((MemFree/1024))""MB"#内存剩余(MB) report_MemUsedPercent="$(awk"BEGIN{if($MemTotal==0){printf100}else{printf\"%.2f\",$MemUsed*100/$MemTotal}}")""%"#内存使用率% } functiongetDiskStatus(){ echo"" echo"" echo"############################磁盘检查############################" df-hiP|sed's/Mountedon/Mounted/'>/tmp/inode df-hTP|sed's/Mountedon/Mounted/'>/tmp/disk join/tmp/disk/tmp/inode|awk'{print$1,$2,"|",$3,$4,$5,$6,"|",$8,$9,$10,$11,"|",$12}'|column-t #报表信息 diskdata=$(df-TP|sed'1d'|awk'$2!="tmpfs"{print}')#KB disktotal=$(echo"$diskdata"|awk'{total+=$3}END{printtotal}')#KB diskused=$(echo"$diskdata"|awk'{total+=$4}END{printtotal}')#KB diskfree=$((disktotal-diskused))#KB diskusedpercent=$(echo$disktotal$diskused|awk'{if($1==0){printf100}else{printf"%.2f",$2*100/$1}}') inodedata=$(df-iTP|sed'1d'|awk'$2!="tmpfs"{print}') inodetotal=$(echo"$inodedata"|awk'{total+=$3}END{printtotal}') inodeused=$(echo"$inodedata"|awk'{total+=$4}END{printtotal}') inodefree=$((inodetotal-inodeused)) inodeusedpercent=$(echo$inodetotal$inodeused|awk'{if($1==0){printf100}else{printf"%.2f",$2*100/$1}}') report_DiskTotal=$((disktotal/1024/1024))"GB"#硬盘总容量(GB) report_DiskFree=$((diskfree/1024/1024))"GB"#硬盘剩余(GB) report_DiskUsedPercent="$diskusedpercent""%"#硬盘使用率% report_InodeTotal=$((inodetotal/1000))"K"#Inode总量 report_InodeFree=$((inodefree/1000))"K"#Inode剩余 report_InodeUsedPercent="$inodeusedpercent""%"#Inode使用率% } functiongetSystemStatus(){ echo"" echo"" echo"############################系统检查############################" if[-e/etc/sysconfig/i18n];then default_LANG="$(grep"LANG="/etc/sysconfig/i18n|grep-v"^#"|awk-F'"''{print$2}')" else default_LANG=$LANG fi exportLANG="en_US.UTF-8" Release=$(cat/etc/redhat-release2>/dev/null) Kernel=$(uname-r) OS=$(uname-o) Hostname=$(uname-n) SELinux=$(/usr/sbin/sestatus|grep"SELinuxstatus:"|awk'{print$3}') LastReboot=$(who-b|awk'{print$3,$4}') uptime=$(uptime|sed's/.*up\([^,]*\),.*/\1/') echo"系统:$OS" echo"发行版本:$Release" echo"内核:$Kernel" echo"主机名:$Hostname" echo"SELinux:$SELinux" echo"语言/编码:$default_LANG" echo"当前时间:$(date+'%F%T')" echo"最后启动:$LastReboot" echo"运行时间:$uptime" #报表信息 report_DateTime=$(date+"%F%T")#日期 report_Hostname="$Hostname"#主机名 report_OSRelease="$Release"#发行版本 report_Kernel="$Kernel"#内核 report_Language="$default_LANG"#语言/编码 report_LastReboot="$LastReboot"#最近启动时间 report_Uptime="$uptime"#运行时间(天) report_Selinux="$SELinux" exportLANG="$default_LANG" } functiongetServiceStatus(){ echo"" echo"" echo"############################服务检查############################" echo"" if[[$centosVersion>7]];then conf=$(systemctllist-unit-files--type=service--state=enabled--no-pager|grep"enabled") process=$(systemctllist-units--type=service--state=running--no-pager|grep".service") #报表信息 report_SelfInitiatedService="$(echo"$conf"|wc-l)"#自启动服务数量 report_RuningService="$(echo"$process"|wc-l)"#运行中服务数量 else conf=$(/sbin/chkconfig|grep-E":on|:启用") process=$(/sbin/service--status-all2>/dev/null|grep-E"isrunning|正在运行") #报表信息 report_SelfInitiatedService="$(echo"$conf"|wc-l)"#自启动服务数量 report_RuningService="$(echo"$process"|wc-l)"#运行中服务数量 fi echo"服务配置" echo"--------" echo"$conf"|column-t echo"" echo"正在运行的服务" echo"--------------" echo"$process" } functiongetAutoStartStatus(){ echo"" echo"" echo"############################自启动检查##########################" conf=$(grep-v"^#"/etc/rc.d/rc.local|sed'/^$/d') echo"$conf" #报表信息 report_SelfInitiatedProgram="$(echo$conf|wc-l)"#自启动程序数量 } functiongetLoginStatus(){ echo"" echo"" echo"############################登录检查############################" last|head } functiongetNetworkStatus(){ echo"" echo"" echo"############################网络检查############################" if[[$centosVersion<7]];then /sbin/ifconfig-a|grep-vpackets|grep-vcollisions|grep-vinet6 else #ipa foriin$(iplink|grepBROADCAST|awk-F:'{print$2}');doipaddshow$i|grep-E"BROADCAST|global"|awk'{print$2}'|tr'\n''';echo"";done fi GATEWAY=$(iproute|grepdefault|awk'{print$3}') DNS=$(grepnameserver/etc/resolv.conf|grep-v"#"|awk'{print$2}'|tr'\n'','|sed's/,$//') echo"" echo"网关:$GATEWAY" echo"DNS:$DNS" #报表信息 IP=$(ip-finetaddr|grep-v127.0.0.1|grepinet|awk'{print$NF,$2}'|tr'\n'','|sed's/,$//') MAC=$(iplink|grep-v"LOOPBACK\|loopback"|awk'{print$2}'|sed'N;s/\n//'|tr'\n'','|sed's/,$//') report_IP="$IP"#IP地址 report_MAC=$MAC#MAC地址 report_Gateway="$GATEWAY"#默认网关 report_DNS="$DNS"#DNS } functiongetListenStatus(){ echo"" echo"" echo"############################监听检查############################" TCPListen=$(ss-ntul|column-t) echo"$TCPListen" #报表信息 report_Listen="$(echo"$TCPListen"|sed'1d'|awk'/tcp/{print$5}'|awk-F:'{print$NF}'|sort|uniq|wc-l)" } functiongetCronStatus(){ echo"" echo"" echo"############################计划任务检查########################" Crontab=0 forshellin$(grep-v"/sbin/nologin"/etc/shells);do foruserin$(grep"$shell"/etc/passwd|awk-F:'{print$1}');do crontab-l-u$user>/dev/null2>&1 status=$? if[$status-eq0];then echo"$user" echo"--------" crontab-l-u$user letCrontab=Crontab+$(crontab-l-u$user|wc-l) echo"" fi done done #计划任务 find/etc/cron*-typef|xargs-ils-l{}|column-t letCrontab=Crontab+$(find/etc/cron*-typef|wc-l) #报表信息 report_Crontab="$Crontab"#计划任务数 } functiongetHowLongAgo(){ #计算一个时间戳离现在有多久了 datetime="$*" [-z"$datetime"]&&echo"错误的参数:getHowLongAgo()$*" Timestamp=$(date+%s-d"$datetime")#转化为时间戳 Now_Timestamp=$(date+%s) Difference_Timestamp=$(($Now_Timestamp-$Timestamp)) days=0;hours=0;minutes=0; sec_in_day=$((60*60*24)); sec_in_hour=$((60*60)); sec_in_minute=60 while(($(($Difference_Timestamp-$sec_in_day))>1)) do letDifference_Timestamp=Difference_Timestamp-sec_in_day letdays++ done while(($(($Difference_Timestamp-$sec_in_hour))>1)) do letDifference_Timestamp=Difference_Timestamp-sec_in_hour lethours++ done echo"$days天$hours小时前" } functiongetUserLastLogin(){ #获取用户最近一次登录的时间,含年份 #很遗憾last命令不支持显示年份,只有"last-tYYYYMMDDHHMMSS"表示某个时间之间的登录,我 #们只能用最笨的方法了,对比今天之前和今年元旦之前(或者去年之前和前年之前……)某个用户 #登录次数,如果登录统计次数有变化,则说明最近一次登录是今年。 username=$1 :${username:="`whoami`"} thisYear=$(date+%Y) oldesYear=$(last|tail-n1|awk'{print$NF}') while(($thisYear>=$oldesYear));do loginBeforeToday=$(last$username|grep$username|wc-l) loginBeforeNewYearsDayOfThisYear=$(last$username-t$thisYear"0101000000"|grep$username|wc-l) if[$loginBeforeToday-eq0];then echo"从未登录过" break elif[$loginBeforeToday-gt$loginBeforeNewYearsDayOfThisYear];then lastDateTime=$(last-i$username|head-n1|awk'{for(i=4;i<(NF-2);i++)printf"%s",$i}')"$thisYear"#格式如:SatNov220:332015 lastDateTime=$(date"+%Y-%m-%d%H:%M:%S"-d"$lastDateTime") echo"$lastDateTime" break else thisYear=$((thisYear-1)) fi done } functiongetUserStatus(){ echo"" echo"" echo"############################用户检查############################" #/etc/passwd最后修改时间 pwdfile="$(cat/etc/passwd)" Modify=$(stat/etc/passwd|grepModify|tr'.'''|awk'{print$2,$3}') echo"/etc/passwd最后修改时间:$Modify($(getHowLongAgo$Modify))" echo"" echo"特权用户" echo"--------" RootUser="" foruserin$(echo"$pwdfile"|awk-F:'{print$1}');do if[$(id-u$user)-eq0];then echo"$user" RootUser="$RootUser,$user" fi done echo"" echo"用户列表" echo"--------" USERs=0 echo"$( echo"用户名UIDGIDHOMESHELL最后一次登录" forshellin$(grep-v"/sbin/nologin"/etc/shells);do forusernamein$(grep"$shell"/etc/passwd|awk-F:'{print$1}');do userLastLogin="$(getUserLastLogin$username)" echo"$pwdfile"|grep-w"$username"|grep-w"$shell"|awk-F:-vlastlogin="$(echo"$userLastLogin"|tr'''_')"'{print$1,$3,$4,$6,$7,lastlogin}' done letUSERs=USERs+$(echo"$pwdfile"|grep"$shell"|wc-l) done )"|column-t echo"" echo"空密码用户" echo"----------" USEREmptyPassword="" forshellin$(grep-v"/sbin/nologin"/etc/shells);do foruserin$(echo"$pwdfile"|grep"$shell"|cut-d:-f1);do r=$(awk-F:'$2=="!!"{print$1}'/etc/shadow|grep-w$user) if[!-z$r];then echo$r USEREmptyPassword="$USEREmptyPassword,"$r fi done done echo"" echo"相同ID的用户" echo"------------" USERTheSameUID="" UIDs=$(cut-d:-f3/etc/passwd|sort|uniq-c|awk'$1>1{print$2}') foruidin$UIDs;do echo-n"$uid"; USERTheSameUID="$uid" r=$(awk-F:'ORS="";$3=='"$uid"'{print":",$1}'/etc/passwd) echo"$r" echo"" USERTheSameUID="$USERTheSameUID$r," done #报表信息 report_USERs="$USERs"#用户 report_USEREmptyPassword=$(echo$USEREmptyPassword|sed's/^,//') report_USERTheSameUID=$(echo$USERTheSameUID|sed's/,$//') report_RootUser=$(echo$RootUser|sed's/^,//')#特权用户 } functiongetPasswordStatus{ echo"" echo"" echo"############################密码检查############################" pwdfile="$(cat/etc/passwd)" echo"" echo"密码过期检查" echo"------------" result="" forshellin$(grep-v"/sbin/nologin"/etc/shells);do foruserin$(echo"$pwdfile"|grep"$shell"|cut-d:-f1);do get_expiry_date=$(/usr/bin/chage-l$user|grep'Passwordexpires'|cut-d:-f2) if[[$get_expiry_date='never'||$get_expiry_date='never']];then printf"%-15s永不过期\n"$user result="$result,$user:never" else password_expiry_date=$(date-d"$get_expiry_date""+%s") current_date=$(date"+%s") diff=$(($password_expiry_date-$current_date)) letDAYS=$(($diff/(60*60*24))) printf"%-15s%s天后过期\n"$user$DAYS result="$result,$user:$DAYSdays" fi done done report_PasswordExpiry=$(echo$result|sed's/^,//') echo"" echo"密码策略检查" echo"------------" grep-v"#"/etc/login.defs|grep-E"PASS_MAX_DAYS|PASS_MIN_DAYS|PASS_MIN_LEN|PASS_WARN_AGE" } functiongetSudoersStatus(){ echo"" echo"" echo"############################Sudoers检查#########################" conf=$(grep-v"^#"/etc/sudoers|grep-v"^Defaults"|sed'/^$/d') echo"$conf" echo"" #报表信息 report_Sudoers="$(echo$conf|wc-l)" } functiongetInstalledStatus(){ echo"" echo"" echo"############################软件检查############################" rpm-qa--last|head|column-t } functiongetProcessStatus(){ echo"" echo"" echo"############################进程检查############################" if[$(ps-ef|grepdefunct|grep-vgrep|wc-l)-ge1];then echo"" echo"僵尸进程"; echo"--------" ps-ef|head-n1 ps-ef|grepdefunct|grep-vgrep fi echo"" echo"内存占用TOP10" echo"-------------" echo-e"PID%MEMRSSCOMMAND $(psaux|awk'{print$2,$4,$6,$11}'|sort-k3rn|head-n10)"|column-t echo"" echo"CPU占用TOP10" echo"------------" topb-n1|head-17|tail-11 #报表信息 report_DefunctProsess="$(ps-ef|grepdefunct|grep-vgrep|wc-l)" } functiongetJDKStatus(){ echo"" echo"" echo"############################JDK检查#############################" java-version2>/dev/null if[$?-eq0];then java-version2>&1 fi echo"JAVA_HOME=\"$JAVA_HOME\"" #报表信息 report_JDK="$(java-version2>&1|grepversion|awk'{print$1,$3}'|tr-d'"')" } functiongetSyslogStatus(){ echo"" echo"" echo"############################syslog检查##########################" echo"服务状态:$(getStatersyslog)" echo"" echo"/etc/rsyslog.conf" echo"-----------------" cat/etc/rsyslog.conf2>/dev/null|grep-v"^#"|grep-v"^\\$"|sed'/^$/d'|column-t #报表信息 report_Syslog="$(getStatersyslog)" } functiongetFirewallStatus(){ echo"" echo"" echo"############################防火墙检查##########################" #防火墙状态,策略等 if[[$centosVersion<7]];then /etc/init.d/iptablesstatus>/dev/null2>&1 status=$? if[$status-eq0];then s="active" elif[$status-eq3];then s="inactive" elif[$status-eq4];then s="permissiondenied" else s="unknown" fi else s="$(getStateiptables)" fi echo"iptables:$s" echo"" echo"/etc/sysconfig/iptables" echo"-----------------------" cat/etc/sysconfig/iptables2>/dev/null #报表信息 report_Firewall="$s" } functiongetSNMPStatus(){ #SNMP服务状态,配置等 echo"" echo"" echo"############################SNMP检查############################" status="$(getStatesnmpd)" echo"服务状态:$status" echo"" if[-e/etc/snmp/snmpd.conf];then echo"/etc/snmp/snmpd.conf" echo"--------------------" cat/etc/snmp/snmpd.conf2>/dev/null|grep-v"^#"|sed'/^$/d' fi #报表信息 report_SNMP="$(getStatesnmpd)" } functiongetState(){ if[[$centosVersion<7]];then if[-e"/etc/init.d/$1"];then if[`/etc/init.d/$1status2>/dev/null|grep-E"isrunning|正在运行"|wc-l`-ge1];then r="active" else r="inactive" fi else r="unknown" fi else #CentOS7+ r="$(systemctlis-active$12>&1)" fi echo"$r" } functiongetSSHStatus(){ #SSHD服务状态,配置,受信任主机等 echo"" echo"" echo"############################SSH检查#############################" #检查受信任主机 pwdfile="$(cat/etc/passwd)" echo"服务状态:$(getStatesshd)" Protocol_Version=$(cat/etc/ssh/sshd_config|grepProtocol|awk'{print$2}') echo"SSH协议版本:$Protocol_Version" echo"" echo"信任主机" echo"--------" authorized=0 foruserin$(echo"$pwdfile"|grep/bin/bash|awk-F:'{print$1}');do authorize_file=$(echo"$pwdfile"|grep-w$user|awk-F:'{printf$6"/.ssh/authorized_keys"}') authorized_host=$(cat$authorize_file2>/dev/null|awk'{print$3}'|tr'\n'','|sed's/,$//') if[!-z$authorized_host];then echo"$user授权\"$authorized_host\"无密码访问" fi letauthorized=authorized+$(cat$authorize_file2>/dev/null|awk'{print$3}'|wc-l) done echo"" echo"是否允许ROOT远程登录" echo"--------------------" config=$(cat/etc/ssh/sshd_config|grepPermitRootLogin) firstChar=${config:0:1} if[$firstChar=="#"];then PermitRootLogin="yes"#默认是允许ROOT远程登录的 else PermitRootLogin=$(echo$config|awk'{print$2}') fi echo"PermitRootLogin$PermitRootLogin" echo"" echo"/etc/ssh/sshd_config" echo"--------------------" cat/etc/ssh/sshd_config|grep-v"^#"|sed'/^$/d' #报表信息 report_SSHAuthorized="$authorized"#SSH信任主机 report_SSHDProtocolVersion="$Protocol_Version"#SSH协议版本 report_SSHDPermitRootLogin="$PermitRootLogin"#允许root远程登录 } functiongetNTPStatus(){ #NTP服务状态,当前时间,配置等 echo"" echo"" echo"############################NTP检查#############################" if[-e/etc/ntp.conf];then echo"服务状态:$(getStatentpd)" echo"" echo"/etc/ntp.conf" echo"-------------" cat/etc/ntp.conf2>/dev/null|grep-v"^#"|sed'/^$/d' fi #报表信息 report_NTP="$(getStatentpd)" } functionuploadHostDailyCheckReport(){ json="{ \"DateTime\":\"$report_DateTime\", \"Hostname\":\"$report_Hostname\", \"OSRelease\":\"$report_OSRelease\", \"Kernel\":\"$report_Kernel\", \"Language\":\"$report_Language\", \"LastReboot\":\"$report_LastReboot\", \"Uptime\":\"$report_Uptime\", \"CPUs\":\"$report_CPUs\", \"CPUType\":\"$report_CPUType\", \"Arch\":\"$report_Arch\", \"MemTotal\":\"$report_MemTotal\", \"MemFree\":\"$report_MemFree\", \"MemUsedPercent\":\"$report_MemUsedPercent\", \"DiskTotal\":\"$report_DiskTotal\", \"DiskFree\":\"$report_DiskFree\", \"DiskUsedPercent\":\"$report_DiskUsedPercent\", \"InodeTotal\":\"$report_InodeTotal\", \"InodeFree\":\"$report_InodeFree\", \"InodeUsedPercent\":\"$report_InodeUsedPercent\", \"IP\":\"$report_IP\", \"MAC\":\"$report_MAC\", \"Gateway\":\"$report_Gateway\", \"DNS\":\"$report_DNS\", \"Listen\":\"$report_Listen\", \"Selinux\":\"$report_Selinux\", \"Firewall\":\"$report_Firewall\", \"USERs\":\"$report_USERs\", \"USEREmptyPassword\":\"$report_USEREmptyPassword\", \"USERTheSameUID\":\"$report_USERTheSameUID\", \"PasswordExpiry\":\"$report_PasswordExpiry\", \"RootUser\":\"$report_RootUser\", \"Sudoers\":\"$report_Sudoers\", \"SSHAuthorized\":\"$report_SSHAuthorized\", \"SSHDProtocolVersion\":\"$report_SSHDProtocolVersion\", \"SSHDPermitRootLogin\":\"$report_SSHDPermitRootLogin\", \"DefunctProsess\":\"$report_DefunctProsess\", \"SelfInitiatedService\":\"$report_SelfInitiatedService\", \"SelfInitiatedProgram\":\"$report_SelfInitiatedProgram\", \"RuningService\":\"$report_RuningService\", \"Crontab\":\"$report_Crontab\", \"Syslog\":\"$report_Syslog\", \"SNMP\":\"$report_SNMP\", \"NTP\":\"$report_NTP\", \"JDK\":\"$report_JDK\" }" #echo"$json" curl-l-H"Content-type:application/json"-XPOST-d"$json""$uploadHostDailyCheckReportApi"2>/dev/null } functioncheck(){ version getSystemStatus getCpuStatus getMemStatus getDiskStatus getNetworkStatus getListenStatus getProcessStatus getServiceStatus getAutoStartStatus getLoginStatus getCronStatus getUserStatus getPasswordStatus getSudoersStatus getJDKStatus getFirewallStatus getSSHStatus getSyslogStatus getSNMPStatus getNTPStatus getInstalledStatus } #执行检查并保存检查结果 check>$RESULTFILE echo"检查结果:$RESULTFILE" 添加host.info信息 192.168.2.30 root root 192.168.2.28 root root 192.168.2.27 root root 192.168.2.29 root root 执行python脚本生成文件 #使用exe执行python脚本 安装pyinstaller E:\python新版学习\pyinstaller-develop>PyInstaller E:\python新版学习\3.6.2\inspe ction.py

优秀的个人博客,低调大师

Docker自动化双主-02自动化脚本制作镜像启动

基于 DOCKER MySQL 主从搭建: 一、根据 Linux 系统版本号安装 dockerEG:Centos 7:1.shell>sudo yum update2.添加 dockeryum$[dockerrepo] name=Docker Repository baseurl=https://yum.dockerproject.org/repo/main/centos/7/ enabled=1gpgcheck=1 gpgkey=https://yum.dockerproject.org/gpg EOF 3.安装 docker 的包shell>sudo yum install docker-engine4.将 docker 加到服务里面shell>sudo systemctl enable docker.service 5.启动 dockershell> sudo systemctl start docker二、将 docker 镜像导入 linux: 镜像文件上传到/opt/dtstack 目录下: 1.载入镜像:Shell>cd /opt/dtstackShell>docker load < mysql-5.6-x86_64.tar[root@centos7 docker]# docker load < mysql-5.6-x86_64.tarc411e76031a4: Loading layer [==================================================>] 201.9 MB/201.9 MBabd40b5d012f: Loading layer [==================================================>] 60.97 MB/60.97 MB7d082aa6bb0c: Loading layer [==================================================>] 263.3 MB/263.3 MB173e11cd6be1: Loading layer [==================================================>] 6.656 kB/6.656 kBbdb04743768e: Loading layer [==================================================>]sudotee /etc/yum.repos.d/docker.repo <<-'EOF'3.072 kB/3.072 kB9da6836b8f64: Loading layer [==================================================>] 4.872 MB/4.872 MBd47abb872b5c: Loading layer [==================================================>] 20.3 MB/20.3 MB008419e2fbfc: Loading layer [==================================================>] 108.9 MB/108.9 MB95d9d0bff849: Loading layer [==================================================>] 23.04 kB/23.04 kB71405897d961: Loading layer [==================================================>] 1.68 MB/1.68 MB58b14b6eb7df: Loading layer [==================================================>] 49.48 MB/49.48 MBfffc1c2170a5: Loading layer [==================================================>] 94.12 MB/94.12 MB9d60f33d82f1: Loading layer [==================================================>] 23.7 MB/23.7 MB2b9b1be70303: Loading layer [==================================================>] 6.656 kB/6.656 kBa2018f6089d9: Loading layer [==================================================>] 1.122 GB/1.122 GB9e274ef73e1d: Loading layer [==================================================>] 13.82 kB/13.82 kBd0ca45b2e986: Loading layer [==================================================>] 6.144 kB/6.144 kBLoaded image: dtstack_db_01:5.62.列出本地已有镜像:Shell> docker images REPOSITORY TAG SIZEdtstack_db_01 5.6 1.92 GB3.传参脚本说明: #!/bin/bash APP_NAME=db_01DOCKER_IMAGE=dtstack_db_01:5.6docker_run(){docker run -dt \--restart=always --name $APP_NAME \IMAGE ID 26e6aa6ed6c2CREATED 22 hours ago -p 3306:3306 #映射的端口号-v /etc/localtime:/etc/localtime:ro \-v /data/mysql/my3306:/data/mysql \/data/mysql 目录必须存在-e "dbport=3306" #dbport-e "db_root_password=root123" -e "db_master_ip=172.16.1.65" -e "db_slave_ip=172.16.1.86" \ 宿主机目录挂载到/data/mysql/myxxx 下, -e "db_tag=master" -e "db_instance=4" \实例...-i $DOCKER_IMAGE} docker_run4.运行 mysql_docker.sh 脚本 Shell>./mysql_docker.sh db 角色 选择性部署多实例。EG:单实例 db_instance=1, 3 则是 3 个 5.检查容器运行状态: Shell>docker ps –a CONTAINER IDSTATUSIMAGE PORTSCOMMAND"/bin/sh -c '/home..." 34 seconds agoNAMESCREATED dd47a290ae94Up 34 secondsdtstack_db_01:5.60.0.0.0:3306->3306/tcp, 3307-3311/tcp db_01 6.进行观察日志信息是否搭建成功:Shell>docker logs dd47a290ae94170317 19:31:09 mysqld_safe Logging to '/data/mysql/log/alert.log'.170317 19:31:09 mysqld_safe Starting mysqld daemon with databases from /data/mysql/data create replicaiton logmaster change master toupdate root password logSLAVE: 同上述操作: 1.需要更改的: #!/bin/bash APP_NAME=db_01DOCKER_IMAGE=dtstack_db_01:5.6docker_run(){ docker run -dt \ --restart=always \--name $APP_NAME \-p 3306:3306 \-v /etc/localtime:/etc/localtime:ro \-v /data/mysql/my3306:/data/mysql \-e "dbport=3306" \-e "db_root_password=root123" \-e "db_master_ip=172.16.1.69" \-e "db_slave_ip=172.16.1.86" \-e "db_tag=slave" #将 tag 标签转换为 slave,其他不需要修改 -e "db_instance=4" \-i $DOCKER_IMAGE}2.启动 mysql_slave.sh 脚本: Shell>./mysql_docker.sh [root@localhost docker]# docker ps -aCONTAINER ID CREATED NAMES fb45654c2eda Up 6 minutes docker_runIMAGE STATUSdtstack_db_01:5.6 0.0.0.0:3306->3306/tcp, 3307-3311/tcpCOMMAND PORTS6 minutes agodb_01"/bin/sh -c '/home..."三、通过外部连接 mysql:检查主从是否正常:1.SLAVE:Shell>mysql -h172.16.1.86 -uroot -proot123 -P3306 -e "start slave;show slave statusG" | grep _Running | awk 'NR!=3'Warning: Using a password on the command line interface can be insecure.2.master:[root@centos7 docker]# mysql -h172.16.1.65 -uroot -proot123 -P3306 -e "start slave;show slave statusG" | grep _Running | awk 'NR!=3' Slave_IO_Running: Yes Slave_SQL_Running: Yes Slave_IO_Running: Yes

优秀的个人博客,低调大师

amin例子的简单研究

amin这个例子,使用了比较复杂高阶的qml技巧,但是也有局限性。下面分3个部分,分别是界面部分,算法部分和扩展部分,简单地对这个问题进行理解。 由衷感谢:http://amin-ahmadi.com/quick-camera-cv/ 给本程序很多借鉴 代码下载地址:https://files.cnblogs.com/files/jsxyhelu/GO_GTD2%E5%AE%8C%E6%88%90.zip视频地址 :https://files.cnblogs.com/files/jsxyhelu/amin%E4%BE%8B%E5%AD%90%E8%A7%86%E9%A2%91.zip 一、qml界面部分: 1、专门生成了用于提示的dialog /* 只有一个OK的弹出界面,目前这个弹出界面只给捕获的时候使用 */ Dialog { property alias text : msgDlgLabel.text id : messageDialog modal : true x : (parent.width - width) / 2 y : (parent.height - height) / 2 standardButtons : Dialog.Ok //上面只有一个label Label { id : msgDlgLabel font.bold : true wrapMode : Text.Wrap width : parent.width //点击事件 onLinkActivated : { if(link == "图像处理") { tabBar.currentIndex = 1 } messageDialog.close() } } } 2、整体界面采用swipe和page以及footer的形式,达到了简洁高效 /* 主要的swipview界面 */ SwipeView { id : swipeView currentIndex : 0 anchors.rightMargin : 0 anchors.bottomMargin : 0 anchors.leftMargin : 0 anchors.topMargin : 0 anchors.fill : parent //TabBar和swipview要对应起来 onCurrentIndexChanged : { tabBar.setCurrentIndex(swipeView.currentIndex) } //视频预览 Page {…… 以及 /* 主要的footer操作 */ footer : TabBar { id : tabBar currentIndex : 0 padding : 10 3、使用了HTML增加文本显示效果,并且是可以切换的 Text { id : helpText wrapMode : Text.Wrap anchors.left : parent.left anchors.right : parent.right text : "由衷感谢:<a href=\"http://amin-ahmadi.com/quick-camera-cv/\">http://amin-ahmadi.com/quick-camera-cv</a>" + "给本程序很多借鉴" + "<br>" + "<br>" + "作者博客:" + "<br>" + "<a href=\"jsxyhelu.cnblogs.com\">jsxyhelu.cnblogs.com</a>" + "<br>" + "<a href=\"jsxyhelu.cnblogs.com\"><img src=\"http://images2015.cnblogs.com/blog/508489/201607/508489-20160731065441684-483128601.png\" alt=\"欢迎访问!\"></a>" + "<br>" + "<b>版权</b>" + "本程序使用 <a href=\"http://qt.io/\">Qt Framework</a> 作为GUI" + "<br>" + "同时使用 <a href=\"http://opencv.org/\">OpenCV</a> 做图像处理算法." + "<br>" + "程序采用ICO来自<a href=\"http://flaticon.com/\">Flat Icon</a>." onLinkActivated : { Qt.openUrlExternally(link); } } 应该这样讲,有这段代码作为例子,那么这种样式的程序在界面上基本不成问题。 二、算法实现部分: qml是弱语法,比较类似lambda,所以这种语言的使用对于习惯c语言的我来说有难度,想要精通需要时间;另一个方面,因为需要和OpenCV进行交互,所以更复杂一点。本例中综合使用了qml使用c++封装出来的对象,以及“信号、槽”机制等;在 摄像头获取和图片采集实现中, 硬件层综合使用了qml和qcamera,捕获使用了 QCameraImageCapture ,具体这样用 在qml中,使用 //摄像头选择对话框 ComboBox { id : cameraCombo Layout.fillWidth : true Layout.fillHeight : true model : QtMultimedia.availableCameras textRole : "displayName" delegate : ItemDelegate { text : modelData.displayName } onCurrentIndexChanged : { camera.stop() camera.deviceId = model[currentIndex].deviceId camera.start() } } 这样可以获得所有可用摄像头的句柄,然后直接传递到c++中 //调用qcamera进行图像采集 void QCvImageProcessor : :setCamera(QVariant v) { QObject *o = qvariant_cast <QObject * >(v); camera = qvariant_cast <QCamera * >(o - >property( "mediaObject")); camera - >setCaptureMode(QCamera : :CaptureStillImage); imageCapture = new QCameraImageCapture(camera); camera - >focus() - >setFocusMode(QCameraFocus : :ContinuousFocus); camera - >focus() - >setFocusPointMode(QCameraFocus : :FocusPointAuto); //直接在这里设置动作级联 connect(imageCapture, &QCameraImageCapture : :imageSaved, [ this]( int id, const QString &fileName) { Q_UNUSED(id); processSavedImage(fileName); }); } void QCvImageProcessor : :capture() { if(imageCapture - >isReadyForCapture()) { //注意这里获得一个可用的图片地址的方法 imageCapture - >capture(QStandardPaths : :writableLocation(QStandardPaths : :PicturesLocation)); } else { emit errorOccured( "Camera is not ready to capture."); } } 还是使用 QCameraImageCapture,QCamera来完成捕获。 由于在andoird中,videocapture不能给使用,那么qcamera作为qt专属,来实现摄像头采集功能是非常合适的,这里给出了具体系统方法。 三、进一步扩展部分: QCameraImageCapture只能捕获静态图片,但是作为一个完整的图像处理程序,一定要能够处理并显示实时的视频数据,如何解决?继续探索! 感谢阅读至此,希望有所帮助。 来自为知笔记(Wiz) 附件列表 目前方向:图像拼接融合、图像识别 联系方式:jsxyhelu@foxmail.com

优秀的个人博客,低调大师

Android——动画专题研究

动画类型 Android的animation由四种类型组成 XML中 alpha 渐变透明度动画效果 scale 渐变尺寸伸缩动画效果 translate 画面转换位置移动动画效果 rotate 画面转移旋转动画效果 JavaCode中 AlphaAnimation 渐变透明度动画效果 ScaleAnimation 渐变尺寸伸缩动画效果 TranslateAnimation 画面转换位置移动动画效果 RotateAnimation 画面转移旋转动画效果 Android动画模式 主要有两种动画模式: 一种是tweened animation(渐变动画) XML中 JavaCode alpha AlphaAnimation scale ScaleAnimation 一种是frame by frame(画面转换动画) XML中 JavaCode translate TranslateAnimation rotate RotateAnimation 如何在XML文件中定义动画 ① 打开Eclipse,新建Android工程 ② 在res目录中新建anim文件夹 ③ 在anim目录中新建一个myanim.xml(注意文件名小写) ④ 加入XML的动画代码 <?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android"> <alpha/> <scale/> <translate/> <rotate/> </set> Android动画解析--XML<alpha> <?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android" > <alpha android:fromAlpha="0.1" android:toAlpha="1.0" android:duration="3000" /> <!-- 透明度控制动画效果 alpha 浮点型值: fromAlpha 属性为动画起始时透明度 toAlpha 属性为动画结束时透明度 说明: 0.0表示完全透明 1.0表示完全不透明 以上值取0.0-1.0之间的float数据类型的数字 长整型值: duration 属性为动画持续时间 说明: 时间以毫秒为单位 --> </set> <scale> <?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android"> <scale android:interpolator="@android:anim/accelerate_decelerate_interpolator" android:fromXScale="0.0" android:toXScale="1.4" android:fromYScale="0.0" android:toYScale="1.4" android:pivotX="50%" android:pivotY="50%" android:fillAfter="false" android:duration="700" /> </set> <!-- 尺寸伸缩动画效果 scale 属性:interpolator 指定一个动画的插入器 在我试验过程中,使用android.res.anim中的资源时候发现 有三种动画插入器: accelerate_decelerate_interpolator 加速-减速 动画插入器 accelerate_interpolator 加速-动画插入器 decelerate_interpolator 减速- 动画插入器 其他的属于特定的动画效果 浮点型值: fromXScale 属性为动画起始时 X坐标上的伸缩尺寸 toXScale 属性为动画结束时 X坐标上的伸缩尺寸 fromYScale 属性为动画起始时Y坐标上的伸缩尺寸 toYScale 属性为动画结束时Y坐标上的伸缩尺寸 说明: 以上四种属性值 0.0表示收缩到没有 1.0表示正常无伸缩 值小于1.0表示收缩 值大于1.0表示放大 pivotX 属性为动画相对于物件的X坐标的开始位置 pivotY 属性为动画相对于物件的Y坐标的开始位置 说明: 以上两个属性值 从0%-100%中取值 50%为物件的X或Y方向坐标上的中点位置 长整型值: duration 属性为动画持续时间 说明: 时间以毫秒为单位 布尔型值: fillAfter 属性 当设置为true ,该动画转化在动画结束后被应用 --> <translate> <?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android"> <translate android:fromXDelta="30" android:toXDelta="-80" android:fromYDelta="30" android:toYDelta="300" android:duration="2000" /> <!-- translate 位置转移动画效果 整型值: fromXDelta 属性为动画起始时 X坐标上的位置 toXDelta 属性为动画结束时 X坐标上的位置 fromYDelta 属性为动画起始时 Y坐标上的位置 toYDelta 属性为动画结束时 Y坐标上的位置 注意: 没有指定fromXType toXType fromYType toYType 时候, 默认是以自己为相对参照物 长整型值: duration 属性为动画持续时间 说明: 时间以毫秒为单位 --> </set> <rotate> <?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android"> <rotate android:interpolator="@android:anim/accelerate_decelerate_interpolator" android:fromDegrees="0" android:toDegrees="+350" android:pivotX="50%" android:pivotY="50%" android:duration="3000" /> <!-- rotate 旋转动画效果 属性:interpolator 指定一个动画的插入器 在我试验过程中,使用android.res.anim中的资源时候发现 有三种动画插入器: accelerate_decelerate_interpolator 加速-减速 动画插入器 accelerate_interpolator 加速-动画插入器 decelerate_interpolator 减速- 动画插入器 其他的属于特定的动画效果 浮点数型值: fromDegrees 属性为动画起始时物件的角度 toDegrees 属性为动画结束时物件旋转的角度 可以大于360度 说明: 当角度为负数——表示逆时针旋转 当角度为正数——表示顺时针旋转 (负数from——to正数:顺时针旋转) (负数from——to负数:逆时针旋转) (正数from——to正数:顺时针旋转) (正数from——to负数:逆时针旋转) pivotX 属性为动画相对于物件的X坐标的开始位置 pivotY 属性为动画相对于物件的Y坐标的开始位置 说明: 以上两个属性值 从0%-100%中取值 50%为物件的X或Y方向坐标上的中点位置 长整型值: duration 属性为动画持续时间 说明: 时间以毫秒为单位 --> </set> 如何使用XML中的动画效果 public static Animation loadAnimation (Context context, int id) //第一个参数Context为程序的上下文 //第二个参数id为动画XML文件的引用 //例子: myAnimation= AnimationUtils.loadAnimation(this,R.anim.my_action); //使用AnimationUtils类的静态方法loadAnimation()来加载XML中的动画XML文件 如何在Java代码中定义动画 //在代码中定义 动画实例对象 private Animation myAnimation_Alpha; private Animation myAnimation_Scale; private Animation myAnimation_Translate; private Animation myAnimation_Rotate; //根据各自的构造方法来初始化一个实例对象 myAnimation_Alpha=new AlphaAnimation(0.1f, 1.0f); myAnimation_Scale =new ScaleAnimation(0.0f, 1.4f, 0.0f, 1.4f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); myAnimation_Translate=new TranslateAnimation(30.0f, -80.0f, 30.0f, 300.0f); myAnimation_Rotate=new RotateAnimation(0.0f, +350.0f, Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF, 0.5f); Android动画解析--JavaCodeAlphaAnimation ① AlphaAnimation类对象定义 private AlphaAnimation myAnimation_Alpha; ② AlphaAnimation类对象构造 AlphaAnimation(float fromAlpha, float toAlpha) //第一个参数fromAlpha为 动画开始时候透明度 //第二个参数toAlpha为 动画结束时候透明度 myAnimation_Alpha=new AlphaAnimation(0.1f, 1.0f); //说明: // 0.0表示完全透明 // 1.0表示完全不透明 ③ 设置动画持续时间 myAnimation_Alpha.setDuration(5000); //设置时间持续时间为 5000毫秒 本文转自luojinghappy 51CTO博客,原文链接:http://blog.51cto.com/luojinghappy/326035,如需转载请自行联系原作者

优秀的个人博客,低调大师

JPDA 架构研究22 - 总结

引入: 前面有几十篇文章从JPDA的各个层面深入剖析了这个协议的细节和其中的各个组成部分细节,现在到了我们总结的时候了。因为图比文字更直观,所以这里精心设计一组图来详细阐述这其中的整个奥秘。如果要对某些部分做特别关注,可以参见前面相关文章。 分析(还是分场景): 场景1: JVM加载阶段: 其中dt_mem只适合于windows平台。 场景2:建立连接阶段(分两种情况.): Scenario 1: 如果是 Target VM主动发起对Debugger的连接。 Scenario 2:如果是Target VM被动等Debugger建立的连接。 场景3:调试阶段: 本文转自 charles_wang888 51CTO博客,原文链接:http://blog.51cto.com/supercharles888/1588773,如需转载请自行联系原作者

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册