面试常问

Posted by t298 on July 19, 2021

基础部分

包装类

java的基本数据类型不支持“对象”的特性,所以每种基本数据类型都有一个对应的包装类:

  • byte -> Byte
  • short -> Short
  • int -> Integer
  • long -> Long
  • float -> Float
  • double -> Double
  • boolean -> Boolean
  • char -> Character

基本数据类型和包装类的区别:

  1. 定义不同。包装类属于对象,基本数据类型不是
  2. 声明和使用方式不同。包装类使用new初始化,有些集合类的定义不能使用基本数据类型,例如 ArrayList
  3. 初始值不同。包装类默认值为null,基本数据类型则不同的类型不一样(具体见上表)
  4. 存储方式和位置不同,从而性能不同。基本数据类型存储在栈(stack)中,包装类则分成引用和实例,引用在栈(stack)中,具体实例在堆(heap)中。可以通过程序来验证速度的不同。

拆箱和装箱

基本类型和对应的包装类可以相互装换:

由基本类型向对应的包装类转换称为装箱,例如把 int 包装成 Integer 类的对象:

包装类向对应的基本类型转换称为拆箱,例如把 Integer 类的对象重新简化为 int。

== 和 equals() 的区别

== 对于基本类型和引用类型的作用效果是不同的:

  • 对于基本数据类型来说,== 比较的是值。
  • 对于引用数据类型来说,== 比较的是对象的内存地址。

因为 Java 只有值传递,所以,对于 == 来说,不管是比较基本数据类型,还是引用数据类型的变量,其本质比较的都是值,只是引用类型变量存的值是对象的地址。

equals() 不能用于判断基本数据类型的变量,只能用来判断两个对象是否相等。equals()方法存在于Object类中,而Object类是所有类的直接或间接父类,因此所有的类都有equals()方法。

equals() 方法存在两种使用情况:

  • 类没有重写 equals()方法 :通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象,使用的默认是 Objectequals()方法。
  • 类重写了 equals()方法 :一般我们都重写 equals()方法来比较两个对象中的属性是否相等;若它们的属性相等,则返回 true(即,认为这两个对象相等)。

Java对部分经常使用的数据采用缓存技术,在类第一次被加载时换创建缓存和数据。当使用等值对象时直接从缓存中获取,从而提高了程序执行性能。

(通常只对常用数据进行缓存,Integer类型有缓存 -128-127的对象。缓存上限可以通过配置jvm更改)

链表和数组的区别

数组占用的是一块连续的内存区,而链表在内存中,是分散的。数组可以通过下标访问任何一个元素,而链表只能通过节点指针一个个找。但是对于增加数据,数组中会把所有的元素统一向后移动,而链表只需要更改指针对应位置就可以了。

list和set的区别

在List中允许插入重复的元素,而在Set中不允许重复元素存在。

List是有序集合,会保留元素插入时的顺序,Set是无序集合。

List可以通过下标来访问,而Set不能

ArrayList和LinkedList的区别?

ArrayList是实现了基于动态数组的数据结构,而LinkedList是基于链表的数据结构

对于随机访问get和set,ArrayList要优于LinkedList,因为LinkedList要移动指针

对于新增和删除操作add和remove,LinkedList比较占优势,因为ArrayList要移动数据。

ArrayList使用在查询比较多的情况,而LinkedList用在插入删除比较多的情况。

new一个“String”会有几个对象?

1
2
3
String str1 = "abc";  // 在常量池中

String str2 = new String("abc"); // 在堆上

当直接赋值时,字符串“abc”会被存储在常量池中,只有1份,此时的赋值操作等于是创建0个或1个对象。如果常量池中已经存在了“abc”,那么不会再创建对象,直接将引用赋值给str1;如果常量池中没有“abc”,那么创建一个对象,并将引用赋值给str1。

那么,通过new String(“abc”);的形式又是如何呢?答案是1个或2个。

当JVM遇到上述代码时,会先检索常量池中是否存在“abc”,如果不存在“abc”这个字符串,则会先在常量池中创建这个一个字符串。然后再执行new操作,会在堆内存中创建一个存储“abc”的String对象,对象的引用赋值给str2。此过程创建了2个对象。

当然,如果检索常量池时发现已经存在了对应的字符串,那么只会在堆内创建一个新的String对象,此过程只创建了1个对象。

java为什么是单继承?

Java中类不能多继承类是为了安全。因为无论是抽象类还是非抽象类都包含非抽象的方法(非抽象类也可能没有),当类可以多继承类时,被继承的不同的父类可能会有同名同参的方法,如果子类也没有重写这个同名同参的方法,则在子类的实例调用这个方法的时候就会出现冲突。

说一下多态

由同一个对象调用,产生不同的行为,这种行为就叫多态。 Java实现多态有三个必要条件:继承、重写、向上转型。

说一下重写和重载

重载:一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。

重写:子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。

方法的重写(Overriding)和重载(Overloading)是java多态性的不同表现,重写是父类与子类之间多态性的一种表现,重载可以理解成多态的具体表现形式。

创建线程的几种方式

  1. 继承Thread类,重写run方法,调用线程对象的start()方法来启动该线程。
  2. 实现runnable接口,重写run方法,调用线程对象的start()方法来启动该线程
  3. 创建Callable接口的实现类,并实现call()方法,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。

采用实现Runnable、Callable接口的方式创建多线程时:

  • 优势:线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。

  • 劣势:编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。

使用继承Thread类的方式创建多线程时

  • 优势:编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。
  • 劣势是:线程类已经继承了Thread类,所以不能再继承其他父类。

Runnable和Callable的区别

  1. Callable规定(重写)的方法是call(),Runnable规定(重写)的方法是run()。
  2. Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。
  3. call方法可以抛出异常,run方法不可以。
  4. 运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。

线程池

Java语言虽然内置了多线程支持,启动一个新线程非常方便,但是,创建线程需要操作系统资源(线程资源,栈空间等),频繁创建和销毁大量线程需要消耗大量时间。

那么我们就可以把很多小任务让一组线程来执行,而不是一个任务对应一个新线程。这种能接收大量小任务并进行分发处理的就是线程池。

简单地说,线程池内部维护了若干个线程,没有任务的时候,这些线程都处于等待状态。如果有新任务,就分配一个空闲线程执行。如果所有线程都处于忙碌状态,新任务要么放入队列等待,要么增加一个新线程进行处理。

创建一个线程池总共有7种方法但是大体可以分为下面两种

  • 一类是通过 ThreadPoolExecutor 创建的线程池;
  • 另一个类是通过 Executors 创建的线程池。
  1. Executors.newFixedThreadPool:创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待;
  2. Executors.newCachedThreadPool:创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程;
  3. Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执行顺序;
  4. Executors.newScheduledThreadPool:创建一个可以执行延迟任务的线程池;
  5. Executors.newSingleThreadScheduledExecutor:创建一个单线程的可以执行延迟任务的线程池;
  6. Executors.newWorkStealingPool:创建一个抢占式执行的线程池(任务执行顺序不确定)【JDK 1.8 添加】。
  7. ThreadPoolExecutor:最原始的创建线程池的方式,它包含了 7 个参数可供设置,后面会详细讲。

线程池的生命周期

  1. create-创建,即创建线程池;
  2. running-运行,接收新任务同时处理队列中的任务;
  3. shutdown-关闭,在 running 状态时调用了 shutdown()方法,此时不在接收新任务,但会处理已入队中的任务;
  4. stop-关闭,在 running 或 shutdown 状态时调用了 shutdownNow()方法,此时不在接收新任务,也不在处理入队的任务,同时会中断正在处理中的任务;
  5. tidying-整理,所有任务都终止,workerCount 为 0,线程池进入了该状态;
  6. terminated-终止,调用了 terminated()钩子方法。 线程池中的线程数分为核心线程数和最大线程数,非核心线程在空闲时间超过了设置的时间就会被回收了,生命终止。

如果线程池中所有的线程都在忙碌,并且工作队列也满了(前提是工作队列是有界队列),那么此时提交任务,线程池就会拒绝接收。至于拒绝的策略,你可以通过 handler 这个参数来指定。

ThreadPoolExecutor 已经提供了以下 4 种策略。

  • CallerRunsPolicy:提交任务的线程自己去执行该任务。
  • AbortPolicy:默认的拒绝策略,会 throws RejectedExecutionException。
  • DiscardPolicy:直接丢弃任务,没有任何异常抛出。
  • DiscardOldestPolicy:丢弃最老的任务,其实就是把最早进入工作队列的任务丢弃,然后把新任务加入到工作队列。

什么是线程安全

当多个线程访问某个方法时,不管你通过怎样的调用方式或者说这些线程如何交替的执行,我们在主程序中不需要去做任何的同步,这个类的结果行为都是我们设想的正确行为,那么我们就可以说这个类时线程安全的。

说一下io流

根据数据的流向可以分为输入流和输出流,根据字节流和字符流。

  • 输入流 :把数据从其他设备上读取到内存中的流。
  • 输出流 :把数据从内存 中写出到其他设备上的流。

  • 字节流 :以字节为单位,读写数据的流。
  • 字符流 :以字符为单位,读写数据的流,只能操作纯字符数据。

说一下b+树

  • B+树有两种类型的节点:内部结点(也称索引结点)和叶子结点。内部节点就是非叶子节点,内部节点不存储数据,只存储索引,数据都存储在叶子节点。
  • 内部结点中的key都按照从小到大的顺序排列,对于内部结点中的一个key,左树中的所有key都小于它,右子树中的key都大于等于它。叶子结点中的记录也按照key的大小排列。
  • 每个叶子结点都存有相邻叶子结点的指针,叶子结点本身依关键字的大小自小而大顺序链接。
  • 父节点存有右孩子的第一个元素的索引。

HashMap 和 HashTable 区别

HashMap是map接口的实现类,不是线程安全的。它是将键映射到值的对象,其中键和值都是对象,并且不能包含重复键,但可以包含重复值。HashMap 允许 null key 和 null value,而 HashTable 不允许。

HashTable 是线程安全 Collection。HashMap 是 HashTable 的轻量级实现,他们都完成了Map 接口,主要区别在于 HashMap 允许 null key 和 null value,由于非线程安全,效率上可能高于 Hashtable。

HashMap的底层

HashMap 的底层实现是数组+链表+红黑树的形式的。

HashMap 有两个重要的参数:容量(capacity)和 负载因子(loadFactor)

  • 容量:是指 HashMap 中桶的数量,默认值是 16
  • 负载因子:判断 HashMap 是否需要扩容,默认值是 0.75

HashMap 存放的元素总数量 / 容量,当该值等于 0.75 的时候,HashMap 就需要进行扩容

16*0.75 = 12

当 HashMap 中元素个数超过 12 的时候,数组就需要进 行扩容,成倍扩容 32,也就是2倍扩容。

如果数组的长度小于64,则不进行红黑树的转换,而是继续进行数组扩容,如果数组的长度大于64,再将链表转为红黑树。

HashMap的存值过程:

  1. 根据key计算hash值。
  2. 在put的时候判断数组是否存在,如果不存在则用resize 方法创建默认长度为16的数组。
  3. 确定要存入的 Node 在数组中的位置,根据 hash 值与数组最大索引进行按位与运算得到索引位置。
  4. 判断该位置是否有元素,如果没有直接创建一个Node 存入。
  5. 如果有元素,判断 key 是否相同,如果相同则覆盖,并且将原来的值直接返回。
  6. 如果 key 不相同,在原 Node 基础上添加新的Node,判断该位置是链表还是红黑树。
  7. 如果是红黑树,将 Node 存入红黑树。
  8. 如果是链表,遍历链表,找到最后一位,将 Node 存入。
  9. 将 Node 存入链表之后,判断链表的结构是否要调整,判断链表长度是否超过 8,如果超过 8 需要将链表转为红黑树,这里还有一个条件,如果数组的容量小于64,不转换红黑树,而是进行数组扩容,当数组的容量大于 64 的时候,再将链表转为红黑树。
  10. 存完之后,再次判断数组是否进行扩容,根据负载因子来判断。

如何让HashMap变得线程安全?

如果需要满足线程安全,可以用 Collections的synchronizedMap方法使HashMap具有线程安全的能力,或者使用ConcurrentHashMap。

SynchronizedMap 一次锁住整张表来保证线程安全,所以每次只能有一个线程来访问map.

ConcurrentHashMap 使用分段锁来保证在多线程下的性能。

ConcurrentHashMap 默认将hash 表分为 16 个桶,是一次锁住一个桶。诸如 get,put,remove 等常用操作只锁当前需要用到的桶。这样,原来只能一个线程进入,现在却能同时有 16 个写线程执行,并发性能的提升是显而易见的。

另外 ConcurrentHashMap 使用了一种不同的迭代方式。在这种迭代方式中,当iterator 被创建后集合再发生改变就不再是抛出ConcurrentModificationException,取而代之的是在改变时 new 新的数据从而不影响原有的数据 ,iterator 完成后再将头指针替换为新的数据 ,这样 iterator线程可以使用原来老的数据,而写线程也可以并发的完成改诸如 get,put,remove 等常用操作只锁当前需要用到的桶。

浏览器输入百度后发生了什么?

1.域名解析

2.浏览器与服务器建立连接

3.web浏览器发送HTTP请求

4.web服务器处理请求并返回HTTP响应

5.浏览器接收HTTP响应

6.浏览器渲染页面

说一下http的请求头有哪些

Referer:浏览器向WEB 服务器表明自己是从哪个网页URL获得点击当前请求中的网址/URL

Host:客户端指定自己想访问的WEB服务器的域名/IP 地址和端口号

Accept-Language:浏览器申明自己接收的语言。语言跟字符集的区别:中文是语言,中文有多种字符集,比如big5,gb2312,gbk等等。

Proxy-Authenticate:代理服务器响应浏览器,要求其提供代理身份验证信息。

说一下http

HTTP协议定义Web客户端如何从Web服务器请求Web页面,以及服务器如何把Web页面传送给客户端。HTTP协议采用了请求/响应模型。客户端向服务器发送一个请求报文,请求报文包含请求的方法、URL、协议版本、请求头部和请求数据。服务器以一个状态行作为响应,响应的内容包括协议的版本、成功或者错误代码、服务器信息、响应头部和响应数据。

https

一般http中存在如下问题:

  • 请求信息明文传输,容易被窃听截取。
  • 数据的完整性未校验,容易被篡改
  • 没有验证对方身份,存在冒充危险

为了解决上述HTTP存在的问题,就用到了HTTPS。

HTTPS 协议(HyperText Transfer Protocol over Secure Socket Layer):一般理解为HTTP+SSL/TLS,通过 SSL证书来验证服务器的身份,并为浏览器和服务器之间的通信进行加密。

那么SSL又是什么?

SSL(Secure Socket Layer,安全套接字层):1994年为 Netscape 所研发,SSL 协议位于 TCP/IP 协议与各种应用层协议之间,为数据通讯提供安全支持。

TLS(Transport Layer Security,传输层安全):其前身是 SSL,它最初的几个版本(SSL 1.0、SSL 2.0、SSL 3.0)由网景公司开发,1999年从 3.1 开始被 IETF 标准化并改名,发展至今已经有 TLS 1.0、TLS 1.1、TLS 1.2 三个版本。SSL3.0和TLS1.0由于存在安全漏洞,已经很少被使用到。TLS 1.3 改动会比较大,目前还在草案阶段,目前使用最广泛的是TLS 1.1、TLS 1.2。

浏览器在使用HTTPS传输数据的流程是什么?

  1. 首先客户端通过URL访问服务器建立SSL连接。
  2. 服务端收到客户端请求后,会将网站支持的证书信息(证书中包含公钥)传送一份给客户端。
  3. 客户端的服务器开始协商SSL连接的安全等级,也就是信息加密的等级。
  4. 客户端的浏览器根据双方同意的安全等级,建立会话密钥,然后利用网站的公钥将会话密钥加密,并传送给网站。
  5. 服务器利用自己的私钥解密出会话密钥。
  6. 服务器利用会话密钥加密与客户端之间的通信。

socket

socket的含义就是两个应用程序通过一个双向的通信连接实现数据的交换,连接的一段就是一个socket,又称为套接字。实现一个socket连接通信至少需要两个套接字,一个运行在服务端(插孔),一个运行在客户端(插头)。

套接字用于描述IP地址和端口,是一个通信链的句柄。应用程序通过套接字向网络发出请求或应答网络请求。注意的是套接字既不是程序也不是协议,只是操作系统提供给通信层的一组抽象API接口。

post和get的区别

  • 都包含请求头请求行,post多了请求body。
  • get多用来查询,请求参数放在url中,不会对服务器上的内容产生作用。post用来提交,如把账号密码放入body中。
  • GET是直接添加到URL后面的,直接就可以在URL中看到内容,而POST是放在报文内部的,用户无法直接看到。
  • GET提交的数据长度是有限制的,因为URL长度有限制,具体的长度限制视浏览器而定。而POST没有。

什么是设计模式

解决编程里某类问题的通用模板,总结出来就是设计模式。在软件领域,“四人帮”首次系统化地提出了3大类(创建模式、行为模式、组合模式)共23种经典的可以解决常见软件设计问题的可复用设计方案,为可复用软件设计奠定了一定的理论基础。

  • 策略模式:

什么是servlet?说一下生命周期

任何一个应用程序,必然包括这三个步骤。其中接收请求和响应请求是共性功能,且没有差异性。访问淘宝和访问京东,都是接收,响应给浏览器的都是JSON数据。于是,大家就把接收和响应两个步骤抽取成Web服务器,但处理请求的逻辑是不同的。没关系,抽取出来做成Servlet,交给程序员自己编写。

当然,随着后期互联网发展,出现了三层架构,所以一些逻辑就从Servlet抽取出来,分担到Service和Dao。但是Servlet并不擅长往浏览器输出HTML页面,所以出现了JSP。

生命周期:Servlet 加载—>实例化—>服务—>销毁。

消息队列

解耦、异步、削峰

消息就是两个应用之间传递的数据,消息队列就是在消息传输过程中保存消息的容器。

Synchronized和Lock的区别

类别 Synchronized Lock
存在层次 java的关键字,在jvm层次上 是一个类
锁的获取 A线程获得锁,b线程等待。A线程阻塞,B线程一直等待 多种方式
锁的释放 1. 获取锁的线程执行完同步代码 2. 线程执行发生异常,jvm会释放锁 在finally中必须释放锁,不然容易造成死锁
锁的状态 无法判断 可以判断
锁的类型 可重入,不可中断,非公平 可重入,不可中断,非公平(两者皆可)
性能 少量同步 大量同步

用法区别

synchronized:在需要同步的对象中加入此控制,synchronized 可以加在方法上,也可以加在特定代码块中,括号中表示需要锁的对象。

lock:需要显示指定起始位置和终止位置。一般使用 ReentrantLock 类做为锁,多个线程中必须要使用一个 ReentrantLock 类做为对象才能保证锁的生效。且在加锁和解锁处需要通过 lock() 和 unlock() 显示指出。所以要在 finally 块中写 unlock() 以防死锁。

总结

  1. lock 是一个接口,而 synchronized 是 Java 的一个关键字,synchronized 是内置的语言实现。
  2. 异常是否释放锁:synchronized 在发生异常时候会自动释放占有的锁,因此不会出现死锁;而 lock 发生异常时候,不会主动释放占有的锁,必须手动 unlock 来释放锁,可能引起死锁的发生。(所以最好将同步代码块用 try catch包起来,finally 中写入 unlock,避免死锁的发生。)
  3. 是否响应中断lock 等待锁过程中可以用 interrupt 来中断等待,而 synchronized 只能等待锁的释放,不能响应中断。
  4. 是否知道获取锁:Lock 可以通过 trylock 来知道有没有获取锁,而 synchronized 不能。
  5. Lock 可以提高多个线程进行读操作的效率。(可以通过 ReadWriteLock 读写分离)
  6. 在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时 Lock 的性能要远远优于 synchronized。所以说,在具体使用时要根据适当情况选择。
  7. synchronized 使用 Object对象本身的 wait 、notify、notifyAll 调度机制,而 Lock 可以使用 Condition 进行线程之间的调度。

Mysql的锁有哪些?

可以分为全局锁、表级锁和行锁三类。

说一下事务

在执行SQL语句的时候,某些业务要求,一系列操作必须全部执行,而不能仅执行一部分。对于单条SQL语句,数据库系统自动将其作为一个事务执行,这种事务被称为隐式事务。要手动把多条SQL语句作为一个事务执行,使用BEGIN开启一个事务,使用COMMIT提交一个事务,这种事务被称为显式事务

说一下索引

在关系数据库中,如果有上万甚至上亿条记录,在查找记录的时候,想要获得非常快的速度,就需要使用索引。

索引是关系数据库中对某一列或多个列的值进行预排序的数据结构。通过使用索引,可以让数据库系统不必扫描整个表,而是直接定位到符合条件的记录,这样就大大加快了查询速度。

使用ADD INDEX idx_score (score)就创建了一个名称为idx_score,使用列score的索引。

索引失效

可以使用explain命令,查看mysql的执行计划。

  1. 不满足最左前缀原则

    MySQL中的索引可以以一定顺序引用多列,这种索引叫作联合索引。如User表的name和city加联合索引就是(name,city),而最左前缀原则指的是,如果查询的时候查询条件精确匹配索引的左边连续一列或几列,则此列就可以被用到

  2. 范围索引列没有放最后

  3. 使用了select*

  4. 索引列上有计算

  5. 索引列上使用了函数

  6. 字符类型没加引号

  7. 使用了 is null 或者 is not null 但是没注意字段是否允许为空

  8. like查询左边有%

  9. 使用 or 关键字时没有注意

必要时可以使用force index来强制查询sql走某个索引。

jdbc的操作步骤

加载数据库驱动类 打开数据库连接 执行sql语句 处理返回结果 关闭资源

sql优化

  1. 避免使用select *

    select *不会走覆盖索引,会出现大量回表操作

  2. 用union all代替union

    union获取的是排重后的数据,但是也会更消耗资源

  3. 小表驱动大表

    主要有inexists两个关键字

    sql语句中包含了in关键字,则它会优先执行in里面的子查询语句,然后再执行in外面的语句。如果in里面的数据量很少,作为条件查询速度更快。

    sql语句中包含了exists关键字,它优先执行exists左边的语句(即主查询语句)。然后把它作为条件,去跟右边的语句匹配。如果匹配上,则可以查询出数据。如果匹配不上,数据就被过滤掉了。

  4. 避免对数据库进行多次请求

  5. 多用limit

    获取用户的最新订单可以排序后使用limit 1

  6. in中的值不能太多

  7. 高效的分页

  8. 连接查询代替子查询

  9. join的表不宜过多

  10. 控制索引的数量

  11. 选择合理的字段类型

  12. 使用group by时先缩小范围在进行分组

  13. 索引失效

spring

什么是spring?

Spring 是一个开源框架,Spring是于2003 年兴起的一个轻量级的Java 开发框架,它是为了解决企业应用开发的复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许使用者选择使用哪一个组件,同时为 J2EE 应用程序开发提供集成的框架。Spring 使用基本的 JavaBean 来完成以前只可能由 EJB 完成的事情。

Spring的生命周期

实例化、属性赋值、初始化、销毁,

什么是ioc

IoC 就是依赖倒置原则的一种设计思路,就是将原本在程序中自己手动创建对象的控制权,交由 Spring 框架来管理。Spring 框架负责控制对象的生命周期对象之间的关系。IoC 在其他语言中也有应用,并非 Spirng 特有。ioc 容器实际上就是个 map(key,value),里面存的是各种对象(在xml里配置的bean节点   repository、service、controller、component)。

Spring IOC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。 IOC 容器负责创建对象,将对象连接在一起,配置这些对象,并从创建中处理这些对象的整个生命周期,直到它们被完全销毁。

IoC的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象。这一点是通过DI(Dependency Injection,依赖注入)来实现的。比如对象A需要操作数据库,以前我们总是要在A中自己编写代码来获得一个Connection对象,有了 spring我们就只需要告诉spring,A中需要一个Connection,至于这个Connection怎么构造,何时构造,A不需要知道。在系统运行时,spring会在适当的时候制造一个Connection,然后像打针一样,注射到A当中,这样就完成了对各个对象之间关系的控制。A需要依赖 Connection才能正常运行,而这个Connection是由spring注入到A中的,依赖注入的名字就这么来的。那么DI是如何实现的呢? Java 1.3之后一个重要特征是反射(reflection),它允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性,spring就是通过反射来实现注入的。

什么是aop

面向切面编程(AOP是Aspect Oriented Program的首字母缩写) ,我们知道,面向对象的特点是继承、多态和封装。而封装就要求将功能分散到不同的对象中去,这在软件设计中往往称为职责分配。实际上也就是说,让不同的类设计不同的方法。这样代码就分散到一个个的类中去了。这样做的好处是降低了代码的复杂程度,使类可重用。但是人们也发现,在分散代码的同时,也增加了代码的重复性。什么意思呢?比如说,我们在两个类中,可能都需要在每个方法中做日志。按面向对象的设计方法,我们就必须在两个类的方法中都加入日志的内容。也许他们是完全相同的,但就是因为面向对象的设计让类与类之间无法联系,而不能将这些重复的代码统一起来。 也许有人会说,那好办啊,我们可以将这段代码写在一个独立的类独立的方法里,然后再在这两个类中调用。但是,这样一来,这两个类跟我们上面提到的独立的类就有耦合了,它的改变会影响这两个类。那么,有没有什么办法,能让我们在需要的时候,随意地加入代码呢?这种在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。 一般而言,我们管切入到指定类指定方法的代码片段称为切面,而切入到哪些类、哪些方法则叫切入点。有了AOP,我们就可以把几个类共有的代码,抽取到一个切片中,等到需要时再切入对象中去,从而改变其原有的行为。

aop的代理方式

JDK动态代理和CGLIB动态代理。

JDK 动态代理只能对实现了接口的类生成代理,而不能针对类.主要涉及到 java.lang.reflect 包中的两个类:Proxy 和 InvocationHandler。 InvocationHandler 是一个接口,通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编制在一起。 Proxy 利用 InvocationHandler 动态创建 一个符合某一接口的实例,生成目标类的代理对象。

CGLIB 是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法(继承)CGLib 全称为 CodeGeneration Library,是一个强大的高性能,高质量的代码生成类库, 可以在运行期扩展Java 类与实现 Java 接口,CGLib 封装了 asm,可以再运行期动态生成新class。

CGLIB和 JDK 动态代理相比较:

JDK 创建代理有一个限制,就是只能为接口创建代理实例, 而对于没有通过接口定义业务方法的类,则可以通过CGLib 创建动态代理。(一个是只能在接口内,另一个在类内)

Spring 在选择用 JDK 还是 CGLiB 的依据:

  1. 当 Bean 实现接口时,Spring 就会用 JDK 的动态代理
  2. 当 Bean 没有实现接口时,Spring 使用 CGlib 是实现
  3. 可以强制使用 CGlib(在 spring 配置中加入<aop:aspectj-autoproxy proxy-target-class=“true”/>

spring怎么注入bean

通过set方法注入Bean

通过构造方法注入Bean

通过属性去注入Bean

java中的反射

JDBC利用反射将数据库的表字段映射到java对象的getter/setter方法。

@transactional 失效原因

  1. @Transactional 应用在非 public 修饰的方法上

    被aop增强的方法都应该是public的,而不能是private的

  2. @Transactional 注解属性 propagation 设置错误

    这种失效是由于配置错误,若是错误的配置以下三种 propagation,事务将不会发生回滚。

    TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。 TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。

    TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。

  3. @Transactional 注解属性 rollbackFor 设置错误

    rollbackFor 可以指定能够触发事务回滚的异常类型。Spring默认抛出了未检查unchecked异常(继承自 RuntimeException 的异常)或者 Error才回滚事务;其他异常不会触发回滚事务。如果在事务中抛出其他类型的异常,但却期望 Spring 能够回滚事务,就需要指定 rollbackFor属性。

  4. 同一个类中方法调用,导致@Transactional失效

    开发中避免不了会对同一个类里面的方法调用,比如有一个类Test,它的一个方法A,A再调用本类的方法B(不论方法B是用public还是private修饰),但方法A没有声明注解事务,而B方法有。则外部调用方法A之后,方法B的事务是不会起作用的。这也是经常犯错误的一个地方。

    那为啥会出现这种情况?其实这还是由于使用Spring AOP代理造成的,因为只有当事务方法被当前类以外的代码调用时,才会由Spring生成的代理对象来管理。

  5. 异常被你的 catch“吃了”导致@Transactional失效

    你手动的捕获这个异常并进行处理,程序认为当前事务应该正常commit

  6. 数据库引擎不支持事务

    这种情况出现的概率并不高,事务能否生效数据库引擎是否支持事务是关键。常用的MySQL数据库默认使用支持事务的innodb引擎。一旦数据库引擎切换成不支持事务的myisam,那事务就从根本上失效了。

Spring的缓存

  1. @Cacheable

    @Cacheble注解表示这个方法有了缓存的功能,方法的返回值会被缓存下来,下一次调用该方法前,会去检查是否缓存中已经有值,如果有就直接返回,不调用方法。如果没有,就调用方法,然后把结果缓存起来。这个注解一般用在查询方法上

  2. @CachePut

    加了@CachePut注解的方法,会把方法的返回值put到缓存里面缓存起来,供其它地方使用。它通常用在新增方法上

  3. @CacheEvict

    使用了CacheEvict注解的方法,会清空指定缓存。一般用在更新或者删除的方法上

  4. @Caching

    @Caching注解可以让我们在一个方法或者类上同时指定多个Spring Cache相关的注解。其拥有三个属性:cacheable、put和evict,分别用于指定@Cacheable、@CachePut和@CacheEvict。

redis的数据类型

支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。

  • 有序集合 zset(sorted-set)相关特性
    1. redis 有序集合 zset 与 无序 set 类型的一样,都是 string 类型的集合元素,且元素不允许重复。
    2. zset 的每个元素都会关联一个 double 类型的分数(score)。redis 就是通过分数来为集合中的成员进行从小到大的排序。
    3. 有序集合的成员是唯一的,但是对应的分数 (score)是可以重复的。

Spring Boot

常用注解

@Controller,@Service,@Repository,@Resource,@GetMapping,@PutMapping,@RequestParam,@PathVariable,@RequestBody,@NotEmpty,@NotBlank,@Null,

@Entity,@Table,@Transactional

Spring中@Component和@Bean的区别

  1. @Component注解表明一个类会作为组件类,并告知Spring要为这个类创建bean。

  2. @Bean注解告诉Spring这个方法将会返回一个对象,这个对象要注册为Spring应用上下文中的bean。通常方法体中包含了最终产生bean实例的逻辑。

两者的目的是一样的,都是注册bean到Spring容器中。区别:

@Component(@Controller、@Service、@Repository)通常是通过类路径扫描来自动侦测以及自动装配到Spring容器中。

而@Bean注解通常是我们在标有该注解的方法中定义产生这个bean的逻辑。

@Component 作用于类,@Bean作用于方法。

总结:

@Component和@Bean都是用来注册Bean并装配到Spring容器中,但是Bean比Component的自定义性更强。可以实现一些Component实现不了的自定义加载类。

Spring Cloud

Spring Cloud 是什么?

Spring Cloud是⼀系列框架的有序集合(Spring Cloud是⼀个规范),它并没有重复造轮子,而是将⽬前各家公司开发的⽐较成熟、经得起实际考验的服务框架组合起来。通过Spriddng Boot(自动装配)⻛格进⾏再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了⼀套简单易懂、易部署和易维护的分布式系统开发⼯具包。

Spring Cloud 规范及实现意图要解决的问题其实就是微服务架构实施过程中存在的⼀些问题,⽐如微服务架构中的服务注册发现问题、⽹络问题(⽐如熔断场景)、 统⼀认证安全授权问题、负载均衡问题、链路追踪等问题。

docker

ps kill pull

linux查看日志

命令格式: tail[必要参数][选择参数][文件]

-f 循环读取

-q 不显示处理信息

-v 显示详细的处理信息

-c<数目> 显示的字节数

-n<行数> 显示行数

q, –quiet, –silent 从不输出给出文件名的首部

-s, –sleep-interval=S 与-f合用,表示在每次反复的间隔休眠S秒

常用命令:pwd,rename,rm,touch,mkdir,cat

话术

自我介绍

面试官,你好。我叫张浩天,今年22岁,大学学的是软件技术专业,在上家公司担任岗位的是java开发,公司的主要业务主要是针对政府的需求进行对辖区的各种数据进行一个可视化的管理,我们的系统都是以前后端分离的模块化进行开发,虽然说是前后端分离,但其实都是我自己来的,公司的前端开发人员更主要的是负责的是一些样式的美化以及专业性更强的业务。

你自己比较擅长的是那方面呢

我比较擅长学习和总结。互联网这个行业,你要是没有一定的学习能力,你肯定是要被淘汰的。而我是热爱这个行业,一直坚持着博客的更新,不管是新的知识还是工作中遇到的难题,我都会去学习并且总结经验,我也会一直保持我的学习热情。

23. 你自己比较擅长的是那方面呢

24. 说一下你学习过程中遇到的问题以及你是怎么解决的