[{"content":"待补充知识点 多线程顺序执行 redis的Sentinel redis持久化 冷门：\nhibernate一级缓存和二级缓存的区别是？ ThreadLocal的内存泄漏问题 CAS自旋锁、基础锁的概念 中间件：\n算法方面也只是问了下快排，汉罗塔代码、还有非递归快排\\贪心算法\\弹性伸缩\\架构的设计uml图\n排序排序算法 冒泡是稳定排序么 堆排序是稳定排序呢 堆排序空间复杂度\nspringboot启动过程说一下 SpringBootApplication注解由哪几个注解组成\n如何打印线程堆栈信息 性能调优做过么(没做过)\n基础知识：\n基础算法的手写\n快速排序 k8s和es的基础知识\nk8s管理容器 es全文检索 广度：spring技术-nosql。\n深度：线程池，java底层。\n高频知识点 基础的JVM内存模型\n静态初始化相关的一些东西：静态块/静态变量\u0026gt;构造块\u0026gt;构造方法。\njvm类加载过程、springbean加载过程\n双亲委派机制\n数据库索引，索引类型，索引底层数据结构\n索引到常见的锁、乐观锁、悲观。幻读脏读产生的原因，如何避免\n常用设计模式：单例，工程，策略，装饰\n数据库设计范式\nTODO Important 不知道也要说思路，根据自己的思路输出大概的猜想和实现，或者让面试引导自己去理解和学习\n自我介绍 工作内容，主要负责方面，主要做的事情\n注意说话时的节奏和技巧\n反问点： 工作主要负责，系统目前使用的架构。 工作地点、薪资，公积金比例，年终奖（是否转正后满一年才有年终奖和调薪），调薪晋升周期 总共几轮。什么时候给结果。前几轮的面试认可度，根据反馈对于薪资协商，适当报高 JVM内存结构 参考：JVM调优总结\nJVM内存模型 jvm线程共享区域\nheap堆：java对象，垃圾回收重点区域 元数据（方法区）：final，static等常量，类信息（类方法，接口等）、运行时常量池 jvm线程私有区域\n虚拟机栈：线程中每个方法执行时，创建一个栈帧用于存储局部变量表、操作数栈、动态连接、方法出口等信息。每一个方法被调用直至执行完毕的过程，就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程，局部变量 本地方法栈：native调用本地方法 程序计数器：字节码指令、程序控制流的指示器，分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。 JVM堆栈 jvm堆\n堆（数据结构）：堆可以被看成是一棵树，如：堆排序。\n栈（数据结构）：一种先进后出的数据结构。\n堆中存的是对象。栈中存的是基本数据类型和堆中对象的引用。\n栈是运行时单位，堆是存储单位；栈解决程序的运行问题，即程序如何执行，或者说如何处理数据；堆解决的是数据存储的问题，即数据怎么放、放在哪儿。\n面向对象就是堆和栈的完美结合。其实，面向对象方式的程序与以前结构化的程序在执行上没有任何区别。但是，面向对象的引入，使得对待问题的思考方式发生了改变，而更接近于自然方式的思考。当我们把对象拆开，你会发现\n对象的属性其实就是数据，存放在堆中；\n而对象的行为（方法），就是运行逻辑，放在栈中。\n我们在编写对象的时候，其实即编写了数据结构，也编写的处理数据的逻辑。不得不承认，面向对象的设计，确实很美\n堆中存的是对象。栈中存的是基本数据类型和堆中对象的引用。\nJVM的gc算法 gc调优：heap内存的合理分配。设置年轻代和老年代的比值，调整gc回收器的选择\n标记清除：\n一阶段对于被引用的对象进行标记。二阶段遍历整个heap，对所有未标记的对象进行清除。\n缺点-会产生内存碎片，且需要暂停应用\n复制：\n每次只是用内存区域的一般。gc时，将正在使用的对象复制到另一半空闲区域同时整理。\n缺点：内存区域真正使用的只有一半\n标记整理：\n一阶段标记被引用的对象，二阶段清理未被标记的对象，同时整理未被清除的对象到堆的一块连续内存区域\nJVM的gc策略 jvm的对象\nScavenge GC（Minor GC）\n是主要对Young的GC\n一般情况下，当新对象生成，并且在Eden申请空间失败时，就会触发Scavenge GC，对Eden区域进行GC，清除非存活对象，并且把尚且存活的对象移动到Survivor区。\nMajor GC\n老年代GC称为Major GC\nFull GC\n触发full gc的条件\n由Eden区、From Space区向To Space区复制时，对象大小大于To Space可用内存，则把该对象转存到老年代，且老年代的可用内存小于该对象大小\nSystem.gc()方法的调用\n对整个堆进行整理，包括Young、Tenured和Perm。\n可以直接在老年代进行对象创建吗？ 分配的对象大于eden space空间 eden space的剩余空间不总，直接分配到老年代 大对象直接进入老年代，大对象一般指对象占用的连续内存空间过大，比如很长的字符串和数组，-XX:PretenureSizeThreshold参数控制是否进入老年代 为什么要区分新生代，老年代\n因为有的对象寿命长，有的对象寿命短。应该将寿命长的对象放在一个区，寿命短的对象放在一个区。不同的区采用不同的垃圾收集算法。寿命短的区清理频次高一点，寿命长的区清理频次低一点。提高效率。\n垃圾回收算法参数设置\nJVM常用设置参数\n堆设置的常用参数如下：\n-Xms :初始堆大小\n-Xmx :最大堆大小，当Xms和Xmx设置相同时，堆就无法进行自动扩展。 -XX:NewSize=n :设置年轻代大小\n-XX:NewRatio=n: 设置年轻代和年老代的比值。如:为3，表示年轻代与年老代比值为1：3，年轻代占整个年轻代年老代和的1/4\n-XX:SurvivorRatio=n :年轻代中Eden区与Survivor区的比值。注意Survivor区有两个。如：XX:SurvivorRatio=3，表示Eden区的大小是一个Survivor区的三倍，但Survivor区有两个，那么Eden：Survivor=3：2，一个Survivor区占整个年轻代的1/5。 -XX:MaxPermSize=n :设置持久代大小 收集器设置\n-XX:+UseSerialGC :设置串行收集器\n-XX:+UseParallelGC :设置并行收集器\n-XX:+UseParalledlOldGC :设置并行年老代收集器\n-XX:+UseConcMarkSweepGC :设置并发收集器 垃圾回收统计信息\n-XX:+PrintHeapAtGC GC的heap详情 -XX:+PrintGCDetails GC详情 -XX:+PrintGCTimeStamps 打印GC时间信息 -XX:+PrintTenuringDistribution 打印年龄信息等 -XX:+HandlePromotionFailure 老年代分配担保（true or false） 并行收集器设置\n-XX:ParallelGCThreads=n :设置并行收集器收集时使用的CPU数。并行收集线程数。\n-XX:MaxGCPauseMillis=n :设置并行收集最大暂停时间\n-XX:GCTimeRatio=n :设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n) 并发收集器设置 -XX:+CMSIncrementalMode :设置为增量模式。适用于单CPU情况。\n-XX:ParallelGCThreads=n :设置并发收集器年轻代收集方式为并行收集时，使用的CPU数。并行收集线程数。 JVM如何判断对象生死（即jvm如何标记被引用的对象） 引用计数算法\n给对象设置一个引用计数器，被引用一次计数器加一次，引用失效就减一次\n可达性分析算法\n构造类似树结构的引用分析树，根节点是GC-Root起始点，被引用的对象会加载到树节点，引用失效就断开树节点。\n判断对象生死就从根节点遍历，能遍历到是还在被引用的对象，遍历不到的就是失效的对象\nJava中可作为GC Roots的对象包括：\n虚拟机栈（栈帧中的局部变量表）中引用的对象。 方法区中类静态属性引用的对象。 方法区中常量引用的对象。 本地方法栈中JNI（通常情况下的native方法）引用的对象。 为什么它们可以作为GC Roots？因为这些对象肯定不会被回收。比如，虚拟机栈中是正在执行的方法，所以里面引用的对象不会被回收。\nvolatile 保证可见性 禁止指令重排 不保证原子性 volatile保证了修饰的共享变量在转换为汇编语言时，会加上一个以lock为前缀的指令，当CPU发现这个指令时，立即会做两件事情：\n将当前内核中线程工作内存中该共享变量刷新到主存； 通知其他内核里缓存的该共享变量内存地址无效； jvmOOM 从Java内存模型来看，除了程序计数器不会发生OOM外，哪些区域会发生OOM的情况呢？\n堆内存，堆内存不足是最常见的发送OOM的原因之一。 如果在堆中没有内存完成对象实例的分配，并且堆无法再扩展时，将抛出OutOfMemoryError异常，抛出的错误信息是“java.lang.OutOfMemoryError:Java heap space”。 当前主流的JVM可以通过-Xmx和-Xms来控制堆内存的大小，发生堆上OOM的可能是存在内存泄露，也可能是堆大小分配不合理。 Java虚拟机栈和本地方法栈，这两个区域的区别不过是虚拟机栈为虚拟机执行Java方法服务，而本地方法栈则为虚拟机使用到的Native方法服务，在内存分配异常上是相同的。 在JVM规范中，对Java虚拟机栈规定了两种异常： a. 如果线程请求的栈大于所分配的栈大小，则抛出StackOverFlowError错误，比如进行了一个不会停止的递归调用； b. 如果虚拟机栈是可以动态拓展的，拓展时无法申请到足够的内存，则抛出OutOfMemoryError错误。 直接内存：直接内存虽然不是虚拟机运行时数据区的一部分，但既然是内存，就会受到物理内存的限制。在JDK1.4中引入的NIO使用Native函数库在堆外内存上直接分配内存，但直接内存不足时，也会导致OOM。 方法区：随着Metaspace元数据区的引入，方法区的OOM错误信息也变成了“java.lang.OutOfMemoryError:Metaspace” 对于旧版本的Oracle JDK，由于永久代的大小有限，而JVM对永久代的垃圾回收并不积极，如果往永久代不断写入数据，例如String.Intern()的调用，在永久代占用太多空间导致内存不足，也会出现OOM的问题，对应的错误信息为“java.lang.OutOfMemoryError:PermGen space” 内存区域 是否线程私有 是否会发生OOM 程序计数器 是 否 虚拟机栈 是 是 本地方法栈 是 是 方法区 否 是 直接内存 否 是 堆 否 是 JAVA基础 基础知识 子类继承父类，一定会调用父类的无参构造方法(递归调用到最高层父类的构造方法) super()，this()。必须在构造方法的第一行调用 接口可以进行多继承，普通类和抽象类不允许多继承 Throwable是所有异常类的父类 抽象类用于继承，接口用于实现 什么反射，为什么要引入反射机制\n通过类路径获取类的全属性\n引入反射机制：运行时动态加载类，提高系统灵活性\nJVM类加载过程 jvm的类加载一般分为 7个阶段：加载、验证、准备、解析、初始化、使用、卸载\n加载、验证、准备、初始化和卸载5个阶段的顺序是确定的，类的加载过程必须按照这种顺序按部就班地开始，而解析阶段则不一定：它在某些情况下可以再初始化阶段后再开始，这是为了支持Java语言的运行时绑定(也称动态绑定或晚期绑定)，大概步骤如下\n装载：查找和导入Class文件 链接：把类的二进制数据合并到JRE中 ​\t校验：检查载入Class文件数据的正确性 ​\t准备：给类的静态变量分配存储空间 ​\t解析：将符号引用转成直接引用 初始化：对类的静态变量，静态代码块执行初始化操作 加载：\n类加载过程中，类加载时机：new关键字，static关键字修饰，反射调用，虚拟机指定main之类\n类的实例化是指创建一个类的实例(对象)的过程；实例化一定会走构造函数 类的初始化是指为类各个成员赋初始值的过程，是类生命周期中的一个阶段。初始化是对类的成员的初始化，不一定会走构造函数 通过类的全限定名获取类的二进制字节流。 存储字节流中的静态存储转化为方法区运行时的数据结构。 jvm堆中生成代表这个类的java.lang.Class实例 类加载和类连接阶段，可能并不是按照指定的先后顺序，可能在类加载的过程中，类连接操作已经开始了。但是这两个阶段的开始时间任然保持固定的先后顺序\n验证：\nclass文件格式验证：是否以魔数开头，常量池是否有不被支持的类型 元数据验证，类文件是否符合java语言规范 字节码验证，符号引用验证 准备：\n为类变量(static修饰)分配内存，然后初始化其值\n如果类变量是常量，则直接赋值为该常量值否则为java类型的默认的零值\n解析：\n将常量池中的符号引用替换成直接引用，符号引用指的是：new、getField等替换成直接引用即执行目标地址\n初始化：\n类初始化是类加载过程的最后一步，初始化是执行类构造器cinit方法的过程.\n除了在加载阶段用户应用程序可以通过自定义类加载器参与之外，其余动作完全由虚拟机主导和控制。到了初始化阶段，才真正开始执行类中定义的java程序代码(字节码)。\n有且只有下面5种情况才会立即初始化类，称为主动引用：\nnew 对象时 读取或设置类的静态字段（除了 被final，已在编译期把结果放入常量池的 静态字段）或调用类的静态方法时； 用java.lang.reflect包的方法对类进行反射调用没初始化过的类时 初始化一个类时发现其父类没初始化，则要先初始化其父类 含main方法的那个类，jvm启动时，需要指定一个执行主类，jvm先初始化这个类 其他对类的引用 称为被动引用，加载类时不会进行初始化动作\n双亲委派模型 BootstrapClassLoader(启动类加载器) ：最顶层的加载类，由C++实现，负责加载 %JAVA_HOME%/lib目录下的jar包和类或者或被 -Xbootclasspath参数指定的路径中的所有类。 ExtensionClassLoader(扩展类加载器) ：主要负责加载目录 %JRE_HOME%/lib/ext 目录下的jar包和类，或被 java.ext.dirs 系统变量所指定的路径下的jar包。 AppClassLoader(应用程序类加载器) :面向我们用户的加载器，负责加载当前应用classpath下的所有jar包和类。 双亲委派是指每次收到类加载请求时，先将请求委派给父类加载器完成（所有加载请求最终会委派到顶层的Bootstrap ClassLoader加载器中），如果父类加载器无法完成这个加载（该加载器的搜索范围中没有找到对应的类），子类尝试自己加载， 如果都没加载到，则会抛出 ClassNotFoundException 异常， 看到这里其实就解释了文章开头提出的第一个问题，父加载器已经加载了JDK 中的 String.class 文件，所以我们不能定义同名的 String java 文件。\n为什么会有这样的规矩设定？\n因为这样可以避免重复加载，当父亲已经加载了该类的时候，就没有必要 ClassLoader 再加载一次。考虑到安全因素，我们试想一下，如果不使用这种委托模式，那我们就可以随时使用自定义的String来动态替代java核心api中定义的类型，这样会存在非常大的安全隐患，而双亲委托的方式，就可以避免这种情况，因为String 已经在启动时就被引导类加载器（Bootstrcp ClassLoader）加载，所以用户自定义的ClassLoader永远也无法加载一个自己写的String，除非你改变 JDK 中 ClassLoader 搜索类的默认算法。\n静态变量加载，静态代码块加载时间点 当类加载器将类加载到JVM中的时候就会创建静态变量，这跟对象是否创建无关。静态变量加载的时候就会分配内存空间。\n静态代码块的代码只会在类第一次初始化的时候执行一次。\n一个类可以有多个静态代码块，它并不是类的成员，也没有返回值，并且不能直接调用。静态代码块不能包含this或者super,它们通常被用初始化静态变量。\n类中代码块的加载顺序：\n静态块/静态变量\u0026gt;构造块\u0026gt;构造方法。\npublic class TestStaticLoadSort { private static String str = \u0026#34;h\u0026#34;; public TestStaticLoadSort() { System.out.println(\u0026#34;构造方法.\u0026#34;); } static { str += \u0026#34;i\u0026#34;; System.out.println(\u0026#34;静态块\u0026#34;); } { System.out.println(\u0026#34;构造块\u0026#34;); } public static void main(String[] args) { TestStaticLoadSort tss = new TestStaticLoadSort(); System.out.println(tss.str); TestStaticLoadSort tss1 = new TestStaticLoadSort(); } /** * ===============输出结果============= * 静态块 * 构造块 * 构造方法. * hi * 构造块 * 构造方法. */ } JDK代理、cglib代理 一般的话，如果业务类实现了接口，jdk的代理可以直接代理业务类的接口。\n而cglib代理一般是针对类本身进行代理\nJDK动态代理只能对实现了接口的类生成代理，而不能针对类 CGLIB是针对类实现代理，主要是对指定的类生成一个子类，覆盖其中的方法，因为是继承，所以该类或方法最好不要声明成final JDK静态代理\n\u0026lt;exclusions\u0026gt; \u0026lt;exclusion\u0026gt; \u0026lt;groupId\u0026gt;com.hundsun.jrescloud\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;jrescloud-starter-rpc-mvc-registry\u0026lt;/artifactId\u0026gt; \u0026lt;/exclusion\u0026gt; \u0026lt;/exclusions\u0026gt; 新建功能接口\n新建功能接口实现的业务bean\n新建业务bean的代理类proxyBean，代理类中\n实例化被代理的类。\n实现功能接口，业务bean功能被proxyBean代理，加上自定义代理逻辑\nproxyBean实例化，具体业务场景使用实际调用的被proxyBean代理后的业务bean的功能\nJDK动态代理\n整个JDK动态代理的秘密也就这些，简单一句话，动态代理就是要生成一个包装类对象，由于代理的对象是动态的，所以叫动态代理。由于我们需要增强，这个增强是需要留给开发人员开发代码的，因此代理类不能直接包含被代理对象，而是一个InvocationHandler，该InvocationHandler包含被代理对象，并负责分发请求给被代理对象，分发前后均可以做增强。从原理可以看出，JDK动态代理是“对象”的代理。\n动态代理类实现invocationHandler接口，实现invode方法\ninvoke接受三个参数，被代理类proxy，被代理方法Method，被代理方法入参args。\nMethod.invoke(targe,args) //targe是被代理类\n动态代理类构造方法接受需要被代理的类，以Object接受\n动态代理类提供getProxy方法初始化，Proxy.newProxyInstance，接受的入参为 被代理类的类加载器，类实现，代理类。\nNeedProxyInteface needProxy= new JDKDynamicClass(new NeedProxy).getProxy();\npublic class JdkDynamicProxyMishu implements InvocationHandler { //需要被动态代理的类 private Object target; public JdkDynamicProxyMishu(Object target) { this.target = target; } /** * 获取被代理接口实例对象 * * @param \u0026lt;T\u0026gt; * @return */ public \u0026lt;T\u0026gt; T getProxy() { return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } /** * 代理调用被代理的类 * * @param proxy * @param method * @param args * @return * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(\u0026#34;dynamic before === proxy: method:\u0026#34; + method + \u0026#34; args:\u0026#34; + args); Object res = method.invoke(target, args); System.out.println(\u0026#34;dynamic after === proxy: method:\u0026#34; + method + \u0026#34; args:\u0026#34; + args); return res; } } Main函数中\n// 2 jdk动态代理 // 保存生成的代理类的字节码文件 // 启动设置 -Dsun.misc.ProxyGenerator.saveGeneratedFiles=true Gongneng proxyLongzong = new JdkDynamicProxyMishu(new Laozong()).getProxy(); proxyLongzong.chifan(); String xiaomubiao = proxyLongzong.xiaomubiao(); System.out.println(xiaomubiao); String jianghua1 = proxyLongzong.jianghua(\u0026#34;讲话内容\u0026#34;); System.out.println(jianghua1); Gongneng proxyLongzong2 = new JdkDynamicProxyMishu(new Laozong2()).getProxy(); proxyLongzong2.chifan(); String xiaomubiao1 = proxyLongzong2.xiaomubiao(); System.out.println(xiaomubiao1); String jianghua2 = proxyLongzong2.jianghua(\u0026#34;讲话内容\u0026#34;); System.out.println(jianghua2); cglib动态代理\n它和jdk动态代理有所不同，对外表现上看CreatProxyedObj，它只需要一个类型clazz就可以产生一个代理对象， 所以说是“类的代理”，且创造的对象通过打印类型发现也是一个新的类型**。不同于jdk动态代理，jdk动态代理要求对象必须实现接口（三个参数的第二个参数），cglib对此没有要求。**\n\u0026ldquo;代理\u0026quot;的目的是构造一个和被代理的对象有同样行为的对象，一个对象的行为是在类中定义的，对象只是类的实例。所以构造代理，不一定非得通过持有、包装对象这一种方式。\n通过“继承”可以继承父类所有的公开方法，然后可以重写这些方法，在重写时对这些方法增强，这就是cglib的思想。根据里氏代换原则（LSP），父类需要出现的地方，子类可以出现，所以cglib实现的代理也是可以被正常使用的\n代理类需要实现MethodInterceptor接口 使用时需要声明指定被代理类 //代理类声明 public class NewMishu implements MethodInterceptor { @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println(\u0026#34;cglib 输出预约时间\u0026#34;); Object result = methodProxy.invokeSuper(o,objects); System.out.println(\u0026#34;cglib 输出记录访客信息\u0026#34;); return result; } } //指定被代理类和代理类，调用代理方法 public class NenFangwenzhe { public static void main(String[] args) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(Laozong.class); enhancer.setCallback(new NewMishu()); Laozong laozong = (Laozong)enhancer.create(); laozong.chifan(); } } JDK中具体的动态代理类是怎么产生的呢？\n产生代理类$Proxy0类\n执行了Proxy.newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)\n将产生$Proxy0类，它继承Proxy对象，并根据第二个参数，实现了被代理类的所有接口，自然就可以生成接口要实现的所有方法了（这时候会重写hashcode，toString和equals三个方法），但是还没有具体的实现体；\n将代理类$Proxy0类加载到JVM中 这时候是根据Proxy.newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)它的第一个参数\u0026mdash;-就是被代理类的类加载器，把当前的代理类加载到JVM中\n创建代理类$Proxy0类的对象 调用的$Proxy0类的$Proxy0（InvocationHandler）构造函数，生成$Proxy0类的对象 参数就是Proxy.newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)它的第三个参数\n这个参数就是我们自己实现的InvocationHandler对象，我们知道InvocationHandler对象中组合加入了代理类代理的接口类的实现类；所以，$Proxy0对象调用所有要实现的接口的方法，都会调用InvocationHandler对象的invoke（）方法实现；\nobject类的方法有哪些 toString、hashcode、equals\nwait、notify、notifyall\n抽象类可不可以实例化? 抽象类被继承后，继承类实例化，会触发抽象类的构造函数\n但是不能直接使用new关键字去实例化\nabstract class B { private String str; public B(String a) { System.out.println(\u0026#34;父类已经实例化\u0026#34;); this.str=a; System.out.println(str); } public abstract void play(); } public class A extends B{ public A(String a) { super(a); System.out.println(\u0026#34;子类已经实例化\u0026#34;); } @Override public void play() { System.out.println(\u0026#34;我实现了父类的方法\u0026#34;); } public static void main(String[] args) { B aa=new A(\u0026#34;a\u0026#34;); } } 父类已经实例化 a 子类已经实例化 字节流、字符流的区别 FileInputStream // 文件的字节输入流\nFileOutputStream // 文件的字节输出流\nFileReader // 文件的字符输入流\nFileWriter // 文件的字符输出流\nJava的字节流 InputStream是所有字节输入流的祖先，而OutputStream是所有字节输出流的祖先。\nJava的字符流 Reader是所有读取字符串输入流的祖先，而writer是所有输出字符串的祖先。 InputStream，OutputStream,Reader,writer都是抽象类。所以不能直接new\n字节流是最基本的，所有的InputStream和OutputStream的子类都是,主要用在处理二进制数据，它是按字节来处理的 但实际中很多的数据是文本，又提出了字符流的概念，它是按虚拟机的encode来处理，也就是要进行字符集的转化 这两个之间通过 InputStreamReader,OutputStreamWriter来关联，实际上是通过byte[]和String来关联 在实际开发中出现的汉字问题实际上都是在字符流和字节流之间转化不统一而造成的\n在从字节流转化为字符流时，实际上就是byte[]转化为String时， public String(byte bytes[], String charsetName) 有一个关键的参数字符集编码，通常我们都省略了，那系统就用操作系统的lang 而在字符流转化为字节流时，实际上是String转化为byte[]时， byte[] String.getBytes(String charsetName)\nhash算法原理，怎么避免hash重复 一般来说是hash的目的是将非固定长度的输入转化成固定长度的输出，该输出被称为散列值\n哈希函数H(key)和处理冲突方法将一组关键字映射到一个有限的地址区间上，并以关键字在地址区间中的象作为记录在表中的存储位置，这种表称为哈希表或散列表。这一映像称为散列，所得存储位置称为哈希地址或散列地址。\nhash一般是关键字加hash函数\nhash函数一般有\n随机数法\n直接寻址：即取关键字或者关键字的线性函数的值为散列地\n避免hash重复\n开放地址法\n开放地执法有一个公式:Hi=(H(key)+di) MOD m i=1,2,…,k(k\u0026lt;=m-1) 其中，m为哈希表的表长。di 是产生冲突的时候的增量序列。如果di值可能为1,2,3,…m-1，称线性探测再散列。 如果di取1，则每次冲突之后，向后移动1个位置.如果di取值可能为1,-1,2,-2,4,-4,9,-9,16,-16,…kk,-kk(k\u0026lt;=m/2)，称二次探测再散列。 如果di取值可能为伪随机数列。称伪随机探测再散列。\n再哈希法\n当发生冲突时，使用第二个、第三个、哈希函数计算地址，直到无冲突时。缺点：计算时间增加。 比如上面第一次按照姓首字母进行哈希，如果产生冲突可以按照姓字母首字母第二位进行哈希，再冲突，第三位，直到不冲突为止\n链地址法（拉链法）\n将所有关键字为同义词的记录存储在同一线性链表中。如下：\nJ2EE servlet生命周期\ninit()\nServlet()-\u0026gt;doPost,doGet\nDestory()\nservlet、filter、listener的区别\n本质都是servlet\nservlet是对请求的实际处理\nInit doGet doPost 、、、Destory\nfilter是对请求的过滤\nfilter能够在一个请求到达servlet之前预处理用户请求，也可以在离开servlet时处理http响应：在执行servlet之前，首先执行filter程序，并为之做一些预处理工作；\n根据程序需要修改请求和响应；在servlet被调用之后截获servlet的执行\nInit dofilter\nListener\n前两个一般是针对URL的\nlistener一般是针对对象数据的变化\n监听器主要是用来监听 request seesion application 对象存取数据的变化\ncontext-param -\u0026gt; listener -\u0026gt; filter -\u0026gt; servlet\nJSP的常见内置对象\nrequest，response，session，application\npageContext，out，config，page，exception\nJSP中的四种作用域\n包括page、request、session和application，具体来说：\npage代表与一个页面相关的对象和属性。 request代表与Web客户机发出的一个请求相关的对象和属性。一个请求可能跨越多个页面，涉及多个Web组件；需要在页面显示的临时数据可以置于此作用域。 session代表与某个用户与服务器建立的一次会话相关的对象和属性。跟某个用户相关的数据应该放在用户自己的session中。 application代表与整个Web应用程序相关的对象和属性，它实质上是跨越整个Web应用程序，包括多个页面、请求和会话的一个全局作用域。 强引用、软引用、弱引用 强引用(StrongReference):\n强引用就是指在程序代码之中普遍存在的，比如下面这段代码中的object和str都是强引用：\nObject object = new Object(); String str = \u0026#34;hello\u0026#34;; public class Main { public static void main(String[] args) { new Main().fun1(); } public void fun1() { Object object = new Object(); Object[] objArr = new Object[1000]; } } 例如运行到：Object[] objArr = new Object[1000];\n如果内存不足，JVM会抛出OOM错误也不会回收object指向的对象。不过要注意的是，当fun1运行完之后，object和objArr都已经不存在了，所以它们指向的对象都会被JVM回收。如果想中断强引用和某个对象之间的关联，可以显示地将引用赋值为null，这样一来的话，JVM在合适的时间就会回收该对象。\n软引用(SoftReference)：\n软引用是用来表示一些有用但不是必须的对象。用软引用关联的对象，只有在内存不足的时候jvm才会回收改对象\npublic class Main { public static void main(String[] args) { SoftReference\u0026lt;String\u0026gt; sr = new SoftReference\u0026lt;String\u0026gt;(new String(\u0026#34;hello\u0026#34;)); System.out.println(sr.get()); } } 弱引用（WeakReference）:\n弱引用也是用来描述非必需对象的，当JVM进行垃圾回收时，无论内存是否充足，都会回收被弱引用关联的对象。在java中，用java.lang.ref.WeakReference类来表示。下面是使用示例：\npublic class Main { public static void main(String[] args) { WeakReference\u0026lt;String\u0026gt; sr = new WeakReference\u0026lt;String\u0026gt;(new String(\u0026#34;hello\u0026#34;)); System.out.println(sr.get()); System.gc(); //通知JVM的gc进行垃圾回收 System.out.println(sr.get()); } } 虚引用(PhantomReference)：\n虚引用和前面的软引用、弱引用不同，它并不影响对象的生命周期。在java中用java.lang.ref.PhantomReference类表示。如果一个对象与虚引用关联，则跟没有引用与之关联一样，在任何时候都可能被垃圾回收器回收。\npublic class Main { public static void main(String[] args) { ReferenceQueue\u0026lt;String\u0026gt; queue = new ReferenceQueue\u0026lt;String\u0026gt;(); PhantomReference\u0026lt;String\u0026gt; pr = new PhantomReference\u0026lt;String\u0026gt;(new String(\u0026#34;hello\u0026#34;), queue); System.out.println(pr.get()); } } 接口和抽象类的异同 相同：\n都可以定义抽象方法，抽象方法都必须被子类或者实现类重写 都不能实例化对象 都可以定义public static 方法，public static final 常量 不同点：\n抽象类需要用abstract修饰，且抽象方法需要abstact修饰。而接口类需要interface修饰，抽象方法直接定义即可\n抽象类除了可以定义抽象方法，还可以和普通类一样正常定义常量和方法。而接口类，只能定义抽象方法，静态方法，final static常量 default方法。\n一个类只能继承extends单个抽象类。而可以实现implement多个接口\n抽象类是对象更具体的封装。而接口一般只是封装了对象的功能\n基本数据类型和引用数据类型 基本数据类型： byte，short，int，long。\nfloat，double，char，boolean\n存放在栈中的简单数据段，数据大小确定，内存空间大小可以分配，它们是直接按值存放的，所以可以直接按值访问\n注：类中方法的局部变量一般是在栈中存储。类中的全局变量，静态变量等一般是在堆中的\n引用数据类型： 类、 接口类型、 数组类型、 枚举类型、 注解类型、 字符串型等对象类型\n数据传递方式 基本变量类型：在方法中定义的非全局基本数据类型变量，调用方法时作为参数是按数值传递的\n引用变量类型：引用数据类型变量，调用方法时作为参数是按引用传递的\n两种传递当时实际都是值传递：基本类型传递的是值的副本，引用类型传递的是引用的副本\n基本数据类型和包装数据类型 包装类和基本数据类型对应的类一一对应存在，方便涉及到对象的操作。 包含每种基本数据类型的相关属性如最大值、最小值等，以及相关的操作方法 例：int和Integer的区别\nInteger是int的包装类，int则是java的一种基本数据类型 Integer变量必须实例化后才能使用，而int变量不需要 Integer实际是对象的引用，当new一个Integer时，实际上是生成一个指针指向此对象；而int则是直接存储数据值 Integer的默认值是null，int的默认值是0 String、StringBuilder、StringBuffer 线程安全性\nString 中的对象是不可变的，也就可以理解为常量，线程安全。AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类，定义了一些字符串的基本操作，如 expandCapacity、append、insert、indexOf 等公共方法。StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁，所以是线程安全的。StringBuilder 并没有对方法进行加同步锁，所以是非线程安全的。\n性能\n每次对 String 类型进行改变的时候，都会生成一个新的 String 对象，然后将指针指向新的 String 对象。StringBuffer 每次都会对 StringBuffer 对象本身进行操作，而不是生成新的对象并改变对象引用。相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升，但却要冒多线程不安全的风险。\nJava异常 异常都有一个共同的祖先 java.lang 包中的 Throwable 类。\nException（异常） 和 Error（错误） ，二者都是 Java 异常处理的重要子类，各自都包含大量子类。\n异常和错误的区别：异常能被程序本身处理，错误是无法处理。\nnullpointerexception\njava.lang.nullpointerexception //这个异常大家肯定都经常遇到，异常的解释是\u0026#34;程序遇上了空指针\u0026#34;，简单地说就是调用了未经初始化的对象或者是不存在的对象，这个错误经常出现在创建图片，调用\t数组这些操作中，比如图片未经初始化，或者图片创建时的路径错误等等。 Classnotfound\njava.lang.classnotfoundexception //这个异常是很多原本在jb等开发环境中开发的程序员，把jb下的程序包放在wtk下编译经常出现的问题，异常的解释是\u0026#34;指定的类不存在\u0026#34;，这里主要考虑一下类的名称和路径是否正确即可，如果是在jb下做的程序包，一般都是默认加上package的，所以转到wtk下后要注意把package的路径加上。 arrayindexoutofboundsexception//数组下标越界 illegalargumentexception//参数不合法 IOException//输入输出异常 JDK8新特性 stream流 lamba函数 java.util.concurrent包，封装了常用的类， 线程相关：ThreadPoolExecutor、Callable新建线程、FutureTask特性 时间相关：TimeUnit bean相关：atomic原子类 算法基础 稳定排序 定义：保证排序前2个相等的数其在序列的前后位置顺序和排序后它们两个的前后位置顺序相同。在简单形式化一下，如果Ai = Aj，Ai原来在位置前，排序后Ai还是要在Aj位置前。\n时间复杂度 不论是数组、链表还是二叉树、二叉排序树（搜索树）、红黑树，我们要找到其中特定的一个元素，方法只有一个那就是挨个比较直到找到为止，这就造成了查找的时间复杂度总是与N有关系。\n数组 链表 二叉树 二叉排序树 红黑树 查找 O(N) O(N) O(N) O(log2N)~O(N) O(log2N) 数组：特指无序数组。假设数组中有N个元素，我们要找到其中一个特定的元素，通常要进过N/2次比较，所以时间复杂度上来说还是O(N)。如果数组是有序数组的话，可以折半查找，此时的时间复杂度是O(log2N)。 链表：同理于数组，假设有N个元素，要找到其中一个特定的元素，时间复杂度还是O(N)。 二叉树：注意是二叉树，父节点与左子节点、右子节点之间没有大小关系，实在不明白，可以看看这篇文章二叉树（从建树、遍历到存储）Java。此时要从N个节点中找到特定的节点，只能是遍历每一个元素，时间复杂度是O(N)。 二叉排序树：此时父节点与左、右子节点之间就有大小关系了。在理想状态下即节点分布均匀的情况下相当于折半查找，所以时间复杂度是O(log2N)，最坏情况是出现左、右斜树，此时时间复杂度会降到O(N)，一般情况下时间复杂度会介于两者之间即O(N)到O(log2N)。 红黑树：虽然红黑树在插入、删除操作上很是麻烦，但是对于查找操作跟二叉排序树是一模一样的，因为红黑树不过是加了平衡算法的二叉排序树而已，二叉排序树最基本的父节点与左、右子节点之间的大小关系肯定是满足的，所以时间复杂度是O(log2N)。 只看表达式的可能感觉不强烈，那我们假设N=1000000（一百万）。\n数组 链表 二叉树 二叉排序树 红黑树 查找 1000000（一百万） 1000000（一百万） 1000000（一百万） 20~1000000 20 LRU算法（least recently used,最近最少使用） 算法根据数据的历史访问记录来进行淘汰数据，其核心思想是“如果数据最近被访问过，那么将来被访问的几率也更高”。\n常见的实现是使用链表保存缓存\n新数据加入链表头部 每当缓存命中，即缓存数据再次被访问，则将数据移到链表头部 链表满的时候，丢弃链表尾部数据 【命中率】 当存在热点数据时，LRU的效率很好，但偶发性的、周期性的批量操作会导致LRU命中率急剧下降，缓存污染情况比较严重。 【复杂度】 实现简单。 【代价】 命中时需要遍历链表，找到命中的数据块索引，然后需要将数据移到头部。\ntodo：优化热点数据\ndijkstra算法求最短路径 todo\n基本数据结构 常用基本数据结构\n集合：数组\n线性结构：栈、队列、链表\n树形结构：堆\n图形结构：散列表、图\n数据结构分类：\n集合，线性结构，树形结构，图形结构（树、图也被称为非线性）\n集合 数据结构中的元素之间除了“同属一个集合” 的相互关系外，别无其他关系；\n线性结构 数据结构中的元素存在一对一的相互关系\n线性结构的特点：在数据元素有限集中，除第一个元素无直接前驱，最后一个元素无直接后续以外，每个数据元素有且仅有一个直接前驱元素和一个直接后继元素。\n线性表定义：n个类型相同数据元素的有限序列。\n常用的线性结构：线性表、栈、队列、链表、数组\n栈 队列 链表 数组 注：线性表，栈和队列、数组的异同\n都是线性结构，都是逻辑结构的概念。都可以用顺序存储或链表存储；栈和队列是两种特殊的线性表，即受限的线性表，只是对插入、删除运算加以限制。\n线性表是一种抽象数据类型；数组是一种具体的数据结构，线性表是元素之间具有1对1的线性关系的数据元素的集合，而数组是具体的实现，即一组数据元素到数组下标的一一映射\n树形结构 树是一种数据结构，它是由n（n\u0026gt;=1）个有限节点组成一个具有层次关系的集合。\n拓展树（平衡二叉树、红黑树、B+树）\n简单二叉树 每个结点最多有两颗子树，结点的度最大为2。 左子树和右子树是有顺序的，次序不能颠倒。 即使某结点只有一个子树，也要区分左右子树。 完全二叉树、满二叉树 完全二叉树：设二叉树的深度为h，除第 h 层外，其它各层 (1～h-1) 的结点数都达到最大个数， 第 h 层所有的结点都连续集中在最左边\n满二叉树：深度为k且有2^k-1个结点的二叉树称为满二叉树\n堆 堆中某个节点的值总是不大于或不小于其父节点的值； 堆总是一棵完全二叉树。 平衡二叉树 它是一棵空树或它的左右两个子树的高度差的绝对值不超过1，并且左右两个子树都是一棵平衡二叉树。\n二叉查找树 也称二叉搜索树，或二叉排序树。其定义也比较简单，要么是一颗空树，要么就是具有如下性质的二叉树：\n若任意节点的左子树不空，则左子树上所有结点的值均小于它的根结点的值；\n若任意节点的右子树不空，则右子树上所有结点的值均大于它的根结点的值；\n任意节点的左、右子树也分别为二叉查找树；\n没有键值相等的节点。\n最优二叉树——哈夫曼树 最优二叉树就是从已给出的目标带权结点(单独的结点) 经过一种方式的组合形成一棵树.使树的权值最小.\n基本概念：\n路径长度\n在树中从一个结点到另一个结点所经历的分支构成了这两个结点间的路径上的分支数称为它的路径长度\n树的路径长度\n树的路径长度是从树根到树中每一结点的路径长度之和。在结点数目相同的二叉树中，完全二叉树的路径长度最短。\n树的带权路径长度(Weighted Path Length of Tree，简记为WPL) 结点的权：在一些应用中，赋予树中结点的一个有某种意义的实数。 结点的带权路径长度：结点到树根之间的路径长度与该结点上权的乘积。 树的带权路径长度(Weighted Path Length of Tree)：定义为树中所有叶结点的带权路径长度之和，通常记为：\n例：给定4个叶子结点a，b，c和d，分别带权7，5，2和4。构造如下图所示的三棵二叉树(还有许多棵)，它们的带权路径长度分别为：\n​ (a)WPL=72+52+22+42=36 ​ (b)WPL=73+53+21+42=46 ​ (c)WPL=71+52+23+43=35\n其中(c)树的WPL最小，可以验证，它就是哈夫曼树。\n红黑树 每个个节点或者是黑色，或者是红色。\n根节点是黑色。\n每个叶子节点（NIL）是黑色。 [注意：这里叶子节点，是指为空(NIL或NULL)的叶子节点！如果一个节点是红色的，则它的子节点必须是黑色的。 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。\nb-树 b-树就是b树，不存在b减树的读法\nb树相对以二叉查找树的优点：减少了磁盘IO，索引树的高度表示了索引IO次数\n一个m阶的B树具有如下几个特征\n根结点至少有两个子女。\n每个中间节点都包含k-1个元素和k个孩子，其中 m/2 \u0026lt;= k \u0026lt;= m\n每一个叶子节点都包含k-1个元素，其中 m/2 \u0026lt;= k \u0026lt;= m 所有的叶子结点都位于同一层。\n每个节点中的元素从小到大排列，节点当中k-1个元素正好是k个孩子包含的元素的值域分划。 由于b树有众多条件限制，所以他是一个自平衡的树\nb树的值域划分：\nb+树：\n有k个子树的中间节点包含有k个元素（B树中是k-1个元素），每个元素不保存数据，只用来索引，所有数据都保存在叶子节点。 所有的叶子结点中包含了全部元素的信息，及指向含这些元素记录的指针，且叶子结点本身依关键字的大小自小而大顺序链接。 所有的中间节点元素都同时存在于子节点，在子节点元素中是最大（或最小）元素。 B-树和B+树的区别：\nb-树的每个节点包含数据、指针、关键字三个部分，每个节点对应着一个外存中的数据块，其中数据是指真实需要的数据，指针是指向子节点的指针，关键字是查找对象的比较目标。需要注意的是每个节点查找都需要读入内存，需要一次io操作，然后对于读入内存的数据再进行内存中的查找方法\n相对于b树b+树更加的\u0026rsquo;矮胖\u0026rsquo;，b+树中只在叶子节点保存了数据，非叶子节点都只包含关键字和指针，这种方式使得每个非叶子节点能包含更多的关键字信息，更加的减少磁盘IO次数。\nb+树除了叶子节点外，其他节点都不存储卫星数据，只存储索引数据。b树每个节点有索引又有卫星数据；卫星数据及索引对应的数据\n注： b+树怎么实现索引功能： https://blog.csdn.net/qq_26222859/article/details/80631121 散列表 也叫哈希表，是根据关键码和值 (key和value) 直接进行访问的数据结构，通过key和value来映射到集合中的一个位置，这样就可以很快找到集合中的对应元素。\nHashMap，HashTable等，利用hash表的优势\n因为哈希表是基于数组衍生的数据结构，在添加删除元素方面是比较慢的，所以很多时候需要用到一种数组结合链表的一种结构\njdk1.8之后才换成了数组加红黑树的结构\n图性结构 图是由结点的有穷集合V和边的集合E组成。其中，为了与树形结构加以区别，在图结构中常常将结点称为顶点，边是顶点的有序偶对，若两个顶点之间存在一条边，就表示这两个顶点具有相邻关系。\n基础数据结构的编码实现 二叉树，前序后序的编码实现\n前中后序的遍历实际上根据根节点的输出顺序的命名。\n一般根节点先输出即 根-左-右 为前序遍历，同样的 左-根-右为中序遍历，左-右-根为后序遍历\n树的深度和广度遍历是怎么样过程\n简单集合类的代码底层原理 HashMap：\nhashmap在put的时候，我们把每次put进入hashmap的数据称之为为entry，hashmap的所有键值对存储在一个数组中，这个数组是hashmap的主干，首先是通过hash函数算出put的k所存放的位置。因为数组初始化长度是有限的，如果通过hash函数和k计算出重复的entry存放位置，可以通过链表的形式存储。\n同样get的时候，hash函数计算k所在位置，如果计算出的位置的entry的k符合即返回。不符合判断当前节点是否存在链表，有则链式查询。\nhashmap的index的计算方式：index = HashCode（Key） \u0026amp; （Length - 1） （Length是HashMap的长度）\n参考：漫画：什么是HashMap？\n不允许重复的键\nJDK1.7及之前：数组+链表\nJDK1.8：数组+链表+红黑树\nTreeMap：相对于Hashmap来说，Treemap一般是基于红黑树，且其按key的自然顺序或自定义顺序存储键值对\nHashSet:\n不允许重复的值；使用Map的Key存值,Value存放一个固定的Object\nArrayList：\n它是基于数组实现的List类，它封装了一个Object[]类型的数组，长度可以动态的增长\nLinkedList:\n数组的地址是连续的，链表的地址是不连续的\n双向链表的数据结构\n常用集合类知识 集合类分类\nList和Set的区别\nlist方法可以允许重复的对象，而set方法不允许重复对象 list可以插入多个null元素，而set只允许插入一个null元素 list是一个有序的容器，保持了每个元素的插入顺序。即输出顺序就是输入顺序，而set方法是无序容器，无法保证每个元素的存储顺序，TreeSet通过 Comparator 或者 Comparable 维护了一个排序顺序 list是一个有序的容器，保持了每个元素的插入顺序。即输出顺序就是输入顺序，而set方法是无序容器，无法保证每个元素的存储顺序，TreeSet通过 Comparator 或者 Comparable 维护了一个排序顺序\nHashSet和HashMap的区别\nHashSet和HashMap的区别，hashset的hash函数计算的val的hash函数，即hashset中没有相同的val。\nhashmap计算的是k的hash值\n本身属于不同的集合类，set属于集合类，map是k v\nCollection Collections的区别\ncollection:它属于集合框架中的一个接口，并且是顶层接口。\nCollectios 则是在集合框架中给我们提供的一个工具类，它提供了一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作。\n为什么hashMap的主干数组长度是2的倍数 其实在hashmap的使用长度超过负载因子时，hashmap会自动扩容。由于hash值太大不能拿来直接散列，所以要用hash值对数组长度取余操作，进一步放到数组下标里。数组下标的计算方法index = HashCode（Key） \u0026amp; （Length - 1）。 采用二进制位操作 \u0026amp;，如果长度不是2的幂次数，则通过 hash(key)\u0026amp;(length-1) 位运算得出的index冲突几率高。这就解释了 HashMap 的长度为什么是2的幂次方。\nString、StringBuilder、StringBuffer String：适用于少量的字符串操作的情况\nStringBuilder：适用于单线程下在字符缓冲区进行大量操作的情况-线程不安全\nStringBuffer：适用多线程下在字符缓冲区进行大量操作的情况-线程安全\n阻塞队列实现 ArrayBlockingQueue\n基于数组实现的阻塞队列，初始化时需要定义数组的大小，也就是队列的大小，所以这个队列是一个有界队列\nLinkedBlockingQueue\n基于链表实现的阻塞队列，既然是链表，那么就可以看出这种阻塞队列含有链表的特性，那就是无界。但是实际上LinkedBlockingQueue是有界队列，默认大小是Integer的最大值，而也可以通过构造方法传入固定的capacity大小设置\n可以看出LinkedBlockingQueue的属性和ArrayBlockingQueue的属性大致差不多，都是通过ReentrantLock和Condition来实现多线程之间的同步，而LinkedBlockingQueue却多了一个ReentrantLock，而不是入队和出队共用同一个锁\n那么为什么ArrayBlockingQueue只需要一个ReentrantLock而LinkedBlockingQueue需要两个ReentrantLock呢？\nReentrantLock肯定是越多越好，锁越多那么相同锁的竞争就越少；LinkedBlockingQueue分别有入队锁和出队锁，所以入队和出队的时候不会有竞争锁的关系；而ArrayBlockingQueue只有一个Lock，那么不管是入队还是出队，都需要竞争同一个锁，所以效率会低点。ArrayBlockingQueue是环形数组结构，入队的地址和出队的地址可能是同一个，比如数组table大小为1，那么第一次入队和出队需要操作的位置都是table[0]这个元素，所以入队和出队必须共用同一把锁；而LinkedBlockingQueue是链表形式，内存地址是散列的，入队的元素地址和出队的元素地址永远不可能会是同一个地址。所以可以采用两个锁，分别对入队进行加锁同步和对出队进行加锁同步即可。\n线程安全容器 线程安全：\nHashTable，ConcurrentHashMap | Vector，CopyOnWrieArrayList，Collections.synchronizedList | CopyOnWrieArraySet，Collections.synchronizedSet\n不安全：\nHashMap | ArrayList | HashSet\n不安全会报：ConcurrentModificationException异常\n分析异常原因：HashMap在put kv时，底层指令是先判断集合是否已满是否需要拓展，再put k再put v。\n由于多线程调度关系，如果判断集合是否已满操作没有进行线程安全处理。可能有多个线程put 相同的k v（数据冗余）。\n如果再put kv。操作没有进行线程安全处理,可能会出现数据丢失情况\nreHash的过程，可能会出现链表环，put操作会陷入死循环\narraylist 如何进行扩充，平衡二叉树原理和特点。\nHashMap、Hashset、HashTable、ConcurrentHashMap 的原理和差异 指定初始容量，减少内存消耗\nhashmap\n初始容量为16，负载因子0.75 线程不安全，数组+链表，数组加红黑树，大于16*0.75自动扩容，在链表长度超过8，Node数组超过64时会将链表结构转换为红黑树 index的计算是：hash(key) \u0026amp; (length -1) 可以时候null的key和value hashtable\nKey value 不能为空，线程安全，修改时锁住了整个hashtable效率低 初始11size hashset\n以hashmap的key存放值，hashmap的value默认是空object对象 值不能重复，本身是set集合的 concurrentHashMap\n使用锁分段的技术，将hashmap的主干数组分成了多段数据用segment[]存储，理论情况下支持n个进程进行并发操作 LinkedHashMap和hashMap LinkedHashMap是继承于HashMap，是基于HashMap和双向链表来实现的。 HashMap无序；LinkedHashMap有序 网络原理 正向代理、反向代理 正向代理是在客户端设置的，客户端先设置vpn转发地址，vpn转发到不同服务器\n反向代理是在服务端设置，客户端无感知，客户端访问的是虚拟的ip端口，进行负载等功能，对客户端屏蔽服务端的信息\nSocket socket是应用层与TCP/IP协议族通信的中间软件抽象层，它是一组接口。在设计模式中，Socket其实就是一个门面模式，它把复杂的TCP/IP协议族隐藏在Socket接口后面，对用户来说，一组简单的接口就是全部，让Socket去组织数据，以符合指定的协议。\nsocket在五层网络模型中的位置，处于应用层和传输层的中间，是对传输层协议的抽象封装。\nsocket的基本操作：\nsocket()函数：用于创建一个socket描述符（socket descriptor），它唯一标识一个socket\nsocket函数的三个参数分别为：\ndomain：即协议域，又称为协议族（family）。常用的协议族有，AF_INET、AF_INET6、AF_LOCAL（或称AF_UNIX，Unix域socket）、AF_ROUTE等等。协议族决定了socket的地址类型，在通信中必须采用对应的地址，如AF_INET决定了要用ipv4地址（32位的）与端口号（16位的）的组合、AF_UNIX决定了要用一个绝对路径名作为地址。 type：指定socket类型。常用的socket类型有，SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等（socket的类型有哪些？）。 protocol：故名思意，就是指定协议。常用的协议有，IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等，它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议（这个协议我将会单独开篇讨论！）。 bind()函数：把一个地址族中的特定地址赋给socket，把一个ipv4或ipv6地址和端口号组合赋给socket\nlisten()、connect()函数：作为一个服务器，在调用socket()、bind()之后就会调用listen()来监听这个socket，如果客户端这时调用connect()发出连接请求，服务器端就会接收到这个请求。\naccept()函数：TCP服务器端依次调用socket()、bind()、listen()之后，就会监听指定的socket地址了。TCP客户端依次调用socket()、connect()之后就想TCP服务器发送了一个连接请求。TCP服务器监听到这个请求之后，就会调用accept()函数取接收请求，这样连接就建立好了。之后就可以开始网络I/O操作了，即类同于普通文件的读写I/O操作\nread()、write()函数等\nclose()函数\nBIO、NIO JAVA的BIO、NIO和 AIO 是 Java 语言对操作系统的各种 IO 模型的封装。在使用这些 API 的时候，不需要关心操作系统层面的知识，也不需要根据不同操作系统编写不同的代码。只需要使用Java的API\nBIO(Bolcking I/O)\n同步阻塞I/O模式，数据的读取写入必须阻塞在一个线程内等待其完成。BIO通信（一请求一应答）模型图\nBIO 通信模型 的服务端，通常由一个独立的 Acceptor 线程负责监听客户端的连接在 while(true) 循环中服务端会调用 accept() 方法等待接收客户端的连接的方式监听请求，请求一旦接收到一个连接请求，就可以建立通信套接字在这个通信套接字上进行读写操作，此时不能再接收其他客户端连接请求，只能等待同当前连接的客户端的操作执行完成， 不过可以通过多线程来支持多个客户端的连接，如上图所示。传统的NIO是基于字节流和字符流进行操作的。\n缺点：由于客户端连接数与服务器线程数成正比关系，可能造成不必要的线程开销，严重的还将导致服务器内存溢出。当然，这种情况可以通过线程池机制改善，但并不能从本质上消除这个弊端\nNIO(Non-blocking I/O)\n同步非阻塞的。它支持面向缓冲的，基于通道的I/O操作方法。 NIO提供了与传统BIO模型中的 Socket 和 ServerSocket 相对应的 SocketChannel 和 ServerSocketChannel 两种不同的套接字通道实现。服务器的实现模式是多个请求一个线程，即请求会注册到多路复用器Selector上，多路复用器轮询到连接有IO请求时才启动一个线程处理.\nNIO 包含下面几个核心的组件：\nChannel(通道) Buffer(缓冲区) Selector(选择器) NIO和BIO的区别：\nBIO是同步阻塞的，NIO为同步非阻塞 BIO是面向IO流操作的，NIO是面向缓冲区 BIO的流的读写是单向的。NIO是通过通道进行缓冲区操作，通道是双向的可读可先 对于并发要求不高可以使用BIO，节省内存空间。高并发情况下使用NIO，减少线程切换，但是会增加内存空间的消耗，因为缓冲区的存在。以空间换取时间 由于传统的JavaNIO手动实现较为复杂。一般使用Netty框架实现NIO的需求，Netty对于复杂的NIO接口做了封装和优化 TCP三次握手、四次挥手 TCP 的三个特点：面向连接、可靠性和面向字节流。\nSYN（Synchronize Sequence Numbers），同步序列编号；\nACK（Acknowledge Character），确认字符；\n三次握手建立连接\n客户端：发送SYN连接包-第一次连接保证的是什么todo\n服务端：接受到SYN，发送SYN+ACK连接加确认包\n客户端：发送ACK确认包\n四次挥手断开连接\n客户端：FIN断开连接包\n服务端：接受FIN断开包，发送ACK断开确认包\n服务端：发送FIN+ACK断开包（需要等正在发送的数据发送完成）\n客户端：接受FIN+ACK，发送ACK断开连接\nNetty框架 Netty是一个高性能、异步事件驱动的NIO框架，它提供了对TCP、UDP和文件传输的支持，作为一个异步NIO框架，Netty的所有IO操作都是异步非阻塞的，通过Future-Listener机制，用户可以方便的主动获取或者通过通知机制获得IO操作结果。\nHTTPS相对HTTP有啥好处 https有加密的过程，对通信内容加密，ssl\\tls+http https有验证报文的完整性 Tomcat支持的四种线程模型 描述 BIO 阻塞式IO，采用传统的java IO进行操作，该模式下每个请求都会创建一个线程，适用于并发量小的场景 NIO 同步非阻塞，比传统BIO能更好的支持大并发，tomcat 8.0 后默认采用该模式 APR tomcat 以JNI形式调用http服务器的核心动态链接库来处理文件读取或网络传输操作，需要编译安装APR库 AIO 异步非阻塞，tomcat8.0后支持 架构 分布式和微服务区别 分布式\n分布式的核心是拆，将一个完整的项目拆分为多个模块，并且将模块分开部署\n分布式的项目拆分逻辑：水平拆分，垂直拆分\n水平拆分：根据应用分层拆分思想拆分。例如经典的前后端分离架构。将应用拆分成3层架构，表示层(jsp\\servlet\\html)、业务逻辑层（service）、数据访问层（dao）。在分开部署。可以将表示层部署在服务器A，业务逻辑层和数据访问层部署在服务器B，通过网关等中间件进行服务通信。\n垂直拆分：根据业务应用的功能进行拆分，比如电商项目，可以将电商项目拆分为订单项目，用户项目，秒杀项目。拆分后每个项目任然可以作为独立的项目运行。这种属于垂直拆分。\n微服务\n微服务字面上就是非常微小的服务，可以理解为粒度更加细的垂直划分\n例如上面的电商项目中的订单项目，可以更加服务边界，进一步进行拆分为购物项目、结算项目、售后等。上述的订单项目可以作为一个分布式项目组成元素，但是不适合作为微服务的元素，因为订单项目本身还可以进行更小的服务拆分。\n总结 分布式：拆了就行，对于服务边界的划分较为宽松\n微服务：对于项目中的服务边界划分清晰。细粒度的拆分\n流量监控、热点配置、服务降级熔断 Hystrix,sentinel\n服务网关 鉴权，负载，流控\nZull gateway\n注册中心 Nacos\\zk\\Eureka\nZK：以CAP原理中的CP为主，主要保证的是多节点注册信息的一致性和分区容错性\nEureka：以CAP中的AP为主，保证注册中心多节点的可用性和分区容错性为主\nNACOS：支持AP和CP的切换\n配置中心 nacos\n链路追踪 Sleuth\n分布式事务 seata\n设计模式 生产者——消费者 ShareData shareData = new ShareData(); // 生产 for(int i=0;i\u0026lt;10;i++){ new Thread(()-\u0026gt;{ shareData.increment(); },\u0026#34;aaa\u0026#34;+i).start() } // 消费 for(int i=0;i\u0026lt;10;i++){ new Thread(()-\u0026gt;{ shareData.decrement(); },\u0026#34;aaa\u0026#34;+i).start() } class ShareData(){ private int num=0; private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); public void increment(){ lock.lock(); try{ while(number!=0){ condition.await(); } number++; conditon.signAll(); }catch(){ }finally{ lock.unlock(); } } public void decrement(){ lock.lock(); try{ while(number==0){ condition.await(); } number--; condition.signAll(); }catch(){ }finally{ lock.unlock(); } } } 单例模式 public class SingletonDemo{ private static volatile SinletonDemo singletonInstance; private SingletonDemo(){ } private SingletonDemo getSingletonInstance(){ if(singletonInstance==null){ synchronized(SingletonDemo.class){ if(singletonInstance==null){ singletonInstance = new SingletonDemo(); } } } return singletonInstance; } } 策略模式\n一个接口多个实现，加载时根据策略加载不同实现\n装饰者模式\n公用父类，实例化子类时，个性化子类属性\nSpring框架 spring的核心原理 AOP(Aspect Oriented Programming)-面向切面编程:\n核心逻辑：在不修改原有代码情况下添加新功能。主要是通过指定代码切入点，通过反射原理进行功能解耦，提高代码复用率。主要用于日志，性能监控，异常处理等\nIOC(Inversation of controller)-控制反转:\n核心逻辑：将对象的初始化，销毁等逻辑由spring容器统一控制管理\n便于AOP操作 方便管理对象之间的复杂依赖关系 DI(Dependency Injection)-依赖注入:\n依赖注入实现IOC方法和途径，通过依赖注入，将容器中对象的依赖对象，动态注入\nspringcloud和dubbo的底层区别 dubbo使用Netty这样的NIO框架，是基于TCP协议传输的，配合以Hession序列化完成RPC通信\nDubbo 协议默认使用 Netty 作为基础通信组件，用于实现各进程节点之间的内部通信\n而SpringCloud是基于Http协议+rest接口调用远程过程的通信，相对来说，Http请求会有更大的报文，占的带宽也会更多，使用的是JackSon处理JSON是序列化\nSpring内部最核心的就是IOC了，动态注入，让一个对象的创建不用new了，可以自动的生产，这其实就是利用java里的反射，反射其实就是在运行时动态的去创建、调用对象，Spring就是在运行时，跟xml Spring的配置文件来动态的创建对象和调用对象里的方法的 。\nSpring还有一个核心就是AOP面向切面编程，可以为某一类对象进行监督和控制（也就是在调用这类对象的具体方法的前后去调用你指定的模块）从而达到对一个模块扩充的功能。这些都是通过配置类达到的。\nSpring目地就是让对象与对象（模块与模块）之间的关系没有通过代码来关联，都是通过配置类说明管理的\nRPC和HTTP的异同\n相同：\n都是基于tcp协议的，远程服务调用 不同：\nrpc协议，要求通信双方要使用相同的rpc框架，因为不同的rpc可能使用的序列化方法不一样，不同的RPC协议，数据格式不一定相同\n需要关心通信中的细节，但是效率较高\nhttp协议，不需要通信双方都是相同的框架，只要满足http协议，满足restful原则既可以进行通信，一般请求数据格式使用json格式，不需要关注通信细节，http协议做了基本封装，但是消息较为臃肿，效率相对低\naop的底层实现 通过aop注解获取需要进行操作的类（类全路径），通过反射获取类内容，通过动态代理进行代理改类是方法执行，再其方法执行前后加上自定义方法\nspring常用注解 @Transactional 参数：rollbackFor-指定异常回滚，noRollbackFor-指定异常不回滚\nisolation-事务隔离级别（默认可重复读）\ntimeout-事务超时时间\n注：\n@Transactional 只能被应用到public方法上, 对于其它非public的方法,如果标记了@Transactional也不会报错,但方法没有事务功能. 为什么transactional只能用在public方法上\n在使用 Spring AOP 代理时，Spring 在调用在图 1中的 TransactionInterceptor 在目标方法执行前后进行拦截之前，DynamicAdvisedInterceptor（CglibAopProxy 的内部类）的的 intercept 方法或 JdkDynamicAopProxy 的 invoke 方法会间接调用 AbstractFallbackTransactionAttributeSource（Spring 通过这个类获取表 1.@Transactional 注解的事务属性配置属性信息）的 computeTransactionAttribute 方法\nprotected TransactionAttribute computeTransactionAttribute(Method method, Class \u0026lt; ?\u0026gt;targetClass) { // Don\u0026#39;t allow no-public methods as required. if (allowPublicMethodsOnly() \u0026amp;\u0026amp; !Modifier.isPublic(method.getModifiers())) { return null; } 这个方法会检查目标方法的修饰符是不是 public，若不是 public，就不会获取 @Transactional 的属性配置信息，最终会造成不会用 TransactionInterceptor 来拦截该目标方法进行事务管理。\n事务失效的场景\n如果方法中有try{}catch(Exception e){}处理，那么try里面的代码块就脱离了事务的管理，若要事务生效需要在catch中throw new RuntimeException (\u0026ldquo;xxxxxx\u0026rdquo;)；这是可能存在的事务失效的场景。\n在@Transactional注解的方法中，再调用本类中的其他方法method2时，那么method2方法上的@Transactional注解是不！会！生！效！的！\n@Transactional注解通过AOP实现方式图解1\n通过代理对象在目标对象前后进行方法增强，也就是事务的开启提交和回滚。那么继续调用本类中其他方法是怎样呢，如下图\n@Transactional注解通过AOP实现方式图解2\n可见目标对象内部的自我调用，也就是通过this.指向的目标对象将不会执行方法的增强。\n避免 Spring 的 AOP 的自调用问题，必须要跨service调用\n事务的传播行为\n@Transactional(propagation=Propagation.REQUIRED)：默认的spring事务传播级别，使用该级别的特点是，如果上下文中已经存在事务，那么就加入到事务中执行，如果当前上下文中不存在事务，则新建事务执行，所以这个级别通常能满足处理大多数的业务场景。 @Transactional(propagation=PROPAGATION.SUPPORTS)：从字面意思就知道，supports(支持)，该传播级别的特点是，如果上下文存在事务，则支持当前事务，加入到事务执行，如果没有事务，则使用非事务的方式执行。所以说，并非所有的包在transactionTemplate.execute中的代码都会有事务支持。这个通常是用来处理那些并非原子性的非核心业务逻辑操作，应用场景较少。 @Transactional(propagation=PROPAGATION.MANDATORY)：该级别的事务要求上下文中必须要存在事务，否则就会抛出异常！配置该方式的传播级别是有效的控制上下文调用代码遗漏添加事务控制的保证手段。比如一段代码不能单独被调用执行，但是一旦被调用，就必须有事务包含的情况，就可以使用这个传播级别。 @Transactional(propagation=PROPAGATION.REQUIRES_NEW)：从字面即可知道，每次都要一个新的事务，该传播级别的特点是，每次都会新建一个事务，并且同时将上下文中的事务挂起，当新建事务执行完成以后，上下文事务再恢复执行。 这是一个很有用的传播级别，举一个应用场景：现在有一个发送100个红包的操作，在发送之前，要做一些系统的初始化、验证、数据记录操作，然后发送100封红包，然后再记录发送日志，发送日志要求100%的准确，如果日志不准确，那么整个父事务逻辑需要回滚。 怎么处理整个业务需求呢？就是通过这个PROPAGATION.REQUIRES_NEW 级别的事务传播控制就可以完成。发送红包的子事务不会直接影响到父事务的提交和回滚。 @Transactional(propagation=PROPAGATION.NOT_SUPPORTED) ：这个也可以从字面得知，not supported(不支持)，当前级别的特点是,如果上下文中存在事务， 则挂起事务，执行当前逻辑，结束后恢复上下文的事务。 这个级别有什么好处？可以帮助你将事务极可能的缩小。我们知道一个事务越大，它存在的风险也就越多。所以在处理事务的过程中，要保证尽可能的缩小范围。比如一段代码，是每次逻辑操作都必须调用的，比如循环1000次的某个非核心业务逻辑操作。这样的代码如果包在事务中，势必造成事务太大，导致出现一些难以考虑周全的异常情况。所以这个事务这个级别的传播级别就派上用场了，用当前级别的事务模板抱起来就可以了。 @Transactional(propagation=PROPAGATION.NEVER)：该事务更严格，上面一个事务传播级别只是不支持而已，有事务就挂起，而PROPAGATION_NEVER传播级别要求上下文中不能存在事务，一旦有事务，就抛出runtime异常，强制停止执行！ @Transactional(propagation=PROPAGATION.NESTED)：字面也可知道，nested，嵌套级别事务。该传播级别特征是，如果上下文中存在事务，则嵌套事务执行，如果不存在事务，则新建事务。 那么什么是嵌套事务呢？ @Autowired和@Resource的区别 所在的jar包不一样，autowried在spring的包，Resource在jdk1.6后的原生包 autowried是按照type去匹配对象的，resource是按照name去匹配对象的 autowried默认不允许空 @bean@controller@restcontroller spring的类加载过程 加载-所有的bean已经被扫描加载到applicationcontext中 解析-使用getBean或者注解通过beanname或者beantype解析要加载的bean 合并继承-合并父类子类的bean定义信息 实例化-bean的实例化方法执行，创建bean实例 初始化-初始化方法调用，属性填充 获得最终的实例bean spring中bean生命周期 bean创建 bean注入 注入前的afterpropertieset方法 调整init-method方法 bean使用 bean销毁 destroy方法 destroy-method方法 Spring常用的接口和类 ApplicationContextAware\n获取ApplicationContext，获取容器中的上下文对象，包括实体bean之类。\nApplicationListener接口\n需要监听自定义事件时，可以新建一个实现ApplicationListener接口的类，并将该类配置到Spring容器中\n/** * 自定义事件监听器 */ public class CustomEventListener implements ApplicationListener { public void onApplicationEvent(ApplicationEvent event) { if(event instanceof AnimalEvent){ AnimalEvent animalEvent = (AnimalEvent)event; System.out.println(\u0026#34;触发自定义事件：Animal name is \u0026#34; + animalEvent.getName()); } } } initializingBean接口\n当需要在bean的全部属性设置成功后做些特殊的处理，可以让该bean实现InitializingBean接口。 效果等同于bean的init-method属性的使用或者@PostContsuct注解的使用。 三种方式的执行顺序：先注解，然后执行InitializingBean接口中定义的方法，最后执行init-method属性指定的方法\nDisposableBean接口 当需要在bean销毁之前做些特殊的处理，可以让该bean实现DisposableBean接口。 效果等同于bean的destroy-method属性的使用或者@PreDestory注解的使用。 三种方式的执行顺序：先注解，然后执行DisposableBean接口中定义的方法，最后执行destroy-method属性指定的方法。\nspringboot的启动过程 SpringBootApplication注解是三个注解的集成\n@Configuration\n@EnableAutoConfiguration\n@ComponentScan\n主要做了容器配置加载，spring上下文初始化，监听初始化，spring工厂初始化，业务bean初始化等\n线程 线程新建 继承Thread类、实现Runnable接口、实现Callable接口通过FutureTask包装器来创建Thread线程、使用ExecutorService、Callable、Future实现有返回结果的多线程，其中前两种方式线程执行完后都没有返回值，后两种是带返回值的。\n继承Thread类，调用start()方法（Thread常用的方法-start()\\run()\\yield()\\join()\\sleep()） 实现Runnable接口，重写run()方法，调用start()方法 此处注意，上述两种无论哪种启动线程时都是调用start()方法\nstart(）方法，直接调用run()方法可以达到多线程的目的通常，系统通过调用线程类的start()方法来启动一个线程，此时该线程处于就绪状态，而非运行状态，这也就意味着这个线程可以被JVM来调度执行。在调度过程中，JVM会通过调用线程类的run()方法来完成试机的操作，当run()方法结束之后，此线程就会终止\nrun()和start()的区别可以用一句话概括：单独调用run()方法，是同步执行；通过start()调用run()，是异步执行。\n如果直接调用run方法，那多线程并发异步执行的意义就不大了； 除非有特殊的业务场景需求\n实现Callable接口，重新call方法。\n使用时，实例化callable实现类—\u0026gt; 使用FutureTask包装callable实现类—\u0026gt;使用FutureTask对象新建线程 -\u0026gt;调用start方法\npublic class ThreadDemoByCallable implements Callable\u0026lt;Integer\u0026gt; { /** * Computes a result, or throws an exception if unable to do so. * * @return computed result * @throws Exception if unable to compute a result */ @Override public Integer call() throws Exception { System.out.println(\u0026#34;子线程在进行计算\u0026#34;); System.out.println(\u0026#34;子线程:\u0026#34; + Thread.currentThread().getName()); Thread.sleep(4000); int sum = 0; for (int i = 0; i \u0026lt; 100; i++) { sum += i; } System.out.println(\u0026#34;子线程运行结果,sum = \u0026#34; + sum); return sum; } } public class ThreadByCallableMain { public static void main(String[] args) { // 方法3 // 1 创建ThreadDemo3对象 Callable\u0026lt;Integer\u0026gt; myCallable = new ThreadDemoByCallable(); /*Callable\u0026lt;String\u0026gt; myCallable = () -\u0026gt; { System.out.println(\u0026#34;自定义lambda:\u0026#34; + Thread.currentThread().getName()); return \u0026#34;自定义lambda\u0026#34;; };*/ // 2 使用FutureTask来包装MyCallable对象 FutureTask\u0026lt;Integer\u0026gt; ft = new FutureTask\u0026lt;Integer\u0026gt;(myCallable); //FutureTask\u0026lt;String\u0026gt; ft = new FutureTask\u0026lt;String\u0026gt;(myCallable); // 3 FutureTask对象作为Thread对象的target创建新的线程 Thread thread1 = new Thread(ft); long startTime = System.currentTimeMillis(); ThreadDemoByThread testT = new ThreadDemoByThread(\u0026#34;测试线程\u0026#34;); testT.start(); // 4 线程进入到就绪状态 thread1.start(); try { Thread.sleep(5000); } catch (InterruptedException e1) { e1.printStackTrace(); } System.out.println(Thread.currentThread().getName() + \u0026#34;主线程在执行任务\u0026#34;); } } 使用线程池例如用Executor框架, 创建线程池，从线程池中获取线程\npublic class ThreadDemoByExecutors { public static void main(String[] args) throws InterruptedException { ExecutorService executor = Executors.newFixedThreadPool(3); ThreadDemoByRunnable task = new ThreadDemoByRunnable(\u0026#34;测试\u0026#34;); int count = 3; while (count \u0026gt; 0) { executor.execute(task); count--; } Thread.sleep(100); task.running = false; ThreadDaemon timer = new ThreadDaemon(); timer.setDaemon(true); executor.execute(timer); executor.shutdownNow(); } } 线程状态 线程通信 wait()、notify()、notifyAll()、join()\n线程关闭 volatile修饰线程可见标志，通过标志判断 使用interrupt中断 直接stop 线程池 一个线程池包括以下四个基本组成部分：\n线程池管理器（ThreadPool）：用于创建并管理线程池，包括 创建线程池，销毁线程池，添加新任务； 工作线程（WorkThread）：线程池中线程，在没有任务时处于等待状态，可以循环的执行任务； 任务接口（Task）：每个任务必须实现的接口，以供工作线程调度任务的执行，它主要规定了任务的入口，任务执行完后的收尾工作，任务的执行状态等； 任务队列（TaskQueue）：用于存放没有处理的任务。提供一种缓冲机制。 线程池的几种不同的创建方法\nJDK 1.8使用Executor创建线程池实例。提供了多种方式的创建线程池的实例，newCachedThreadPool(\u0026hellip;)、newFixedThreadPool(\u0026hellip;)、newSingleThreadExecutor、newScheduledThreadPool等，其底层使用的是ThreadPoolExecutor，只不过Executor提供几种默认参数的ThreadPoolExecutor实现类。一般情况下，建议使用ThreadPoolExecutor，根据业务情况手动配置参数\n//提供了默认参数的 ExecutorService executor = Executors.newFixedThreadPool(3); ThreadDemoByRunnable task = new ThreadDemoByRunnable(\u0026#34;测试\u0026#34;); int count = 3; while (count \u0026gt; 0) { executor.execute(task); count--; } Thread.sleep(100); task.running = false; ThreadDaemon timer = new ThreadDaemon(); timer.setDaemon(true); executor.execute(timer); executor.shutdownNow(); //全部参数 ThreadPoolExecutor tpe = new ThreadPoolExecutor(5, 10, 100, MILLISECONDS, new ArrayBlockingQueue\u0026lt;Runnable\u0026gt;(5)); for (int i = 0; i \u0026lt; 13; i++) { pool.execute(new ThreadTaskByRunnable(\u0026#34;Task\u0026#34; + i)); } pool.shutdown(); newCachedThreadPool创建一个可缓存线程池，如果线程池长度超过处理需要，可灵活回收空闲线程，若无可回收，则新建线程。\nnewFixedThreadPool 创建一个定长线程池，可控制线程最大并发数，超出的线程会在队列中等待。\nnewScheduledThreadPool 创建一个定长线程池，支持定时及周期性任务执行。\nnewSingleThreadExecutor 创建一个单线程化的线程池，它只会用唯一的工作线程来执行任务，保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行\n自定义线程线程池配置参数详情\nTodo important 线程的一些常用参数配置\ncorePoolSize： 线程池维护线程的最少数量 maximumPoolSize：线程池维护线程的最大数量 keepAliveTime： 线程池维护线程所允许的空闲时间（解释：当线程池的数量超过corePoolSize时，多余的空闲线程的存活时间。） unit： 线程池维护线程所允许的空闲时间的单位 workQueue： 线程池所使用的缓冲队列 handler： 线程池对拒绝任务的处理策略\n好处\n降低资源消耗：减少频繁的开启创建线程的时间和性能消耗 提高性能：不用手动创建线程，从线程池取即可， 增强系统的稳定性：线程是系统稀缺资源，统一的线程池管理有利于提供系统稳定性，方便统一管理和维护调优 缺点\n线程池初始化时时间消耗较大 线程池的使用较为复杂，需要谨慎调优，使用不当可能会触发系统异常 线程池中线程优先级无法自定义调整 ThreadLocal\n用途：线程变量副本，每个线程用于自己的变量副本，相互不影响 内存泄漏问题，线程强引用，不会被GC。使用完线程共享变量后，显示调用ThreadLocalMap.remove方法清除线程共享变量，让JVM自动GC 如何实现的多线程并发-具体步骤\nList集合里面存储的是要执行的任务runable的实现 concurrentHashMap存放执行结果 循环List任务集合，使用CompletableFuture的runAsync方法，从jdk的默认线程池里面取线程执行任务，结果放入concurrentHashMap 多线程顺序执行 todo - fixme\n在子线程中通过join()方法指定顺序\n在主线程中通过join()方法指定顺序\n通过倒数计时器CountDownLatch实现\n通过创建单一化线程池newSingleThreadExecutor()实现\n线程安全 锁 公平/非公平\n* 公平/非公平锁FairSync * 公平锁：多个线程按照申请锁的顺序来获取锁 * 非公平锁：多个线程获取锁的顺序并不是按照申请锁的顺序，有可能后申请的线程比先申请的线程 * 优先获取锁，可能会出现优先级反转，或者是饥饿现象（可能存在线程一直获取不到锁） 可重入锁（递归锁）\n* 可重入锁(递归锁) ReentrantLock * 线程可以进入任何一个已经获取锁的，正在同步的代码块 自旋锁\n* 通过while循环，不断重试，实际没加锁（即代码无lock类） * 基于CAS compareAndSet AtomicLong中有个内部变量value保存着实际的long值，所有的操作都是针对该变量进行。也就是说，高并发环境下，value变量其实是一个热点，也就是N个线程竞争一个热点。\nLongAdder的基本思路就是分散热点，将value值分散到一个数组中，不同线程会命中到数组的不同槽中，各个线程只对自己槽中的那个值进行CAS操作。\n这样热点就被分散了，冲突的概率就小很多。如果要获取真正的long值，只要将各个槽中的变量值累加返回。\nAtomicLong是多个线程针对单个热点值value进行原子操作。而LongAdder是每个线程拥有自己的槽，各个线程一般只对自己槽中的那个值进行CAS操作。\n读写锁\n* 读写锁 ReentrantReadWriteLock * \u0026lt;p\u0026gt; * 多个线程同时读一个资源类，没有任何问题，为了满足并发量，读取共享资源可以同时进行 * 但是 * 如果有一个线程想写共享资源类，就不应该再有其他线程可以对该资源类进行读或者写 * 即： * 读-读 共存 * 读-写 不共存 * 写—写 不共存 * 写操作：原子独占，不可中断 * \u0026lt;p\u0026gt; * A\\B原则 * before使用该技术前 * after使用技术后 乐观锁悲观锁\n参考\n乐观锁适合读多写少的场景。悲观锁适合读少写多的场景\n悲观锁：\n悲观锁具有强烈的独占和排他性，每次读取数据都假设最坏的情况，默认认为其他线程会更改数据，因此需要进行加锁操作。当其他线程进行访问时，需要堵塞挂起。\n悲观锁的实现有传统关系型数据库中的行锁、表锁、读锁、写锁，java里面的synchronized关键字的实现\n悲观锁又分为：共享锁，排他锁\n共享锁：多个事务线程可以同时共享一把锁，都能访问数据，但是只能读，又被称为读锁\n排他锁：不和其他事务线程共享锁，独占锁，仅仅拥有锁的线程可以进行读写操作\n乐观锁：\n乐观锁假设数据一般不会造成冲突，一般只有在用户进行更新提交时，才会校验数据是否有冲突，如果有冲突，返回错误信息给用户决定如何操作\n乐观锁本身不会刻意使用数据库的锁机制，依赖的是数据本身来保证数据的真确性，\n乐观锁的常见实现：\ncas自旋锁：自旋锁在每次进行数据更新是，会重新从主内存取最新的值和当前要修改的值做比对，一致时才进行修改，不一致时，重新自旋取值比较。\n自旋锁的ABA问题：使用版本号控制修改记录。每次数据修改version+1，再对比修改前版本写入，如果版本不相等，重新读取更新\nsychronize和Reentrantlock区别 AQS: abstractQueuedSynchronizer 抽象队列同步器\nsychronize：\n属于jvm层面的锁，底层是用的jvm的monitor 避免死锁，反编译后可以发现其每次都有两次的锁释放。 不需要手动释放锁 执行过程不可中断 非公平锁，可重入锁 Reentrantlock：\n属于java.util.concurrent包，属于api层面的锁 需要手动释放锁，可能会出现死锁 执行过程可中断 公平和非公平都支持， 支持锁绑定多个条件condition，实现线程的部分和精准唤醒 在Java中Lock接口比synchronized块的优势是什么？你需要实现一个高效的缓存，它允许多个用户读，但只允许一个用户写，以此来保持它的完整性，你会怎样去实现它？\n//使用读写锁ReentrantReadWriteLock public class Demo{ public static void main(String[] args){ Mycache mycache = new Mycache(); for(i=0;i\u0026lt;10;i++){ final int finalI = i; new Thread(()-\u0026gt;{ mycache.put(i+\u0026#34;\u0026#34;,i); },\u0026#34;thread-test\u0026#34;).start(); } } } calss MyCache{ private volatile Map\u0026lt;String,Obejct\u0026gt; cacheMap = new HashTable\u0026lt;\u0026gt;(); private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(); public void put(String k,Object v){ rwLock.writeLock().lock(); try{ cacheMap.put(k,v) }catch(Exception e){ e.printstackTrace() }finally{ rwLock.writeLock().lock(); } } public Object void get(String k){ rwLock.readLock().lock(); try{ cacheMap.get(k); }catch(Exception){ e.printStackTrace(); }finally{ rwLock.readLock.unlock(); } } } 阻塞同步、非阻塞同步 阻塞\\非阻塞：程序在等待调用结果时的状态\n阻塞：在未返回调用结果前，当前线程会被挂起，只有调用返回数据时，线程才会继续执行\n非阻塞：线程在接口调用时，不能立即得到结果，不会阻止线程。会通过其他方法，类似轮训，异步通知等操作获取调用结果\n同步\\异步：消息通信机制\n同步：在发出一个调用时，在没有得到结果之前，该调用就不返回。但是一旦调用返回，就得到返回值了\n异步：发出一个调用时，立即返回，只是返回是否调用成功，调用者不会立刻得到调用结果。被调用者(类似接口)通过状态、通知来通知调用者，或通过回调函数处理这个调用结果。\nJAVA性能优化 Arthas 中间件 ORM Object Relational Mapping。对象关联关系映射框架\n常见的ORM框架有：Mybatis，hibernate。\nJPA\nJava Persistence API（Java 持久层 API）：用于对象持久化的 API\nJPA 包括三个方面的技术：\nORM 映射元数据，支持 XML 和 JDK 注解两种元数据的形式\nJPA 的 API\n查询语言：JPQL\n作用：使得应用程序以统一的方式访问持久层，是一种规范。\n常见的JPA的规范实现：Hibernate\n区别\n一般来说，mybatis的学习成本低，上手较快 mybatis需要手动写sql语句，而hibernate基本不需要 mybatis属于对jdbc的轻量级封装，而hibernate属于重量级封装，功能较全面 JDBC JavaDataBase Connectivity，用Java语言来操作数据库。原来我们操作数据库是在控制台使用SQL语句来操作数据库，JDBC是用Java语言向数据库发送SQL语句。\nSUN提供一套访问数据库的规范（就是一组接口），并提供连接数据库的协议标准，然后各个数据库厂商会遵循SUN的规范提供一套访问自己公司的数据库服务器的API出现。SUN提供的规范命名为JDBC，而各个厂商提供的，遵循了JDBC规范的，可以访问自己数据库的API被称之为驱动！\nJDBC是接口，而JDBC驱动才是接口的实现，没有驱动无法完成数据库连接！每个数据库厂商都有自己的驱动，用来连接自己公司的数据库。\nJDBC中常用的类：\nDriverManager – 类，用来获取Connection\n管理数据库驱动程序的列表。内容是否符合从Java应用程序使用的通信子协议正确的数据\nConnection – 接口\n与数据库通信的所有方法。连接对象表示通信上下文，即，与数据库中的所有的通信是通过唯一的连接对象\nStatement – 接口\n可以使用这个接口创建的对象的SQL语句提交到数据库。一些派生的接口接受除执行存储过程的参数\nResultSet – 接口\n这些对象保存从数据库后，执行使用Statement对象的SQL查询中检索数据。它作为一个迭代器，让你可以通过移动它的数据\nzookeeper 节点类型 持久节点(PERSISTENT) 持久节点，创建后一直存在，直到主动删除此节点。\n持久顺序节点(PERSISTENT_SEQUENTIAL) 持久顺序节点，创建后一直存在，直到主动删除此节点。在ZK中，每个父节点会为它的第一级子节点维护一份时序，记录每个子节点创建的先后顺序。\n临时节点(EPHEMERAL) 临时节点在客户端会话失效后节点自动清除。临时节点下面不能创建子节点。\n顺序临时节点(EPHEMERAL_SEQUENTIAL) 临时节点在客户端会话失效后节点自动清除。临时节点下面不能创建子节点。父节点getChildren会获得顺序的节点列表。\nrabbitmq 默认情况下，RbbitMQ 的消息默认存放在内存上面，如果不特别声明设置，消息不会持久化保存到硬盘上面的，如果节点重启或者意外crash掉，消息就会丢失。RabbitMQ分发完消息后，也会从内存中把消息删除掉。\n消息丢失问题： 消息丢失的原因\n生产者生成消息，发送过程丢失\n网络原因，发送过程丢包导致服务端接受不到信息 代码层面，代码逻辑处理不当，导致信息丢失 如何保证不丢失\nAMQP协议提供的了事务机制，由于同步操作，但是会大大降低性能（不推荐）\n发送方确认机制（publisher confirm）\n串行confirm模式：producer每发送一条消息后，调用waitForConfirms()方法，等待broker端confirm，如果服务器端返回false或者在超时时间内未返回，客户端进行消息重传。\n批量confirm模式：producer每发送一批消息后，调用waitForConfirms()方法，等待broker端confirm。\n异步confirm模式：提供一个回调方法，broker confirm了一条或者多条消息后producer端会回调这个方法。\n消息队列存储消息丢失，或者可靠性不足\n消息未持久化直接宕机 多节点中，某一个节点宕机 如何保证不丢失\n多节点同步机制，保证消息持久化 消息有效标志，提供可靠性标志 消息队列到消费者丢失\n此时消息如果处理不当会有丢失风险，后面会讲到如何处理这个情况，消费端也有ack机制 如何保证不丢失\n接收方确认机制消费者取到消息后，从消息中取出唯一标识，先判断此消息有没有被消费过，若已消费过，则直接ACK(避免重复消费) ，正常处理成功后，将生产者Redis中的此消息删除，并ACK(告诉server端此消息已成功消费) 消费异常重试机制。遇到异常时，捕获异常，验证自己在消息中设定的重试次数是否超过阀值，若超过，则放入死信队列，若未超过，则向将消息中的重试次数加1，抛出自定义异常，进入重试机制。多次未成功消费，持久化入表，人工进行消息补偿措施 rabbitmq、 rocketmq、kakaf的区别\ntodo\nredis应用场景 redis数据结构 string、hash：hashTable、list：ziplist\\LinkedList、sets、sortedSets\n缓存穿透和缓存雪崩问题 **缓存穿透：**即黑客故意去请求缓存中不存在的数据，导致所有的请求都怼到数据库上，从而数据库连接异常。\n**缓存雪崩：**即缓存同一时间大面积的失效，这个时候又来了一波请求，结果请求都怼到数据库上，从而导致数据库连接异常。\n临时token、短信通知\nredis如何进行持久化存储 Redis提供了RDB和AOF两种不同的数据持久化方式\n2种模式都开启使用的AOF模式\nRDB(Redis DataBase)\n在不同的时间点，将redis存储的数据生成快照并存储到磁盘等介质上。默认保存的文件名为dump.rdb\n无论是由主进程生成还是子进程来生成，其过程如下：\n生成临时rdb文件，并写入数据。 完成数据写入，用临时文代替代正式rdb文件。 删除原来的db文件。 DB默认生成的文件名为dump.rdb，当然，我可以通过配置文件进行更加详细配置，比如在单机下启动多个redis服务器进程时，可以通过端口号配置不同的rdb名称，如下所示：\n# 是否压缩rdb文件 rdbcompression yes # rdb文件的名称 dbfilename redis-6379.rdb # rdb文件保存目录 dir ~/redis/ 两种方式触发RDB模式的持久化\n使用的save、bgsave命令手动触发。save命令执行时主进程会同步阻塞，bgsave命令执行时主线程会fork一个子进程来进行数据同步，主进程不会堵塞但是子进程依旧堵塞，子进程IO写入dump.rdb文件完成后会退出。\n# 同步数据到磁盘上 \u0026gt; save # 异步保存数据集到磁盘上 \u0026gt; bgsave 使用redis配置文件，指定RDB持久化触发的的条件，比如【多少秒内至少达到多少写操作】就开启RDB数据同步。\n# 900s内至少达到一条写命令 save 900 1 # 300s内至少达至10条写命令 save 300 10 # 60s内至少达到10000条写命令 save 60 10000 之后在启动服务器时加载配置文件。\n# 启动服务器加载配置文件 redis-server redis.conf 这种配置文件触发，和bgsave命令类似。使用子进程同步。弊端是如果设置陈触发时间太短，容易频繁的写入rdb文件，影响redis性能，设置的时间太长会导致数据丢失\nAOF(Append Only File)\nAOF持久化方式会记录客户端对服务器的每一次写操作命令，并将这些写操作以Redis协议追加保存到以后缀为aof文件末尾，在Redis服务器重启时，会加载并运行aof文件的命令，以达到恢复数据的目的。\n3种AOF的文件写入策略\nalways\n客户端的每一个写操作都保存到aof文件当，这种策略很安全，但是每个写请注都有IO操作，所以也很慢。\neverysec\nappendfsync的默认写入策略，每秒写入一次aof文件，因此，最多可能会丢失1s的数据。\nno\nRedis服务器不负责写入aof，而是交由操作系统来处理什么时候写入aof文件。更快，但也是最不安全的选择，不推荐使用。\nRDB和AOF的优缺点\nRDB由于同步时间问题，可能会出现数据丢失。AOF如果合理的设置同步策略基本不会出现丢失数据的问题\nRDB和AOF同时使用时，使用AOF文件，因为AOF的记录信息更多文件体积，相对的恢复速度较慢，但是\n数据完整性较高\nRedis保证主从一致性 两个思路\n半同步复制 ， 等从库复制成功才返回写成功\n设一个key记录着一次写的数据,然后设置一个同步时间，如果在这个时间内，有一个读请求,看看对应的key有没有相关数据,有的话,说明数据近期发生过写事件，这样key的数据就继续读主库，否则就读从库\nRedis如何保证缓存（Redis）和数据库（MySQL）一致性 主要有两个问题：\n执行顺序问题：先更新缓存还是先更新数据库 更新缓存：缓存内容变化时，是更新缓存（update）,还是直接淘汰缓存（delete） 选择：先淘汰缓存，再更新数据库，之后新增缓存\n原因：由于更新缓存时，如果更新操作消耗更大，可能在高并发情况下，导致数据不一致，推荐直接淘汰缓存\n先淘汰在更新数据库再新增缓存，在高并发的情况可能会导致缓存长时间不一致的问题：\n并发量较大的情况下，采用同步更新缓存的策略： A线程进行写操作，先成功淘汰缓存，但由于网络或其它原因，还未更新数据库或正在更新 B线程进行读操作，发现缓存中没有想要的数据，从数据库中读取数据，但此时A线程还未完成更新操作，所以读取到的是旧数据，并且B线程将旧数据放入缓存。注意此时是没有问题的，因为数据库中的数据还未完成更新，所以数据库与缓存此时存储的都是旧值，数据没有不一致 在B线程将旧数据读入缓存后，A线程终于将数据更新完成，此时是有问题的，数据库中是更新后的新数据，缓存中是更新前的旧数据，数据不一致。如果在缓存中没有对该值设置过期时间，旧数据将一直保存在缓存中，数据将一直不一致，直到之后再次对该值进行修改时才会在缓存中淘汰该值 此时可能会导致cache与数据库的数据一直或很长时间不一致 为了解决这个问题\n可以使用异步更新缓存： 线程进行写操作，先成功淘汰缓存，但由于网络或其它原因，还未更新数据库或正在更新 B线程进行读操作，发现缓存中没有想要的数据，从数据库中读取数据，但B线程只是从数据库中读取想要的数据，并不将这个数据放入缓存中，所以并不会导致缓存与数据库的不一致 A线程更新数据库后，通过订阅binlog来异步更新缓存 此时数据库与缓存的内容将一直都是一致的 延时双删\nA线程进行写操作，先成功淘汰缓存，但由于网络或其它原因，还未更新数据库或正在更新\nB线程进行读操作，从数据库中读入旧数据，共耗时N秒\n在B线程将旧数据读入缓存后，A线程将数据更新完成，此时数据不一致\nA线程将数据库更新完成后，休眠M秒(M比N稍大即可)，然后再次淘汰缓存，此时缓存中即使有旧数据也会被淘汰，此时可以保证数据的一致性\n其它线程进行读操作时，缓存中无数据，从数据库中读取的是更新后的新数据\n引入延时双删后，存在两个新问题：\nA线程需要在更新数据库后，还要休眠M秒再次淘汰缓存，等所有操作都执行完，这一个更新操作才真正完成，降低了更新操作的吞吐量 解决办法：用“异步淘汰”的策略，将休眠M秒以及二次淘汰放在另一个线程中，A线程在更新完数据库后，可以直接返回成功而不用等待。 如果第二次缓存淘汰失败，则不一致依旧会存在 解决办法：用“重试机制”，即当二次淘汰失败后，报错并继续重试，直到执行成功个人 Redis Sentinel机制与用法 Redis-Sentinel是Redis官方推荐的高可用性(HA)解决方案，当用Redis做Master-slave的高可用方案时，假如master宕机了，Redis本身(包括它的很多客户端)都没有实现自动进行主备切换，而Redis-sentinel本身也是一个独立运行的进程，它能监控多个master-slave集群，发现master宕机后能进行自懂切换。\n同时redis-sentinel本身也是需要集群的\nNginx 有状态的请求，nginx是怎么处理的\n保留状态属性直接转发\nsql索引以及数据库 数据库设计3大范式 第一范式(确保每列保持原子性)\n第一范式是最基本的范式。如果数据库表中的所有字段值都是不可分解的原子值，就说明该数据库表满足了第一范式。\n第一范式的合理遵循需要根据系统的实际需求来定。比如某些数据库系统中需要用到“地址”这个属性，本来直接将“地址”属性设计成一个数据库表的字段就行。但是如果系统经常会访问“地址”属性中的“城市”部分，那么就非要将“地址”这个属性重新拆分为省份、城市、详细地址等多个部分进行存储，这样在对地址中某一部分操作的时候将非常方便。这样设计才算满足了数据库的第一范式，如下表所示。\n上表所示的用户信息遵循了第一范式的要求，这样在对用户使用城市进行分类的时候就非常方便，也提高了数据库的性能。\n第二范式(确保表中的每列都和主键相关)\n第二范式在第一范式的基础之上更进一层。第二范式需要确保数据库表中的每一列都和主键相关，而不能只与主键的某一部分相关（主要针对联合主键而言）。也就是说在一个数据库表中，一个表中只能保存一种数据，不可以把多种数据保存在同一张数据库表中。\n比如要设计一个订单信息表，因为订单中可能会有多种商品，所以要将订单编号和商品编号作为数据库表的联合主键，如下表所示。\n订单信息表\n这样就产生一个问题：这个表中是以订单编号和商品编号作为联合主键。这样在该表中商品名称、单位、商品价格等信息不与该表的主键相关，而仅仅是与商品编号相关。所以在这里违反了第二范式的设计原则。\n而如果把这个订单信息表进行拆分，把商品信息分离到另一个表中，把订单项目表也分离到另一个表中，就非常完美了。如下所示。\n这样设计，在很大程度上减小了数据库的冗余。如果要获取订单的商品信息，使用商品编号到商品信息表中查询即可。\n第三范式(确保每列都和主键列直接相关,而不是间接相关)\n第三范式需要确保数据表中的每一列数据都和主键直接相关，而不能间接相关。\n比如在设计一个订单数据表的时候，可以将客户编号作为一个外键和订单表建立相应的关系。而不可以在订单表中添加关于客户其它信息（比如姓名、所属公司等）的字段。如下面这两个表所示的设计就是一个满足第三范式的数据库表。\n这样在查询订单信息的时候，就可以使用客户编号来引用客户信息表中的记录，也不必在订单信息表中多次输入客户信息的内容，减小了数据冗余。\nsql锁表 在使用SQL时，大都会遇到这样的问题，update一条记录时，需要通过Select来检索出其值或条件，然后在通过这个值来执行修改操作。\n但当以上操作放到多线程中并发处理时会出现问题：某线程select了一条记录但还没来得及update时，另一个线程仍然可能会进来select到同一条记录\n一般解决办法就是使用锁和事务的联合机制：\nmysql\u0026gt; select k from t where id=1 lock in share mode; #乐观锁（s锁，共享锁），可读\nmysql\u0026gt; select k from t where id=1 for update; #悲观锁，排他锁\n乐观锁（默认值）：读取数据时不锁，更新时检查是否数据已经被更新过，如果是则取消当前更新，一般在悲观锁的等待时间过长而不能接受时我们才会选择乐观锁。\n悲观锁：在读取数据时锁住那几行，其他对这几行的更新需要等到悲观锁结束时才能继续 。\n注:for update 仅适用于InnoDB，并且必须开启事务，在begin与commit之间才生效。\n索引类别 非聚集索引:\n该索引中索引的逻辑顺序与磁盘上行的物理存储顺序不同，一个表中可以拥有多个非聚集索引。\n唯一索引：值唯一，允许空值null\n普通索引：允许重复和空值、仅加速查询\n单列索引：一个索引只包括单个列\n聚集索引：\n数据行的物理顺序与列值的顺序相同\n主键索引：唯一且不能为空，不能null\n组合索引：在多个列字段上面创建索引，使用组合索引时，遵循最左前缀集合。即（field1，field2，field3）的组合索引，是安装从左到右匹配索引的\n索引值改变了，树的结构如何改变 sql优化 数据库查询、优化方法，sql常用函数，存储过程\n优化方法\n关注耗时的读写是否走了索引，避免进行全表扫描\n避免使用selet *，查询时指定返回的具体字段\n使用varchar/nvarchar 代替 char/nchar\n以varchar(10)和char(10)存储3个字符长度的数据举例。varchar数据占用空间为3，最大为10；char占用的为10，3个实际字符和7个空字符\n索引的底层实现\nsql常用函数、以oracle为例\nAvg()返回平均值，sum()返回总和 count()返回行数 MAX()、MIN() 返回列最大和最小值 Upper()、lower()大小写转化 存储过程是在大型数据库系统中，一组为了完成特定功能的SQL语句集，存储在数据库中，一次编译永久有效，可通过调用语句进行复用。\n把SQL语句进行封装，并且可以使用简单的语句进行调用，这样就可以不用重复写一样的SQL，提高工作效率。\n查看sql执行计划，查看是否全表扫描，是否走了索引\n查看dump文件，是否有线程堵塞和锁住\noracle数据库的awr文件\n热块效应 反向索引 count(*)慢的话，查询指定索引\n查询的时候可以指定查询使用哪个索引\n查询的时候可以指定查询使用哪个索引。 EXPLAIN SELECT COUNT(id) FROM temp_orders force index (PRIMARY)； sql能join的尽量避免多个select的使用，多个select会有网络消耗\n如何知道是否进行了全表扫描或者是走了索引\n查看sql的执行计划，explain plan\n手写sql语句 5000万条数据，在500ms毫秒之内\n减少函数运算 更改where后的条件先后排序 给表添加索引 两个引擎\nmysql\n对数据库ACID事务的支持。并且还提供了行级锁和外键的约束。它的设计的目标就是处理大数据容量的数据库系统。\n不支持全文搜索的。同时，启动也比较的慢，它是不会保存表的行数的。锁的力度小，写操作等不会锁全表\n启动也比较的慢，它是不会保存表的行数的\nMyIASM引擎\n不提供事务的支持，也不支持行级锁和外键\nMyIASM引擎是保存了表的行数，写操作锁全表。读效率高\nB+树数据库引擎的底层实现\nb树：多叉树、节点支持存储多个数据\n什么情况加索引无效\n如果条件中有or，即使其中有条件带索引也不会使用(这也是为什么尽量少用or的原因)\n注意：要想使用or，又想让索引生效，只能将or条件中的每个列都加上索引\n对索引进行了运算会导致查询不走索引，会使用全表扫描\n如果列类型是字符串，那一定要在条件中将数据使用引号引用起来,否则不使用索引\nlike查询是以%开头。like \u0026ldquo;AMS%\u0026ldquo;是支持的\n多列索引，不按照列索引顺序查询\n多个索引： INDEX name (last_name), INDEX_2 name (first_name) 多列索引： INDEX name (last_name,first_name) 生效： SELECT * FROM test WHERE last_name=\u0026#39;Kun\u0026#39; AND first_name=\u0026#39;Li\u0026#39;; sql会先过滤出last_name符合条件的记录，在其基础上在过滤first_name符合条件的记录。那如果我们分别在last_name和first_name上创建两个列索引，mysql的处理方式就不一样了，它会选择一个最严格的索引来进行检索，可以理解为检索能力最强的那个索引来检索，另外一个利用不上了，这样效果就不如多列索引了。 SELECT * FROM test WHERE last_name=\u0026#39;Widenius\u0026#39;; SELECT * FROM test WHERE last_name=\u0026#39;Widenius\u0026#39; AND first_name=\u0026#39;Michael\u0026#39;; SELECT * FROM test WHERE last_name=\u0026#39;Widenius\u0026#39; AND (first_name=\u0026#39;Michael\u0026#39; OR first_name=\u0026#39;Monty\u0026#39;); SELECT * FROM test WHERE last_name=\u0026#39;Widenius\u0026#39; AND first_name \u0026gt;=\u0026#39;M\u0026#39; AND first_name \u0026lt; \u0026#39;N\u0026#39;; 不生效： SELECT * FROM test WHERE first_name=\u0026#39;Michael\u0026#39;; SELECT * FROM test WHERE last_name=\u0026#39;Widenius\u0026#39; OR first_name=\u0026#39;Michael\u0026#39;; 隔离级别 隔离性是指，多个用户的并发事务访问同一个数据库时，一个用户的事务不应该被其他用户的事务干扰，多个并发事务之间要相互隔离。\n数据库的A-原子Atomicity，C-一致Consistency，I-隔离Isolation，D-持久Durability\n原子性：指处于同一个事务中的多条语句是不可分割的。即它对数据库的修改要么全部执行，要么全部不执行\n一致性：事务必须使数据库从一个一致性状态变换到另外一个一致性状态。比如转账，转账前两个账户余额之和为2k，转账之后也应该是2K。\n隔离性：指多线程环境下，一个线程中的事务不能被其他线程中的事务打扰 持久性：事务一旦提交，就应该被永久保存起来。\n持久性：事务一旦提交，就应该被永久保存起来。\n不考虑隔离会产生的问题\n脏读\n脏读是指一个事务在处理数据的过程中，读取到另一个未提交事务的数据\n不可重复读\n不可重复读是指对于数据库中的某个数据，一个事务范围内的多次查询却返回了不同的结果，这是由于在查询过程中，数据被另外一个事务修改并提交了\n幻读\n一个事务按相同的查询条件重新读取以前检索过的数据，却发现其他事务插入了满足其查询条件的新数据，这种现象就称为幻读。\n不可重复读、幻读、脏读的区别异同\n脏读读取到的是一个未提交的数据，而不可重复读读取到的是前一个事务提交的数据。（隔离级别：read uncommited）\n幻读和不可重复读都是读取了另一条已经提交的事务，脏读读取的是另一个事务未提交的事务\n不可重复读查询的都是同一个数据项，而幻读针对的是一批数据整体（比如数据的个数）。\n不可重复度和幻读的区别：\n但如果从控制的角度来看, 两者的区别就比较大 对于前者, 只需要锁住满足条件的记录 对于后者, 要锁住满足条件及其相近的记录\n避免不可重复读需要锁行就行 避免幻读则需要锁表 不可重复读重点在于update和delete，而幻读的重点在于insert。\n如果使用锁机制来实现这两种隔离级别，在可重复读中，该sql第一次读取到数据后，就将这些数据加锁，其它事务无法修改这些数据，就可以实现可重复 读了。但这种方法却无法锁住insert的数据，所以当事务A先前读取了数据，或者修改了全部数据，事务B还是可以insert数据提交，这时事务A就会 发现莫名其妙多了一条之前没有的数据，这就是幻读，不能通过行锁来避免。需要Serializable隔离级别 ，读用读锁，写用写锁，读锁和写锁互斥，这么做可以有效的避免幻读、不可重复读、脏读等问题，但会极大的降低数据库的并发能力。\n所以说不可重复读和幻读最大的区别，就在于如何通过锁机制来解决他们产生的问题。\n四种隔离级别解决了上述问题\n1.读未提交（Read uncommitted）：事务可以读取到别的事务未提交的事务\n这种事务隔离级别下，select语句不加锁。\n此时，可能读取到不一致的数据，即“读脏 ”。这是并发最高，一致性最差的隔离级别。\n2.读已提交（Read committed）：\n可避免 脏读 的发生。\n在互联网大数据量，高并发量的场景下，几乎 不会使用 上述两种隔离级别。\n3.可重复读（Repeatable read）：\nMySql默认隔离级别。\n可避免 脏读 、不可重复读 的发生。\n4.串行化（Serializable ）：\n可避免 脏读、不可重复读、幻读 的发生。\n如何实现可重复度读 可重复读是指：一个事务执行过程中看到的数据，总是跟这个事务在启动时看到的数据是一致的。\n采用InnoDB引擎的数据库，InnoDB 里面每个事务都有一个唯一的事务 ID，叫作 transaction id。它在事务开始的时候向 InnoDB 的事务系统申请的，是按申请顺序严格递增的。\n每条记录在更新的时候都会同时记录一条 undo log，这条 log 就会记录上当前事务的 transaction id，记为 row trx_id。记录上的最新值，通过回滚操作，都可以得到前一个状态的值。\n如下图所示，一行记录被多个事务更新之后，最新值为 k=22。假设事务A在 trx_id=15 这个事务提交后启动，事务A 要读取该行时，就通过 undo log，计算出该事务启动瞬间该行的值为 k=10。\n在可重复读隔离级别下，一个事务在启动时，InnoDB 会为事务构造一个数组，用来保存这个事务启动瞬间，当前正在”活跃“的所有事务ID。”活跃“指的是，启动了但还没提交。\n数组里面事务 ID 为最小值记为低水位，当前系统里面已经创建过的事务 ID 的最大值加 1 记为高水位。\n这个视图数组和高水位，就组成了当前事务的一致性视图（read-view）。\n这个视图数组把所有的 row trx_id 分成了几种不同的情况。\n如果 trx_id 小于低水位，表示这个版本在事务启动前已经提交，可见； 如果 trx_id 大于高水为，表示这个版本在事务启动后生成，不可见； 如果 trx_id 大于低水位，小于高水位，分为两种情况： 若 trx_id 在数组中，表示这个版本在事务启动时还未提交，不可见； 若 trx_id 不在数组中，表示这个版本在事务启动时已经提交，可见。 InnoDB 就是利用 undo log 和 trx_id 的配合，实现了事务启动瞬间”秒级创建快照“的能力\nDelete\\Drop\\Truncate delete（用于table和view）\n每次从表删一行，删除操作事务记录\n不减少表和索引占用的空间\ntruncate（只用于table）\n一次性删除表中所有数据，不记录日志且无法恢复。\n表和索引占用的空间会恢复\ndrop（用于table）\n删除整个表，包括表结构和数据\n表空间释放\n用itearte接受数据库查询结果 保证sql的幂等性 数据库使用唯一索引\n保证新增sql\n全局唯一ID\n适用于微服务场景。唯一ID提取成单独微服务，保证分布式的唯一ID\n多版本控制\nsql或者接口中采用状态字段或者版本字段控制\n使用过滤字段过滤sql的结果\nLinux常用使用命令 查看某个进程的状态 ps -ef|grep ams cat /proc/PID/status 了解哪些设计模式 手敲代码实现单例模式 单例：减少对象的反复实例化，单例只初始化时实例一次对象\n双锁单例\npublic class DclSingleton{ private volatile static DclSingleton dclSingleton; private DclSingleton(){ } public static DclSingleton instance(){ if(dclSingleton==null){ synchronized(DclSingleton.class){ if(dclSingleton==null){ dclSingleton=new DclSingleton; } } } return dclSingleton; } } 枚举单例\npublic enum EnumSingleton{ INSTANCE; private final CommonBean commonInstance; EnumSingleton{ this.commonInstance = new CommonBean(); } public CommonBean getInstance(){ return this.commonInstance } } 工厂：根据不同需求场景，使用一个类型工厂创建多种对象。例如使用图形工厂类，根据使用场景使用图形工厂类创建不同图形实例，使用工厂创建出来，而不是通过对象自己的构造函数创建。\nShapeFactory shapeFactory = new ShapeFactory(); // 画圆 Shape circle = shapeFactory.getShape(\u0026#34;CIRCLE\u0026#34;); circle.draw(); // 画方形 Shape square = shapeFactory.getShape(\u0026#34;SQUARE\u0026#34;); square.draw(); // 画三角 Shape rectangle = shapeFactory.getShape(\u0026#34;RECTANGLE\u0026#34;); rectangle.draw(); 生产消费：典型微服务生产消费者\n策略模式：相同接口，多个实现类\n题目 随机生成 Salary {name, baseSalary, bonus }的记录，如“wxxx,10,1”，每行一条记录，总共1000万记录，写入文本文件（UFT-8编码）， 然后读取文件，name的前两个字符相同的，其年薪累加，比如wx，100万，3个人，最后做排序和分组，输出年薪总额最高的10组： wx, 200万，10人 lt, 180万，8人 \u0026hellip;. name 4位a-z随机， baseSalary [0,100]随机 bonus[0-5]随机 年薪总额 = baseSalary*13 + bonus 请努力将程序优化到5秒内执行完\n@Getter@Setter@ToString public class Salary { private String name; //员工姓名 private Integer baseSalary; //基础工资 private Integer bonus; //奖金 } /** * 生成1000w条随机数据 */ public class SalaryTest { public static void main(String[] args) throws IOException { File file = new File(\u0026#34;D:/upload/test.txt\u0026#34;); FileWriter out = new FileWriter(file, true); Integer count = 10000000; while (true){ StringBuilder name = new StringBuilder(4); String chars = \u0026#34;abcdefghijklmnopqrstuvwxyz\u0026#34;; for (int i = 0 ; i \u0026lt; 4; i++){ name.append(chars.charAt((int) (Math.random() * 26))) ; } Salary salary = new Salary(); salary.setName(name.toString()); salary.setBaseSalary((int) (Math.random() * 100 + 1)); salary.setBonus((int) (Math.random() * 5 + 1)); count --; out.write(JSON.toJSONString(salary)); out.write(\u0026#34;\\n\u0026#34;); if (count ==0){ out.close(); return; } } } } public class Test { public static void main(String[] args) throws IOException { long time = new Date().getTime(); /** * 其实也可以用随机流;将文件分块;多起几个线程执行; 这样的做的话得将文件分块;意思就是通过scan的api;先全文循环后标记;分成几份. 1000w的数据不大.这样反而效率更低 */ BufferedReader reader = new BufferedReader(new FileReader(\u0026#34;D:/upload/test.txt\u0026#34;)); String line = null; HashMap\u0026lt;String,Salary\u0026gt; map = new HashMap(); while((line = reader.readLine()) != null){ Salary salary = JSON.parseObject(String.valueOf(line), Salary.class); String key = salary.getName().substring(0, 2); Salary result = map.get(key); if (result != null) { result.setBaseSalary(result.getBaseSalary() + salary.getBaseSalary() * 13 + salary.getBonus()); result.setBonus(result.getBonus() +1); }else { result = new Salary(); result.setName(key); result.setBonus(1); result.setBaseSalary(salary.getBaseSalary() * 13 + salary.getBonus()); map.put(key,result); } } ArrayList\u0026lt;Salary\u0026gt; values = new ArrayList(); Collection\u0026lt;Salary\u0026gt; co = map.values(); values.addAll(co); /** java8之后提供流排序;效率更高 **/ List\u0026lt;Salary\u0026gt; list = map.values().stream().sorted(new Comparator\u0026lt;Salary\u0026gt;() { @Override public int compare(Salary o1, Salary o2) { return o2.getBaseSalary() - o1.getBaseSalary(); } }).collect(Collectors.toList()); /* Collections.sort( values, new Comparator\u0026lt;Salary\u0026gt;() { public int compare(Salary o1, Salary o2) { return o2.getBaseSalary() - o1.getBaseSalary(); } }); */ System.out.println((new Date().getTime() - time)); for (int i =0 ; i \u0026lt; 10 ; i++){ System.out.println(list.get(i)); } } } // 链接：https://www.jianshu.com/p/f092fb562c87 使用二分查找的方式来定位某一元素\n对于一个有序数组，我们通常采用二分查找的方式来定位某一元素，请编写二分查找的算法，在数组中查找指定元素。给定一个整数数组A及它的大小n，同时给定要查找的元素val，请返回它在数组中的位置(从0开始)，若不存在该元素，返回-1。若该元素出现多次，请返回第一次出现的位置。\npublic int getBinarySearch(int[] nums,int n,int val){ int high = n-1,low=0,mid=0,flag=-1; while(low\u0026lt;=high){ if(nums[mid]==val){ flag = mid } if(nums[mid]\u0026lt;val){ low=mid+1; } if(nums[mid]\u0026gt;=val){ high=mid-1; } } return flag; } 请用你熟悉的开发语言，完成如下题目: 输入:若干个集合,各集合中的元素不会重复 输出:求这些集合的笛卡尔积例如: 输入:N个集合(这里N=3) :(a,b)(x,y)(1,2,3) 输出: ((a,x,1), (a,x,2)…(b,y,3)) 在保证正确性的情况下尽可能优化效率，同时注意代码风格\n利用循环的方式实现我输入n 得到n对应的裴波拉契数字，裴波拉契举例：1 1 2 3 5 8 13 21 。。。。。。。\n用Java编写一个会导致死锁的程序\nstatic Object lock1 = new Object(); static Object lock2 = new Object(); synchronized(lock1.class){ synchronized(lock2.class){ } } synchronized(lock2.class){ synchronized(lock1.class){ } } 1.hash校验问题\n2.tomcat临时目录问题\n","permalink":"https://cyn-blog.pages.dev/posts/06.javainterview/10.interviewnote/interviewnote/","summary":"\u003ch2 id=\"待补充知识点\"\u003e待补充知识点\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e多线程顺序执行\u003c/li\u003e\n\u003cli\u003eredis的Sentinel\u003c/li\u003e\n\u003cli\u003eredis持久化\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e冷门：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003ehibernate一级缓存和二级缓存的区别是？\u003c/li\u003e\n\u003cli\u003eThreadLocal的内存泄漏问题\u003c/li\u003e\n\u003cli\u003eCAS自旋锁、基础锁的概念\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e中间件：\u003c/p\u003e","title":"面试笔记"},{"content":"ppt2video ppt文件转换为MP4工具类\n1 功能描述 将输入的ppt文件转化成视频，视频是每页ppt和ppt的备注文字转化成的语音合成\n将每一页的ppt切成图片，每一页ppt备注文字转化成语音 将所有的语音合成一份完整的语音，以语音长度为视频长度，与图片合成最终视频 视频中每段备注文字语音对应每页ppt，每段语音结束视频页面跳转到下一页ppt 2 参考输入输出 输入：待转化ppt文件路径 输出：转化后视频文件路径 // 例如： // 输入 - /home/hsfstore/hsStoredata/data/00/00/wKgh_V4EZzaEUj9wAAAAAAAAAAA79.pptx // 输出 - /home/hsfstore/hsStoredata/data/00/00/wKgh_V4EZzaEUj9wAAAAAAAAAAA79-pptToVideo.mp4 3 调用方式 # ssh java -jar PptToVideoTool.jar [参数] 4 参数说明 目前仅支持单个参数，参数类型为String，为ppt在服务器上的全路径 后续支持TTS运行目录 5 所需环境说明 TTS: 科大讯飞tts包 FFmpeg: version-3.4.2 6 运行配置 ConstantParam类中调整TTSFILEPATH字段为实际安装目录 科大讯飞的TTS和FFmpeg目前都支持在windows上安装，测试时可以用windows版本 本工程在Liunx服务器上验证通过，windows尚未验证 ","permalink":"https://cyn-blog.pages.dev/posts/05.opensource/12.ppttovideo/ppttovideo/","summary":"\u003ch1 id=\"ppt2video\"\u003eppt2video\u003c/h1\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/ching7/ppt2video.git\"\u003eppt文件转换为MP4工具类\u003c/a\u003e\u003c/p\u003e\n\u003ch2 id=\"1-功能描述\"\u003e1 功能描述\u003c/h2\u003e\n\u003cp\u003e将输入的ppt文件转化成视频，视频是每页ppt和ppt的备注文字转化成的语音合成\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e将每一页的ppt切成图片，每一页ppt备注文字转化成语音\u003c/li\u003e\n\u003cli\u003e将所有的语音合成一份完整的语音，以语音长度为视频长度，与图片合成最终视频\u003c/li\u003e\n\u003cli\u003e视频中每段备注文字语音对应每页ppt，每段语音结束视频页面跳转到下一页ppt\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"2-参考输入输出\"\u003e2 参考输入输出\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e输入：待转化ppt文件路径\u003c/li\u003e\n\u003cli\u003e输出：转化后视频文件路径\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// 例如：\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// 输入 - /home/hsfstore/hsStoredata/data/00/00/wKgh_V4EZzaEUj9wAAAAAAAAAAA79.pptx\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// 输出 - /home/hsfstore/hsStoredata/data/00/00/wKgh_V4EZzaEUj9wAAAAAAAAAAA79-pptToVideo.mp4\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"3-调用方式\"\u003e3 调用方式\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-cmake\" data-lang=\"cmake\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e# ssh\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003ejava\u003c/span\u003e \u003cspan class=\"err\"\u003e-jar\u003c/span\u003e \u003cspan class=\"err\"\u003ePptToVideoTool.jar\u003c/span\u003e \u003cspan class=\"err\"\u003e[参数]\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"4-参数说明\"\u003e4 参数说明\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e目前仅支持单个参数，参数类型为String，为ppt在服务器上的全路径\u003c/li\u003e\n\u003cli\u003e后续支持TTS运行目录\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"5-所需环境说明\"\u003e5 所需环境说明\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003eTTS\u003c/code\u003e: 科大讯飞\u003ccode\u003etts\u003c/code\u003e包\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eFFmpeg\u003c/code\u003e: \u003ccode\u003eversion-3.4.2\u003c/code\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"6-运行配置\"\u003e6 运行配置\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003eConstantParam\u003c/code\u003e类中调整\u003ccode\u003eTTSFILEPATH\u003c/code\u003e字段为实际安装目录\u003c/li\u003e\n\u003cli\u003e科大讯飞的\u003ccode\u003eTTS\u003c/code\u003e和\u003ccode\u003eFFmpeg\u003c/code\u003e目前都支持在windows上安装，测试时可以用windows版本\u003c/li\u003e\n\u003cli\u003e本工程在\u003ccode\u003eLiunx\u003c/code\u003e服务器上验证通过，\u003ccode\u003ewindows\u003c/code\u003e尚未验证\u003c/li\u003e\n\u003c/ul\u003e","title":"PPT转视频"},{"content":"smartisan-mall-simple simple商城-demo体验\nsimple商城-github源码\n介绍 锤子科技官网简单demo实现。前台原模板来自 vue-mall 原前台工程。\n后台原模板使用的是mongdb实现。本项目使用springboot进行了重写，原有功能基本全部实现。\n层级目录 smartisan-mall-simple ├── DevCodes\t//springboot后台实现 | └── smartisan-mall-simple-dev ├── Sql\t//数据库sql文件 | └── mall_template.sql └── WebCodes //vue前台 └── smartisan-mall-simple-web 技术架构 前台\nvue，详情见 [vue-mall]。原前台工程\n后台\nspringboot、mybatis-plus、renren逆向\n使用说明 环境准备\nnode.js jdk8及以上 mysql 启动\nclone或者download仓库到本地目录\n启动前台\n准备node.js环境后台\n默认前台端口为9999，后台代理端口为3333\n进入WebCodes/smartisan-mall-simple-web npm install -U / npm run dev 启动后台\n准备jdk环境，mysql环境。修改 application.yml 对应的数据库连接。\n交流沟通 有问题欢迎沟通交流，issue\n","permalink":"https://cyn-blog.pages.dev/posts/05.opensource/11.simplemall/simplemalldemo/","summary":"\u003ch2 id=\"smartisan-mall-simple\"\u003esmartisan-mall-simple\u003c/h2\u003e\n\u003cp\u003e\u003ca href=\"http://106.54.70.153/\"\u003esimple商城-demo体验\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/ching7/smartisan-mall-simple\"\u003esimple商城-github源码\u003c/a\u003e\u003c/p\u003e\n\u003ch3 id=\"介绍\"\u003e介绍\u003c/h3\u003e\n\u003cp\u003e锤子科技官网简单demo实现。前台原模板来自 \u003ca href=\"https://github.com/yucccc/vue-mall\"\u003evue-mall\u003c/a\u003e 原前台工程。\u003c/p\u003e\n\u003cp\u003e后台原模板使用的是mongdb实现。本项目使用springboot进行了重写，原有功能基本全部实现。\u003c/p\u003e","title":"简单商城"},{"content":"使用说明 简介 该组件可以实现浏览器自定义的图片列表采集\n支持处理黑白、彩色、灰度图片 支持图片自定义文字水印、图片水印 支持自定义文件分片上传 支持切换视频源和分辨率（需要插件盒子支持） 注意： 最新版本chrome浏览器会限制网页调用本地设备，需要配置允许访问\nchrome浏览器输入 chrome://flags/#unsafely-treat-insecure-origin-as-secure，输入需要调用设备的ip+post，如：127.0.0.1:8080。并且启用配置 chrome浏览器：设置\u0026raquo;隐私设置和安全性\u0026raquo;网站设置\u0026raquo;权限，确保有摄像头和麦克风权限 组件文件 scanBox ├── archItem.vue // 右侧采集项 ├── archSubList.vue // 右侧采集列表 ├── captureView.vue // 采集器视频流处理 ├── file.js // 图片处理js-提供了常用的js图片处理、文字以及图片水印功能等 ├── fileUpload.js // 文件上传处理js-提供图片分片上传和普通上传逻辑 ├── imageThumbList.vue // 右侧下方图片列表 ├── imageThumbNail.vue // 右侧下方图片缩略图 ├── localUpload.vue //本地上传控件 └── scanBox.vue //采集器主界面 组件依赖说明 \u0026ldquo;element-ui\u0026rdquo;: \u0026ldquo;^2.14.0\u0026rdquo; -组件基础框架 \u0026ldquo;spark-md5\u0026rdquo;: \u0026ldquo;^3.0.1\u0026rdquo; -文件md5计算 \u0026ldquo;vue-cropper\u0026rdquo;: \u0026ldquo;^0.5.5\u0026rdquo; -页面文件截图操作 \u0026ldquo;v-viewer\u0026rdquo;: \u0026ldquo;^1.5.1\u0026rdquo; -图片预览 \u0026ldquo;vuedraggable\u0026rdquo;: \u0026ldquo;^2.24.3\u0026rdquo; -图片列表拖拽调整 使用说明 // 1 导入文件 import scanBox from \u0026#39;./scanBox/scanBox\u0026#39; // 2 组件注册 components: { scanBox }, // 3 引入标签 \u0026lt;scan-box ref=\u0026#34;scanBox\u0026#34; :scanList=\u0026#39;scanList\u0026#39; :deviceFrameSizeList=\u0026#34;deviceFrameSizeList\u0026#34; @endScanHandler=\u0026#34;endScanHandler\u0026#34;/\u0026gt; // 4 方法调用，显示采集弹框 openScanBox () { // 初始化分辨率(三方插件支持) this.deviceFrameSizeList = deviceFrameSizeList || [] this.$refs.scanBox.showWin(\u0026#39;scan\u0026#39;, 0, this.scanList) } // 5 监听采集完成返回文件列表 endScanHandler (scanImageList) { // 采集完成返回完整的采集信息，自己做上传处理 console.log(\u0026#39;endScanHandler===\u0026#39;, scanImageList) } // 6 自定义文件上传以及文件处理 import { beforeUpload } from \u0026#39;./scanBox/fileUpload\u0026#39; uploadFile (scanImageList) { // todo 采集完成返回完整的采集信息，自己做上传处理 // 上传 // 移除未改变项 let uploadScanImageList = scanImageList.filter(item =\u0026gt; item.isChange !== false) // 上传接口需要的额外参数 let expandInfo = { acpt_id: \u0026#39;99999999\u0026#39;, cust_id: \u0026#39;99999999\u0026#39;, operator_no: 10571, op_branch_no: 1, branch_no: 1 } // 上传前是否添加水印 let isWater = true // 水印信息 let waterMsg = \u0026#39;waterMsg\u0026#39; // 上传url const uploadUrl = \u0026#39;https://test.com/fileUpload\u0026#39; // 图片水印url let waterLogoUrl = \u0026#39;https://gitee.com/ching7777/gitee_graph_bed/raw/master/img/80.jpg\u0026#39; beforeUpload(uploadScanImageList, acptInfo, isWater, waterMsg, waterLogoUrl, uploadUrl).then(res =\u0026gt; { console.log(\u0026#39;单个采集项上传完成\u0026#39;) // todo 每个采集项上传进度 }).catch(err =\u0026gt; { this.$hMessage.error(err) }) console.log(\u0026#39;全部采集项上传完成\u0026#39;) }, 参数说明 \u0026lt;scan-box ref=\u0026#34;scanBox\u0026#34; :scanList=\u0026#39;scanList\u0026#39; :deviceFrameSizeList=\u0026#34;deviceFrameSizeList\u0026#34; @endScanHandler=\u0026#34;endScanHandler\u0026#34;/\u0026gt; 参数\nscanList：需要采集的图片列表\ndeviceFrameSizeList：当前设备支持的分辨率\n方法\nendScanHandler：采集结束后返回的文件列表\n参数以及方法详细信息\n# 需要采集的图片列表 scanList = [{ \u0026#39;archFileNo\u0026#39;: \u0026#39;-1\u0026#39;, //采集文件编号-建议纯数字，每一项不重复 \u0026#39;filePath\u0026#39;: \u0026#39;\u0026#39;,//当前采集项首页图片 \u0026#39;haveScan\u0026#39;: \u0026#39;0\u0026#39;,//当前采集项是否必须采集 \u0026#39;imageName\u0026#39;: \u0026#39;批量扫描\u0026#39;,//当前采集项名称 \u0026#39;imageSet\u0026#39;: \u0026#39;3\u0026#39;, //当前采集文件类型，1:黑白 2:灰度 3:彩色 \u0026#39;importLocalFlag\u0026#39;: \u0026#39;0\u0026#39;, //当前采集是否可以本地导入 \u0026#39;index\u0026#39;: \u0026#39;0\u0026#39;, // 当前采集项索引 \u0026#39;tipInfo\u0026#39;:\u0026#39;test\u0026#39;,//当前档案采集提示信息 \u0026#39;isChange\u0026#39;: false,// 当前采集项是否发生变化 \u0026#39;pageInfo\u0026#39;: [{ \u0026#39;filePath\u0026#39;: \u0026#34;https://gitee.com/ching7777/gitee_graph_bed/raw/master/img/80.jpg\u0026#34;, \u0026#39;pageNo\u0026#39;: 1, }],// 当前采集项每一页信息 \u0026#39;pageNum\u0026#39;: 9999,//当前采集项可以采集的页数 },...] # 支持的设备分辨率（需要三方插件） deviceFrameSizeList = [ { \u0026#34;FrameSize\u0026#34;: [\u0026#34;640x480\u0026#34;, \u0026#34;800x600\u0026#34;, \u0026#34;1280x720\u0026#34;, \u0026#34;1280x960\u0026#34;, \u0026#34;1600x1200\u0026#34;], \u0026#34;Name\u0026#34;: \u0026#34;ZLPorCamera\u0026#34; }, { \u0026#34;FrameSize\u0026#34;: [\u0026#34;640x480\u0026#34;, \u0026#34;800x600\u0026#34;, \u0026#34;1280x720\u0026#34;, \u0026#34;1600x1200\u0026#34;, \u0026#34;1920x1080\u0026#34;,\u0026#34;2048x1536\u0026#34;,\u0026#34;2592x1944\u0026#34;,\u0026#34;3264x2448\u0026#34;], \u0026#34;Name\u0026#34;: \u0026#34;RXSX Video\u0026#34; } ] # 采集结束后返回的文件列表 scanList = [{ \u0026#39;archFileNo\u0026#39;: \u0026#39;-1\u0026#39;, //采集文件编号-建议纯数字，每一项不重复 \u0026#39;filePath\u0026#39;: \u0026#39;data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD\u0026#39;,//当前采集项首页图片 \u0026#39;haveScan\u0026#39;: \u0026#39;0\u0026#39;,//当前采集项是否必须采集 \u0026#39;imageName\u0026#39;: \u0026#39;头像\u0026#39;,//当前采集项名称 \u0026#39;imageSet\u0026#39;: \u0026#39;3\u0026#39;, //当前采集文件类型，1:黑白 2:灰度 3:彩色 \u0026#39;importLocalFlag\u0026#39;: \u0026#39;0\u0026#39;, //当前采集是否可以本地导入 \u0026#39;index\u0026#39;: \u0026#39;0\u0026#39;, // 当前采集项索引 \u0026#39;tipInfo\u0026#39;:\u0026#39;test\u0026#39;,//当前档案采集提示信息 \u0026#39;isChange\u0026#39;: false,// 当前采集项是否发生变化 \u0026#39;pageInfo\u0026#39;: [{ \u0026#39;filePath\u0026#39;: \u0026#34;data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD\u0026#34;, \u0026#39;pageNo\u0026#39;: 1, },{ filePath: \u0026#34;data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD\u0026#34; pageNo: 2 }],// 当前采集项每一页信息 \u0026#39;pageNum\u0026#39;: 9999,//当前采集项可以采集的页数 },...] ","permalink":"https://cyn-blog.pages.dev/posts/05.opensource/10.scanbox/scanboxreadme/","summary":"\u003ch1 id=\"使用说明\"\u003e使用说明\u003c/h1\u003e\n\u003ch3 id=\"简介\"\u003e简介\u003c/h3\u003e\n\u003cp\u003e该组件可以实现浏览器自定义的图片列表采集\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e支持处理黑白、彩色、灰度图片\u003c/li\u003e\n\u003cli\u003e支持图片自定义文字水印、图片水印\u003c/li\u003e\n\u003cli\u003e支持自定义文件分片上传\u003c/li\u003e\n\u003cli\u003e支持切换视频源和分辨率（需要插件盒子支持）\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"注意\"\u003e注意：\u003c/h3\u003e\n\u003cp\u003e最新版本chrome浏览器会限制网页调用本地设备，需要配置允许访问\u003c/p\u003e","title":"使用说明"},{"content":"图像采集器DEMO ","permalink":"https://cyn-blog.pages.dev/posts/05.opensource/10.scanbox/scanbox/","summary":"\u003ch1 id=\"图像采集器demo\"\u003e图像采集器DEMO\u003c/h1\u003e","title":"图像采集器DEMO"},{"content":"Spring基础知识点 BeanFactory ApplicationContext 的区别 接口 BeanFactory 和 ApplicationContext 都是用来从容器中获取 Spring beans 的，但是，他们二者有很大不同\n什么是 Spring Bean这是一个非常简单而又很复杂的问题，通常来说，Spring beans 就是被 Spring 容器所管理的 Java 对象，来看一个简单的例子\npublic class HelloWorld { private String message; public void setMessage(String message){ this.message = message; } public void getMessage(){ System.out.println(\u0026#34;My Message : \u0026#34; + message); } } 在基于 XML 的配置中， beans.xml 为 Spring 容器管理 bean 提供元数据\n什么是 Spring 容器 Spring 容器负责实例化，配置和装配 Spring beans，下面来看如何为 IoC 容器配置我们的 HelloWorld POJO\n\u0026lt;?xml version = \u0026#34;1.0\u0026#34; encoding = \u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;beans xmlns = \u0026#34;http://www.springframework.org/schema/beans\u0026#34; xmlns:xsi = \u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34; xsi:schemaLocation = \u0026#34;http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd\u0026#34;\u0026gt; \u0026lt;bean id = \u0026#34;helloWorld\u0026#34; class = \u0026#34;com.cyn.HelloWorld\u0026#34;\u0026gt; \u0026lt;property name = \u0026#34;message\u0026#34; value = \u0026#34;Hello World!\u0026#34;/\u0026gt; \u0026lt;/bean\u0026gt; \u0026lt;/beans\u0026gt; 现在，它已经被 Spring 容器管理了，接下来的问题是：我们怎样获取它？\nBeanFactory 和 ApplicationContext 的不同点 BeanFactory 接口 这是一个用来访问 Spring 容器的 root 接口，要访问 Spring 容器，我们将使用 Spring 依赖注入功能，使用 BeanFactory 接口和它的子接口特性：\nBean 的实例化/串联 通常情况，BeanFactory 的实现是使用懒加载的方式，这意味着 beans 只有在我们通过 getBean() 方法直接调用它们时才进行实例化实现 BeanFactory 最常用的 API 是 XMLBeanFactory这里是如何通过 BeanFactory 获取一个 bean 的例子：\npublic class HelloWorldApp{ public static void main(String[] args) { XmlBeanFactory factory = new XmlBeanFactory (new ClassPathResource(\u0026#34;beans.xml\u0026#34;)); HelloWorld obj = (HelloWorld) factory.getBean(\u0026#34;helloWorld\u0026#34;); obj.getMessage(); } } ApplicationContext 接口 ApplicationContext 是 Spring 应用程序中的中央接口，用于向应用程序提供配置信息它继承了 BeanFactory 接口，所以 ApplicationContext 包含 BeanFactory 的所有功能以及更多功能！它的主要功能是支持大型的业务应用的创建特性：\nBean instantiation/wiring Bean 的实例化/串联 自动的 BeanPostProcessor 注册 自动的 BeanFactoryPostProcessor 注册 方便的 MessageSource 访问（i18n） ApplicationEvent 的发布 与 BeanFactory 懒加载的方式不同，它是预加载，所以，每一个 bean 都在 ApplicationContext 启动之后实例化这里是 ApplicationContext 的使用例子：\npublic class HelloWorldApp{ public static void main(String[] args) { ApplicationContext context=new ClassPathXmlApplicationContext(\u0026#34;beans.xml\u0026#34;); HelloWorld obj = (HelloWorld) context.getBean(\u0026#34;helloWorld\u0026#34;); obj.getMessage(); } } 参考： https://mp.weixin.qq.com/s/YBQB086ADBjHUmwrFQrWew\n","permalink":"https://cyn-blog.pages.dev/posts/03.framework/10.spring/springbase/","summary":"\u003ch2 id=\"spring基础知识点\"\u003eSpring基础知识点\u003c/h2\u003e\n\u003ch3 id=\"beanfactory--applicationcontext-的区别\"\u003eBeanFactory  ApplicationContext 的区别\u003c/h3\u003e\n\u003cp\u003e接口 BeanFactory 和 ApplicationContext 都是用来从容器中获取 Spring beans 的，但是，他们二者有很大不同\u003c/p\u003e\n\u003cp\u003e什么是 Spring Bean这是一个非常简单而又很复杂的问题，通常来说，Spring beans 就是被 Spring 容器所管理的 Java 对象，来看一个简单的例子\u003c/p\u003e","title":"Spring基础知识"},{"content":"Java 设计模式 设计模式是什么 设计模式，简单来说就前人编程总结出来的套路\n为什么要学习设计模式 适应变化，提高代码复用率。改善系统的设计，增强系统的健壮性、可扩展性，为以后需求修改作铺垫.\n设计模式的六个基本原则 单一职责\n官方解释：应该有且仅有一个原因引起类的变更。简单点说，一个类，最好只负责一件事，只有一个引起它变化的原因。\n通俗来说就是一个类只负责一项职责；譬如一个学生类，那么它就应该只包含学生相关的属性和操作而不会包含其他身份的属性和操作；但是想要每个类都做到单一职责在实际当中，就会增加实体类的数量，但是这是必然的，因为很多时候不管做人还是做事都是不可以两全其美的\n在OOP里面，高内聚、低耦合是软件设计追求的目标，而单一职责原则可以看做是高内聚、低耦合的引申，将职责定义为引起变化的原因，以提高内聚性，以此来减少引起变化的原因。职责过多，可能引起变化的原因就越多，这将是导致职责依赖，相互之间就产生影响，从而极大的损伤其内聚性和耦合度。单一职责通常意味着单一的功能，因此不要为类实现过多的功能点，以保证实体只有一个引起它变化的原因。\n里氏替换\n官方解释：所有引用基类（父类）的地方必须能透明地使用其子类的对象。原则：子类可以扩展父类的功能，但不能改变父类原有的功能。\n父类能出现的地方都可以用子类来代替，而且换成子类也不会出现任何错误或异常，而使用者也无需知道是父类还是子类，但反过来则不成立。总之，就是抽象。\n子类必须完全实现父类的抽象方法，但不能覆盖父类的非抽象方法；\n子类中可以增加自己特有的方法； 当子类的方法重载父类的方法时，方法的前置条件(即方法的形参)要比父类方法的输入参数要更宽松； 当子类的方法实现父类的抽象方法时，方法的后置条件(即方法的返回值)要比父类更严格。 优点：\n提高代码的重用性，子类拥有父类的方法和属性；\n提高代码的可扩展性，子类可形似于父类，但异于父类，保留自我的特性；\n缺点：\n继承是侵入性的，只要继承就必须拥有父类的所有方法和属性，在一定程度上约束了子类，降低了代码的灵活性；\n增加了耦合，当父类的常量、变量或者方法被修改了，需要考虑子类的修改，所以一旦父类有了变动，很可能会造成不可预知的错误\n依赖倒置\n官方解释：高层模块不应该依赖低层模块，二者都应该依赖其抽象；抽象不应该依赖细节（实现）；细节（实现）应该依赖抽象；\n高层模块不应该直接依赖于底层模块的具体实现，而应该依赖于底层的抽象。换言之，模块间的依赖是通过抽象发生，实现类之间不发生直接的依赖关系，其依赖关系是通过接口或抽象类产生的。\n接口和抽象类不应该依赖于实现类，而实现类依赖接口或抽象类。这一点其实不用多说，很好理解，“面向接口编程”思想正是这点的最好体现。\n尽量使用接口来去规范依赖，目的是增大其扩展性；如一个读取数据的类，应该使用接口来规范，因为数据可以通过很多途径获取，如网络，文本，数据库等等\n接口隔离（最小化接口原则 或 接口分离原则）\n官方解释：客户端不应该依赖它不需要的接口；一个类对另一个类的依赖应该建立在最小的接口上。\n一个类不该实现它不需要的接口，如类a需实现接口x，而x中有1,2,3这三个方法，而只有1是类a需要的，那么这是就不应该实现接口x；可以用适配器模式解决\n迪米特法则（最小知原则）\n官方解释：一个软件实体应当尽可能少地与其他实体发生相互作用，如果一个系统符合迪米特法则，那么当其中某一个模块发生修改时，就会尽量少地影响其他模块，扩展会相对容易，这是对软件实体之间通信的限制，迪米特法则要求限制软件实体之间通信的宽度和深度。迪米特法则可降低系统的耦合度，使类与类之间保持松散的耦合关系。\n在类的划分上，应当尽量创建松耦合的类，类之间的耦合度越低，就越有利于复用，一个处在松耦合中的类一旦被修改，不会对关联的类造成太大波及；在类的结构设计上，每一个类都应当尽量降低其成员变量和成员函数的访问权限；在类的设计上，只要有可能，一个类型应当设计成不变类；在对其他类的引用上，一个对象对其他对象的引用应当降到最低。\n例如：类的功能不要全暴露出去，应该只暴露该类职责的基本功能即可，目的是做到类的高内聚低耦合(我们编写一个类的字段通常会用private修饰，这就是该原则的体现)\n如果其中的一个对象需要调用另一个对象的某一个方法的话，可以通过第三者转发这个调用。简言之，就是通过引入一个合理的第三者来降低现有对象之间的耦合度。\n开闭原则\n官方解释：软件实体（包括类、模块、功能等）应该对扩展开放，但是对修改关闭\n简单理解就是，如有个线上的系统中有个功能类A，现在需要扩展该类的功能，这时正确的做法不是修改类A的源码，而是使用其他扩展的方式来去完成功能的扩展，目录是避免修改源码带来的隐患。Spring中的AOP就很好的实现的这个原则\n常用的设计模式以及对应基本原则 todo\n","permalink":"https://cyn-blog.pages.dev/posts/01.dev/12.java/designpattern/","summary":"\u003ch2 id=\"java-设计模式\"\u003eJava 设计模式\u003c/h2\u003e\n\u003ch3 id=\"设计模式是什么\"\u003e设计模式是什么\u003c/h3\u003e\n\u003cp\u003e设计模式，简单来说就前人编程总结出来的套路\u003c/p\u003e\n\u003ch3 id=\"为什么要学习设计模式\"\u003e为什么要学习设计模式\u003c/h3\u003e\n\u003cp\u003e适应变化，提高代码复用率。改善系统的设计，增强系统的健壮性、可扩展性，为以后需求修改作铺垫.\u003c/p\u003e","title":"设计模式"},{"content":"git版本控制规范 规范Git commit背景 Git每次提交代码都需要写commit message，否则就不允许提交。\n一般来说，commit message应该清晰明了，说明本次提交的目的，具体做了什么操作……但是在日常开发中，commit message千奇百怪，中英文混合使用、fix bug等各种笼统的message司空见怪，这就导致后续代码维护成本特别大，有时自己都不知道自己的fix bug修改的是什么问题。基于以上这些问题，我们希望通过某种方式来监控用户的git commit message，让规范更好的服务于质量，提高自己的开发效率。\n规范建设 规范梳理 初期我们在互联网上搜索了大量有关git commit规范的资料，但只有Angular规范是目前使用最广的写法，比较合理和系统化，并且有配套的工具（IDEA就有插件支持这种写法）。最后综合阿里巴巴高德地图相关部门已有的规范总结出了一套git commit规范。\ncommit message格式\n\u0026lt;type\u0026gt;(\u0026lt;scope\u0026gt;): \u0026lt;subject\u0026gt; type(必须)\n用于说明git commit的类别，只允许使用下面的标识。\nfeat：新功能（feature）。\nfix/to：修复bug，可以是QA发现的BUG，也可以是研发自己发现的BUG。\nfix：产生diff并自动修复此问题。适合于一次提交直接修复问题 to：只产生diff不自动修复此问题。适合于多次提交。最终修复问题提交时使用fix docs：文档（documentation）。\nstyle：格式（不影响代码运行的变动）。\nrefactor：重构（即不是新增功能，也不是修改bug的代码变动）。\nperf：优化相关，比如提升性能、体验。\ntest：增加测试。\nchore：构建过程或辅助工具的变动。\nrevert：回滚到上一个版本。\nmerge：代码合并。\nsync：同步主线或分支的Bug。\nscope(可选)\nscope用于说明 commit 影响的范围，比如数据层、控制层、视图层等等，视项目不同而不同。\n例如在Angular，可以是location，browser，compile，compile，rootScope， ngHref，ngClick，ngView等。如果你的修改影响了不止一个scope，你可以使用*代替。\nsubject(必须)\nsubject是commit目的的简短描述，不超过50个字符。\n建议使用中文（感觉中国人用中文描述问题能更清楚一些）。\n结尾不加句号或其他标点符号。 根据以上规范git commit message将是如下的格式： fix(DAO):用户查询缺少username属性 feat(Controller):用户查询接口开发 这样规范git commit到底有哪些好处呢？\n便于程序员对提交历史进行追溯，了解发生了什么情况。 一旦约束了commit message，意味着我们将慎重的进行每一次提交，不能再一股脑的把各种各样的改动都放在一个git commit里面，这样一来整个代码改动的历史也将更加清晰。 格式化的commit message才可以用于自动化输出Change log。 参考:如何规范你的Git commit？]\n","permalink":"https://cyn-blog.pages.dev/posts/01.dev/13.codestd/gitstd/","summary":"\u003ch2 id=\"git版本控制规范\"\u003egit版本控制规范\u003c/h2\u003e\n\u003ch3 id=\"规范git-commit背景\"\u003e规范Git commit背景\u003c/h3\u003e\n\u003cp\u003eGit每次提交代码都需要写commit message，否则就不允许提交。\u003c/p\u003e\n\u003cp\u003e一般来说，commit message应该清晰明了，说明本次提交的目的，具体做了什么操作……但是在日常开发中，commit message千奇百怪，中英文混合使用、fix bug等各种笼统的message司空见怪，这就导致后续代码维护成本特别大，有时自己都不知道自己的fix bug修改的是什么问题。基于以上这些问题，我们希望通过某种方式来监控用户的git commit message，让规范更好的服务于质量，提高自己的开发效率。\u003c/p\u003e","title":"git版本控制规范"},{"content":"Java JVM虚拟机 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域 有各自的用途，以及创建和销毁的时间，有的区域随着虚拟机进程的启动而一直存在，有些区域则是 依赖用户线程的启动和结束而建立和销毁。根据《Java虚拟机规范》的规定，Java虚拟机所管理的内存 将会包括以下几个运行时数据区域\n","permalink":"https://cyn-blog.pages.dev/posts/01.dev/12.java/jvm/","summary":"\u003ch1 id=\"java-jvm虚拟机\"\u003eJava JVM虚拟机\u003c/h1\u003e\n\u003cp\u003eJava虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域\n有各自的用途，以及创建和销毁的时间，有的区域随着虚拟机进程的启动而一直存在，有些区域则是\n依赖用户线程的启动和结束而建立和销毁。根据《Java虚拟机规范》的规定，Java虚拟机所管理的内存\n将会包括以下几个运行时数据区域\u003c/p\u003e","title":"Java 虚拟机"},{"content":"Swagger简单使用 Swagger 是一个规范和完整的框架，用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。总体目标是使客户端和文件系统作为服务器以同样的速度来更新。文件的方法，参数和模型紧密集成到服务器端的代码，允许API来始终保持同步。Swagger 主要包含了以下三个部分：\nSwagger Editor：基于浏览器的编辑器，我们可以使用它编写我们 OpenAPI 规范。 Swagger UI：它会将我们编写的 OpenAPI 规范呈现为交互式的 API 文档，后文我将使用浏览器来查看并且操作我们的 Rest API。 Swagger Codegen：它可以通过为 OpenAPI（以前称为 Swagger）规范定义的任何 API 生成服务器存根和客户端 SDK 来简化构建过程。 其功能十分强大，Swagger 为我们提供了一套通过代码和注解自动生成文档的方法，这一点对于保证 API 文档的及时性将有很大的帮助。我们日常开发常用\n生成API文档， 功能测试 导入依赖 \u0026lt;!-- Swagger --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;io.springfox\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;springfox-swagger2\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;2.2.2\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;io.springfox\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;springfox-swagger-ui\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;2.2.2\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; 添加配置类 @Configuration @EnableSwagger2 public class SwaggerConfig { /** * 创建API应用 * apiInfo() 增加API相关信息 * 通过select()函数返回一个ApiSelectorBuilder实例,用来控制哪些接口暴露给Swagger来展现， * 本例采用指定扫描的包路径来定义指定要建立API的目录。 * * @return */ @Bean public Docket createRestApi() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .select() .apis(RequestHandlerSelectors.basePackage(\u0026#34;com.ll.farm.mall\u0026#34;)) .paths(PathSelectors.any()) .build(); } private ApiInfo apiInfo() { return new ApiInfoBuilder() .title(\u0026#34;后端接口标题\u0026#34;) .description(\u0026#34;后端接口描述\u0026#34;) .contact( new Contact(\u0026#34;farm-mall\u0026#34;, \u0026#34;mall.farm-mall.com\u0026#34;, \u0026#34;farm-mall@mall.com\u0026#34;) ) .version(\u0026#34;1.0.0-SNAPSHOT\u0026#34;) .build(); } } 如上代码所示，通过添加Swagger配置类，Spring框架扫描创建Bean之后，apiInfo()用来创建该Api的基本信息（这些基本信息会展现在文档页面中）。\n测试接口 添加测试类\n@RequestMapping(\u0026#34;/ums\u0026#34;) @RestController @Slf4j @Api(value = \u0026#34;用户菜单演示\u0026#34;, tags = \u0026#34;用户菜单\u0026#34;) public class AdminMenusController { @Autowired AdminMenuService adminMenuService; @Autowired Hello hello; @RequestMapping(value = \u0026#34;/getMenus\u0026#34;, method = RequestMethod.POST) @ApiOperation(notes = \u0026#34;需要id\u0026#34;, value = \u0026#34;获取用户菜单\u0026#34;) public AdminMenu getMenus(@ApiParam(required = true, value = \u0026#34;用户id\u0026#34;) @RequestBody Integer userId) { hello.getHello(); return adminMenuService.getAdminMenus(); } } 启动项目。访问http://127.0.0.1:8090/swagger-ui.html\n点击接口进行测试\nswagger注解介绍 Swagger的注解大致来说可以分为两类，一类是用于Controller层，就是我们的接口；还有一类是用于Model的，也就是一些POJO类，比如用于接收参数的类呀、返回给前端的。\n用于Controller层的注解 注解 作用范围 说明 @Api 类 用在类上，说明该类的作用 @ApiOperation 方法 注解来给API增加方法说明 @ApiImplicitParam 方法 用来注解来给方法入参增加说明 @ApiImplicitParams 方法 用在方法上包含一组参数说明，用于包含多个@ApiImplicitParam @ApiParam 方法的参数上 当方法的参数时一个类时，用此注解较 用于Model的注解 注解 作用范围 说明 @ApiModel 类 描述一个Model的信息（一般用在请求参数无法使用@ApiImplicitParam注解进行描述的时候） @ApiModelProperty 成员变量 描述一个model的属性，对该成员变量进行说明 注意点 如果方法注解上不写请求方法的参数\n生成的文档中，会包含所有的请求类型，开发中建议加上，减少冗余\n参考：\nSwagger使用手册\nSwagger使用教程\n在 Spring Boot 项目中使用 Swagger 文档\n","permalink":"https://cyn-blog.pages.dev/posts/03.framework/13.utils/swaggerdemo/","summary":"\u003ch2 id=\"swagger简单使用\"\u003eSwagger简单使用\u003c/h2\u003e\n\u003cp\u003eSwagger 是一个规范和完整的框架，用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。总体目标是使客户端和文件系统作为服务器以同样的速度来更新。文件的方法，参数和模型紧密集成到服务器端的代码，允许API来始终保持同步。Swagger 主要包含了以下三个部分：\u003c/p\u003e","title":"swagger简单使用"},{"content":"SpringBoot日志整合log4j2 + slf4j 良好的日志输出能够在遇到问题的时候很快的定位到出现问题的地方。所以我们首先把log4j集成进去。\n我们使用的是log4j2，在使用方面与log4j基本上没什么区别，比较大的区别是log4j2不再支持properties配置文件，支持xml、json格式的文件.\nproperties文件的可阅读性相对xml差了一些\n单独使用log4j2 pom添加依赖\n\u0026lt;dependencies\u0026gt; \u0026lt;!-- springBoot 的启动器 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-web\u0026lt;/artifactId\u0026gt; \u0026lt;exclusions\u0026gt;\u0026lt;!-- 去掉springboot默认配置 --\u0026gt; \u0026lt;exclusion\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-logging\u0026lt;/artifactId\u0026gt; \u0026lt;/exclusion\u0026gt; \u0026lt;/exclusions\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;!--log4j2 依赖--\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-log4j2\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;/dependencies\u0026gt; 添加log4j2配置文件 resources文件下新建log4j2.xml配置文件\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;!--Configuration后面的status，这个用于设置log4j2自身内部的信息输出，可以不设置，当设置成trace时，你会看到log4j2内部各种详细输出--\u0026gt; \u0026lt;!--monitorInterval：Log4j能够自动检测修改配置 文件和重新配置本身，设置间隔秒数--\u0026gt; \u0026lt;configuration monitorInterval=\u0026#34;5\u0026#34;\u0026gt; \u0026lt;!--日志级别以及优先级排序: OFF \u0026gt; FATAL \u0026gt; ERROR \u0026gt; WARN \u0026gt; INFO \u0026gt; DEBUG \u0026gt; TRACE \u0026gt; ALL --\u0026gt; \u0026lt;!--变量配置--\u0026gt; \u0026lt;Properties\u0026gt; \u0026lt;!-- 格式化输出：%date表示日期，%thread表示线程名，%-5level：级别从左显示5个字符宽度 %msg：日志消息，%n是换行符--\u0026gt; \u0026lt;!-- %logger{36} 表示 Logger 名字最长36个字符 --\u0026gt; \u0026lt;property name=\u0026#34;LOG_PATTERN\u0026#34; value=\u0026#34;%date{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n\u0026#34; /\u0026gt; \u0026lt;!-- 定义日志存储的路径 --\u0026gt; \u0026lt;property name=\u0026#34;FILE_PATH\u0026#34; value=\u0026#34;更换为你的日志路径\u0026#34; /\u0026gt; \u0026lt;property name=\u0026#34;FILE_NAME\u0026#34; value=\u0026#34;更换为你的项目名\u0026#34; /\u0026gt; \u0026lt;/Properties\u0026gt; \u0026lt;appenders\u0026gt; \u0026lt;console name=\u0026#34;Console\u0026#34; target=\u0026#34;SYSTEM_OUT\u0026#34;\u0026gt; \u0026lt;!--输出日志的格式--\u0026gt; \u0026lt;PatternLayout pattern=\u0026#34;${LOG_PATTERN}\u0026#34;/\u0026gt; \u0026lt;!--控制台只输出level及其以上级别的信息（onMatch），其他的直接拒绝（onMismatch）--\u0026gt; \u0026lt;ThresholdFilter level=\u0026#34;info\u0026#34; onMatch=\u0026#34;ACCEPT\u0026#34; onMismatch=\u0026#34;DENY\u0026#34;/\u0026gt; \u0026lt;/console\u0026gt; \u0026lt;!--文件会打印出所有信息，这个log每次运行程序会自动清空，由append属性决定，适合临时测试用--\u0026gt; \u0026lt;File name=\u0026#34;Filelog\u0026#34; fileName=\u0026#34;${FILE_PATH}/test.log\u0026#34; append=\u0026#34;false\u0026#34;\u0026gt; \u0026lt;PatternLayout pattern=\u0026#34;${LOG_PATTERN}\u0026#34;/\u0026gt; \u0026lt;/File\u0026gt; \u0026lt;!-- 这个会打印出所有的info及以下级别的信息，每次大小超过size，则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩，作为存档--\u0026gt; \u0026lt;RollingFile name=\u0026#34;RollingFileInfo\u0026#34; fileName=\u0026#34;${FILE_PATH}/info.log\u0026#34; filePattern=\u0026#34;${FILE_PATH}/${FILE_NAME}-INFO-%d{yyyy-MM-dd}_%i.log.gz\u0026#34;\u0026gt; \u0026lt;!--控制台只输出level及以上级别的信息（onMatch），其他的直接拒绝（onMismatch）--\u0026gt; \u0026lt;ThresholdFilter level=\u0026#34;info\u0026#34; onMatch=\u0026#34;ACCEPT\u0026#34; onMismatch=\u0026#34;DENY\u0026#34;/\u0026gt; \u0026lt;PatternLayout pattern=\u0026#34;${LOG_PATTERN}\u0026#34;/\u0026gt; \u0026lt;Policies\u0026gt; \u0026lt;!--interval属性用来指定多久滚动一次，默认是1 hour--\u0026gt; \u0026lt;TimeBasedTriggeringPolicy interval=\u0026#34;1\u0026#34;/\u0026gt; \u0026lt;SizeBasedTriggeringPolicy size=\u0026#34;10MB\u0026#34;/\u0026gt; \u0026lt;/Policies\u0026gt; \u0026lt;!-- DefaultRolloverStrategy属性如不设置，则默认为最多同一文件夹下7个文件开始覆盖--\u0026gt; \u0026lt;DefaultRolloverStrategy max=\u0026#34;15\u0026#34;/\u0026gt; \u0026lt;/RollingFile\u0026gt; \u0026lt;!-- 这个会打印出所有的warn及以下级别的信息，每次大小超过size，则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩，作为存档--\u0026gt; \u0026lt;RollingFile name=\u0026#34;RollingFileWarn\u0026#34; fileName=\u0026#34;${FILE_PATH}/warn.log\u0026#34; filePattern=\u0026#34;${FILE_PATH}/${FILE_NAME}-WARN-%d{yyyy-MM-dd}_%i.log.gz\u0026#34;\u0026gt; \u0026lt;!--控制台只输出level及以上级别的信息（onMatch），其他的直接拒绝（onMismatch）--\u0026gt; \u0026lt;ThresholdFilter level=\u0026#34;warn\u0026#34; onMatch=\u0026#34;ACCEPT\u0026#34; onMismatch=\u0026#34;DENY\u0026#34;/\u0026gt; \u0026lt;PatternLayout pattern=\u0026#34;${LOG_PATTERN}\u0026#34;/\u0026gt; \u0026lt;Policies\u0026gt; \u0026lt;!--interval属性用来指定多久滚动一次，默认是1 hour--\u0026gt; \u0026lt;TimeBasedTriggeringPolicy interval=\u0026#34;1\u0026#34;/\u0026gt; \u0026lt;SizeBasedTriggeringPolicy size=\u0026#34;10MB\u0026#34;/\u0026gt; \u0026lt;/Policies\u0026gt; \u0026lt;!-- DefaultRolloverStrategy属性如不设置，则默认为最多同一文件夹下7个文件开始覆盖--\u0026gt; \u0026lt;DefaultRolloverStrategy max=\u0026#34;15\u0026#34;/\u0026gt; \u0026lt;/RollingFile\u0026gt; \u0026lt;!-- 这个会打印出所有的error及以下级别的信息，每次大小超过size，则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩，作为存档--\u0026gt; \u0026lt;RollingFile name=\u0026#34;RollingFileError\u0026#34; fileName=\u0026#34;${FILE_PATH}/error.log\u0026#34; filePattern=\u0026#34;${FILE_PATH}/${FILE_NAME}-ERROR-%d{yyyy-MM-dd}_%i.log.gz\u0026#34;\u0026gt; \u0026lt;!--控制台只输出level及以上级别的信息（onMatch），其他的直接拒绝（onMismatch）--\u0026gt; \u0026lt;ThresholdFilter level=\u0026#34;error\u0026#34; onMatch=\u0026#34;ACCEPT\u0026#34; onMismatch=\u0026#34;DENY\u0026#34;/\u0026gt; \u0026lt;PatternLayout pattern=\u0026#34;${LOG_PATTERN}\u0026#34;/\u0026gt; \u0026lt;Policies\u0026gt; \u0026lt;!--interval属性用来指定多久滚动一次，默认是1 hour--\u0026gt; \u0026lt;TimeBasedTriggeringPolicy interval=\u0026#34;1\u0026#34;/\u0026gt; \u0026lt;SizeBasedTriggeringPolicy size=\u0026#34;10MB\u0026#34;/\u0026gt; \u0026lt;/Policies\u0026gt; \u0026lt;!-- DefaultRolloverStrategy属性如不设置，则默认为最多同一文件夹下7个文件开始覆盖--\u0026gt; \u0026lt;DefaultRolloverStrategy max=\u0026#34;15\u0026#34;/\u0026gt; \u0026lt;/RollingFile\u0026gt; \u0026lt;/appenders\u0026gt; \u0026lt;!--Logger节点用来单独指定日志的形式，比如要为指定包下的class指定不同的日志级别等。--\u0026gt; \u0026lt;!--然后定义loggers，只有定义了logger并引入的appender，appender才会生效--\u0026gt; \u0026lt;loggers\u0026gt; \u0026lt;!--过滤掉spring和mybatis的一些无用的DEBUG信息--\u0026gt; \u0026lt;logger name=\u0026#34;org.mybatis\u0026#34; level=\u0026#34;info\u0026#34; additivity=\u0026#34;false\u0026#34;\u0026gt; \u0026lt;AppenderRef ref=\u0026#34;Console\u0026#34;/\u0026gt; \u0026lt;/logger\u0026gt; \u0026lt;!--监控系统信息--\u0026gt; \u0026lt;!--若是additivity设为false，则 子Logger 只会在自己的appender里输出，而不会在 父Logger 的appender里输出。--\u0026gt; \u0026lt;Logger name=\u0026#34;org.springframework\u0026#34; level=\u0026#34;info\u0026#34; additivity=\u0026#34;false\u0026#34;\u0026gt; \u0026lt;AppenderRef ref=\u0026#34;Console\u0026#34;/\u0026gt; \u0026lt;/Logger\u0026gt; \u0026lt;root level=\u0026#34;info\u0026#34;\u0026gt; \u0026lt;appender-ref ref=\u0026#34;Console\u0026#34;/\u0026gt; \u0026lt;appender-ref ref=\u0026#34;Filelog\u0026#34;/\u0026gt; \u0026lt;appender-ref ref=\u0026#34;RollingFileInfo\u0026#34;/\u0026gt; \u0026lt;appender-ref ref=\u0026#34;RollingFileWarn\u0026#34;/\u0026gt; \u0026lt;appender-ref ref=\u0026#34;RollingFileError\u0026#34;/\u0026gt; \u0026lt;/root\u0026gt; \u0026lt;/loggers\u0026gt; \u0026lt;/configuration\u0026gt; xml配置解析 配置简介 日志级别\n机制：如果一条日志信息的级别大于等于配置文件的级别，就记录。\ntrace：追踪，就是程序推进一下，可以写个trace输出 debug：调试，一般作为最低级别，trace基本不用。 info：输出重要的信息，使用较多 warn：警告，有些信息不是错误信息，但也要给程序员一些提示。 error：错误信息。用的也很多。 fatal：致命错误。 输出源\nCONSOLE（输出到控制台） FILE（输出到文件） 格式\nSimpleLayout：以简单的形式显示\nHTMLLayout：以HTML表格显示\nPatternLayout：自定义形式显示\nPatternLayout自定义日志布局： %d{yyyy-MM-dd HH:mm:ss, SSS} : 日志生产时间,输出到毫秒的时间 %-5level : 输出日志级别，-5表示左对齐并且固定输出5个字符，如果不足在右边补0 %c : logger的名称(%logger) %t : 输出当前线程名称 %p : 日志输出格式 %m : 日志内容，即 logger.info(\u0026#34;message\u0026#34;) %n : 换行符 %C : Java类名(%F) %L : 行号 %M : 方法名 %l : 输出语句所在的行数, 包括类名、方法名、文件名、行数 hostName : 本地机器名 hostAddress : 本地ip地址 配置详解\n根节点Configuration有两个属性:\nstatus用来指定log4j本身的打印日志的级别. monitorinterval用于指定log4j自动重新配置的监测间隔时间，单位是s,最小是5s.\nstatus monitorinterval 根节点Configuration有两个子节点:\nAppenders Loggers(表明可以定义多个Appender和Logger). Appenders节点 常见的有三种子节点:Console、RollingFile、File\nConsole节点用来定义输出到控制台的Appender.\nname:指定Appender的名字. target:SYSTEM_OUT 或 SYSTEM_ERR,一般只设置默认:SYSTEM_OUT. PatternLayout:输出格式，不设置默认为:%m%n. File节点用来定义输出到指定位置的文件的Appender.\nname:指定Appender的名字. fileName:指定输出日志的目的文件带全路径的文件名. PatternLayout:输出格式，不设置默认为:%m%n. RollingFile节点用来定义超过指定条件自动删除旧的创建新的Appender.\nname:指定Appender的名字. fileName:指定输出日志的目的文件带全路径的文件名. PatternLayout:输出格式，不设置默认为:%m%n. filePattern : 指定当发生Rolling时，文件的转移和重命名规则. Policies:指定滚动日志的策略，就是什么时候进行新建日志文件输出日志. TimeBasedTriggeringPolicy:Policies子节点，基于时间的滚动策略，interval属性用来指定多久滚动一次，默认是1 hour。modulate=true用来调整时间：比如现在是早上3am，interval是4，那么第一次滚动是在4am，接着是8am，12am\u0026hellip;而不是7am. SizeBasedTriggeringPolicy:Policies子节点，基于指定文件大小的滚动策略，size属性用来定义每个日志文件的大小. DefaultRolloverStrategy:用来指定同一个文件夹下最多有几个日志文件时开始删除最旧的，创建新的(通过max属性)。 Loggers节点，常见的有两种:Root和Logger. Root节点用来指定项目的根日志，如果没有单独指定Logger，那么就会默认使用该Root日志输出\nlevel:日志输出级别，共有8个级别，按照从低到高为：All \u0026lt; Trace \u0026lt; Debug \u0026lt; Info \u0026lt; Warn \u0026lt; Error \u0026lt; AppenderRef：Root的子节点，用来指定该日志输出到哪个Appender. Logger节点用来单独指定日志的形式，比如要为指定包下的class指定不同的日志级别等。 level:日志输出级别，共有8个级别，按照从低到高为：All \u0026lt; Trace \u0026lt; Debug \u0026lt; Info \u0026lt; Warn \u0026lt; Error \u0026lt; Fatal \u0026lt; OFF. name:用来指定该Logger所适用的类或者类所在的包全路径,继承自Root节点. AppenderRef：Logger的子节点，用来指定该日志输出到哪个Appender,如果没有指定，就会默认继承自Root.如果指定了，那么会在指定的这个Appender和Root的Appender中都会输出，此时我们可以设置Logger的additivity=\u0026ldquo;false\u0026quot;只在自定义的Appender中进行输出。 测试demo：\n@RequestMapping(\u0026#34;/ums\u0026#34;) @RestController @Log4j2 @Api(value = \u0026#34;用户菜单演示\u0026#34;, tags = \u0026#34;用户菜单\u0026#34;) public class AdminMenusController { private Logger logger = LoggerFactory.getLogger(this.getClass()); @Autowired AdminMenuService adminMenuService; @Autowired Hello hello; @RequestMapping(value = \u0026#34;/getMenus\u0026#34;, method = RequestMethod.POST) @ApiOperation(notes = \u0026#34;需要id\u0026#34;, value = \u0026#34;获取用户菜单\u0026#34;) public AdminMenu getMenus(@ApiParam(required = true, value = \u0026#34;用户id\u0026#34;) @RequestBody Integer userId) { hello.getHello(); logger.info(\u0026#34;getMenus:{} Log4j2\u0026#34;, userId); System.out.println(\u0026#34;userId :\u0026#34; + userId); return adminMenuService.getAdminMenus(); } } 日志为:\n20:05:22.710 [http-nio-8090-exec-2] INFO com.ll.farm.mall.admin.controller.AdminMenusController - getMenus:123 Log4j2 userId :123 使用slf4j整合log4j2 实际开发中，推荐开发人员直接使用log4j2去记录日志信息。\n为什么阿里巴巴禁止工程师直接使用日志系统(Log4j、Logback)中的 API\n需要使用slf4j整合log4j2，确保系统兼容性，整体步骤和上述步骤一至。\n上面的 log4j2 已经适配了slf4j日志门面，所以我们的代码无需替换，只需要替换使用方法。\n使用log记录时，不能使用@Log4j2注解，需要使用@Slf4j注解，其他步骤一致\n参考：\nSpring Boot 学习笔记(二) 整合 log4j2\nSpringboot整合log4j2日志全解\nSpring Boot 整合 slf4j+log4j 实现日志管理\n","permalink":"https://cyn-blog.pages.dev/posts/03.framework/12.log/log4j2andslf4j/","summary":"\u003ch2 id=\"springboot日志整合log4j2--slf4j\"\u003eSpringBoot日志整合log4j2 + slf4j\u003c/h2\u003e\n\u003cp\u003e良好的日志输出能够在遇到问题的时候很快的定位到出现问题的地方。所以我们首先把log4j集成进去。\u003c/p\u003e\n\u003cp\u003e我们使用的是log4j2，在使用方面与log4j基本上没什么区别，比较大的区别是\u003cstrong\u003elog4j2不再支持properties配置文件，支持xml、json格式的文件.\u003c/strong\u003e\u003c/p\u003e","title":"SpringBoot日志整合"},{"content":"SpringBoot多模块开发，聚合打包 使用springboot进行微服务开发时，单个微服务内部，根据业务不同需要划分多个模块进行业务实现，就涉及到多模块管理和聚合\n如下图所示是一个mall工程\n其中业务模块为\nadmin ai manager nosql oss search 聚合模块为\ndeploy impl parent 业务模块根据业务需求进行开发，重点关注聚合模块，最外层的pom进行公用依赖管理\nparent模块-公用模块 parent模块主要用于所用模块公用的工具类，配置类等的实现\nparent会被所有业务子模块依赖\nimpl模块-工程聚合 impl模块没有代码实现，主要用于项目的依赖模块管理\nimpl依赖了所有的业务模块和parent模块，用于deploy做依赖打包\ndeploy模块-启动、打包 deploy模块主要负责\n项目的启动 项目配置管理 项目打包配置 其中pom文件尤为关键\npom只依赖了impl，打包时，会将所有业务模块根据配置，进行打包\n","permalink":"https://cyn-blog.pages.dev/posts/03.framework/10.spring/springbootmodule/","summary":"\u003ch2 id=\"springboot多模块开发聚合打包\"\u003eSpringBoot多模块开发，聚合打包\u003c/h2\u003e\n\u003cp\u003e使用springboot进行微服务开发时，单个微服务内部，根据业务不同需要划分多个模块进行业务实现，就涉及到多模块管理和聚合\u003c/p\u003e\n\u003cp\u003e如下图所示是一个mall工程\u003c/p\u003e","title":"SpringBoot多模块开发，聚合打包"},{"content":"控制反转 控制反转（Inversion of Control，缩写为IoC），是 Martin Fowler 教授提出的一种软件设计模式，是面向对象编程中的一种设计原则，可以用来减低计算机代码之间的耦合度。\n其中最常见的方式叫做依赖注入（Dependency Injection，简称DI），还有一种方式叫依赖查找(Dependency Lookup）。通俗的来理解，就是本来当需要某个类（构造函数）的某个方法时，自己需要主动实例化变为被动，不需要再考虑如何实例化其他依赖的类，只需要依赖注入 。所谓依赖注入就是由 IoC 容器在运行期间，动态地将某种依赖关系注入到对象之中。所以 IoC 和 DI 是从不同的角度的描述的同一件事情，就是通过引入IoC 容器，利用依赖注入的方式，实现对象之间的解耦。\n技术描述 Class A中用到了Class B的对象b，一般情况下，需要在A的代码中显式的new一个B的对象。\n采用依赖注入技术之后，A的代码只需要定义一个私有的B对象，不需要直接new来获得这个对象，而是通过相关的容器控制程序来将B对象在外部new出来并注入到A类里的引用中。而具体获取的方法、对象被获取时的状态由配置文件（如XML）来指定。\n简单实例 强耦合调用方式\n将 A 调用 B 的对象修改为 C 类的对象，修改的是调用方的代码，所以我们认为代码的调用权在调用方。\n基于 IoC（控制反转）的调用方式\n将上图的需求，修改为使用 IoC 的调用代码方式。就是将代码的控制权从调用方修改为被调用方，意味着，代码的调用权转移给被调用方（我们也称为服务方），不用修改调用方的代码，只要修改配置文件就实现对象的切换。\n如下图：将 A 类调用 B 类的对象修改为 C 类的对象，修改的是被调用方的配置文件的代码，所以代码的调用权转移到了被调用方。通过控制反转，我们可以实现增加模块或者移除模块统一由配置文件关联，所以增加或者移除模块配置 XML 配置文件即可。\n我们将代码的调用权（控制权）从调用方转移给被调用方（服务提供方）的设计模式称为控制反转（IoC）。\n根据上图可以得出，实现一个 IoC 的框架，必须要解决两个问题：\n被调用方（服务方），在程序启动时就要根据配置文件类以及类与类的关系创建好对象，放在一个容器里面。\n调用方使用一个接口或类的引用（不用使用 new），就可以创建获得对象。\n我们将这种不用 new，而是根据接口或者类的引用就可以从被调用的容器里获得创建的对象的方式称为依赖注入。\n所以 控制反转（IoC）= 依赖注入 + 面向接口的编程思想的实现\n在这里，我们首先抓住一个重点：Spring 之所以可以实现可插拔程序，是实现了不用 new ，使用类或接口就可以获得对象。\n参考：\n浅析控制反转\nIoC（控制反转）的概述\n","permalink":"https://cyn-blog.pages.dev/posts/03.framework/10.spring/ioc/","summary":"\u003ch2 id=\"控制反转\"\u003e控制反转\u003c/h2\u003e\n\u003cp\u003e\u003cstrong\u003e控制反转\u003c/strong\u003e（Inversion of Control，缩写为\u003cstrong\u003eIoC\u003c/strong\u003e），是 \u003ca href=\"https://link.zhihu.com/?target=https%3A//en.wikipedia.org/wiki/Martin_Fowler_(software_engineer)\"\u003eMartin Fowler\u003c/a\u003e 教授提出的一种软件设计模式，是\u003ca href=\"https://baike.baidu.com/item/%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E7%BC%96%E7%A8%8B\"\u003e面向对象编程\u003c/a\u003e中的一种\u003cstrong\u003e设计原则\u003c/strong\u003e，可以用来减低计算机代码之间的\u003ca href=\"https://baike.baidu.com/item/%E8%80%A6%E5%90%88%E5%BA%A6\"\u003e耦合度\u003c/a\u003e。\u003c/p\u003e\n\u003cp\u003e其中最常见的方式叫做\u003cstrong\u003e依赖注入\u003c/strong\u003e（Dependency Injection，简称\u003cstrong\u003eDI\u003c/strong\u003e），还有一种方式叫\u003cstrong\u003e依赖查找\u003c/strong\u003e(Dependency Lookup）。通俗的来理解，就是本来当需要某个类（构造函数）的某个方法时，自己需要主动实例化变为被动，不需要再考虑如何实例化其他依赖的类，只需要依赖注入  。所谓依赖注入就是由 IoC 容器在运行期间，动态地将某种依赖关系注入到对象之中。所以 IoC 和 DI 是从不同的角度的描述的同一件事情，就是通过引入\u003cstrong\u003eIoC 容器\u003c/strong\u003e，利用依赖注入的方式，实现对象之间的解耦。\u003c/p\u003e","title":"控制反转"},{"content":"面向切面编程 在软件业，AOP为Aspect Oriented Programming的缩写，意为：面向切面编程，可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。AOP实际是GoF设计模式的延续，设计模式孜孜不倦追求的是调用者和被调用者之间的解耦,提高代码的灵活性和可扩展性，AOP可以说也是这种目标的一种实现。\n主要功能 日志记录，性能统计，安全控制，事务处理，异常处理等等。\n主要意图 将日志记录，性能统计，安全控制，事务处理，异常处理等代码从业务逻辑代码中划分出来，通过对这些行为的分离，我们希望可以将它们独立到非指导业务逻辑的方法中，进而改变这些行为的时候不影响业务逻辑的代码。\n","permalink":"https://cyn-blog.pages.dev/posts/03.framework/10.spring/aop/","summary":"\u003ch2 id=\"面向切面编程\"\u003e面向切面编程\u003c/h2\u003e\n\u003cp\u003e在软件业，AOP为Aspect Oriented Programming的缩写，意为：\u003ca href=\"https://baike.baidu.com/item/%E9%9D%A2%E5%90%91%E5%88%87%E9%9D%A2%E7%BC%96%E7%A8%8B/6016335\"\u003e面向切面编程\u003c/a\u003e，可以通过\u003ca href=\"https://baike.baidu.com/item/%E9%A2%84%E7%BC%96%E8%AF%91\"\u003e预编译\u003c/a\u003e方式和运行期动态代理实现在不修改\u003ca href=\"https://baike.baidu.com/item/%E6%BA%90%E4%BB%A3%E7%A0%81\"\u003e源代码\u003c/a\u003e的情况下给程序动态统一添加功能的一种技术。AOP实际是GoF设计模式的延续，设计模式孜孜不倦追求的是调用者和被调用者之间的解耦,提高代码的灵活性和可扩展性，AOP可以说也是这种目标的一种实现。\u003c/p\u003e\n\u003ch3 id=\"主要功能\"\u003e主要功能\u003c/h3\u003e\n\u003cp\u003e日志记录，性能统计，安全控制，事务处理，\u003ca href=\"https://baike.baidu.com/item/%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86\"\u003e异常处理\u003c/a\u003e等等。\u003c/p\u003e\n\u003ch3 id=\"主要意图\"\u003e主要意图\u003c/h3\u003e\n\u003cp\u003e将日志记录，性能统计，安全控制，事务处理，\u003ca href=\"https://baike.baidu.com/item/%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86\"\u003e异常处理\u003c/a\u003e等代码从业务逻辑代码中划分出来，通过对这些行为的分离，我们希望可以将它们独立到非指导业务逻辑的方法中，进而改变这些行为的时候不影响业务逻辑的代码。\u003c/p\u003e","title":"面向切面编程"},{"content":"SpringBoot快速上手demo SpringBoot简介 是什么\n在介绍 SpringBoot 之前我们首先来简单介绍一下 Spring。Spring 是诞生于2002年的 Java 开发框架，可以说已经成为 Java 开发的事实标准。所谓事实标准就是虽然 Java 官方没有说它就是开发标准，但是在当前 Java 开发的众多项目中，当我们谈到产品级的 Java 项目的时候，大多都是基于 Spring 或者应用了 Spring 特性的。\nSpring 基于 IOC 和 AOP 两个特性对 Java 开发本身进行了大大的简化。但是一个大型的项目需要集成很多其他组件，比如一个 WEB 项目，至少要集成 MVC 框架、Tomcat 这种 WEB 容器、日志框架、ORM框架，连接数据库要选择连接池吧……使用 Spring 的话每集成一个组件都要去先写它的配置文件，比较繁琐且容易出错。\n然后就有了SpringBoot。\nSpring Boot 是由 Pivotal 团队提供的全新框架，2014 年 4 月发布 Spring Boot 1.0 2018 年 3 月 Spring Boot 2.0发布。它是对spring的进一步封装，其设计目的是用来简化 Spring 应用的初始搭建以及开发过程。怎么简化的呢？就是通过封装、抽象、提供默认配置等方式让我们更容易使用。\nSpringBoot 基于 Spring 开发。SpringBoot 本身并不提供 Spring 框架的核心特性以及扩展功能，也就是说，它并不是用来替代 Spring 的解决方案，而是和 Spring 框架紧密结合用于提升 Spring 开发者体验的工具。\n关于 SpringBoot 有一句很出名的话就是约定大于配置。采用 Spring Boot 可以大大的简化开发模式，它集成了大量常用的第三方库配置，所有你想集成的常用框架，它都有对应的组件支持，例如 Redis、MongoDB、Jpa、kafka，Hakira 等等。SpringBoot 应用中这些第三方库几乎可以零配置地开箱即用，大部分的 SpringBoot 应用都只需要非常少量的配置代码，开发者能够更加专注于业务逻辑。\n为什么\n为什么会产生 SpringBoot 呢？\n刚才说 SpringBoot 简化了基于 Spring 开发，这只是最直观的一方面，事实上 SpringBoot 的诞生有它所处的大时代背景这个原因在里面的，那就是微服务，这也是谈 SpringBoot 必谈微服务的原因。\n2014年一个叫 Martin Fowler （同时也是经典著作《重构：改善既有代码的设计》一书的作者）发表了一篇关于微服务的博客，比较形象生动地介绍了什么是微服务，然后微服务才慢慢被人所熟知。他说微服务其实是一种架构风格，我们在开发一个应用的时候这个应用应该是由一组小型服务组成，每个小型服务都运行在自己的进程内；小服务之间通过HTTP的方式进行互联互通。和微服务相对应的就是我们之前的，单体应用，就是大名鼎鼎的 all in one 的风格。这种风格把所有的东西都写在一个应用里面，比如我们熟悉的OA，CRM，ERP系统，所有的页面，所有的代码都放在一起，打成打成一个war包，然后把war包放在Tomcat容器中运行。\n这种传统web开发的架构模式当然也有它的优势，比如它测试部署比较简单，因为不涉及到多个服务的互联互调，只需要把一个包上传到服务器就行了，可以说是一人吃饱全家不饿。同样也不会给运维带来麻烦，方便水平扩展，只需要又把相同的应用复制多份放在不同的服务器中就达到了扩展的目的。\n单体应用的的缺点也显而易见，容易牵一发而动全身，比如要更改一个小小的功能，就可能需要重新部署整个应用。当然，更大的挑战就是日益增长的用户需求。\n怎么用\n介绍了一大堆，那 SpringBoot 的开箱即用是怎么体现的呢。\nSpringBoot 官方推荐的构建应用的方式是使用 Spring Initializr，直接在网页上选择好构建工具、语言、SpringBoot 版本，填好自己的项目名和初始依赖，然后点Generate 按钮，就能下载一个构建好的工程的zip包，只需要把这个包解压之后导入IDE就可以了。\n这已经是一个包含依赖的、完整的、可独立运行的springboot应用了！你所需要做的就是往里面填充自己的业务代码！\n当然，如果能直接使用IDE来进行上述操作可以让这个过程变得更顺滑。如果你使用的是 IDEA 商业版的话，新建工程的时候直接有 Spring 的选项；如果是IDEA社区版的话，可以安装 Spring Assistant 这个插件可以实现同样的功能。它们的原理是帮你把连接 Spring Initializr 并下载解压这个过程自动化了，所以只需要保持网络畅通就行了。\nSpringBoot Spring + Spring MVC Spring Initializr 1. 安装Tomcat 2. 引入spring必要依赖 spring-webmvc spring-context spring-beans spring-aspects \u0026hellip;\n3. 引入必要的第三方依赖，jdbc,test,log这些依赖依然要注意版本兼容问题 4. 新建webapp/WEB-INFO/web.xml 5. applicationContext.xml 6. Springmvc.xml \u0026hellip; 那如果要用原生的springMVC来实现这个事情就复杂了，可以看看右边我大概罗列的这些步骤，当时学的时候让我我非常头疼。要单独安装Tomcat，安装的过程中要注意版本和当前的spring版本是否兼容，手动引入spring各个模块的依赖。pom.xml就不说了，maven工程都要用到，然后还有web.xml-用来配置servlet、拦截规则、字符编码器等等，applicationContext.xml，springmvc.xml 等一大堆xml文件……\n这个过程对初学者非常不友好，记忆这些步骤和配置文件能让人崩溃，xml这种表达方式又不是很直观。这些东西称为脚手架，在小公司里面会搭建这些东西就可以算半个师傅了，小弟们就可以在搭好的架子里面写业务代码了。\n再聊回微服务，试想一下，如果我们要跟上时代的步伐，使用微服务去开发软件，每个功能模块都部署成一个单独的服务，这个时候我们再使用纯粹的 Spring 去开发，每开发一个服务都需要重复的搭建项目骨架，然后编写各种配置文件，几十几百个服务加起来，这部分工作量是很大的，这还不算业务代码的开发时间。这种时候就是 SpringBoot 发挥它开箱即用的特质的时候了。然后多个微服务之间再通过 Spring 全家桶里面的 SpringCloud 进行管理，比如服务注册、服务发现等等。所以我们现在说 SpringBoot 是 Java 企业级开发的一站式解决方案。\n软件工程是一个不断抽象，不断把复杂的东西简化的这样一套理论和工具，不是是说使用起来越复杂就可以彰显我的高端和牛逼，有时候反而是做多错多。所以SpringBoot告诉我们这些工作都没必要，框架来做就行了，你们可以专注于代码逻辑。\n小结\nSpringBoot 具有如下优点：\n快速创建独立运行的Spring项目以及与主流框架集成、 使用嵌入式的Servlet容器，应用无需打成WAR包 Starters自动依赖与版本控制 大量的自动配置，简化开发，也可修改默认值 无需配置XML，无代码生成，开箱即用 准生产环境的运行时应用监控 与云计算的天然集成 SpringBoot的demo idea新建一个maven工程\n模块化 新增三个模块\nqs-api、qs-pub、qs-service\n注意：在idea中，右侧红框中，是否有灰色的模块，这样的模块，需要重新在右侧mvn导入。否则在模块间依赖的时候会出现无法导入的问题\n依赖导入 导入springboot默认配置和启动器 导入lombok简化bean \u0026lt;parent\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-parent\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;2.0.0.RELEASE\u0026lt;/version\u0026gt; \u0026lt;/parent\u0026gt; \u0026lt;properties\u0026gt; \u0026lt;lombok.version\u0026gt;1.16.20\u0026lt;/lombok.version\u0026gt; \u0026lt;/properties\u0026gt; \u0026lt;dependencies\u0026gt; \u0026lt;!-- springBoot 的启动器 --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-web\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;!-- lombok插件--\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.projectlombok\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;lombok\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;${lombok.version}\u0026lt;/version\u0026gt; \u0026lt;scope\u0026gt;provided\u0026lt;/scope\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;/dependencies\u0026gt; Demo编写 打包测试 添加mvn配置\n\u0026lt;build\u0026gt; \u0026lt;plugins\u0026gt; \u0026lt;plugin\u0026gt; \u0026lt;groupId\u0026gt;org.apache.maven.plugins\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;maven-compiler-plugin\u0026lt;/artifactId\u0026gt; \u0026lt;configuration\u0026gt; \u0026lt;source\u0026gt;1.8\u0026lt;/source\u0026gt; \u0026lt;target\u0026gt;1.8\u0026lt;/target\u0026gt; \u0026lt;/configuration\u0026gt; \u0026lt;/plugin\u0026gt; \u0026lt;plugin\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-maven-plugin\u0026lt;/artifactId\u0026gt; \u0026lt;executions\u0026gt; \u0026lt;execution\u0026gt; \u0026lt;goals\u0026gt; \u0026lt;goal\u0026gt;repackage\u0026lt;/goal\u0026gt; \u0026lt;/goals\u0026gt; \u0026lt;/execution\u0026gt; \u0026lt;/executions\u0026gt; \u0026lt;/plugin\u0026gt; \u0026lt;/plugins\u0026gt; \u0026lt;/build\u0026gt; 在springboot启动的模块，pom新增打包配置\n右侧mvn，package打包成jar，启动测试\n","permalink":"https://cyn-blog.pages.dev/posts/03.framework/10.spring/springbootdemo/","summary":"\u003ch2 id=\"springboot快速上手demo\"\u003eSpringBoot快速上手demo\u003c/h2\u003e\n\u003ch3 id=\"springboot简介\"\u003eSpringBoot简介\u003c/h3\u003e\n\u003cp\u003e\u003cstrong\u003e是什么\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e在介绍 SpringBoot 之前我们首先来简单介绍一下 Spring。Spring 是诞生于2002年的 Java 开发框架，可以说已经成为 Java 开发的事实标准。所谓事实标准就是虽然 Java 官方没有说它就是开发标准，但是在当前 Java 开发的众多项目中，当我们谈到产品级的 Java 项目的时候，大多都是基于 Spring 或者应用了 Spring 特性的。\u003c/p\u003e","title":"SpringBoot快速上手demo"},{"content":"Java 反射 什么是反射：\n正常情况下我们新建一个类就是：\nStudent st = new Student() 通过new方式，调用默认构造方法新建一个类，加载到jvm虚拟机中，这种类的加载方法存在一种问题，就是运行时不灵活，如果我们把student类换成teacher类，就需要重新修改代码。\n而java的反射机制提供了一种方法，在程序运行时动态加载所需要的类\nClass c1 = Class.forName(\u0026#34;com.cyh.test.Teacher\u0026#34;); //创建此Class对象所表示类的一个新实例, //newInstance方法调用的是Teacher的空参数构造方法 Object o = c1.newInstance(); 类路径可以从配置文件加载，从而实现不需要修改代码，实现类的替换\n除此之外，还可以根据反射特性，对于任意一个类。都能都知道这个类的所有属性和方法，对于任意一个对象，都能够调用它的任意一个方法和属\n","permalink":"https://cyn-blog.pages.dev/posts/01.dev/12.java/reflect/","summary":"\u003ch1 id=\"java-反射\"\u003eJava 反射\u003c/h1\u003e\n\u003cp\u003e什么是\u003cstrong\u003e反射\u003c/strong\u003e：\u003c/p\u003e\n\u003cp\u003e正常情况下我们新建一个类就是：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eStudent\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003est\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eStudent\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e通过new方式，调用默认构造方法新建一个类，加载到jvm虚拟机中，这种类的加载方法存在一种问题，就是运行时不灵活，如果我们把student类换成teacher类，就需要重新修改代码。\u003c/p\u003e","title":"Java 反射"},{"content":"Java 注解 Annotation 中文译过来就是注解、标释的意思，在 Java 中注解是一个很重要的知识点，但经常还是有点让新手不容易理解。\n比较糟糕的技术文档主要特征之一就是：用专业名词来介绍专业名词。 比如：\nJava 注解用于为 Java 代码提供元数据。作为元数据，注解不直接影响你的代码执行，但也有一些类型的注解实际上可以用于这一目的。Java 注解是从 Java5 开始添加到 Java 的。 这是大多数网站上对于 Java 注解，解释确实正确，但是说实在话，我第一次学习的时候，头脑一片空白。这什么跟什么啊？听了像没有听一样。因为概念太过于抽象，所以初学者实在是比较吃力才能够理解，然后随着自己开发过程中不断地强化练习，才会慢慢对它形成正确的认识。\n我在写这篇文章的时候，我就在思考。如何让自己或者让读者能够比较直观地认识注解这个概念？是要去官方文档上翻译说明吗？我马上否定了这个答案。\n后来，我想到了一样东西————墨水，墨水可以挥发、可以有不同的颜色，用来解释注解正好。\n不过，我继续发散思维后，想到了一样东西能够更好地代替墨水，那就是印章。印章可以沾上不同的墨水或者印泥，可以定制印章的文字或者图案，如果愿意它也可以被戳到你任何想戳的物体表面。\n但是，我再继续发散思维后，又想到一样东西能够更好地代替印章，那就是标签。标签是一张便利纸，标签上的内容可以自由定义。常见的如货架上的商品价格标签、图书馆中的书本编码标签、实验室中化学材料的名称类别标签等等。\n并且，往抽象地说，标签并不一定是一张纸，它可以是对人和事物的属性评价。也就是说，标签具备对于抽象事物的解释。\n所以，基于如此，我完成了自我的知识认知升级，我决定用标签来解释注解。\n注解如同标签 之前某新闻客户端的评论有盖楼的习惯，于是 “乔布斯重新定义了手机、罗永浩重新定义了傻X” 就经常极为工整地出现在了评论楼层中，并且广大网友在相当长的一段时间内对于这种行为乐此不疲。这其实就是等同于贴标签的行为。 在某些网友眼中，罗永浩就成了傻X的代名词。\n广大网友给罗永浩贴了一个名为“傻x”的标签，他们并不真正了解罗永浩，不知道他当教师、砸冰箱、办博客的壮举，但是因为“傻x”这样的标签存在，这有助于他们直接快速地对罗永浩这个人做出评价，然后基于此，罗永浩就可以成为茶余饭后的谈资，这就是标签的力量。\n而在网络的另一边，老罗靠他的人格魅力自然收获一大批忠实的拥泵，他们对于老罗贴的又是另一种标签。\n老罗还是老罗，但是由于人们对于它贴上的标签不同，所以造成对于他的看法大相径庭，不喜欢他的人整天在网络上评论抨击嘲讽，而崇拜欣赏他的人则会愿意挣钱购买锤子手机的发布会门票。\n我无意于评价这两种行为，我再引个例子。\n《奇葩说》是近年网络上非常火热的辩论节目，其中辩手陈铭被另外一个辩手马薇薇攻击说是————“站在宇宙中心呼唤爱”，然后贴上了一个大大的标签————“鸡汤男”，自此以后，观众再看到陈铭的时候，首先映入脑海中便是“鸡汤男”三个大字，其实本身而言陈铭非常优秀，为人师表、作风正派、谈吐举止得体，但是在网络中，因为娱乐至上的环境所致，人们更愿意以娱乐的心态来认知一切，于是“鸡汤男”就如陈铭自己所说成了一个撕不了的标签。\n我们可以抽象概括一下，标签是对事物行为的某些角度的评价与解释。\n到这里，终于可以引出本文的主角注解了。\n初学者可以这样理解注解：想像代码具有生命，注解就是对于代码中某些鲜活个体的贴上去的一张标签。简化来讲，注解如同一张标签。\n在未开始学习任何注解具体语法而言，你可以把注解看成一张标签。这有助于你快速地理解它的大致作用。如果初学者在学习过程有大脑放空的时候，请不要慌张，对自己说：\n注解，标签。注解，标签。\n注解语法 因为平常开发少见，相信有不少的人员会认为注解的地位不高。其实同 classs 和 interface 一样，注解也属于一种类型。它是在 Java SE 5.0 版本中开始引入的概念。\n注解的定义 注解通过 @interface 关键字进行定义。\npublic @interface TestAnnotation {} 它的形式跟接口很类似，不过前面多了一个 @ 符号。上面的代码就创建了一个名字为 TestAnnotaion 的注解。\n你可以简单理解为创建了一张名字为 TestAnnotation 的标签。\n注解的应用 上面创建了一个注解，那么注解的的使用方法是什么呢。\n@TestAnnotation public class Test {} 创建一个类 Test,然后在类定义的地方加上 @TestAnnotation 就可以用 TestAnnotation 注解这个类了。\n你可以简单理解为将 TestAnnotation 这张标签贴到 Test 这个类上面。\n不过，要想注解能够正常工作，还需要介绍一下一个新的概念那就是元注解。\n元注解 元注解是什么意思呢？\n元注解是可以注解到注解上的注解，或者说元注解是一种基本注解，但是它能够应用到其它的注解上面。\n如果难于理解的话，你可以这样理解。元注解也是一张标签，但是它是一张特殊的标签，它的作用和目的就是给其他普通的标签进行解释说明的。\n元标签有 @Retention、@Documented、@Target、@Inherited、@Repeatable 5 种。\n@Retention Retention 的英文意为保留期的意思。当 @Retention 应用到一个注解上的时候，它解释说明了这个注解的的存活时间。\n它的取值如下：\nRetentionPolicy.SOURCE 注解只在源码阶段保留，在编译器进行编译时它将被丢弃忽视。 RetentionPolicy.CLASS 注解只被保留到编译进行的时候，它并不会被加载到 JVM 中。 RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候，它会被加载进入到 JVM 中，所以在程序运行时可以获取到它们。 我们可以这样的方式来加深理解，@Retention 去给一张标签解释的时候，它指定了这张标签张贴的时间。@Retention 相当于给一张标签上面盖了一张时间戳，时间戳指明了标签张贴的时间周期。\n@Retention(RetentionPolicy.RUNTIME) public @interface TestAnnotation {} 上面的代码中，我们指定 TestAnnotation 可以在程序运行周期被获取到，因此它的生命周期非常的长。\n@Documented 顾名思义，这个元注解肯定是和文档有关。它的作用是能够将注解中的元素包含到 Javadoc 中去。\n@Target Target 是目标的意思，@Target 指定了注解运用的地方。\n你可以这样理解，当一个注解被 @Target 注解时，这个注解就被限定了运用的场景。\n类比到标签，原本标签是你想张贴到哪个地方就到哪个地方，但是因为 @Target 的存在，它张贴的地方就非常具体了，比如只能张贴到方法上、类上、方法参数上等等。@Target 有下面的取值\nElementType.ANNOTATION_TYPE 可以给一个注解进行注解 ElementType.CONSTRUCTOR 可以给构造方法进行注解 ElementType.FIELD 可以给属性进行注解 ElementType.LOCAL_VARIABLE 可以给局部变量进行注解 ElementType.METHOD 可以给方法进行注解 ElementType.PACKAGE 可以给一个包进行注解 ElementType.PARAMETER 可以给一个方法内的参数进行注解 ElementType.TYPE 可以给一个类型进行注解，比如类、接口、枚举 @Inherited Inherited 是继承的意思，但是它并不是说注解本身可以继承，而是说如果一个超类被 @Inherited 注解过的注解进行注解的话，那么如果它的子类没有被任何注解应用的话，那么这个子类就继承了超类的注解。 说的比较抽象。代码来解释。\n@Inherited @Retention(RetentionPolicy.RUNTIME) @interface Test {} @Test public class A {} public class B extends A {} 注解 Test 被 @Inherited 修饰，之后类 A 被 Test 注解，类 B 继承 A,类 B 也拥有 Test 这个注解。\n可以这样理解：\n老子非常有钱，所以人们给他贴了一张标签叫做富豪。\n老子的儿子长大后，只要没有和老子断绝父子关系，虽然别人没有给他贴标签，但是他自然也是富豪。\n老子的孙子长大了，自然也是富豪。\n这就是人们口中戏称的富一代，富二代，富三代。虽然叫法不同，好像好多个标签，但其实事情的本质也就是他们有一张共同的标签，也就是老子身上的那张富豪的标签。\n@Repeatable Repeatable 自然是可重复的意思。@Repeatable 是 Java 1.8 才加进来的，所以算是一个新的特性。\n什么样的注解会多次应用呢？通常是注解的值可以同时取多个。\n举个例子，一个人他既是程序员又是产品经理,同时他还是个画家。\n@interface Persons { Person[] value(); } @Repeatable(Persons.class) @interface Person{ String role default \u0026#34;\u0026#34;; } @Person(role=\u0026#34;artist\u0026#34;) @Person(role=\u0026#34;coder\u0026#34;) @Person(role=\u0026#34;PM\u0026#34;) public class SuperMan{} 注意上面的代码，@Repeatable 注解了 Person。而 @Repeatable 后面括号中的类相当于一个容器注解。\n什么是容器注解呢？就是用来存放其它注解的地方。它本身也是一个注解。\n我们再看看代码中的相关容器注解。\n@interface Persons { Person[] value(); } 按照规定，它里面必须要有一个 value 的属性，属性类型是一个被 @Repeatable 注解过的注解数组，注意它是数组。\n如果不好理解的话，可以这样理解。Persons 是一张总的标签，上面贴满了 Person 这种同类型但内容不一样的标签。把 Persons 给一个 SuperMan 贴上，相当于同时给他贴了程序员、产品经理、画家的标签。\n我们可能对于 @Person(role=”PM”) 括号里面的内容感兴趣，它其实就是给 Person 这个注解的 role 属性赋值为 PM ，大家不明白正常，马上就讲到注解的属性这一块。\n注解的属性 注解的属性也叫做成员变量。注解只有成员变量，没有方法。注解的成员变量在注解的定义中以“无形参的方法”形式来声明，其方法名定义了该成员变量的名字，其返回值定义了该成员变量的类型。\n@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface TestAnnotation { int id(); String msg(); } 上面代码定义了 TestAnnotation 这个注解中拥有 id 和 msg 两个属性。在使用的时候，我们应该给它们进行赋值。\n赋值的方式是在注解的括号内以 value=”” 形式，多个属性之前用 ，隔开。\n@TestAnnotation(id=3,msg=\u0026#34;hello annotation\u0026#34;) public class Test {} 需要注意的是，在注解中定义属性时它的类型必须是 8 种基本数据类型外加 类、接口、注解及它们的数组。\n注解中属性可以有默认值，默认值需要用 default 关键值指定。比如：\n@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface TestAnnotation { public int id() default -1; public String msg() default \u0026#34;Hi\u0026#34;; } TestAnnotation 中 id 属性默认值为 -1，msg 属性默认值为 Hi。 它可以这样应用。\n@TestAnnotation() public class Test {} 因为有默认值，所以无需要再在 @TestAnnotation 后面的括号里面进行赋值了，这一步可以省略。\n另外，还有一种情况。如果一个注解内仅仅只有一个名字为 value 的属性时，应用这个注解时可以直接接属性值填写到括号内。\npublic @interface Check { String value(); } 上面代码中，Check 这个注解只有 value 这个属性。所以可以这样应用。\n@Check(\u0026#34;hi\u0026#34;) int a; 这和下面的效果是一样的\n@Check(value=\u0026#34;hi\u0026#34;) int a; 最后，还需要注意的一种情况是一个注解没有任何属性。比如\npublic @interface Perform {} 那么在应用这个注解的时候，括号都可以省略。\n@Perform public void testMethod(){} Java 预置的注解 学习了上面相关的知识，我们已经可以自己定义一个注解了。其实 Java 语言本身已经提供了几个现成的注解。\n@Deprecated 这个元素是用来标记过时的元素，想必大家在日常开发中经常碰到。编译器在编译阶段遇到这个注解时会发出提醒警告，告诉开发者正在调用一个过时的元素比如过时的方法、过时的类、过时的成员变量。\npublic class Hero { @Deprecated public void say(){ System.out.println(\u0026#34;Noting has to say!\u0026#34;); } public void speak(){ System.out.println(\u0026#34;I have a dream!\u0026#34;); } } 定义了一个 Hero 类，它有两个方法 say() 和 speak() ，其中 say() 被 @Deprecated 注解。然后我们在 IDE 中分别调用它们。 可以看到，say() 方法上面被一条直线划了一条，这其实就是编译器识别后的提醒效果。\n@Override 这个大家应该很熟悉了，提示子类要复写父类中被 @Override 修饰的方法\n@SuppressWarnings 阻止警告的意思。之前说过调用被 @Deprecated 注解的方法后，编译器会警告提醒，而有时候开发者会忽略这种警告，他们可以在调用的地方通过 @SuppressWarnings 达到目的。\n@SuppressWarnings(\u0026#34;deprecation\u0026#34;) public void test1(){ Hero hero = new Hero(); hero.say(); hero.speak(); } @SafeVarargs 参数安全类型注解。它的目的是提醒开发者不要用参数做一些不安全的操作,它的存在会阻止编译器产生 unchecked 这样的警告。它是在 Java 1.7 的版本中加入的。\n@SafeVarargs // Not actually safe! static void m(List\u0026lt;String\u0026gt;... stringLists) { Object[] array = stringLists; List\u0026lt;Integer\u0026gt; tmpList = Arrays.asList(42); array[0] = tmpList; // Semantically invalid, but compiles without warnings String s = stringLists[0].get(0); // Oh no, ClassCastException at runtime! } 上面的代码中，编译阶段不会报错，但是运行时会抛出 ClassCastException 这个异常，所以它虽然告诉开发者要妥善处理，但是开发者自己还是搞砸了。\nJava 官方文档说，未来的版本会授权编译器对这种不安全的操作产生错误警告。\n@FunctionalInterface 函数式接口注解，这个是 Java 1.8 版本引入的新特性。函数式编程很火，所以 Java 8 也及时添加了这个特性。\n函数式接口 (Functional Interface) 就是一个具有一个方法的普通接口。 比如\n@FunctionalInterface public interface Runnable { /** * When an object implementing interface \u0026lt;code\u0026gt;Runnable\u0026lt;/code\u0026gt; is used * to create a thread, starting the thread causes the object\u0026#39;s * \u0026lt;code\u0026gt;run\u0026lt;/code\u0026gt; method to be called in that separately executing * thread. * \u0026lt;p\u0026gt; * The general contract of the method \u0026lt;code\u0026gt;run\u0026lt;/code\u0026gt; is that it may * take any action whatsoever. * * @see java.lang.Thread#run() */ public abstract void run(); } 我们进行线程开发中常用的 Runnable 就是一个典型的函数式接口，上面源码可以看到它就被 @FunctionalInterface 注解。\n可能有人会疑惑，函数式接口标记有什么用，这个原因是函数式接口可以很容易转换为 Lambda 表达式。这是另外的主题了，有兴趣的同学请自己搜索相关知识点学习。\n注解的提取 博文前面的部分讲了注解的基本语法，现在是时候检测我们所学的内容了。\n我通过用标签来比作注解，前面的内容是讲怎么写注解，然后贴到哪个地方去，而现在我们要做的工作就是检阅这些标签内容。 形象的比喻就是你把这些注解标签在合适的时候撕下来，然后检阅上面的内容信息。\n要想正确检阅注解，离不开一个手段，那就是反射。\n注解与反射。 注解通过反射获取。首先可以通过 Class 对象的 isAnnotationPresent() 方法判断它是否应用了某个注解\npublic boolean isAnnotationPresent(Class\u0026lt;? extends Annotation\u0026gt; annotationClass) {} 然后通过 getAnnotation() 方法来获取 Annotation 对象。\npublic \u0026lt;A extends Annotation\u0026gt; A getAnnotation(Class\u0026lt;A\u0026gt; annotationClass) {} 或者是 getAnnotations() 方法。\npublic Annotation[] getAnnotations() {} 前一种方法返回指定类型的注解，后一种方法返回注解到这个元素上的所有注解。\n如果获取到的 Annotation 如果不为 null，则就可以调用它们的属性方法了。比如\n@TestAnnotation() public class Test { public static void main(String[] args) { boolean hasAnnotation = Test.class.isAnnotationPresent(TestAnnotation.class); if ( hasAnnotation ) { TestAnnotation testAnnotation = Test.class.getAnnotation(TestAnnotation.class); System.out.println(\u0026#34;id:\u0026#34;+testAnnotation.id()); System.out.println(\u0026#34;msg:\u0026#34;+testAnnotation.msg()); } } } 程序的运行结果是：\nid:-1 msg:1 这个正是 TestAnnotation 中 id 和 msg 的默认值。\n上面的例子中，只是检阅出了注解在类上的注解，其实属性、方法上的注解照样是可以的。同样还是要假手于反射。\n@TestAnnotation(msg=\u0026#34;hello\u0026#34;) public class Test { @Check(value=\u0026#34;hi\u0026#34;) int a; @Perform public void testMethod(){} @SuppressWarnings(\u0026#34;deprecation\u0026#34;) public void test1(){ Hero hero = new Hero(); hero.say(); hero.speak(); } public static void main(String[] args) { boolean hasAnnotation = Test.class.isAnnotationPresent(TestAnnotation.class); if ( hasAnnotation ) { TestAnnotation testAnnotation = Test.class.getAnnotation(TestAnnotation.class); //获取类的注解 System.out.println(\u0026#34;id:\u0026#34;+testAnnotation.id()); System.out.println(\u0026#34;msg:\u0026#34;+testAnnotation.msg()); } try { Field a = Test.class.getDeclaredField(\u0026#34;a\u0026#34;); a.setAccessible(true); //获取一个成员变量上的注解 Check check = a.getAnnotation(Check.class); if ( check != null ) { System.out.println(\u0026#34;check value:\u0026#34;+check.value()); } Method testMethod = Test.class.getDeclaredMethod(\u0026#34;testMethod\u0026#34;); if ( testMethod != null ) { // 获取方法中的注解 Annotation[] ans = testMethod.getAnnotations(); for( int i = 0;i \u0026lt; ans.length;i++) { System.out.println(\u0026#34;method testMethod annotation:\u0026#34;+ans[i].annotationType().getSimpleName()); } } } catch (NoSuchFieldException e) { // TODO Auto-generated catch block e.printStackTrace(); System.out.println(e.getMessage()); } catch (SecurityException e) { // TODO Auto-generated catch block e.printStackTrace(); System.out.println(e.getMessage()); } catch (NoSuchMethodException e) { // TODO Auto-generated catch block e.printStackTrace(); System.out.println(e.getMessage()); } } } 它们的结果如下：\nid:-1 msg:hello check value:hi method testMethod annotation:Perform 需要注意的是，如果一个注解要在运行时被成功提取，那么 @Retention(RetentionPolicy.RUNTIME) 是必须的。\n注解的使用场景 我相信博文讲到这里大家都很熟悉了注解，但是有不少同学肯定会问，注解到底有什么用呢？\n对啊注解到底有什么用？\n我们不妨将目光放到 Java 官方文档上来。\n文章开始的时候，我用标签来类比注解。但标签比喻只是我的手段，而不是目的。为的是让大家在初次学习注解时能够不被那些抽象的新概念搞懵。既然现在，我们已经对注解有所了解，我们不妨再仔细阅读官方最严谨的文档。\n注解是一系列元数据，它提供数据用来解释程序代码，但是注解并非是所解释的代码本身的一部分。注解对于代码的运行效果没有直接影响。\n注解有许多用处，主要如下：\n提供信息给编译器： 编译器可以利用注解来探测错误和警告信息 编译阶段时的处理： 软件工具可以用来利用注解信息来生成代码、Html文档或者做其它相应处理。 运行时的处理： 某些注解可以在程序运行的时候接受代码的提取 值得注意的是，注解不是代码本身的一部分。 如果难于理解，可以这样看。罗永浩还是罗永浩，不会因为某些人对于他“傻x”的评价而改变，标签只是某些人对于其他事物的评价，但是标签不会改变事物本身，标签只是特定人群的手段。所以，注解同样无法改变代码本身，注解只是某些工具的的工具。\n还是回到官方文档的解释上，注解主要针对的是编译器和其它工具软件(SoftWare tool)。\n当开发者使用了Annotation 修饰了类、方法、Field 等成员之后，这些 Annotation 不会自己生效，必须由开发者提供相应的代码来提取并处理 Annotation 信息。这些处理提取和处理 Annotation 的代码统称为 APT（Annotation Processing Tool)。\n现在，我们可以给自己答案了，注解有什么用？给谁用？给 编译器或者 APT 用的。\n如果，你还是没有搞清楚的话，我亲自写一个好了。\n亲手自定义注解完成某个目的 我要写一个测试框架，测试程序员的代码有无明显的异常。\n—— 程序员 A : 我写了一个类，它的名字叫做 NoBug，因为它所有的方法都没有错误。 —— 我：自信是好事，不过为了防止意外，让我测试一下如何？ —— 程序员 A: 怎么测试？ —— 我：把你写的代码的方法都加上 @Jiecha 这个注解就好了。 —— 程序员 A: 好的。\npackage ceshi; import ceshi.Jiecha; public class NoBug { @Jiecha public void suanShu(){ System.out.println(\u0026#34;1234567890\u0026#34;); } @Jiecha public void jiafa(){ System.out.println(\u0026#34;1+1=\u0026#34;+1+1); } @Jiecha public void jiefa(){ System.out.println(\u0026#34;1-1=\u0026#34;+(1-1)); } @Jiecha public void chengfa(){ System.out.println(\u0026#34;3 x 5=\u0026#34;+ 3*5); } @Jiecha public void chufa(){ System.out.println(\u0026#34;6 / 0=\u0026#34;+ 6 / 0); } public void ziwojieshao(){ System.out.println(\u0026#34;我写的程序没有 bug!\u0026#34;); } } 上面的代码，有些方法上面运用了 @Jiecha 注解。\n这个注解是我写的测试软件框架中定义的注解。\npackage ceshi; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @Retention(RetentionPolicy.RUNTIME) public @interface Jiecha { } 然后，我再编写一个测试类 TestTool 就可以测试 NoBug 相应的方法了。\npackage ceshi; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class TestTool { public static void main(String[] args) { // TODO Auto-generated method stub NoBug testobj = new NoBug(); Class clazz = testobj.getClass(); Method[] method = clazz.getDeclaredMethods(); //用来记录测试产生的 log 信息 StringBuilder log = new StringBuilder(); // 记录异常的次数 int errornum = 0; for ( Method m: method ) { // 只有被 @Jiecha 标注过的方法才进行测试 if ( m.isAnnotationPresent( Jiecha.class )) { try { m.setAccessible(true); m.invoke(testobj, null); } catch (Exception e) { // TODO Auto-generated catch block //e.printStackTrace(); errornum++; log.append(m.getName()); log.append(\u0026#34; \u0026#34;); log.append(\u0026#34;has error:\u0026#34;); log.append(\u0026#34;\\n\\r caused by \u0026#34;); //记录测试过程中，发生的异常的名称 log.append(e.getCause().getClass().getSimpleName()); log.append(\u0026#34;\\n\\r\u0026#34;); //记录测试过程中，发生的异常的具体信息 log.append(e.getCause().getMessage()); log.append(\u0026#34;\\n\\r\u0026#34;); } } } log.append(clazz.getSimpleName()); log.append(\u0026#34; has \u0026#34;); log.append(errornum); log.append(\u0026#34; error.\u0026#34;); // 生成测试报告 System.out.println(log.toString()); } } 测试的结果是：\n1234567890 1+1=11 1-1=0 3 x 5=15 chufa has error: caused by ArithmeticException / by zero NoBug has 1 error. 提示 NoBug 类中的 chufa() 这个方法有异常，这个异常名称叫做 ArithmeticException，原因是运算过程中进行了除 0 的操作。\n所以，NoBug 这个类有 Bug。\n这样，通过注解我完成了我自己的目的，那就是对别人的代码进行测试。\n所以，再问我注解什么时候用？我只能告诉你，这取决于你想利用它干什么用。\n注解应用实例 注解运用的地方太多了，如： JUnit 这个是一个测试框架，典型使用方法如下：\npublic class ExampleUnitTest { @Test public void addition_isCorrect() throws Exception { assertEquals(4, 2 + 2); } } @Test 标记了要进行测试的方法 addition_isCorrect().\n还有例如ssm框架等运用了大量的注解。\n总结 如果注解难于理解，你就把它类同于标签，标签为了解释事物，注解为了解释代码。 注解的基本语法，创建如同接口，但是多了个 @ 符号。 注解的元注解。 注解的属性。 注解主要给编译器及工具类型的软件用的。 注解的提取需要借助于 Java 的反射技术，反射比较慢，所以注解使用时也需要谨慎计较时间成本。 转载：java注解-最通俗易懂的讲解\n","permalink":"https://cyn-blog.pages.dev/posts/01.dev/12.java/annotation/","summary":"\u003ch2 id=\"java-注解\"\u003eJava 注解\u003c/h2\u003e\n\u003cp\u003eAnnotation 中文译过来就是注解、标释的意思，在 Java 中注解是一个很重要的知识点，但经常还是有点让新手不容易理解。\u003c/p\u003e\n\u003cp\u003e比较糟糕的技术文档主要特征之一就是：用专业名词来介绍专业名词。\n比如：\u003c/p\u003e","title":"注解"},{"content":"💦JAVA Library目的是整合Java学习过程中的琐碎知识点，致力于提升基础代码能力。\nJava基础 基础知识 什么是反射 通俗易懂解释注解 JVM简介 常见的设计模式 Java8 Java8新特性 框架 Spring框架 spring基础 SpringBootDemo工程 框架特性-AOP 框架特性-IOC 常用注解 多模块开发，聚合打包 中间件 代理服务器 nginx简单使用 网络 CORS-跨域资源共享 对象存储 FastDFS-分布式文件系统 敏捷开发工具 swagger-API文档工具 前端 todo 拓展 如何使用vuepress玩转blog 编码规范 git基础规范 面试 todo ","permalink":"https://cyn-blog.pages.dev/posts/04.javalibrary/javalibrary/","summary":"\u003cp\u003e💦\u003ccode\u003eJAVA Library\u003c/code\u003e目的是整合Java学习过程中的琐碎知识点，致力于提升基础代码能力。\u003c/p\u003e\n\u003ch2 id=\"java基础\"\u003eJava基础\u003c/h2\u003e\n\u003ch3 id=\"基础知识\"\u003e基础知识\u003c/h3\u003e\n\u003col\u003e\n\u003cli\u003e\u003ca href=\"../01.dev/12.java/reflect.html\"\u003e什么是反射\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"../01.dev/12.java/annotation.html\"\u003e通俗易懂解释注解\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"../01.dev/12.java/jvm.html\"\u003eJVM简介\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"../01.dev/12.java/designpattern.html\"\u003e常见的设计模式\u003c/a\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch3 id=\"java8\"\u003eJava8\u003c/h3\u003e\n\u003col\u003e\n\u003cli\u003e\u003ca href=\"../01.dev/12.java/java8newfeature.html\"\u003eJava8新特性\u003c/a\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch2 id=\"框架\"\u003e框架\u003c/h2\u003e\n\u003ch3 id=\"spring框架\"\u003eSpring框架\u003c/h3\u003e\n\u003col start=\"0\"\u003e\n\u003cli\u003e\u003ca href=\"/03.framework/10.spring/springbase.html\"\u003espring基础\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"../03.framework/10.spring/springbootdemo.html\"\u003eSpringBootDemo工程\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"../03.framework/10.spring/aop.html\"\u003e框架特性-AOP\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"../03.framework/10.spring/ioc.html\"\u003e框架特性-IOC\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"../03.framework/10.spring/annotation.html\"\u003e常用注解\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"../03.framework/10.spring/springbootmodule.html\"\u003e多模块开发，聚合打包\u003c/a\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch2 id=\"中间件\"\u003e中间件\u003c/h2\u003e\n\u003ch3 id=\"代理服务器\"\u003e代理服务器\u003c/h3\u003e\n\u003col\u003e\n\u003cli\u003e\u003ca href=\"../01.dev/11.network/nginx.html\"\u003enginx简单使用\u003c/a\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch3 id=\"网络\"\u003e网络\u003c/h3\u003e\n\u003col\u003e\n\u003cli\u003e\u003ca href=\"../01.dev/11.network/cros.html\"\u003eCORS-跨域资源共享\u003c/a\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch3 id=\"对象存储\"\u003e对象存储\u003c/h3\u003e\n\u003col\u003e\n\u003cli\u003e\u003ca href=\"../01.dev/10.storage/fastdfs.html\"\u003eFastDFS-分布式文件系统\u003c/a\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch3 id=\"敏捷开发工具\"\u003e敏捷开发工具\u003c/h3\u003e\n\u003col\u003e\n\u003cli\u003e\u003ca href=\"../03.framework/13.utils/swaggerdemo.html\"\u003eswagger-API文档工具\u003c/a\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch2 id=\"前端\"\u003e前端\u003c/h2\u003e\n\u003ch3 id=\"todo\"\u003etodo\u003c/h3\u003e\n\u003ch3 id=\"拓展\"\u003e拓展\u003c/h3\u003e\n\u003col\u003e\n\u003cli\u003e\u003ca href=\"../02.front/10.vuepress/manual.html\"\u003e如何使用vuepress玩转blog\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e编码规范\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"../01.dev/13.codestd/gitstd.html\"\u003egit基础规范\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch2 id=\"面试\"\u003e面试\u003c/h2\u003e\n\u003ch3 id=\"todo-1\"\u003etodo\u003c/h3\u003e","title":"Java 知识库"},{"content":"领域驱动设计（Domain-Drive-Design） 领域驱动设计(简称 ddd)概念来源于2004年著名建模专家eric evans发表的他最具影响力的书籍:《domain-driven design –tackling complexity in the heart of software》(中文译名：领域驱动设计—软件核心复杂性应对之道)一书。，书中提出了“领域驱动设计(简称 ddd)”的概念。\n领域驱动设计一般分为两个阶段：\n以一种领域专家、设计人员、开发人员都能理解的“通用语言”作为相互交流的工具，在不断交流的过程中发现和挖出一些主要的领域概念，然后将这些概念设计成一个领域模型； 由领域模型驱动软件设计，用代码来表现该领域模型。领域需求的最初细节，在功能层面通过领域专家的讨论得出。 领域驱动设计告诉我们，在通过软件实现一个业务系统时，建立一个领域模型是非常重要和必要的。\n实际上 DDD 的概念和逻辑本身并不复杂，很多概念和名词是为了解决一些特定的问题才引入的，并和面向对象思想兼容，可以说 DDD 也是面向对象思想中的一个子集。如果遵从奥卡姆剃刀的原则，“如无必要，勿增实体”，我们先把 DDD 这些概念丢开，从一个案例出发，在必要的时候将这些概念引入。\n参考：使用 DDD 指导业务设计的一点思考\n","permalink":"https://cyn-blog.pages.dev/posts/03.framework/11.microservices/domaindrivedesign/","summary":"\u003ch1 id=\"领域驱动设计domain-drive-design\"\u003e领域驱动设计（Domain-Drive-Design）\u003c/h1\u003e\n\u003cp\u003e领域驱动设计(简称 ddd)概念来源于2004年著名建模专家eric evans发表的他最具影响力的书籍:《domain-driven design –tackling complexity in the heart of software》(中文译名：领域驱动设计—软件核心复杂性应对之道)一书。，书中提出了“领域驱动设计(简称 ddd)”的概念。\u003c/p\u003e","title":"DDD(Domain-Drive-Design)领域驱动设计"},{"content":"Spring框架常用注解 @Import注解 —— 导入资源 自定义类导入spring容器\n创建需要导入的类\npublic class CatImport { } 启动时手动导入\n@Import(CatImport.class}) @SpringBootApplication(scanBasePackages = {\u0026#34;com.cyn.config\u0026#34;}) public class SpringAnnotationApplication { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(SpringAnnotationApplication.class, args); CatImport catImport = context.getBean(CatImport.class); System.out.println(catImport); } } 输出\ncom.cyn.config.CatImport@35f26e72 从输出结果知，@Import注解把用到的bean导入到了当前容器中。\n@ConfigurationProperties —— 配置加载 常用于个性化配置文件。编写项目代码时，我们要求更灵活的配置，更好的模块化整合。通常情况下，我们可以使用 @Value 注解或着使用 Spring Environmen bean 访问这些属性。\n一般情况下如果一个项目中写了很多个配置，例如\nspring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver spring.datasource.url=jdbc:oracle:thin:@127.0.0.1:1521:orcl spring.datasource.username=cyn spring.datasource.password=cyn 这种情况下，我们在使用时，按照原有的配置读取就会比较繁琐\npublic class TestConfig { @Value(\u0026#34;${spring.datasource.driver-class-name}\u0026#34;) private String driverClassName; // :后代表取不到时的默认值 @Value(\u0026#34;${spring.datasource.url:default}\u0026#34;) private String url; @Value(\u0026#34;${spring.datasource.username}\u0026#34;) private String username; @Value(\u0026#34;${spring.datasource.password}\u0026#34;) private String password; } 这种使用@Value注入配置方式有时显得很笨重\n我们将使用更安全的方式(@ConfigurationProperties )来获取这些属性\n@Data @ConfigurationProperties(prefix=\u0026#34;spring.datasource\u0026#34;) public class TestConfigProperties { private String driverClassName; private String url; private String username; private String password; } @ConfigurationProperties 的基本用法非常简单:我们为每个要捕获的外部属性提供一个带有字段的类。请注意以下几点:\n前缀定义了哪些外部属性将绑定到类的字段上 根据 Spring Boot 宽松的绑定规则，类的属性名称必须与外部属性的名称匹配 我们可以简单地用一个值初始化一个字段来定义一个默认值 类本身可以是包私有的 类的字段必须有公共 setter 方法 参考：\n@ConfigurationProperties 注解使用姿势，这一篇就够了\n@PostConstruct —— Bean初始化 如果一个类A中有个成员变量p被@Autowried注解，那么@Autowired注入是发生在A的构造方法执行完之后的。\n如果想在生成对象时完成某些初始化操作，依赖于依赖注入，那么就无法在构造函数中实现。\n可以使用@PostConstruct注解一个方法来完成初始化，@PostConstruct注解的方法将会在依赖注入完成后被自动调用。\n@Component public class PostConstructDemo { public PostConstructDemo() { System.out.println(\u0026#34;ParentBean construct\u0026#34;); } @Bean public DemoA getDemoA() { return new DemoA(\u0026#34;DemoA init\u0026#34;); } @PostConstruct public void init() { System.out.println(getDemoA()); System.out.println(\u0026#34;ParentBean init\u0026#34;); } } 测试：\n@SpringBootApplication(scanBasePackages = {\u0026#34;com.cyn\u0026#34;}) public class SpringAnnotationApplication { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(SpringAnnotationApplication.class, args); PostConstructDemo postConstructDemo = context.getBean(PostConstructDemo.class); } } 输出：\nParentBean construct DemoA{name=\u0026#39;DemoA init\u0026#39;} ParentBean init 可以看出执行顺序： Constructor \u0026raquo; @Autowired \u0026raquo; @PostConstruct\n@Resource \u0026ndash; 依赖注入 @Autowired与@Resource都可以用来装配bean，都可以写在字段或setter方法上\n@Autowired默认按类型装配，默认情况下必须要求依赖对象存在，如果要允许null值，可以设置它的required属性为false。如果想使用名称装配可以结合@Qualifier注解进行使用。\n@Resource，默认按照名称进行装配，名称可以通过name属性进行指定，如果没有指定name属性，当注解写在字段上时，默认取字段名进行名称查找。如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是，如果name属性一旦指定，就只会按照名称进行装配。\n","permalink":"https://cyn-blog.pages.dev/posts/03.framework/10.spring/annotation/","summary":"\u003ch1 id=\"spring框架常用注解\"\u003eSpring框架常用注解\u003c/h1\u003e\n\u003ch2 id=\"import注解--导入资源\"\u003e@Import注解 —— 导入资源\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e自定义类导入spring容器\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003e创建需要导入的类\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eCatImport\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e启动时手动导入\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nd\"\u003e@Import\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eCatImport\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eclass\u003c/span\u003e\u003cspan class=\"p\"\u003e})\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nd\"\u003e@SpringBootApplication\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003escanBasePackages\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;com.cyn.config\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e})\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eSpringAnnotationApplication\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003evoid\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003emain\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eargs\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eConfigurableApplicationContext\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003econtext\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eSpringApplication\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003erun\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eSpringAnnotationApplication\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eclass\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eargs\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eCatImport\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ecatImport\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003econtext\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetBean\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eCatImport\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eclass\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eprintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecatImport\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e输出\u003c/p\u003e","title":"Spring框架常用注解"},{"content":"JAVA8 新特性 ❕❗❗❕ 所有的demo可见 GITHUB\nLambda 什么是Lambda\nLambda 表达式（lambda expression）是一个匿名函数，Lambda表达式基于数学中的λ演算得名，直接对应于其中的lambda抽象（lambda abstraction），是一个匿名函数，即没有函数名的函数。Lambda表达式可以表示闭包。\n什么是闭包\n闭包就是能够读取其他函数内部变量的函数。例如在javascript中，只有函数内部的子函数才能读取局部变量，所以闭包可以理解成**“定义在一个函数内部的函数“**。\n优势\nLambda 表达式主要用来定义行内执行的方法类型接口\nLambda 表达式免去了使用匿名方法的麻烦，并且给予Java简单但是强大的函数化的编程能力。\n例子\npublic class LambdaDemo2 { public static void main(String[] args) { int[] nums = {1, 2, 3, 4, 5}; Arrays.stream(nums).forEach(value -\u0026gt; System.out.println(value)); } } 此处的\nvalue -\u0026gt; System.out.println(value) 就是典型的Lambda表达式\nLambda使用中的问题\n从lambda 表达式引用的本地变量必须是最终变量或实际上的最终变量\n第一个问题，为什么存在这样的限制？\n要回答这个问题，我们需要首先明白，匿名内部类外面的value和里面的value是同一个内存地址中的数据么？\n很明显不是，因为我们都知道，局部变量存在于栈帧的局部变量表中，一旦方法结束，栈帧被销毁，这个变量（这份数据）就不再存在，但是匿名内部类中的value可能在栈帧销毁后继续存在（比如在这个例子中，匿名内部类被提交到了线程池中）。\n所以，只有一个可能，在匿名内部类被创建的时候，被捕获的局部变量发生了复制。如果我们允许在匿名内部类中执行value++操作，带来的后果就是，匿名内部类中的value的拷贝被更新了，但是原先的value不会受到任何影响（因为它可能已经不存在了）——你看上去好像两个value是同一个地址，同一份数据，但是实际上发生了拷贝，和方法调用的值传递如出一辙。这是很可怕的一件事情，它会让你误以为，在匿名内部类中执行value++会改变原先的局部变量value。\n第二个问题：那为什么Java 8之后我可以不写final了呢？\n如下图\nJava 8引入了lambda表达式，我们从此可以非常方便地编写大量的小代码块，但是在捕获外围的局部变量这件事上，lambda表达式和匿名内部类没有任何区别——被捕获的局部变量必须是final的。这就带来了一个问题，继续坚持把局部变量声明成final的话，烦也烦死了。 因此，JLS做出了一个妥协：\n假如一个局部变量在整个生命周期中都没有被改变（指向），那么它就是effectively final的——换句话说，不是final，胜似final。这样的局部变量也允许被lambda表达式或者匿名内部类所捕获，不过只能看不能摸——可以读取，但是不能修改。\nStreamAPI Java 8 API添加了一个新的抽象称为流Stream，可以让你以一种声明的方式处理数据。\nStream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。\nStream API可以极大提高Java程序员的生产力，让程序员写出高效率、干净、简洁的代码。\n这种风格将要处理的元素集合看作一种流， 流在管道中传输， 并且可以在管道的节点上进行处理， 比如筛选， 排序，聚合等。\n元素流在管道中经过中间操作（intermediate operation）的处理，最后由最终操作(terminal operation)得到前面处理的结果。\n+--------------------+ +------+ +------+ +---+ +-------+ | stream of elements +-----\u0026gt; |filter+-\u0026gt; |sorted+-\u0026gt; |map+-\u0026gt; |collect| +--------------------+ +------+ +------+ +---+ +-------+ 以上的流程转换为 Java 代码为：\nList\u0026lt;Integer\u0026gt; transactionsIds = widgets.stream() .filter(b -\u0026gt; b.getColor() == RED) .sorted((x,y) -\u0026gt; x.getWeight() - y.getWeight()) .mapToInt(Widget::getWeight) .sum(); 什么是 Stream？ Stream（流）是一个来自数据源的元素队列并支持聚合操作\n元素是特定类型的对象，形成一个队列。 Java中的Stream并不会存储元素，而是按需计算。 数据源 流的来源。 可以是集合，数组，I/O channel， 产生器generator 等。 聚合操作 类似SQL语句一样的操作， 比如filter, map, reduce, find, match, sorted等。 和以前的Collection操作不同， Stream操作还有两个基础的特征：\nPipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道， 如同流式风格（fluent style）。 这样做可以对操作进行优化， 比如延迟执行(laziness)和短路( short-circuiting)。 内部迭代： 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代， 这叫做外部迭代。 Stream提供了内部迭代的方式， 通过访问者模式(Visitor)实现。 如何生成Stream // stream 生成流 List\u0026lt;String\u0026gt; strings = Arrays.asList(\u0026#34;abc\u0026#34;, \u0026#34;\u0026#34;, \u0026#34;bc\u0026#34;, \u0026#34;efg\u0026#34;, \u0026#34;abcd\u0026#34;, \u0026#34;\u0026#34;, \u0026#34;jkl\u0026#34;); List\u0026lt;String\u0026gt; filtered = strings.stream() .filter(string -\u0026gt; !string.isEmpty()) .collect(Collectors.toList()); System.out.println(strings); System.out.println(filtered); System.out.println(\u0026#34;===============\u0026#34;); 如上述代码，使用一个String的List数组，使用stream方法生成，并使用流式操作返回了一个stream流。输出如下\n[abc, , bc, efg, abcd, , jkl] [abc, bc, efg, abcd, jkl] =============== 常用Stream forEach\nJava8的Stream中使用 \u0026lsquo;forEach\u0026rsquo; 来迭代流中的每个数据\n// forEach List\u0026lt;String\u0026gt; strings = Arrays.asList(\u0026#34;abc\u0026#34;, \u0026#34;\u0026#34;, \u0026#34;bc\u0026#34;, \u0026#34;efg\u0026#34;, \u0026#34;abcd\u0026#34;, \u0026#34;\u0026#34;, \u0026#34;jkl\u0026#34;); strings.stream() .forEach(string -\u0026gt; System.out.println(string)); System.out.println(\u0026#34;===============\u0026#34;); //输出 abc bc efg abcd jkl =============== map\n用于映射每个元素到对应的结果（对流中的每个元素做操作后返回流）\n// map List\u0026lt;String\u0026gt; strings = Arrays.asList(\u0026#34;abc\u0026#34;, \u0026#34;\u0026#34;, \u0026#34;bc\u0026#34;, \u0026#34;efg\u0026#34;, \u0026#34;abcd\u0026#34;, \u0026#34;\u0026#34;, \u0026#34;jkl\u0026#34;); strings.stream() .map(string -\u0026gt; string + \u0026#34;Map\u0026#34;) .forEach(string -\u0026gt; System.out.println(string)); System.out.println(\u0026#34;===============\u0026#34;); // 输出 abcMap Map bcMap efgMap abcdMap Map jklMap =============== filter\n用于通过设置的条件过滤出元素，filter中的匿名函数为true时，stream中的数据保留，false时，该数据被过滤\n// filter List\u0026lt;String\u0026gt; strings = Arrays.asList(\u0026#34;abc\u0026#34;, \u0026#34;\u0026#34;, \u0026#34;bc\u0026#34;, \u0026#34;efg\u0026#34;, \u0026#34;abcd\u0026#34;, \u0026#34;\u0026#34;, \u0026#34;jkl\u0026#34;); strings.stream() .filter(s -\u0026gt; s.contains(\u0026#34;b\u0026#34;)) .forEach(s -\u0026gt; System.out.println(s)); System.out.println(\u0026#34;===============\u0026#34;); // 输出 abc bc abcd =============== limit\n获取指定数量的流，下述代码获取了原有流中的前两个数据\n// limit 于获取指定数量的流 List\u0026lt;String\u0026gt; strings = Arrays.asList(\u0026#34;abc\u0026#34;, \u0026#34;\u0026#34;, \u0026#34;bc\u0026#34;, \u0026#34;efg\u0026#34;, \u0026#34;abcd\u0026#34;, \u0026#34;\u0026#34;, \u0026#34;jkl\u0026#34;); strings.stream() .limit(2) .forEach(s -\u0026gt; System.out.println(s)); System.out.println(\u0026#34;===============\u0026#34;); //输出 abc =============== 并行流parallel\n采用并行流收集元素到集合中时。\nparallelStream提供了流的并行处理，它是Stream的另一重要特性，其底层使用Fork/Join框架实现。简单理解就是多线程异步任务的一种实现。\nList\u0026lt;Integer\u0026gt; numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9); numbers.stream() .forEach(num -\u0026gt; System.out.println(\u0026#34; \u0026gt;\u0026gt;\u0026gt;\u0026gt; \u0026#34; + num)); System.out.println(\u0026#34;---------\u0026#34;); numbers.parallelStream() .forEach(num -\u0026gt; System.out.println(\u0026#34; \u0026gt;\u0026gt;\u0026gt;\u0026gt; \u0026#34; + num)); System.out.println(\u0026#34;---------\u0026#34;); numbers.parallelStream() .forEach(num -\u0026gt; System.out.println(Thread.currentThread().getName() + \u0026#34; \u0026gt;\u0026gt;\u0026gt;\u0026gt; \u0026#34; + num)); System.out.println(\u0026#34;=========\u0026#34;); 上述代码使用了分别采用了并行流和普通流式操作。从下面的输出可以看出：\n普通的流式操作输出的数字是按照顺序输出的。单线程顺序执行。\n并行流的输出是无序的，而且可以看出是用不同线程执行了输出。\n\u0026gt;\u0026gt;\u0026gt;\u0026gt; 1 \u0026gt;\u0026gt;\u0026gt;\u0026gt; 2 \u0026gt;\u0026gt;\u0026gt;\u0026gt; 3 \u0026gt;\u0026gt;\u0026gt;\u0026gt; 4 \u0026gt;\u0026gt;\u0026gt;\u0026gt; 5 \u0026gt;\u0026gt;\u0026gt;\u0026gt; 6 \u0026gt;\u0026gt;\u0026gt;\u0026gt; 7 \u0026gt;\u0026gt;\u0026gt;\u0026gt; 8 \u0026gt;\u0026gt;\u0026gt;\u0026gt; 9 --------- \u0026gt;\u0026gt;\u0026gt;\u0026gt; 6 \u0026gt;\u0026gt;\u0026gt;\u0026gt; 5 \u0026gt;\u0026gt;\u0026gt;\u0026gt; 3 \u0026gt;\u0026gt;\u0026gt;\u0026gt; 4 \u0026gt;\u0026gt;\u0026gt;\u0026gt; 2 \u0026gt;\u0026gt;\u0026gt;\u0026gt; 7 \u0026gt;\u0026gt;\u0026gt;\u0026gt; 8 \u0026gt;\u0026gt;\u0026gt;\u0026gt; 1 \u0026gt;\u0026gt;\u0026gt;\u0026gt; 9 --------- main \u0026gt;\u0026gt;\u0026gt;\u0026gt; 6 ForkJoinPool.commonPool-worker-1 \u0026gt;\u0026gt;\u0026gt;\u0026gt; 8 ForkJoinPool.commonPool-worker-4 \u0026gt;\u0026gt;\u0026gt;\u0026gt; 2 ForkJoinPool.commonPool-worker-5 \u0026gt;\u0026gt;\u0026gt;\u0026gt; 3 ForkJoinPool.commonPool-worker-2 \u0026gt;\u0026gt;\u0026gt;\u0026gt; 7 ForkJoinPool.commonPool-worker-3 \u0026gt;\u0026gt;\u0026gt;\u0026gt; 5 ForkJoinPool.commonPool-worker-4 \u0026gt;\u0026gt;\u0026gt;\u0026gt; 4 ForkJoinPool.commonPool-worker-1 \u0026gt;\u0026gt;\u0026gt;\u0026gt; 1 main \u0026gt;\u0026gt;\u0026gt;\u0026gt; 9 ========= 注意：最好调用collect方法，采用Foreach方法或者map方法可能会出现线程安全问题\ncollect\nCollectors 类实现了很多归约操作，例如将流转换成集合和聚合元素。Collectors 可用于返回列表或字符串：\n// Collectors List\u0026lt;String\u0026gt; strings = Arrays.asList(\u0026#34;abc\u0026#34;, \u0026#34;\u0026#34;, \u0026#34;bc\u0026#34;, \u0026#34;efg\u0026#34;, \u0026#34;abcd\u0026#34;, \u0026#34;\u0026#34;, \u0026#34;jkl\u0026#34;); String filtereds = strings.stream() .filter(string -\u0026gt; !string.isEmpty()) .collect(Collectors.joining(\u0026#34;.\u0026#34;)); List\u0026lt;String\u0026gt; filteredss = strings .stream() .filter(string -\u0026gt; !string.isEmpty()) .collect(Collectors.toList()); System.out.println(filtereds); System.out.println(filteredss); System.out.println(\u0026#34;===============\u0026#34;); // 输出 abc.bc.efg.abcd.jkl [abc, bc, efg, abcd, jkl] =============== 数值流\nJava*引入了数值流 IntStream, DoubleStream, LongStream，这种流中的元素都是原始数据类型，分别是 int，double，long\nIntStream intStream = IntStream.rangeClosed(1, 10); System.out.println(intStream.sum()); System.out.println(\u0026#34;###\u0026#34;); int[] intArr = IntStream.rangeClosed(1, 10).toArray(); Arrays.stream(intArr) .forEach( value -\u0026gt; System.out.println(value+\u0026#34; \u0026#34;)); System.out.println(\u0026#34;###\u0026#34;); OptionalInt intMax = IntStream.rangeClosed(1, 10).max(); System.out.println(intMax.getAsInt()); System.out.println(\u0026#34;###\u0026#34;); Optional容器\nNullPointerException可以说是每一个 Java 程序员都非常讨厌看到的一个词，针对这个问题， Java 8 引入了一个新的容器类 Optional，可以代表一个值存在或不存在，这样就不用返回容易出问题的 null。之前文章的代码中就经常出现这个类，也是针对这个问题进行的改进。\nOptional 类比较常用的几个方法有：\nisPresent() ：值存在时返回 true，反之 flase\nget() ：返回当前值，若值不存在会抛出异常\norElse(T) ：值存在时返回该值，否则返回 T 的值\nOptional 类还有三个特化版本 OptionalInt，OptionalLong，OptionalDouble\n流式操作可能出现的问题 foreach中的return函数\nforeach()处理集合时不能使用break和continue这两个方法，也就是说不能按照普通的for循环遍历集合时那样根据条件来中止遍历，而如果要实现在普通for循环中的效果时，可以使用return来达到，也就是说如果你在一个方法的lambda表达式中使用return时，这个方法是不会返回的，而只是执行下一次遍历\nList\u0026lt;String\u0026gt; list = Arrays.asList(\u0026#34;123\u0026#34;, \u0026#34;45634\u0026#34;, \u0026#34;7892\u0026#34;, \u0026#34;abcdef\u0026#34;, \u0026#34;a\u0026#34;, \u0026#34;b\u0026#34;); list.stream().forEach(e -\u0026gt; { if (e.length() \u0026gt;= 5) { return; } System.out.println(e); }); //结果 123 7892 a b 并行流的陷阱\n线程安全\n由于并行流使用多线程，则一切线程安全问题都应该是需要考虑的问题，如：资源竞争、死锁、事务、可见性等等。\n线程消费\n在虚拟机启动时，我们指定了worker线程的数量，整个程序的生命周期都将使用这些工作线程；这必然存在任务生产和消费的问题，如果某个生产者生产了许多重量级的任务（耗时很长），那么其他任务毫无疑问将会没有工作线程可用；更可怕的事情是这些工作线程正在进行IO阻塞。\n本应利用并行加速处理的业务，因为工作者不够反而会额外增加处理时间，使得系统性能在某一时刻大打折扣。而且这一类问题往往是很难排查的。我们并不知道一个重量级项目中的哪一个框架、哪一个模块在使用并行流。\n串行流\n适合存在线程安全问题、阻塞任务、重量级任务，以及需要使用同一事务的逻辑。\n并行流\n适合没有线程安全问题、较单纯的数据处理任务\nCompletableFuture组合式异步编程 Future接口\nFuture接口在Java 5中被引入，设计初衷是对将来某个时刻会发生的结果进行建模。它建模了一种异步计算，返回一个执行运算结果的引用，当运算结束后，这个引用被返回给调用方。在Future中触发那些潜在耗时的操作把调用线程解放出来，让它能继续执行其他有价值的工作，不需要等待耗时的操作完成。\npublic static void main(String[] args) throws ExecutionException, InterruptedException { // 1 创建future FutureTask\u0026lt;String\u0026gt; stringFuture = new FutureTask\u0026lt;String\u0026gt;(new Callable\u0026lt;String\u0026gt;() { @Override public String call() throws Exception { Thread.sleep(1000); return \u0026#34;Future--Test\u0026#34;; } }); // 线程执行 ExecutorService executor = Executors.newCachedThreadPool(); executor.execute(stringFuture); //2 //向ExecutorService提交一个Callable对象 Future\u0026lt;String\u0026gt; future = executor.submit(new Callable\u0026lt;String\u0026gt;() { @Override public String call() throws InterruptedException { Thread.sleep(1000); //以异步方式在新线程中执行耗时的操作 return \u0026#34;延时1秒\u0026#34;; } }); // 注意get会 阻塞 System.out.println(stringFuture.get()); System.out.println(future.get()); executor.shutdown(); } 这种编程方式让你的线程可以在ExecutorService以并发方式调用另一个线程执行耗时操作的同时，去执行一些其他任务。如果已经运行到没有异步操作的结果就无法继续进行时，可以调用它的get方法去获取操作结果。如果操作已经完成，该方法会立刻返回操作结果，否则它会阻塞线程，直到操作完成，返回相应的结果。 为了处理长时间运行的操作永远不返回的可能性，虽然Future提供了一个无需任何参数的get方法，但还是推荐使用重载版本的get方法，它接受一个超时的参数，可以定义线程等待Future结果的时间，而不是永无止境地等待下去\nFuture接口的局限性 使用Future获得异步执行结果时，要么调用阻塞方法get()，要么轮询看isDone()是否为true，这两种方法都不是很好，因为主线程也会被迫等待。\n实现异步API\nCompletableFuture的优点是：\n异步任务结束时，会自动回调某个对象的方法； 异步任务出错时，会自动回调某个对象的方法； 主线程设置好回调后，不再关心异步任务的执行。 public static void main(String[] args) throws Exception { Instant start = Instant.now(); // 两个CompletableFuture执行异步查询: CompletableFuture\u0026lt;String\u0026gt; cfQueryFromSina = CompletableFuture.supplyAsync(() -\u0026gt; { return queryCode(\u0026#34;中国石油\u0026#34;, \u0026#34;https://finance.sina.com.cn/code/\u0026#34;); }); CompletableFuture\u0026lt;String\u0026gt; cfQueryFrom163 = CompletableFuture.supplyAsync(() -\u0026gt; { return queryCode(\u0026#34;中国石油\u0026#34;, \u0026#34;https://money.163.com/code/\u0026#34;); }); // 用anyOf合并为一个新的CompletableFuture: CompletableFuture\u0026lt;Object\u0026gt; cfQuery = CompletableFuture.anyOf(cfQueryFromSina, cfQueryFrom163); /*cfQuery.thenAccept((result) -\u0026gt; { System.out.println(\u0026#34;测试 result\u0026#34; + result); });*/ // 两个CompletableFuture执行异步查询: CompletableFuture\u0026lt;Double\u0026gt; cfFetchFromSina = cfQuery.thenApplyAsync((code) -\u0026gt; { return fetchPrice((String) code, \u0026#34;https://finance.sina.com.cn/price/\u0026#34;); }); CompletableFuture\u0026lt;Double\u0026gt; cfFetchFrom163 = cfQuery.thenApplyAsync((code) -\u0026gt; { return fetchPrice((String) code, \u0026#34;https://money.163.com/price/\u0026#34;); }); // 用anyOf合并为一个新的CompletableFuture: CompletableFuture\u0026lt;Object\u0026gt; cfFetch = CompletableFuture.anyOf(cfFetchFromSina, cfFetchFrom163); // 最终结果: cfFetch.thenAccept((result) -\u0026gt; { System.out.println(\u0026#34;price: \u0026#34; + result); Instant end = Instant.now(); System.out.println(\u0026#34;获取最终结果花费时间：\u0026#34; + Duration.between(start, end).toMillis() + \u0026#34;ms\u0026#34;); }); // 主线程不要立刻结束，否则CompletableFuture默认使用的线程池会立刻关闭: Thread.sleep(2000); } static String queryCode(String name, String url) { System.out.println(\u0026#34;query code from \u0026#34; + url + \u0026#34;...\u0026#34;); try { long sleepTime = (long) (Math.random() * 1000); Thread.sleep(sleepTime); System.out.println(url + \u0026#34; used time \u0026#34; + sleepTime); } catch (InterruptedException e) { } return \u0026#34;601857\u0026#34;; } static Double fetchPrice(String code, String url) { System.out.println(\u0026#34;query price from \u0026#34; + url + \u0026#34;...\u0026#34;); try { long sleepTime = (long) (Math.random() * 1000); Thread.sleep(sleepTime); System.out.println(url + \u0026#34; used time \u0026#34; + sleepTime); } catch (InterruptedException e) { } return 5 + Math.random() * 20; } 结果：\nquery code from https://finance.sina.com.cn/code/... query code from https://money.163.com/code/... https://finance.sina.com.cn/code/ used time 40 query price from https://money.163.com/price/... query price from https://finance.sina.com.cn/price/... https://money.163.com/code/ used time 207 https://finance.sina.com.cn/price/ used time 895 price: 12.113254661738953 获取最终结果花费时间：997ms https://money.163.com/price/ used time 935 参考：\n廖雪峰的官方网站\n菜鸟教程\nlambda表达式\n","permalink":"https://cyn-blog.pages.dev/posts/01.dev/12.java/java8newfeature/","summary":"\u003ch1 id=\"java8-新特性\"\u003eJAVA8 新特性\u003c/h1\u003e\n\u003cp\u003e\u003ca href=\"##Lambda\"\u003e\u003cimg alt=\"java\" loading=\"lazy\" src=\"https://img.shields.io/badge/JAVA-1.8+-green.svg\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e❕❗❗❕ 所有的demo可见 \u003ca href=\"https://github.com/ching7/javaBaseStudy.git\"\u003eGITHUB\u003c/a\u003e\u003c/p\u003e\n\u003ch2 id=\"lambda\"\u003eLambda\u003c/h2\u003e\n\u003cp\u003e\u003cstrong\u003e什么是\u003ccode\u003eLambda\u003c/code\u003e\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eLambda 表达式（lambda expression）是一个匿名函数，Lambda表达式基于数学中的λ演算得名，直接对应于其中的lambda抽象（lambda abstraction），是一个\u003cstrong\u003e匿名函数\u003c/strong\u003e，即没有函数名的函数。Lambda表达式可以表示闭包。\u003c/p\u003e","title":"Java8新特性"},{"content":"CORS （跨域资源共享） CORS是一个W3C标准，全称是\u0026quot;跨域资源共享\u0026quot;（Cross-origin resource sharing）。\n同源安全策略 默认阻止“跨域”获取资源。但是 CORS 给了web服务器这样的权限，即服务器可以选择，允许浏览器向跨源服务器发出请求。CORS需要浏览器和服务器同时支持。\n整个CORS通信过程，都是浏览器自动完成，不需要用户参与。对于开发者来说，CORS通信与同源的AJAX通信没有差别，代码完全一样。浏览器一旦发现AJAX请求跨源，就会自动添加一些附加的头信息，有时还会多出一次附加的请求，但用户不会有感觉。\n同源安全策略 1995年，同源政策由 Netscape 公司引入浏览器。目前，所有浏览器都实行这个政策。\n最初，它的含义是指，A网页设置的 Cookie，B网页不能打开，除非这两个网页\u0026quot;同源\u0026quot;。所谓\u0026quot;同源\u0026quot;指的是\u0026quot;三个相同\u0026quot;。\n协议相同 域名相同 端口相同 URL 结果 原因 http://store.company.com/dir2/other.html 同源 只有路径不同 http://store.company.com/dir/inner/another.html 同源 只有路径不同 https://store.company.com/secure.html 失败 协议不同 http://store.company.com:81/dir/etc.html 失败 端口不同 ( http:// 默认端口是80) http://news.company.com/dir/other.html 失败 主机不同 同源政策的目的，是为了保证用户信息的安全，防止恶意的网站窃取数据。\n设想这样一种情况：A网站是一家银行，用户登录以后，又去浏览其他网站。如果其他网站可以读取A网站的 Cookie，会发生什么？\n很显然，如果 Cookie 包含隐私（比如存款总额），这些信息就会泄漏。更可怕的是，Cookie 往往用来保存用户的登录状态，如果用户没有退出登录，其他网站就可以冒充用户，为所欲为。因为浏览器同时还规定，提交表单不受同源政策的限制。\nCORS请求 浏览器将CORS请求分成两类\n简单请求（simple request） 非简单请求（not-so-simple request）。 只要同时满足以下两大条件，就属于简单请求。\n（1) 请求方法是以下三种方法之一：\nHEAD GET POST （2）HTTP的头信息不超出以下几种字段：\nAccept Accept-Language Content-Language Last-Event-ID Content-Type：只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain 这是为了兼容表单（form），因为历史上表单一直可以发出跨域请求。AJAX 的跨域设计就是，只要表单可以发，AJAX 就可以直接发。\n凡是不同时满足上面两个条件，就属于非简单请求。\n浏览器对这两种请求的处理，是不一样的。\nCROS简单请求 基本流程 对于简单请求，浏览器直接发出CORS请求。具体来说，就是在头信息之中，增加一个Origin字段。\n下面是一个例子，浏览器发现这次跨源AJAX请求是简单请求，就自动在头信息之中，添加一个Origin字段。\nGET /cors HTTP/1.1 Origin: http://api.foo.com Host: api.alice.com Accept-Language: en-US Connection: keep-alive User-Agent: Mozilla/5.0... 上面的头信息中，Origin字段用来说明，本次请求来自哪个源（协议 + 域名 + 端口）。服务器根据这个值，决定是否同意这次请求。\n如果Origin指定的源，不在许可范围内，服务器会返回一个正常的HTTP回应。浏览器发现，这个回应的头信息没有包含Access-Control-Allow-Origin字段（详见下文），就知道出错了，从而抛出一个错误，被XMLHttpRequest的onerror回调函数捕获。注意，这种错误无法通过状态码识别，因为HTTP回应的状态码有可能是200。\n如果Origin指定的域名在许可范围内，服务器返回的响应，会多出几个头信息字段。\nAccess-Control-Allow-Origin: http://api.foo.com Access-Control-Allow-Credentials: true Access-Control-Expose-Headers: FooBar Content-Type: text/html; charset=utf-8 上面的头信息之中，有三个与CORS请求相关的字段，都以Access-Control-开头。\n（1）Access-Control-Allow-Origin\n该字段是必须的。它的值要么是请求时Origin字段的值，要么是一个*，表示接受任意域名的请求。\n（2）Access-Control-Allow-Credentials\n该字段可选。它的值是一个布尔值，表示是否允许发送Cookie。默认情况下，Cookie不包括在CORS请求之中。设为true，即表示服务器明确许可，Cookie可以包含在请求中，一起发给服务器。这个值也只能设为true，如果服务器不要浏览器发送Cookie，删除该字段即可。\n（3）Access-Control-Expose-Headers\n该字段可选。CORS请求时，XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段：Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段，就必须在Access-Control-Expose-Headers里面指定。上面的例子指定，getResponseHeader('FooBar')可以返回FooBar字段的值。\nwithCredentials 属性 上面说到，CORS请求默认不发送Cookie和HTTP认证信息。如果要把Cookie发到服务器，一方面要服务器同意，指定Access-Control-Allow-Credentials字段。\nAccess-Control-Allow-Credentials: true 另一方面，开发者必须在AJAX请求中打开withCredentials属性。\nvar xhr = new XMLHttpRequest(); xhr.withCredentials = true; 否则，即使服务器同意发送Cookie，浏览器也不会发送。或者，服务器要求设置Cookie，浏览器也不会处理。\n但是，如果省略withCredentials设置，有的浏览器还是会一起发送Cookie。这时，可以显式关闭withCredentials。\nxhr.withCredentials = false; 需要注意的是，如果要发送Cookie，Access-Control-Allow-Origin就不能设为星号，必须指定明确的、与请求网页一致的域名。同时，Cookie依然遵循同源政策，只有用服务器域名设置的Cookie才会上传，其他域名的Cookie并不会上传，且（跨源）原网页代码中的document.cookie也无法读取服务器域名下的Cookie。\n非简单请求 预检请求 非简单请求是那种对服务器有特殊要求的请求，比如请求方法是PUT或DELETE，或者Content-Type字段的类型是application/json。\n非简单请求的CORS请求，会在正式通信之前，增加一次HTTP查询请求，称为\u0026quot;预检\u0026quot;请求（preflight）。\n浏览器先询问服务器，当前网页所在的域名是否在服务器的许可名单之中，以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复，浏览器才会发出正式的XMLHttpRequest请求，否则就报错。\n下面是一段浏览器的JavaScript脚本。\nvar url = \u0026#39;http://api.alice.com/cors\u0026#39;; var xhr = new XMLHttpRequest(); xhr.open(\u0026#39;PUT\u0026#39;, url, true); xhr.setRequestHeader(\u0026#39;X-Custom-Header\u0026#39;, \u0026#39;value\u0026#39;); xhr.send(); 上面代码中，HTTP请求的方法是PUT，并且发送一个自定义头信息X-Custom-Header。\n浏览器发现，这是一个非简单请求，就自动发出一个\u0026quot;预检\u0026quot;请求，要求服务器确认可以这样请求。下面是这个\u0026quot;预检\u0026quot;请求的HTTP头信息。\nOPTIONS /cors HTTP/1.1 Origin: http://api.bob.com Access-Control-Request-Method: PUT Access-Control-Request-Headers: X-Custom-Header Host: api.alice.com Accept-Language: en-US Connection: keep-alive User-Agent: Mozilla/5.0... \u0026ldquo;预检\u0026quot;请求用的请求方法是OPTIONS，表示这个请求是用来询问的。头信息里面，关键字段是Origin，表示请求来自哪个源。\n除了Origin字段，\u0026ldquo;预检\u0026quot;请求的头信息包括两个特殊字段。\n（1）Access-Control-Request-Method\n该字段是必须的，用来列出浏览器的CORS请求会用到哪些HTTP方法，上例是PUT。\n（2）Access-Control-Request-Headers\n该字段是一个逗号分隔的字符串，指定浏览器CORS请求会额外发送的头信息字段，上例是X-Custom-Header。\n预检请求的回应 服务器收到\u0026quot;预检\u0026quot;请求以后，检查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以后，确认允许跨源请求，就可以做出回应。\nHTTP/1.1 200 OK Date: Mon, 01 Dec 2008 01:15:39 GMT Server: Apache/2.0.61 (Unix) Access-Control-Allow-Origin: http://api.bob.com Access-Control-Allow-Methods: GET, POST, PUT Access-Control-Allow-Headers: X-Custom-Header Content-Type: text/html; charset=utf-8 Content-Encoding: gzip Content-Length: 0 Keep-Alive: timeout=2, max=100 Connection: Keep-Alive Content-Type: text/plain 上面的HTTP回应中，关键的是Access-Control-Allow-Origin字段，表示http://api.bob.com可以请求数据。该字段也可以设为星号，表示同意任意跨源请求。\nAccess-Control-Allow-Origin: * 如果服务器否定了\u0026quot;预检\u0026quot;请求，会返回一个正常的HTTP回应，但是没有任何CORS相关的头信息字段。这时，浏览器就会认定，服务器不同意预检请求，因此触发一个错误，被XMLHttpRequest对象的onerror回调函数捕获。控制台会打印出如下的报错信息。\nXMLHttpRequest cannot load http://api.alice.com. Origin http://api.bob.com is not allowed by Access-Control-Allow-Origin. 服务器回应的其他CORS相关字段如下。\nAccess-Control-Allow-Methods: GET, POST, PUT Access-Control-Allow-Headers: X-Custom-Header Access-Control-Allow-Credentials: true Access-Control-Max-Age: 1728000 （1）Access-Control-Allow-Methods\n该字段必需，它的值是逗号分隔的一个字符串，表明服务器支持的所有跨域请求的方法。注意，返回的是所有支持的方法，而不单是浏览器请求的那个方法。这是为了避免多次\u0026quot;预检\u0026quot;请求。\n（2）Access-Control-Allow-Headers\n如果浏览器请求包括Access-Control-Request-Headers字段，则Access-Control-Allow-Headers字段是必需的。它也是一个逗号分隔的字符串，表明服务器支持的所有头信息字段，不限于浏览器在\u0026quot;预检\u0026quot;中请求的字段。\n（3）Access-Control-Allow-Credentials\n该字段与简单请求时的含义相同。\n（4）Access-Control-Max-Age\n该字段可选，用来指定本次预检请求的有效期，单位为秒。上面结果中，有效期是20天（1728000秒），即允许缓存该条回应1728000秒（即20天），在此期间，不用发出另一条预检请求。\n浏览器的正常请求和回应 一旦服务器通过了\u0026quot;预检\u0026quot;请求，以后每次浏览器正常的CORS请求，就都跟简单请求一样，会有一个Origin头信息字段。服务器的回应，也都会有一个Access-Control-Allow-Origin头信息字段。\n下面是\u0026quot;预检\u0026quot;请求之后，浏览器的正常CORS请求。\nPUT /cors HTTP/1.1 Origin: http://api.bob.com Host: api.alice.com X-Custom-Header: value Accept-Language: en-US Connection: keep-alive User-Agent: Mozilla/5.0... 上面头信息的Origin字段是浏览器自动添加的。\n下面是服务器正常的回应。\nAccess-Control-Allow-Origin: http://api.bob.com Content-Type: text/html; charset=utf-8 上面头信息中，Access-Control-Allow-Origin字段是每次回应都必定包含的。\n","permalink":"https://cyn-blog.pages.dev/posts/01.dev/11.network/cros/","summary":"\u003ch1 id=\"cors-跨域资源共享\"\u003e\u003cstrong\u003eCORS\u003c/strong\u003e （跨域资源共享）\u003c/h1\u003e\n\u003cp\u003eCORS是一个W3C标准，全称是\u0026quot;跨域资源共享\u0026quot;（Cross-origin resource sharing）。\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://developer.mozilla.org/zh-CN/docs/Web/Security/Same-origin_policy\"\u003e同源安全策略\u003c/a\u003e 默认阻止“跨域”获取资源。但是 CORS 给了web服务器这样的权限，即服务器可以选择，允许浏览器向跨源服务器发出请求。CORS需要浏览器和服务器同时支持。\u003c/p\u003e","title":"跨域资源共享"},{"content":"nginx(代理服务器) nginx安装（linux） 安装nginx环境依赖\n配置yum源 安装pcre 依赖、安装 openssl 、 zlib 、 gcc 依赖 nginx 官网下载软件\n使用tar -zxvf ***命令解压、./configure命令、make \u0026amp;\u0026amp; make install命令配置编译\n这里也可以指定临时文件的目录\n# 创建临时目录 mkdir -p /var/temp/nginx # 用下面的命令 ./configure \\ --prefix=/usr/local/nginx \\ --pid-path=/var/run/nginx/nginx.pid \\ --lock-path=/var/lock/nginx.lock \\ --error-log-path=/var/log/nginx/error.log \\ --http-log-path=/var/log/nginx/access.log \\ --with-http_gzip_static_module \\ --http-client-body-temp-path=/var/temp/nginx/client \\ --http-proxy-temp-path=/var/temp/nginx/proxy \\ --http-fastcgi-temp-path=/var/temp/nginx/fastcgi \\ --http-uwsgi-temp-path=/var/temp/nginx/uwsgi \\ --http-scgi-temp-path=/var/temp/nginx/scgi 启动nginx，进入目录 /usr/local/nginx/sbin中，./nginx启动nginx\n测试访问nginx linux开启80端口访问权限 vi /etc/sysconfig/iptables、打开iptables的配置文件，添加一行\n-A INPUT -m state --state NEW -m tcp -p tcp --dport 80 -j ACCEPT。\n输入service iptables restart重启服务。\n输入service iptables status，回车就会显示正在生效的规则。\n## 注意 firewalld 和 iptables 根据不同linux机器不同 # 有些人安装的linux的系统默认防火墙不是iptables,而是firewall,那就得使用以下方式关闭防火墙了。 systemctl stop firewalld.service #停止firewall systemctl disable firewalld.service #禁止firewall开机启动 ## 选择合适的方式开放端口 远程访问，ip:80端口，显示：\u0026ldquo;Welcome to nginx!\u0026quot;。安装成功 ngnix常用命令 进入目录/usr/local/nginx/sbin sudo nginx #打开 nginx nginx -s reload|reopen|stop|quit #重新加载配置|重启|停止|退出 nginx nginx -t #测试配置是否有语法错误 nginx [-?hvVtq] [-s signal] [-c filename] [-p prefix] [-g directives] -?,-h : 打开帮助信息 -v : 显示版本信息并退出 -V : 显示版本和配置选项信息，然后退出 -t : 检测配置文件是否有语法错误，然后退出 -q : 在检测配置文件期间屏蔽非错误信息 -s signal : 给一个 nginx 主进程发送信号：stop（停止）, quit（退出）, reopen（重启）, reload（重新加载配置文件） -p prefix : 设置前缀路径（默认是：/usr/local/Cellar/nginx/1.2.6/） -c filename : 设置配置文件（默认是：/usr/local/etc/nginx/nginx.conf） -g directives : 设置配置文件外的全局指令 注意：执行./nginx启动nginx，这里可以-c指定加载的nginx配置文件，如下： ./nginx -c /usr/local/nginx/conf/nginx.conf 如果不指定-c，nginx在启动时默认加载conf/nginx.conf文件，此文件的地址也可以在编译安装nginx时指定./configure的参数（--conf-path= 指向配置文件（nginx.conf）） #启动命令 ./nginx # 停止命令 ./nginx -s stop 或者全部停止 ./nginx -s quit #重启ngnix ./ngnix -s reload #查看ngnix 状态 netstat -tupln | grep ngnix nginx配置文件 配置文件目录/usr/local/nginx/conf下的nginx.conf\n包含三部分内容\n全局块：配置服务器整体运行的配置指令 比如 worker_processes 1; 处理并发数的配置\nevents 块 ：影响 Nginx 服务器与用户的网络连接 比如 worker_connections 1024; 支持的最大连接数为 1024\nhttp 块\nNginx的HTTP配置主要包括三个区块，结构如下： http { //这个是协议级别 include mime.types; default_type application/octet-stream; keepalive_timeout 65; gzip on; server { //这个是服务器级别 listen 80; server_name localhost; location / { //这个是请求级别 root html; index index.html index.htm; } } } 还包含两部分： http 全局块 server 块\n反向代理Demo 代理：\n在Java设计模式中，代理模式是这样定义的：给某个对象提供一个代理对象，并由代理对象控制原对象的引用。\n在举一个现实生活中的例子：比如我们要买一间二手房，虽然我们可以自己去找房源，但是这太花费时间精力了，而且房屋质量检测以及房屋过户等一系列手续也都得我们去办，再说现在这个社会，等我们找到房源，说不定房子都已经涨价了，那么怎么办呢？最简单快捷的方法就是找二手房中介公司（为什么？别人那里房源多啊），于是我们就委托中介公司来给我找合适的房子，以及后续的质量检测过户等操作，我们只需要选好自己想要的房子，然后交钱就行了。\n代理简单来说，就是如果我们想做什么，但又不想直接去做，那么这时候就找另外一个人帮我们去做。那么这个例子里面的中介公司就是给我们做代理服务的，我们委托中介公司帮我们找房子。\n正向代理代理客户端，反向代理代理服务器：\n即正向代理时在客户端运行，反向代理时在服务器上运行\n反向代理，其实客户端对代理是无感知的，因为客户端不需要任何配置就可以访问，我们只需要将请求发送到反向代理服务器，由反向代理服务器去选择目标服务器获取数据后，在返回给客户端，此时反向代理服务器和目标服务器对外就是一个服务器，暴露的是代理服务器地址，隐藏了真实服务器IP地址。\nDemo1 预期效果：打开浏览器，在浏览器地址栏输入地址 www.chenyn.com ，跳转到 liunx 系统 tomcat 主页 面中\n配置启动tomcat\n解压tomcat，进入bin下运行程序 开放8080端口 配置域名映射ip\n进入windows的 系统盘/windows/system32/drivers/etc 编辑hosts，新增192.168.209.128 www.chenyn.com 配置nginx.conf\n修改http配置块下面的server块的location / 块 location / { root html; proxy_pass http://127.0.0.1:8080; index index.html index.htm; } 访问www.chenyn.com:80 可以访问到tomcat页面\nDemo2 预取效果：使用 nginx 反向代理，根据访问的路径跳转到不同端口的服务中、nginx 监听端口为 9001\n访问 http:// 192.168.209.128 :9001/edu/ 直接跳转到 127.0.0.1:8080 访问 http:// 192.168.209.128 :9001/vod/ 直接跳转到 127.0.0.1:8081\n准备两个 tomcat 服务器，一个 8080 端口，一个 8081 端口、创建文件夹和测试页面\n开放端口8080、8081、9001\n配置nginx.conf，修改http配置块下面的server块的location / 块\nlocation ~ /edu/ { root html; proxy_pass http://127.0.0.1:8080; index index.html index.htm; } location ~ /vod/ { proxy_pass http://127.0.0.1:8081; } 访问192.168.209.128:9001/edu/test.html和192.168.209.128:9001/vod/test.html\n会页面会跳转到8080 和8081 服务器\n负载均衡Demo 预期效果：浏览器地址栏输入地址 http://192.168.17.129:9001/edu/test.html ，负载均衡效果，平均 8080 和 8081 端口中\n启动两台tomcat,8080\\8081\n配置nginx.conf\\修改http配置块，注意多个location会导致负载均衡失效\nupstream myserver { ip_bash; server 127.0.0.1:8080 weight=1; server 127.0.0.1:8081 weight=1; } server { listen 80; server_name localhost; location / { proxy_pass http://myserver; proxy_set_header Host $host; } } 注意浏览器的缓存，尽量用chrome 的无痕模式访问。\n负载均衡策略\n1、轮询（默认） 每个请求按时间顺序逐一分配到不同的后端服务器，如果后端服务器 down 掉，能自动剔除。 2、权重-weight weight 代表权重默认为 1, 权重越高被分配的客户端越多 3、ip_hash（session共享问题） 每个请求按访问 ip 的 hash 结果分配，这样每个访客固定访问一个后端服务器 4、 fair （第三方） 按后端服务器的响应时间来分配请求，响应时间短的优先分配。 动静分离Demo Nginx 动静分离简单来说就是把动态跟静态请求分开，不能理解成只是单纯的把动态页面和静态页面物理分离。严格意义上说应该是动态请求跟静态请求分开，可以理解成使用Nginx 处理静态页面，Tomcat处理动态页面\n一种是纯粹把静态文件独立成单独的域名，放在独立的服务器上，也是目前主流推崇的方案；\n另外一种方法就是动态跟静态文件混合在一起发布，通过 nginx 来分开。 通过 location 指定不同的后缀名实现不同的请求转发\n服务器上新增静态资源文件夹\n配置nginx.conf\nlocation /www/ { root /home/chenyn/data/; } location /image/ { root /home/chenyn/data/; ## 显示文件目录 autoindex on; } root 详解\nlocation ^~ /t/ { root /www/root/html/; } 如果一个请求的URI是/t/a.html时，web服务器将会返回服务器上的/www/root/html/t/a.html的文件。\n8 nginx 配置高可用的集群 待新增\n9 nginx原理 master-workers 的机制的好处 首先，对于每个 worker 进程来说，独立的进程，不需要加锁，所以省掉了锁带来的开销， 同时在编程以及问题查找时，也会方便很多。其次，采用独立的进程，可以让互相之间不会 影响，一个进程退出后，其它进程还在工作，服务不会中断， master 进程则很快启动新的 worker 进程。当然， worker 进程的异常退出，肯定是程序有 bug 了，异常退出，会导致当 前 worker 上的所有请求失败，不过不会影响到所有请求，所以降低了风险。 需要设置多少个 worker Nginx 同 redis 类似都采用了 io 多路复用机制，每个 worker 都是一个独立的进程，但每个进 程里只有一个主线程，通过异步非阻塞的方式来处理请求， 即使是千上万个请求也不在话 下。每个 worker 的线程可以把一个 cpu 的性能发挥到极致。所以 worker 数和服务器的 cpu 数相等是最为适宜的。设少了会浪费 cpu ，设多了会造成 cpu 频繁切换上下文带来的损耗。 ","permalink":"https://cyn-blog.pages.dev/posts/01.dev/11.network/nginx/","summary":"\u003ch1 id=\"nginx代理服务器\"\u003enginx(代理服务器)\u003c/h1\u003e\n\u003ch2 id=\"nginx安装linux\"\u003enginx安装（linux）\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e安装nginx环境依赖\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e配置yum源\u003c/li\u003e\n\u003cli\u003e安装pcre 依赖、安装 openssl 、 zlib 、 gcc 依赖\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e\u003ca href=\"http://nginx.org/\"\u003enginx 官网下载软件\u003c/a\u003e\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e使用\u003ccode\u003etar -zxvf ***\u003c/code\u003e命令解压、\u003ccode\u003e./configure\u003c/code\u003e命令、\u003ccode\u003emake \u0026amp;\u0026amp; make install\u003c/code\u003e命令配置编译\u003c/p\u003e","title":"nginx(代理服务器)"},{"content":"FastDFS(分布式文件系统) 1 什么是FastDFS 1.1 简介 FastDFS是用c语言编写的一款开源的分布式文件系统，它是由淘宝资深架构师余庆编写并开源。FastDFS专为互联网量身定制，充分考虑了冗余备份、负载均衡、线性扩容等机制，并注重高可用、高性能等指标，使用FastDFS很容易搭建一套高性能的文件服务器集群提供文件上传、下载等服务。\n为什么要使用FastDFS呢？\n已有的的NFS、GFS都是通用的分布式文件系统，通用的分布式文件系统的优点的是开发体验好，但是系统复杂性高、性能一般，而专用的分布式文件系统虽然开发体验性差，但是系统复杂性低并且性能高。\nFastDFS非常适合存储图片等那些小文件，FastDFS不对文件进行分块，所以它就没有分块合并的开销，FastDFS网络通信采用socket，通信速度很快。\n1.2 工作原理 1.2.1 FastDSF架构 FastDFS架构包括 Tracker server和Storag eserver。客户端请求Tracker server进行文件上传、下载，通过Tracker server调度最终由Storage server完成文件上传和下载。\nTracker\nTracker Server作用是负载均衡和调度，通过Tracker server在文件上传时可以根据一些策略找到Storage server提供文件上传服务。可以将Tracker称为追踪服务器或调度服务器。\nTracker server更像一个管理指挥员，管理Storage server，协调客户机将文件上传到Storage Server\nFastDFS集群中的Tracker server可以有多台，Tracker server之间是相互平等关系同时提供服务，Tracker server不存在单点故障。客户端请求racker server采用轮询方式，如果请求的tracker无法提供服务则换另一个tracker。\nStorage\nStorage Server作用是文件存储，客户端上传的文件最终存储在Storage服务器上，Storage server没有实现自己的文件系统而是使用操作系统的文件系统来管理文件。可以将storage称为存储服务器。\nStorage集群采用了分组存储方式。storage集群由一个或多个组构成，集群存储总容量为集群中所有组的存储容量之和。一个组由一台或多台存储服务器组成，组内的Storage server之间是平等关系，不同组的Storage server之间不会相互通信，同组内的Storage server之间会相互连接进行文件同步，从而保证同组内每个storage上的文件完 全一致的。一个组的存储容量为该组内存储服务器容量最小的那个，由此可见组内存储服务器的软硬件配置最好是一致的。\n采用分组存储方式的好处是灵活、可控性较强。比如上传文件时，可以由客户端直接指定上传到的组也可以由tracker进行调度选择。一个分组的存储服务器访问压力较大时，可以在该组增加存储服务器来扩充服务能力（纵向扩容）。当系统容量不足时，可以增加组来扩充存储容量（横向扩容）。\nStorage状态收集\nStorage server会连接集群中所有的Tracker server，定时向他们报告自己的状态，包括磁盘剩余空间、文件同步状况、文件上传下载次数等统计信息。\n1.2.2 文件上传流程 客户端上传文件后存储服务器将文件ID返回给客户端，此文件ID用于以后访问该文件的索引信息。文件索引信息包括：组名，虚拟磁盘路径，数据两级目录，文件名。\ngroup1/M00/00/00/wKjRgF3PPvOAOF7HAAFl33KnvNs832.jpg 组名：文件上传后所在的storage组名称，在文件上传成功后有storage服务器返回，需要客户端自行保存。 虚拟磁盘路径：storage配置的虚拟路径，与磁盘选项store_path*对应。如果配置了store_path0则是M00，如果配置了store_path1则是M01，以此类推。 数据两级目录：storage服务器在每个虚拟磁盘路径下创建的两级目录，用于存储数据文件 文件名：与文件上传时不同。是由存储服务器根据特定信息生成，文件名包含：源存储服务器IP地址、文件创 建时间戳、文件大小、随机数和文件拓展名等信息。 1.2.3 文件下载流程 tracker根据请求的文件路径即文件ID 来快速定义文件。\n比如请求下边的文件：\ngroup1/M00/00/00/wKjRgF3PPvOAOF7HAAFl33KnvNs832.jpg 通过组名tracker能够很快的定位到客户端需要访问的存储服务器组是group1，并选择合适的存储服务器提供客户端访问。 存储服务器根据“文件存储虚拟磁盘路径”和“数据文件两级目录”可以很快定位到文件所在目录，并根据文件名找到 客户端需要访问的文件。 2 FastDFS入门 2.1 FastDFS安装配置(tracker) 注：本次之安装一台tracker、storage,且都在同一台机器上、方便调试。有兴趣的可以拓展成分布式\n2.1.1 安装条件 需要一台CentOS7虚拟机 下载FastDFS安装包，地址：https://github.com/happyfish100/FastDFS ，本文档使用FastDFS_v5.05.tar.gz 2.1.2 准备安装环境 FastDFS是C语言开发，编译依赖gcc环境，安装 gcc\nyum -y install gcc-c++ 依赖依赖libevent库\nyum -y install libevent libfastcommon是FastDFS官方提供的，libfastcommon包含了FastDFS运行所需要的一些基础库。本文档使用的是libfastcommonV1.0.7.tar.gz,下载地址：https://github.com/happyfish100/libfastcommon/releases\n# 将libfastcommonV1.0.7.tar.gz拷贝至/usr/local/下 cd /usr/local tar -zxvf libfastcommonV1.0.7.tar.gz cd libfastcommon-1.0.7 ./make.sh ./make.sh install 注意：libfastcommon安装好后会自动将库文件拷贝至/usr/lib64下，由于FastDFS程序引用usr/lib目录所以需要将/usr/lib64下的库文件拷贝至/usr/lib下。\n# 这里要根据linux 32还是64位的实际调整 cp /usr/lib64/libfastcommon.so /usr/lib/libfastcommon.so 2.1.3 安装FastDFS # 将FastDFS_v5.05.tar.gz拷贝至/usr/local/下 tar -zxvf FastDFS_v5.05.tar.gz cd FastDFS ./make.sh ./make.sh install # 安装成功将安装目录下的conf下的文件拷贝到/etc/fdfs/下。 2.1.4 配置FastDFS(tracker) 安装成功后进入/etc/fdfs目录\n# 拷贝一份新的tracker配置文件： cp tracker.conf.sample tracker.conf # 修改tracker.conf vi tracker.conf # base_path=/home/yuqing/FastDFS # 改为：（根据个人具体情况调整） # base_path=/home/FastDFS # 配置http端口： http.server_port=80 2.1.5 启动Tracker # 注意控制台的打印信息（注意启动顺序-tracker-storage） /usr/bin/fdfs_trackerd /etc/fdfs/tracker.conf restart 2.2 FastDFS安装配置(storage) 2.2.1 安装过程 同 Tracker\n2.2.2 配置FastDFS(Storage) 安装成功后进入/etc/fdfs目录\n# 拷贝一份新的storage配置文件： cp storage.conf.sample storage.conf # 修改storage.conf vi storage.conf # group_name=group1 # base_path=/home/yuqing/FastDFS 改为：base_path=/home/FastDFS（根据个人具体情况调整） # store_path0=/home/yuqing/FastDFS 改为：store_path0=/home/FastDFS/fdfs_storage（根据个人具体情况调整） #如果有多个挂载磁盘则定义多个store_path，如下（根据个人具体情况调整） #store_path1=..... #store_path2=...... tracker_server=192.168.101.3:22122 #配置tracker服务器:IP #如果有多个则配置多个tracker tracker_server=192.168.101.4:22122 #配置http端口 http.server_port=80 2.2.3 启动Storage # 注意控制台的打印信息（注意启动顺序-tracker-storage） /usr/bin/fdfs_storaged /etc/fdfs/storage.conf restart 2.3 测试FastDFS是否安装成功 2.3.1 上传图片测试 FastDFS安装成功可通过/usr/bin/fdfs_test测试上传、下载等操作。\n修改/etc/fdfs/client.conf\ntracker_server根据自己部署虚拟机的情况/配置\n# 拷贝一份新的client配置文件： cp client.conf.sample client.conf # 修改配置文件 vi client.conf # base_path=/home/fastdfs # tracker_server=192.168.101.3:22122 使用格式：\n/usr/bin/fdfs_test 客户端配置文件地址 upload 上传文件\n比如将/home下的图片上传到FastDFS中：\n/usr/bin/fdfs_test /etc/fdfs/client.conf upload /home/tomcat.png 看到如下日志：\nThis is FastDFS client test program v5.05 Copyright (C) 2008, Happy Fish / YuQing FastDFS may be copied only under the terms of the GNU General Public License V3, which may be found in the FastDFS source kit. Please visit the FastDFS Home Page http://www.csource.org/ for more detail. [2019-11-18 22:56:28] DEBUG - base_path=/home/chenyn/fastdfs/FastDFS, connect_timeout=30, network_timeout=60, tracker_server_count=1, anti_steal_token=0, anti_steal_secret_key length=0, use_connection_pool=0, g_connection_pool_max_idle_time=3600s, use_storage_id=0, storage server id count: 0 tracker_query_storage_store_list_without_group: server 1. group_name=, ip_addr=192.168.127.128, port=23000 group_name=group1, ip_addr=192.168.127.128, port=23000 storage_upload_by_filename group_name=group1, remote_filename=M00/00/00/wKh_gF3SsRyAJg6iAABlnZYG9PM641.png source ip address: 192.168.127.128 file timestamp=2019-11-18 22:56:28 file size=26013 file crc32=2517038323 example file url: http://192.168.127.128/group1/M00/00/00/wKh_gF3SsRyAJg6iAABlnZYG9PM641.png storage_upload_slave_by_filename group_name=group1, remote_filename=M00/00/00/wKh_gF3SsRyAJg6iAABlnZYG9PM641_big.png source ip address: 192.168.127.128 file timestamp=2019-11-18 22:56:29 file size=26013 file crc32=2517038323 example file url: http://192.168.127.128/group1/M00/00/00/wKh_gF3SsRyAJg6iAABlnZYG9PM641_big.png 恭喜你成功了:slightly_smiling_face:~\n2.3.2 下载图片 http://192.168.127.128/group1/M00/00/00/wKh_gF3SsRyAJg6iAABlnZYG9PM641_big.png # 此路径就是文件的下载路径 对应storage服务器上的 /home/chenyn/fastdfs/FastDFS/storage/data/00/00/wKh_gF3SsRyAJg6iAABlnZYG9PM641_big.png文件。 # 由于现在还没有和nginx整合无法使用http下载。待新增 3 SpringBoot整合FastDFS实现文件上传下载测试 使用javaApi测试文件的上传。 java版本的fastdfs-client地址在：https://github.com/happyfish100/fastdfs-client-java，参考此工程编写测试用 例。\n3.1 环境搭建 新建maven工程\n添加依赖\n\u0026lt;parent\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-parent\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;2.1.9.RELEASE\u0026lt;/version\u0026gt; \u0026lt;relativePath/\u0026gt; \u0026lt;!-- lookup parent from repository --\u0026gt; \u0026lt;/parent\u0026gt; \u0026lt;groupId\u0026gt;cn.itcast.javaee\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;fastdfs\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.0‐SNAPSHOT\u0026lt;/version\u0026gt; \u0026lt;dependencies\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-web\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-test\u0026lt;/artifactId\u0026gt; \u0026lt;scope\u0026gt;test\u0026lt;/scope\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;commons-io\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;commons-io\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;2.4\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;!-- https://mvnrepository.com/artifact/net.oschina.zcx7878/fastdfs-client-java --\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;net.oschina.zcx7878\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;fastdfs-client-java\u0026lt;/artifactId\u0026gt; \u0026lt;version\u0026gt;1.27.0.0\u0026lt;/version\u0026gt; \u0026lt;/dependency\u0026gt;\t\u0026lt;/dependencies\u0026gt; resources目录下新建配置文件fastdfs-client.properties\nfastdfs.connect_timeout_in_seconds = 5 fastdfs.network_timeout_in_seconds = 30 fastdfs.charset = UTF‐8 fastdfs.http_anti_steal_token = false fastdfs.http_secret_key = FastDFS1234567890 fastdfs.http_tracker_http_port = 80 # 根据实际情况调整 fastdfs.tracker_servers = 192.168.101.64:22122 3.2 文件上传、查询、下载 /** * 文件上传 */ @Test public void fileUpload() { System.out.println(\u0026#34;java.version=\u0026#34; + System.getProperty(\u0026#34;java.version\u0026#34;)); try { // 加载配置文件 //ClientGlobal.init(conf_filename); ClientGlobal.initByProperties(\u0026#34;fastdfs-client.properties\u0026#34;); System.out.println(\u0026#34;network_timeout=\u0026#34; + ClientGlobal.g_network_timeout + \u0026#34;ms\u0026#34;); System.out.println(\u0026#34;charset=\u0026#34; + ClientGlobal.g_charset); //创建tracker客户端 TrackerClient tracker = new TrackerClient(); TrackerServer trackerServer = tracker.getConnection(); StorageServer storageServer = null; //定义一个storage客户端 StorageClient1 client = new StorageClient1(trackerServer, storageServer); //文件元信息 NameValuePair[] metaList = new NameValuePair[1]; metaList[0] = new NameValuePair(\u0026#34;fileName\u0026#34;, \u0026#34;C:\\\\Users\\\\hspcadmin\\\\Desktop\\\\cat.jpg\u0026#34;); //执行上传 String fileId = client.upload_file1(\u0026#34;C:\\\\Users\\\\hspcadmin\\\\Desktop\\\\cat.jpg\u0026#34;, \u0026#34;jpg\u0026#34;, metaList); System.out.println(\u0026#34;upload success. file id is: \u0026#34; + fileId); //关闭tracker服务 trackerServer.close(); } catch (Exception ex) { ex.printStackTrace(); } } /** * 查询文件 */ @Test public void queryFile(){ System.out.println(\u0026#34;java.version=\u0026#34; + System.getProperty(\u0026#34;java.version\u0026#34;)); try { // 加载配置文件 //ClientGlobal.init(conf_filename); ClientGlobal.initByProperties(\u0026#34;fastdfs-client.properties\u0026#34;); System.out.println(\u0026#34;network_timeout=\u0026#34; + ClientGlobal.g_network_timeout + \u0026#34;ms\u0026#34;); System.out.println(\u0026#34;charset=\u0026#34; + ClientGlobal.g_charset); //创建tracker客户端 TrackerClient tracker = new TrackerClient(); TrackerServer trackerServer = tracker.getConnection(); StorageServer storageServer = null; //定义一个storage客户端 StorageClient1 client = new StorageClient1(trackerServer, storageServer); //查询文件 FileInfo fileInfo = client.query_file_info(\u0026#34;group1\u0026#34;,\u0026#34;M00/00/00/wKjRgF3PSxiAHuHtAAFl33KnvNs253.jpg\u0026#34;); FileInfo fileInfo1 = client.query_file_info1(\u0026#34;group1/M00/00/00/wKjRgF3PSxiAHuHtAAFl33KnvNs253.jpg\u0026#34;); System.out.println(fileInfo1); //查询文件元信息 NameValuePair[] fileInfos = client.get_metadata1(\u0026#34;group1/M00/00/00/wKjRgF3PSxiAHuHtAAFl33KnvNs253.jpg\u0026#34;); System.out.println(fileInfos); //关闭tracker服务 trackerServer.close(); } catch (Exception ex) { ex.printStackTrace(); } } /** * 文件下载 */ @Test public void fileDownload(){ try { // 加载配置文件 //ClientGlobal.init(conf_filename); ClientGlobal.initByProperties(\u0026#34;fastdfs-client.properties\u0026#34;); System.out.println(\u0026#34;network_timeout=\u0026#34; + ClientGlobal.g_network_timeout + \u0026#34;ms\u0026#34;); System.out.println(\u0026#34;charset=\u0026#34; + ClientGlobal.g_charset); //创建tracker客户端 TrackerClient tracker = new TrackerClient(); TrackerServer trackerServer = tracker.getConnection(); StorageServer storageServer = null; //定义一个storage客户端 StorageClient1 client = new StorageClient1(trackerServer, storageServer); //下载文件 byte[] fileBytes = client.download_file1(\u0026#34;group1/M00/00/00/wKjRgF3PSxiAHuHtAAFl33KnvNs253.jpg\u0026#34;); File file = new File(\u0026#34;D:/chenyn.jpg\u0026#34;); FileOutputStream fileOutputStream = new FileOutputStream(file); fileOutputStream.write(fileBytes); fileOutputStream.close(); //关闭tracker服务 trackerServer.close(); } catch (Exception ex) { ex.printStackTrace(); } } 1.如果有发生socker连接超时，注意查看linux机器的防火墙是否关闭\n2.如果发现上传文件之后，文件目录下有-m，-big文件等，这些文件是上传文件的元数据\n# 或者端口是否开放 vi /etc/sysconfig/iptables # -A INPUT -m state --state NEW -m tcp -p tcp --dport 80 -j ACCEPT # 查看开放的端口数量 service iptables status # 查看元数据文件, 是 参数author,height,width的键值对 vi test.png.m # 关闭元数据上传 # 在调用client.upload_file()时，meta_list入参传null即可 4 文件服务案例 4.1 需求 上传页面上传图片到fastDFS文件系统。\n进入上传页面，点击上传图片，选择本地图片 选择本地图片后，进行上传，前端浏览器会通过http调用文件管理服务的文件上传接口进行上传 文件管理服务通过socket请求FastDFS文件系统的上传图片，最终图片保存在FastDFS文件系统中的storage server中。 浏览上传的图片\n进入视频下载页面\n前端浏览器通过图片地址请求图片服务器代理nginx\n图片服务器代理根据负载情况将图片浏览请求转发到一台storage server\nstorage server找到图片后通过nginx将图片响应给网友\n4.2 安装 FastDFS、Nginx 4.2.1 安装FastDFS 参上\n4.2.2 安装Nginx 首先参考nginx基础入门，了解和搭建nginx服务器\n4.2.3 安装 FastDFS-nginx-module 本文使用的是 FastDFS-nginx-module_v1.16.tar.gz\n参考github地址：https://github.com/happyfish100/fastdfs-nginx-module\n注意：要先关闭所有的nginx进程，再进行下面的步骤\n调整 fastdfs-nginx-module配置\n# 调整 mod_fastdfs.conf配置 # 进入fastdfs-nginx-module解压目录 cd /home/chenyn/fastdfs/fastdfs-nginx-module/src # 复制mod_fastdfs.conf复制到/etc/fdfs/里面 cp mod_fastdfs.conf /etc/fdfs/ # 调整mod_fastdfs.conf配置，根据实际情况调整路径 # 更改 base_path=/home/chenyn/fastdfs/FastDFS # 更改 tracker_server=192.168.209.128:22122 # 更改 store_path0=/home/chenyn/fastdfs/FastDFS/storage # 调整fastdfs-nginx-module的config文件 vi config #（这一步很重要，很重要，很重要（重要的事情说三遍） # CORE_INCS=\u0026#34;$CORE_INCS /usr/local/include/fastdfs /usr/local/include/fastcommon/\u0026#34; --删除local # CORE_LIBS=\u0026#34;$CORE_LIBS -L/usr/local/lib -lfastcommon -lfdfsclient\u0026#34; --删除local 重新编译nginx\n# 进入解压的nginx目录（根据个人情况调整目录结构） cd /home/chenyn/nginx/nginx-1.17.5 # 配置FastDFS-nginx-module 到nginx中 # 创建临时目录 mkdir -p /var/temp/nginx # 用下面的命令 ./configure \\ --add-module=/home/chenyn/fastdfs/fastdfs-nginx-module/src # (/home/chenyn/fastdfs/fastdfs-nginx-module/src根据自己的文件目录来配) # 重新编译 make make install 4.2.4 配置nginx 只有一个group时，最简单的配置：当mod_fastdfs.conf 配置文件中只有一个group1, 且配置了url_have_group_name = false时，即访问地址不使用分组名称，那么只需在nginx的配置文件中增加以下配置即可:（不推荐）\n# 进入nginx配置目录 cd /usr/local/nginx/conf #在nginx.conf里面的server{里面添加location /M00……}，添加下面的几行： location /M00 { root /home/chenyn/fastdfs/FastDFS/storage/data; ngx_fastdfs_module; } #配置负载均衡 #storage群group1组 upstream storage_server_group1{ server 192.168.101.5:80 weight=10; server 192.168.101.6:80 weight=10; } #storage群group2组 upstream storage_server_group2{ server 192.168.101.7:80 weight=10; server 192.168.101.8:80 weight=10; } 多个group配置，当配置多个组，且mod_fastdfs.conf 里面指定了url_have_group_name= true 时，配置方式:（建议即使用的是单个group，也按该方法配置）\nlocation ~ /group([0-9]) /M00 { root /home/chenyn/fastdfs/FastDFS/storage/data; ngx_fastdfs_module; } # 比如:在group1上的 nginx 的nginx.conf 配置是 location /group1/M00 { root /home/chenyn/fastdfs/FastDFS/storage/data; ngx_fastdfs_module; } # 比如:在group2上的 nginx 的nginx.conf 配置是 location /group2/M00 { root /home/chenyn/fastdfs/FastDFS/storage/data; ngx_fastdfs_module; } 创建软连接\n# 根据实际存储位置调整路径 # 创建软连接的目的是为了将M00虚拟路径转化一下 ln -s /home/chenyn/fastdfs/FastDFS/storage/data /home/chenyn/fastdfs/FastDFS/storage/data/M00 调整FastDFS配置\n# 进入 FastDFS解压目录 cd /home/chenyn/fastdfs/FastDFS/conf # 移动配置文件 cp http.conf /etc/fdfs/ cp mime.types /etc/fdfs/ 启动FastDFS、nginx\n# 启动Tracker /usr/bin/fdfs_trackerd /etc/fdfs/tracker.conf restart # 启动Storage /usr/bin/fdfs_storaged /etc/fdfs/storage.conf restart # 启动nginx cd /usr/local/nginx/sbin ./nginx -s restart 4.3 通过HTTP访问FastDFS图片 http://192.168.209.128/group1/M00/00/00/wKjRgF3PSxiAHuHtAAFl33KnvNs253.jpg 如果可以正常查看或者下载，恭喜配置成功:kissing_smiling_eyes:\n如果失败，可以仔细查看一下上面的命令和配置，注意命令尽量不要复制，可能会有问题:relaxed:\n4.4 安装 FastDHT实现文件去重处理 FastDHT是一个高性能的分布式哈希系统，它是基于键值对存储的，而且它需要依赖于Berkeley DB作为数据存储的媒介，同时需要依赖于libfastcommon。\nFastDHT集群由一个或者多个组group组成，同组服务器上存储的数据是相同的，数据同步只在组的服务器之间进行；组内各个服务是对等的，对数据进行存取时，可以根据 key的hash值来决定使用哪台机器。\n4.4.1 安装Berkeley DB 在安装FastDHT之前，需要首先安装Berkeley DB，也需要安装libfastcommon，不过这安装FastDFS时已经安装了libfastcommon，所以这里省略。\n# 下载 berkeley-db cd /home/chenyn/fastdfs wget http://download.oracle.com/berkeley-db/db-4.7.25.tar.gz # 解压 tar -zxvf db-4.7.25.tar.gz # 进入解压目录 cd db-4.7.25/build_unix # 验证安装环境（安装到了/usr目录下） sudo ../dist/configure --prefix=/usr # 编译，从Makefile中读取指令，然后编译 sudo make # 安装，从Makefile中读取指令，安装到指定位置 sudo make install 4.4.2 安装FastDHT cd /home/chenyn/fastdfs # wget下载FastDHT安装包 （可以根据实际情况选择版本） wget http://sourceforge.net/projects/fastdht/files/FastDHT%20server%20and%20php%20ext/FastDHT%20Server%20Source%20Code%20V2.01/FastDHT_v2.01.tar.gz # 解压 tar –zxvf FastDHT_v2.01.tar.gz cd FastDHT # 编译之前需要修改make.sh vi make.sh # 文件中的 “CFLAGS=\u0026#39;-Wall -D_FILE_OFFSET_BITS=64 -D_GNU_SOURCE\u0026#39;” 修改为 “CFLAGS=\u0026#39;-Wall -D_FILE_OFFSET_BITS=64 -D_GNU_SOURCE -I/usr/include/ -L/usr/lib/\u0026#39;” （-I/usr/include/ -L/usr/lib/）这里对应上面Berkeley的安装目录 sudo ./make.sh sudo ./make.sh install 4.4.3 配置FastDHT 先确认目录/data/fastdht/已创建，如果没有创建，执行以下命令创建目录：\nmkdir -p /data/fastdht # 配置fdht_client.conf文件 cd /etc/fdht vi fdht_client.conf # 修改下面配置 base_path=/data/fastdht keep_alive=1 #include /etc/fdht/fdht_servers.conf ---该配置不是注释，一定要加!!! # 配置fdht_servers.conf vi fdht_servers.conf # 根据fdht的机器数量调整，下面为1台参考配置 group_count=1 group0=192.168.209.128;11411 # 配置fdhtd.conf vi fdhtd.conf port=11411 bash_path=/data/fastdht cache_size=32MB #include /etc/fdht/fdht_servers.conf ---该配置不是注释，一定要加!!! # 配置fastdfs的storage.conf vi /etc/fdfs/storage.conf # 是否检查上传文件已经存在。如果已经存在，则建立一个索引链接以节省空间 check_file_duplicate=1 # check_file_duplicate=1时，FastDHT的命名空间 key_namespace=FastDFS # 长连接配置选项，0为短连接 1为长连接 keep_alive=1 #include /etc/fdht/fdht_servers.conf ---该配置不是注释，一定要加!!! 4.4.4 启动FastDHT 启动前关闭防火墙，或者开启端口（11411）\nvi /etc/sysconfig/iptables 添加如下端口行： # FastDHT Port -A INPUT -m state --state NEW -m tcp -p tcp --dport 11411 -j ACCEPT # 修改之后重启防火墙： service iptables restart # 之后，就可以直接启动FastDHT了。 /usr/local/bin/fdhtd /etc/fdht/fdhtd.conf # 可以监控11411端口号的使用情况，确认是否启动成功。 netstat –unltp | grep 11411 4.4.5 上传测试 使用3.2节的上传方法上传重复文件，进入/M00/00/00 查看\n出现上图，软连接指向，恭喜~~:smiley:\n4.5 常见错误 配置FastDFS-nginx-moudle发生nginx访问不了的情况，查看nginx启动日志，按错误信息排查\n#Nginx 服务器启动失败的，错误信息：trunk_shared.c, line: 177, \u0026#34;Permission denied\u0026#34; can\u0026#39;t be accessed 只需修改Nginx配置文件，输入命令 “ vi /usr/local/nginx/conf/nginx.conf ”,在配置文件的开头加入 “ user root; ” 即可 # nginx日志报错ERROR - file: ../common/fdfs_global.c, line: 52, the format of filename vi /etc/fdfs/mod_fastdfs.conf 将 url_have_group_name=false 改为 url_have_group_name=true # 其他常见问题,或者根据日志错误信息找百度 http://www.mamicode.com/info-detail-1992668.html 启动fdht报错：error while loading shared libraries: xxx.so.0:cannot open shared object file: No such file or directory\n出现这类错误表示，系统不知道xxx.so放在哪个目录下，这时候就要在/etc/ld.so.conf中加入xxx.so所在的目录。 一般而言，有很多的so会存放在/usr/local/lib这个目录底下，去这个目录底下找，果然发现自己所需要的.so文件。 所以，在/etc/ld.so.conf中加入/usr/local/lib这一行，保存之后，再运行：/sbin/ldconfig –v 更新一下配置即可。 ","permalink":"https://cyn-blog.pages.dev/posts/01.dev/10.storage/fastdfs/","summary":"\u003ch1 id=\"fastdfs分布式文件系统\"\u003eFastDFS(分布式文件系统)\u003c/h1\u003e\n\u003ch2 id=\"1--什么是fastdfs\"\u003e1  什么是FastDFS\u003c/h2\u003e\n\u003ch3 id=\"11--简介\"\u003e1.1  简介\u003c/h3\u003e\n\u003cp\u003e\u003ccode\u003eFastDFS\u003c/code\u003e是用c语言编写的一款开源的分布式文件系统，它是由淘宝资深架构师余庆编写并开源。\u003ccode\u003eFastDFS\u003c/code\u003e专为互联网量身定制，充分考虑了冗余备份、负载均衡、线性扩容等机制，并注重高可用、高性能等指标，使用\u003ccode\u003eFastDFS\u003c/code\u003e很容易搭建一套高性能的文件服务器集群提供文件上传、下载等服务。\u003c/p\u003e","title":"FastDFS(分布式文件系统)"},{"content":"如何使用vuepress玩转blog 环境：node.js 编码工具：vscode vuepress官网\n1 搭建环境 1.1 全局安装Vuepress yarn global add vuepress # 或者：npm install -g vuepress 1.2 项目初始化 新建blog项目文件夹（注意该目录为blog项目的主文件夹）\n可以使用命令新建文件夹，也可以手工创建\nmkdir project 进入到project文件夹中，使用命令行初始化项目:\nyarn init -y # 或者 npm init -y 将会创建一个package.json文件，长这样子：\n{ \u0026#34;name\u0026#34;: \u0026#34;project\u0026#34;, \u0026#34;version\u0026#34;: \u0026#34;1.0.0\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;main\u0026#34;: \u0026#34;index.js\u0026#34;, \u0026#34;scripts\u0026#34;: { \u0026#34;test\u0026#34;: \u0026#34;echo \\\u0026#34;Error: no test specified\\\u0026#34; \u0026amp;\u0026amp; exit 1\u0026#34; }, \u0026#34;keywords\u0026#34;: [], \u0026#34;author\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;license\u0026#34;: \u0026#34;ISC\u0026#34; } 在project根目录下新建docs文件夹 mkdir docs # 这个会作为项目文档的根目录来使用 在docs文件夹创建.vuepress文件夹 mkdir .vuepress # 所有vuepress的菜单配置文件、自动生成文件、静态资源都在本目录下 在.vuepress下面创建config.js文件 touch config.js # config.js是VuePress必要的配置文件，它导出一个javascript对象。 先简单配置config.js\nmodule.exports = { title: \u0026#39;Hello VuePress\u0026#39;, description: \u0026#39;Just playing around\u0026#39; } 在.vuepress下面创建public文件夹 mkdir public # 这个文件夹是用来放置静态资源的，打包出来之后会放在.vuepress/dist/的根目录。 创建首页 在docs文件夹下面创建README.MD文件，用来生成首页：\n默认的主题提供了一个首页，像下面一样设置home:true即可，可以把下面的设置放入README.md中，待会儿你将会看到跟VuePress一样的主页\n--- home: true heroImage: /image/logo.jpg actionText: 快速上手 → actionLink: /zh/guide/ features: - title: 简洁至上 details: 以 Markdown 为中心的项目结构，以最少的配置帮助你专注于写作。 - title: Vue驱动 details: 享受 Vue + webpack 的开发体验，在 Markdown 中使用 Vue 组件，同时可以使用 Vue 来开发自定义主题。 - title: 高性能 details: VuePress 为每个页面预渲染生成静态的 HTML，同时在页面被加载的时候，将作为 SPA 运行。 footer: MIT Licensed | Copyright © 2018-present Evan You --- 需要在docs/.vuepress/public下放一张静态图片作为首页logo\n2 启动项目 2.1 项目结构解析 project ├─── docs │ ├── README.md │ └── .vuepress │ ├── public │ └── config.js └── package.json 在package.json里添加两个启动命令\n{ \u0026#34;scripts\u0026#34;: { \u0026#34;docs:dev\u0026#34;: \u0026#34;vuepress dev docs\u0026#34;, \u0026#34;docs:build\u0026#34;: \u0026#34;vuepress build docs\u0026#34; } } 2.2 启动vuepress # 构建：build生成静态的HTML文件,默认会在 .vuepress/dist 文件夹下 yarn docs:build # 或者：npm run docs:build # 启动：默认是 localhost:8080 端口 yarn docs:dev # 或者：npm run docs:dev 3 配置vuepress config.js配置\n3.1 基本的项目配置 module.exports = { title: \u0026#39;chenyanan の blog\u0026#39;, description: \u0026#39;学习最好的时间就是现在\u0026#39;, // 注入到当前页面的 HTML \u0026lt;head\u0026gt; 中的标签 head: [ [\u0026#39;link\u0026#39;, { rel: \u0026#39;icon\u0026#39;, href: \u0026#39;/ico-pig.png\u0026#39; }], // 增加一个自定义的 favicon(网页标签的图标) ], //base: \u0026#39;/vuepress-blog/\u0026#39;, // 这是部署到github相关的配置 下面会讲 markdown: { lineNumbers: true // 代码块显示行号 }, themeConfig: { sidebarDepth: 2, // e\u0026#39;b将同时提取markdown中h2 和 h3 标题，显示在侧边栏上。 lastUpdated: \u0026#39;Last Updated\u0026#39;, // 文档更新时间：每个文件git最后提交的时间 } } 3.2 导航栏配置 themeConfig: { nav:[ { text: \u0026#39;后端\u0026#39;, link: \u0026#39;/dev/\u0026#39; }, // 内部链接 以docs为根目录 { text: \u0026#39;前端\u0026#39;, link: \u0026#39;/front/\u0026#39; }, { text: \u0026#39;微服务\u0026#39;, link: \u0026#39;/videoDemo.html\u0026#39; }, { text: \u0026#39;架构\u0026#39;, link: \u0026#39;#\u0026#39; }, { text: \u0026#39;读书\u0026#39;, link: \u0026#39;#\u0026#39; }, { text: \u0026#39;音乐\u0026#39;, link: \u0026#39;#\u0026#39; }, // 下拉列表 { text: \u0026#39;GitHub\u0026#39;, items: [ { text: \u0026#39;GitHub地址\u0026#39;, link: \u0026#39;https://github.com/ching7\u0026#39; },// 外部链接 ] } ] } 3.3 侧边栏配置 module.exports = { themeConfig: { sidebar:{ // docs文件夹下面的dev文件夹 文档中md文件 书写的位置(命名随意) \u0026#39;/dev/\u0026#39;: [ \u0026#39;/dev/\u0026#39;, // dev文件夹的README.md 不是下拉框形式 { title: \u0026#39;侧边栏下拉框的标题1\u0026#39;, children: [ \u0026#39;/dev/java/test\u0026#39;, // 以docs为根目录来查找文件 // 上面地址查找的是：docs\u0026gt;dev\u0026gt;test.md 文件 // 自动加.md 每个子选项的标题 是该md文件中的第一个h1/h2/h3标题 ] } ], // docs文件夹下面的front文件夹 这是第二组侧边栏 跟第一组侧边栏没关系 \u0026#39;/front/\u0026#39;: [ \u0026#39;/front/\u0026#39;, { title: \u0026#39;第二组侧边栏下拉框的标题1\u0026#39;, children: [ \u0026#39;/front/js/test\u0026#39; ] } ] } } } 4 发布vuepress到github 4.1 配置config.js 在docs/.vuepress/config.js设置正确的base\n如果你打算发布到 https://\u0026lt;USERNAME\u0026gt;.github.io/，则可以省略这一步，因为 base 默认即是 \u0026quot;/\u0026quot;。\n如果你打算发布到 https://\u0026lt;USERNAME\u0026gt;.github.io/\u0026lt;REPO\u0026gt;/（也就是说你的仓库在 https://github.com/\u0026lt;USERNAME\u0026gt;/\u0026lt;REPO\u0026gt;），则将 base 设置为 \u0026quot;/\u0026lt;REPO\u0026gt;/\u0026quot;。\nmodule.exports = { base: \u0026#39;/test/\u0026#39;, // 比如你的仓库是test } 4.2 创建脚本文件 在project的根目录下创建delpoy.sh脚本\n#!/usr/bin/env sh # 确保脚本抛出遇到的错误 set -e # 生成静态文件 npm run docs:build # 进入生成的文件夹 cd docs/.vuepress/dist # 如果是发布到自定义域名 # echo \u0026#39;www.example.com\u0026#39; \u0026gt; CNAME git init git add -A git commit -m \u0026#39;deploy\u0026#39; # 如果发布到 https://\u0026lt;USERNAME\u0026gt;.github.io USERNAME=你的用户名 # git push -f git@github.com:\u0026lt;USERNAME\u0026gt;/\u0026lt;USERNAME\u0026gt;.github.io.git master # 如果发布到 https://\u0026lt;USERNAME\u0026gt;.github.io/\u0026lt;REPO\u0026gt; REPO=github上的项目 # git push -f git@github.com:\u0026lt;USERNAME\u0026gt;/\u0026lt;REPO\u0026gt;.git master:gh-pages cd - 4.3 调整package.json { \u0026#34;scripts\u0026#34;: { \u0026#34;dev\u0026#34;: \u0026#34;vuepress dev docs\u0026#34;, // 本地运行项目 npm run dev \u0026#34;build\u0026#34;: \u0026#34;vuepress build docs\u0026#34;, // 构建项目 nom run build \u0026#34;d\u0026#34;: \u0026#34;bash deploy.sh\u0026#34; // 部署项目 npm run d } } 4.4 部署vuepress到github 然后你每次可以运行下面的命令行，来把最新更改推到github上：\nnpm run d 参考资料:\nVuePress 手摸手教你搭建一个类Vue文档风格的技术文档/博客\nVuePress + GitHub Pages 搭建个人博客\n利用 GitHub Pages 快速搭建个人博客\n","permalink":"https://cyn-blog.pages.dev/posts/02.front/10.vuepress/manual/","summary":"\u003ch1 id=\"如何使用vuepress玩转blog\"\u003e如何使用vuepress玩转blog\u003c/h1\u003e\n\u003cp\u003e环境：\u003ccode\u003enode.js\u003c/code\u003e  编码工具：\u003ccode\u003evscode\u003c/code\u003e  \u003ca href=\"https://vuepress.vuejs.org/\"\u003evuepress官网\u003c/a\u003e\u003c/p\u003e\n\u003ch2 id=\"1-搭建环境\"\u003e1 搭建环境\u003c/h2\u003e\n\u003ch3 id=\"11-全局安装vuepress\"\u003e1.1 全局安装Vuepress\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-cmake\" data-lang=\"cmake\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003eyarn\u003c/span\u003e \u003cspan class=\"err\"\u003eglobal\u003c/span\u003e \u003cspan class=\"err\"\u003eadd\u003c/span\u003e \u003cspan class=\"err\"\u003evuepress\u003c/span\u003e \u003cspan class=\"c\"\u003e# 或者：npm install -g vuepress\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"12-项目初始化\"\u003e1.2 项目初始化\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e新建blog项目文件夹（注意该目录为blog项目的主文件夹）\u003c/p\u003e","title":"如何使用vuepress玩转blog"}]