-
面向对象 什么是面向对象? 对比面向过程,是两种不同的处理问题的角度 面向过程更注重事情的每一个步骤及顺序,面向对象更注重事情有哪些参与者(对象)、及各自需要做什么 比如:洗衣机洗衣服 面向过程会将任务拆解成一系列的步骤(函数),1、打开洗衣机----->2、放衣服----->3、放洗衣粉----->4、清洗----->5、烘干 面向对象会拆出人和洗衣机两个对象:人:打开洗衣机 放衣服 放洗衣粉洗衣机:清洗 烘干 从以上例子能看出,面向过程比较直接高效,而面向对象更易于复用、扩展和维护 面向对象 封装:封装的意义,在于明确标识出允许外部使用的所有成员函数和数据项 内部细节对外部调用透明,外部调用无需修改或者关心内部实现 javabean的属性私有,提供getset对外访问,因为属性的赋值或者获取逻辑只能由javabean本身决定。而不能由外部胡乱修改 orm框架 操作数据库,我们不需要关心链接是如何建立的、sql是如何执行的,只需要引入mybatis,调方法即可继承:继承基类的方法,并做出自己的改变和/或扩展子类共性的方法或者属性直接使用父类的,而不需要自己再定义,只需扩展自己个性化的 多态:基于对象所属类的不同,外部对同一个方法的调用,实际执行的逻辑不同。 继承,方法重写,父类引用指向子类对象 无法调用子类特有的功能 JDK JRE JVM JDK: Java Develpment Kit java 开发工具 JRE: Java Runtime Environment java运行时环境 JVM: java Virtual Machine java 虚拟机 ==和equals比较 ==对比的是栈中的值,基本数据类型是变量值,引用类型是堆中内存对象的地址 equals:object中默认也是采用==比较,通常会重写 Object String 上述代码可以看出,String类中被复写的equals()方法其实是比较两个字符串的内容。 hashCode与equals hashCode介绍: hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,Java中的任何类都包含有 hashCode() 函数。 散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)为什么要有hashCode: 以“HashSet如何检查重复”为例子来说明为什么要有hashCode:对象加入HashSet时,HashSet会先计算对象的hashcode值来判断对象加入的位置,看该位置是否有值,如果没有、HashSet会假设对象没有重复出现。但是如果发现有值,这时会调用equals()方法来检查两个对象是否真的相同。如果两者相同,HashSet就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。这样就大大减少了equals的次数,相应就大大提高了执行速度。 如果两个对象相等,则hashcode一定也是相同的两个对象相等,对两个对象分别调用equals方法都返回true 两个对象有相同的hashcode值,它们也不一定是相等的因此,equals方法被覆盖过,则hashCode方法也必须被覆盖 hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据) final 终的 修饰类:表示类不可被继承修饰方法:表示方法不可被子类覆盖,但是可以重载 修饰变量:表示变量一旦被赋值就不可以更改它的值。 修饰成员变量 如果final修饰的是类变量,只能在静态初始化块中指定初始值或者声明该类变量时指定初始值。 如果final修饰的是成员变量,可以在非静态初始化块、声明该变量或者构造器中执行初始值。 修饰局部变量 系统不会为局部变量进行初始化,局部变量必须由程序员显示初始化。因此使用final修饰局部变量时,即可以在定义时指定默认值(后面的代码不能对变量再赋值),也可以不指定默认值,而在后面的代码中对final变量赋初值(仅一次) 修饰基本类型数据和引用类型数据 如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。但是引用的值是可变的。 为什么局部内部类和匿名内部类只能访问局部final变量?编译之后会生成两个class文件,Test.class Test1.class 首先需要知道的一点是: 内部类和外部类是处于同一个级别的,内部类不会因为定义在方法中就会随着方法的执行完毕就被销毁。 这里就会产生问题:当外部类的方法结束时,局部变量就会被销毁了,但是内部类对象可能还存在(只有没有人再引用它时,才会死亡)。这里就出现了一个矛盾:内部类对象访问了一个不存在的变量。为了解决这个问题,就将局部变量复制了一份作为内部类的成员变量,这样当局部变量死亡后,内部类仍可以访问它,实际访问的是局部变量的"copy"。这样就好像延长了局部变量的生命周期 将局部变量复制为内部类的成员变量时,必须保证这两个变量是一样的,也就是如果我们在内部类中修改了成员变量,方法中的局部变量也得跟着改变,怎么解决问题呢? 就将局部变量设置为final,对它初始化后,我就不让你再去修改这个变量,就保证了内部类的成员变量和方法的局部变量的一致性。这实际上也是一种妥协。使得局部变量与内部类内建立的拷贝保持一致。 String、StringBuffer、StringBuilder String是final修饰的,不可变,每次操作都会产生新的String对象 StringBuffer和StringBuilder都是在原对象上操作 StringBuffer是线程安全的,StringBuilder线程不安全的 StringBuffer方法都是synchronized修饰的 性能:StringBuilder > StringBuffer > String 场景:经常需要改变字符串内容时使用后面两个 优先使用StringBuilder,多线程使用共享变量时使用StringBuffer 重载和重写的区别 重载: 发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。 重写: 发生在父子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;如果父类方法访问修饰符为private则子类就不能重写该方法。 接口和抽象类的区别 抽象类可以存在普通成员函数,而接口中只能存在public abstract 方法。 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的。 抽象类只能继承一个,接口可以实现多个。 接口的设计目的,是对类的行为进行约束(更准确的说是一种“有”约束,因为接口不能规定类不可以有什么行为),也就是提供一种机制,可以强制要求不同的类具有相同的行为。它只约束了行为的有无,但不对如何实现行为进行限制。 而抽象类的设计目的,是代码复用。当不同的类具有某些相同的行为(记为行为集合A),且其中一部分行为的实现方式一致时(A的非真子集,记为B),可以让这些类都派生于一个抽象类。在这个抽象类中实现了B,避免让所有的子类来实现B,这就达到了代码复用的目的。而A减B的部分,留给各个子类自己实现。正是因为A-B在这里没有实现,所以抽象类不允许实例化出来(否则当调用到A-B时,无法执行)。 抽象类是对类本质的抽象,表达的是 is a 的关系,比如:BMW is a Car。抽象类包含并实现子类的通用特性,将子类存在差异化的特性进行抽象,交由子类去实现。 而接口是对行为的抽象,表达的是 like a 的关系。比如:Bird like a Aircraft(像飞行器一样可以飞),但其本质上 is a Bird。接口的核心是定义行为,即实现类可以做什么,至于实现类主体是谁、是如何实现的,接口并不关心。 使用场景:当你关注一个事物的本质的时候,用抽象类;当你关注一个操作的时候,用接口。 抽象类的功能要远超过接口,但是,定义抽象类的代价高。因为高级语言来说(从实际设计上来说也是)每个类只能继承一个类。在这个类中,你必须继承或编写出其所有子类的所有共性。虽然接口在功能上会弱化许多,但是它只是针对一个动作的描述。而且你可以在一个类中同时实现多个接口。在设计阶段会降低难度 List和Set的区别 List:有序,按对象进入的顺序保存对象,可重复,允许多个Null元素对象,可以使用Iterator取出所有元素,在逐一遍历,还可以使用get(int index)获取指定下标的元素 Set:无序,不可重复, 多允许有一个Null元素对象,取元素时只能用Iterator接口取得所有元素,在逐一遍历各个元素 ArrayList和LinkedList区别 ArrayList:基于动态数组,连续内存存储,适合下标访问(随机访问),扩容机制:因为数组长度固定,超出长度存数据时需要新建数组,然后将老数组的数据拷贝到新数组,如果不是尾部插入数据还会涉及到元素的移动(往后复制一份,插入新元素),使用尾插法并指定初始容量可以极大提升性能、甚至超过linkedList(需要创建大量的node对象) LinkedList:基于链表,可以存储在分散的内存中,适合做数据插入及删除操作,不适合查询:需要逐一遍历 遍历LinkedList必须使用iterator不能使用for循环,因为每次for循环体内通过get(i)取得某一元素时都需要对list重新进行遍历,性能消耗极大。 另外不要试图使用indexOf等返回元素索引,并利用其进行遍历,使用indexlOf对list进行了遍历,当结果为空时会遍历整个列表。 HashMap和HashTable有什么区别?其底层实现是什么? 区别 : HashMap方法没有synchronized修饰,线程非安全,HashTable线程安全; HashMap允许key和value为null,而HashTable不允许 2.底层实现:数组+链表实现 jdk8开始链表高度到8、数组长度超过64,链表转变为红黑树,元素以内部类Node节点存在 计算key的hash值,二次hash然后对数组长度取模,对应到数组下标,如果没有产生hash冲突(下标位置没有元素),则直接创建Node存入数组,如果产生hash冲突,先进行equal比较,相同则取代该元素,不同,则判断链表高度插入链表,链 表高度达到8,并且数组长度到64则转变为红黑树,长度低于6则将红黑树转回链表 key为null,存在下标0的位置数组扩容 ConcurrentHashMap原理,jdk7和jdk8版本的区别 jdk7:数据结构:ReentrantLock+Segment+HashEntry,一个Segment中包含一个HashEntry数组,每个 HashEntry又是一个链表结构 元素查询:二次hash,第一次Hash定位到Segment,第二次Hash定位到元素所在的链表的头部 锁:Segment分段锁 Segment继承了ReentrantLock,锁定操作的Segment,其他的Segment不受影响,并发度为segment个数,可以通过构造函数指定,数组扩容不会影响其他的segment get方法无需加锁,volatile保证 jdk8: 数据结构:synchronized+CAS+Node+红黑树,Node的val和next都用volatile修饰,保证可见性查找,替换,赋值操作都使用CAS 锁:锁链表的head节点,不影响其他元素的读写,锁粒度更细,效率更高,扩容时,阻塞所有的读写操作、并发扩容 读操作无锁: Node的val和next使用volatile修饰,读写线程对该变量互相可见 数组用volatile修饰,保证扩容时被读线程感知 什么是字节码?采用字节码的好处是什么? java中的编译器和解释器: Java中引入了虚拟机的概念,即在机器和编译程序之间加入了一层抽象的虚拟的机器。这台虚拟的机器在任何平台上都提供给编译程序一个的共同的接口。 编译程序只需要面向虚拟机,生成虚拟机能够理解的代码,然后由解释器来将虚拟机代码转换为特定系统的机器码执行。在Java中,这种供虚拟机理解的代码叫做 字节码(即扩展名为 .class的文件),它不面向任何特定的处理器,只面向虚拟机。 每一种平台的解释器是不同的,但是实现的虚拟机是相同的。Java源程序经过编译器编译后变成字节码,字节码由虚拟机解释执行,虚拟机将每一条要执行的字节码送给解释器,解释器将其翻译成特定机 器上的机器码,然后在特定的机器上运行。这也就是解释了Java的编译与解释并存的特点。 Java源代码---->编译器---->jvm可执行的Java字节码(即虚拟指令)---->jvm---->jvm中解释器----->机器可执行的二进制机器码---->程序运行。 采用字节码的好处: Java语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以Java程序运行时比较高效,而且,由于字节码并不专对一种特定的机器,因此,Java程序无须重新编译便可在多种不同的计算机上运行。 Java中的异常体系 Java中的所有异常都来自顶级父类Throwable。 Throwable下有两个子类Exception和Error。 Error是程序无法处理的错误,一旦出现这个错误,则程序将被迫停止运行。 Exception不会导致程序停止,又分为两个部分RunTimeException运行时异常和CheckedException检查异常。 RunTimeException常常发生在程序运行过程中,会导致程序当前线程执行失败。CheckedException常常发生在程序编译过程中,会导致程序编译不通过。 Java类加载器 JDK自带有三个类加载器:bootstrap ClassLoader、ExtClassLoader、AppClassLoader。 BootStrapClassLoader是ExtClassLoader的父类加载器,默认负责加载%JAVA_HOME%lib下的jar包和 class文件。 ExtClassLoader是AppClassLoader的父类加载器,负责加载%JAVA_HOME%/lib/ext文件夹下的jar包和 class类。 AppClassLoader是自定义类加载器的父类,负责加载classpath下的类文件。系统类加载器,线程上下文加载器 继承ClassLoader实现自定义类加载器 双亲委托模型 双亲委派模型的好处: 主要是为了安全性,避免用户自己编写的类动态替换 Java的一些核心类,比如 String。 同时也避免了类的重复加载,因为 JVM中区分不同类,不仅仅是根据类名,相同的 class文件被不同的 ClassLoader加载就是不同的两个类 GC如何判断对象可以被回收 引用计数法:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收,可达性分析法:从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是不可用的,那么虚拟机就判断是可回收对象。 引用计数法,可能会出现A 引用了 B,B 又引用了 A,这时候就算他们都不再使用了,但因为相互引用 计数器=1 永远无法被回收。 GC Roots的对象有: 虚拟机栈(栈帧中的本地变量表)中引用的对象方法区中类静态属性引用的对象 方法区中常量引用的对象 本地方法栈中JNI(即一般说的Native方法)引用的对象可达性算法中的不可达对象并不是立即死亡的,对象拥有一次自我拯救的机会。对象被系统宣告死亡至少要经历两次标记过程:第一次是经过可达性分析发现没有与GC Roots相连接的引用链,第二次是在由虚拟机自动建立的Finalizer队列中判断是否需要执行finalize()方法。 当对象变成(GC Roots)不可达时,GC会判断该对象是否覆盖了finalize方法,若未覆盖,则直接将其回收。否则,若对象未执行过finalize方法,将其放入F-Queue队列,由一低优先级线程执行该队列中对象的finalize方法。执行finalize方法完毕后,GC会再次判断该对象是否可达,若不可达,则进行回收,否则,对象“复活” 每个对象只能触发一次finalize()方法 由于finalize()方法运行代价高昂,不确定性大,无法保证各个对象的调用顺序,不推荐大家使用,建议遗忘它。 线程、并发相关 线程的生命周期?线程有几种状态 1.线程通常有五种状态,创建,就绪,运行、阻塞和死亡状态。 2.阻塞的情况又分为三种: (1)、等待阻塞:运行的线程执行wait方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池”中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify或notifyAll方法才能被唤醒,wait是object类的方法 (2)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入“锁池”中。 (3)、其他阻塞:运行的线程执行sleep或join方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状 态。当sleep状态超时、join等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。 sleep是Thread类的方法 1.新建状态(New):新创建了一个线程对象。 2.就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。 3.运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。 4.阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。 5.死亡状态(Dead):线程执行完了或者因异常退出了run方法,该线程结束生命周期。 sleep()、wait()、join()、yield()的区别 1.锁池 所有需要竞争同步锁的线程都会放在锁池当中,比如当前对象的锁已经被其中一个线程得到,则其他线程需要在这个锁池进行等待,当前面的线程释放同步锁后锁池中的线程去竞争同步锁,当某个线程得到后会进入就绪队列进行等待cpu资源分配。 2.等待池 当我们调用wait()方法后,线程会放到等待池当中,等待池的线程是不会去竞争同步锁。只有调用了 notify()或notifyAll()后等待池的线程才会开始去竞争锁,notify()是随机从等待池选出一个线程放到锁池,而notifyAll()是将等待池的所有线程放到锁池当中 sleep 是 Thread 类的静态本地方法,wait 则是 Object 类的本地方法。 sleep方法不会释放lock,但是wait会释放,而且会加入到等待队列中。 sleep就是把cpu的执行资格和执行权释放出去,不再运行此线程,当定时时间结束再取回cpu资源,参与cpu 的调度,获取到cpu资源后就可以继续运行了。而如果sleep时该线程有锁,那么sleep不会释放这个锁,而是把锁带着进入了冻结状态,也就是说其他需要这个锁的线程根本不可能获取到这个锁。也就是说无法执行程序。如果在睡眠期间其他线程调用了这个线程的interrupt方法,那么这个线程也会抛出 interruptexception异常返回,这点和wait是一样的。 sleep方法不依赖于同步器synchronized,但是wait需要依赖synchronized关键字。 sleep不需要被唤醒(休眠之后推出阻塞),但是wait需要(不指定时间需要被别人中断)。 sleep 一般用于当前线程休眠,或者轮循暂停操作,wait 则多用于多线程之间的通信。 sleep 会让出 CPU 执行时间且强制上下文切换,而 wait 则不一定,wait 后可能还是有机会重新竞争到锁继续执行的。 yield()执行后线程直接进入就绪状态,马上释放了cpu的执行权,但是依然保留了cpu的执行资格,所以有可能cpu下次进行线程调度还会让这个线程获取到执行权继续执行 join()执行后线程进入阻塞状态,例如在线程B中调用线程A的join(),那线程B会进入到阻塞队列,直到线程A结束或中断线程 对线程安全的理解 不是线程安全、应该是内存安全,堆是共享内存,可以被所有线程访问 当多个线程访问一个对象时,如果不用进行额外的同步控制或其他的协调操作,调用这个对象的行为都可以获得正确的结果,我们就说这个对象是线程安全的 堆是进程和线程共有的空间,分全局堆和局部堆。全局堆就是所有没有分配的空间,局部堆就是用户分配的空间。堆在操作系统对进程初始化的时候分配,运行过程中也可以向系统要额外的堆,但是用完了要还给操作系统,要不然就是内存泄漏。 在Java中,堆是Java虚拟机所管理的内存中 大的一块,是所有线程共享的一块内存区域,在虚拟机启动时创建。堆所存在的内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。 栈是每个线程独有的,保存其运行状态和局部自动变量的。栈在线程开始的时候初始化,每个线程的栈互相独立,因此,栈是线程安全的。操作系统在切换线程的时候会自动切换栈。栈空间不需要在高级语言里面显式的分配和释放。 目前主流操作系统都是多任务的,即多个进程同时运行。为了保证安全,每个进程只能访问分配给自己的内存空间,而不能访问别的进程的,这是由操作系统保障的。 在每个进程的内存空间中都会有一块特殊的公共区域,通常称为堆(内存)。进程内的所有线程都可以访问到该区域,这就是造成问题的潜在原因。 Thread、Runable的区别 Thread和Runnable的实质是继承关系,没有可比性。无论使用Runnable还是Thread,都会new Thread,然后执行run方法。用法上,如果有复杂的线程操作需求,那就选择继承Thread,如果只是简单的执行一个任务,那就实现runnable。 原因是:MyThread创建了两个实例,自然会卖出两倍,属于用法错误 对守护线程的理解 守护线程:为所有非守护线程提供服务的线程;任何一个守护线程都是整个JVM中所有非守护线程的保姆; 守护线程类似于整个进程的一个默默无闻的小喽喽;它的生死无关重要,它却依赖整个进程而运行;哪天其他线程结束了,没有要执行的了,程序就结束了,理都没理守护线程,就把它中断了; 注意: 由于守护线程的终止是自身无法控制的,因此千万不要把IO、File等重要操作逻辑分配给它;因为它不靠谱; 守护线程的作用是什么?举例, GC垃圾回收线程:就是一个经典的守护线程,当我们的程序中不再有任何运行的Thread,程序就不会再产生垃圾,垃圾回收器也就无事可做,所以当垃圾回收线程是JVM上仅剩的线程时,垃圾回收线程会自动离开。它始终在低级别的状态中运行,用于实时监控和管理系统中的可回收资源。 应用场景:(1)来为其它线程提供服务支持的情况;(2) 或者在任何情况下,程序结束时,这个线程必须正常且立刻关闭,就可以作为守护线程来使用;反之,如果一个正在执行某个操作的线程必须要正确地关闭掉否则就会出现不好的后果的话,那么这个线程就不能是守护线程,而是用户线程。通常都是些关键的事务,比方说,数据库录入或者更新,这些操作都是不能中断的。 thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个 IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。 在Daemon线程中产生的新线程也是Daemon的。 守护线程不能用于去访问固有资源,比如读写操作或者计算逻辑。因为它会在任何时候甚至在一个操作的中间发生中断。 Java自带的多线程框架,比如ExecutorService,会将守护线程转换为用户线程,所以如果要使用后台线程就不能用Java的线程池。 ThreadLocal的原理和使用场景 每一个Thread对象均含有一个ThreadLocalMap类型的成员变量threadLocals,它存储本线程中所有ThreadLocal对象及其对应的值 ThreadLocalMap由一个个Entry对象构成 Entry继承自WeakReference<ThreadLocal<?>>,一个Entry由ThreadLocal对象和Object构成。由此可见,Entry的key是ThreadLocal对象,并且是一个弱引用。当没指向key的强引用后,该 key就会被垃圾收集器回收 当执行set方法时,ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap对象。再以当前ThreadLocal对象为key,将值存储进ThreadLocalMap对象中。 get方法执行过程类似。ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap 对象。再以当前ThreadLocal对象为key,获取对应的value。由于每一条线程均含有各自私有的ThreadLocalMap容器,这些容器相互独立互不影响,因此不会存在线程安全性问题,从而也无需使用同步机制来保证多条线程访问容器的互斥性。 使用场景: 在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。 线程间数据隔离 进行事务操作,用于存储线程事务信息。 数据库连接,Session会话管理。 Spring框架在事务开始时会给当前线程绑定一个Jdbc Connection,在整个事务过程都是使用该线程绑定的 connection来执行数据库操作,实现了事务的隔离性。Spring框架里面就是用的ThreadLocal来实现这种隔离 ThreadLocal内存泄露原因,如何避免 内存泄露为程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光, 不再会被使用的对象或者变量占用的内存不能被回收,就是内存泄露。 强引用:使用 普遍的引用(new),一个对象具有强引用,不会被垃圾回收器回收。当内存空间不足, Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不回收这种对象。如果想取消强引用和某个对象之间的关联,可以显式地将引用赋值为null,这样可以使JVM在合适的时间就会回收该对象。 弱引用:JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。在java中,用 java.lang.ref.WeakReference类来表示。可以在缓存中使用弱引用。 ThreadLocal的实现原理,每一个Thread维护一个ThreadLocalMap,key为使用弱引用的ThreadLocal 实例,value为线程变量的副本 hreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal不存在外部强引用时, Key(ThreadLocal)势必会被GC回收,这样就会导致ThreadLocalMap中key为null, 而value还存在着强引用,只有thead线程退出以后,value的强引用链条才会断掉,但如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链(红色链条) key 使用强引用 当hreadLocalMap的key为强引用回收ThreadLocal时,因为ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏。 key 使用弱引用 当ThreadLocalMap的key为弱引用回收ThreadLocal时,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。当key为null,在下一次ThreadLocalMap调用 set(),get(),remove()方法的时候会被清除value值。 因此,ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。 ThreadLocal正确的使用方法 每次使用完ThreadLocal都调用它的remove()方法清除数据 将ThreadLocal变量定义成private static,这样就一直存在ThreadLocal的强引用,也就能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的value值,进而清除掉 。 并发、并行、串行的区别 串行在时间上不可能发生重叠,前一个任务没搞定,下一个任务就只能等着并行在时间上是重叠的,两个任务在同一时刻互不干扰的同时执行。 并发允许两个任务彼此干扰。统一时间点、只有一个任务运行,交替执行 并发的三大特性 原子性是指在一个操作中cpu不可以在中途暂停然后再调度,即不被中断操作,要不全部执行完成,要不都不执行。就好比转账,从账户A向账户B转1000元,那么必然包括2个操作:从账户A减去1000元,往账户B加上1000元。2个操作必须全部完成。 那程序中原子性指的是 小的操作单元,比如自增操作,它本身其实并不是原子性操作,分了3步的,包括读取变量的原始值、进行加1操作、写入工作内存。所以在多线程中,有可能一个线程还没自增完,可能才执行到第二部,另一个线程就已经读取了值,导致结果错误。那如果我们能保证自增操作是一个原子性的操作,那么就能保证其他线程读取到的一定是自增后的数据。 关键字:synchronized 可见性 当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。 若两个线程在不同的cpu,那么线程1改变了i的值还没刷新到主存,线程2又使用了i,那么这个i值肯定还是之前的,线程1对变量的修改线程没看到这就是可见性问题。 如果线程2改变了stop的值,线程1一定会停止吗?不一定。当线程2更改了stop变量的值之后,但是还没来得及写入主存当中,线程2转去做其他事情了,那么线程1由于不知道线程2对stop变量的更改,因此还会一直循环下去。 关键字:volatile、synchronized、final 有序性 虚拟机在进行代码编译时,对于那些改变顺序之后不会对 终结果造成影响的代码,虚拟机不一定会按照我们写的代码的顺序来执行,有可能将他们重排序。实际上,对于有些代码进行重排序之后,虽然对变量的值没有造成影响,但有可能会出现线程安全问题。 write方法里的1和2做了重排序,线程1先对flag赋值为true,随后执行到线程2,ret直接计算出结果,再到线程1,这时候a才赋值为2,很明显迟了一步 关键字:volatile、synchronized volatile本身就包含了禁止指令重排序的语义,而synchronized关键字是由“一个变量在同一时刻只允许一条线程对其进行lock操作”这条规则明确的。 synchronized关键字同时满足以上三种特性,但是volatile关键字不满足原子性。 在某些情况下,volatile的同步机制的性能确实要优于锁(使用synchronized关键字或 java.util.concurrent包里面的锁),因为volatile的总开销要比锁低。 我们判断使用volatile还是加锁的唯一依据就是volatile的语义能否满足使用的场景(原子性) volatile 保证被volatile修饰的共享变量对所有线程总是可见的,也就是当一个线程修改了一个被volatile修饰共享变量的值,新值总是可以被其他线程立即得知。 如果线程2改变了stop的值,线程1一定会停止吗?不一定。当线程2更改了stop变量的值之后,但是还没来得及写入主存当中,线程2转去做其他事情了,那么线程1由于不知道线程2对stop变量的更改,因此还会一直循环下去。 禁止指令重排序优化。 write方法里的1和2做了重排序,线程1先对flag赋值为true,随后执行到线程2,ret直接计算出结果,再到线程1,这时候a才赋值为2,很明显迟了一步。 但是用volatile修饰之后就变得不一样了 第一:使用volatile关键字会强制将修改的值立即写入主存; 第二:使用volatile关键字的话,当线程2进行修改时,会导致线程1的工作内存中缓存变量stop的缓存行无效(反映到硬件层的话,就是CPU的L1或者L2缓存中对应的缓存行无效); 第三:由于线程1的工作内存中缓存变量stop的缓存行无效,所以线程1再次读取变量stop的值时会去主存读取。 inc++; 其实是两个步骤,先加加,然后再赋值。不是原子性操作,所以volatile不能保证线程安全。 为什么用线程池?解释下线程池参数? 降低资源消耗;提高线程利用率,降低创建和销毁线程的消耗。 提高响应速度;任务来了,直接有线程可用可执行,而不是先创建线程,再执行。 提高线程的可管理性;线程是稀缺资源,使用线程池可以统一分配调优监控。 corePoolSize 代表核心线程数,也就是正常情况下创建工作的线程数,这些线程创建后并不会消除,而是一种常驻线程 maxinumPoolSize 代表的是 大线程数,它与核心线程数相对应,表示 大允许被创建的线程数,比如当前任务较多,将核心线程数都用完了,还无法满足需求时,此时就会创建新的线程,但是线程池内线程总数不会超过 大线程数 keepAliveTime、unit 表示超出核心线程数之外的线程的空闲存活时间,也就是核心线程不会 消除,但是超出核心线程数的部分线程如果空闲一定的时间则会被消除,我们可以通过 setKeepAliveTime 来设置空闲时间 workQueue 用来存放待执行的任务,假设我们现在核心线程都已被使用,还有任务进来则全部放入队列,直到整个队列被放满但任务还再持续进入则会开始创建新的线程 ThreadFactory 实际上是一个线程工厂,用来生产线程执行任务。我们可以选择使用默认的创建工厂,产生的线程都在同一个组内,拥有相同的优先级,且都不是守护线程。当然我们也可以选择自定义线程工厂,一般我们会根据业务来制定不同的线程工厂 Handler 任务拒绝策略,有两种情况,第一种是当我们调用shutdown 等方法关闭线程池后,这时候即使线程池内部还有没执行完的任务正在执行,但是由于线程池已经关闭,我们再继续想线程池提交任务就会遭到拒绝。另一种情况就是当达到 大线程数,线程池已经没有能力继续处理新提交的任务时,这是也就拒绝 简述线程池处理流程 线程池中阻塞队列的作用?为什么是先添加列队而不是先创建最大线程? 1、一般的队列只能保证作为一个有限长度的缓冲区,如果超出了缓冲长度,就无法保留当前的任务了,阻塞队列通过阻塞可以保留住当前想要继续入队的任务。 阻塞队列可以保证任务队列中没有任务时阻塞获取任务的线程,使得线程进入wait状态,释放cpu资源。 阻塞队列自带阻塞和唤醒的功能,不需要额外处理,无任务执行时,线程池利用阻塞队列的take方法挂起,从而维持核心线程的存活、不至于一直占用cpu资源 2、在创建新线程的时候,是要获取全局锁的,这个时候其它的就得阻塞,影响了整体效率。 就好比一个企业里面有10个(core)正式工的名额, 多招10个正式工,要是任务超过正式工人数 (task > core)的情况下,工厂领导(线程池)不是首先扩招工人,还是这10人,但是任务可以稍微积压一下,即先放到队列去(代价低)。10个正式工慢慢干,迟早会干完的,要是任务还在继续增加,超过正式工的加班忍耐极限了(队列满了),就的招外包帮忙了(注意是临时工)要是正式工加上外包还是不能完成任务,那新来的任务就会被领导拒绝了(线程池的拒绝策略)。 线程池中线程复用原理 线程池将线程和任务进行解耦,线程是线程,任务是任务,摆脱了之前通过 Thread 创建线程时的一个线程必须对应一个任务的限制。 在线程池中,同一个线程可以从阻塞队列中不断获取新任务来执行,其核心原理在于线程池对 Thread 进行了封装,并不是每次执行任务都会调用 Thread.start() 来创建新线程,而是让每个线程去执行一个“循环任务”,在这个“循环任务”中不停检查是否有任务需要被执行,如果有则直接执行,也就是调用任务中的 run 方法,将 run 方法当成一个普通的方法执行,通过这种方式只使用固定的线程就将所有任务的 run 方法串联起来。 spring 如何实现一个IOC容器 配置文件配置包扫描路径 递归包扫描获取.class文件 反射、确定需要交给IOC管理的类 对需要注入的类进行依赖注入 配置文件中指定需要扫描的包路径定义一些注解,分别表示访问控制层、业务服务层、数据持久层、依赖注入注解、获取配置文件注解 从配置文件中获取需要扫描的包路径,获取到当前路径下的文件信息及文件夹信息,我们将当前路径下所有以.class结尾的文件添加到一个Set集合中进行存储遍历这个set集合,获取在类上有指定注解的类,并将其交给IOC容器,定义一个安全的Map用来存储这些对象遍历这个IOC容器,获取到每一个类的实例,判断里面是有有依赖其他的类的实例,然后进行递归注入 spring是什么? 轻量级的开源的J2EE框架。它是一个容器框架,用来装javabean(java对象),中间层框架(万能胶)可以起一个连接作用,比如说把Struts和hibernate粘合在一起运用,可以让我们的企业开发更快、更简洁 Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架 --从大小与开销两方面而言Spring都是轻量级的。 --通过控制反转(IoC)的技术达到松耦合的目的 --提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务进行内聚性的开发 --包含并管理应用对象(Bean)的配置和生命周期,这个意义上是一个容器。 --将简单的组件配置、组合成为复杂的应用,这个意义上是一个框架。 谈谈你对AOP的理解 系统是由许多不同的组件所组成的,每一个组件各负责一块特定功能。除了实现自身核心功能之外,这些组件还经常承担着额外的职责。例如日志、事务管理和安全这样的核心服务经常融入到自身具有核心业务逻辑的组件中去。这些系统服务经常被称为横切关注点,因为它们会跨越系统的多个组件。 当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。 日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。 在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。 AOP:将程序中的交叉业务逻辑(比如安全,日志,事务等),封装成一个切面,然后注入到目标对象 (具体业务逻辑)中去。AOP可以对某个对象或某些对象的功能进行增强,比如对象中的方法进行增强,可以在执行某个方法之前额外的做一些事情,在某个方法执行之后额外的做一些事情 谈谈你对IOC的理解 容器概念、控制反转、依赖注入 ioc容器:实际上就是个map(key,value),里面存的是各种对象(在xml里配置的bean节点、 @repository、@service、@controller、@component),在项目启动的时候会读取配置文件里面的 bean节点,根据全限定类名使用反射创建对象放到map里、扫描到打上上述注解的类还是通过反射创建对象放到map里。 这个时候map里就有各种对象了,接下来我们在代码里需要用到里面的对象时,再通过DI注入 (autowired、resource等注解,xml里bean节点内的ref属性,项目启动的时候会读取xml节点ref属性根据id注入,也会扫描这些注解,根据类型或id注入;id就是对象名)。 控制反转: 没有引入IOC容器之前,对象A依赖于对象B,那么对象A在初始化或者运行到某一点的时候,自己必须主动去创建对象B或者使用已经创建的对象B。无论是创建还是使用对象B,控制权都在自己手上。 引入IOC容器之后,对象A与对象B之间失去了直接联系,当对象A运行到需要对象B的时候,IOC容器会主动创建一个对象B注入到对象A需要的地方。 通过前后的对比,不难看出来:对象A获得依赖对象B的过程,由主动行为变为了被动行为,控制权颠倒过来了,这就是“控制反转”这个名称的由来。 全部对象的控制权全部上缴给“第三方”IOC容器,所以,IOC容器成了整个系统的关键核心,它起到了一种类似“粘合剂”的作用,把系统中的所有对象粘合在一起发挥作用,如果没有这个“粘合剂”,对象与对象之间会彼此失去联系,这就是有人把IOC容器比喻成“粘合剂”的由来。 依赖注入: “获得依赖对象的过程被反转了”。控制被反转之后,获得依赖对象的过程由自身管理变为了由IOC容器主动注入。依赖注入是实现IOC的方法,就是由IOC容器在运行期间,动态地将某种依赖关系注入到对象之中。 BeanFactory和ApplicationContext有什么区别? ApplicationContext是BeanFactory的子接口 ApplicationContext提供了更完整的功能: ①继承MessageSource,因此支持国际化。 ②统一的资源文件访问方式。 ③提供在监听器中注册bean的事件。 ④同时加载多个配置文件。 ⑤载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层。 BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用 getBean()),才对该Bean进行加载实例化。这样,我们就不能发现一些存在的Spring的配置问 题。如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常。 ApplicationContext,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。 ApplicationContext启动后预载入所有的单实例Bean,通过预载入单实例bean ,确保当你需要的时候,你就不用等待,因为它们已经创建好了。 相对于基本的BeanFactory,ApplicationContext 唯一的不足是占用内存空间。当应用程序配置 Bean较多时,程序启动较慢。 BeanFactory通常以编程的方式被创建,ApplicationContext还能以声明的方式创建,如使用 ContextLoader。 BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的 使用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册。 描述一下Spring Bean的生命周期? 解析类得到BeanDefinition 如果有多个构造方法,则要推断构造方法 确定好构造方法后,进行实例化得到一个对象 对对象中的加了@Autowired注解的属性进行属性填充 回调Aware方法,比如BeanNameAware,BeanFactoryAware 调用BeanPostProcessor的初始化前的方法 调用初始化方法 调用BeanPostProcessor的初始化后的方法,在这里会进行AOP 如果当前创建的bean是单例的则会把bean放入单例池 使用bean Spring容器关闭时调用DisposableBean中destory()方法 解释下Spring支持的几种bean的作用域。 singleton:默认,每个容器中只有一个bean的实例,单例的模式由BeanFactory自身来维护。该对象的生命周期是与Spring IOC容器一致的(但在第一次被注入时才会创建)。 prototype:为每一个bean请求提供一个实例。在每次注入时都会创建一个新的对象 request:bean被定义为在每个HTTP请求中创建一个单例对象,也就是说在单个请求中都会复用这一个单例对象。 session:与request范围类似,确保每个session中有一个bean的实例,在session过期后,bean 会随之失效。 application:bean被定义为在ServletContext的生命周期中复用一个单例对象。 websocket:bean被定义为在websocket的生命周期中复用一个单例对象。 global-session:全局作用域,global-session和Portlet应用相关。当你的应用部署在Portlet容器中工作时,它包含很多portlet。如果你想要声明让所有的portlet共用全局的存储变量的话,那么这全局变量需要存储在global-session中。全局作用域与Servlet中的session作用域效果相同。 Spring框架中的单例Bean是线程安全的么? Spring中的Bean默认是单例模式的,框架并没有对bean进行多线程的封装处理。 如果Bean是有状态的 那就需要开发人员自己来进行线程安全的保证, 简单的办法就是改变bean的作用域 把 "singleton"改为’‘protopyte’ 这样每次请求Bean就相当于是 new Bean() 这样就可以保证线程的安全了。 有状态就是有数据存储功能 无状态就是不会保存数据 controller、service和dao层本身并不是线程安全的,只是如果只 是调用里面的方法,而且多线程调用一个实例的方***在内存中复制变量,这是自己的线程的工作内存,是安全的。 Dao会操作数据库Connection,Connection是带有状态的,比如说数据库事务,Spring的事务管理器使用Threadlocal为不同线程维护了一套独立的connection副本,保证线程之间不会互相影响(Spring 是如何保证事务获取同一个Connection的) 不要在bean中声明任何有状态的实例变量或类变量,如果必须如此,那么就使用ThreadLocal把变量变 为线程私有的,如果bean的实例变量或类变量需要在多个线程之间共享,那么就只能使用 synchronized、lock、CAS等这些实现线程同步的方法了。 Spring 框架中都用到了哪些设计模式? 简单工厂:由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类。 Spring中的BeanFactory就是简单工厂模式的体现,根据传入一个唯一的标识来获得Bean对象,但是否是在传入参数后创建还是传入参数前创建这个要根据具体情况来定。 工厂方法: 实现了FactoryBean接口的bean是一类叫做factory的bean。其特点是,spring会在使用getBean()调用获得该bean时,会自动调用该bean的getObject()方法,所以返回的不是factory这个bean,而是这个 bean.getOjbect()方法的返回值。 单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点 spring对单例的实现: spring中的单例模式完成了后半句话,即提供了全局的访问点BeanFactory。但没有从构造器级别去控制单例,这是因为spring管理的是任意的java对象。 适配器模式: Spring定义了一个适配接口,使得每一种Controller有一种对应的适配器实现类,让适配器代替 controller执行相应的方法。这样在扩展Controller时,只需要增加一个适配器类就完成了SpringMVC 的扩展了。 装饰器模式:动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator模式相比生成子类更为灵活。 动态代理: 观察者模式: spring的事件驱动模型使用的是 观察者模式 ,Spring中Observer模式常用的地方是listener的实现。 策略模式: 模板方法:父类定义了骨架(调用哪些方法及顺序),某些特定方法由子类实现。 大的好处:代码复用,减少重复代码。除了子类要实现的特定方法,其他方法及方法调用顺序都在父类中预先写好了。 Spring事务的实现方式和原理以及隔离级别? 在使用Spring框架时,可以有两种使用事务的方式,一种是编程式的,一种是申明式的, @Transactional注解就是申明式的。 首先,事务这个概念是数据库层面的,Spring只是基于数据库中的事务进行了扩展,以及提供了一些能让程序员更加方便操作事务的方式。 比如我们可以通过在某个方法上增加@Transactional注解,就可以开启事务,这个方法中所有的sql都会在一个事务中执行,统一成功或失败。 在一个方法上加了@Transactional注解后,Spring会基于这个类生成一个代理对象,会将这个代理对象作为bean,当在使用这个代理对象的方法时,如果这个方法上存在@Transactional注解,那么代理逻辑会先把事务的自动提交设置为false,然后再去执行原本的业务逻辑方法,如果执行业务逻辑方法没有出现异常,那么代理逻辑中就会将事务进行提交,如果执行业务逻辑方法出现了异常,那么则会将事务进行回滚。 当然,针对哪些异常回滚事务是可以配置的,可以利用@Transactional注解中的rollbackFor属性进行配置,默认情况下会对RuntimeException和Error进行回滚。 spring事务隔离级别就是数据库的隔离级别:外加一个默认级别 spring事务传播机制 多个事务方法相互调用时,事务如何在这些方法间传播 方法A是一个事务的方法,方法A执行过程中调用了方法B,那么方法B有无事务以及方法B对事务的要求不同都会对方法A的事务具体执行造成影响,同时方法A的事务对方法B的事务执行也有影响,这种影响具体是什么就由两个方法所定义的事务传播类型所决定。 REQUIRED(Spring默认的事务传播类型):如果当前没有事务,则自己新建一个事务,如果当前存在事务,则加入这个事务 SUPPORTS:当前存在事务,则加入当前事务,如果当前没有事务,就以非事务方法执行 MANDATORY:当前存在事务,则加入当前事务,如果当前事务不存在,则抛出异常。 REQUIRES_NEW:创建一个新事务,如果存在当前事务,则挂起该事务。 NOT_SUPPORTED:以非事务方式执行,如果当前存在事务,则挂起当前事务 NEVER:不使用事务,如果当前事务存在,则抛出异常 NESTED:如果当前事务存在,则在嵌套事务中执行,否则REQUIRED的操作一样(开启一个事务) 和REQUIRES_NEW的区别 REQUIRES_NEW是新建一个事务并且新开启的这个事务与原有事务无关,而NESTED则是当前存在事务时(我们把当前事务称之为父事务)会开启一个嵌套事务(称之为一个子事务)。 在NESTED情况下父事务回滚时,子事务也会回滚,而在REQUIRES_NEW情况下,原有事务回滚,不会影响新开启的事务。 和REQUIRED的区别 REQUIRED情况下,调用方存在事务时,则被调用方和调用方使用同一事务,那么被调用方出现异常时,由于共用一个事务,所以无论调用方是否catch其异常,事务都会回滚 而在NESTED情况下,被调用方发生异常时,调用方可以catch其异常,这样只有子事务回滚,父事务不受影响 spring事务什么时候会失效? spring事务的原理是AOP,进行了切面增强,那么失效的根本原因是这个AOP不起作用了!常见情况有如下几种 发生自调用,类里面使用this调用本类的方法(this通常省略),此时这个this对象不是代理类,而是UserService对象本身!解决方法很简单,让那个this变成UserService的代理类即可! 方法不是public的 数据库不支持事务 没有被spring管理 异常被吃掉,事务不会回滚(或者抛出的异常没有被定义,默认为RuntimeException) 什么是bean的自动装配,有哪些方式? 开启自动装配,只需要在xml配置文件中定义“autowire”属性。 autowire属性有五种装配的方式: Cutomer构造函数的参数person的类型为Person,Spirng会将Person类型通过构造方法进行自动装配。 <bean id="cutomer" class="com.xxx.xxx.Cutomer" autowire="construtor"/> <bean id="person" class="com.xxx.xxx.Person"/> autodetect-如果有默认的构造器,则通过constructor方式进行自动装配,否则使用byType方式进行自动装配。 如果有默认的构造器,则通过constructor方式进行自动装配,否则使用byType方式进行自动装配。 @Autowired自动装配bean,可以在字段、setter方法、构造函数上使用。 springmvc、springBoot Spring Boot、Spring MVC 和 Spring 有什么区别 spring是一个IOC容器,用来管理Bean,使用依赖注入实现控制反转,可以很方便的整合各种框架,提供AOP机制弥补OOP的代码重复问题、更方便将不同类不同方法中的共同处理抽取成切面、自动注入给方法执行,比如日志、异常等 springmvc是spring对web框架的一个解决方案,提供了一个总的前端控制器Servlet,用来接收请求,然后定义了一套路由策略(url到handle的映射)及适配执行handle,将handle结果使用视图解析技术生成视图展现给前端 springboot是spring提供的一个快速开发工具包,让程序员能更方便、更快速的开发spring+springmvc 应用,简化了配置(约定了默认配置),整合了一系列的解决方案(starter机制)、redis、 mongodb、es,可以开箱即用 SpringMVC 工作流程 用户发送请求至前端控制器 DispatcherServlet。 DispatcherServlet 收到请求调用 HandlerMapping 处理器映射器。 处理器映射器找到具体的处理器(可以根据 xml 配置、注解进行查找),生成处理器及处理器拦截器 (如果有则生成)一并返回给 DispatcherServlet。 DispatcherServlet 调用 HandlerAdapter 处理器适配器。 HandlerAdapter 经过适配调用具体的处理器(Controller,也叫后端控制器) Controller 执行完成返回 ModelAndView。 HandlerAdapter 将 controller 执行结果 ModelAndView 返回给 DispatcherServlet。8) DispatcherServlet 将 ModelAndView 传给 ViewReslover 视图解析器。 ViewReslover 解析后返回具体 View。 DispatcherServlet 根据 View 进行渲染视图(即将模型数据填充至视图中)。 DispatcherServlet 响应用户。 Spring MVC的主要组件? Handler:也就是处理器。它直接应对着MVC中的C也就是Controller层,它的具体表现形式有很多,可以是类,也可以是方法。在Controller层中@RequestMapping标注的所有方法都可以看成是一个 Handler,只要可以实际处理请求就可以是Handler HandlerMapping initHandlerMappings(context),处理器映射器,根据用户请求的资源uri来查找Handler的。在 SpringMVC中会有很多请求,每个请求都需要一个Handler处理,具体接收到一个请求之后使用哪个 Handler进行,这就是HandlerMapping需要做的事。 HandlerAdapter initHandlerAdapters(context),适配器。因为SpringMVC中的Handler可以是任意的形式,只要能处理请求就ok,但是Servlet需要的处理方法的结构却是固定的,都是以request和response为参数的方法。如何让固定的Servlet处理方法调用灵活的Handler来进行处理呢?这就是HandlerAdapter要做的事情。 Handler是用来干活的工具;HandlerMapping用于根据需要干的活找到相应的工具;HandlerAdapter 是使用工具干活的人。 HandlerExceptionResolver initHandlerExceptionResolvers(context), 其它组件都是用来干活的。在干活的过程中难免会出现问题,出问题后怎么办呢?这就需要有一个专门的角色对异常情况进行处理,在SpringMVC中就是 HandlerExceptionResolver。具体来说,此组件的作用是根据异常设置ModelAndView,之后再交给 render方法进行渲染。 ViewResolver initViewResolvers(context),ViewResolver用来将String类型的视图名和Locale解析为View类型的视图。View是用来渲染页面的,也就是将程序返回的参数填入模板里,生成html(也可能是其它类型)文件。这里就有两个关键问题:使用哪个模板?用什么技术(规则)填入参数?这其实是ViewResolver 主要要做的工作,ViewResolver需要找到渲染所用的模板和所用的技术(也就是视图的类型)进行渲染,具体的渲染过程则交由不同的视图自己完成。 RequestToViewNameTranslator initRequestToViewNameTranslator(context),ViewResolver是根据ViewName查找View,但有的 Handler处理完后并没有设置View也没有设置ViewName,这时就需要从request获取ViewName了,如何从request中获取ViewName就是RequestToViewNameTranslator要做的事情了。 RequestToViewNameTranslator在Spring MVC容器里只可以配置一个,所以所有request到 ViewName的转换规则都要在一个Translator里面全部实现。 LocaleResolver initLocaleResolver(context), 解析视图需要两个参数:一是视图名,另一个是Locale。视图名是处理器返回的,Locale是从哪里来的?这就是LocaleResolver要做的事情。LocaleResolver用于从request 解析出Locale,Locale就是zh-cn之类,表示一个区域,有了这个就可以对不同区域的用户显示不同的结果。SpringMVC主要有两个地方用到了Locale:一是ViewResolver视图解析的时候;二是用到国际化资源或者主题的时候。 ThemeResolver initThemeResolver(context),用于解析主题。SpringMVC中一个主题对应一个properties文件,里面存放着跟当前主题相关的所有资源、如图片、css样式等。SpringMVC的主题也支持国际化,同一个主 题不同区域也可以显示不同的风格。SpringMVC中跟主题相关的类有 ThemeResolver、ThemeSource 和Theme。主题是通过一系列资源来具体体现的,要得到一个主题的资源,首先要得到资源的名称,这是ThemeResolver的工作。然后通过主题名称找到对应的主题(可以理解为一个配置)文件,这是 ThemeSource的工作。 后从主题中获取资源就可以了。 MultipartResolver initMultipartResolver(context),用于处理上传请求。处理方法是将普通的request包装成 MultipartHttpServletRequest,后者可以直接调用getFile方法获取File,如果上传多个文件,还可以调用getFileMap得到FileName->File结构的Map。此组件中一共有三个方法,作用分别是判断是不是上传请求,将request包装成MultipartHttpServletRequest、处理完后清理上传过程中产生的临时资源。 FlashMapManager initFlashMapManager(context),用来管理FlashMap的,FlashMap主要用在redirect中传递参数。 Spring Boot 自动配置原理? @Import + @Configuration + Spring spi 自动配置类由各个starter提供,使用@Configuration + @Bean定义配置类,放到META- INF/spring.factories下使用Spring spi扫描META-INF/spring.factories下的配置类 使用@Import导入自动配置类 如何理解 Spring Boot 中的 Starter 使用spring + springmvc使用,如果需要引入mybatis等框架,需要到xml中定义mybatis需要的bean starter就是定义一个starter的jar包,写一个@Configuration配置类、将这些bean定义在里面,然后在 starter包的META-INF/spring.factories中写入该配置类,springboot会按照约定来加载该配置类 开发人员只需要将相应的starter包依赖进应用,进行相应的属性配置(使用默认配置时,不需要配 置),就可以直接进行代码开发,使用对应的功能了,比如mybatis-spring-boot--starter,springboot-starter-redis 什么是嵌入式服务器?为什么要使用嵌入式服务器? 节省了下载安装tomcat,应用也不需要再打war包,然后放到webapp目录下再运行只需要一个安装了 Java 的虚拟机,就可以直接在上面部署应用程序了 springboot已经内置了tomcat.jar,运行main方法时会去启动tomcat,并利用tomcat的spi机制加载 springmvc ———————————————— 版权声明:本文为CSDN博主「陈橘又青」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/m0_63947499/article/details/127218727
-
java 的transient关键字的作用是需要实现Serilizable接口,将不需要序列化的属性前添加关键字transient,序列化对象的时候,这个属性就不会序列化到指定的目的地中。用法@transient 就是在给某个javabean上需要添加个属性,但是这个属性你又不希望给存到数据库中去,仅仅是做个临时变量,用一下。不修改已经存在数据库的数据的数据结构。那么这个注解就可以一用。只要在你准备添加的临时属性上添加这个注解,然后getter和setter自动完成一下,就可以啦。@Document(collection = "streams")public class Stream implements Serializable,Cloneable { private static final long serialVersionUID = 1L; @Transient private boolean alarmsettingsActive = false;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}上面的是个人理解,下面呢就是详细说说。transient的作用 我们都知道一个对象只要实现了Serilizable接口,这个对象就可以被序列化,java的这种序列化模式为开发者提供了很多便利,我们可以不必关系具体序列化的过程,只要这个类实现了Serilizable接口,这个类的所有属性和方法都会自动序列化。 然而在实际开发过程中,我们常常会遇到这样的问题,这个类的有些属性需要序列化,而其他属性不需要被序列化,打个比方,如果一个用户有一些敏感信息(如密码,银行卡号等),为了安全起见,不希望在网络操作(主要涉及到序列化操作,本地序列化缓存也适用)中被传输,这些信息对应的变量就可以加上transient关键字。换句话说,这个字段的生命周期仅存于调用者的内存中而不会写到磁盘里持久化。 总之,java 的transient关键字为我们提供了便利,你只需要实现Serilizable接口,将不需要序列化的属性前添加关键字transient,序列化对象的时候,这个属性就不会序列化到指定的目的地中。transient使用小结1)一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问。2)transient关键字只能修饰变量,而不能修饰方法和类。注意,本地变量是不能被transient关键字修饰的。变量如果是用户自定义类变量,则该类需要实现Serializable接口。3)被transient关键字修饰的变量不再能被序列化,一个静态变量不管是否被transient修饰,均不能被序列化。原文链接:https://blog.csdn.net/rongxiang111/article/details/86476028
-
大家好,这两天在做excel导出功能,使用的是easyExcel,不得不说其功能全面,很好入手。 但是在开发的过程中也会遇到一些文档里无法提供解决的问题,这里我分享代码,方便以后自己观看,也希望能帮到想要导出功能的童鞋。 JAVA 导出 EXCEL入口 注: 大家好,今天更新内容:eastExcel 使用模板下载,本地正常下载,但是发布到服务器上遇到一个一些问题, 目前抽空在看看解决方法,再最下面贴出来希望有大佬看到能指点一二,小弟在这里先谢过了。 我又换了一个方法去实现通过模板下载excel功能 <dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>2.2.0-beta1</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <scope>test</scope> </dependency> @Override public void exportInfo(List<CustomerInfo> customerDealerInfo, HttpServletResponse response) throws IOException { OutputStream out = null; BufferedOutputStream bos = null; try { String templateFileName = FileUtil.getPath() + "templates" + File.separator + "模板.xls"; response.setContentType("application/vnd.ms-excel"); response.setCharacterEncoding("utf-8"); String fileName = URLEncoder.encode("下载后的名称.xls", "utf-8"); response.setHeader("Content-disposition", "attachment; filename=" + new String(fileName.getBytes("UTF-8"), "ISO-8859-1")); out = response.getOutputStream(); bos = new BufferedOutputStream(out); //读取Excel ExcelWriter excelWriter = EasyExcel.write(bos).withTemplate(templateFileName).build(); WriteSheet writeSheet = EasyExcel.writerSheet().build(); //customerDealerInfo 是我查询并需导出的数据,并且里面的字段和excel需要导出的字段对应 // 直接写入Excel数据 excelWriter.fill(customerDealerInfo, writeSheet); excelWriter.finish(); bos.flush(); } catch (Exception e) { // 重置response response.reset(); response.setContentType("application/json"); response.setCharacterEncoding("utf-8"); Map<String, String> map = new HashMap<String, String>(16); map.put("status", "failure"); map.put("message", "下载文件失败" + e.getMessage()); response.getWriter().println(JSON.toJSONString(map)); } } public class FileUtil { public static InputStream getResourcesFileInputStream(String fileName) { return Thread.currentThread().getContextClassLoader().getResourceAsStream("" + fileName); } public static String getPath() { return FileUtil.class.getResource("/").getPath(); } public static File createNewFile(String pathName) { File file = new File(getPath() + pathName); if (file.exists()) { file.delete(); } else { if (!file.getParentFile().exists()) { file.getParentFile().mkdirs(); } } return file; } public static File readFile(String pathName) { return new File(getPath() + pathName); } } 我查询并需导出的数据,并且里面的字段和excel需要导出的字段对应 -------------------------------------------------华丽的分割线------------------------------------------------------------- 大神能看到这里,感激不经,废话不多,请过目 这里是报错行: ExcelWriter excelWriter = EasyExcel.write(bos).withTemplate(templateFileName).build(); com.alibaba.excel.exception.ExcelGenerateException: Create workbook failure at com.alibaba.excel.context.WriteContextImpl.<init>(WriteContextImpl.java:87) at com.alibaba.excel.write.ExcelBuilderImpl.<init>(ExcelBuilderImpl.java:35) at com.alibaba.excel.ExcelWriter.<init>(ExcelWriter.java:47) at com.alibaba.excel.write.builder.ExcelWriterBuilder.build(ExcelWriterBuilder.java:130) at cn.gaialab.gaiamind.microservice.brp.service.ecxport.impl.PlanVisitDataServiceImpl.exportRoutePlanningStore(PlanVisitDataServiceImpl.java:121) at cn.gaialab.gaiamind.microservice.brp.web.ecxport.ExportCustomerInfoController.exportRoutePlanningStore1(ExportCustomerInfoController.java:41) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190) at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138) at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:888) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:793) at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040) at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943) at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898) at javax.servlet.http.HttpServlet.service(HttpServlet.java:634) at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) at javax.servlet.http.HttpServlet.service(HttpServlet.java:741) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:526) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408) at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:861) at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1579) at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Thread.java:748) Caused by: org.apache.poi.openxml4j.exceptions.OLE2NotOfficeXmlFileException: The supplied data appears to be in the OLE2 Format. You are calling the part of POI that deals with OOXML (Office Open XML) Documents. You need to call a different part of POI to process this data (eg HSSF instead of XSSF) at org.apache.poi.openxml4j.opc.internal.ZipHelper.verifyZipHeader(ZipHelper.java:147) at org.apache.poi.openxml4j.opc.internal.ZipHelper.openZipStream(ZipHelper.java:175) at org.apache.poi.openxml4j.opc.ZipPackage.<init>(ZipPackage.java:104) at org.apache.poi.openxml4j.opc.OPCPackage.open(OPCPackage.java:307) at org.apache.poi.ooxml.util.PackageHelper.open(PackageHelper.java:37) at org.apache.poi.xssf.usermodel.XSSFWorkbook.<init>(XSSFWorkbook.java:303) at com.alibaba.excel.util.WorkBookUtil.createWorkBook(WorkBookUtil.java:32) at com.alibaba.excel.context.WriteContextImpl.<init>(WriteContextImpl.java:85) ... 55 common frames omitted 2020-03-24 15:00:49.261 INFO 11336 --- [tbeatExecutor-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_SERVICE-BRP/${eureka.instance.hostname}:service-brp:7081 - Re-registering apps/SERVICE-BRP 2020-03-24 15:00:49.261 INFO 11336 --- [tbeatExecutor-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_SERVICE-BRP/${eureka.instance.hostname}:service-brp:7081: registering service... 2020-03-24 15:00:49.266 INFO 11336 --- [tbeatExecutor-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_SERVICE-BRP/${eureka.instance.hostname}:service-brp:7081 - registration status: 204 2020-03-24 15:02:23.933 INFO 11336 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver : Resolving eureka endpoints via configuration 感言 感谢大家的帮助,问题已经解决,并且重新写了一篇,希望能给看到这的朋友,送上一点绵薄之力。 ———————————————— 原文链接:https://blog.csdn.net/weixin_42153503/article/details/104994360
-
常规使用SpringAOP功能,都是对一个Service中的B方法进行切入记录日志,这些时候AOP是能起作用的。但是假如B方法被Service中的A方法调用,在B方法上的切入便会失效,导致无法记录日志。 要弄清楚切入失败的原因,就要先了解切入的原理。为什么能够切入目标对象,原理就是创建了代理类,在代理类中调用目标方法的前后进行切入。比如说目标对象是service,代理对象是$proxy0,这时候切入对B方法的调用就是$proxy0.B(),执行流程就是先执行切面中的逻辑比如记录日志,然后再invoke调用service的B方法,所以可以切入;但是执行A方法$proxy0.A()只能对A方法起作用,A里面再使用this调用B的时候使用的是service的原目标对象s.(),而不是$proxy0.B(),所以对B的切入无效,因为压根就没有用代理对象去调用,自然不能使用代理对象提供的切面增强功能。 解决方案就是把service目标对象的A方法中对B方法的调用(this.B())改成使用代理对象对B的调用,那么怎么获取代理对象呢?AopContext.currentProxy()使用了保存了代理对象因此在A方法中使用【((Service) AopContext.currentProxy()).B()】就能解决切入失效的问题。 另外,如果使用【AopContext.currentProxy()】方法获取当前代理对象需要在类上添加【@EnableAspectJAutoProxy(exposeProxy=true)】注解 exposeProxy:在AopContext中暴露代理对象。指示代理应由 AOP 框架公开为ThreadLocal以通过AopContext类进行检索。 默认关闭,即不保证可访问的AopContext。 ———————————————— 原文链接:https://blog.csdn.net/qq_16159433/article/details/120952972
-
SpringBoot 注解@Async不生效的解决方法问题如图:这里虽然加了@EnableAsync和@Async,但是异步请求依然没有生效解决方案:方法一:同一个类中调用需要先获取代理对象,也就是手动获取对象12345678910111213141516171819@Service@EnableAsyncpublic class DemoService { public void add(){ DemoService bean = SpringUtil.getBean(DemoService.class); System.out.println("开始"); bean.sendToKafka(); System.out.println("结束"); } @Async public void sendToKafka() { try { Thread.sleep(10000); System.out.println("我睡醒了!!!"); } catch (InterruptedException e) { e.printStackTrace(); } }}方法二:不同的类调用,直接注入即可AsyncHandle.java (异步处理类)12345678910111213@Service@EnableAsyncpublic class AsyncHandle { @Async public void sendToKafka() { try { Thread.sleep(10000); System.out.println("我睡醒了!!!"); } catch (InterruptedException e) { e.printStackTrace(); } }}DemoService.java (业务类)1234567891011@Servicepublic class DemoService { @Autowired private AsyncHandle asyncHandle; public void add(){ System.out.println("开始"); asyncHandle.sendToKafka(); System.out.println("结束"); }}总结:1、在需要用到的@Async注解的类上加上@EnableAsync,或者直接加在springboot启动类上2、异步处理方法(也就是加了@Async注解的方法)只能返回的是void或者Future类型3、同一个类中调用异步方法需要先获取代理类,因为@Async注解是基于Spring AOP (面向切面编程)的,而AOP的实现是基于动态代理模式实现的。有可能因为调用方法的是对象本身而不是代理对象,因为没有经过Spring容器。。。。。。这点很重要,也是经常遇到的。原文链接:https://www.jb51.net/article/236337.htm
-
本文主要讨论 集合Set 中存储对象的 hashCode 与 equals 方法应遵循的约束关系.新手对Set中contains()方法的疑惑 [java] 普通类会继承Object类的一切方法,但是不会重写Object类的方法 import java.util.HashSet; class Dog{ String color; public Dog(String s){ color = s; } } public class SetAndHashCode { public static void main(String[] args) { HashSet dogSet = new HashSet(); dogSet.add(new Dog('white')); dogSet.add(new Dog('white')); System.out.println('We have ' + dogSet.size() + ' white dogs!'); if(dogSet.contains(new Dog('white'))){ System.out.println('We have a white dog!'); }else{ System.out.println('No white dog!'); } } } 上述代码的输出为: [plain] We have 2 white dogs! No white dog! 程序中添加了两只白色的小狗到集合dogSet中. 且 size()方法显示有2只白色的小狗.但为什么用 contains()方法来判断时却提示没有白色的小狗呢? Set的contains(Object o) 方法详解 Java的API文档指出: 当且仅当 本set包含一个元素 e,并且满足(o==null ? e==null : o.equals(e))条件时,contains()方法才返回true. 因此 contains()方法 必定使用equals方法来检查是否相等. 需要注意的是: set 中是可以包含 null值的(常见的集合类都可以包含null值). 所以如果添加了null,然后判断是否包含null,将会返回true,代码如下所示: [java] HashSet a = new HashSet(); a.add(null); if(a.contains(null)){ System.out.println('true'); } Java的根类Object定义了 public boolean equals(Object obj) 方法.因此所有的对象,包括数组(array,[]),都实现了此方法。 在自定义类里,如果没有明确地重写(override)此方法,那么就会使用Object类的默认实现.即只有两个对象(引用)指向同一块内存地址(即同一个实际对象, x==y为true)时,才会返回true。 如果把Dog类修改为如下代码,能实现我们的目标吗? [java class Dog{ String color; public Dog(String s){ color = s; } //重写equals方法, 最佳实践就是如下这种判断顺序: public boolean equals(Object obj) { if (!(obj instanceof Dog)) return false; if (obj == this) return tue; return this.color == ((Dog) obj).color; } } 英文答案是: no. 问题的关键在于 Java中hashCode与equals方法的紧密联系. hashCode() 是Object类定义的另一个基础方法. equals()与hashCode()方法之间的设计实现原则为: 如果两个对象相等(使用equals()方法),那么必须拥有相同的哈希码(使用hashCode()方法). 即使两个对象有相同的哈希值(hash code),他们不一定相等.意思就是: 多个不同的对象,可以返回同一个hash值. hashCode()的默认实现是为不同的对象返回不同的整数.有一个设计原则是,hashCode对于同一个对象,不管内部怎么改变,应该都返回相同的整数值. 在上面的例子中,因为未定义自己的hashCode()实现,因此默认实现对两个对象返回两个不同的整数,这种情况破坏了约定原则。 解决办法 [java] class Dog{ String color; public Dog(String s){ color = s; } //重写equals方法, 最佳实践就是如下这种判断顺序: public boolean equals(Object obj) { if (!(obj instanceof Dog)) return false; if (obj == this) return true; return this.color == ((Dog) obj).color; } public int hashCode(){ return color.length();//简单原则 } } 但是上面的hashCode实现,要求Dog的color是不变的.否则会出现如下的这种困惑: [java] import java.util.HashSet; import java.util.Set; public class TestContains { public static final class Person{ private String name = ''; public Person(String n) { setName(n); }public String getName() { return name; } public void setName(String name) { this.name = (name==null)? '' : name; } @Override public int hashCode() { // 请考虑是否值得这么做,因为此时name是会变的. return name.length(); // 推荐让name不可改变 } @Override public boolean equals(Object obj) { if(!(obj instanceof Person)){ return false; } if(obj == this){ return true; } return this.name.equals(((Person)obj).name); } }; public static void main(String[] args) { Set persons = new HashSet(); // Person person = new Person('tiemao'); persons.add(person); // 修改name, 则依赖hash的集合可能失去作用 person.setName('ren'); // 同一个对象,居然是false,原因是我们重写了hashCode,打破了hashCode不变的基本约定 boolean has = persons.contains(person); int size = persons.size(); System.out.println('has='+has); // has=false. System.out.println('size='+size);// size=1 } } ———————————————— 原文链接:https://blog.csdn.net/weixin_40007548/article/details/114090134
-
一、List列表与Set列表的区别 List列表是有序、可以重复、线程不安全的列表,Set是无序、不能重复、线程不安全的列表。但List和Set可以通过方法来转换为线程安全的,加互斥锁。 Set<Long> set=new HashSet<>(); // 转换为线程安全的集合 Collections.synchronizedSet(set); List<Long> lists=new ArrayList<>(); Collections.synchronizedList(lists); 二、List与Set的转换 1) List转换为set List<Long> ids=new ArrayList<>(); HashSet<Long> set=new HashSet<>(ids); 2) Set转换为List HashSet<Long> set=new HashSet<>(); List<Long> ids=new ArrayList<>(set); 此处都可以使用 addAll()方法来替换。 需要注意的是: List转换为Set的时候,当有重复数据时,转换为出现数据丢失的情况,因为Set集合不允许有重复数据。 ———————————————— 原文链接:https://blog.csdn.net/qq_33036061/article/details/103968822
-
1.显示支付二维码 支付流程 首先用户选好商品后 跳到结算页面 在点击支付提交时应先将表单的数据保存到数据库(一般都会有订单表一二级) 经过后台保存数据后再转发到前台(二维码是后台调用微信生成的) 看到扫码页面 当用户扫码成功后 更改订单状态为已支付(一般情况 根据业务定) 开始首先第一步 当用户选好商品后 点击 支付按钮时发送一个请求到后台控制层 让后跳到扫码页面显示商品信息页面 这个页面应该有一个标签 <img src=”请求的控制类地址” /> 注意下面写的方法 就是输出一个二维码 跳到二维码页面img标签会去访问src这个路径方法 让后开始准备 一般会吧微信支付用到的参数封装成一个类,或者properties,xml,yml等等这样的文件(在企业中基本都是后者)这里使用类 方便一些 导入要使用的jar包和工具类 在控制类中加入以下几个工具方法(也可以封装成工具类) /** * 获取本机IP地址 * @return IP */ private static String getRemortIP(HttpServletRequest request) { if (request.getHeader("x-forwarded-for") == null) { return request.getRemoteAddr(); } return request.getHeader("x-forwarded-for"); } /** * 微信支付签名算法sign */ private String getSign(Map<String,Object> map) { StringBuffer sb = new StringBuffer(); String[] keyArr = (String[]) map.keySet().toArray(new String[map.keySet().size()]);//获取map中的key转为array Arrays.sort(keyArr);//对array排序 for (int i = 0, size = keyArr.length; i < size; ++i) { if ("sign".equals(keyArr[i])) { continue; } sb.append(keyArr[i] + "=" + map.get(keyArr[i]) + "&"); } sb.append("key=" + WeixinPayConfig.key); String sign = Md5Util.string2MD5(sb.toString()); return sign; } /** * 通过返回IO流获取支付地址 * @param in * @return */ private String getElementValue(InputStream in,String key){ SAXReader reader = new SAXReader(); Document document=null; try { document = reader.read(in); } catch (DocumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } Element root = document.getRootElement(); List<Element> childElements = root.elements(); for (Element child : childElements) { System.out.println(child.getName()+":"+child.getStringValue()); if(key.equals(child.getName())){ return child.getStringValue(); } } return null; } /** * 类型转换 * @author chenp * @param matrix * @return */ public static BufferedImage toBufferedImage(BitMatrix matrix) { int width = matrix.getWidth(); int height = matrix.getHeight(); BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { image.setRGB(x, y, matrix.get(x, y) == true ? 0xff000000 : 0xFFFFFFFF); } } return image; } 以上方法都要用到 可以封住成一个工具类 由于是演示这里直接放在当前类中了 上面这些弄完后就可以开工了 首先要测试参数 把刚刚复制的 通过返回IO流获取支付地址getElementValue这个方法里的代码注掉 请求的方法里面 有如下代码 String orderNo=DateUtil.getCurrentDateStr(); // 生成订单号(由项目定) Map<String,Object> map=new HashMap<String,Object>(); map.put("appid", WeixinPayConfig.appid); // 公众账号ID(配置文件里的属性) map.put("mch_id", WeixinPayConfig.mch_id); // 商户号(配置文件里的属性) map.put("device_info", WeixinPayConfig.device_info); // 设备号(配置文件里的属性) map.put("notify_url", WeixinPayConfig.notify_url); // 异步通知地址(配置文件里的属性) map.put("trade_type", "NATIVE"); // 交易类型(表示扫码支付 详情看微信公众平台) map.put("out_trade_no", orderNo); // 商户订单号(由项目定) map.put("body", "测试商品"); // 商品描述(由项目定) map.put("total_fee", 100); // 标价金额(单位 '分') // map.put("spbill_create_ip", getRemortIP(request)); // 终端IP(正式环境下用) map.put("spbill_create_ip", "127.0.0.1"); // 终端IP(测试环境) map.put("nonce_str", StringUtil.getRandomString(30)); // 随机字符串(StringUtil工具类提供) map.put("sign", getSign(map)); // 签名(同类中的getSign方法提供) String xml=XmlUtil.genXml(map); //发送xml (XmlUtil工具类提供) System.out.println(xml); // 发现xml消息(由httpclient-4.5.2.jar,httpcore-4.4.9.jar提供) InputStream in=HttpClientUtil.sendXMLDataByPost(WeixinPayConfig.url, xml).getEntity().getContent(); String code_url=getElementValue(in,""); // 获取二维码地址(同类中的getElementValue方法提供) ``` 让后测试看到下面这段就说明成功了(控制台) 如果错误了可以看一下错误原因 成功后在将getElementValue方法里刚刚注掉的代码解注 然后在访问的方法里继续添加代码 String orderNo=DateUtil.getCurrentDateStr(); // 生成订单号(由项目定) Map<String,Object> map=new HashMap<String,Object>(); map.put("appid", WeixinPayConfig.appid); // 公众账号ID(配置文件里的属性) map.put("mch_id", WeixinPayConfig.mch_id); // 商户号(配置文件里的属性) map.put("device_info", WeixinPayConfig.device_info); // 设备号(配置文件里的属性) map.put("notify_url", WeixinPayConfig.notify_url); // 异步通知地址(配置文件里的属性) map.put("trade_type", "NATIVE"); // 交易类型(表示扫码支付 详情看微信公众平台) map.put("out_trade_no", orderNo); // 商户订单号(由项目定) map.put("body", "测试商品"); // 商品描述(由项目定) map.put("total_fee", 100); // 标价金额(单位 '分') // map.put("spbill_create_ip", getRemortIP(request)); // 终端IP(正式环境下用) map.put("spbill_create_ip", "127.0.0.1"); // 终端IP(测试环境) map.put("nonce_str", StringUtil.getRandomString(30)); // 随机字符串(StringUtil工具类提供) map.put("sign", getSign(map)); // 签名(同类中的getSign方法提供) String xml=XmlUtil.genXml(map); //发送xml (XmlUtil工具类提供) System.out.println(xml); // 发现xml消息(由httpclient-4.5.2.jar,httpcore-4.4.9.jar提供) InputStream in=HttpClientUtil.sendXMLDataByPost(WeixinPayConfig.url, xml).getEntity().getContent(); String code_url=getElementValue(in,"code_url"); // 获取二维码地址(同类中的getElementValue方法提供) MultiFormatWriter multiFormatWriter = new MultiFormatWriter(); Map hints = new HashMap(); BitMatrix bitMatrix = null; try { bitMatrix = multiFormatWriter.encode(code_url, BarcodeFormat.QR_CODE, 250, 250,hints); BufferedImage image = toBufferedImage(bitMatrix); //输出二维码图片流 ImageIO.write(image, "png", response.getOutputStream()); } catch (WriterException e1) { e1.printStackTrace(); } 完成后 测试一下 出现二维码就说明成功了 2.异步请求处理 在1中生成的二维码,用户扫描支付后 将会调用公众平台设置的回调地址 接下来根据回调地址来写方法 然后先是拷贝方法 根1一样 封住成工具类或者放在本类 /** * 是否签名正确,规则是:按参数名称a-z排序,遇到空值的参数不参加签名。 * @return boolean */ public static boolean isTenpaySign(String characterEncoding, SortedMap<Object, Object> packageParams, String API_KEY) { StringBuffer sb = new StringBuffer(); Set es = packageParams.entrySet(); Iterator it = es.iterator(); while(it.hasNext()) { Map.Entry entry = (Map.Entry)it.next(); String k = (String)entry.getKey(); String v = (String)entry.getValue(); if(!"sign".equals(k) && null != v && !"".equals(v)) { sb.append(k + "=" + v + "&"); } } sb.append("key=" + API_KEY); //算出摘要 String mysign = Md5Util.MD5Encode(sb.toString(), characterEncoding).toLowerCase(); String tenpaySign = ((String)packageParams.get("sign")).toLowerCase(); return tenpaySign.equals(mysign); } 然后回调地址的方法里写 //读取参数 InputStream inputStream ; StringBuffer sb = new StringBuffer(); inputStream = req.getInputStream(); String s ; BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8")); while ((s = in.readLine()) != null){ sb.append(s); } in.close(); inputStream.close(); System.out.println("sb:"+sb.toString()); //解析xml成map Map<String, String> m = new HashMap<String, String>(); try { m = XmlUtil.doXMLParse(sb.toString()); } catch (JDOMException e) { // TODO Auto-generated catch block e.printStackTrace(); } //过滤空 设置 TreeMap SortedMap<Object,Object> packageParams = new TreeMap<Object,Object>(); Iterator<String> it = m.keySet().iterator(); while (it.hasNext()) { String parameter = it.next(); String parameterValue = m.get(parameter); String v = ""; if(null != parameterValue) { v = parameterValue.trim(); } packageParams.put(parameter, v); } // 微信支付的API密钥 String key = WeixinPayConfig.key; if(isTenpaySign("UTF-8", packageParams, key)){ // 验证通过 if("SUCCESS".equals((String)packageParams.get("result_code"))){ System.out.println("验证通过"); }else{ System.out.println("支付失败"); } }else{ System.out.println("验证未通过"); } 在验证通过后更改 订单的状态 为已支付 在编写根据订单查询订单状态的方法 在二维码页面每隔多少秒就调用一次查看状态 若为已支付状态就可以跳转页面了 3.HTML在线支付 环境准备 加入 HMACSHA256Uitl.java 工具类 接口调用url private static String url="https://api.mch.weixin.qq.com/pay/downloadfundflow"; 使用到的工具方法 /** * 微信支付签名算法sign */ private static String getSign(Map<String,Object> map) { StringBuffer sb = new StringBuffer(); String[] keyArr = (String[]) map.keySet().toArray(new String[map.keySet().size()]);//获取map中的key转为array Arrays.sort(keyArr);//对array排序 for (int i = 0, size = keyArr.length; i < size; ++i) { if ("sign".equals(keyArr[i])) { continue; } sb.append(keyArr[i] + "=" + map.get(keyArr[i]) + "&"); } sb.append("key=" + WeixinPayConfig.key); // String sign = Md5Util.string2MD5(sb.toString()); String sign=HMACSHA256Uitl.HMACSHA256(sb.toString().getBytes(), WeixinPayConfig.key.getBytes()); return sign; } 然后编写方法 因为是测试 这里使用的是main 实际根据业务更改 public static void main(String[] args) throws UnsupportedOperationException, ClientProtocolException, IOException { Map<String,Object> map=new HashMap<String,Object>(); map.put("appid", WeixinPayConfig.appid); // 公众账号ID map.put("mch_id", WeixinPayConfig.mch_id); // 商户号 map.put("nonce_str", StringUtil.getRandomString(30)); // 随机字符串 map.put("bill_date", "20180419"); // 资金账单日期 map.put("account_type", "Basic"); // 资金账户类型 map.put("sign", getSign(map)); // 签名 String xml=XmlUtil.genXml(map); System.out.println(xml); InputStream in=HttpClientUtil.sendXMLDataByHttpsPost(url, xml).getEntity().getContent(); // 发现xml消息 StringBuffer out=new StringBuffer(); byte []b=new byte[4096]; for(int n;(n=in.read(b))!=-1;){ out.append(new String(b,0,n)); } System.out.println(out.toString()); } 出现以上结果就成功了 出现报错的话 根据提示 更改参数即可 自行根据以上逻辑嵌入到页面中即可 3.查询订单 创建参数 private static String url="https://api.mch.weixin.qq.com/pay/orderquery"; 写入配置文件里即可 由于是测试 这里写在类中 再添加工具方法(getElementValue这个方法有重载 跟1,2里的不一样) /** * 通过返回IO流获取支付地址 * @param in * @return */ private static void getElementValue(InputStream in){ SAXReader reader = new SAXReader(); Document document=null; try { document = reader.read(in); } catch (DocumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } Element root = document.getRootElement(); List<Element> childElements = root.elements(); for (Element child : childElements) { System.out.println(child.getName()+":"+child.getStringValue()); } } 这个和2,1中的方法是同一个 /** * 微信支付签名算法sign */ private static String getSign(Map<String,Object> map) { StringBuffer sb = new StringBuffer(); String[] keyArr = (String[]) map.keySet().toArray(new String[map.keySet().size()]);//获取map中的key转为array Arrays.sort(keyArr);//对array排序 for (int i = 0, size = keyArr.length; i < size; ++i) { if ("sign".equals(keyArr[i])) { continue; } sb.append(keyArr[i] + "=" + map.get(keyArr[i]) + "&"); } sb.append("key=" + WeixinPayConfig.key); String sign = Md5Util.string2MD5(sb.toString()); return sign; } 这里直接写的main方法 具体要求根据业务更改 public static void main(String[] args) throws UnsupportedOperationException, ClientProtocolException, IOException { Map<String,Object> map=new HashMap<String,Object>(); map.put("appid", WeixinPayConfig.appid); // 公众账号ID map.put("mch_id", WeixinPayConfig.mch_id); // 商户号 //map.put("transaction_id", "4200000087201804105653326283"); // 微信订单号 map.put("out_trade_no", "20180405055656553"); // 商户订单号(项目生成的订单号) map.put("mch_id", WeixinPayConfig.mch_id); // 商户号 map.put("nonce_str", StringUtil.getRandomString(30)); // 随机字符串 map.put("sign", getSign(map)); // 签名 String xml=XmlUtil.genXml(map); System.out.println(xml); InputStream in=HttpClientUtil.sendXMLDataByPost(url, xml).getEntity().getContent(); // 发现xml消息 getElementValue(in); } 然后运行 看见结果后 就成功了 如果有错误根据错误提示 排错即可 4.关闭订单 创建参数 private static String url="https://api.mch.weixin.qq.com/pay/closeorder"; 写入配置文件里即可 由于是测试 这里写在类中 这里直接写的main方法 具体要求根据业务更改(还是需要3里面的两个方法) public static void main(String[] args) throws UnsupportedOperationException, ClientProtocolException, IOException { Map<String,Object> map=new HashMap<String,Object>(); map.put("appid", WeixinPayConfig.appid); // 公众账号ID map.put("mch_id", WeixinPayConfig.mch_id); // 商户号 map.put("out_trade_no", "20180404022005421"); // 商户订单号 map.put("nonce_str", StringUtil.getRandomString(30)); // 随机字符串 map.put("sign", getSign(map)); // 签名 String xml=XmlUtil.genXml(map); System.out.println(xml); InputStream in=HttpClientUtil.sendXMLDataByPost(url, xml).getEntity().getContent(); // 发现xml消息 getElementValue(in); } 然后运行 看见结果后 就成功了 如果有错误根据错误提示 排错即可 5.申请退款 首先 退款要用到安全证书 在微信公众平台登陆后 在下面下载下载完后放在磁盘内 然后准备工作 将CertUtil.java 导入在工程内(更改里面的安全证书路径,项目中要配置在配置文件中) 接口调用url private static String url="https://api.mch.weixin.qq.com/secapi/pay/refund"; 使用到的工具方法 /** * 通过返回IO流获取支付地址 * @param in * @return */ private static void getElementValue(InputStream in){ SAXReader reader = new SAXReader(); Document document=null; try { document = reader.read(in); } catch (DocumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } Element root = document.getRootElement(); List<Element> childElements = root.elements(); for (Element child : childElements) { System.out.println(child.getName()+":"+child.getStringValue()); } } /** * 微信支付签名算法sign */ private static String getSign(Map<String,Object> map) { StringBuffer sb = new StringBuffer(); String[] keyArr = (String[]) map.keySet().toArray(new String[map.keySet().size()]);//获取map中的key转为array Arrays.sort(keyArr);//对array排序 for (int i = 0, size = keyArr.length; i < size; ++i) { if ("sign".equals(keyArr[i])) { continue; } sb.append(keyArr[i] + "=" + map.get(keyArr[i]) + "&"); } sb.append("key=" + WeixinPayConfig.key); String sign = Md5Util.string2MD5(sb.toString()); return sign; } public static void main(String[] args) throws UnsupportedOperationException, ClientProtocolException, IOException { Map<String,Object> map=new HashMap<String,Object>(); map.put("appid", WeixinPayConfig.appid); // 公众账号ID map.put("mch_id", WeixinPayConfig.mch_id); // 商户号 map.put("transaction_id", "4200000094201804192059258077"); // 微信订单号 //map.put("out_trade_no", "20180419105343760"); // 商户订单号 map.put("nonce_str", StringUtil.getRandomString(30)); // 随机字符串 map.put("out_refund_no", DateUtil.getCurrentDateStr()); // 商户退款单号 map.put("total_fee", 100); // 订单金额 map.put("refund_fee", 10); // 退款金额 map.put("sign", getSign(map)); // 签名 String xml=XmlUtil.genXml(map); System.out.println(xml); InputStream in=HttpClientUtil.sendXMLDataByHttpsPost(url, xml).getEntity().getContent(); // 发现xml消息 getElementValue(in); } 出现以上结构就成功了 出现报错的话 根据提示 更改参数即可 7 查询退款 接口调用url private static String url="https://api.mch.weixin.qq.com/pay/refundquery "; 使用到的工具方法 /** * 通过返回IO流获取支付地址 * @param in * @return */ private static void getElementValue(InputStream in){ SAXReader reader = new SAXReader(); Document document=null; try { document = reader.read(in); } catch (DocumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } Element root = document.getRootElement(); List<Element> childElements = root.elements(); for (Element child : childElements) { System.out.println(child.getName()+":"+child.getStringValue()); } } /** * 微信支付签名算法sign */ private static String getSign(Map<String,Object> map) { StringBuffer sb = new StringBuffer(); String[] keyArr = (String[]) map.keySet().toArray(new String[map.keySet().size()]);//获取map中的key转为array Arrays.sort(keyArr);//对array排序 for (int i = 0, size = keyArr.length; i < size; ++i) { if ("sign".equals(keyArr[i])) { continue; } sb.append(keyArr[i] + "=" + map.get(keyArr[i]) + "&"); } sb.append("key=" + WeixinPayConfig.key); String sign = Md5Util.string2MD5(sb.toString()); return sign; } 然后编写方法 因为是测试 这里使用的是main 实际根据业务更改 public static void main(String[] args) throws UnsupportedOperationException, ClientProtocolException, IOException { Map<String,Object> map=new HashMap<String,Object>(); map.put("appid", WeixinPayConfig.appid); // 公众账号ID map.put("mch_id", WeixinPayConfig.mch_id); // 商户号 // map.put("transaction_id", "4200000094201804192059258077"); // 微信订单号 // map.put("out_trade_no", "20180419105343760"); // 商户订单号 // map.put("out_refund_no", "20180427112536831"); // 商户退款单号 map.put("refund_id", "50000506552018042704327042706"); // 微信退款单号 map.put("nonce_str", StringUtil.getRandomString(30)); // 随机字符串 // map.put("offset", 1); // 偏移量 map.put("sign", getSign(map)); // 签名 String xml=XmlUtil.genXml(map); System.out.println(xml); InputStream in=HttpClientUtil.sendXMLDataByPost(url, xml).getEntity().getContent(); // 发现xml消息 getElementValue(in); } 出现报错的话 根据提示 更改参数即可 本文资料+工具类+Demo https://pan.baidu.com/s/1pwnfknFwCLV86aBa3wCQoQ ———————————————— 原文链接:https://blog.csdn.net/u014131617/article/details/85631453
-
1.时间类型的处理中遇到的问题时间类型的参数处理中我们可能会遇到字符串类型的时间参数,可能会遇到Date类型的时间参数,可能会遇到DateTime类型的时间参数,可能会到时间戳类型的时间参数,那么他们之间是如果转换的呢?要怎么样才能获得我们需要的时间类型呢?本文中将进行简单转换介绍。2.转换介绍2.1 字符串类型和DateTime类型的相互转换2.1.1 字符类型转成DateTime类型string startTime="2022-10-29 13:34:35"; DateTime parseTime = DateTime.Parse(startTime); 2.1.2 DateTime类型转成String类型DateTime date = new DateTime(); string format = "YYYY-MM-dd-HH-mm-ss"; string formatStr = date.ToString(format); 2.2 时间戳转换为Date2.2.1 获取当前时间的时间戳在获得当前的时间戳的时候,一般是会获取到毫秒级别的,但是我们一般使用只需要到秒就可以了,所以可以除以1000来获取精确到秒的时间戳。此时我们获取的时间戳是long类型数据,我们使用时常常会进行转换,比如转换成字符串类型的时间戳:String timestamp = String.valueOf(new Date().getTime() / 1000); 将字符串类型的时间戳转换成Integer类型的时间戳:int integer = Integer.valueOf(timestamp);2.2.2 将精确到秒的时间戳转换成Date如果想获取到秒的时间戳需要乘以1000在进行new Date的转换,如果不乘转换出来有可能会造成前面年份变成1970年这种的情况。long longTimeStamp = new Long(new Long(timeStamp) * 1000); Date date = new Date(longTimeStamp); 2.2.3 将Date类型转换为格式化好的字符串类型SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dareString = simpleDateFormat.format(date); 2.3 LocalDateTime和Date直接的转换因为我们知道Date和SimpleDateFormat类都是线程不安全的,而现在高并发的处理越来越广泛,因此我们开始越来越多的使用LocalDateTime和DateTimeFormatter因为他们都是线程安全的,那么在使用LocalDateTime的时候就会涉及到转换的问题,常遇到的就是LocalDateTime和Date直接的转换,下面我们进行简单的介绍:2.3.1 Date类型和LocalDate之间的转换Date date = new Date(); Instant instant = date.toInstant(); ZoneId zoneId = ZoneId.systemDefault(); LocalDate localDate = instant.atZone(zoneId).toLocalDate(); ZoneId zoneId = ZoneId.systemDefault(); ZonedDateTime zdt = localDate.atStartOfDay().atZone(zoneId); Date date = Date.from(zdt.toInstant());2.3.2 Date类型和LocalDateTime之间的转换Date date = new Date();Instant instant = date.toInstant();ZoneId zoneId = ZoneId.systemDefault();LocalDateTime localDateTime = instant.atZone(zoneId).toLocalDateTime();ZoneId zoneId = ZoneId.systemDefault();ZonedDateTime zdt = localDateTime.atZone(zoneId);Date date = Date.from(zdt.toInstant());2.3.3 LocalDateTime转字符串LocalDateTime dt = LocalDateTime.now(); String formatTime = dt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));以上就是时间类型转换中所遇到的各种转换问题的简单整理。
-
↵()↵↵:r↵(),↵1.简介在数据类型的处理过程中我们知道有基础的八大数据类型,还有与其对应的java中帮我们封装好的包装器类。为什么数据类型会分为两种类型呢?原因是Java语言是一种面向对象的语言,这种语言中的一切都是对象(万物皆对象),为了的程序编写的方便我们引入了基本数据类型,但是基础数据类型只是用于数据的存储我们没办法去操作这些基本数据类型,因此我们为了能够将这些基本数据类型转化为对象进行操作,就对八大基本数据类型中的每一种基本数据类型都引入了对应的包装类型,这样我们就能通过基本数据类型的包装器类对其进行操作了,而且从java5开始引入了自动装箱/拆箱机制,使得二者可以互相进行转换转换。2.八大基本数据类型对应的包装器类介绍byte(整数类型 8位1个字节) -->对应的包装器类:Byteshort(整数类型 16位2个字节) -->对应的包装器类: Shortint(整数类型 32位4个字节) -->对应的包装器类:Integerlong(整数类型 64位8个字节) -->对应的包装器类:Longfloat (浮点类型 32位4个字节)-->对应的包装器类:Floatdouble(浮点类型 64位8个字节) -->对应的包装器类:Doublechar (字符类型 2个字节)-->对应的包装器类:Characterboolean(布尔类型 1个字节) -->对应的包装器类:Boolean3.类型转换我们知道基本数据类型和他们对应的包装器类之间是可以转换的,下面我们以int、Integer、long、Long之间的转换为例子:int->long:属于小类型向大类型转换,会自动转换;long->int :属于大类型向小类型转换,需要强制转换;int->Integer :属于基本数据类型向包装数据类型转换,会自动封箱,可以通过直接赋值或者new的方式将其装进去;int->Long :属于基本数据类型向包装数据类型转换,会自动封箱,可以通过直接赋值或者new的方式将其装进去;long->Integer : 可以通过Math.toIntExact(xxx)方法进行转换;long->Long : 属于基本数据类型向包装数据类型转换,会自动封箱,可以通过直接赋值或者new的方式将其装进去;Integer->int : 属于包装数据类型向基本数据类型转换,自动拆箱;Integer->long :属于自动拆箱获取对应int类型数据之后会自动转化为long类型;Integer->Long : 不能强制或直接获取,但是可以通过先转换为int/long基本数据类型,然后再进行转换。Long->int : 包装数据类型向小范围的基本数据类型转换,自动拆箱之后需要强制转换。或者调用获取值得api;Long->long : 包装类型转基本类型,自动拆箱;Long->Integer : 不能强制或直接获取,但是可以通过先转换为int/long基本数据类型,然后再进行转换。以上就是基本数据类型和对应的包装器类之间的简单介绍。
-
一,面向对象介绍 面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步- 步实现 ,使用的时候再一个- 一个的依次调用就可以了。 面向对象是把事务分解成为一个个对象,然后由对象之间分工与合作,面向对象编程具有灵活、代码可复用、容易维护和开发的优点 面向对象特性:封装,继承,多态 面向过程 ●优点: 性能比面向对象高,适合跟硬件联系很紧密 的东西,例如单片机就采用的面向过程编程。 ●缺点:没有面向对象易维护、易复用、易扩展。 面向对象 ●优点:易维护、易复用、易扩展,由于面向对象有 封装、继承、多态性的特性,可以设计出低耦合的 系统,使系统更加灵活、更加易于维护 ●缺点: 性能比面向过程低 面向对象的思维特点: 1.抽取(抽象)对象共用的属性和行为组织(封装)成一个类(模板) 2.对类进行实例化,获取类的对象 二,创建生成实例 类construtor 构造函数 constructor()方法是类的构造函数(默认方法) ,用于传递参数返回实例对象,通过new命令生成对象实例时,自动调用该方法。如果没有显示定义类内部会自动给我们创建一 个constructor() (1)通过class关键字创建类,类名习惯性定义首字母大写 (2)类里面的constructor函数,可以接受传递过来的参数,同时返回实例对象 (3)constructor 函数只要new生成实例时,就会自动调用这个函数,即使不写这个函数,类也会自动生成这个函数 (4)生成实例new不能省略 (5)最后注意语法规范,创建类类名后面不要加小括号,生成实例类名后面加小括号,构造函数不需要加function 三,继承 儿子继承父亲的东西 extends 在继承的时候千万不要忘记 extends 哦! 四,super关键字 1.继承中,如果实例化子类输出一个方法,先看子类有没有这个方法,如果有就先执行子类的方法 2.继承中,如果子类里面没有,就去查找父类有没有这个方法,如果有,就执行父类的这个方发(就近原则) 注意: 利用super 调用父类的构造函数super必须在子类的this之前调用 ———————————————— 版权声明:本文为CSDN博主「Aic山鱼」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/zhaochen1127/article/details/127188794
-
依赖下载 micronaut-data-jdbc-3.8.1 时提示500错误信息。cid:link_0错误信息{"errors":[{"status":500,"message":"DownloadFromCacheAndS3Failed, sha1 is 29398d538f5cb6d84486e6b0658f6e19127f8a47"}]}无法正确的下载改依赖信息,期望能通过华为镜像正常的下载依赖。感谢!!!
-
1. 什么是动态代理? 动态代理是通过创建代理对象,在不改变原有代码的基础上,给程序增加新的功能,实现了程序的功能增强。 2.动态代理的实现方式有几种? JDK动态代理 CGLB动态代理 3. JDK动态代理 使用了JDK中的InvocationHandler接口,Method类和Proxy类。JDK的动态代理的要求:目标类必须要有接口。(目标类就是那个我们需要增强功能的方法所在的那个类,也是接口实现类所在的类)。我们通常回给目标类中要增强的方法增加上日志,事务等非业务功能。 4. CGLB动态代理 CGLB动态代理即Code Generation Library,是一个开源的第三方工具库,其原理是继承,去生成目标类的子类对象,这样对子类的功能进行增强。但是要求:目标类不能用final修饰,目标类中的方法也不能被final修饰。 5.动态代理的效率 CGLB动态代理的效率要大于JDK动态代理的效率。 6.为什么要使用动态代理呢? 设计出来动态代理是为了解决一定的需求,因为在我们实际的开发过程中,经常会遇到,要给写好的某个方法的前面或者是后面增加方法,甚至是给好几个类的方法增加新的功能。若是我们直接在方法上写上新的增加的方法,就会显得很乱,而且很冗余。 那么我们初步改进可以通过写一个工具类去解决,在一个工具类中写上我们要增强的方法,但是要求这些增强的方法是静态修饰的,这样我们就可以在原有的代码上,使用类名.方法名的方式,来给原有的功能增加新的功能。这样一定程度上的解决了代码混乱的程度,但是还是不太完美。 再次改进:这个时候就是使用动态代理。 这就体现了动态代理的优势,即在不修改任何原有代码的情况下,实现给原有功能的增强。 7. JDK动态代理详细使用介绍 由于我们在Java项目中,直接使用动态代理的地方不多,所以我们都是使用的aspectj框架中写好的动态代理。aspectj框架中的动态代理就是封装的JDK动态代理。 下面将仔细介绍使用JDK动态代理的步骤: 最好可以先创建一个工具类,将要增强的功能先写好,这样我们便可以一句代码能调动这个用来增强的功能。 自定义类,去实现InvocationHandler接口,并且,重写invoke()方法。 在这个自定义的类中,添加Objeck属性,并且写上有参的构造方法。 在invoke()方法中写上如下代码 然后去主类中,使用动态代理,实现功能的增强。 注意: 上面的JDK动态代理的原理是反射机制。 我们可以使用proxy.getClass().getName()看到的值为com.sun.proxy.$Proxy ———————————————— 版权声明:本文为CSDN博主「白胡子永远」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/weixin_44362089/article/details/127386386
-
【问题来源】【必填】【可选问题来源:某地12345新建AICC项目】【问题简要】:在排队的时候,坐席在强制签入后设置为示忙也会进电话,openeye会振铃,调用的接口为:https://ip:port/agentgateway/resource/onlineagent/{agentid}/forcelogin带的参数为:{password:"1qaz!QAZ",releasephone:"false",agenttype:"4",autoenteridle:"false",phonenum:"8099",autoanswer:false,status:5}希望签入的时候立马示忙 不要进电话测试的主叫号码:15058525652 被叫:2047141 工号:999 分机:8099【问题类别】【可选问题分类:CCGATEWAY问题】【AICC解决方案版本】【必填】【AICC可选择版本:AICC 8.15.0】【UAP可选择版本:UAP9600 V100R005C00SPC026】【CTI可选择版本:ICD V300R008C23】【期望解决时间】越快越好
-
ThrowableThrowable 是 Java 语言中所有错误与异常的超类。Throwable 包含两个子类:Error(错误)和 Exception(异常),它们通常用于指示发生了异常情况。Throwable 包含了其线程创建时线程执行堆栈的快照,它提供了 printStackTrace() 等接口用于获取堆栈跟踪数据等信息。Error(错误)定义:Error 类及其子类。程序中无法处理的错误,表示运行应用程序中出现了严重的错误。特点:此类错误一般表示代码运行时 JVM 出现问题。通常有 VirtualMachineError(虚拟机运行错误)、NoClassDefFoundError(类定义错误)等。比如 OutOfMemoryError:内存不足错误;StackOverflowError:栈溢出错误。此类错误发生时,JVM 将终止线程。这些错误是不受检异常,非代码性错误。因此,当此类错误发生时,应用程序不应该去处理此类错误。按照Java惯例,我们是不应该实现任何新的Error子类的!Exception(异常)程序本身可以捕获并且可以处理的异常。Exception 这种异常又分为两类:运行时异常和编译时异常。运行时异常定义:RuntimeException 类及其子类,表示 JVM 在运行期间可能出现的异常。特点:Java 编译器不会检查它。也就是说,当程序中可能出现这类异常时,倘若既"没有通过throws声明抛出它",也"没有用try-catch语句捕获它",还是会编译通过。比如NullPointerException空指针异常、ArrayIndexOutBoundException数组下标越界异常、ClassCastException类型转换异常、ArithmeticExecption算术异常。此类异常属于不受检异常,一般是由程序逻辑错误引起的,在程序中可以选择捕获处理,也可以不处理。虽然 Java 编译器不会检查运行时异常,但是我们也可以通过 throws 进行声明抛出,也可以通过 try-catch 对它进行捕获处理。如果产生运行时异常,则需要通过修改代码来进行避免。例如,若会发生除数为零的情况,则需要通过代码避免该情况的发生!RuntimeException 异常会由 Java 虚拟机自动抛出并自动捕获(就算我们没写异常捕获语句运行时也会抛出错误!!),此类异常的出现绝大数情况是代码本身有问题应该从逻辑上去解决并改进代码。编译时异常定义: Exception 中除 RuntimeException 及其子类之外的异常。特点: Java 编译器会检查它。如果程序中出现此类异常,比如 ClassNotFoundException(没有找到指定的类异常),IOException(IO流异常),要么通过throws进行声明抛出,要么通过try-catch进行捕获处理,否则不能通过编译。在程序中,通常不会自定义该类异常,而是直接使用系统提供的异常类。该异常我们必须手动在代码里添加捕获语句来处理该异常。2.受检异常与非受检异常Java 的所有异常可以分为受检异常(checked exception)和非受检异常(unchecked exception)。受检异常编译器要求必须处理的异常。正确的程序在运行过程中,经常容易出现的、符合预期的异常情况。一旦发生此类异常,就必须采用某种方式进行处理。除 RuntimeException 及其子类外,其他的 Exception 异常都属于受检异常。编译器会检查此类异常,也就是说当编译器检查到应用中的某处可能会此类异常时,将会提示你处理本异常——要么使用try-catch捕获,要么使用方法签名中用 throws 关键字抛出,否则编译不通过。非受检异常编译器不会进行检查并且不要求必须处理的异常,也就说当程序中出现此类异常时,即使我们没有try-catch捕获它,也没有使用throws抛出该异常,编译也会正常通过。该类异常包括运行时异常(RuntimeException极其子类)和错误(Error)
上滑加载中
推荐直播
-
华为云码道Agent集成与鸿蒙实战2026/08/11 周二 19:00-21:00
王一男-华为云码道产品规划专家;李炎-华为云码道产品专家;彭江敏-华为云鸿蒙端云一体化开发专家
本次直播带你解读华为云码道7月份产品新特性、新功能。更有专家演示码道Agent Space × 钉钉机器集成实战,从0到1打通消息通道;码道鸿蒙端云一体化实战,快速搭建员工签到系统。
回顾中 -
华为云开发者AI素养直播课·第五期2026/09/04 周五 16:00-18:00
林华鼎-华为云AI开发者运营负责人;蒋春阳-华为云AI开发者案例开发专家
本期直播内容: AI工具体验营 · 第5-8课连讲。Agent-Team 多智能体协作完成毕业设计实践
回顾中 -
华为云开发者AI素养ClassRoom·第六期2026/09/08 周二 19:00-20:00
樊渊-2026华为软件挑战赛冠军
高手来了:看软挑高手解析二维排样问题—从工业难题到算法突破
回顾中
热门标签