设计模式常见面试题汇总

面试整理

1、数据结构

常见的数据结构:栈(又称为堆栈)、队列、数组、链表和红黑树

  • 栈:先进后出,是一种运算受限的线性表限制是仅允许在标的一端进行插入和删除操作

  • 队列:简称队,先进先出,队列的入口、出口各占一侧。也是一种运算受限的线性表,仅允许在表的一端进行插入,而在表的另一端进行删除

  • 数组:Array,是有序的元素序列,数组是在内存中开辟一段连续的空间,并在此空间存放元素。查找元素快、增删元素慢 通过for循环遍历 arr[i]

  • 链表:linked list,由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时i动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。查找元素慢、增删元素快

  • 红黑树:本身就是二叉树binary tree ,每个节点最多有两个子树的树结构

Java数据结构面试题及答案

  一、线性表(重点)

  线性表是由N个元素组成的有序序列,也是最常见的一种数据结构。重点有两个数组和链表。

  1、数组

  数组是一种存储单元连续,用来存储固定大小元素的线性表。java中对应的集合实现,比如ArrayList。

  2、链表

  链表又分单链表和双链表,是在物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中指针的链接次序实现的。java中对应的集合实现,比如LinkedList。

  二、栈与队列

  1、栈

  

Java数据结构面试题及答案

  栈,是一种运算受限的线性表,重点掌握其后进先出的特点。表的末端叫栈顶,基本操作有push(进栈)和pop(出栈)。java中stack就是简单的栈实现。

  2、队列

  

Java数据结构面试题及答案

  队列也是一种操作受限制的线性表,重点掌握其先进先出的特点。表的前端只允许进行删除操作,表的后端进行插入操作。进行插入操作的端称为队尾,进行删除操作的端称为队头。java中很多Queue的实现,消息中间件的队列本质也是基于此的。

  三、树(重点)

  在非线性结构里面,树是非常非常重要的一种数据结构。基于其本身的结构优势,尤其在查找领域,应用广泛,其中又以二叉树最为重要。树的话我们这里只重点说一下二叉树。

  1、二叉搜索树

  二叉搜索树又叫二叉查找树,又叫二叉排序树。性质如下:(1) 若左子树不空,则左子树上所有结点的值均小于它的根结点的值;(2) 若右子树不空,则右子树上所有结点的值均大于它的根结点的值;(3) 左、右子树也分别为二叉排序树;(4) 没有键值相等的结点。

  2、平衡二叉树

  

Java数据结构面试题及答案

  平衡二叉树又叫AVL树。性质如下:它的左子树和右子树都是平衡二叉树,且左子树和右子树的深度之差的绝对值不超过1。

  3、红黑树

  

Java数据结构面试题及答案

  红黑树是一种特殊的平衡二叉树,它保证在最坏情况下基本动态集合操作的事件复杂度为O(log n)。

  红黑树放弃了追求完全平衡,追求大致平衡,在与平衡二叉树的时间复杂度相差不大的情况下,保证每次插入最多只需要三次旋转就能达到平衡,实现起来也更为简单。平衡二叉树追求绝对平衡,条件比较苛刻,实现起来比较麻烦,每次插入新节点之后需要旋转的次数不能预知。

2、Java中有几种类型的流?

1.字符流和字节流。

在这里插入图片描述

  1. 字节流继承inputStream和OutputStream
  2. 字符流继承自InputSteamReader和OutputStreamWriter
  3. 总体结构图
    在这里插入图片描述

2.字节流和字符流哪个好?怎么选择?

  1. 大多数情况下使用字节流会更好,因为大多数时候 IO 操作都是直接操作磁盘文件,所以这些流在传输时都是以字节的方式进行的(图片等都是按字节存储的)
  2. 如果对于操作需要通过 IO 在内存中频繁处理字符串的情况使用字符流会好些,因为字符流具备缓冲区,提高了性能

3. 什么是缓冲区?有什么作用?

  1. 缓冲区就是一段特殊的内存区域,很多情况下当程序需要频繁地操作一个资源(如文件或数据库)则性能会很低,所以为了提升性能就可以将一部分数据暂时读写到缓存区,以后直接从此区域中读写数据即可,这样就显著提升了性。
  2. 对于 Java 字符流的操作都是在缓冲区操作的,所以如果我们想在字符流操作中主动将缓冲区刷新到文件则可以使用 flush() 方法操作。

4. 字符流和字节流有什么区别?

字符流和字节流的使用非常相似,但是实际上字节流的操作不会经过缓冲区(内存)而是直接操作文本本身的,而字符流的操作会先经过缓冲区(内存)然后通过缓冲区再操作文件

5. 什么是Java序列化,如何实现Java序列化?

  1. 序列化就是一种用来处理对象流的机制,将对象的内容进行流化。可以对流化后的对象进行读写操作,可以将流化后的对象传输于网络之间。序列化是为了解决在对象流读写操作时所引发的问题
  2. 序列化的实现:将需要被序列化的类实现Serialize接口,没有需要实现的方法,此接口只是为了标注对象可被序列化的,然后使用一个输出流(如:FileOutputStream)来构造一个ObjectOutputStream(对象流)对象,再使用ObjectOutputStream对象的write(Object obj)方法就可以将参数obj的对象写出

6. PrintStream、BufferedWriter、PrintWriter的比较?

  1. PrintStream类的输出功能非常强大,通常如果需要输出文本内容,都应该将输出流包装成PrintStream后进行输出。它还提供其他两项功能。与其他输出流不同,PrintStream 永远不会抛出 IOException;而是,异常情况仅设置可通过 checkError 方法测试的内部标志。另外,为了自动刷新,可以创建一个 PrintStream
  2. BufferedWriter:将文本写入字符输出流,缓冲各个字符从而提供单个字符,数组和字符串的高效写入。通过write()方法可以将获取到的字符输出,然后通过newLine()进行换行操作。BufferedWriter中的字符流必须通过调用flush方法才能将其刷出去。并且BufferedWriter只能对字符流进行操作。如果要对字节流操作,则使用BufferedInputStream
  3. PrintWriter的println方法自动添加换行,不会抛异常,若关心异常,需要调用checkError方法看是否有异常发生,PrintWriter构造方法可指定参数,实现自动刷新缓存(autoflush)

7. BufferedReader属于哪种流,它主要是用来做什么的,它里面有那些经典的方法?

属于处理流中的缓冲流,可以将读取的内容存在内存里面,有readLine()方法,它,用来读取一行

8. 什么是节点流,什么是处理流,它们各有什么用处,处理流的创建有什么特征?

  1. 节点流 直接与数据源相连,用于输入或者输出
  2. 处理流:在节点流的基础上对之进行加工,进行一些功能的扩展
  3. 处理流的构造器必须要 传入节点流的子类

9.流一般需要不需要关闭,如果关闭的话在用什么方法,一般要在那个代码块里面关闭比较好,处理流是怎么关闭的,如果有多个流互相调用传入是怎么关闭的?

  1. 流一旦打开就必须关闭,使用close方法
  2. 放入finally语句块中(finally 语句一定会执行)
  3. 调用的处理流就关闭处理流
  4. 多个流互相调用只关闭最外层的流

10. InputStream里的read()返回的是什么,read(byte[] data)是什么意思,返回的是什么值?

  1. 返回的是所读取的字节的int型(范围0-255)
  2. read(byte [ ] data)将读取的字节储存在这个数组。返回的就是传入数组参数个数

11. OutputStream里面的write()是什么意思,write(byte b[], int off, int len)这个方法里面的三个参数分别是什么意思?

  1. write将指定字节传入数据源
  2. Byte b[ ]是byte数组
  3. b[off]是传入的第一个字符、b[off+len-1]是传入的最后的一个字符 、len是实际长度

3、多线程 线程与进程 并发 并行

  • 并发:指两个或多个事件在同一个时间段内发生。

    例子:一个人吃两个馒头

  • 并行:指两个或多个事件在同一时刻发生(同时发生)。

    例子:两个人吃两个馒头

  • 进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。

    就是进入到内存中的程序

  • 线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。

    简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程

    主线程

    举例

    1. JVM执行main方法,main方法就会进入到栈内存
    2. JVM栈会操作系统开辟出一条main方法通向CPU的执行路径
    3. cup就可以通过这个路径来执行main方法
    4. 这个路径就叫做main(主)线程
  1. 多线程使用的优缺点?

优点:

(1)多线程技术使程序的响应速度更快

(2)当前没有进行处理的任务可以将处理器时间让给其它任务

(3)占用大量处理时间的任务可以定期将处理器时间让给其它任务

(4)可以随时停止任务

(5)可以分别设置各个任务的优先级以及优化性能

缺点:

(1)等候使用共享资源时造成程序的运行速度变慢

(2)对线程进行管理要求额外的cpu开销

(3)可能出现线程死锁情况。即较长时间的等待或资源竞争以及死锁等症状。

  1. start()方法和run()方法简介和区别?

start()方法:

1)用start方法来启动线程,真正实现了多线程运行,这时无需等待run方法体代码执行完毕而直接继续执行下面的代码。

2)通过调用Thread类的start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到CPU时间片,就开始执行run()方法。

run()方法:

1)run()方法只是类的一个普通方法而已,如果直接调用Run方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条。

总结:

1)调用start方法方可启动线程,

2)而run方法只是thread的一个普通方法调用,还是在主线程里执行。

3)把需要并行处理的代码放在run()方法中,start()方法启动线程将自动调用run()方法,这是由jvm的内存机制规定的。

4)并且run()方法必须是public访问权限,返回值类型为void.。

  1. Runnable接口和Callable接口的相同点和不同点?

    img

  2. volatile关键字的作用是什么?

(1)多线程使用volatile关键字修饰的变量,保证了其在多线程之间的可见性,即每次读取到volatile变量,一定是最新的数据

(2)Java代码执行中,为了获取更好的性能JVM可能会对指令进行重排序,多线程下可能会出现一些意想不到的问题。使用volatile则会对禁止语义重排序,当然这也一定程度上降低了代码执行效率

  1. CyclicBarrier和CountDownLatch的区别是什么?

    img

  2. volatile和synchronized对比?

1)volatile本质是在告诉jvm当前变量在寄存器中的值是不确定的,需要从主存中读取,synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住.

2)volatile仅能使用在变量级别,synchronized则可以使用在变量,方法.

3)volatile仅能实现变量的修改可见性,而synchronized则可以保证变量的修改可见性和原子性.  

4)volatile不会造成线程的阻塞,而synchronized可能会造成线程的阻塞.

  1. 怎么唤醒一个阻塞的线程?

如果线程是因为调用了wait()、sleep()或者join()方法而导致的阻塞,可以中断线程,并且通过抛出InterruptedException来唤醒它;如果线程遇到了IO阻塞,无能为力,因为IO是操作系统实现的,Java代码并没有办法直接接触到操作系统。

  1. Java中如何获取到线程dump文件?

dump文件的作用:

死循环、死锁、阻塞、页面打开慢等问题,打线程dump是最好的解决问题的途径。因此,线程dump也就是线程堆栈。

获取到线程堆栈dump文件内容分两步:

(1)第一步:获取到线程的pid,Linux环境下可以使用ps -ef | grep java

(2)第二步:打印线程堆栈,可以通过使用jstack pid命令

  1. sleep方法和wait方法的相同点和不同点?

相同点:

二者都可以让线程处于冻结状态。

不同点:

1)首先应该明确sleep方法是Thread类中定义的方法,而wait方法是Object类中定义的方法。

2)sleep方法必须人为地为其指定时间。

wait方法既可以指定时间,也可以不指定时间。

3)sleep方法时间到,线程处于临时阻塞状态或者运行状态。

wait方法如果没有被设置时间,就必须要通过notify或者notifyAll来唤醒。

4)sleep方法不一定非要定义在同步中。

wait方法必须定义在同步中。

5)当二者都定义在同步中时,

线程执行到sleep,不会释放锁。

线程执行到wait,会释放锁。

  1. 生产者和消费者模型的作用是什么?

1)通过平衡生产者的生产能力和消费者的消费能力来提升整个系统的运行效率,这是生产者消费者模型最重要的作用

2)解耦,这是生产者消费者模型附带的作用,解耦意味着生产者和消费者之间的联系少,联系越少越可以独自发展而不需要收到相互的制约

  1. ThreadLocal的作用是什么?

1)ThreadLocal用来解决多线程程序的并发问题

2)ThreadLocal并不是一个Thread,而是Thread的局部变量,当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本.

3)从线程的角度看,目标变量就象是线程的本地变量,这也是类名中“Local”所要表达的意思。

4)线程局部变量并不是Java的新发明,Java没有提供在语言级支持(语法上),而是变相地通过ThreadLocal的类提供支持.

  1. wait方法和notify/notifyAll方法在放弃对象监视器时有什么区别?

wait()方法立即释放对象监视器;

notify()/notifyAll()方法则会等待线程剩余代码执行完毕才会放弃对象监视器。

  1. Lock和synchronized对比?

1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;

2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;

3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;

4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。

5)Lock可以提高多个线程进行读操作的效率。

6)在JDK1.5中,synchronized是性能低效的。因为这是一个重量级操作,它对性能最大的影响是阻塞式的实现,挂起线程和恢复线程的操作都需要转入内核态中完成,这些操作给系统的并发性带来了很大的压力。相比之下使用Java提供的Lock对象,性能更高一些。

但是,JDK1.6,发生了变化,对synchronize加入了很多优化措施,有自适应自旋,锁消除,锁粗化,轻量级锁,偏向锁等等。导致在JDK1.6上synchronize的性能并不比Lock差。因此。提倡优先考虑使用synchronized来进行同步。

1、具有1-5工作经验的,面对目前流行的技术不知从何下手,

需要突破技术瓶颈的。2、在公司待久了,过得很安逸,

但跳槽时面试碰壁。需要在短时间内进修、跳槽拿高薪的。

3、如果没有工作经验,但基础非常扎实,对java工作机制,

常用设计思想,常用java开发框架掌握熟练的。

4、觉得自己很牛B,一般需求都能搞定。

但是所学的知识点没有系统化,很难在技术领域继续突破的。

\5. 群号:高级架构群 468897908备注好信息!

6.阿里Java高级大牛直播讲解知识点,分享知识,

多年工作经验的梳理和总结,带着大家全面、

科学地建立自己的技术体系和技术认知!

14、ConcurrentHashMap的并发度是什么?

ConcurrentHashMap的并发度就是segment的大小,默认为16,这意味着最多同时可以有16条线程操作ConcurrentHashMap,这也是ConcurrentHashMap对Hashtable的最大优势,任何情况下,Hashtable能同时有两条线程获取Hashtable中的数据∂

15、ReadWriteLock是什么?

ReadWriteLock是一个读写锁接口,ReentrantReadWriteLock是ReadWriteLock接口的一个具体实现,实现了读写的分离,读锁是共享的,写锁是独占的,读和读之间不会互斥,读和写、写和读、写和写之间才会互斥,提升了读写的性能。

16、FutureTask是什么?

FutureTask表示一个异步运算的任务。FutureTask里面可以传入一个Callable的具体实现类,可以对这个异步运算的任务的结果进行等待获取、判断是否已经完成、取消任务等操作。由于FutureTask也是Runnable接口的实现类,所以FutureTask也可以放入线程池中。

17、Java中用到的线程调度算法是什么?

抢占式。一个线程用完CPU之后,操作系统会根据线程优先级、线程饥饿情况等数据算出一个总的优先级并分配下一个时间片给某个线程执行。

18、单例模式的线程安全性?

单例模式的线程安全意味着:某个类的实例在多线程环境下只会被创建一次出来。单例模式有很多种的写法,具体分析如下:

(1)饿汉式单例模式的写法:线程安全

(2)懒汉式单例模式的写法:非线程安全

(3)双检锁单例模式的写法:线程安全

19、什么是乐观锁和悲观锁?

(1)乐观锁:对于并发间操作产生的线程安全问题持乐观状态,乐观锁认为竞争不总是会发生,因此它不需要持有锁,将比较-设置这两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有相应的重试逻辑。

(2)悲观锁:对于并发间操作产生的线程安全问题持悲观状态,悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,就像synchronized,直接对操作资源上了锁。

  1. Java编写一个会导致死锁的程序?

死锁现象描述:

线程A和线程B相互等待对方持有的锁导致程序无限死循环下去。

死锁的实现步骤:

(1)两个线程里面分别持有两个Object对象:lock1和lock2。这两个lock作为同步代码块的锁;

(2)线程1的run()方法中同步代码块先获取lock1的对象锁,Thread.sleep(xxx),时间不需要太多,100毫秒差不多了,然后接着获取lock2的对象锁。这么做主要是为了防止线程1启动一下子就连续获得了lock1和lock2两个对象的对象锁

(3)线程2的run)(方法中同步代码块先获取lock2的对象锁,接着获取lock1的对象锁,当然这时lock1的对象锁已经被线程1锁持有,线程2肯定是要等待线程1释放lock1的对象锁的

这样,线程1″睡觉”睡完,线程2已经获取了lock2的对象锁了,线程1此时尝试获取lock2的对象锁,便被阻塞,此时一个死锁就形成了。

死锁的实现代码:

img

输出结果是:

线程A 锁住资源O1,等待O2

线程B 锁住资源O2,等待O1

4、序列化

把对象以流的方式,写入到文件中保存,叫写对象,也叫序列化

  • 序列化:java.io.ObjectOutputStream 类的 writeObject() 方法可以实现序列化;
  • 反序列化:java.io.ObjectInputStream 类的 readObject() 方法用于实现反序列化。

把文件中保存的对象,以流的方式读取出来,叫做读取数据,也叫对象的反序列化

image-20200913202946493

5、jvm原理

摘要

GC 垃圾收集器

Java 堆内存被划分为新生代和年老代两部分,新生代主要使用复制和标记-清除垃圾回收算法;年老代主要使用标记-整理垃圾回收算法

永久代

指内存的永久保存区域,主要存放 Class 和 Meta(元数据)的信息,Class 在被加载的时候被放入永久区域, 它和和存放实例的区域不同,GC不会在主程序运行期对永久区域进行清理。所以这也导致了永久代的区域会随着加载的 Class 的增多而胀满,最终抛出 OOM 异常。

老年代

主要存放应用程序中生命周期长的内存对象。

JVM 运行时内存

Java 堆从 GC 的角度还可以细分为: 新生代(Eden 区、 From Survivor 区和 To Survivor 区)和老年代。

![img](file://C:/Users/Jared/Desktop/img/%E9%9D%A2%E8%AF%95%E5%B0%8F%E7%BB%93.assets/172d0f3d61d605de?lastModify=1600007113)

23、新生代

是用来存放新生的对象。一般占据堆的 1/3 空间。由于频繁创建对象,所以新生代会频繁触发MinorGC 进行垃圾回收。新生代又分为 Eden区、 ServivorFrom、 ServivorTo 三个区。

57、JVM内存模型

线程独占:栈,本地方法栈,程序计数器

线程共享:堆,方法区

58、栈

又称方法栈,线程私有的,线程执行方法是都会创建一个栈阵,用来存储局部变量表,操作栈,动态链接,方法出口等信息.调用方法时执行入栈,方法返回式执行出栈.

59、本地方法栈

与栈类似,也是用来保存执行方法的信息.执行Java方法是使用栈,执行Native方法时使用本地方法栈.

60、程序计数器

保存着当前线程执行的字节码位置,每个线程工作时都有独立的计数器,只为执行Java方法服务,执行Native方法时,程序计数器为空.

61、堆

JVM内存管理最大的一块,对被线程共享,目的是存放对象的实例,几乎所欲的对象实例都会放在这里,当堆没有可用空间时,会抛出OOM异常.根据对象的存活周期不同,JVM把对象进行分代管理,由垃圾回收器进行垃圾的回收管理

62、方法区

又称非堆区,用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器优化后的代码等数据.1.7的永久代和1.8的元空间都是方法区的一种实现。

前言

关于JVM系列面试知识点总结了一个思维导图,分享给大家

img

1、java中会存在内存泄漏吗,请简单描述。

会。自己实现堆载的数据结构时有可能会出现内存泄露。

2、64 位 JVM 中,int 的长度是多数?

Java 中,int 类型变量的长度是一个固定值,与平台无关,都是 32 位。意思就是说,在 32 位 和 64 位 的 Java 虚拟机中,int 类型的长度是相同的。

3、Serial 与 Parallel GC 之间的不同之处?

Serial 与 Parallel 在 GC 执行的时候都会引起 stop-the-world。它们之间主要不同 serial 收集器是默认的复制收集器,执行 GC 的时候只有一个线程,而parallel 收集器使用多个 GC 线程来执行。

4、32 位和 64 位的 JVM,int 类型变量的长度是多数?

32 位和 64 位的 JVM 中,int 类型变量的长度是相同的,都是 32 位或者 4个字节。

5、Java 中 WeakReference 与 SoftReference 的区别?

虽然 WeakReference 与 SoftReference 都有利于提高 GC 和 内存的效率,但是 WeakReference ,一旦失去最后一个强引用,就会被 GC回收,而软引用虽然不能阻止被回收,但是可以延迟到 JVM 内存不足的时候。

6、JVM 选项 -XX:+UseCompressedOops 有什么作用?为什么要使用

当你将你的应用从 32 位的 JVM 迁移到 64 位的 JVM 时,由于对象的指针从32 位增加到了 64 位,因此堆内存会突然增加,差不多要翻倍。这也会对 CPU缓存(容量比内存小很多)的数据产生不利的影响。因为,迁移到 64 位的 JVM主要动机在于可以指定最大堆大小,通过压缩OOP 可以节省一定的内存。通过-XX:+UseCompressedOops 选项,JVM 会使用 32 位的 OOP,而不是 64 位的 OOP。

7、怎样通过 Java 程序来判断 JVM 是 32 位 还是 64位?

你可以检查某些系统属性如 sun.arch.data.model 或 os.arch 来获取该信息。

8、32 位 JVM 和 64 位 JVM 的最大堆内存分别是多数?

理论上说上 32 位的 JVM 堆内存可以到达 2^32,即 4GB,但实际上会比这个小很多。不同操作系统之间不同,如 Windows 系统大约 1.5GB,Solaris 大约3GB。64 位 JVM 允许指定最大的堆内存,理论上可以达到 2^64,这是一个非常大的数字,实际上你可以指定堆内存大小到 100GB。甚至有的 JVM,如 Azul,堆内存到 1000G 都是可能的。

9、JRE、JDK、JVM 及 JIT 之间有什么不同?

JRE 代表 Java 运行时(Java run-time),是运行 Java 引用所必须的。JDK 代表 Java 开发工具(Java development kit),是 Java 程序打开发工具,如 Java编译器,它也包含 JRE。JVM 代表 Java 虚拟机(Java virtual machine),它的责任是运行 Java 应用。JIT 代表即时编译(Just In Time compilation),当代码执行的次数超过一定的阈值时,会将 Java 字节码转换为本地代码,如,主要的热点代码会被准换为本地代码,这样有利大幅度提高 Java 应用的性能。

10、解释 Java 堆空间及 GC?

当通过 Java 命令启动 Java 进程的时候,会为它分配内存。内存的一部分用于创建堆空间,当程序中创建对象的时候,就从对空间中分配内存。GC 是 JVM 内部的一个进程,回收无效对象的内存用于将来的分配

11、JVM 内存区域

img

JVM 内存区域主要分为线程私有区域【程序计数器、虚拟机栈、本地方法区】、线程共享区域【JAVA 堆、方法区】、直接内存。

线程私有数据区域生命周期与线程相同, 依赖用户线程的启动/结束 而 创建/销毁(在 Hotspot VM 内, 每个线程都与操作系统的本地线程直接映射, 因此这部分内存区域的存/否跟随本地线程的生/死对应)。

线程共享区域随虚拟机的启动/关闭而创建/销毁。

直接内存并不是 JVM 运行时数据区的一部分, 但也会被频繁的使用: 在 JDK 1.4 引入的 NIO 提供了基于Channel与 Buffer的IO方式, 它可以使用Native函数库直接分配堆外内存, 然后使用DirectByteBuffer 对象作为这块内存的引用进行操作(详见: Java I/O 扩展), 这样就避免了在 Java堆和 Native 堆中来回复制数据, 因此在一些场景中可以显著提高性能。

img

12、程序计数器(线程私有)

一块较小的内存空间, 是当前线程所执行的字节码的行号指示器,每条线程都要有一个独立的程序计数器,这类内存也称为“线程私有” 的内存。

正在执行 java 方法的话,计数器记录的是虚拟机字节码指令的地址(当前指令的地址) 。如果还是 Native 方法,则为空。

这个内存区域是唯一一个在虚拟机中没有规定任何 OutOfMemoryError 情况的区域。

13、虚拟机栈(线程私有)

是描述java方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。 每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

栈帧( Frame)是用来存储数据和部分过程结果的数据结构,同时也被用来处理动态链接(Dynamic Linking)、 方法返回值和异常分派(Dispatch Exception)。 栈帧随着方法调用而创建,随着方法结束而销毁——无论方法是正常完成还是异常完成(抛出了在方法内未被捕获的异常)都算作方法结束。

img

14、本地方法区(线程私有)

本地方法区和 Java Stack 作用类似, 区别是虚拟机栈为执行 Java 方法服务, 而本地方法栈则为Native 方法服务, 如果一个 VM 实现使用 C-linkage 模型来支持 Native 调用, 那么该栈将会是一个C 栈,但 HotSpot VM 直接就把本地方法栈和虚拟机栈合二为一 。

15、你能保证 GC 执行吗?

不能,虽然你可以调用 System.gc() 或者 Runtime.gc(),但是没有办法保证 GC的执行。

16、怎么获取 Java 程序使用的内存?堆使用的百分比?

可以通过 java.lang.Runtime 类中与内存相关方法来获取剩余的内存,总内存及最大堆内存。通过这些方法你也可以获取到堆使用的百分比及堆内存的剩余空间。Runtime.freeMemory() 方法返回剩余空间的字节数,Runtime.totalMemory()方法总内存的字节数,Runtime.maxMemory() 返回最大内存的字节数。

17、Java 中堆和栈有什么区别?

JVM 中堆和栈属于不同的内存区域,使用目的也不同。栈常用于保存方法帧和局部变量,而对象总是在堆上分配。栈通常都比堆小,也不会在多个线程之间共享,而堆被整个 JVM 的所有线程共享。

18、描述一下 JVM 加载 class 文件的原理机制

JVM 中类的装载是由类加载器(ClassLoader)和它的子类来实现的,Java 中各类加载器是一个重要的 Java 运行时系统组件,它负责在运行时查找和装入类文件中的类。

由于 Java 的跨平台性,经过编译的 Java 源程序并不是一个可执行程序,而是一个或多个类文件。当 Java 程序需要使用某个类时,JVM 会确保这个类已经被加载、连接(验证、准备和解析)和初始化。类的加载是指把类的.class 文件中的数据读入到内存中,通常是创建一个字节数组读入.class 文件,然后产生与所加载类对应的 Class 对象。

加载完成后,Class 对象还不完整,所以此时的类还不可用。当类被加载后就进入连接阶段,这一阶段包括验证、准备(为静态变量分配内存并设置默认的初始值)和解析(将符号引用替换为直接引用)三个步骤。最后 JVM 对类进行初始化,包括:1)如果类存在直接的父类并且这个类还没有被初始化,那么就先初始化父类;2)如果类中存在初始化语句,就依次执行这些初始化语句。

类的加载是由类加载器完成的,类加载器包括:根加载器(BootStrap)、扩展加载器(Extension)、系统加载器(System)和用户自定义类加载器(java.lang.ClassLoader 的子类)。

从 Java 2(JDK 1.2)开始,类加载过程采取了父亲委托机制(PDM)。PDM 更好的保证了 Java 平台的安全性,在该机制中,JVM 自带的Bootstrap 是根加载器,其他的加载器都有且仅有一个父类加载器。类的加载首先请求父类加载器加载,父类加载器无能为力时才由其子类加载器自行加载。JVM 不会向 Java 程序提供对 Bootstrap 的引用。下面是关于几个类

加载器的说明:

(1)Bootstrap:一般用本地代码实现,负责加载 JVM 基础核心类库(rt.jar);

(2)Extension:从 java.ext.dirs 系统属性所指定的目录中加载类库,它的父加载器是 Bootstrap;

(3)System:又叫应用类加载器,其父类是 Extension。它是应用最广泛的类加载器。它从环境变量 classpath 或者系统属性

java.class.path 所指定的目录中记载类,是用户自定义加载器的默认父加载器。

19、GC 是什么?为什么要有 GC?

GC 是垃 圾收 集的 意思 ,内存 处理 是编 程人 员容 易出 现问 题的 地方 ,忘记 或者 错误的内 存回 收会 导致 程序 或系 统的 不稳 定甚 至崩 溃, Java 提供 的 GC 功能 可以 自动监测 对象 是否 超过 作用 域从 而达 到自 动回 收内 存的 目的 ,Java 语言 没有 提供 释放已分配内存的 显示 操作 方法 。Java 程序 员不 用担 心内 存管 理, 因为 垃圾 收集 器会自动 进行 管理 。要 请求 垃圾 收集 ,可 以调 用下 面的 方法 之一 :System.gc() 或Runtime.getRuntime().gc() ,但 JVM 可以 屏蔽 掉线 示的 垃圾 回收 调用 。

垃圾回收可以有效的防止内存泄露,有效的使用可以使用的内存。垃圾回收器通常是作为一个单独的低优先级的线程运行,不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收,程序员不能实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收。在 Java 诞生初期,垃圾回收是 Java最大的亮点之一,因为服务器端的编程需要有效的防止内存泄露问题,然而时过境迁,如今 Java 的垃圾回收机制已经成为被诟病的东。移动智能终端用户通常觉得 iOS 的系统比 Android 系统有更好的用户体验,其中一个深层次的原因就在于 Android 系统中垃圾回收的不可预知性。

20、堆(Heap-线程共享) -运行时数据区

是被线程共享的一块内存区域, 创建的对象和数组都保存在 Java 堆内存中,也是垃圾收集器进行垃圾收集的最重要的内存区域。 由于现代VM 采用分代收集算法, 因此 Java 堆从 GC 的角度还可以细分为: 新生代(Eden 区、 From Survivor 区和 To Survivor 区)和老年代。

21、方法区/永久代(线程共享)

即我们常说的永久代(Permanent Generation), 用于存储被 JVM 加载的类信息、常量、静态变量即、时编译器编译后的代码等数据.HotSpot VM把GC分代收集扩展至方法区, 即使用Java堆的永久代来实现方法区, 这样 HotSpot 的垃圾收集器就可以像管理 Java 堆一样管理这部分内存,而不必为方法区开发专门的内存管理器(永久带的内存回收的主要目标是针对常量池的回收和类型的卸载, 因此收益一般很小) 。

运行时常量池(Runtime Constant Pool)是方法区的一部分。 Class 文件中除了有类的版本、字段、方法、接口等描述等信息外,还有一项信息是常量池 (Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。 Java 虚拟机对 Class 文件的每一部分(自然也包括常量池)的格式都有严格的规定,每一个字节用于存储哪种数据都必须符合规范上的要求,这样才会被虚拟机认可、装载和执行。

22、JVM 运行时内存

Java 堆从 GC 的角度还可以细分为: 新生代(Eden 区、 From Survivor 区和 To Survivor 区)和老年代。

img

23、新生代

是用来存放新生的对象。一般占据堆的 1/3 空间。由于频繁创建对象,所以新生代会频繁触发MinorGC 进行垃圾回收。新生代又分为 Eden区、 ServivorFrom、 ServivorTo 三个区。

Eden 区

Java 新对象的出生地(如果新创建的对象占用内存很大,则直接分配到老年代)。当 Eden 区内存不够的时候就会触发 MinorGC,对新生代区进行一次垃圾回收。

ServivorFrom

上一次 GC 的幸存者,作为这一次 GC 的被扫描者。

ServivorTo

保留了一次 MinorGC 过程中的幸存者。

MinorGC 的过程(复制->清空->互换)

MinorGC 采用复制算法。

(1) eden、 servicorFrom 复制到 ServicorTo,年龄+1

首先,把 Eden 和 ServivorFrom 区域中存活的对象复制到 ServicorTo 区域(如果有对象的年龄以及达到了老年的标准,则赋值到老年代区),同时把这些对象的年龄+1(如果 ServicorTo 不够位置了就放到老年区);

(2)清空 eden、 servicorFrom

然后,清空 Eden 和 ServicorFrom 中的对象;

(3) ServicorTo 和 ServicorFrom 互换

最后, ServicorTo 和 ServicorFrom 互换,原 ServicorTo 成为下一次 GC 时的 ServicorFrom区。

24、老年代

主要存放应用程序中生命周期长的内存对象。

老年代的对象比较稳定,所以 MajorGC 不会频繁执行。在进行 MajorGC 前一般都先进行了一次MinorGC,使得有新生代的对象晋身入老年代,导致空间不够用时才触发。当无法找到足够大的连续空间分配给新创建的较大对象时也会提前触发一次 MajorGC 进行垃圾回收腾出空间。

MajorGC 采用标记清除算法:首先扫描一次所有老年代,标记出存活的对象,然后回收没有标记的对象。 ajorGC 的耗时比较长,因为要扫描再回收。 MajorGC 会产生内存碎片,为了减少内存损耗,我们一般需要进行合并或者标记出来方便下次直接分配。当老年代也满了装不下的时候,就会抛出 OOM(Out of Memory)异常。

25、永久代

指内存的永久保存区域,主要存放 Class 和 Meta(元数据)的信息,Class 在被加载的时候被放入永久区域, 它和和存放实例的区域不同,GC不会在主程序运行期对永久区域进行清理。所以这也导致了永久代的区域会随着加载的 Class 的增多而胀满,最终抛出 OOM 异常。

26、JAVA8 与元数据

在 Java8 中, 永久代已经被移除,被一个称为“元数据区”(元空间)的区域所取代。元空间的本质和永久代类似,元空间与永久代之间最大的区别在于: 元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。 类的元数据放入nativememory, 字符串池和类的静态变量放入 java 堆中, 这样可以加载多少类的元数据就不再由MaxPermSize 控制, 而由系统的实际可用空间来控制。

27、引用计数法

在 Java 中,引用和对象是有关联的。如果要操作对象则必须用引用进行。因此,很显然一个简单的办法是通过引用计数来判断一个对象是否可以回收。简单说,即一个对象如果没有任何与之关联的引用, 即他们的引用计数都不为 0, 则说明对象不太可能再被用到,那么这个对象就是可回收对象。

28、可达性分析

为了解决引用计数法的循环引用问题, Java 使用了可达性分析的方法。通过一系列的“GC roots”对象作为起点搜索。如果在“GC roots”和一个对象之间没有可达路径,则称该对象是不可达的。要注意的是,不可达对象不等价于可回收对象, 不可达对象变为可回收对象至少要经过两次标记过程。两次标记后仍然是可回收对象,则将面临回收。

29、标记清除算法( Mark-Sweep)

最基础的垃圾回收算法,分为两个阶段,标注和清除。标记阶段标记出所有需要回收的对象,清除阶段回收被标记的对象所占用的空间。如图

img

从图中我们就可以发现,该算法最大的问题是内存碎片化严重,后续可能发生大对象不能找到可利用空间的问题。

30、复制算法(copying)

为了解决 Mark-Sweep 算法内存碎片化的缺陷而被提出的算法。按内存容量将内存划分为等大小的两块。每次只使用其中一块,当这一块内存满后将尚存活的对象复制到另一块上去,把已使用的内存清掉,如图:

img

这种算法虽然实现简单,内存效率高,不易产生碎片,但是最大的问题是可用内存被压缩到了原本的一半。且存活对象增多的话, Copying算法的效率会大大降低。

31、标记整理算法(Mark-Compact)

结合了以上两个算法,为了避免缺陷而提出。标记阶段和 Mark-Sweep 算法相同, 标记后不是清理对象,而是将存活对象移向内存的一端。然后清除端边界外的对象。如图:

img

32、分代收集算法

分代收集法是目前大部分 JVM 所采用的方法,其核心思想是根据对象存活的不同生命周期将内存划分为不同的域,一般情况下将 GC 堆划分为老生代(Tenured/Old Generation)和新生代(YoungGeneration)。老生代的特点是每次垃圾回收时只有少量对象需要被回收,新生代的特点是每次垃圾回收时都有大量垃圾需要被回收,因此可以根据不同区域选择不同的算法。

33、新生代与复制算法

目前大部分 JVM 的 GC 对于新生代都采取 Copying 算法,因为新生代中每次垃圾回收都要回收大部分对象,即要复制的操作比较少,但通常并不是按照 1: 1 来划分新生代。一般将新生代划分为一块较大的 Eden 空间和两个较小的 Survivor 空间(From Space, To Space),每次使用Eden 空间和其中的一块 Survivor 空间,当进行回收时,将该两块空间中还存活的对象复制到另一块 Survivor 空间中。

img

34、老年代与标记复制算法

而老年代因为每次只回收少量对象,因而采用 Mark-Compact 算法。

(1)JAVA 虚拟机提到过的处于方法区的永生代(Permanet Generation), 它用来存储 class 类,常量,方法描述等。对永生代的回收主要包括废弃常量和无用的类。

(2)对象的内存分配主要在新生代的 Eden Space 和 Survivor Space 的 From Space(Survivor 目前存放对象的那一块),少数情况会直接分配到老生代。

(3)当新生代的 Eden Space 和 From Space 空间不足时就会发生一次 GC,进行 GC 后, EdenSpace 和 From Space 区的存活对象会被挪到 To Space,然后将 Eden Space 和 FromSpace 进行清理。

(4)如果 To Space 无法足够存储某个对象,则将这个对象存储到老生代。

(5)在进行 GC 后,使用的便是 Eden Space 和 To Space 了,如此反复循环。

(6)当对象在 Survivor 去躲过一次 GC 后,其年龄就会+1。 默认情况下年龄到达 15 的对象会被移到老生代中。

35、JAVA 强引用

在 Java 中最常见的就是强引用, 把一个对象赋给一个引用变量,这个引用变量就是一个强引用。当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的,即使该对象以后永远都不会被用到 JVM 也不会回收。因此强引用是造成 Java 内存泄漏的主要原因之一。

36、JAVA软引用

软引用需要用 SoftReference 类来实现,对于只有软引用的对象来说,当系统内存足够时它不会被回收,当系统内存空间不足时它会被回收。软引用通常用在对内存敏感的程序中。

37、JAVA弱引用

弱引用需要用 WeakReference 类来实现,它比软引用的生存期更短,对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管 JVM 的内存空间是否足够,总会回收该对象占用的内存。

38、JAVA虚引用

虚引用需要 PhantomReference 类来实现,它不能单独使用,必须和引用队列联合使用。 虚引用的主要作用是跟踪对象被垃圾回收的状态。

39、分代收集算法

当前主流 VM 垃圾收集都采用”分代收集” (Generational Collection)算法, 这种算法会根据对象存活周期的不同将内存划分为几块, 如 JVM 中的新生代、老年代、永久代, 这样就可以根据各年代特点分别采用最适当的 GC 算法

40、在新生代-复制算法

每次垃圾收集都能发现大批对象已死, 只有少量存活. 因此选用复制算法, 只需要付出少量存活对象的复制成本就可以完成收集

41、在老年代-标记整理算法

因为对象存活率高、没有额外空间对它进行分配担保, 就必须采用“标记—清理”或“标记—整理” 算法来进行回收, 不必进行内存复制, 且直接腾出空闲内存。

42、分区收集算法

分区算法则将整个堆空间划分为连续的不同小区间, 每个小区间独立使用, 独立回收. 这样做的好处是可以控制一次回收多少个小区间 , 根据目标停顿时间, 每次合理地回收若干个小区间(而不是整个堆), 从而减少一次 GC 所产生的停顿。

43、GC 垃圾收集器

Java 堆内存被划分为新生代和年老代两部分,新生代主要使用复制和标记-清除垃圾回收算法;年老代主要使用标记-整理垃圾回收算法,因此 java 虚拟中针对新生代和年老代分别提供了多种不同的垃圾收集器, JDK1.6 中 Sun HotSpot 虚拟机的垃圾收集器如下:

img

44、Serial 垃圾收集器(单线程、 复制算法)

Serial(英文连续) 是最基本垃圾收集器,使用复制算法,曾经是JDK1.3.1 之前新生代唯一的垃圾收集器。 Serial 是一个单线程的收集器,它不但只会使用一个 CPU 或一条线程去完成垃圾收集工作,并且在进行垃圾收集的同时,必须暂停其他所有的工作线程,直到垃圾收集结束。

Serial 垃圾收集器虽然在收集垃圾过程中需要暂停所有其他的工作线程,但是它简单高效,对于限定单个 CPU 环境来说,没有线程交互的开销,可以获得最高的单线程垃圾收集效率,因此 Serial垃圾收集器依然是 java 虚拟机运行在 Client 模式下默认的新生代垃圾收集器。

45、ParNew 垃圾收集器(Serial+多线程)

ParNew 垃圾收集器其实是 Serial 收集器的多线程版本,也使用复制算法,除了使用多线程进行垃圾收集之外,其余的行为和 Serial 收集器完全一样, ParNew 垃圾收集器在垃圾收集过程中同样也要暂停所有其他的工作线程。

ParNew 收集器默认开启和 CPU 数目相同的线程数,可以通过-XX:ParallelGCThreads 参数来限制垃圾收集器的线程数。 【Parallel:平行的】

ParNew 虽然是除了多线程外和Serial 收集器几乎完全一样,但是ParNew垃圾收集器是很多 java虚拟机运行在 Server 模式下新生代的默认垃圾收集器。

46、Parallel Scavenge 收集器(多线程复制算法、高效)

Parallel Scavenge 收集器也是一个新生代垃圾收集器,同样使用复制算法,也是一个多线程的垃圾收集器, 它重点关注的是程序达到一个可控制的吞吐量(Thoughput, CPU 用于运行用户代码的时间/CPU 总消耗时间,即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)),高吞吐量可以最高效率地利用 CPU 时间,尽快地完成程序的运算任务,主要适用于在后台运算而不需要太多交互的任务。 自适应调节策略也是 ParallelScavenge 收集器与 ParNew 收集器的一个重要区别。

47、Serial Old 收集器(单线程标记整理算法 )

Serial Old 是 Serial 垃圾收集器年老代版本,它同样是个单线程的收集器,使用标记-整理算法,这个收集器也主要是运行在 Client 默认的

java 虚拟机默认的年老代垃圾收集器。在 Server 模式下,主要有两个用途:

(1)在 JDK1.5 之前版本中与新生代的 Parallel Scavenge 收集器搭配使用。

(2)作为年老代中使用 CMS 收集器的后备垃圾收集方案。新生代 Serial 与年老代 Serial Old 搭配垃圾收集过程图:

img

新生代 Parallel Scavenge 收集器与 ParNew 收集器工作原理类似,都是多线程的收集器,都使用的是复制算法,在垃圾收集过程中都需要暂停所有的工作线程。新生代 ParallelScavenge/ParNew 与年老代 Serial Old 搭配垃圾收集过程图:

img

48、Parallel Old 收集器(多线程标记整理算法)

Parallel Old 收集器是Parallel Scavenge的年老代版本,使用多线程的标记-整理算法,在 JDK1.6才开始提供。

在 JDK1.6 之前,新生代使用 ParallelScavenge 收集器只能搭配年老代的 Serial Old 收集器,只能保证新生代的吞吐量优先,无法保证整体的吞吐量, Parallel Old 正是为了在年老代同样提供吞吐量优先的垃圾收集器, 如果系统对吞吐量要求比较高,可以优先考虑新生代Parallel Scavenge和年老代 Parallel Old 收集器的搭配策略。

新生代 Parallel Scavenge 和年老代 Parallel Old 收集器搭配运行过程图

img

49、CMS 收集器(多线程标记清除算法)

Concurrent mark sweep(CMS)收集器是一种年老代垃圾收集器,其最主要目标是获取最短垃圾回收停顿时间, 和其他年老代使用标记-整理算法不同,它使用多线程的标记-清除算法。最短的垃圾收集停顿时间可以为交互比较高的程序提高用户体验。CMS 工作机制相比其他的垃圾收集器来说更复杂。整个过程分为以下 4 个阶段:

初始标记

只是标记一下 GC Roots 能直接关联的对象,速度很快,仍然需要暂停所有的工作线程。

并发标记

进行 GC Roots 跟踪的过程,和用户线程一起工作,不需要暂停工作线程。

重新标记

为了修正在并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,仍然需要暂停所有的工作线程。

并发清除

清除 GC Roots 不可达对象,和用户线程一起工作,不需要暂停工作线程。由于耗时最长的并发标记和并发清除过程中,垃圾收集线程可以和用户现在一起并发工作, 所以总体上来看CMS 收集器的内存回收和用户线程是一起并发地执行。CMS 收集器工作过程

img

50、G1 收集器

Garbage first 垃圾收集器是目前垃圾收集器理论发展的最前沿成果,相比与 CMS 收集器, G1 收集器两个最突出的改进是:

(1)基于标记-整理算法,不产生内存碎片。

(2)可以非常精确控制停顿时间,在不牺牲吞吐量前提下,实现低停顿垃圾回收。G1 收集器避免全区域垃圾收集,它把堆内存划分为大小固定的几个独立区域,并且跟踪这些区域的垃圾收集进度,同时在后台维护一个优先级列表,每次根据所允许的收集时间, 优先回收垃圾最多的区域。区域划分和优先级区域回收机制,确保 G1 收集器可以在有限时间获得最高的垃圾收集效率

51、JVM 类加载机制

JVM 类加载机制分为五个部分:加载,验证,准备,解析,初始化,下面我们就分别来看一下这五个过程。

img

加载

加载是类加载过程中的一个阶段, 这个阶段会在内存中生成一个代表这个类的 java.lang.Class 对象, 作为方法区这个类的各种数据的入口。注意这里不一定非得要从一个 Class 文件获取,这里既可以从 ZIP 包中读取(比如从 jar 包和 war 包中读取),也可以在运行时计算生成(动态代理),也可以由其它文件生成(比如将 JSP 文件转换成对应的 Class 类)。

验证

这一阶段的主要目的是为了确保 Class 文件的字节流中包含的信息是否符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。

准备

准备阶段是正式为类变量分配内存并设置类变量的初始值阶段,即在方法区中分配这些变量所使用的内存空间。注意这里所说的初始值概念,比如一个类变量定义为:

实际上变量 v 在准备阶段过后的初始值为 0 而不是 8080, 将 v 赋值为 8080 的 put static 指令是程序被编译后, 存放于类构造器方法之中。

但是注意如果声明为:

public static final int v = 8080;

在编译阶段会为 v 生成 ConstantValue 属性,在准备阶段虚拟机会根据 ConstantValue 属性将 v赋值为 8080。

解析

解析阶段是指虚拟机将常量池中的符号引用替换为直接引用的过程。符号引用就是 class 文件中的

public static int v = 8080;

实际上变量 v 在准备阶段过后的初始值为 0 而不是 8080, 将 v 赋值为 8080 的 put static 指令是程序被编译后, 存放于类构造器方法之中。但是注意如果声明为:

在编译阶段会为 v 生成 ConstantValue 属性,在准备阶段虚拟机会根据 ConstantValue 属性将 v赋值为 8080。

解析

解析阶段是指虚拟机将常量池中的符号引用替换为直接引用的过程。符号引用就是 class 文件中的

public static final int v = 8080;

在编译阶段会为 v 生成 ConstantValue 属性,在准备阶段虚拟机会根据 ConstantValue 属性将 v赋值为 8080。

解析

解析阶段是指虚拟机将常量池中的符号引用替换为直接引用的过程。符号引用就是 class 文件中的:

(1) CONSTANT_Class_info

(2)CONSTANT_Field_info

(3)CONSTANT_Method_info

等类型的常量。

符号引用

符号引用与虚拟机实现的布局无关, 引用的目标并不一定要已经加载到内存中。 各种虚拟机实现的内存布局可以各不相同,但是它们能接受的符号引用必须是一致的,因为符号引用的字面量形式明确定义在 Java 虚拟机规范的 Class 文件格式中。

直接引用

直接引用可以是指向目标的指针,相对偏移量或是一个能间接定位到目标的句柄。如果有了直接引用,那引用的目标必定已经在内存中存在。

初始化

初始化阶段是类加载最后一个阶段,前面的类加载阶段之后,除了在加载阶段可以自定义类加载器以外,其它操作都由 JVM 主导。到了初始阶段,才开始真正执行类中定义的 Java 程序代码。

类构造器

初始化阶段是执行类构造器方法的过程。 方法是由编译器自动收集类中的类变量的赋值操作和静态语句块中的语句合并而成的。虚拟机会保证子方法执行之前,父类的方法已经执行完毕, 如果一个类中没有对静态变量赋值也没有静态语句块,那么编译器可以不为这个类生成()方法。注意以下几种情况不会执行类初始化:

(1)通过子类引用父类的静态字段,只会触发父类的初始化,而不会触发子类的初始化。

(2)定义对象数组,不会触发该类的初始化。

(3)常量在编译期间会存入调用类的常量池中,本质上并没有直接引用定义常量的类,不会触发定义常量所在的类。

(4)通过类名获取 Class 对象,不会触发类的初始化。

(5)通过 Class.forName 加载指定类时,如果指定参数 initialize 为 false 时,也不会触发类初始化,其实这个参数是告诉虚拟机,是否要对类进行初始化。

(6)通过 ClassLoader 默认的 loadClass 方法,也不会触发初始化动作。

52、类加载器

虚拟机设计团队把加载动作放到 JVM 外部实现,以便让应用程序决定如何获取所需的类, JVM 提供了 3 种类加载器:

启动类加载器(Bootstrap ClassLoader)

负责加载 JAVA_HOME\lib 目录中的, 或通过-Xbootclasspath 参数指定路径中的, 且被虚拟机认可(按文件名识别, 如 rt.jar) 的类。

扩展类加载器(Extension ClassLoader)

负责加载 JAVA_HOME\lib\ext 目录中的,或通过 java.ext.dirs 系统变量指定路径中的类库。

应用程序类加载器(Application ClassLoader):

负责加载用户路径(classpath)上的类库。JVM 通过双亲委派模型进行类的加载, 当然我们也可以通过继承 java.lang.ClassLoader实现自定义的类加载器。

img

53、双亲委派

当一个类收到了类加载请求,他首先不会尝试自己去加载这个类,而是把这个请求委派给父类去完成,每一个层次类加载器都是如此,因此所有的加载请求都应该传送到启动类加载其中,只有当父类加载器反馈自己无法完成这个请求的时候(在它的加载路径下没有找到所需加载的Class), 子类加载器才会尝试自己去加载。

采用双亲委派的一个好处是比如加载位于 rt.jar 包中的类 java.lang.Object,不管是哪个加载器加载这个类,最终都是委托给顶层的启动类加载器进行加载,这样就保证了使用不同的类加载器最终得到的都是同样一个 Object 对象

img

54、OSGI( 动态模型系统)

OSGi(Open Service Gateway Initiative),是面向 Java 的动态模型系统,是 Java 动态化模块化系统的一系列规范。

55、动态改变构造

OSGi 服务平台提供在多种网络设备上无需重启的动态改变构造的功能。为了最小化耦合度和促使这些耦合度可管理, OSGi 技术提供一种面向服务的架构,它能使这些组件动态地发现对方。

56、模块化编程与热插拔

OSGi 旨在为实现 Java 程序的模块化编程提供基础条件,基于 OSGi 的程序很可能可以实现模块级的热插拔功能,当程序升级更新时,可以只停用、重新安装然后启动程序的其中一部分,这对企业级程序开发来说是非常具有诱惑力的特性。

OSGi 描绘了一个很美好的模块化开发目标,而且定义了实现这个目标的所需要服务与架构,同时也有成熟的框架进行实现支持。但并非所有的应用都适合采用 OSGi 作为基础架构,它在提供强大功能同时,也引入了额外的复杂度,因为它不遵守了类加载的双亲委托模型。

57、JVM内存模型

线程独占:栈,本地方法栈,程序计数器

线程共享:堆,方法区

58、栈

又称方法栈,线程私有的,线程执行方法是都会创建一个栈阵,用来存储局部变量表,操作栈,动态链接,方法出口等信息.调用方法时执行入栈,方法返回式执行出栈.

59、本地方法栈

与栈类似,也是用来保存执行方法的信息.执行Java方法是使用栈,执行Native方法时使用本地方法栈.

60、程序计数器

保存着当前线程执行的字节码位置,每个线程工作时都有独立的计数器,只为执行Java方法服务,执行Native方法时,程序计数器为空.

61、堆

JVM内存管理最大的一块,对被线程共享,目的是存放对象的实例,几乎所欲的对象实例都会放在这里,当堆没有可用空间时,会抛出OOM异常.根据对象的存活周期不同,JVM把对象进行分代管理,由垃圾回收器进行垃圾的回收管理

62、方法区

又称非堆区,用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器优化后的代码等数据.1.7的永久代和1.8的元空间都是方法区的一种实现。

63、分代回收

分代回收基于两个事实:大部分对象很快就不使用了,还有一部分不会立即无用,但也不会持续很长时间

img

年轻代->标记-复制

老年代->标记-清除

64、堆和栈的区别

栈是运行时单位,代表着逻辑,内含基本数据类型和堆中对象引用,所在区域连续,没有碎片;堆是存储单位,代表着数据,可被多个栈共享(包括成员中基本数据类型、引用和引用对象),所在区域不连续,会有碎片。

(1)功能不同

栈内存用来存储局部变量和方法调用,而堆内存用来存储Java中的对象。无论是成员变量,局部变量,还是类变量,它们指向的对象都存储在堆内存中。

(2)共享性不同

栈内存是线程私有的。

堆内存是所有线程共有的。

(3)异常错误不同

如果栈内存或者堆内存不足都会抛出异常。

栈空间不足:java.lang.StackOverFlowError。

堆空间不足:java.lang.OutOfMemoryError。

(4)空间大小

栈的空间大小远远小于堆的

65、什么时候会触发FullGC

除直接调用System.gc外,触发Full GC执行的情况有如下四种。

(1)旧生代空间不足

旧生代空间只有在新生代对象转入及创建为大对象、大数组时才会出现不足的现象,当执行Full GC后空间仍然不足,则抛出如下错

误:

java.lang.OutOfMemoryError: Java heap space

为避免以上两种状况引起的FullGC,调优时应尽量做到让对象在Minor GC阶段被回收、让对象在新生代多存活一段时间及不要创建过大的对象及数组。

(2) Permanet Generation空间满

PermanetGeneration中存放的为一些class的信息等,当系统中要加载的类、反射的类和调用的方法较多时,Permanet Generation可能会被占满,在未配置为采用CMS GC的情况下会执行Full GC。如果经过Full GC仍然回收不了,那么JVM会抛出如下错误信息:

java.lang.OutOfMemoryError: PermGen space

为避免Perm Gen占满造成Full GC现象,可采用的方法为增大Perm Gen空间或转为使用CMS GC。

(3)CMS GC时出现promotion failed和concurrent mode failure

对于采用CMS进行旧生代GC的程序而言,尤其要注意GC日志中是否有promotion failed和concurrent mode failure两种状况,当这两种状况出现时可能会触发Full GC。

promotionfailed是在进行Minor GC时,survivor space放不下、对象只能放入旧生代,而此时旧生代也放不下造成的;concurrentmode failure是在执行CMS GC的过程中同时有对象要放入旧生代,而此时旧生代空间不足造成的。

应对措施为:增大survivorspace、旧生代空间或调低触发并发GC的比率,但在JDK 5.0+、6.0+的版本中有可能会由于JDK的bug29导致CMS在remark完毕后很久才触发sweeping动作。对于这种状况,可通过设置-XX:CMSMaxAbortablePrecleanTime=5(单位为ms)来避免。

(4)统计得到的Minor GC晋升到旧生代的平均大小大于旧生代的剩余空间

这是一个较为复杂的触发情况,Hotspot为了避免由于新生代对象晋升到旧生代导致旧生代空间不足的现象,在进行Minor GC时,做了一个判断,如果之前统计所得到的Minor GC晋升到旧生代的平均大小大于旧生代的剩余空间,那么就直接触发Full GC。

例如程序第一次触发MinorGC后,有6MB的对象晋升到旧生代,那么当下一次Minor GC发生时,首先检查旧生代的剩余空间是否大于6MB,如果小于6MB,则执行Full GC。

当新生代采用PSGC时,方式稍有不同,PS GC是在Minor GC后也会检查,例如上面的例子中第一次Minor GC后,PS GC会检查此时旧生代的剩余空间是否大于6MB,如小于,则触发对旧生代的回收。除了以上4种状况外,对于使用RMI来进行RPC或管理的Sun JDK应用而言,默认情况下会一小时执行一次Full GC。可通过在启动时通过- java-Dsun.rmi.dgc.client.gcInterval=3600000来设置Full GC执行的间隔时间或通过-XX:+ DisableExplicitGC来禁止RMI调用System.gc

66、什么是Java虚拟机?为什么Java被称作是“平台无关的编程语言”?

Java虚拟机是一个可以执行Java字节码的虚拟机进程。Java源文件被编译成能被Java虚拟机执行的字节码文件。 Java被设计成允许应用程序可以运行在任意的平台,而不需要程序员为每一个平台单独重写或者是重新编译。Java虚拟机让这个变为可能,因为它知道底层硬件平台的指令长度和其他特性。

67、对象分配规则

(1)对象优先分配在Eden区,如果Eden区没有足够的空间时,虚拟机执行一次Minor GC。

(2)大对象直接进入老年代(大对象是指需要大量连续内存空间的对象)。这样做的目的是避免在Eden区和两个Survivor区之间发生大量的内存拷贝(新生代采用复制算法收集内存)。

(3)长期存活的对象进入老年代。虚拟机为每个对象定义了一个年龄计数器,如果对象经过了1次Minor GC那么对象会进入Survivor区,之后每经过一次Minor GC那么对象的年龄加1,知道达到阀值对象进入老年区。

(4) 动态判断对象的年龄。如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代。

(5) 空间分配担保。每次进行Minor GC时,JVM会计算Survivor区移至老年区的对象的平均大小,如果这个值大于老年区的剩余值大小则进行一次Full GC,如果小于检查HandlePromotionFailure设置,如果true则只进行Monitor GC,如果false则进行Full GC

68、描述一下JVM加载class文件的原理机制?

JVM中类的装载是由类加载器(ClassLoader)和它的子类来实现的,Java中的类加载器是一个重要的Java运行时系统组件,它负责在运行时查找和装入类文件中的类。

由于Java的跨平台性,经过编译的Java源程序并不是一个可执行程序,而是一个或多个类文件。当Java程序需要使用某个类时,JVM会确保这个类已经被加载、连接(验证、准备和解析)和初始化。

类的加载是指把类的.class文件中的数据读入到内存中,通常是创建一个字节数组读入.class文件,然后产生与所加载类对应的Class对象。加载完成后,Class对象还不完整,所以此时的类还不可用。

当类被加载后就进入连接阶段,这一阶段包括验证、准备(为静态变量分配内存并设置默认的初始值)和解析(将符号引用替换为直接引用)三个步骤。最后JVM对类进行初始化,

包括:

(1)如果类存在直接的父类并且这个类还没有被初始化,那么就先初始化父类;

(2)如果类中存在初始化语句,就依次执行这些初始化语句。 类的加载是由类加载器完成的,类加载器包括:根加载器(BootStrap)、扩展加载器(Extension)、系统加载器(System)和用户自定义类加载器(java.lang.ClassLoader的子类)。

从Java 2(JDK 1.2)开始,类加载过程采取了父亲委托机制(PDM)。PDM更好的保证了Java平台的安全性,在该机制中,JVM自带的Bootstrap是根加载器,其他的加载器都有且仅有一个父类加载器。类的加载首先请求父类加载器加载,父类加载器无能为力时才由其子类加载器自行加载。JVM不会向Java程序提供对Bootstrap的引用。下面是关于几个类加载器的说明

Bootstrap:一般用本地代码实现,负责加载JVM基础核心类库(rt.jar);

Extension:从java.ext.dirs系统属性所指定的目录中加载类库,它的父加载器是Bootstrap;

System:又叫应用类加载器,其父类是Extension。它是应用最广泛的类加载器。它从环境变量classpath或者系统属性java.class.path所指定的目录中记载类,是用户自定义加载器的默认父加载器。

69、Java对象创建过程

(1)JVM遇到一条新建对象的指令时首先去检查这个指令的参数是否能在常量池中定义到一个类的符号引用。然后加载这个类(类加载过程在后边讲)

(2)为对象分配内存。一种办法“指针碰撞”、一种办法“空闲列表”,最终常用的办法“本地线程缓冲分配(TLAB)”

(3)将除对象头外的对象内存空间初始化为0

(4)对对象头进行必要设置

70、简述Java的对象结构

Java对象由三个部分组成:对象头、实例数据、对齐填充。

对象头由两部分组成,第一部分存储对象自身的运行时数据:哈希码、GC分代年龄、锁标识状态、线程持有的锁、偏向线程ID(一般占32/64 bit)。第二部分是指针类型,指向对象的类元数据类型(即对象代表哪个类)。如果是数组对象,则对象头中还有一部分用来记录数组长度。

实例数据用来存储对象真正的有效信息(包括父类继承下来的和自己定义的)

对齐填充:JVM要求对象起始地址必须是8字节的整数倍(8字节对齐 )

71、如何判断对象可以被回收

判断对象是否存活一般有两种方式:

引用计数:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收。此方法简单,无法解决对象相互循环引用的问题。

可达性分析(Reachability Analysis):从GC Roots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,不可达对象。

72、JVM的永久代中会发生垃圾回收么

垃圾回收不会发生在永久代,如果永久代满了或者是超过了临界值,会触发完全垃圾回收(Full GC)。如果你仔细查看垃圾收集器的输出信息,就会发现永久代也是被回收的。这就是为什么正确的永久代大小对避免Full GC是非常重要的原因。请参考下Java8:从永久代到元数据区 (注:Java8中已经移除了永久代,新加了一个叫做元数据区的native内存区)

73、垃圾收集算法

GC最基础的算法有三种: 标记 -清除算法、复制算法、标记-压缩算法,我们常用的垃圾回收器一般都采用分代收集算法。

标记 -清除算法

“标记-清除”(Mark-Sweep)算法,如它的名字一样,算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。

复制算法

“复制”(Copying)的收集算法,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。

标记-压缩算法

标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存

分代收集算法

“分代收集”(Generational Collection)算法,把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法

74、调优命令有哪些?

Sun JDK监控和故障处理命令有jps jstat jmap jhat jstack jinfo

(1)jps,JVM Process Status Tool,显示指定系统内所有的HotSpot虚拟机进程。

(1)jstat,JVM statistics Monitoring是用于监视虚拟机运行时状态信息的命令,它可以显示出虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据。

(3)jmap,JVM Memory Map命令用于生成heap dump文件

(4) jhat,JVM Heap Analysis Tool命令是与jmap搭配使用,用来分析jmap生成的dump,jhat内置了一个微型的HTTP/HTML服务器,生成dump的分析结果后,可以在浏览器中查看

(5)jstack,用于生成java虚拟机当前时刻的线程快照。

(6) jinfo,JVM Configuration info 这个命令作用是实时查看和调整虚拟机运行参数

75、调优工具

常用调优工具分为两类,jdk自带监控工具:jconsole和jvisualvm,第三方有:MAT(Memory AnalyzerTool)、GChisto。

(1) jconsole,Java Monitoring and Management Console是从java5开始,在JDK中自带的java监控和管理控制台,用于对JVM中内存,线程和类等的监控

(2)jvisualvm,jdk自带全能工具,可以分析内存快照、线程快照;监控内存变化、GC变化等。

(3)MAT,Memory Analyzer Tool,一个基于Eclipse的内存分析工具,是一个快速、功能丰富的Javaheap分析工具,它可以帮助我们查找内存泄漏和减少内存消耗

(4)GChisto,一款专业分析gc日志的工具

76、Minor GC与Full GC分别在什么时候发生?

新生代内存不够用时候发生MGC也叫YGC,JVM内存不够的时候发生FGC

77、你知道哪些JVM性能调优

设定堆内存大小

-Xmx:堆内存最大限制。

设定新生代大小。 新生代不宜太小,否则会有大量对象涌入老年代

-XX:NewSize:新生代大小

-XX:NewRatio 新生代和老生代占比

-XX:SurvivorRatio:伊甸园空间和幸存者空间的占比

设定垃圾回收器 年轻代用 -XX:+UseParNewGC 年老代用-XX:+UseConcMarkSweepGC

6、常见设计模式

1.说一下设计模式?你都知道哪些?

答:设计模式总共有 23 种,总体来说可以分为三大类:创建型模式( Creational Patterns )、结构型模式( Structural Patterns )和行为型模式( Behavioral Patterns )。

**分类** **包含** **关注点**     创建型模式 工厂模式、抽象工厂模式、单例模式、建造者模式、原型模式 关注于对象的创建,同时隐藏创建逻辑   结构型模式 适配器模式、过滤器模式、装饰模式、享元模式、代理模式、外观模式、组合模式、桥接模式 关注类和对象之间的组合   行为型模式 责任链模式、命令模式、中介者模式、观察者模式、状态模式、策略模式、模板模式、空对象模式、备忘录模式、迭代器模式、解释器模式、访问者模式 关注对象之间的通信    

下面会对常用的设计模式分别做详细的说明。

2.什么是单例模式?

答:单例模式是一种常用的软件设计模式,在应用这个模式时,单例对象的类必须保证只有一个实例存在,整个系统只能使用一个对象实例。

优点:不会频繁地创建和销毁对象,浪费系统资源。

使用场景:IO 、数据库连接、Redis 连接等。

单例模式代码实现:

class Singleton {
    private static Singleton instance = new Singleton();
    public static Singleton getInstance() {
        return instance;
    }
}

单例模式调用代码:

public class Lesson7\_3 {
    public static void main(String[] args) {
        Singleton singleton1 = Singleton.getInstance();
        Singleton singleton2 = Singleton.getInstance();
        System.out.println(singleton1 == singleton2); 
    }
}

程序的输出结果:true

可以看出以上单例模式是在类加载的时候就创建了,这样会影响程序的启动速度,那如何实现单例模式的延迟加载?在使用时再创建?

单例延迟加载代码:

// 单例模式-延迟加载版
class SingletonLazy {
    private static SingletonLazy instance;
    public static SingletonLazy getInstance() {
        if (instance == null) {
            instance = new SingletonLazy();
        }
        return instance;
    }
}

以上为非线程安全的,单例模式如何支持多线程?

使用 synchronized 来保证,单例模式的线程安全代码:

class SingletonLazy {
    private static SingletonLazy instance;
    public static synchronized SingletonLazy getInstance() {
        if (instance == null) {
            instance = new SingletonLazy();
        }
        return instance;
    }
}

3.什么是简单工厂模式?

答:简单工厂模式又叫静态工厂方法模式,就是建立一个工厂类,对实现了同一接口的一些类进行实例的创建。比如,一台咖啡机就可以理解为一个工厂模式,你只需要按下想喝的咖啡品类的按钮(摩卡或拿铁),它就会给你生产一杯相应的咖啡,你不需要管它内部的具体实现,只要告诉它你的需求即可。

优点

  • 工厂类含有必要的判断逻辑,可以决定在什么时候创建哪一个产品类的实例,客户端可以免除直接创建产品对象的责任,而仅仅“消费”产品;简单工厂模式通过这种做法实现了对责任的分割,它提供了专门的工厂类用于创建对象;
  • 客户端无须知道所创建的具体产品类的类名,只需要知道具体产品类所对应的参数即可,对于一些复杂的类名,通过简单工厂模式可以减少使用者的记忆量;
  • 通过引入配置文件,可以在不修改任何客户端代码的情况下更换和增加新的具体产品类,在一定程度上提高了系统的灵活性。

缺点

  • 不易拓展,一旦添加新的产品类型,就不得不修改工厂的创建逻辑;
  • 产品类型较多时,工厂的创建逻辑可能过于复杂,一旦出错可能造成所有产品的创建失败,不利于系统的维护。

简单工厂示意图如下:

img

简单工厂代码实现

class Factory {
    public static String createProduct(String product) {
        String result = null;
        switch (product) {
            case "Mocca":
                result = "摩卡";
                break;
            case "Latte":
                result = "拿铁";
                break;
            default:
                result = "其他";
                break;
        }
        return result;
    }
}

4.什么是抽象工厂模式?

答:抽象工厂模式是在简单工厂的基础上将未来可能需要修改的代码抽象出来,通过继承的方式让子类去做决定。

比如,以上面的咖啡工厂为例,某天我的口味突然变了,不想喝咖啡了想喝啤酒,这个时候如果直接修改简单工厂里面的代码,这种做法不但不够优雅,也不符合软件设计的“开闭原则”,因为每次新增品类都要修改原来的代码。这个时候就可以使用抽象工厂类了,抽象工厂里只声明方法,具体的实现交给子类(子工厂)去实现,这个时候再有新增品类的需求,只需要新创建代码即可。

抽象工厂实现代码如下:

public class AbstractFactoryTest {
   public static void main(String[] args) {
       // 抽象工厂
       String result = (new CoffeeFactory()).createProduct("Latte");
       System.out.println(result); // output:拿铁
   }
}
// 抽象工厂
abstract class AbstractFactory{
   public abstract String createProduct(String product);
}
// 啤酒工厂
class BeerFactory extends AbstractFactory{
   @Override
   public String createProduct(String product) {
       String result = null;
       switch (product) {
           case "Hans":
               result = "汉斯";
               break;
           case "Yanjing":
               result = "燕京";
               break;
           default:
               result = "其他啤酒";
               break;
       }
       return result;
   }
}
/\* \* 咖啡工厂 \*/
class CoffeeFactory extends AbstractFactory{
   @Override
   public String createProduct(String product) {
       String result = null;
       switch (product) {
           case "Mocca":
               result = "摩卡";
               break;
           case "Latte":
               result = "拿铁";
               break;
           default:
               result = "其他咖啡";
               break;
       }
       return result;
   }
}

5.什么是观察者模式?

观察者模式是定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。观察者模式又叫做发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。 优点

  • 观察者模式可以实现表示层和数据逻辑层的分离,并定义了稳定的消息更新传递机制,抽象了更新接口,使得可以有各种各样不同的表示层作为具体观察者角色;
  • 观察者模式在观察目标和观察者之间建立一个抽象的耦合;
  • 观察者模式支持广播通信;
  • 观察者模式符合开闭原则(对拓展开放,对修改关闭)的要求。

缺点

  • 如果一个观察目标对象有很多直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间;
  • 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃;
  • 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。

在观察者模式中有如下角色:

  • Subject:抽象主题(抽象被观察者),抽象主题角色把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象;
  • ConcreteSubject:具体主题(具体被观察者),该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知;
  • Observer:抽象观察者,是观察者者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己;
  • ConcrereObserver:具体观察者,实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。

观察者模式实现代码如下。

1)定义观察者(消息接收方)
/\* \* 观察者(消息接收方) \*/
interface Observer {
    public void update(String message);
}
/\* \* 具体的观察者(消息接收方) \*/
class ConcrereObserver implements Observer {
    private String name;

    public ConcrereObserver(String name) {
        this.name = name;
    }

    @Override
    public void update(String message) {
        System.out.println(name + ":" + message);
    }
}
2)定义被观察者(消息发送方)
/\* \* 被观察者(消息发布方) \*/
interface Subject {
    // 增加订阅者
    public void attach(Observer observer);
    // 删除订阅者
    public void detach(Observer observer);
    // 通知订阅者更新消息
    public void notify(String message);
}
/\* \* 具体被观察者(消息发布方) \*/
class ConcreteSubject implements Subject {
    // 订阅者列表(存储信息)
    private List<Observer> list = new ArrayList<Observer>();
    @Override
    public void attach(Observer observer) {
        list.add(observer);
    }
    @Override
    public void detach(Observer observer) {
        list.remove(observer);
    }
    @Override
    public void notify(String message) {
        for (Observer observer : list) {
            observer.update(message);
        }
    }
}
3)代码调用
public class ObserverTest {
    public static void main(String[] args) {
        // 定义发布者
        ConcreteSubject concreteSubject = new ConcreteSubject();
        // 定义订阅者
        ConcrereObserver concrereObserver = new ConcrereObserver("老王");
        ConcrereObserver concrereObserver2 = new ConcrereObserver("Java");
        // 添加订阅
        concreteSubject.attach(concrereObserver);
        concreteSubject.attach(concrereObserver2);
        // 发布信息
        concreteSubject.notify("更新了");
    }
}

程序执行结果如下:

老王:更新了

Java:更新了

6.什么是装饰器模式?

答:装饰器模式是指动态地给一个对象增加一些额外的功能,同时又不改变其结构。

优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。

装饰器模式的关键:装饰器中使用了被装饰的对象。

比如,创建一个对象“laowang”,给对象添加不同的装饰,穿上夹克、戴上帽子......,这个执行过程就是装饰者模式,实现代码如下。

1)定义顶层对象,定义行为
interface IPerson {
    void show();
}
2)定义装饰器超类
class DecoratorBase implements IPerson{
    IPerson iPerson;
    public DecoratorBase(IPerson iPerson){
        this.iPerson = iPerson;
    }
    @Override
    public void show() {
        iPerson.show();
    }
}
3)定义具体装饰器
class Jacket extends DecoratorBase {
    public Jacket(IPerson iPerson) {
        super(iPerson);
    }
    @Override
    public void show() {
        // 执行已有功能
        iPerson.show();
        // 定义新行为
        System.out.println("穿上夹克");
    }
}
class Hat extends DecoratorBase {
    public Hat(IPerson iPerson) {
        super(iPerson);
    }
    @Override
    public void show() {
        // 执行已有功能
        iPerson.show();
        // 定义新行为
        System.out.println("戴上帽子");
    }
}
4)定义具体对象
class LaoWang implements IPerson{
    @Override
    public void show() {
        System.out.println("什么都没穿");
    }
}
5)装饰器模式调用
public class DecoratorTest {
    public static void main(String[] args) {
        LaoWang laoWang = new LaoWang();
        Jacket jacket = new Jacket(laoWang);
        Hat hat = new Hat(jacket);
        hat.show();
    }
}

7.什么是模板方法模式?

答:模板方法模式是指定义一个模板结构,将具体内容延迟到子类去实现。

优点

  • 提高代码复用性:将相同部分的代码放在抽象的父类中,而将不同的代码放入不同的子类中;
  • 实现了反向控制:通过一个父类调用其子类的操作,通过对子类的具体实现扩展不同的行为,实现了反向控制并且符合开闭原则。

以给冰箱中放水果为例,比如,我要放一个香蕉:开冰箱门 → 放香蕉 → 关冰箱门;如果我再要放一个苹果:开冰箱门 → 放苹果 → 关冰箱门。可以看出它们之间的行为模式都是一样的,只是存放的水果品类不同而已,这个时候就非常适用模板方法模式来解决这个问题,实现代码如下:

/\* \* 添加模板方法 \*/
abstract class Refrigerator {
    public void open() {
        System.out.println("开冰箱门");
    }
    public abstract void put();

    public void close() {
        System.out.println("关冰箱门");
    }
}
class Banana extends Refrigerator {
    @Override
    public void put() {
        System.out.println("放香蕉");
    }
}
class Apple extends Refrigerator {
    @Override
    public void put() {
        System.out.println("放苹果");
    }
}
/\* \* 调用模板方法 \*/
public class TemplateTest {
    public static void main(String[] args) {
        Refrigerator refrigerator = new Banana();
        refrigerator.open();
        refrigerator.put();
        refrigerator.close();
    }
}

程序执行结果:

开冰箱门

放香蕉

关冰箱门

8.什么是代理模式?

代理模式是给某一个对象提供一个代理,并由代理对象控制对原对象的引用。

优点

  • 代理模式能够协调调用者和被调用者,在一定程度上降低了系统的耦合度;
  • 可以灵活地隐藏被代理对象的部分功能和服务,也增加额外的功能和服务。

缺点

  • 由于使用了代理模式,因此程序的性能没有直接调用性能高;
  • 使用代理模式提高了代码的复杂度。

举一个生活中的例子:比如买飞机票,由于离飞机场太远,直接去飞机场买票不太现实,这个时候我们就可以上携程 App 上购买飞机票,这个时候携程 App 就相当于是飞机票的代理商。

代理模式实现代码如下:

/\* \* 定义售票接口 \*/
interface IAirTicket {
    void buy();
}
/\* \* 定义飞机场售票 \*/
class AirTicket implements IAirTicket {
    @Override
    public void buy() {
        System.out.println("买票");
    }
}
/\* \* 代理售票平台 \*/
class ProxyAirTicket implements IAirTicket {
    private AirTicket airTicket;
    public ProxyAirTicket() {
        airTicket = new AirTicket();
    }
    @Override
    public void buy() {
        airTicket.buy();
    }
}
/\* \* 代理模式调用 \*/
public class ProxyTest {
    public static void main(String[] args) {
        IAirTicket airTicket = new ProxyAirTicket();
        airTicket.buy();
    }
}

9.什么是策略模式?

答:策略模式是指定义一系列算法,将每个算法都封装起来,并且使他们之间可以相互替换。

优点:遵循了开闭原则,扩展性良好。

缺点:随着策略的增加,对外暴露越来越多。

以生活中的例子来说,比如我们要出去旅游,选择性很多,可以选择骑车、开车、坐飞机、坐火车等,就可以使用策略模式,把每种出行作为一种策略封装起来,后面增加了新的交通方式了,如超级高铁、火箭等,就可以不需要改动原有的类,新增交通方式即可,这样也符合软件开发的开闭原则。 策略模式实现代码如下:

/\* \* 声明旅行 \*/
interface ITrip {
    void going();
}
class Bike implements ITrip {
    @Override
    public void going() {
        System.out.println("骑自行车");
    }
}
class Drive implements ITrip {
    @Override
    public void going() {
        System.out.println("开车");
    }
}
/\* \* 定义出行类 \*/
class Trip {
    private ITrip trip;

    public Trip(ITrip trip) {
        this.trip = trip;
    }

    public void doTrip() {
        this.trip.going();
    }
}
/\* \* 执行方法 \*/
public class StrategyTest {
    public static void main(String[] args) {
        Trip trip = new Trip(new Bike());
        trip.doTrip();
    }
}

程序执行的结果:

骑自行车

10.什么是适配器模式?

答:适配器模式是将一个类的接口变成客户端所期望的另一种接口,从而使原本因接口不匹配而无法一起工作的两个类能够在一起工作。

优点

  • 可以让两个没有关联的类一起运行,起着中间转换的作用;
  • 灵活性好,不会破坏原有的系统。

缺点:过多地使用适配器,容易使代码结构混乱,如明明看到调用的是 A 接口,内部调用的却是 B 接口的实现。

以生活中的例子来说,比如有一个充电器是 MicroUSB 接口,而手机充电口却是 TypeC 的,这个时候就需要一个把 MicroUSB 转换成 TypeC 的适配器,如下图所示:

适配器实现代码如下:

/\* \* 传统的充电线 MicroUSB \*/
interface MicroUSB {
    void charger();
}
/\* \* TypeC 充电口 \*/
interface ITypeC {
    void charger();
}
class TypeC implements ITypeC {
    @Override
    public void charger() {
        System.out.println("TypeC 充电");
    }
}
/\* \* 适配器 \*/
class AdapterMicroUSB implements MicroUSB {
    private TypeC typeC;

    public AdapterMicroUSB(TypeC typeC) {
        this.typeC = typeC;
    }

    @Override
    public void charger() {
        typeC.charger();
    }
}
/\* \* 测试调用 \*/
public class AdapterTest {
    public static void main(String[] args) {
        TypeC typeC = new TypeC();
        MicroUSB microUSB = new AdapterMicroUSB(typeC);
        microUSB.charger();

    }
}

程序执行结果:

TypeC 充电

11.JDK 类库常用的设计模式有哪些?

答:JDK 常用的设计模式如下:

1)工厂模式

java.text.DateFormat 工具类,它用于格式化一个本地日期或者时间。

public final static DateFormat getDateInstance();
public final static DateFormat getDateInstance(int style);
public final static DateFormat getDateInstance(int style,Locale locale);

加密类

KeyGenerator keyGenerator = KeyGenerator.getInstance("DESede");
Cipher cipher = Cipher.getInstance("DESede");
2)适配器模式

把其他类适配为集合类

List<Integer> arrayList = java.util.Arrays.asList(new Integer[]{1,2,3});
List<Integer> arrayList = java.util.Arrays.asList(1,2,3);
3)代理模式

如 JDK 本身的动态代理。

interface Animal {
    void eat();
}
class Dog implements Animal {
    @Override
    public void eat() {
        System.out.println("The dog is eating");
    }
}
class Cat implements Animal {
    @Override
    public void eat() {
        System.out.println("The cat is eating");
    }
}

// JDK 代理类
class AnimalProxy implements InvocationHandler {
    private Object target; // 代理对象
    public Object getInstance(Object target) {
        this.target = target;
        // 取得代理对象
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("调用前");
        Object result = method.invoke(target, args); // 方法调用
        System.out.println("调用后");
        return result;
    }
}

public static void main(String[] args) {
    // JDK 动态代理调用
    AnimalProxy proxy = new AnimalProxy();
    Animal dogProxy = (Animal) proxy.getInstance(new Dog());
    dogProxy.eat();
}
4)单例模式

全局只允许有一个实例,比如:

Runtime.getRuntime();
5)装饰器

为一个对象动态的加上一系列的动作,而不需要因为这些动作的不同而产生大量的继承类。

java.io.BufferedInputStream(InputStream);  
java.io.DataInputStream(InputStream);  
java.io.BufferedOutputStream(OutputStream);  
java.util.zip.ZipOutputStream(OutputStream);  
java.util.Collections.checkedList(List list, Class type) ;
6)模板方法模式

定义一个操作中算法的骨架,将一些步骤的执行延迟到其子类中。

比如,Arrays.sort() 方法,它要求对象实现 Comparable 接口。

class Person implements Comparable{
    private Integer age;
    public Person(Integer age){
        this.age = age;
    }
    @Override
    public int compareTo(Object o) {
        Person person = (Person)o;
        return this.age.compareTo(person.age);
    }
}
public class SortTest(){
    public static void main(String[] args){
        Person p1 = new Person(10);
        Person p2 = new Person(5);
        Person p3 = new Person(15);
        Person[] persons = {p1,p2,p3};
        //排序
        Arrays.sort(persons);
    }
}

12.IO 使用了什么设计模式?

答:IO 使用了适配器模式和装饰器模式。

  • 适配器模式:由于 InputStream 是字节流不能享受到字符流读取字符那么便捷的功能,借助 InputStreamReader 将其转为 Reader 子类,因而可以拥有便捷操作文本文件方法;
  • 装饰器模式:将 InputStream 字节流包装为其他流的过程就是装饰器模式,比如,包装为 FileInputStream、ByteArrayInputStream、PipedInputStream 等。

13.Spring 中都使用了哪些设计模式?

答:Spring 框架使用的设计模式如下。

  • 代理模式:在 AOP 中有使用
  • 单例模式:bean 默认是单例模式
  • 模板方法模式:jdbcTemplate
  • 工厂模式:BeanFactory
  • 观察者模式:Spring 事件驱动模型就是观察者模式很经典的一个应用,比如,ContextStartedEvent 就是 ApplicationContext 启动后触发的事件
  • 适配器模式:Spring MVC 中也是用到了适配器模式适配 Controller

7、uml

1、什么是UML?具体包括哪些内容?
答:标准建模语言UML。包括用例图,静态图(包括类图、对象图和包图),行为图,交互图(顺序图和合作图)和实现图。

2、Java EE常用的设计模式?
答:Java中的23种设计模式包括:Factory(工厂模式),Builder(建造模式),FactoryMethod(工厂方法模式),Prototype(原始模型模式),Singleton(单例模式),Facade(门面模式),Adapter(适配器模式),Bridge(桥梁模式),Composite(合成模式),Decorator(装饰模式),Flyweight(享元模式),Proxy(代理模式),Command(命令模式),?Interpreter(解释器模式),Visitor(访问者模式),Iterator(迭代子模式),Mediator(调停者模式),Memento(备忘录模式),Observer(观察者模式),State(状态模式),Strategy(策略模式),Template Method(模板方法模式), Chain Of Responsibleity(责任链模式)

3、说说你是如何理解工厂模式的?
答:工厂模式是一种经常被使用到的模式,根据工厂模式实现的类可以根据提供的数据生成一组类中某一个类的实例,通常这一组类都拥有一个公共的抽象父类并且实现了相同的方法,但是,这些方法针对不同的数据进行了不同的操作。
工厂模式的具体实现方法是:首先需要定义一个基类,该类的子类通过不同的方法实现了基类中的方法。然后需要定义一个工厂类,工厂类可以根据条件生成不同的子类实例。当得到子类的实例后,开发人员可以调用基类中的方法而不必考虑到底返回的是哪一个子类的实例。

4、确定模块的功能和模块的接口是在软件设计的那个队段完成的?
  答:概要设计阶段。

UML 统一建模语言

计算机软件:

软件是计算机系统中与硬件相互依存的另一部分, 它包括程序,相关数据及其说明文档

程序: 是按照实现设计的算法要求执行的指令序列

数据: 是程序能正常操作的信息

文档: 是程序开发维护和使用有关的各种图文资料

软件的生产过程:

软件的生产过程: 分为需求分析, 系统分析, 系统设计, 功能设计, 实现, 测试, 运行和维 护等几个主要阶段

项目的开发模型:

瀑布模型: 它将软件开发过程划分为若干个相互区别又彼此关联的阶段,每个阶段的工作都是以上一阶段的结果为依据, 同时为下一阶段的工作提供了前提

适用于: 已明确了客户需求, 切记是明确客户需求切需求不会发生改变时

渐增模型:

是由一组有计划的,循环渐进的, 不断改进的过程版本组成

演化模型:

对事先不能完整定义需求的软件项目的开发可以使用演化模型, 演化模型可以减少由于需求不明给项目带来的风险

螺旋模型:

螺旋模型结合了瀑布模型和演化模型的优点, 并增加了风险分析, 该模块将其活动划分为**定制计划, 风险分析, 实施开发和客户评估**四类, 已采用了循环往复,逐渐完善的方式工作

定制计划: 确定软件开发目标, 选定实施方案, 弄清项目的限制条件

风险分析: 分析所选方案, 考虑如何识别的消除风险

实施开发: 实施软件开发

客户评估: 评价软件的功能和性能, 提出修正建议

智能模型:

基于知识库和专家的软件开发模型, 是知识工程的软件工程在开发模型商相结合的人工智能产物

软件生存周期:

软件设计,开发,使用

包括: 需求分析, 概要设计, 详细设计,实现,组装测试,确认测试,使用, 维护,更新换代

软件开发方法:

结构化程序设计方法

模块化程序设计方法

面向对象程序设计方法

UML的特点:

统一标准

面向对象

可视化,表达能力强

独立于过程

易掌握, 易用

软件系统体系结构:

视图:

用例视图

逻辑视图

构件视图

进程视图

配置视图

UML软件已用例为中心, 已系统体系结构为主线, 采用循环, 迭代, 渐增的方式进行开发

UML系统模型与建模:

三大模型图

用例模型图: 用例图

静态模型图: 类图, 对象图,包图,构建图和配置图

动态模型图: 活动图, 顺序图, 状态图和合作图

UML扩展:

UML扩展机制包括三种: 构造型,标记型和约束型

UML开发的特征:

用例驱动的系统

以体系结构为中心

螺旋上升式的开发过程

以质量控制和风险管理为目标

项目的可行性研究与风险分析:

可行性研究分为:经济可行性研究,技术可行性研究和法律可行性研究

类之间的关系:

关联关系

聚集关系

继承关系

依赖和细化关系

UML设计模式:

分类:

创建型设计模式:工厂模式,建造模式,原型模式,单例模式

工厂模式分为三种: 简单工厂, 工厂方法,抽象方法

结构型设计模式:适配器对象模式,桥接模式,组合模式,装饰模式,外观模式,享元模式, 代理模式

行为型审计模式:命令模式,迭代器模式,责任链模式,中介者模式,备忘录模式,观察者模式,状态模式,策略模式,访问者模式

8、基础类库 零碎

1.java中有几种类型的流?JDK为每种类型的流提供了一些抽象类以供继承,请说出他们分别是哪些类?

答:字节流,字符流。字节流继承于Input Stream Output Stream,字符流继承于Reader Writer。在java.io包中还有许多其他的流,低层流与调层流,高层流主要是为了提高性能和使用方便。

2、启动一个线程是用run()还是start()?

答:启动一个线程是调用start()方法,启动线程并调用run方法。

3、线程的基本概念、线程的基本状态以及状态之间的关系是什么?

答:线程是进程内的并发,没有自已内存空间,共享进程的,线程间的通信成本较低。Java中的线程有四种状态分别是:运行、就绪、挂起、结束。

4、java多线程有几种实现方法,都是什么?同步有几种实现方法,都是什么? stop()和suspend()方法为何不推荐使用?

答:多线程有两种实现方法,分别是继承Thread类与实现Runnable接口。

同步的实现方面有两种,分别是synchronized,wait与notify。

反对使用stop(),是因为它不安全。它会解除由线程获取的所有锁定,而且如果对象处于一种不连贯状态,那么其他线程能在那种状态下检查和修改它们。结果很难检查出真正的问题所在。suspend()方法容易发生死锁。调用suspend()的时候,目标线程会停下来,但却仍然持有在这之前获得的锁定。此时,其他任何线程都不能访问锁定的资源,除非被"挂起"的线程恢复运行。对任何线程来说,如果它们想恢复目标线程,同时又试图使用任何一个锁定的资源,就会造成死锁。所以不应该使用suspend(),而应在自己的Thread类中置入一个标志,指出线程应该活动还是挂起。若标志指出线程应该挂起,便用wait()命其进入等待状态。若标志指出线程应当恢复,则用一个notify()重新启动线程。

5、同步和异步有和异同,在什么情况下分别使用他们?举例说明。

答:同步:上一段代码没的完成,下一段必须等到上一段代码完成后才可以执行。如买票排队

异步:上一段代码没的完成,下一段不必等到上一段代码完成就可以执行。如手机发送短信。

6、所知道的线程同步的方法都有什么?

答:HashTable中的put,get,remove,Vector的相关方法。

7、如何列出某个目录下的所有文件?

答:代码如下:

Import java.io.File;
File f=new File("C:\\");
        File[] f1=f.listFiles();
        for(int i=0;i
8、sleep() 和 wait() 有什么区别?
答:Sleep是指休眠给定的时间,当这个时间达到之后,线程会再次醒来。
Wait是等待状态,多长时间不清楚,由另一个线程将其唤醒。
9、当一个线程进入一个对象的一个synchronized方法后,其它线程是否可进入此对象的其它方法?
答:如只其它方法是同步方法,不可以进入。如果不是可以进入。
10、Java集合框架是什么?说出一些集合框架的优点?
答:每种编程语言中都有集合,最初的Java版本包含几种集合类:Vector、Stack、HashTable和Array。随着集合的广泛使用,Java1.2提出了囊括所有集合接口、实现和算法的集合框架。在保证线程安全的情况下使用泛型和并发集合类,Java已经经历了很久。它还包括在Java并发包中,阻塞接口以及它们的实现。集合框架的部分优点如下:
(1)使用核心集合类降低开发成本,而非实现我们自己的集合类。
(2)随着使用经过严格测试的集合框架类,代码质量会得到提高。
(3)通过使用JDK附带的集合类,可以降低代码维护成本。
(4)复用性和可操作性。

9、Java的高级特性

\1. Struts 2体系结构流程中最热门的6个Struts 2面试问题和答案

img

-> 1.启动应用程序服务器时,容器将加载web.xml文件。

-> 2.从客户端浏览器向服务器发出带有特定URL的第一个请求时,控件首先到达web.xml文件。它在web.xml中检查URL的映射,并找到过滤器分派器。

-> 3.过滤器分派器确定是否使用动作映射器调用动作,并将控件委派给动作代理。

-> 4. ActionProxy从Configuration Manager获得帮助,Configuration Manager是从struts.xml初始化的。ActionProxy创建一个ActionInvocation。

-> 5. ActionInvocation查找要为此请求调用的Action类,并发现与动作映射关联的intereptor。

-> 6.现在,ActionInvocation调用堆栈中第一个拦截器的intercept()方法。执行第一个拦截器后,Invoke()将检查下一个拦截器。

-> 7.执行完所有拦截器后,将调用动作类。最后,将返回结果字符串,并将呈现相应的视图。

\2. Struts 1.x和Struts 2.x之间的区别?

img

3.什么是拦截器?

拦截器是在请求的预处理和后处理中调用的对象。在struts 2中,拦截器用于执行诸如验证,异常处理,国际化,显示中间结果等操作。

4.什么是价值栈和OGNL?

值堆栈是Struts 2 在其中存储应用程序数据以处理客户端请求的存储区域。数据存储在ActionContext对象中,该对象使用ThreadLocal对特定请求线程具有特定的值。

对象图导航语言(OGNL)是一种功能强大的表达语言,用于处理存储在ValueStack上的数据。从体系结构图中可以看到,两个拦截器和结果页都可以使用OGNL访问存储在ValueStack上的数据。

\5. Struts2动作和拦截器是否是线程安全的?

Struts 2 动作类是线程安全的,因为会为处理该请求的每个请求实例化一个对象。

Struts 2 拦截器是单例类,并且创建了一个新线程来处理请求,因此它不是线程安全的,我们需要仔细实现它们,以避免共享数据出现任何问题。

6.什么是动作上下文和动作调用?

ActionContext是在其中执行动作的对象的容器。每个线程(即ThreadLocal)中存储在ActionContext中的值是唯一的。因此,我们不需要使操作线程安全。

我们可以通过调用ActionContext类的getContext()方法来获取ActionContext的引用。这是静态工厂方法。例如:
ActionContext ctx = ActionContext.getContext();

ActionInvocation表示动作的执行状态。它包含动作和拦截器对象。

Struts框架提供了ActionInvocation接口来处理ActionInvocation。它提供了许多方法,其中一些方法可用于获取ValueStack,ActionProxy,ActionContext,Result等的实例。

===============================================================

\1. java.lang之间的差异。StringBuffer和java.lang。StringBuilder?

java.lang.StringBuffer:线程安全的,同步的,并且没有那么快。

java.lang.StringBuilder:更快,不执行同步。

2.如何处理线程的未捕获异常?

@FunctionalInterface
公共静态接口线程。UncaughtExceptionHandler

\3. [已检查的例外]与[未检查的例外]之间的区别?

该类 Exception 和所有不是其子类的子类 RuntimeException 都是 检查异常。如果检查的异常可以由方法或构造函数的throws 执行抛出,并在方法或构造函数的边界外传播,则需要在方法或构造函数的子句中声明 它们。

Checked Exceptions应该用于预期的但无法预防的错误,可以从中恢复。

4.如何使用非静态内部类和静态内部类?

非静态:Out.In in = new Out()。new In() ;

静态:Out.StaticIn in = new Out.StaticIn() ;

5.匿名内部阶级?

匿名内部类必须扩展超类或实现接口。

ClassA a =新的ClassA();

a.test(新产品(){

  公共双重处理(){

  }

});

6.如何创建动态代理类和动态代理实例?

java.lang.reflect。代理
接口InvocationHandler

静态Class <?> getProxyClass(ClassLoader loader,Class <?> ...接口)
静态对象newProxyInstance(ClassLoader loader,Class <?> []接口,InvocationHandler h)

InvocationHandler handler =新的MyInvocationHandler(...);

类proxyClass = Proxy.getProxyClass(Foo.class.getClassLoader(),新Class [] {Foo.class});
构造函数ctor = proxyClass.getConstructor(new Class [] {InvocationHandler.class});
Foo f =(Foo)ctor.newInstance(new Object [] {handler});

Foo f =(Foo)Proxy.newProxyInstance(Foo.class.getClassLoader(),new Class [] {Foo.class},handler);

7.三种类型的类加载器之间有什么区别?

公共抽象类ClassLoader扩展Object

loadClass(字符串名称,布尔值解析)

findClass(字符串名称)

Bootstrap ClassLoader:加载Java的核心类。(java.exe -Xbootclasspath可以加载其他类)。不是java.lang.ClassLoader的子类。

jre / lib / *。jar

jre /类

扩展ClassLoader:jre / lib / ext / *或由java.ext.dirs系统属性定义。

系统ClassLoader:java -classpath或由java.class.path定义

java.lang.Object
  java.lang.ClassLoader
    java.security.SecureClassLoader
      java.net。URLClassLoader

扩展ClassLoader和System ClassLoader的父级。

8.接口Serializable如何工作?

可序列化类的所有子类型本身都是可序列化的。

在序列化和反序列化期间需要特殊处理的类:

私有无效writeObject(java.io.ObjectOutputStream输出)抛出IOException
私有无效readObject(java.io.ObjectInputStream输入)抛出IOException,ClassNotFoundException;
私有void readObjectNoData()抛出ObjectStreamException;

指定将对象写入流时要使用的替代对象:

ANY-ACCESS-MODIFIER对象writeReplace()抛出ObjectStreamException;

从流中读取时指定一个替换:

ANY-ACCESS-MODIFIER对象readResolve()抛出ObjectStreamException;

ANY-ACCESS-MODIFIER静态最终长serialVersionUID = 42L;

\9. Buffer如何工作?

java.nio.Buffer
容量是它包含的元素数量。
limit是不应读取或写入的第一个元素的索引。
position是下一个要读取或写入的元素的索引。
mark是调用reset方法时将其位置重置到的索引。

0 <=标记<=位置<=极限<=容量

clear()将限制设置为容量,并将位置设置为零。

flip()将限制设置为当前位置,然后将位置设置为零。

rewind()保持限制不变,并将位置设置为零。

缓冲区不能安全用于多个并发线程。

b.flip()。position(23).limit(42);

\10. runnable和callable 接口之间的区别?

可调用可具有返回值。

\11. java.util之间的区别。集合和接口Map <K,V>?

集合仅包含项目,而地图包含键值对。

12.接口Set (java.util.HashSet )和 接口List (java.util.ArrayList 之间的区别?

Set 无序且无重复,List 无序且重复。

\13. HashSet,TreeSet和EnumSet之间的区别?

\14. HashTable,HashMap,EnumMap和TreeMap 之间的区别?

对于HashMap,非线程安全,键和值可以为null。

哈希表的线程安全,键和值不能为null。

转载于:https://www.cnblogs.com/thlzhf/p/5936303.html

10、算法

1)请简单解释算法是什么?

算法是一个定义良好的计算过程,它将一些值作为输入并产生相应的输出值。简单来说,它是将输入转换为输出的一系列计算步骤。

2)解释什么是快速排序算法?

快速排序算法能够快速排序列表或查询。它基于分割交换排序的原则,这种类型的算法占用空间较小,它将待排序列表分为三个主要部分:

  • 小于Pivot的元素
  • 枢轴元素Pivot(选定的比较值)
  • 大于Pivot的元素

3)解释算法的时间复杂度?

算法的时间复杂度表示程序运行完成所需的总时间,它通常用大O表示法来表示。

4)请问用于时间复杂度的符号类型是什么?

用于时间复杂度的符号类型包括:

  • Big Oh:它表示小于或等于目标多项式
  • Big Omega:它表示大于或等于目标多项式
  • Big Theta:它表示与目标多项式相等
  • Little Oh:它表示小于目标多项式
  • Little Omega:它表示大于目标多项式

5)解释二分法检索如何工作?

在二分法检索中,我们先确定数组的中间位置,然后将要查找的值与数组中间位置的值进行比较,若小于数组中间值,则要查找的值应位于该中间值之前,依此类推,不断缩小查找范围,直至得到最终结果。

6)解释是否可以使用二分法检索链表?

由于随机访问在链表中是不可接受的,所以不可能到达O(1)时间的中间元素。因此,对于链表来说,二分法检索是不可以的(对顺序链表或排序后的链表是可以用的)。

7)解释什么是堆排序?

堆排序可以看成是选择排序的改进,它可以定义为基于比较的排序算法。它将其输入划分为未排序和排序的区域,通过不断消除最小元素并将其移动到排序区域来收缩未排序区域。

8)说明什么是Skip list?

Skip list数据结构化的方法,它允许算法在符号表或字典中搜索、删除和插入元素。在Skip list中,每个元素由一个节点表示。搜索函数返回与key相关的值的内容。插入操作将指定的键与新值相关联,删除操作可删除指定的键。

9)解释插入排序算法的空间复杂度是多少?

插入排序是一种就地排序算法,这意味着它不需要额外的或仅需要少量的存储空间。对于插入排序,它只需要将单个列表元素存储在初始数据的外侧,从而使空间复杂度为O(1)。

10)解释什么是“哈希算法”,它们用于什么?

“哈希算法”是一个哈希函数,它使用任意长度的字符串,并将其减少为唯一的固定长度字符串。它用于密码有效性、消息和数据完整性以及许多其他加密系统。

11)解释如何查找链表是否有循环?

要知道链表是否有循环,我们将采用两个指针的方法。如果保留两个指针,并且在处理两个节点之后增加一个指针,并且在处理每个节点之后,遇到指针指向同一个节点的情况,这只有在链表有循环时才会发生。

12)解释加密算法的工作原理?

加密是将明文转换为称为“密文”的密码格式的过程。要转换文本,算法使用一系列被称为“键”的位来进行计算。密钥越大,创建密文的潜在模式数越多。大多数加密算法使用长度约为64到128位的固定输入块,而有些则使用流方法。

13)列出一些常用的加密算法?

一些常用的加密算法是:

  • 3-way
  • Blowfish
  • CAST
  • CMEA
  • GOST
  • DES 和Triple DES
  • IDEA
  • LOKI等等

14)解释一个算法的最佳情况和最坏情况之间有什么区别?

·最佳情况:算法的最佳情况解释为算法执行最佳的数据排列。例如,我们进行二分法检索,如果目标值位于正在搜索的数据中心,则这就是最佳情况,最佳情况时间复杂度为0。

·最差情况:给定算法的最差输入参考。例如快速排序,如果选择关键值的子列表的最大或最小元素,则会导致最差情况出现,这将导致时间复杂度快速退化到O(n2)。

15)解释什么是基数排序算法?

基数排序又称“桶子法”,是通过比较数字将其分配到不同的“桶里”来排序元素的。它是线性排序算法之一。

16)解释什么是递归算法?

递归算法是一个解决复杂问题的方法,将问题分解成较小的子问题,直到分解的足够小,可以轻松解决问题为止。通常,它涉及一个调用自身的函数。

17)提到递归算法的三个定律是什么?

所有递归算法必须遵循三个规律

  1. 递归算法必须有一个基点
  2. 递归算法必须有一个趋向基点的状态变化过程
  3. 递归算法必须自我调用

18)解释什么是冒泡排序算法?

冒泡排序算法也称为下沉排序。在这种类型的排序中,要排序的列表的相邻元素之间互相比较。如果它们按顺序排列错误,将交换值并以正确的顺序排列,直到最终结果“浮”出水面。

11、集合

概述:

  • List , Set, Map都是接口,前两个继承至Collection接口,Map为独立接口
  • Set下有HashSet,LinkedHashSet,TreeSet
  • List下有ArrayList,Vector,LinkedList
  • Map下有Hashtable,LinkedHashMap,HashMap,TreeMap
  • Collection接口下还有个Queue接口,有PriorityQueue类

这里写图片描述

注意:

  • Queue接口与List、Set同一级别,都是继承了Collection接口。
    看图你会发现,LinkedList既可以实现Queue接口,也可以实现List接口.只不过呢, LinkedList实现了Queue接口。Queue接口窄化了对LinkedList的方法的访问权限(即在方法中的参数类型如果是Queue时,就完全只能访问Queue接口所定义的方法 了,而不能直接访问 LinkedList的非Queue的方法),以使得只有恰当的方法才可以使用。
  • SortedSet是个接口,它里面的(只有TreeSet这一个实现可用)中的元素一定是有序的。

总结:

Connection接口:

List 有序,可重复

  • ArrayList
    优点: 底层数据结构是数组,查询快,增删慢。
    缺点: 线程不安全,效率高
  • Vector
    优点: 底层数据结构是数组,查询快,增删慢。
    缺点: 线程安全,效率低
  • LinkedList
    优点: 底层数据结构是链表,查询慢,增删快。
    缺点: 线程不安全,效率高

Set 无序,唯一

  • HashSet
    底层数据结构是哈希表。(无序,唯一)
    如何来保证元素唯一性?
    1.依赖两个方法:hashCode()和equals()
  • LinkedHashSet
    底层数据结构是链表和哈希表。(FIFO插入有序,唯一)
    1.由链表保证元素有序
    2.由哈希表保证元素唯一
  • TreeSet
    底层数据结构是红黑树。(唯一,有序)
    \1. 如何保证元素排序的呢?
    自然排序
    比较器排序
    2.如何保证元素唯一性的呢?
    根据比较的返回值是否是0来决定

针对Collection集合我们到底使用谁呢?(掌握)

唯一吗?

是:Set

排序吗?

是:TreeSet或LinkedHashSet
否:HashSet
如果你知道是Set,但是不知道是哪个Set,就用HashSet。

否:List

要安全吗?

是:Vector
否:ArrayList或者LinkedList

查询多:ArrayList
增删多:LinkedList
如果你知道是List,但是不知道是哪个List,就用ArrayList。

如果你知道是Collection集合,但是不知道使用谁,就用ArrayList。
如果你知道用集合,就用ArrayList。

说完了Collection,来简单说一下Map.

Map接口:

上图:
这里写图片描述

Map接口有三个比较重要的实现类,分别是HashMap、TreeMap和HashTable。

  • TreeMap是有序的,HashMap和HashTable是无序的。
  • Hashtable的方法是同步的,HashMap的方法不是同步的。这是两者最主要的区别。

这就意味着:

  • Hashtable是线程安全的,HashMap不是线程安全的。
  • HashMap效率较高,Hashtable效率较低。
    如果对同步性或与遗留代码的兼容性没有任何要求,建议使用HashMap。 查看Hashtable的源代码就可以发现,除构造函数外,Hashtable的所有 public 方法声明中都有 synchronized关键字,而HashMap的源码中则没有。
  • Hashtable不允许null值,HashMap允许null值(key和value都允许)
  • 父类不同:Hashtable的父类是Dictionary,HashMap的父类是AbstractMap

重点问题重点分析:

(一).TreeSet, LinkedHashSet and HashSet 的区别

1. 介绍

  • TreeSet, LinkedHashSet and HashSet 在java中都是实现Set的数据结构
  • TreeSet的主要功能用于排序
  • LinkedHashSet的主要功能用于保证FIFO即有序的集合(先进先出)
  • HashSet只是通用的存储数据的集合

2. 相同点

  • Duplicates elements: 因为三者都实现Set interface,所以三者都不包含duplicate elements
  • Thread safety: 三者都不是线程安全的,如果要使用线程安全可以Collections.synchronizedSet()

3. 不同点

  • Performance and Speed: HashSet插入数据最快,其次LinkHashSet,最慢的是TreeSet因为内部实现排序
  • Ordering: HashSet不保证有序,LinkHashSet保证FIFO即按插入顺序排序,TreeSet安装内部实现排序,也可以自定义排序规则
  • null:HashSet和LinkHashSet允许存在null数据,但是TreeSet中插入null数据时会报NullPointerException
(1).自然排序

自然排序要进行一下操作:
1.Student类中实现 Comparable接口
2.重写Comparable接口中的Compareto方法

(2).比较器排序

比较器排序步骤:
1.单独创建一个比较类,这里以MyComparator为例,并且要让其继承Comparator接口
2.重写Comparator接口中的Compare方法

3.在主类中使用下面的 构造方法

12、TCP/IP UDP

特点

安全(三次交互,以保证连接的可靠),不会发生数据丢失,但是效率没有UDP高

什么时候应该使用TCP?

当对网络通讯质量有要求的时候,比如:整个数据要准确无误的传递给对方,这往往用于一些要求可靠的应用,比如HTTP、HTTPS、FTP等传输文件的协议,POP、SMTP等邮件传输的协议。

UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。

什么时候应该使用UDP?

当对网络通讯质量要求不高的时候,要求网络通讯速度能尽量的快,这时就可以使用UDP。

13、HTTP

HTTP协议的特点

  • 基于请求/响应模型的协议。
    • 请求和响应必须成对;
    • 先有请求后有响应。
  • 简单快捷
    • 因为发送请求的时候只需要发送请求方式和请求路径即可
  • HTTP协议默认的端口:80

14、集合和数组的区别

集合和数组的区别

区别1:

  • 数组既可以存储基本数据类型,又可以存储引用数据类型,基本数据类型存储的是值,引用数据类型存储的是地址值。
  • 集合只能存储引用数据类型(对象)。集合也能存储基本数据类型(有点矛盾,看后句),但是在存储的时候会自动装箱变成对象。

区别2:

  • 数组长度是固定的,不能自动增长。
  • 集合的长度是可变的,可以根据元素的增长而增长。

二、集合和数组什么时候用

 1、如果元素个数是固定的推荐用数组,效率高。

 2、如果元素个数不是固定的推荐用集合

三、集合和数组什么时候用的原因

  部分集合内部是用数组实现(当然还有是哈希,二叉树等),当每次创建一个集合对象是,默认创建10个长度的数组。当添加到第11个时,它会按照原数组1.5倍创建,在把原来的数组值复制过去,原来10长度的数组将变成垃圾被回收。当添加到16或者更多,以后都是按照原数组1.5倍创建,复制这个过程。所以,当固定长度为100的时候,你选择了集合就是低效率,选择数组就是高效率,因为集合里面有很多创建,复制,销毁过程。

15、数据类型

java的数据类型分两大类:

基本数据类型

方法的参数为基本数据类型时,传递的是数据值。

基本类型是通过诸如 int a = 5; long b = 6L;的形式来定义的,称为自动变量,自动变量存放的是字面值,不是类的实例,它存放在内存的堆栈中,数据大小和生存期必须是确定的,存取速度比较快,在堆栈中的字面值可以共享,也就是说我们定义一个int a = 5;然后又定义了一个int b = 5;这时a与b在内存中指向的是同一个字面常量。

四类八种

四类:整形、浮点型、布尔型、字符型

八种:

  • 整数型 (取值范围)
    • 字节型byte(1个字节)(-128~127) 短整型short(2个字节)(-32768~32767) 整形int(4个字节)(一般默认)(-21亿~21亿) 长整型long(8个字节)( )
  • 浮点型
    • float(单精度浮点数) (4个字节) double(双精度浮点数)(8个字节)(一般默认)
  • 字符型
    • char(字符型)(2个字节)
  • 布尔型
    • boolean(1个字节)(只有两个结果true或者false)

8种基本类型在java中都有对应的封装类型,也就是引用类型:
整数类型 Byte、Short、Integer(-128~127)、Long
浮点数类型 Float、Double
字符型 Character
布尔类型 Boolean

在参数传递时,基本类型都是传值,也就是传递的都是原变量的值得拷贝,改变这个值不会改变原变量

引用数据类型

方法的参数为引用数据类型时传递的是地址值。

除了基本数据就是引用数据类型

基本数据类型加上[] 就变成引用数据类型

类、接口、数组

引用类型一般是通过new关键字来创建,比如Integer num = new Integer(3);它存放在内存的堆中,可以在运行时动态的分配内存大小,生存期也不必事先告诉编译器,当引用类型变量不被使用时,Java内部的垃圾回收器GC会自动回收走。引用变量中存放的不是变量的内容,而是存放变量内容的地址。

引用类型传递的是地址,也就是参数与原变量指向的是同一个地址,所以如果改变参数的值,原变量的值也会改变

16、栈内存 堆内存

image-20200913144016793

17、自动装箱拆箱

DK 1.5 (以后的版本)的新特性自动装箱和拆箱

\1. 自动装箱:把基本类型转换为包装类类型
int a =10;
Integer i = new Integer(a);

Integer value = 10;
为什么基本类型就能直接转化为Integer ,Integer 不应该是new出来的吗
内部会自动的 new Integer(10) 自动装箱

\2. 自动拆箱: 把包装类型转换为基本类型
Integer value2 = new Integer(120);
int a = value2;
对象赋值给基本数据类型,为什么会不报错,因为内部会调用 value2.intValue() 这种就是自动拆箱

举一反三

Double d1 = 9.14 //内部会自动new一个Double 然后传递进去

new 出来的东西 每个都会分配一个内存地址

拆箱:devaning
装箱:

装箱拆箱面试题: 考点(Integer内部装箱的内部实现)

看程序写结果
1.

Integer value1 = new Integer(97);
Integer value2 = new Integer(97);
System.out.println(value1 == value2);
System.out.println(value.equals(value2)); //这个就是比较值
System.out.println("-------------------");

答案 : false true

\2. 自动装箱,如果值一样、地址也一样

Integer value3 = 127; //自动装箱
Integer value4 = 127;
System.out.println(value3 == value4);
System.out.println(value3.equals(value4)); //这个也是比较值

答案:true true

`3.

Integer value5 = 128;
Integer value6 = 128;
System.out.println(value5==value6); //false
System.out.println(value5.equals(value6)); //true

答案: false true

总结: 自动装箱,范围在 -128 ~ 127 【256个数字 】的地址是一样的,其他范围地址不一样
-128 到 127 之间的有个自动装箱的缓存池 如果不在这个范围,就会使用new 新创建

18、Java中的内存分配

栈:stack 存储局部变量,方法在栈中开辟一块栈并运行

堆:heap 存储对象及其成员变量,以及成员方法的内存地址,所有 new 的都在堆中

方法区:其实就是堆中的永久代,Jdk8 改为元空间:存储 class 文件及方法相关信息

本地方法栈:与操作系统相关

寄存器:与 cpu 相关

19、SSM框架

1、Spring 在ssm中起什么作用?

Spring:轻量级框架

作用:Bean工厂,用来管理Bean的生命周期和框架集成。

两大核心:

①. IOC/DI(控制反转/依赖注入) :把dao依赖注入到service层,service层反转给action层,Spring顶层容器为BeanFactory。

②. AOP:面向切面编程

2、Spring的事务?

编程式事务管理:编程方式管理事务,极大灵活性,难维护。

声明式事务管理:可以将业务代码和事务管理分离,用注解和xml配置来管理事务。

3、IOC 在项目中的作用?

作用:Ioc解决对象之间的依赖问题,把所有Bean的依赖关系通过配置文件或注解关联起来,降低了耦合度。

4、Spring的配置文件中的内容?

开启事务注解驱动

事务管理器

开启注解功能,并配置扫描包

配置数据库

配置SQL会话工厂,别名,映射文件

不用编写Dao层的实现类

5、Spring下的注解?

注册:@Controller @Service @Component

注入:@Autowired @Resource

请求地址:@RequestMapping

返回具体数据类型而非跳转:@ResponseBody

6、Spring DI 的三种方式?

构造器注入:通过构造方法初始化

<constructor-arg index="0" type="java.lang.String" value="宝马"></constructor-arg>
1

setter方法注入:通过setter方法初始化

<property name="id" value="1111"></property>
1

接口注入

7、Spring主要使用了什么模式?

工厂模式:每个Bean的创建通过方法

单例模式:默认的每个Bean的作用域都是单例

代理模式:关于Aop的实现通过代理模式

8、IOC,AOP的实现原理?

IOC:通过反射机制生成对象注入

AOP:动态代理

二、SpringMvc面试题

1、SpringMvc 的控制器是不是单例模式,如果是,有什么问题,怎么解决?

问题:单例模式,在多线程访问时有线程安全问题

解决方法:不要用同步,在控制器里面不能写字段

2、SpringMvc 中控制器的注解?

@Controller:该注解表明该类扮演控制器的角色

3、@RequestMapping 注解用在类上的作用?

作用:用来映射一个URL到一个类或者一个特定的处理方法上

4、前台多个参数,这些参数都是一个对象,快速得到对象?

方法:直接在方法中声明这个对象,SpringMvc就自动把属性赋值到这个对象里面

5、SpringMvc中函数的返回值?

String,ModelAndView,List,Set 等

一般String,Ajax请求,返回一个List集合

6、SpringMvc中的转发和重定向?

转发: return:“hello”

重定向 :return:“redirect:hello.jsp”

通过JackSon框架把java里面对象直接转换成js可识别的json对象,具体步骤如下:

加入JackSon.jar

在配置文件中配置json的映射

在接受Ajax方法里面直接返回Object,list等,方法前面需要加上注解@ResponseBody

8、SpringMvc的工作流程图?

img

9、Struts2 和 SpringMvc的区别?

入口不同:

Struts2:filter过滤器

SpringMvc:一个Servlet即前端控制器

开发方式不同:

Struts2:基于类开发,传递参数通过类的属性,只能设置为多例

SpringMvc:基于方法开发(一个url对应一个方法),请求参数传递到方法形参,可以为单例也可以为多例(建议单例)

请求方式不同:

Struts2:值栈村塾请求和响应的数据,通过OGNL存取数据

SpringMvc:通过参数解析器将request请求内容解析,给方法形参赋值,将数据和视图封装成ModelAndView对象,最后又将ModelAndView中的模型数据通过request域传输到页面,jsp视图解析器默认使用的是jstl。

三、Mybatis面试题

1、Ibatis和Mybatis?

Ibatis:2010年,apache的Ibatis框架停止更新,并移交给了google团队,同时更名为MyBatis。从2010年后Ibatis在没更新过,彻底变成了一个孤儿框架。一个没人维护的框架注定被mybatis拍在沙滩上。

Mybatis:Ibatis的升级版本。

2、什么是Mybatis的接口绑定,有什么好处?

Mybatis实现了DAO接口与xml映射文件的绑定,自动为我们生成接口的具体实现,使用起来变得更加省事和方便。

3、什么情况用注解,什么情况用xml绑定?

注解使用情况:Sql语句简单时

xml绑定使用情况:xml绑定 (@RequestMap用来绑定xml文件)

4、Mybatis在核心处理类叫什么?

SqlSession

5、查询表名和返回实体Bean对象不一致,如何处理?

映射键值对即可

<result column="title" property="title" javaType="java.lang.String"/>
1

column:数据库中表的列名

property:实体Bean中的属性名

6、Mybatis的好处?

把Sql语句从Java中独立出来。

封装了底层的JDBC,API的调用,并且能够将结果集自动转换成JavaBean对象,简化了Java数据库编程的重复工作。

自己编写Sql语句,更加的灵活。

入参无需用对象封装(或者map封装),使用@Param注解

7、Mybatis配置一对多?

<collection property="topicComment" column="id" ofType="com.tmf.bbs.pojo.Comment" select="selectComment" />
1

property:属性名

column:共同列

ofType:集合中元素的类型

select:要连接的查询

8、Mybatis配置一对一?

<association property="topicType" select="selectType" column="topics_type_id" javaType="com.tmf.bbs.pojo.Type"/>
1

property:属性名

select:要连接的查询

column:共同列

javaType:集合中元素的类型

9 、${} 和 #{}的区别?

:简单字符串替换,把{}:简单字符串替换,把:简单字符串替换,把{}直接替换成变量的值,不做任何转换,这种是取值以后再去编译SQL语句。

#{}:预编译处理,sql中的#{}替换成?,补全预编译语句,有效的防止Sql语句注入,这种取值是编译好SQL语句再取值。

总结:一般用#{}来进行列的代替

10、获取上一次自动生成的主键值?

select last _insert_id()
1

11、Mybatis如何分页,分页原理?

RowBounds对象分页

在Sql内直接书写,带有物理分页

12、Mybatis工作原理?

image

原理:

通过SqlSessionFactoryBuilder从mybatis-config.xml配置文件中构建出SqlSessionFactory。

SqlSessionFactory开启一个SqlSession,通过SqlSession实例获得Mapper对象并且运行Mapper映射的Sql语句。

完成数据库的CRUD操作和事务提交,关闭SqlSession。

框架整合

Spring

概念

Spring是一个开源的轻量级的应用开发框架,其目的是用于简化企业级应用程序开发,减少侵入;[h1] (IOC)

Spring的IOC和AOP应用,将组件的耦合度降至最低,即解耦,便于系统的维护和升级;

可以与第三方框架和技术整合应用,可以自由选择技术进行开发。

特点

轻量:Spring 是轻量的,基本的版本大约2MB

控制反转IOC:Spring通过控制反转实现了松散耦合,为对象提供所需的依赖,而不需要对象去创建或查找依赖

面向切面的编程(AOP):Spring支持面向切面的编程,并且把应用业务逻辑和系统服务分开

容器:Spring 包含并管理应用中对象的生命周期和配置

MVC****框架:Spring的WEB框架是个精心设计的框架,是Web框架的一个很好的替代品

事务管理:Spring 提供一个持续的事务管理接口,可以扩展到上至本地事务下至全局事务(JTA[A2]

异常处理:Spring 提供方便的API把具体技术相关的异常(比如由JDBC,Hibernate or JDO[A3] 抛出的)转化为一致的unchecked 异常。

组成模块

微信图片_20190215201411

核心容器(应用上下文) 模块 Core Container

Core:核心模块提供了框架的基本组成部分,包括 IoC (控制反转) 和DI (依赖注入)功能。

Bean:(注入bean)工厂是工厂模式的一个实现,提供了控制反转功能,用来把应用的配置和依赖从真正的应用代码中分离。最常用的BeanFactory 实现是XmlBeanFactory 类。

XMLBeanFactory,它根据XML文件中的定义加载beans。该容器从XML 文件读取配置元数据并用它去创建一个完全配置的系统或应用。

Context:上下文模块建立在由核心和 Bean 模块提供的坚实基础上,它是访问定义和配置的任何对象的媒介。ApplicationContext 接口是上下文模块的重点。

SpEl:表达式语言模块在运行时提供了查询和操作一个对象图的强大的表达式语言。

数据访问/集成

JDBC:模块提供了删除冗余的 JDBC 相关编码的 JDBC 抽象层[A4]

ORM[A5] :模块为流行的对象关系映射 API,包括 JPA,JDO,Hibernate 和 iBatis,提供了集成层。

OXM:模块提供了抽象层,它支持对 JAXB,Castor,XMLBeans,JiBX 和 XStream 的对象/XML 映射实现。

JMS:Java消息服务 JMS 模块包含生产和消费的信息的功能。

Transactions****事务模块:为实现特殊接口的类及所有的 POJO 支持编程式和声明式事务管理。

Web模块

Web:模块提供了基本的面向 web 的集成功能,例如多个文件上传的功能和使用 servlet 监听器和面向 web 应用程序的上下文来初始化 IoC 容器。

Web-MVC:模块包含 Spring 的model模型-view视图-controller控制器(MVC),实现了 web 应用程序。

Web-Socket:模块为 WebSocket-based 提供了支持,而且在 web 应用程序中提供了客户端和服务器端之间通信的两种方式。

Web-Portlet:模块提供了在 portlet 环境中实现 MVC,并且反映了 Web-Servlet 模块的功能。

其他模块

AOP[A6] :模块提供了面向切面的编程实现,允许你定义方法拦截器和切入点对代码进行干净地解耦,它实现了应该分离的功能。

Aspects:模块提供了与 AspectJ 的集成,这是一个功能强大且成熟的面向切面编程(AOP)框架。

Instrumentation:模块在一定的应用服务器中提供了类 instrumentation 的支持和类加载器的实现。

Messaging:模块为 STOMP 提供了支持作为在应用程序中 WebSocket 子协议的使用。它也支持一个注解编程模型,它是为了选路和处理来自 WebSocket 客户端的 STOMP 信息。

test:测试模块支持对具有 JUnit 或 TestNG 框架的 Spring 组件的测试。

Spring IOC 控制反转

什么是IOC 控制反转[A7]

Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想。

原始:当某个角色需要另外一个角色协助的时候,在传统的程序设计过程中,通常由调用者来创建被调用者的实例。例如:service需要调用dao,就需要在service中创建一个dao对象

Spring:在spring中创建被调用者的工作不再由调用者来完成,创建被调用者的工作由spring容器来完成,然后用注解注入调用者,因此称为控制反转。

核心原理:配置文件(Bean) + 反射(工厂)+ 容器(map)

作用:它能指导我们如何设计出松耦合、更优良的程序。在传统应用中,程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;现在通过IoC容器,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。

总之,IoC很好的体现了面向对象设计法则之一—— 好莱坞法则:“别找我们,我们找你”;即由IoC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。其设计方式是从顶层开始逐步添加细节,而不是以前传统的底层细节决定顶层。

什么是DI 依赖注入

DI—Dependency Injection,即“依赖注入”:是组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。

作用:依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率。其重点是完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。

方式:在pom问题添加依赖

Spring AOP 切面编程

什么是AOP 切面编程

AOP—Aspect Oriented Programming,即“面向切面编程”,不是什么技术,而是一种设计思想。在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等。利用AOP可以将那些与业务无关,却被业务模块共同调用的逻辑提取并封装起来,减少了系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。

基本概念

Aspect 切面:通常是一个类,里面可以定义切入点和通知

JointPoint 连接点:程序执行过程中明确的点,一般是方法的调用

Advice 通知:AOP在特定的切入点上执行的增强处理,有before前置, after后置, afterReturning, afterThrowing, around环绕

Pointcut 切入点:就是带有通知的连接点,在程序中主要体现为书写切入点表达式

AOP****代理:AOP框架创建的对象,代理就是目标对象的加强。Spring中的AOP代理可以使用JDK动态代理,也可以是CGLIB代理,前者基于接口(InvocationHandler),后者基于子类。

织入方式

织入是将切面和到其他应用类型或对象连接或创建一个被通知对象的过程。

织入可以在编译时,加载时,或运行时完成。

编译时织入:需要特殊的java编译器,如Aspectj

类加载时织入:需要特殊的java编译器,如Aspectj和AspectWerkz

运行时织入:Spring采用的方式,通过动态代理的方式实现。

Spring Aop的实现

Spring中的AOP代理还是离不开Spring的IOC容器,代理的生成,管理及其依赖关系都是由IOC容器负责,Spring默认使用JDK动态代理,在需要代理类而不是代理接口的时候,Spring会自动切换为使用CGLIB代理,不过现在的项目都是面向接口编程,所以JDK动态代理相对来说用的还是多一些。

实现策略

基于XML的实现

在这种情况下,切面由常规类以及基于XML的配置实现。

基于注解的切面实现

在这种情况下(基于@AspectJ的实现),涉及到的切面声明的风格与带有java5标注的普通java类一致。

Spring beans

Spring bean是什么

Spring beans 是那些形成Spring应用的主干的java对象。它们被Spring IOC容器初始化,装配,和管理。Spring容器理解成一个大型工厂,Bean就是该工厂的产品,工厂(Spirng容器)里能生产出来什么样的产品(Bean),完全取决于我们在配置文件中的配置。

生命周期

在说明前可以思考一下Servlet的生命周期:实例化,初始init,接收请求service,销毁destroy;

1、实例化一个Bean--也就是我们常说的new;

2、按照Spring上下文对实例化的Bean进行配置--也就是IOC注入;

实现一系列Aware接口,主要描述Bean和容器直接关系。

3、如果这个Bean已经实现了BeanNameAware接口,会调用它实现的setBeanName(String)方法,此处传递的就是Spring配置文件中Bean的id值

4、如果这个Bean已经实现了BeanFactoryAware接口,会调用它实现的setBeanFactory(setBeanFactory(BeanFactory)传递的是Spring工厂自身(可以用这个方式来获取其它Bean,只需在Spring配置文件中配置一个普通的Bean就可以);

5、如果这个Bean已经实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文(同样这个方式也可以实现步骤4的内容,但比4更好,因为ApplicationContext是BeanFactory的子接口,有更多的实现方法);

6、如果这个Bean关联了BeanPostProcessor接口,将会调用postProcessBeforeInitialization(Object obj, String s)方法,BeanPostProcessor经常被用作是Bean内容的更改,并且由于这个是在Bean初始化结束时调用那个的方法,也可以被应用于内存或缓存技术;

7、如果Bean在Spring配置文件中配置了init-method属性会自动调用其配置的初始化方法。

8、如果这个Bean关联了BeanPostProcessor接口,将会调用postProcessAfterInitialization(Object obj, String s)方法、;

注:以上工作完成以后就可以应用这个Bean了,那这个Bean是一个Singleton的,所以一般情况下我们调用同一个id的Bean会是在内容地址相同的实例,当然在Spring配置文件中也可以配置非Singleton****,这里我们不做赘述。

9、当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean这个接口,会调用那个其实现的destroy()方法;

10、最后,如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法。

以上10步骤可以作为面试或者笔试的模板,另外我们这里描述的是应用Spring上下文Bean的生命周期,如果应用Spring的工厂也就是BeanFactory的话去掉第5步就Ok了。

作用域

singleton :

bean在每个Spring ioc 容器中只有一个实例。缺省的Spring bean 的作用域是Singleton。默认的(估计是为了体现轻量级和性能,而且一般情况下单例足够了,若需要其他方式可自行更改)

prototype

一个bean的定义可以有多个实例。

request

每次http请求都会创建一个bean,该作用域仅在基于web的Spring ApplicationContext情形下有效。

session

在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。

global-session

在一个全局的HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。

自动装配

在Spring 容器中把bean组装到一起,前提是容器需要知道bean的依赖关系,如何通过依赖注入来把它们装配到一起。因此体现配置好bean的依赖关系,Spring 容器能够自动装配相互合作的bean,能通过Bean工厂自动处理bean之间的协作。

装配方式

no:默认的方式是不进行自动装配,通过显式设置ref 属性来进行装配。

byName:通过参数名 自动装配,Spring容器在配置文件中发现bean的autowire属性被设置成byname,之后容器试图匹配、装配和该bean的属性具有相同名字的bean。

byType:通过参数类型自动装配,Spring容器在配置文件中发现bean的autowire属性被设置成byType,之后容器试图匹配、装配和该bean的属性具有相同类型的bean。如果有多个bean符合条件,则抛出错误。

constructor:这个方式类似于byType, 但是要提供给构造器参数,如果没有确定的带参数的构造器参数类型,将会抛出异常。

autodetect:首先尝试使用constructor来自动装配,如果无法工作,则使用byType方式。

局限性

重写:你仍需用 和配置来定义依赖,意味着总要重写自动装配。

基本数据类型:你不能自动装配简单的属性,如基本数据类型,String字符串,和类。

模糊特性:自动装配不如显式装配精确,如果有可能,建议使用显式装配。

创建及依赖配置

实例化 Bean 的三种方式:

构造器方法创建Bean:调用构造方法创建Bean是最常用的一种情况Spring容器通过new关键字调用构造器来创建Bean实例,注:无参使用的是property 标签,有参使用的是constructor-arg标签。

静态工厂方法创建Bean:需要额外提供工厂类,工厂方法是静态方法

实例工厂方法创建Bean:创建实例化工厂类,先注册工程然后引用工厂

依赖配置方式

构造方法方式注入:构造器依赖注入通过容器触发一个类的构造器来实现的,该类有一系列参数,每个参数代表一个对其他类的依赖。构造器参数实现强制依赖

set****方法方式的注入:Setter方法注入是容器通过调用无参构造器或无参static工厂 方法实例化bean之后,调用该bean的setter方法,即实现了基于setter的依赖注入。setter****方法实现可选依赖

名称空间注入:Spring2.5以后提供了一种p名称空间和C名称空间的属性注入. 通过直接写属性名的方式注入属性值。

SpEL****的属性注入:使用spring 提供的一种表达式方式去赋值,最大的好处在于,它能像EL表达式一般,在里面进行运算、 逻辑判断,还可以调用其它Bean的属性和方法给当前属性赋值。

BeanFactory和ApplicationContext比较

相同点

BeanFactory和ApplicationContext,其中ApplicationContext是BeanFactory的子接口。他们都可代表Spring容器,Spring容器是生成Bean实例的工厂,并且管理容器中的Bean。

不同点

BeanFactory核心库是在bean中,实现类是XMLBeanFactory,它根据XML文件中的定义加载beans。该容器从XML 文件读取配置元数据并用它去创建一个完全配置的系统或应用。

ApplicationContext核心库是context中,ApplicationContext通常的实现方式:

FileSystemXmlApplicationContext :此容器从一个XML文件中加载beans的定义,XML Bean 配置文件的全路径名必须提供给它的构造函数。

ClassPathXmlApplicationContext:此容器也从一个XML文件中加载beans的定义,这里,你需要正确设置classpath因为这个容器将在classpath里找bean配置。

WebXmlApplicationContext:此容器加载一个XML文件,此文件定义了一个WEB应用的所有bean。

Application contexts提供一种方法处理文本消息,一个通常的做法是加载文件资源(比如镜像),它们可以向注册为监听器的bean发布事件。另外,在容器或容器内的对象上执行的那些不得不由bean工厂以程序化方式处理的操作,可以在Application contexts中以声明的方式处理。Application contexts实现了MessageSource接口,该接口的实现以可插拔的方式提供获取本地化消息的方法。

初始化区别

BeanFactory在初始化容器时,并未实例化Bean,直到第一次访问某个Bean时才实例目标Bean;

ApplicationContext则在初始化应用上下文时就实例化所有单实例的Bean。因此ApplicationContext的初始化时间会比BeanFactory稍长一些,不过稍后的调用则没有”第一次惩罚”的问题

JavaBean/ Spring bean/ POJO

JavaBean****: JavaBean是遵循了Sun制定的JavaBean 标准的类。其三个特性是:包含默认(无参数)的构造函数,允许通过访问器(getter和setter方法)来访问类的成员属性,实现java.io.Serializable接口。

POJO****: POJO是 Plain Old Java Object(简单的Java对象)的缩写。是一种花式的对普通Java对象的称呼。这个词主要用来区分简单、轻量的Java对象和“重量级“的类

Spring bean****: Spring bean 表示受到Spring管理的对象。具体说来,它是被Spring框架容器初始化、配置和管理的对象。

Spring事务管理

编程式事务管理

编程式事务管理:这意味你通过编程的方式管理事务,给你带来极大的灵活性,但是难维护。

声明式事务管理

声明式事务管理:这意味着你可以将业务代码和事务管理分离,你只需用注解和XML配置来管理事务。

两种事务比较

大多数Spring框架的用户选择声明式事务管理,因为它对应用代码的影响最小,因此更符合一个无侵入的轻量级容器的思想。声明式事务管理要优于编程式事务管理,虽然比编程式事务管理(这种方式允许你通过代码控制事务)少了一点灵活性。

事务的传播特性和隔离级别

propagation_required 需要 如果存在一个事务,则指出当前事情。如果没有事务则开启事务

propagation_supports 支持 如果存在一个事务,支持当前事务,如果没有事务,则非事务的执行

propagation_mandatory 必要的 如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常

propagation_requires_new 总是开启一个新的事务,如果一个事务已经存在,则将这个存在的事务挂起

propagation_not_supported 总是非事务的执行,并挂起任何存在的事务

propagation_never 总是非事务的执行,如果存在一个活动事务,则抛出异常

propagation_nested 如果一个活动的事务存在,则运行在一个嵌套的事务中,如果没有就开启事务

Spring注解

基于注解的容器配置

相对于XML文件,注解型的配置依赖于通过字节码元数据装配组件,而非尖括号的声明。

开发者通过在相应的类,方法或属性上使用注解的方式,直接组件类中进行配置,而不是使用xml表述bean的装配关系。

怎样开启注解装配

开启配置

<context:component-scan base-package="扫描的包名"></context:component-scan>

注解装配在默认情况下是不开启的,为了使用注解装配,我们必须在Spring配置文件中配置元素。

控制类注解

@Component****()

将该类注入到Spring容器中。

value:指定 bean 的 id。如果不指定 value 属性,默认 bean 的 id 是当前类的类名。首字母小写。

衍生注解:@Controller修饰WEB层类; @Service修饰业务层类;@Repository修饰DAO层类。

@scope

注解用来描述类的作用范围的,默认值singleton;多例的使用prototype。

@PostConstrut

设置spring框架初始化此类实例时调用的初始化方法,标注在此类的初始化方法上。

@PreDestroy

用来设置spring框架销毁此类实例时调用的销毁方法,标注在此类的销毁方法上。

注入属性

@Value

注入基本数据类型和 String 类型数据的。

@Autowired

自动按照类型注入,找到变量名一致的id对象给注入进去。

@Qualifier(value = "accountDao02")

value:指定bean的id。再按照Bean的id注入,必须和 @Autowired一起使用。

@Resource(name = "accountDao02")

指定找具体的某一个实现,name参数设置要找到类的名称。

功能性注解

@Configuration

用于指定当前类是一个spring配置类,当创建容器时会从该类上加载注解

@ComponentScan("com.itheima")

用于指定spring在初始化容器时要扫描的包。

@Bean

该注解只能写在方法上,表明使用此方法创建一个对象,并且放入spring容器。

name:给当前@Bean注解方法创建的对象指定一个名称(即bean的id)。

@Import

用于导入其他配置类

@PropertySource

用于加载.properties文件中的配置。@Value结合该注解可以直接给类的属性初始化

测试注解

@RunWith(SpringJUnit4ClassRunner.class)

指明运行的测试环境

@ContextConfiguration("classpath:applicationContext.xml")

指明spring框架的加载的配置文件 有applicationContext.xml

@ContextConfiguration(classes = SpringConfiguration.class)

指明spring框架的加载的配置类 纯注解

切面

aop:aspectj-autoproxy/开启AOP注解

@Aspect在切面类中定义切入点方法

@Before 前置通知

@AfterReturning 后置通知

@Around 环绕通知

@AfterThrowing 异常抛出通知

@After 最终通知

Spring 中的设计模式

单例模式

在 spring 的配置文件中设置 bean 默认为单例模式。

代理模式

jdk 的 java.lang.reflect.Proxy类代理:若目标对象实现了若干接口,IOC, AOP

spring 使用 CGLIB 库生成目标类的子类:若目标兑现没有实现任何接口

模板模式

用来解决代码重复的问题:RestTemplate、 JmsTemplate、 JpaTemplate

前端控制器模式

spring 提供了前端控制器 DispatherServlet 来对请求进行分发

依赖注入

贯穿于 BeanFactory/ApplacationContext 接口的核心理念。

工厂模式

我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用同一个接口来指向新创建的对象。Spring 中使用 beanFactory 来创建对象的实例。

SpringMVC

什么是SpringMVC

概念

SpringMVC 是一种基于Java实现的MVC设计模型的请求驱动类型的轻量级WEB框架。主要作用是处理所有的HTTP请求和响应。

WebApplicationContext[h8] :WebApplicationContext 继承了ApplicationContext 并增加了一些WEB应用必备的特有功能,它不同于一般的ApplicationContext ,因为它能处理主题,并找到被关联的servlet。

优点

1、清晰的角色划分

2、分工明确,而且扩展点相当灵活

3、命令对象就是一个 POJO,无需继承框架特定 API,可以使用命令对象直接作为业务对象

4、和 Spring 其他框架无缝集成,是其它 Web 框架所不具备的

5、可适配,通过 HandlerAdapter 可以支持任意的类作为处理器

6、可定制性, HandlerMapping、 ViewResolver 等能够非常简单的定制

7、功能强大的数据验证、格式化、绑定机制

8、强大的 JSP 标签库,使 JSP 编写更容易

9、RESTful风格的支持、简单的文件上传、约定大于配置的契约式编程支持、基于注解的零配置支持

控制流程原理

img

流程解释

1、客户端发送request请求,服务器前端控制器DispatcherServlet接受请求响应;DisoatcherServlet对请求URL进行解析,得到请求资源标识符URI;将请求转发给HandlerMapping;

2、HandlerMapping处理映射器接受后,将请求映射到对应的处理器来处理请求,将映射结果返回DispatcherServlet

3、DispatcherServlet再请求HandlerAdapter处理器适配器,由适配器制定具体的Handler的去执行

4、Handler处理器是编写的具体业务控制器,在Spring 当中如果写一些处理器组件,一般实现Controller 接口;

5、Handler处理器处理的结果返回ModelAndView给适配器,适配器再返回给前端控制器

6、前端控制器接收ModelAndView后请求视图解析器View Resolver据逻辑视图名解析成物理视图名即具体的页面地址;再生成 View 视图对象,最后对 View 进行渲染将处理结果通过页面展示给用户。

组件模块

DispatcherServlet****前端控制器

整个流程控制的中心,它就相当于 mvc 模式中的 c,由它调用其它组件处理用户的请求,dispatcherServlet 的存在降低了组件之间的耦合性。

HandlerMapping****处理器映射器

负责根据用户请求找到 Handler 即处理器;SpringMVC 提供了不同的映射器实现不同的映射方式;例如:配置文件方式,实现接口方式,注解方式等。

HandlAdapter****处理器适配器

通过 HandlerAdapter 对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。

Handler****处理器

开发中要编写的具体业务控制器。由 DispatcherServlet 把用户请求转发到 Handler,对具体的用户请求进行处理。

View Resolver****视图解析器

首先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成 View 视图对象。

View****视图

View 视图过页面标签或页面模版技术将模型数据通过页面展示给用户。

参数类型

请求参数

String 类型:普通的字符串类型;

POJO 类型:要求表单中参数名称和 POJO 类的属性名称保持一致。并且控制器方法的参数类型是 POJO 类型,表单中 name 属性 必须和pojo的对象属性名一致;

POJO 类中List

POJO 类中Map<String,Account>

返回值

字符串:经过视图解析器解析为 jsp 物理路径: /WEB-INF/pages/success.jsp;

Void:RequestMapping的取值作为视图名称;

ModelAndView:ModelAndView 是 SpringMVC 为我们提供的一个对象,该对象也可以用作控制器方法的返回值。

forward 转发:如果用了 formward: 则路径必须写成实际视图 url,不能写逻辑视图。

Redirect 重定向:返回一个新的url并跳转。

响应json:使用@ResponseBody 注解实现将 controller 方法返回对象转换为 json 响应给客户端

常用注解

@RequestMapping

作用在方法和类上

path:指定请求路径的url

method :指定该方法的请求方式

Params :指定限制请求参数的条件

Headers :发送的请求中必须包含的请求头

@RequestBody

用于获取请求体内容。直接使用得到是 key=value&key=value...结构的数据

@PathVaribale

用于绑定 url 中的占位符。

url支持占位符是spring3.0之后加入的。是springmvc支持restful风格URL的一个重要标志。

@RequestHeader

用于获取请求消息头,value:提供消息头名称

@CookieValue

用于把指定 cookie 名称的值传入控制器方法参数,value:指定 cookie 的名称。

@SessionAttributes

用于多次执行(多次请求)控制器方法间的参数共享。(该注解定义在类上)

@ModelAttribute

出现在方法上,表示当前方法会在控制器的方法执行之前,出现在参数上,获取指定的数据给参数赋值。

Struts2和SpringMVC比较

核心控制器(前端控制器、预处理控制器)

核心控制器的主要用途是处理所有的请求,然后对那些有特殊请求统一进行处理(字符编码、文件上传、参数接受、异常处理)

SpringMVC核心控制器是Servlet,SrpingMVC控制器基于方法级别的拦截,处理器设计为单实例。

struct2是Filter,Struts2控制器基于类级别的拦截,处理器设计为多实例,而且只能设计为多实例模式,消耗更多的服务器内存。

控制器实例

SpringMVC会比Struts快一些,SpringMVC是基于方法设计

Sturts是基于对象,每一次请求都有有个action实例,每次请求执行对应的方法即可。

管理方式

大部分的公司的核心框架结构中,就会使用到spring,而springmvc又是spring中一个模块,所以spring对于springmvc的控制器管理更加方便,而且提供了全注解方式进行管理,各种功能的注解会比较全面,使用简单。

而使用struct2需要采用xml很多配置参数来管理,方式会比较麻烦。

参数传递

springmvc是通过方法和参数进行接收。

Struts2中自身提供多种参数接收,其实都是通过valuestack进行传递和赋值的。

学习难度

springmvc学习成本比较简单,容易上手。

Struts增加了很多新的技术点,比如拦击器,值栈及OGNL表达式,学习成本较高。

intercept实现机制

springmvc是使用的独立的AOP方式,是方法级别的拦截。

Struts是有自己的interceptor机制,配置文件量比较大。

数据返回

springmvc处理ajax请求,直接返回数据,方法中使用注解@ResponseBody或者@RestController,自动帮转换为json对象。

拦截器与过滤器比较

拦截器

SpringSecurity

SpringBoot[h9]

概念

相对于spring方式开发中需要进行大量的配置文件,而且还需要考虑jar包之间的冲突,因此,人们基于约定优于配置的思想,将一些约定俗称的的配置信息添加进行配置好,在开发过程只需要进行简单的起步就完成项目基本框架搭建。SpringBoot不是对Spring功能上的增强,而是提供了一种快速使用Spring的方式。

特点

起步依赖

起步依赖本质上是一个Maven项目对象模型(Project Object Model,POM),定义了对其他库的传递依赖,这些东西加在一起即支持某项功能。

简单的说,起步依赖就是将具备某种功能的坐标打包到一起,并提供一些默认的功能。

自动配置

Spring Boot的自动配置是一个运行时(更准确地说,是应用程序启动时【main方法】)的过程,考虑了众多因素,才决定Spring配置应该用哪个,不该用哪个。该过程是Spring自动完成的。

起步原理

@SpringBootApplication

下面有三个重要的注解:

@SpringBootConfiguration

它就是JavaConfig形式的Spring Ioc容器的配置类使用的那个@Configuration,SpringBoot社区推荐使用基于JavaConfig的配置形式,所以,这里的启动类标注了@Configuration之后,本身其实也是一个IoC容器的配置类。

@ComponentScan

@ComponentScan的功能其实就是自动扫描并加载符合条件的组件(比如@Component和@Controller, @Service, @Repository等)或者bean定义,最终将这些bean定义加载到IoC容器中。

我们可以通过basePackages等属性来细粒度的定制@ComponentScan自动扫描的范围,如果不指定,则默认Spring框架实现会从声明@ComponentScan所在类的package进行扫描当前包及其子包下的文件。

@EnableAutoConfiguration

@EnableAutoConfiguration也是借助@Import的帮助,将所有符合自动配置条件的bean定义加载到IoC容器,仅此而已!会扫描到spring.factories 文件, 加载application.properties 或者application.yal(yaml)文件

@AutoConfigurationPackage:自动配置包

它其实是注册了一个Bean的定义。

new PackageImport(metadata).getPackageName(),它其实返回了当前主程序类的 同级以及子级的包组件。

@Import: 导入自动配置的组件

Import(AutoConfigurationImportSelector.class)注解:

AutoConfigurationImportSelector 继承了 DeferredImportSelector 继承了 ImportSelector

ImportSelector有一个方法为:selectImports。

可以看到第九行,它其实是去加载 public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";外部文件。这个外部文件,有很多自动配置的类。如下:

@Import(EnableAutoConfigurationImportSelector.class),借助EnableAutoConfigurationImportSelector,@EnableAutoConfiguration可以帮助SpringBoot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器。就像一只“八爪鱼”一样。

SpringFactoriesLoader

借助于Spring框架原有的一个工具类:SpringFactoriesLoader的支持,@EnableAutoConfiguration可以智能的自动配置功效才得以大功告成!

SpringFactoriesLoader属于Spring框架私有的一种扩展方案,其主要功能就是从指定的配置文件META-INF/spring.factories加载配置。@EnableAutoConfiguration借助于Spring框架原有的一个工具类:SpringFactoriesLoader的支持,才可以智能的自动配置功效才得以大功告成!

配合@EnableAutoConfiguration使用的话,它更多是提供一种配置查找的功能支持,即根据@EnableAutoConfiguration的完整类名org.springframework.boot.autoconfigure.EnableAutoConfiguration作为查找的Key,获取对应的一组@Configuration类。

@EnableAutoConfiguration自动配置的魔法骑士就变成了:从classpath中搜寻所有的META-INF/spring.factories配置文件,并将其中org.springframework.boot.autoconfigure.EnableutoConfiguration对应的配置项通过反射(Java Refletion)实例化为对应的标注了@Configuration的JavaConfig形式的IoC容器配置类,然后汇总为一个并加载到IoC****容器。

执行流程

第一步:使用的是SpringApplication的静态run方法创建一个SpringApplication对象实例。

个创建好的SpringApplication的实例方法。在SpringApplication实例初始化的时候,它会提前做几件事情:

根据classpath里面是否存在某个特征类

(org.springframework.web.context.ConfigurableWebApplicationContext)来决定是否应该

一个为Web应用使用的ApplicationContext类型。

使用SpringFactoriesLoader在应用的classpath中查找并加载所有可用的ApplicationContextInitializer。

使用SpringFactoriesLoader在应用的classpath中查找并加载所有可用的ApplicationListener。

推断并设置main方法的定义类。

第二步:遍历执行所有通过SpringFactoriesLoader可以查找到并加载的SpringApplicationRunListener。调用它们的started()方法,告诉这些SpringApplicationRunListener,“嘿,SpringBoot应用要开始执行咯!

第三步:创建并配置当前Spring Boot应用将要使用的Environment(包括配置要使用的PropertySource以及Profile)。

遍历调用所有SpringApplicationRunListener的environmentPrepared()的方法,告诉他们:“当前SpringBoot应用使用的Environment准备好了咯!”。

第四步:如果SpringApplication的showBanner属性被设置为true,则打印banner。

第五步:根据用户是否明确设置了applicationContextClass类型以及初始化阶段的推断结果,决定该为当前SpringBoot应用创建什么类型的ApplicationContext并创建完成,然后根据条件决定是否添加ShutdownHook,决定是否使用自定义的BeanNameGenerator,决定是否使用自定义的ResourceLoader,当然,最重要的,将之前准备好的Environment设置给创建好的ApplicationContext使用。

第六步:ApplicationContext创建好之后,SpringApplication会再次借助Spring-FactoriesLoader,查找并加载classpath中所有可用的ApplicationContext-Initializer,然后遍历调用这些ApplicationContextInitializer的initialize(applicationContext)方法来对已经创建好的ApplicationContext进行进一步的处理。

第七步:遍历调用所有SpringApplicationRunListener的contextPrepared()方法和contextLoaded()方法。将之前通过@EnableAutoConfiguration获取的所有配置以及其他形式的IoC容器配置加载到已经准备完毕的ApplicationContext。

第八步:调用ApplicationContext的refresh()方法,完成IoC容器可用的最后一道工序。 查找当前ApplicationContext中是否注册有CommandLineRunner,如果有,则遍历执行它们。正常情况下,遍历执行SpringApplicationRunListener的finished()方法

SpringCloud

概念

由于微服务是也是分布式的,需要一个解决方案来统一管理和配置这些零散的微服务。于是SpringCloud诞生了,就是基于SpringBoot提供的一套微服务解决方案。他巧妙的简化了分布式系统基础设施的开发,提供了快速构建分布式系统的一些工具,提供了服务注册与发现,配置中心,全链路监控,服务网关,负载均衡,熔断器等组件,是各个微服务架构落地技术的集合体。

SpringBoot可以离开SpringCloud独立使用开发项目,但是SpringCloud离不开SpringBoot,属于依赖的关系。

和Dubbo对比

Dubbo在服务注册中心用Zookeeper,服务调用方式RPC(方式) 内部封装了Netty , 用了TCP协议。

Spring Cloud使用Eureka做为注册中心,服务调用方式是基于HTTP协议的restful风格的方式调用。此外,Spring Cloud作为spring全家桶成员的一组,

还提供了6大核心组件,这个是Dubbo无法比拟的[h10]

服务发现——****Netflix Eureka:主要做项目的注册与发现管理,在负载均衡方面采用轮询算法。

服务调用——****Netflix Feign:实现远微服务程调用 , 例子: 问答微服务与基础微服务之间的调用

熔断器——****Netflix Hystrix:在需要调用的服务之间,开启了熔断器,防止出现雪崩。

服务网关[h11] ——****Netflix Zuul:分为前台网关和后台网关,达到好管理微服务的作用.在这个项目中通过服务网关服务之间的建立路由配置,减轻了维护成本,同时采用了身份认证和安全,通过token验证在Zuul进行过滤实现用户后台登录问题。

分布式配置—[h12] —****Spring Cloud Config:将各个微服务的配置文件放在git(私服)上面进行统一管理,方便配置的更改。

消息总线 —— Spring Cloud Bus:了实现配置文件更改后,我们采用rabbitmq消息中间件实现配置的实时更新 , 配合Spring Cloud Config 一起使用。

Eureka 服务发现组件

概念

SpringCloud将它集成在自己的子项目spring-cloud-netflix中,实现SpringCloud的服务发现功能。Eureka包含两个组件:Eureka Server和Eureka Client

Eureka Server提供服务注册服务,各个节点(各个微服务)启动后,会在Eureka Server中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息。服务节点的信息可以在界面中直观的看到。

Eureka Client是一个java客户端(微服务),用于简化与Eureka Server的交互,客户端同时也就别一个内置的、使用轮询(round-robin)负载算法的负载均衡器。

服务发现心跳包:在应用启动后,将会向Eureka Server发送心跳,默认周期为30秒,如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,Eureka Server将会从服务注册表中把这个服务节点移除(默认90秒)

保护模式

由于会出现因网络故障原因导致心跳超过90s,微服务本身其实是健康的,所以为了避免这样的误删所以有个保护模式的机制。

它的架构哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服务都会保留),也不盲目注销任何健康的微服务。使用自我保护模式,可以让Eureka集群更加的健壮、稳定.

在Spring Cloud中,可以使用eureka.server.enable-self-preservation = false 禁用自我保护模式。 (不建议使用)

配置过程

服务端(注册中心)开发

在父工程pom.xml定义SpringCloud版本;

创建子工程tensquare_eureka添加依赖

子工程tensquare_eureka添加application.yml

server:

port: 6868 端口

eureka:

client:

register-with-eureka: false #是否将自己注册到Eureka服务中,本身就是所有无需注册

fetch-registry: false #是否从Eureka中获取注册信息

serviceUrl: #Eureka客户端与Eureka服务端进行交互的地址格式

defaultZone: http://127.0.0.1😒{server.port}/eureka/

编写启动类 ,创建包com.tensquare.eureka ,包下建立类 , 添加@EnableEurekaServer 开启eureka服务

服务注册(配置客户端****)

在每个需要注册的微服务(客户端)添加依赖

修改每个微服务的application.yml,添加注册eureka服务的配置

eureka:

client:

serviceUrl: #Eureka客户端与Eureka服务端进行交互的地址

defaultZone: http://127.0.0.1:6868/eureka/

instance:

prefer-ip-address: true #把自己的ip地址报告给Eureka(远程需要,本地测试没有问题)

修改每个服务类的启动类,添加注解

@EnableEurekaClient

启动测试:将每个微服务启动起来,会发现eureka的注册列表中可以看到这些微服务了

Feign实现服务间的调用

微服务调用范式

在1号模块添加依赖,在项目包下创建接口LabelClient

@FeignClient注解用于指定从哪个服务(依赖的服务)中调用功能 ,名称必须一样

@RequestMapping注解用于对被调用的微服务进行地址映射;

@PathVariable注解一定要指定参数名称

在需要的类中

注入该接口进行调用,

此时启动类添加两个注解

@EnableDiscoveryClient

@EnableFeignClients

负载均衡

策略

IRule是一个接口用来定义负载均衡的, SpringCloud为我们提供了7种负载均衡算法

RoudRobinRule: 轮询(默认的)

RandomRule: 随机

过滤掉故障的,对身剩下的进行轮询

根据平均响应时间就算所有服务的权重,响应时间越快服务权重越大被选中 的概率越高.

还有几种获取服务失败则后处理的优化配置,用的不对,不记得了

AvailabilityFilteringRule: 会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,还有并发的连接数超过阈值的服务,然后对剩余的服务列表按照轮询策略进行访问

WeightResponseTimeRule: 根据平均响应时间就算所有服务的权重,响应时间越快服务权重越大被选中 的概率越高.刚启动时如果统计信息不足,则使用RoudRobinRule策略,等统计信息足够,会切换到WeightedResponseTimeRule

RetryRule: 先按照RoundRobinRule的策略获取服务,如果获取服务失败则在指定时间内会进行重试,获取可用的服务

BestAvailableRule: 会过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务

ZoneAvoidanceRule: 复合判断server所在区域的性能和server的可用性选择服务器

配置方式

在启动类中注入IRule类,需要什么负载均衡类型就返回对应的名称即可。

Hystrix 熔断器

背景介绍

对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。

解决方案

为了防止雪崩效应而产生的解决方案。

处理原理

Hystrix 能使你的系统在出现依赖服务失效的时候,通过隔离系统所依赖的服务,防止服务级联失败,同时提供失败回退机制,更优雅地应对失效,并使你的系统能更快地从异常中恢复。

处理过程

“断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。

配置过程

Feign 本身支持Hystrix,不需要额外引入依赖。

在需要依赖的地方配置application.yml ,开启hystrix

设置处理机制

其实需要继承这个类 HystrixCommand

设置参数:

//至少有10个请求,熔断器才进行错误率的计算;

//熔断器中断请求5秒后会进入半打开状态,放部分流量过去重试;

//错误率达到50开启熔断保护

执行命令的几种方法

Hystrix提供了4种执行命令的方法,execute()和queue() 适用于HystrixCommand对象,而observe()和toObservable()适用于HystrixObservableCommand对象。

execute()

以同步堵塞方式执行run(),只支持接收一个值对象。hystrix会从线程池中取一个线程来执行run(),并等待返回值。

queue()

以异步非阻塞方式执行run(),只支持接收一个值对象。调用queue()就直接返回一个Future对象。可通过 Future.get()拿到run()的返回结果,但Future.get()是阻塞执行的。若执行成功,Future.get()返回单个返回值。当执行失败时,如果没有重写fallback,Future.get()抛出异常。

observe()

事件注册前执行run()/construct(),支持接收多个值对象,取决于发射源。调用observe()会返回一个hot Observable,也就是说,调用observe()自动触发执行run()/construct(),无论是否存在订阅者。

如果继承的是HystrixCommand,hystrix会从线程池中取一个线程以非阻塞方式执行run();如果继承的是HystrixObservableCommand,将以调用线程阻塞执行construct()。

toObservable()

事件注册后执行run()/construct(),支持接收多个值对象,取决于发射源。调用toObservable()会返回一个cold Observable,也就是说,调用toObservable()不会立即触发执行run()/construct(),必须有订阅者订阅Observable时才会执行。

如果继承的是HystrixCommand,hystrix会从线程池中取一个线程以非阻塞方式执行run(),调用线程不必等待run();如果继承的是HystrixObservableCommand,将以调用线程堵塞执行construct(),调用线程需等待construct()执行完才能继续往下走。

返回方法

创建熔断实现类,实现自接口LabelClient ,并注入到spring容器

return new Result(false, StatusCode.ERROR,"熔断器启动了");

修改调用接口注解配置

@FeignClient中添加fallback = 实现类.class

Zuul 微服务网关

背景介绍

在微服务多的情况下,相互之间的请求会出现复杂,而且出现跨域、认证复杂,服务之间相互调时重构问题。

上述问题,都可以借助微服务网关解决。微服务网关是介于客户端和服务器端之间的中间层,所有的外部请求都会先经过微服务网关。

Zuul组件的核心是一系列的过滤器,这些过滤器可以完成以下功能:身份认证和安全、动态路由、压力测试、负载分配

配置过程

建立网关微服务

创建子模块,pom.xml引入依赖zuul ,需要一个独立的模块

创建application.yml

tensquare-article: #文章(名字随便取)

path: /article/** #配置路由规则

serviceId: tensquare-article #指定Eureka注册中心中的服务id

配置端口,注入到注册中心,配置需要通过zuul的微服务信息

微服务名字+配置路由规则+在Eureka注册中心中的服务的id

编写启动类

@EnableZuulProxy

@EnableEurekaClient

@SpringBootApplication

请求路径:http://localhost:9012/base/label 网关的端口+配置路由规则

Zuul过滤器

创建WebFilter过滤器并继承ZuulFilter类

可重写里面方法:

filterType:控制过滤器的调用的位置,前、后、路由请求、发送错误

filterOrder:通过int值来定义过滤器的执行顺序 越小优先级越高。优先级为0,数字越大,优先级越低

shouldFilter:执行该过滤器,此处为true,说明需要过滤

Run:过滤器的具体逻辑

运用

1、后台管理

管理后台使用,所以需要在过滤器中对token进行验证。 验证不通过,禁止访问。

2、Token丢失的坑

经过网关的时候,header会丢失导致token丢失. 我们现在在过滤器中接收header,转发给微服务。

public Object run() throws ZuulException {

​ System.out.println("zuul过滤器...");

​ //向header中添加鉴权令牌

​ RequestContext requestContext = RequestContext.getCurrentContext();

​ //获取header

​ HttpServletRequest request = requestContext.getRequest();

​ String authorization = request.getHeader("Authorization");

​ if (authorization != null) {

​ requestContext.addZuulRequestHeader("Authorization", authorization);

​ }

​ return null;

}

SpringCloudConfig 集中配置组件(分布式配置)

背景

为了方便服务配置文件统一管理,实时更新。支持放在远程Git仓库中。在spring cloud config 组件中,在spring cloud config 组件中,分两个角色,一是config server,二是config client。

Config Server是一个可横向扩展、集中式的配置服务器,它用于集中管理应用程序各个环境下的配置,默认使用Git存储配置文件内容,也可以使用SVN存储,或者是本地文件存储。

Config Client是Config Server的客户端,用于操作存储在Config Server中的配置内容。微服务在启动时会请求Config Server获取配置文件的内容,请求到后再启动容器。

配置方式

配置中心微服务

1、在git上建立项目,上传配置文件

2、创建工程模块 配置中心微服务 tensquare_config

3、配置中心微服务 tensquare_config ,pom.xml引入依赖

4、复制对应项目git地址 ,放到编写配置文件application.yml

6、启动项目,添加注解@EnableConfigServer

配置客户端

1、客户端添加依赖spring-cloud-starter-config

2、bootstrap.yml 先于 application.yml 加载,bootstrap.yml用来程序引导时执行,记录配置中心的请求路径和统一管理的配置名称

SpringCloudBus 消息总线组件

概念

修改服务器中的配置并没有更新立刻到工程,只有重新启动程序才会读取配置。 那我们如果想在不重启微服务的情况下更新配置如何来实现呢? 我们使用SpringCloudBus来实现配置的自动更新。目前唯一的实现是使用AMQP代理作为传输。

内部依赖消息队列,rabbitmq

Activemq 用jms协议

Rabbitmq 用AMQP协议

配置方式

配置服务端(配置中心****tensquare_config)

在配置工程项目中添加:spring-cloud-bus、spring-cloud-stream-binder-rabbit

修改application.yml ,添加配置:

1、添加rabbitmq的ip,放在Spring的容器下

2、暴露触发消息总线的地址,bus-refresh

配置客户端(微服务****base)

改完后需要通知微服务

引入依赖:spring-cloud-bus、spring-cloud-stream-binder-rabbit、spring-boot-starter-actuator

在码云的配置文件:配置rabbitMQ的地址,和服务端相同

增加自定义配置 sms:ip

在Controller层中通过

@Value("${sms.ip}"):读取配置文件中ip属性

@RefreshScope:在Controller添加,此注解用于刷新配置

@RequestMapping(value = "/ip", method = RequestMethod.GET)读取配置文件信息

SpringData

概念

SpringData是 Spring 基于 ORM 框架、JPA 规范的基础上封装的一套JPA应用框架,解脱了DAO层的操作,基本上所有CRUD都可以依赖于它来实现,切换不同的ORM框架时提供了极大的方便,同时也使数据库层操作更加简单,方便解耦。

JPA规范本质上就是一种ORM规范,注意不是ORM框架——因为JPA并未提供ORM实现,它只是制订了一些规范,提供了一些编程的API接口,但具体实现则由服务厂商来提供实现,其中Hibernte是服务厂商之一。

基本使用

接口定义进行CRUD操作

创建一个Dao层接口,并实现JpaRepository和JpaSpecificationExecutor。

@Query

通过配置nativeQuery可设置查询方式:True的时候sql查询、False的是jpql[h13] 查询。实现多表关联查询

命名规则查询

查询方法以findBy开头,条件的属性用条件关键字连接,条件属性首字母需大写。

Specifications动态查询[h14]

有时候我们在查询某个实体的时候,给定的条件是不固定的,这时就需要动态构建相应的查询语句,相比JPQL,其优势是类型安全,更加的面向对象。

Spring Data 常见注解

创建映射关系

@Entity 声明实体类

@Table(name="cst_customer") 建立实体类和表的映射关系

@Id 声明当前私有属性为主键

@GeneratedValue(strategy=GenerationType.IDENTITY) 配置主键的生成策略

IDENTITY:主键由数据库自动生成(主要是自动增长型),其中mysql数据库可以支持自增长

SEQUENCE:根据底层数据库的序列来生成主键,条件是数据库支持序列。其中oracle数据库支持序列

AUTO:主键由程序控制(默认采用sequence的形式维护)

TABLE:使用一个特定的数据库表格来保存主键

@Column(name="cust_id") 指定和表中cust_id字段的映射关系

sql查询注解

@Query 注解的使用非常简单,只需在方法上面标注该注解,同时提供一个JPQL查询语句即可

@Modifying 来将该操作标识为修改[A15] 查询

关系映射

@OneToMany(targetEntity = LinkMan.class) 一的一方

@JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id") 关系映射

@ManyToOne(targetEntity = Customer.class) 多的一方

@ManyToMany(targetEntity = Role.class) 多对多关系

@JoinTable 多对多的中间表

事务管理

@Transactional 配置事务

@Rollback(false) 不自动回滚

和mybatis对比

相同点

都是java中orm框架,屏蔽了jdbc的api底层访问细节,可直接完成数据库的持久化操作。

不同点

Hibernate要比mybatis功能强大很多,因为hibernate自动生成sql语句

Hibernate无法直接写控制sql语句,对于特定、高效、复杂的的sql语言难以适应了

mybatis是在xml中配置sql语言,支持sql语句自动化

mybatis要比hibernate简单,是面向sql的,不用考虑对象见一些复杂的映射关系。


减少入侵?这是什么鬼呢?

涉及到一个设计方面的概念, 也就是耦合性的问题

拓展性强的设计的标准是”高内聚, 低耦合”, 侵入式强指的是耦合太强了, 判断的标准就是当引入了这个组件导致其他代码或者设计要做相应的更改以适应新组件. 这样的情况我们就认为这个新组件具有入侵性.

Java Transaction API (Java 事务API)

Java data object

\1. 注册驱动

\2. 获取连接

\3. 创建sql 执行平台

\4. 执行sql 语句

\5. 处理结果

\6. 释放资源

全ORM 框架 : Hibernate

半ORM 框架 : Mybatis

切入点:

通知:

切面:切入点和通知

增强:

自我理解:

有时间了解下这个玩意吧

SpringBoot****常用注解

核心组件的原理和配置方法

Token丢失的坑

最新的方式:好像修改yml的配置即可,可自上网查看

如何部署在本地局域网服务器

语言要熟悉下

准备几个常见的额类,和大概的流程

posted @ 2020-09-13 22:36  _Anke  阅读(817)  评论(0编辑  收藏  举报