1.java基础
内存管理
堆:
存储的是new出来的对象(包括实例变量、数组的元素)
垃圾:没有任何引用所指向的对象
垃圾回收器(GC)不定时到堆中清扫垃圾,回收过程是透明的(看不到的),并不一定一发现垃圾就立刻回收,通过调用System.gc()建议虚拟机尽快调度GC来回收
实例变量的生命周期:
在创建时对象时存储在堆中,对象被回收时一并被回收
内存泄漏:不再使用的对象还没有被及时的回收,严重的泄漏会导致系统的崩溃
建议:不再使用的对象应及时将引用设置为null
栈:
存储正在调用的方法中的局部变量(包括方法的参数)
调用方法时会在栈中为该方法分配一块对应的栈帧,栈帧中存储局部变量(包括方法的参数),方法调用结束时,栈帧被自动清除,局部变量一并被清除
局部变量的生命周期:
调用方法时存储在栈中,方法调用结束时与栈帧一并被清除
方法区:
- 存储**.class字节码文件(包括静态变量、所有方法)**
- 方法只有一份,通过this来区分具体的调用对象
String
- String s = new String("hello");?
String s = new String("hello");
问:如上语句创建了几个对象?
答:2个
第一个:字面量"hello"
----java会创建一个String对象表示字面量"hello",并将其存入常量池
第二个:new String()
----new String()时会再创建一个字符串对象,并引用hello字符串的内容
StringBuilder和StringBuffer的区别:
- StringBuffer:线程安全的,同步处理的,性能稍慢
- StringBuilder:非线程安全的,并发处理的,性能稍快-----一般都是用StringBuilder的
数组长度是length属性,字符串长度是length方法
日期
IO
**java.io.InputStream:**所有字节输入流的超类,其中定义了读取数据的方法.因此将来不管读取的是什么设备(连接该设备的流)都有这些读取的方法,因此我们可以用相同的方法读取不同设备中的数据
**java.io.OutputStream:**所有字节输出流的超类,其中定义了写出数据的方法.
节点流:也称为低级流.节点流的另一端是明确的,是实际读写数据的流,读写一定是建立在节点流基础上进行的.
处理流:也称为高级流.处理流不能独立存在,必须连接在其他流上,目的是当数据流经当前流时对数据进行加工处理来简化我们对数据的该操作.
java.io.FileInputStream和java.io.FileOutputStream 文件流 节点流
作用是真实连接我们程序和文件之间的"管道"。其中文件输入流用于从文件中读取字节。而文件输出流则
用于向文件中写入字节。
OutputStream(所有字节输出流的超类)中定义了写出字节的方法: write(int d) 写入一个字节 write(byte[] data) 一次性将给定的字节数组所有字节写入到文件中 write(byte[] data,int offset,int len) 一次性将给定的字节数组从下标offset处开始的连续len个字节写入文件写文本数据
String提供方法: byte[] getBytes(String charsetName) 将当前字符串转换为一组字节
文件输出流-追加模式
重载的构造方法可以将文件输出流创建为追加模式
- FileOutputStream(String path,boolean append)
- FileOutputStream(File file,boolean append)
当第二个参数传入true时,文件流为追加模式,即:指定的文件若存在,则原有数据保留,新写入的数据会被顺序的追加到文件中
缓冲流
java.io.BufferedOutputStream和BufferedInputStream.
缓冲流是一对高级流,作用是提高读写数据的效率.
缓冲流内部有一个字节数组,默认长度是8K.缓冲流读写数据时一定是将数据的读写方式转换为块读写来保证读写效率.
缓冲输出流写出数据时的缓冲区问题
通过缓冲流写出的数据会被临时存入缓冲流内部的字节数组,直到数组存满数据才会真实写出一次
/*
缓冲流的flush方法用于强制将缓冲区中已经缓存的数据一次性写出。
注:该方法实际上实在字节输出流的超类OutputStream上定义的,并非只有缓冲
输出流有这个方法。但是实际上只有缓冲输出流的该方法有实际意义,其他的流实现
该方法的目的仅仅是为了在流连接过程中传递flush动作给缓冲输出流。
*/
bos.flush();//冲
对象流
java.io.ObjectOutputStream和ObjectInputSteam
对象流是一对高级流,在流连接中的作用是进行对象的序列化与反序列化。
对象序列化:将一个java对象按照其结构转换为一组字节的过程
对象反序列化:将一组字节还原为java对象(前提是这组字节是一个对象序列化得到的字节)
transient关键字可以修饰属性,用于在进行对象序列化时忽略不必要的属性,达到对象瘦身的目的
对象输出流在进行序列化对象时,要求该对象所属的类必须实现接口:java.io.Serializable接口
字符流
- java将流按照读写单位划分为字节流与字符流.
- java.io.InputStream和OutputStream是所有字节流的超类
- 而java.io.Reader和Writer则是所有字符流的超类,它们和字节流的超类是平级关系.
- Reader和Writer是两个抽象类,里面规定了所有字符流都必须具备的读写字符的相关方法.
- 字符流最小读写单位为字符(char),但是底层实际还是读写字节,只是字符与字节的转换工作由字符流完成.
转换流
java.io.InputStreamReader和OutputStreamWriter
它们是字符流非常常用的一对实现类同时也是一对高级流,实际开发中我们不直接操作它们,但是它们在流连接中是非常重要的一环.
缓冲字符流
PrintWriter的自动行刷新功能
如果实例化PW时第一个参数传入的是一个流,则此时可以再传入一个boolean型的参数,此值为true时就打开了自动行刷新功能。 即: 每当我们用PW的println方法写出一行字符串后会自动flush.
缓冲字符输入流:java.io.BufferedReader
是一个高级的字符流,特点是块读文本数据,并且可以按行读取字符串。
反射
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;
对于任意一个对象,都能够调用它的任意一个方法和属性;
这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
要想解剖一个类,必须先要获取到该类的字节码文件对象。
而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象
Class对象的由来是将class文件读入内存,并为之创建一个Class对象。
Class 类的实例表示正在运行的 Java 应用程序中的类和接口。也就是jvm中有N多的实例每个类都有该Class对象。(包括基本数据类型) Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的defineClass 方法自动构造的。也就是这不需要我们自己去处理创建,JVM已经帮我们创建好了
线程
重点:多线程并发安全问题
什么是多线程并发安全问题:
当多个线程并发操作同一临界资源,由于线程切换时机不确定,导致执行顺序出现混乱。
解决办法:
将并发操作改为同步操作就可有效的解决多线程并发安全问题
同步与异步的概念:同步和异步都是说的多线程的执行方式。
多线程各自执行各自的就是异步执行,
而多线程执行出现了先后顺序进行就是同步执行
synchronized的两种用法
1.直接在方法上声明,此时该方法称为同步方法,同步方法同时只能被一个线程执行
2**.同步块**,推荐使用。同步块可以更准确的控制需要同步执行的代码片段。
有效的缩小同步范围可以在保证并发安全的前提下提高并发效率
线程:一个顺序的单一的程序执行流程就是一个线程。代码一句一句的有先后顺序的执行。
多线程:多个单一顺序执行的流程并发运行。造成"感官上同时运行"的效果。
并发: 多个线程实际运行是走走停停的。线程调度程序会将CPU运行时间划分为若干个时间片段并
尽可能均匀的分配给每个线程,拿到时间片的线程被CPU执行这段时间。当超时后线程调度
程序会再次分配一个时间片段给一个线程使得CPU执行它。如此反复。由于CPU执行时间在
纳秒级别,我们感觉不到切换线程运行的过程。所以微观上走走停停,宏观上感觉一起运行
的现象成为并发运行!
用途:
- 当出现多个代码片段执行顺序有冲突时,希望它们各干各的时就应当放在不同线程上"同时"运行
- 一个线程可以运行,但是多个线程可以更快时,可以使用多线程运行
线程的生命周期图
创建线程有两种方式
方式一:继承Thread并重写run方法
定义一个线程类,重写run方法,在其中定义线程要执行的任务(希望和其他线程并发执行的任务)。
注:启动该线程要调用该线程的start方法,而不是run方法!!!
优点:
在于结构简单,便于匿名内部类形式创建。
缺点:
- 1:直接继承线程,会导致不能在继承其他类去复用方法,这在实际开发中是非常不便的。
- 2:定义线程的同时重写了run方法,会导致线程与线程任务绑定在了一起,不利于线程的重用。
方式二:实现Runnable接口单独定义线程任务
线程优先级
线程start后会纳入到线程调度器中统一管理,线程只能被动的被分配时间片并发运行,而无法主动索取时间片.线程调度器尽可能均匀的将时间片分配给每个线程.
线程有10个优先级,使用整数1-10表示
- 1为最小优先级,10为最高优先级.5为默认值
- 调整线程的优先级可以最大程度的干涉获取时间片的几率.优先级越高的线程获取时间片的次数越多,反之则越少.
sleep阻塞
线程提供了一个静态方法:
- static void sleep(long ms)
- 使运行该方法的线程进入阻塞状态指定的毫秒,超时后线程会自动回到RUNNABLE状态等待再次获取时间片并发运行.
当一个线程调用sleep方法处于睡眠阻塞的过程中,该线程的interrupt()方法被调用时,sleep方法会抛出该异常从而打断睡眠阻塞.
守护线程
守护线程也称为:后台线程
- 守护线程是通过普通线程调用setDaemon(boolean on)方法设置而来的,因此创建上与普通线程无异.
- 守护线程的结束时机上有一点与普通线程不同,即:进程的结束.
- 进程结束:当一个进程中的所有普通线程都结束时,进程就会结束,此时会杀掉所有正在运行的守护线程.
通常当我们不关心某个线程的任务什么时候停下来,它可以一直运行,但是程序主要的工作都结束时它应当跟着结束时,这样的任务就适合放在守护线程上执行.比如GC就是在守护线程上运行的.
互斥锁
当多个线程执行不同的代码片段,但是这些代码片段之间不能同时运行时就要设置为互斥的.
使用synchronized锁定多个代码片段,并且指定的同步监视器是同一个时,这些代码片段之间就是互斥的.
网络编程
java.net.Socket
Socket(套接字)封装了TCP协议的通讯细节,是的我们使用它可以与服务端建立网络链接,并通过 它获取两个流(一个输入一个输出),然后使用这两个流的读写操作完成与服务端的数据交互
java.net.ServerSocket
ServerSocket运行在服务端,作用有两个:
1:向系统申请服务端口,客户端的Socket就是通过这个端口与服务端建立连接的。
2:监听服务端口,一旦一个客户端通过该端口建立连接则会自动创建一个Socket,并通过该Socket与客户端进行数据交互。
如果我们把Socket比喻为电话,那么ServerSocket相当于是某客服中心的总机。
* 运行在服务端的ServerSocket主要完成两个工作:
* 1:向服务端操作系统申请服务端口,客户端就是通过这个端口与ServerSocket建立链接
serverSocket = new ServerSocket(8088);
* 2:监听端口,一旦一个客户端建立链接,会立即返回一个Socket。通过这个Socket
* 就可以和该客户端交互了
Socket socket = serverSocket.accept();
这个方法是一个阻塞方法,调用后方法"卡住",此时开始等待客户端
的链接,直到一个客户端链接,此时该方法会立即返回一个Socket实例
客户端与服务端完成第一次通讯(发送一行字符串)
Socket提供了两个重要的方法:
OutputStream getOutputStream()
该方法会获取一个字节输出流,通过这个输出流写出的字节数据会通过网络发送给对方。
InputStream getInputStream()
通过该方法获取的字节输入流读取的是远端计算机发送过来的数据。
集合
什么是集合
集合与数组一样,可以保存一组元素,并且提供了操作元素的相关方法,使用更方便.
java集合框架中相关接口
java.util.Collection接口:
java.util.Collection是所有集合的顶级接口.Collection下面有多种实现类,因此我们有更多的数据结构可供选择.
Collection下面有两个常见的子接口:
- java.util.List:线性表.是可重复集合,并且有序.
- java.util.Set:不可重复的集合,大部分实现类是无序的.
这里可重复指的是集合中的元素是否可以重复,而判定重复元素的标准是依靠元素自身equals比较
的结果.为true就认为是重复元素.
集合存放的是元素的引用
集合只能存放引用类型元素,并且存放的是元素的引用
集合的遍历
Collection提供了统一的遍历集合方式:迭代器模式
Iterator iterator()
该方法会获取一个用于遍历当前集合元素的迭代器.
java.util.Iterator接口
迭代器接口,定义了迭代器遍历集合的相关操作.
不同的集合都实现了一个用于遍历自身元素的迭代器实现类,我们无需记住它们的名字,用多态的角度把他们看做为Iterator即可.
迭代器遍历过程中不得通过集合的方法增删元素
迭代器的remove方法可以将通过next方法获取的元素从集合中删除。
List集
java.util.List接口,继承自Collection.
List集合是可重复集,并且有序,提供了一套可以通过下标操作元素的方法
常用实现类:
- java.util.ArrayList:内部使用数组实现,查询性能更好.
- java.util.LinkedList:内部使用链表实现,首尾增删元素性能更好
异常
- java中所有错误的超类为:Throwable。其下有两个子类:Error和Exception
- Error的子类描述的都是系统错误,比如虚拟机内存溢出等。
- Exception的子类描述的都是程序错误,比如空指针,下表越界等。
- 通常我们程序中处理的异常都是Exception。
异常处理机制中的finally
finally块定义在异常处理机制中的最后一块。它可以直接跟在try之后,或者最后一个catch之后。
finally可以保证只要程序执行到了try语句块中,无论try语句块中的代码是否出现异常,最终finally都必定执行。
finally通常用来做释放资源这类操作。
自动关闭特性
JDK7之后,java提供了一个新的特性:自动关闭。旨在IO操作中可以更简洁的使用异常处理机制完成最后的close操作。
throw关键字
throw用来对外主动抛出一个异常,通常下面两种情况我们主动对外抛出异常:
- 1:当程序遇到一个满足语法,但是不满足业务要求时,可以抛出一个异常告知调用者。
- 2:程序执行遇到一个异常,但是该异常不应当在当前代码片段被解决时可以抛出给调用者。
2.框架技术
1. Spring框架
1.1. Spring框架的作用
Spring框架主要解决了创建对象、管理对象的问题。
1.2. Spring框架的依赖项
当项目中需要使用Spring框架时,需要添加的依赖项是:spring-context
1.3. Spring框架创建对象的做法
Spring框架创建对象有2种做法:
在任何配置类(添加了
@Configuration)中,自定义方法,返回某种类型的(你需要的)对象,并在方法上添加@Bean注解- 此方式创建出来的对象,在Spring容器中的名称就是方法名称
- 此方法应该是
public - 此方法的返回值类型,是你期望Spring框架管理的数据的类型
- 此方法的参数列表,应该为空
- 此方法的方法体,应该是自行设计的,没有要求
配置组件扫描,并在组件类上添加组件注解
此方式创建出来的对象,在Spring容器中的名称默认是将类名首字母改为小写
- 例如:类名是
AdminController,则对象在Spring容器中的名称为adminController - 此规则仅适用于类名的第1个字母大写,且第2个字母小写的情况,如果不符合此规则,则对象在Spring容器中的名称就是类名
- 可以通过组件注解的参数来指定名称
- 例如:类名是
在任何配置类上,添加
@ComponentScan,当加载此配置类时,就会激活组件扫描可以配置
@ComponentScan的参数,此参数应该是需要被扫描的根包(会扫描所配置的包,及其所有子孙包),且此注解参数的值是数组类型的例如:
@ComponentScan("cn.tedu")
如果没有配置
@ComponentScan的参数中的根包,则组件扫描的范围就是当前类的包及其子孙包需要在各组件类上添加组件注解,才会被创建对象,常见的组件注解有:
@Component:通用注解@Controller:控制器类的注解@RestController:仅添加Spring MVC框架后可使用@ControllerAdvice:仅添加Spring MVC框架后可使用@RestControllerAdvice:仅添加Spring MVC框架后可使用
@Service:Service这种业务类的注解@Repository:处理数据源中的数据读写的类的注解
以上4种组件注解在Spring框架作用范围之内是完全等效的
在Spring框架中,还有
@Configuration注解,也是组件注解的一种,但是Spring对此注解的处理更加特殊(Spring框架对配置类使用了代理模式)
对于这2种创建对象的做法,通常:
- 如果是自定义的类,优先使用组件扫描的做法来创建对象
- 如果不是自定义的类,无法使用组件扫描的做法,只能在配置类中通过
@Bean方法来创建对象
当Spring成功的创建了对象后,会将对象保存在Spring应用程序上下文(ApplicationContext)中,后续,当需要这些对象时,可以从Spring应用程序上下文中获取!
由于Spring应用程序上下文中持有大量对象的引用,所以,Spring应用程序上下文也通常被称之为“Spring容器”。
1.4. Spring框架管理的对象的作用域
默认情况下,Spring框架管理的对象都是单例的!
单例:在任何时间点,某个类的对象最多只有1个!
可以在类上添加@Scope("prototype")使得被Spring管理的对象是“非单例的”。
提示:
@Scope注解还可以配置为@Scope("singleton"),此singleton表示“单例的”,是默认的。
默认情况下,Spring框架管理的单例的对象是“预加载的”,相当于设计模式中的单例模式的“饿汉式单例模式”。
提示:可以在类上添加
@Lazy注解,使得此对象是“懒加载的”,相当于“懒汉式单例模式”,只会在第1次需要获取对象时才把对象创建出来!
注意:Spring框架并不是使用了设计模式中的“单例模式”,只是从对象的管理方面,对象的作用域表现与单例模式的极为相似而已。
1.5. 自动装配
自动装配:当某个量需要值时,Spring框架会自动的从容器中找到合适的值,为这个量赋值。
自动装配的典型表现是在属性上添加@Autowired注解,例如:
@RestController
public class AlbumController {
// ↓↓↓↓↓ 自动装配的典型表现 ↓↓↓↓↓
@Autowired
private IAlbumService albumService;
}
或者:
@RestController
public class AlbumController {
private IAlbumService albumService;
// ↓↓↓↓↓↓↓ 自动装配 ↓↓↓↓↓↓↓
public AlbumController(IAlbumService albumService) {
this.albumService = albumService;
}
}
提示:Spring创建对象时需要调用构造方法,如果类中仅有1个构造方法(如上所示),Spring会自动调用,如果这唯一的构造方法是有参数的,Spring也会自动从容器中找到合适的对象来调用此构造方法,如果容器没有合适的对象,则无法创建!如果类中有多个构造方法,默认情况下,Spring会自动调用添加了
@Autowired注解的构造方法,如果多个构造方法都没有添加此注解,则Spring会自动调用无参数的构造方法,如果也不存在无参数构造方法,则会报错!
或者:
@RestController
public class AlbumController {
private IAlbumService albumService;
@Autowired
// ↓↓↓↓↓↓↓ 自动装配 ↓↓↓↓↓↓↓
public void setAlbumService(IAlbumService albumService) {
this.albumService = albumService;
}
}
另外,在配置类中的@Bean方法也可以在需要的时候自行添加参数,如果Spring容器中有合适的值,Spring也会从容器中找到值来调用方法。
关于“合适的值”,Spring对于@Autowired的处理机制是:查找在Spring容器中匹配类型的对象的数量:
- 1个:直接装配,且装配成功
- 0个:取决于
@Autowired注解的required属性true(默认值):装配失败,在加载时即报错false:放弃装配,则此量的值为null,在接下来的使用过程中可能导致NPE(NullPointerException)
- 超过1个:取决于是否存在某个Spring Bean(Spring容器中的对象)的名称与当前量的名称匹配
- 存在:成功装配
- 不存在:装配失败,在加载时即报错
关于通过名称匹配:
- 默认情况下,要求量(全局变量、方法参数等)的名称与对象在Spring容器中名称完全相同,视为匹配
- 可以在量(全局变量、方法参数等)的声明之前添加
@Qualifier注解,通过此注解参数来指定名称,以匹配某个Spring容器的对象@Qualifier注解是用于配合自动装配机制的,单独使用没有意义
其实,还可以使用@Resource注解实现自动装配,但不推荐!
Spring框架对@Resource注解的自动装配机制是:先根据名称再根据类型来实现自动装配。
@Resource是javax包中的注解,根据此注解的声明,此注解只能添加在类上、属性上、方法上,不可以添加在构造方法上、方法的参数上。
1.6. 关于IoC与DI
IoC:Inversion of Control,控制反转,即将对象的创建、管理的权力(控制能力)交给框架
DI:Dependency Injection,依赖注入,即为依赖项注入值
Spring框架通过 DI 实现/完善 了IoC。
1.7. 关于Spring AOP
AOP:面向切面的编程。
注意:AOP并不是Spring框架特有的技术,只是Spring框架很好的支持了AOP。
AOP主要用于:日志、安全、事务管理、自定义的业务规则
在项目中,数据的处理流程大致是:
添加品牌:请求 ------> Controller ------> Service ------> Mapper ------> DB
添加类别:请求 ------> Controller ------> Service ------> Mapper ------> DB
删除品牌:请求 ------> Controller ------> Service ------> Mapper ------> DB
其实,各请求提交到服务器端后,数据的处理流程是相对固定的!
2. Spring MVC框架
1.1. Spring MVC框架的作用
Spring MVC框架主要解决了接收请求、响应结果的相关问题。
1.2. Spring MVC框架的依赖项
当项目中需要使用Spring MVC框架时,需要添加的依赖项是:spring-webmvc
1.3. 配置请求路径
通过@RequestMapping系列注解可以配置请求路径
1.4. 限制请求方式
通过@RequestMapping注解的method属性限制请求方式,例如:
@RequestMapping(value = "/login", method = RequestMethod.POST)
或者,直接使用衍生的注解,例如@GetMapping、@PostMapping。
1.5. 接收请求参数
可以在处理请求的方法的参数列表中自由设计请求参数,可以:
将各请求参数逐一列举出来,表现为方法的多个参数,例如:
public JsonResult<Void> login(String username, String password) { ... }将各请求参数封装到自定义的类型中,使用自定义的类型作为方法的参数
关于请求参数的注解:
@RequestParam:此注解可以用于:修改请求参数的名称(没有太多实用价值),强制要求必须提交此参数(可以通过Validation框架的@NotNull实现同样效果),设置请求参数的默认值(适用于允许客户端不提交此请求参数时)@PathVariable:当URL中设计了占位符参数时,必须在对应的方法参数上添加此注解,以表示此参数的值来自URL中的占位符位置的值,如果占位符中的名称与方法参数名称不匹配,可以通过此注解的参数来配置@RequestBody:当方法的参数添加了此注解时,客户端提交的请求参数必须是对象格式的,当方法的参数没有添加此注解时,客户端提交的请求参数必须是FormData格式的
1.6. 响应结果
默认情况下,处理请求的方法的返回值将表示“处理响应结果的视图组件的名称,及相关的数据”,在Spring MVC中,有一种内置的返回值类型是ModelAndView,不是前后端分离的做法!
在处理请求的方法上,可以添加@ResponseBody注解,当添加此注解后,处理请求的方法的返回值将表示“响应的数据”,不再由服务器端决定视图组件,这种做法也叫做“响应正文”!这是前后端分离的做法!
@ResponseBody注解可以添加在处理请求的方法上,将作用于当前方法,也可以添加在控制器类上,将作用于控制器类中所有处理请求的方法!
控制器类需要添加@Controller注解,才是控制器类,或者,也可以改为添加@RestController,此注解是由@Controller和@ResponseBody组合而成的!所以,添加@RestController后,当前控制器类中所有处理请求的方法都是“响应正文”的!
当控制器处理请求需要响应正文时,Spring MVC框架会根据处理请求的方法的返回值类型,来决定使用某个MessageConverter(消息转换器),来将返回值转换为响应到客户端的数据,不同的返回值类型对应不同的消息转换器,例如,返回值类型是String时,Spring MVC框架将使用StringHttpMessageConverter,如果某个返回值类型是Spring MVC框架没有对应的消息转换器的,且当前项目添加了jackson-databind依赖项后,会自动使用此依赖项中的消息转换器,而jackson-databind中的消息转换器会将方法返回的结果转换为JSON格式的字符串!另外,如果当前项目是使用XML来配置Spring MVC框架的,还需要添加<annotation-driven/>标签以开启“注解驱动”,如果是使用注解进行配置的,则需要在配置类上添加@EnableWebMvc注解,如果是在Spring Boot中应用Spring MVC,不需要此配置!
1.7. 处理异常
添加了@ExceptionHandler注解的方法,就是处理异常的方法。
处理异常的方法到底处理哪种异常,由@ExceptionHandler注解参数或方法的参数中的异常类型来决定!如果@ExceptionHandler注解没有配置参数,由方法的参数中的异常类型决定,如果@ExceptionHandler注解配置了参数,由以注解参数中配置的类型为准!
处理异常的方法可以声明在控制器类,将只作用于当前控制器类中的方法抛出的异常!
通常,建议将处理异常的方法声明在专门的类中,并在此类上添加@ControllerAdvice注解,当添加此注解后,此类中特定的方法(例如处理异常的方法)将作用于每次处理请求的过程中!如果处理异常后的将“响应正文”,也可以在处理异常的方法上添加@ResponseBody注解,或在当前类上添加@ResponseBody,或使用@RestControllerAdvice取代@ControllerAdvice和@ResponseBody。
1.8. Spring MVC框架的核心执行流程
强烈建议抽时间看扩展视频教程!
3. Spring Boot框架
1.1. Spring Boot框架的作用
Spring Boot框架主要解决了统一管理依赖项与简化配置相关的问题。
统一管理依赖项:在开发实践中,项目中需要使用到的依赖项可能较多,在没有Spring Boot时,可能需要自行添加若干个依赖项,并且,需要自行保证这些依赖项的版本不会发生冲突,或保证不存在不兼容的问题,Spring Boot提供了starter依赖项(依赖项的名称中有starter字样),这种依赖项包含了使用某个框架时可能涉及的一系列依赖项,并处理好了各依赖项的版本相关问题,保证各依赖项的版本兼容、不冲突!例如spring-boot-starter-web就包含了Spring MVC的依赖项(spring-webmvc)、jackson-databind、tomcat等。
简化配置:Spring Boot默认完成了各项目中最可预测的配置,它是一种“约定大于配置”的思想,当然,这些配置也都是可以修改的,例如通过在application.properties中添加指定属性名的配置。
1.2. Spring Boot框架的依赖项
当项目中需要使用Spring Boot框架时,需要添加的基础依赖项是:spring-boot-starter。
此基础依赖项被其它各带有starter字样的依赖项所包含,所以,通常不必显式的添加此基础依赖项。
1.3. 典型的应用技巧
关于日志
在spring-boot-starter中已经包含spring-boot-starter-logging,所以,在添加任何starter依赖后,在项目中均可使用日志。
关于配置文件
Spring Boot项目默认在src/main/resources下已经准备好了配置文件,且Spring Boot默认识别的配置文件的文件名是application,其扩展名可以是.properties或.yml。
此配置文件会被Spring Boot自动读取,框架指定属性名称的各配置值会被自动应用,自定义的配置需要通过Environment对象或通过@Value注解自行读取并应用,所有不是指定属性名称的配置都是自定义配置。
其实,读取.properties配置文件是Spring框架做到的功能,而.yml配置是Spring框架并不支持的,但是,在Spring Boot中,添加了解析.yml文件的依赖项,所以,在Spring Boot中既可以使用.properties也可以使用.yml。
另外,Spring Boot还很好的支持了Profile配置(也是Spring框架做到的功能)。
4.Mybatis
Mybatis的占位符中使用的名称
在较低版本的框架中,为了保证能够使用#{id}、#{password}这种名称的占位符,需要在抽象方法的各参数前添加@Param注解,以配置参数的名称,例如:
以上规则也适用于动态SQL中的<foreach>标签的collection属性,关于此属性的值,如果抽象方法的参数只有1个,当参数类型是数组或可变参数时,取值为array,如果参数类型是List,取值为list,如果抽象方法的参数超过1个,使用@Param配置参数名,则collection属性的值就是@Param注解配置的参数名!
Mybatis的占位符的格式
在Mybatis中,配置SQL语句时,参数可以使用#{}格式的占位符表示,也可以使用${}格式的占位符来表示!
本质上,使用#{}占位符时,Mybatis在通过JDBC底层实现时,使用了预编译的处理,所以,占位符的位置只可能是个值,不可能是字段名或别的,所以不需要使用一对单引号框住;使用${}占位符时,并没有使用预编译,而只是将参数值拼接到SQL语句中执行,所以,对于非数值型的参数值,需要使用一对单引号框住!
由于使用#{}占位符时,是预编译的,所以,完全不存在SQL注入的风险,而${}占位符是先拼接SQL再编译并执行的,拼接的参数值有可能改变语义,所以,存在SQL注入的风险!
当然,${}格式的占位符虽然有不少缺点,但是,它可以表示SQL语句中的任何片段,而#{}只能表示某个值!而且,关于SQL注入,其实要想实现SQL注入,传入的值也是需要满足许多特征性条件的,只要在执行SQL语句之前使用正则表达式进行验证,就可以避免出现SQL注入!
Mybatis的缓存机制
Mybatis框架默认是有2套缓存机制的,分别称之一级缓存和二级缓存。
Mybatis框架的一级缓存也称之为“会话(Session)缓存”,默认是开启的,且无法关闭!
一级缓存必须保证多次的查询操作满足:同一个SqlSession、同一个Mapper、执行相同的SQL查询、使用相同的参数。
一级缓存会因为以下任意一种原因而消失:
- 调用SqlSession对象的clearCache()方法,将清除当前会话中此前产生的所有一级缓存数据
- 当前执行了任何写操作(增 / 删 / 改),无论任何数据有没有发生变化,都会清空此前产生的缓存数据
Mybatis框架的二级缓存也称之为“namespace缓存”,是作用于某个namespace的,具体 表现为:无论是否为同一个SqlSession,只要执行的是相同的Mapper的查询,且查询参数相同,就可以应用二级缓存。
在使用Spring Boot与Mybatis的项目中,二级缓存默认是全局开启的,但各namespace默认并未开启,如果需要在namespace中开启二级缓存,需要在XML文件中添加<cache/>标签,则表示当前XML中所有查询都开启了二级缓存!
需要注意:使用二级缓存时,需要保证查询结果的类型实现了Serializable接口!
另外,还可以在<select>标签上配置useCache属性,以配置“是否使用缓存”,此属性的默认值为true,表示“使用缓存”。
当应用二级缓存后,在日志上会提示[Cache Hit Ratio],表示“当前namespace缓存命中率”。
与一级缓存相同,只需要发生任何写操作,都会自动清除缓存数据!
Mybatis在查询数据时,会优先尝试从二级缓存中查询是否存在缓存数据,如果命中,将直接返回,如果未命中,则尝试从一级缓存中查询是否存在缓存数据,如果命中,将返回,如果仍未命中,将执行数据库查询。
二级缓存的示例代码:
提示:一定要在XML中添加<cache>才能够使用二级缓存。
5.Spring Security
SSO:Single Sign On,单点登录,表现为客户端只需要在某1个服务器上通过认证,其它服务器也可以识别此客户端的身份!
单点登录的实现手段主要有2种:
使用Session机制,并共享Session
spring-boot-starter-data-redis结合spring-session-data-redis
使用Token机制
- 各服务器需要有同样的解析JWT的代码
当前,csmall-passport中已经使用JWT,则可以在csmall-product项目中也添加Spring Security框架和解析JWT的代码,则csmall-product项目也可以识别用户的身份、检查权限。
登录
在用户登录时,Spring Security会验证身份信息,如果验证成功,Spring Security 将这个信息保存在上下文中,然后将身份信息转换成JWT字符串,返回给客户端。以后用户的每次访问都会在请求头中携带这个JWT字符串信息,然后后端将这个Jwt信息转换会Authentication,从Spring Security上下文中查找。如果存在,Spring Security框架会根据Authentication对象识别用户的身份、权限等,如果不存在,则视为“未登录”。
在Spring Security框架中,对于“登录”(通过认证)的判断标准是:在SecurityContext(Security上下文)中是否存在Authentication对象(认证信息),如果存在,Spring Security框架会根据Authentication对象识别用户的身份、权限等,如果不存在,则视为“未登录”。
关于Session
HTTP协议本身是无状态协议,无法保存用户信息,即:某客户端第1次访问了服务器端,可能产生了某些数据,此客户端再次访问服务器端时,服务器端无法识别出这个客户端是此前曾经来访的客户端。
为了能够识别客户端的身份,当某客户端第1次向服务器端发起请求时,服务器端将向客户端响应一个JSESSIONID数据,其本质是一个UUID数据,在客户端后续的访问中,客户端会自动携带此JSESSIONID,以至于服务器端能够识别此客户端的身份。同时,在服务器端,还是一个Map结构的数据,此数据是使用JSESSIONID作为Key的,所以,每个客户端在服务器端都有一个与之对应在的在此Map中的Value,也就是Session数据!
提示:UUID是全球唯一的,从设计上,它能够保证在同一时空中的唯一性。
由于Session的运作机制,决定了它必然存在缺点:
- 默认不适用于集群或分布式系统,因为Session是内存中的数据,所以,默认情况下,Session只存在于与客户端交互的那台服务器上,如果使用了集群,客户端每次请求的服务器都不是同一台服务器,则无法有效的识别客户端的身份
- 可以通过共享Session等机制解决
- 不适合长时间保存数据,因为Session是内存中的数据,并且,所有来访的客户端在服务器端都有对应的Session数据,就必须存在Session清除机制,如果长期不清除,随着来访的客户端越来越多,将占用越来越多的内存,服务器将无法存储这大量的数据,通常,会将Session设置为15分钟或最多30分钟清除
3.微服务
nacos
Nacos是Spring Cloud Alibaba提供的一个软件 这个软件主要具有注册中心和配置中心
Nacos心跳机制
常见面试题
心跳:周期性的操作,来表示自己是健康可用的机制
注册到Nacos的微服务项目(模块)都是会遵循这个心跳机制的
心跳机制的目的
1.是表示当前微服务模块运行状态正常的手段
2.是表示当前微服务模块和Nacos保持沟通和交换信息的机制
默认情况下,服务启动开始每隔5秒会向Nacos发送一个"心跳包",这个心跳包中包含了当前服务的基本信息
Nacos接收到这个心跳包,首先检查当前服务在不在注册列表中,如果不在按新服务的业务进行注册,如果在,表示当前这个服务是健康状态
如果一个服务连续3次心跳(默认15秒)没有和Nacos进行信息的交互,就会将当前服务标记为不健康的状态
如果一个服务连续6次心跳(默认30秒)没有和Nacos进行信息的交互,Nacos会将这个服务从注册列表中剔除
这些时间都是可以通过配置修改的
实例类型分类
实际上Nacos的服务类型还有分类
- 临时实例(默认)
- 持久化实例(永久实例)
默认每个服务都是临时实例
Dubbo
什么是RPC
RPC只是实现远程调用的一套标准
该标准主要规定了两部分内容
1.通信协议
通信协议指的就是远程调用的通信方式
实际上这个通知的方式可以有多种
2.序列化协议
序列化协议指通信内容的格式,双方都要理解这个格式
发送信息是序列化过程,接收信息需要反序列化
Dubbo是一套RPC框架。既然是框架,我们可以在框架结构高度,定义Dubbo中使用的通信协议,使用的序列化框架技术,而数据格式由Dubbo定义,我们负责配置之后直接通过客户端调用服务端代码。
可以说Dubbo就是RPC概念的实现能够实现微服务相互调用的功能!
Dubbo对协议的支持
RPC框架分通信协议和序列化协议
Dubbo框架支持多种通信协议和序列化协议,可以通过配置文件进行修改
Dubbo支持的通信协议
- dubbo协议(默认)
支持的序列化协议
- hessian2(默认)
Dubbo服务的注册与发现
在Dubbo的调用过程中,必须包含注册中心的支持
项目调用服务的模块必须在同一个注册中心中
注册中心推荐阿里自己的Nacos,兼容性好,能够发挥最大性能
但是Dubbo也支持其它软件作为注册中心(例如Redis,zookeeper等)
Dubbo的注册发现流程
1.首先生产者启动服务时,将自己服务注册到注册中心,其中包括当前生产者的ip地址和端口号等信息,Dubbo会同时注册该项目提供的远程调用的方法
2.消费者启动项目,也注册到注册中心,同时从注册中心中获得当前项目具备的所有服务列表
3.当注册中心中有新的服务出现时,(在心跳时)会通知已经订阅发现的消费者,消费者会更新所有服务列表
4.RPC调用,消费者需要调用远程方法时,根据注册中心服务列表的信息,只需服务名称,不需要ip地址和端口号等信息,就可以利用Dubbo调用远程方法了
Dubbo内置负载均衡策略算法
- random loadbalance:随机分配策略(默认) 根据权重 3: 5: 7: 生产随机数
- round Robin Loadbalance:权重平均分配 性能比为5:3:1服务为A5次,B3次,C1次
- leastactive Loadbalance:活跃度自动感知分配 记录每个服务器处理一次请求的时间 按照时间比例来分配任务数,运行一次需要时间多的分配的请求数较少
- consistanthash Loadbalance:一致性hash算法分配
Seata
Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务
Seata将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
Seata构成部分包含
- 事务协调器TC
- 事务管理器TM
- 资源管理器RM
我们项目使用AT(自动)模式完成分布式事务的解决
AT模式运行过程
1.事务的发起方(TM)会向事务协调器(TC)申请一个全局事务id,并保存
2.Seata会管理事务中所有相关的参与方的数据源,将数据操作之前和之后的镜像都保存在undo_log表中,这个表是seata组件规定的表,没有它就不能实现效果,依靠它来实现提交(commit)或回滚(roll back)的操作
3.事务的发起方(TM)会连同全局id一起通过远程调用,运行资源管理器(RM)中的方法
4.RM接收到全局id,去运行指定方法,并将运行结果的状态发送给TC
5.如果所有分支运行都正常,TC会通知所有分支进行提交,真正的影响数据库内容,
所以如果我们在业务过程中有一个节点操作的是Redis或其它非关系型数据库时,就无法使用AT模式
除了AT模式之外还有TCC、SAGA 和 XA 事务模式
3.9.1.TCC模式
简单来说,TCC模式就是自己编写代码完成事务的提交和回滚
在TCC模式下,我们需要为参与事务的业务逻辑编写一组共3个方法
(prepare\commit\rollback)
prepare:准备
commit:提交
rollback:回滚
- prepare方法是每个模块都会运行的方法
- 当所有模块的prepare方法运行
- 都正常时,运行commit
- 当任意模块运行的prepare方法有异常时,运行rollback
这样的话所有提交或回滚代码都由自己编写
优点:虽然代码是自己写的,但是事务整体提交或回滚的机制仍然可用(仍然由TC来调度)
缺点:每个业务都要编写3个方法来对应,代码冗余,而且业务入侵量大
3.9.2.SAGA模式
SAGA模式的思想是对应每个业务逻辑层编写一个新的类,可以设置指定的业务逻辑层方法发生异常时,运行当新编写的类中的代码
相当于将TCC模式中的rollback方法定义在了一个新的类中
这样编写代码不影响已经编写好的业务逻辑代码
一般用于修改已经编写完成的老代码
缺点是每个事务分支都要编写一个类来回滚业务,
会造成类的数量较多,开发量比较大
3.9.3.XA模式
支持XA协议的数据库分布式事务,使用比较少
Sentinel
Sentinel也是Spring Cloud Alibaba的组件
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。4.7.QPS与并发线程数
QPS:是每秒请求数
单纯的限制在一秒内有多少个请求访问控制器方法
并发线程数:是当前正在使用服务器资源请求线程的数量
限制的是使用当前服务器的线程数
Gate Way
"网"指网络,"关"指关口或关卡
网关:就是指网络中的关口\关卡
网关就是当前微服务项目的"统一入口"
程序中的网关就是当前微服务项目对外界开放的统一入口
所有外界的请求都需要先经过网关才能访问到我们的程序
提供了统一入口之后,方便对所有请求进行统一的检查和管理
kafka
20.2kafka软件结构
Kafka是一个结构相对简单的消息队列(MQ)软件
Kafka Cluster(Kafka集群)
Producer:消息的发送方,也就是消息的来源,Kafka中的生产者
Consumer:消息的接收方,也是消息的目标,Kafka中的消费者
Topic:话题或主题的意思,消息的收发双方要依据同一个话 题名称,才不会将信息错发给别人
Record:消息记录,就是生产者和消费者传递的信息内容,保存在指定的Topic中
20.3.Kafka的特征与优势
Kafka作为消息队列,它和其他同类产品相比,突出的特点就是性能强大
Kafka将消息队列中的信息保存在硬盘中
Kafka对硬盘的读取规则进行优化后,效率能够接近内存
硬盘的优化规则主要依靠"顺序读写,零拷贝,日志压缩等技术"
Kafka处理队列中数据的默认设置:
- Kafka队列信息能够一直向硬盘中保存(理论上没有大小限制)
- Kafka默认队列中的信息保存7天,可以配置这个时间,缩短这个时间可以减少Kafka的磁盘消耗
RabbitMQ
20.6.Rabbitmq的工作模式
常见面试题
Rabbitmq的工作模式有六种:simple简单模式、work工作模式、publish/subscribe订阅模式、routing路由模式、topic 主题模式、RPC模式。
simple简单模式为一个队列中一条消息,只能被一个消费者消费。
Work工作模式为一个生产者,多个消费者,每个消费者获取到的消息唯一。
publish/subscribe订阅模式为一个生产者发送的消息被多个消费者获取。
routing路由模式为生产者发送的消息主要根据定义的路由规则决定往哪个队列发送。
topic 主题模式为生产者,一个交换机(topicExchange),模糊匹配路由规则,多个队列,多个消费者。
RPC模式为客户端 Client 先发送消息到消息队列,远程服务端 Server 获取消息,然后再写入另一个消息队列,向原始客户端 Client 响应消息处理结果。
20.7.RabbitMQ路由模式的结构
RabbitMQ软件支持很多种工作模式,我们学习其中的路由模式
路由模式比较常用,而且功能强大,但是结构比Kafka的主题模式复杂
和Kafka不同,Kafka是使用话题名称来收发信息,结构简单
RabbitMQ路由模式是使用交换机\路由key指定要发送消息的队列
消息的发送者发送消息时,需要指定交换机和路由key名称
消息的接收方接收消息时,只需要指定队列的名称
在编写代码上,相比于Kafka,每个业务要编写一个配置类
这个配置类中要绑定交换机和路由key的关系,以及路由Key和队列的关系
Elasticsearch
全文搜索引擎
它本质就是一个java项目,使用它进行数据的增删改查就是访问这个项目的控制器方法(url路径)
ES的底层技术
ES使用了java的一套名为Lucene的API
这个API提供了全文搜索引擎核心操作的接口,相当于搜索引擎的核心支持,ES是在Lucene的基础上进行了完善,实现了开箱即用的搜索引擎软件
市面上和ES功能类似的软件有
Solr/MongoDB
聚集索引:就是数据库保存数据的物理顺序依据,默认情况下就是主键id,所以按id查询数据库中的数据效率非常高
非聚集索引:如果想在非主键列上添加索引,就是非聚集索引了
所以当我们项目中设计了根据用户输入关键字进行模糊查询时,需要使用全文搜索引擎来优化
索引面试题
1.创建的索引会占用硬盘空间
2.创建索引之后,对该表进行增删改操作时,会引起索引的更新,所以效率会降低
3.对数据库进行批量新增时,先删除索引,增加完毕之后再创建
4.不要对数据样本少的列添加索引
5.模糊查询时,查询条件前模糊的情况,是无法启用索引的
6.每次从数据表中查询的数据的比例越高,索引的效果越低
6.5.Elasticsearch运行原理
要想使用ES提高模糊查询效率
首先要将数据库中的数据复制到ES中
在新增数据到ES的过程中,ES可以对指定的列进行分词索引保存在索引库中
形成倒排索引结构
分词器
ik_smart
优点:特征是粗略快速的将文字进行分词,占用空间小,查询速度快
缺点:分词的颗粒度大,可能跳过一些重要分词,导致查询结果不全面,查全率低
ik_max_word
优点:特征是详细的文字片段进行分词,查询时查全率高,不容易遗漏数据
缺点:因为分词太过详细,导致有一些无用分词,占用空间较大,查询速度慢
ES启动后,ES服务可以创建多个index(索引),index可以理解为数据库中表的概念
一个index可以创建多个保存数据的document(文档),一个document理解为数据库中的一行数据
一个document中可以保存多个属性和属性值,对应数据库中的字段(列)和字段值
