导图社区 java
分享了java语法,jdk等;其他页面为java框架,如Spring等。仅供参考。有需要的赶紧收藏下来吧!
编辑于2023-05-25 10:41:13 山东省java project
开发
JDK
SDK中的一员 严格区分大小写
JRE
JDK、JRE、JVM的区别: 1)JDK:java开发工具包,是java的核心,包括:JRE+编译、运行等命令工具 2)JRE:java运行环境,是运行java程序所必须的环境的集合,包括:JVM+java系统类库 3)JVM:java虚拟机,是java实现跨平台的最核心部分,能够运行java语言所开发的程序
JVM 内存管理(运行)
栈 :行走的(8位十六进制)数字地址 栈帧 局部变量(方法参数)
正在调用ing 二进制: 1001 0011 十六进制: 9 3
方法区: .class字节码 (静态变量、所有方法)
堆:new 对象 (实例变量、数组元素)
API(Application Program Interface) 系统类库
java.lang (语言包)
默认引入
Object鼻祖
所有类都直接或间接继承了Object 万物皆对象,为了多态 自己写的类需要重写一下Object中的方法(可以generate)
String toString( )
输出引用类型变量时默认调用Object的toString(), 注意:String、StringBuilder、集合都重写了toString()
返回格式为:类的全称@十六进制地址,没有 参考意义,所以常常重写toString()来返回对象具体的属性值
@Override public int hashCode() { return Objects.hash(age, name, id); }
boolean equals(Object obj)
注意:String重写了equals()来比较字符串内容是否相同,而StringBuilder并没有
默认比较的还是==(即比较地址),没有参考意义,所以常常重 写equals()来比较具体的属性值是否相同
@Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Test test = (Test) o; return age == test.age && id == test.id && Objects.equals(name, test.name); }
native int hashCode()
Class<?> getClass()
获取当前类的信息
final native void wait(long timeout) throws InterruptedException
线程为了某个对象停止一段时间 没用指定时间的话默认一直等待
会因为线程被打断或多线程并发问题导致异常苏醒,因此需要释放对象锁以被线程监控, 即必须在 synchronized 修饰的代码块中使用,那么相应的 notify 方法同样必须在 synchronized 修饰的代码块中使用,如果没有在synchronized 修饰的代码块中使用时运行时会抛出IllegalMonitorStateException的异常。
synchronized (obj) { try { obj.wait(); } catch (InterruptedException e) { e.printStackTrace(); } }
final native void notify();
until either another thread invokes the notify() method or the notifyAll() method for this object
final native void notifyAll();
protected void finalize()
实例被垃圾回收器回收的时候触发的操作
System
虚拟机
gc()
垃圾回收器(GC) 回收没有引用的对象 回收过程透明,过程不可见 不定时,不一定会立刻回收 由System.gc()建议JVM来定时调度 属于一条独立的守护线程。
内存泄漏
不在使用的对象没有及时回收。
严重时会导致系统崩溃。
建议及时将引用变量设置为null。
long currentTimeMillis()
static 时间戳
GMT 0时区的1970-01-01 0:00:00 元年至今历史的毫秒数
final static InputStream in
打开输入流
String
final修饰 类,不能被继承 底层为由final修饰的字符数组char[] 内存中采用Unicode编码格式 会自行创建对象 字符串一旦创建,对象内容永远无法改变,但引用可以重新赋值 由于String为不可变对象,修改内容要新建新的对象,因此不适合频繁的修改操作。 若手动new对象,会创建两个对象,第一个自行创建并放入常量池中,第二个为new对象并将其赋值给变量; 两个对象内容一样(a.equals(b)==true)但地址不同(a==b==false)
private final char value[];
(优化措施)字符串常量池
字面量:在编译期即编译好(非变量) 存储以字面量/直接量("")的方式,创建对象的引用(地址) 复用是不会重复创建对象,以减少内存开销
equals(String str)
boolean 判断变量内容是否为s
length( )
int 获取字符串长度
trim( )
去除两边的空白字符,并作为新的对象存储
startsWith(String str)/endsWith(String str)
boolean 判断头/尾是否为s
charAt(int index)
char 根据下标找到字符
4*indexOf(String str, int fromIndex)/lastIndexOf(String str, int fromIndex)
int 根据字符找下标,找不到为-1
2*subString(String beginIndex, String endIndex)
截取[start, end)范围内的片段
6*valueOf(char data)
static 将其他类型转换为字符串类型(比拼接的效率要高)
正则表达式
独立于java语言之外 用于描述字符串的内容格式,使用它时通常用于匹配一个字符串是否符合格式要求
toUpperCase( )/toLowerCase( )
全体,改变英文字符大小写
语法(普通字符+特殊字符)
[]:表示一个字符,该字符可以是[]中指定的内容
例如: [abc]:这个字符可以是a或b或c [a-z]:表示任意一个小写字母 [a-zA-Z]:表示任意一个字母 [a-zA-Z0-9]:表示任意一个字母数字 [a-zA-Z0-9_]:表示任意一个数字字母下划线 [^abc]:该字符只要不是a或b或c
()用于分组,是将小括号里面的内容看做是一个整体
例如: (abc){3} 表示abc整体出现3次. 可以匹配abcabcabc 但是不能匹配aaa 或abcabc (abc|def){3}表示abc或def整体出现3次. 可以匹配: abcabcabc 或 defdefdef 或 abcdefabc 但是不能匹配abcdef 或abcdfbdef
量词
?: 表示前面的内容出现0-1次
+: 表示前面的内容最少出现1次
*: 表示前面的内容出现任意次(0-多次)---匹配内容与+一致,只是可以一次都不写
{n}: 表示前面的内容出现n次
{n,m}: 表示前面的内容出现最少n次最多m次
例如: [abc]{3,5} 可以匹配:aaa 或 abcab 或者 abcc 但是不能匹配:aaaaaa 或 aabbd
{n,}: 表示前面的内容出现n次以上(含n次)
预定义字符
.: 表示任意一个字符,没有范围限制
\d: 表示任意一个数字,等同于[0-9]
\w: 表示任意一个单词字符,等同于[a-zA-Z0-9_]----单词字符指字母、数字和_
\s :表示任意一个空白字符
\D: 不是数字
\W: 不是单词字符
\S: 不是空白字符
matches(String regex)
boolean 判断字符串格式
replaceAll(String regex, String replacement)
String 掩盖替换
split( ,)
String regex 会忽略后面的空字符串 String regex,limit x 拆分出x项,当limit 的值大于可拆分项时,仅保留最大可拆分项 值为0时,等同于没有这个重载 值为-1及其他负数时,应拆尽拆。
String[ ] 拆
byte[] getBytes()
AbstractStringBuilder
每次都会对对象本身进行操作
StringBuffer
线程安全:加了互斥锁
当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象就是线程安全的。
同步处理
性能稍慢
StringBuilder(修改)
非线程安全的,并发处理的,性能稍快 适用于频繁修改,性能高,仅获得10%-15%的性能提升。
append(String str)
末尾追加内容
insert(int offset, String str)
在offset后插入内容
delete(int start, int end)
删除[start, end)的内容
replace(int start, int end, String str)
替换[start, end)的内容
toString( )
转化为字符串
基本数据类型的包装类(以Integer为例)
为了遵循面向对象的原则, 包装完成之后为引用数据类型 Btye Short Integer Long Character Float
拆装箱
jdk1.5提供了自动拆装箱特性, 当基本类型与包装类型之间赋值时, 会自动触发拆装箱, 但本质是补齐代码
valueOf(int i)
会创建一个对象 会复用-128到127中的数据,因此在这个范围内的地址相同 可以省略,输入数据后自动调用
static Integer 装箱:将基本数据包装
intValue( )(一般直接省略)
int 拆箱
parseInt(String s)
static int 将字符串类型转换为int类型
Error
错误 不可能指望程序能处理这样的情况 这些异常发生时,Java虚拟机(JVM)一 般会选择线程终止。
Virtual MachineError
Java虚拟机运行错误
NoClassDefFoundError
Throwable
java中所有错误的超类为:Throwable。 其下有两个子类:Error和Exception Error的子类描述的都是系统错误,比如虚拟机内存溢出等。 Exception的子类描述的都是程序错误,比如空指针,下表越界等。 通常我们程序中处理的异常都是Exception。
printStackTrace()
输出堆栈信息到控制台,由于多线程的原因其输出顺序会与System.out.println("出错啦!")发生混乱。
输出堆栈信息到控制台,此操作属于耗时操作,可能会造成线程阻塞
流量多的项目禁止使用
getMessage()
String 获取错误信息,一般用于记录日志或提示给用户使用。
string toString():返回异常发生时的简要描述
string getLocalizedMessage():返回异常对象的本地化信息。使用Throwable的子类覆盖这个方法,可以声称本地化信息。如果子类没有覆盖该方法,则该方法返回的信息与getMessage()返回的结果相同
5*Exception
异常
非RuntimeException
可检测异常:可检测异常经编译器验证,对于声明抛出异常的任何方法,编译器将强制执行处理或 声明规则,不捕捉这个异常,编译器就通不过,不允许编译
不可控异常
5*RunTimeException
非检测异常(编译器检测不出来),因为普通JVM操作引起的运行时异常随时可能发生,此类异常一般是由特定操作引发。但这些操作在java应用程序中会频繁出现。因此它们不受编译器检查与处理或声明规则的限制。
可处理异常:在产生此类异常时,不一定非要采取任何适当操作,编译器不会检查是否已经解决了这样一个异常。
常见的RuntimeException子类[ ]
IllegalArgumentException:抛出的异常表明向方法传递了一个不合法或不正确的参数
NullPointerException:操作的引用是空的,抛出该异常
ArrayIndexOutOfBoundsException:当使用的数组下标超出数组允许范围时,抛出该异常
ClassCastException:强制类型转换类型不匹配,抛出该异常
NumberFormatException:当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。
异常处理机制
异常若不处理的话,JVM会直接终止程序的运行。 java异常处理机制 异常处理机制是用来处理那些可能存在的异常,但是无法通过修改逻辑完全规避的场景。 而如果通过修改逻辑可以规避的异常是bug,不应当用异常处理机制在运行期间解决!应当在编码时及 时修正
throw new xxxException("");
自定义异常
超类异常提供的所有种类构造器用来对外主动抛出一个新的异常实例
通常下面两种情况我们主动对外抛出异常:
1:当程序遇到一个满足语法,但是不满足业务要求时,可以抛出一个异常告知调用者。
2:程序执行遇到一个异常,但是该异常不应当在当前代码片段被解决时可以抛出给调用者。
定义自定义异常需要注意以下问题:
异常的类名要做到见名知义
需要是Exception的子类
当某句代码抛出了一个异常时,JVM会做如下操作:
1:检查报错这句话是否有被异常处理机制控制(有没有try-catch) 如果有,则执行对应的catch操作,如果没有catch可以捕获该异常则视为没有 异常处理动作
2:如果没有异常处理,则异常会被抛出当当前代码所在的方法之外由调用当前方法的 代码片段处理该异常当try中某句代码报错后,就会跳出try执行下面对应的catch块,执行后就会 退出catch继续向后执行。因此try语句块中报错代码以下的内容都不会被执行
异常抛出后不会再向下执行代码,而是跟着异常向外抛直到被处理。
throws xxxException,xxxException
RuntimeEception默认抛出异常不需要写明,但在其抛出的过程中未被try-catch处理同样会被终止线程。 除了RuntimeException之外,抛出什么异常就要在方法上声明throws什么异常 当我们调用一个含有throws声明异常抛出的方法时,编译器要求我们必须处理这个异常,否则编译不通过。
当一个方法中使用throw抛出一个非RuntimeException的异常时,就要在该方法上使用throws声明这个 异常的抛出。此时调用该方法的代码就必须处理这个异常,否则编译不通过。
处理手段有两种:
1. 使用try-catch捕获并处理这个异常
2. 在当前方法(本案例就是main方法)上继续使用throws声明该异常的抛出给调用者解决。
具体选取
那种取决于异常处理的责任问题。
注意,永远不应当在main方法上使用throws!!
子类重写超类含有throws声明异常抛出的方法时 对throws的几种特殊的重写规则
允许(以内)
抛出部分异常
不抛异常
抛出子异常
不允许(以外)
抛出额外异常
抛出超类异常
try{ 可能会报错的代码片段... }catch(XXXException e){ 出现错误后的补救措施(B计划) }
当try语句块中可能出现的几种不同异常对应的处理办法相同时,可以采取合并( | ) catch的做法,用同一个catch来捕获这几种可能出现的异常,而执行措施使用 同一个。
当catch捕获某个超类型异常时,那么try语句块中出现它类型异常时都可以被这个 catch块捕获并处理。
如果多个catch捕获的异常之间存在继承关系时,一定是子类异常在上超类异常在下
try{ 代码片段... }finally{ }
异常处理机制中的finally finally块定义在异常处理机制中的最后一块。它可以直接跟在try之后,或者最后一个catch之后。 finally可以保证只要程序执行到了try语句块中,无论try语句块中的代码是否出现异常,最终finally 都必定执行。 finally通常用来做释放资源这类操作。
interface Runnable
线程的目标类需要实现Runnable接口,并重写run方法。
abstract void run();
Callable 接口可以返回结果。
Thread
JAVA虚拟机为其分配内存,并初始化成员变量的值。 此时仅仅是个对象。
创建线程
方式一:继承Thread并重写run方法 Thread t = new Thread(){ public void run(){ } };
在其中定义线程要执行的任务(希望和其他线程并发执行的任务)。
第一种创建线程的优点: 结构简单,利于匿名内部类形式创建。 缺点: 1:存在继承冲突问题, 由于java是单继承的,这会导致继承了Thread就无法再继承其他类去复用方法 2:定义线程的同时重写了run方法,这等于将线程的任务定义在了这个线程中导致 线程只能干这件事。重(chong)用性很低。
方式二:实现Runnable接口单独定义线程任务 public class MyRunable implments Runable{ public void run(){ } } 创建线程时,把对象作为参数传进去 Thread t = new Thread(new MyRunable);
优点:由于是实现接口,没有继承冲突问题,线程与任务没有耦合关系,便于线程的重用
缺点:创建复杂一些(其实也不能算缺点)
init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals)
新的线程创建后,会对其初始化
线程组名
默认 null
位于run方法中要运行的目标代码,即新线程
默认 null
线程名
默认 "Thread-" + nextThreadNum()
堆栈大小
默认 0
访问控制
默认 true
static native Thread currentThread();
执行main方法的线程是虚拟机自动创建的,并为线程取了个名字叫"main",因此 我们也称执行main方法的线程为主线程:Thread[main,5,main]
该方法可以获取运行这个方法的线程。 (重写了toString方法,信息包括了: 线程名、线程优先级、线程组名)
setName(String name)
final synchronized
getName()
final String
getId()
long
setDaemon(boolean on)
守护线程 守护线程也称为:后台线程 守护线程是通过普通线程调用setDaemon(boolean on)方法设置而来的,因此创建上与普通线程无异. 守护线程的结束时机上有一点与普通线程不同,即:进程的结束. 进程结束:当一个进程中的所有普通线程都结束时,进程就会结束,此时会杀掉所有正在运行的守护线 程. 通常当我们不关心某个线程的任务什么时候停下来,它可以一直运行,但是程序主要的工作都结束时它应当跟着结束时,这样的任务就适合放在守护线程上执行.比如GC就是在守护线程上运行的.
final 设为守护线程
setPriority(int newPriority)
线程的优先级 线程start后会纳入到线程调度器中统一管理,线程只能被动的被分配时间片并发运行,而无法主动索取时间片.线程调度器尽可能均匀的将时间片分配给每个线程. 线程有10个优先级,分别用整数1-10表示.其中1为最低优先级,10为最高优先级, 5为默认优先级. 线程优先级越高的线程获取CPU时间片的概率越高.
final 设置优先级
getPriority()
final int
get/set 线程
start()
线程调用完start方法后会纳入到系统的线程调度器程序中被统一管理。 线程调度器会分配时间片段给线程,使得CPU执行该线程这段时间,用完后 线程调度器会再分配一个时间片段给一个线程,如此反复,使得多个线程 都有机会执行一会,做到走走停停,并发运行。 线程第一次被分配到时间后会执行它的run方法开始工作。
这时候线程处于等待CPU分配资源阶段,谁先抢的CPU资源,谁开始执行; 该线程进入就绪状态,JAVA虚拟机会为其创建方法调用栈和程序计数器。线程的执行是由底层平台控制, 具有一定的随机性。
static native sleep(long millis)
sleep阻塞 使运行该方法的线程进入阻塞状态指定的毫秒,超时后线程会自动回到RUNNABLE状态等待再次获取时间片并发运行. sleep方法处理异常:InterruptedException. 当一个线程调用sleep方法处于睡眠阻塞的过程中,该线程的interrupt()方法被调用时,sleep方法会抛出该异常从而打断睡眠阻塞. wait/sleep的区别 sleep是Thread的静态方法,wait是Object的方法,任何对象实例都可以调用 sleep不会释放锁,它也不需要占用锁。wait会释放锁,但调用它的前提是当前线程占有锁(即代码要在synchronized中) 他们都可以被interrupted()方法中断(线程中断方法)
final void join()
等待调用join方法的线程结束或持续了指定时间还没结束之后,其他程序再继续执行,一般用于等待异步线程执行完结果之后才能继续运行的场景。
interrupt() 结束线程的阻塞状态
一个线程对象处于阻塞状态中时,另一个线程在运行过程中用此对象调用该方法可使该线程结束阻塞状态。
线程阻塞
sleep
wait
wait 方法是属于 Object 类中的,wait 过程中线程会释放对象锁,只有当其他线程调用 notify 才能唤醒此线程。wait 使用时必须先获取对象锁,即必须在 synchronized 修饰的代码块中使用,那么相应的 notify 方法同样必须在 synchronized 修饰的代码块中使用,如果没有在synchronized 修饰的代码块中使用时运行时会抛出IllegalMonitorStateException的异常。
join
底层使用 wait() 方法来实现
等待调用join方法的线程结束之后,程序再继续执行,一般用于等待异步线程执行完结果之后才能继续运行的场景。
yield() (仅用于演示线程的并发安全问题)
static native 可以让执行该方法的线程主动放弃本次剩余时间片(非阻塞)
进程:计算机当前正在运行的程序,占用内存和CPU,一个进程包含多个线程
线程:一个顺序的单一的程序执行流程就是一个线程。代码一句一句的有先后顺序的执行。
java程序中所有的代码都是靠线程执行的,main方法也不例外. 执行main方法的线程是虚拟机自动创建的,并为线程取了个名字叫"main",因此我们也称执行main方法的线程为主线程.
多线程:多个单一顺序执行的流程并发运行。造成"感官上同时运行"的效果。
并发:多个线程实际运行是走走停停的。线程调度程序会将CPU运行时间划分为若干个时间片段并尽可能均匀的分配给每个线程,拿到时间片的线程被CPU执行这段时间。当超时后线程调度程序会再次分配一个时间片段给一个线程使得CPU执行它。如此反复。由于CPU执行时间在纳秒级别,我们感觉不到切换线程运行的过程。所以微观上走走停停,宏观上感觉一起运行的现象成为并发运行!
多线程并发安全问题 临界资源:操作该资源的全过程同时只能被单个线程完成. 当多个线程并发操作同一临界资源,由于线程切换时机不确定,导致操作临界资源的顺序出现混乱严重时可能导致系统瘫痪.
让同一时刻只有一个线程在执行某段代码。 避免数据错误,和可能发生的服务宕机
并发安全问题解决方案:
JVM 锁就是我们常说的 synchronized、Lock。 线程排队, 相当于让多个线程从原来的抢着操作 改为排队操作。
1. synchronized
修饰实例方法,作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁
修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁 。
在静态方法上使用synchronized 当在静态方法上使用synchronized后,该方法是一个同步方法.由于静态方法所属类,所以一定具有同步效果.静态方法使用的同步监视器对象为当前类的类对象(Class的实例). 在JVM中,每个被加载的类都有且只有一个Class的实例与之对应
当一个静态方法上使用synchronized后,那么该方法一定是同步]的 静态方法上如果使用了synchronized,那么同步监视器对象不可选, 是当前类的类对象.(Foo.class) Class类,它的每一个实例用于表示JVM加载的一个类,因此Class类的 实例就被称为一个类的类对象.在JVM内部每个被加载的类都有且只有一个 Class实例与之对应.(后面的反射知识点会详细说它)
2. synchronized(同步监视器对象——有效且合适的锁对象){ 需要同步执行的代码片段 }
锁对象为进入到同步块里的线程对象,其他线程需要等待锁对象结束才能继续更换锁对象。通常此锁对象为 this。 静态方法中锁对象为 类名.class。
同步块:有效的缩小同步范围可以在保证并发安全的前提下尽可能的提高并发效率.同步块可以更准确的控制需要多个线程排队执行的代码片段. 同步块可以更精准的锁定需要多个线程同步执行的代码片段. 使用同步块时要明确制定同步监视器对象,该对象需要同时满足: 1:必须是一个引用类型的实例 2:多个需要同步执行该代码片段的线程看到的必须是同一个 同步监视器对象 synchronized(同步监视器对象——有效且合适的锁对象){ 需要同步执行的代码片段 } 原则:抢谁就锁谁 同步监视器对象即上锁的对象,要想保证同步块中的代码被多个线程同步运行,则要求多个线程看到的同步 监视器对象是同一个. 凡是用实例化表达式作为锁对象一律无效
字符串字面量就不是一个合适的锁对象. 因为字符串字面量始终表示同一个字符串对象,因此无论什么情况 下多个线程执行这个同步块始终看到同一个字符串对象,因此时刻 处于同步的,但是在不应当同步的时候也同步就不是合适的锁对象
在静态方法中如果使用同步块,那么同步监视器对象就用当前类的类对象 类对象写法:类名.class
常见锁策略
互斥锁
当多个线程执行不同的代码片段,但是这些代码片段之间不能同时运行时就要设置为互斥锁.
使用synchronized锁定多个代码片段,并且指定的同步监视器是同一个时,这些代码片段之间就是互斥的.
互斥性
当使用多个synchronized锁定多个代码片段,并且指定了相同的同步监视器对象时
那么这些代码片段之间就是互斥的,多个线程不能同时执行它们
乐观锁/悲观锁
乐观锁
表现:CAS(COMPARE AND SWAP)机制:对比替换 V:内存中的值 A:预期的旧值 B:要替换的新值
乐观锁认为一般情况下不会出现冲突,所以只会在更新数据的时候才对冲突进行数据检测,如果没有发生冲突则直接修改,如果发生冲突则不做任何修改,然后把结果返回给用户,让用户自行处理。
比较V和A是否相等,相等则将V更改为B,否则提示修改失败。核心:先比较再替换
实现借助Unsafe类,Unsafe类调用操作系统的Atomic::cmpxchg(原子性汇编指令) 我们一般不用Unsafe类,因为其中的方法可以直接对内存进行操作,是不安全的。
推荐使用Atomicxxx来实现CAS机制保证线程安全。
ABA问题
总是假设最坏的情况,每次去拿数据的时候就认为别人会修改,所以每次进行访问都要进行上锁,这样被人拿数据时就会阻塞直到它拿到锁。
synchronized、lock 都是悲观锁
公平锁/非公平锁
非公平锁:抢占式执行,有一些先来的任务还在排队,刚好释放锁的时候新来了一个任务,此时并不会通知任务队列来执行任务,而是执行新来的任务。
公平锁:所有任务来了之后先排队,线程空闲之后去任务队列按顺序执行最早任务。(排队做核酸
读写锁
读写锁 (Readers-Writer Lock)顾名思义就是把一把锁分为两个部分:读锁和写锁,读锁允许多个线程同时获得,因为读本身就是线程安全的,而写锁是互斥锁,不允许多个线程同时获得,并且读写也是互斥的。ALL IN ALL :读读不互斥、读写互斥、写写互斥。 JAVA标准库提供了ReentrantReadWriteLock类,实现了读写锁 读写锁适合于“频繁读,不频繁写”的场景
独占锁/共享锁
独占锁:任何时候只有一个线程能执行资源操作:synchronized、lock
共享锁:可以同时被多个线程读取,但只能被一个线程修改。比如java中的读写锁。读锁可以同时被多个线程读取,而写锁则只能被一个线程修改。
可重入锁/自旋锁
解决死锁问题
死锁,假如线程 A 持有资源 2,线程 B 持有资源 1,他们同时都想申请对方的资源,所以这两个线程就会互相等待而进入死锁状态。
可重入锁指的是该线程获取了该锁之后,可以无限次的进入该锁锁住的代码。
自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。
ReenTrantLock
API 层面,需要 lock() 和 unlock 方法配合 try/finally 语句块来完成
增加了一些高级功能。主要来说主要有三点:
①等待可中断;
通过lock.lockInterruptibly()来实现这个机制。
通过lock.lockInterruptibly()来实现这个机制。
②可实现公平锁;
ReentrantLock(boolean fair) 构造方法来制定是否是公平的。
③可实现选择性通知(锁可以绑定多个条件)
要借助于Condition接口与newCondition() 方法
volatile
volatile关键字是线程同步的轻量级实现,只能用于变量
多线程访问volatile关键字不会发生阻塞
volatile关键字主要用于解决变量在多个线程之间的可见性
Class
Class类的实例被称为"类对象"。 JVM在加载一个类的字节码文件到内部时就会实例化一个Class实例 与加载的类对应,用这个Class实例记录加载的类的相关信息。并且 在JVM内部每个被加载的类【都有且只有一个】类对象与之对应。
getClassLoader()
ClassLoader 加载器:获取.java下的目录
getResource(String name)
java.net.URL "." 获取当前类的目录
className.class
forName(String className) 参数:类的完全限定名(包名.类名)
static Class<?> 获取目标类
获取一个类的类对象的方式
1:类名.class
Class cls = String.class;
Class cls = int.class;
2.Class.forName(String className)
参数:类的完全限定名(包名.类名)
Class cls = Class.forName("java.lang.String")
3:使用ClassLoader加载
4:对象.getClass()
newInstance()
T 方法会调用类对象所表示的类的【公开的无参构造器】实例化
返回值Object类型的变量接收
Object o = cls.newInstance();
getConstructor(Class<?>... parameterTypes)
Constructor<T> 获取构造器
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
getDeclaredMethod(String name, Class<?>... parameterTypes)
Method
Method[] getDeclaredMethods()
两个方法可以获取本类定义的方法(含有私有的,不含有继承的)
getMethod(String name, Class<?>... parameterTypes)
...: JDK5之后推出了一个特性:变长参数 编译器认可,可变长参数的要求是:必须为一个方法的最后一个参数 编译后可变长参数为数组类型 (String[] args)
Method 调用方法名为name的可变参数方法
Method[] getMethods()
调用当前类对象所表示的类的所有公开方法
两个方法仅能获取该类的公开方法(包括从超类继承的方法)
interface AnnotatedElement
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
判断某类、属性、方法等,是否被参数的注解类标记
reflect
Constructor
该类的每一个实例用于表示一个类中的某个构造器
newInstance(Object ... initargs)
T 调用有参构造器创建加参对象
Method
Method类的每一个实例称为"方法对象"。 该类的每个实例表示某个类中的一个方法。通过方法对象我们可以得知 其表示的方法的相关信息,如:方法名,返回值类型,参数个数,参数类型 等等。
invoke(Object obj, Object... args)
Object 执行方法
getParameterCount()
int 获取参数个数
getModifiers()
int 获取修饰状态
AccessibleObject
setAccessible(boolean flag)
暴力反射:反射机制可访问目标类的私有成员 true打开结束后一定要false
强行打开访问权限。
可以破坏单例模式
规范示例
m.setAccessible(true);//强行打开访问权限
m.invoke(o);
m.setAccessible(false);//对于私有成员,访问后关闭(必须)
Modifier
定义了多个状态常量
static final int PUBLIC = 0x00000001;
annotation 注解
1. @interface Documented
将自定义的类,标记为注解
2. @interface Retention
RetentionPolicy value();
value参数配置注解标记的目标元素的保留策略
enum RetentionPolicy
SOURCE,
静态资源
CLASS,
类
RUNTIME
编译
3. @interface Target
ElementType[] value();
value参数配置注解标记的目标元素类型
enum ElementType
元素类型枚举
TYPE,
类注解
FIELD,
METHOD,
方法注解
PARAMETER,
参数注解
CONSTRUCTOR,
构造器
LOCAL_VARIABLE,
局部变量
ANNOTATION_TYPE,
注解
PACKAGE,
包
@since 1.8
TYPE_PARAMETER,
直译为类型变量,被该值所修饰,意味着声明的注解可以作用在泛型类,泛型接口,泛型方法上。
TYPE_USE
类型注解
Inherited
参数配置
有很多非RunTimeException要处理
Java反射机制
本质是将编译完成的.class字节码文件进行反射获取和调用 JAVA反射机制 反射是JAVA的动态机制,可以在程序【运行期间】再确定,如:对象实例化,方法调用属性操作等。 特点:反射机制可以大大的提高代码的灵活度和可扩展性, 但是随之带来的是较慢的运行效率和更多的系统开销。因此不能过度依赖反射机制。 反射机制进行实例化的步骤: 1:加载要实例化对象的类的类对象 2:直接通过类对象的方法newInstance()实例化
Iterable<T>
可遍历接口
void forEach(Consumer<? super T> action)
迭代方法
java.util 集合包,存储多个对象
集合和数组一样,可以保存一组数据。 提供了操作集合元素的相关方法,使用更加方便 集合只能存储引用类型元素
泛型:
jdk1.5时推出了一个特性:泛型(不能为基本数据类型)
泛型也称为参数化类型,允许我们在使用一个类时,传入某个类型来规定其内部的属性、方法参数或返回值类型,使得我们使用的时候更加方便。 泛型在集合中被广泛使用,用来指定集合中元素的类型
若不指定泛型的具体类型,则默认为Object。
若指定了泛型的具体类型,在获取泛型的值时,编译器会补充强转操作
操作步骤
1.声明泛型类属性 T
2.类添加此泛型 Xxx<T> 以标记此类有泛型
方法参数使用此泛型类时,需要在方法的返回值前添加此泛型 以标记方法参数有泛型 public static <T> JsonResult<T> ok(T data){ }
Collection<E> 接口
是所有集合的顶级接口,封装了所有集合所共有的方法,下面有多 种实现类,因此我们有更多的数据结构可以选择
add(E e)
boolean 添加给定元素,成功添加返回true
addAll(Collection<? extends E> c)
remove(Object o)
boolean 删除给定元素,成功删除返回true
removeAll(Collection<?> c)
boolean 删除交集,成功返回true
clear()
清空集合
retainAll(Collection<?> c)
boolean 保留交集,成功返回true
size()
int 返回当前集合的元素个数
isEmpty()
boolean 判断当前集合是否为空集
contains(Object o)
boolean 判断集合是否包含给定元素
containsAll(Collection<?> c)
boolean 判断是否包含给定集合
Object[] toArray();
集合转为数组
数组转为集合:Arrays类的静态方法asList()
Iterator<E> iterator();
获取遍历器
default Stream<E> stream()
开启序列流
Iterator<E> 迭代器接口
Collection接口提供了统一的遍历集合的方式:迭代器模式。 语法: while(c.hasNext){ c.next; } 定义了迭代器遍历集合的相关操作,不同的集合都实现了用于遍历自 身元素的迭代器实现类,但我们无需记住它们的名字,从多态的角度把它们看成Iterator即 可 jdk1.5时推出了一个特性: 增强型for循环,也称为新循环,让我们使用相同的语法遍历集合 和数组。 语法: for(元素类型 变量名 : 集合或数组){ 循环体 } 循环体仅一行代码,和省略{}
问 hasNext()
boolean
取 next()
E
删 remove()
4*Scanner 扫描器
获取管理员在控制台上输入的信息
nextLine()
String 扫描输入的字符串
List 接口
线性表、集合可重复、有序 List集合的特点是可以存放重复元素且有序 其提供了一组通过下标操作元素的方法
RandomAccess
随机访问标识
实现了RandomAccess接口的list,优先选择普通for循环 ,其次foreach,
未实现RandomAccess接口的list, 优先选择iterator遍历(foreach遍历底层也是通过iterator实现的),大size的数据,千万不要使用普通for循环
ArrayList
底层使用的是Object数组, 查询性能更好。 在实际应用过程中查询占重头,因此该list集合使用频率更高。
add(int index,E e)
将给定元素插入到指定位置
remove(int index)
E 删除给定元素,返回值为被删除元素
set(int index,E e)
E 将给定元素设置到指定位置,返回值为被替换的元素
get(int index)
E 获取指定下标的元素
subList(int start,int end)
对子集的操作就是对原集合的操作 将子集清除即删除了原集合的该区域元素
List 获取当前List集合中指定范围内的子集,含头不含尾。
sort(Comparator<? super E> c)
JDK1.8之后 List集合自身也提供了sort方法,可以根据排序器对元素进行排序
boolean 添加给定集合,成功添加返回true
Vector类的所有方法都是同步的。可以由两个线程安全地访问一个Vector对象、但是一个线程访问Vector的话代码要 在同步操作上耗费大量的时间。
LinksList
LinkedList 底层使用的是双向链表数据结构(JDK1.6之 前为循环链表,JDK1.7取消了循环。注意双向链表和双向循环链表的区别:) 增删元素性能更好
插入,删除元素时间复杂度不受元素位置的影响,都是 近似 O(1)而数组为近似 O(n)。
Set 接口
集合不可重复 利用equals()判断 大多数实现类是无序的
HashSet
TreeSet
二叉树
Collections
集合的工具类, 提供了多个静态方法 便于我们操作集合. 如:搜索、排序、线程安全化等操作。
reverse(List<?> list)
static 反转List集合
sort(List<T> list)
static 自然排序(由小到大)
shuffle(List<?> list)
static 打乱
sort(List<T> list, Comparator<? super T> c)
使用sort方法时临时传入一个比较规则 该比较规则类需要实现接口:Comparator 匿名内部类传入一个比较规则
static 根据内部类排序 (int 返回值大于零则大返回值小于零则小)
Comparator<E> 排序器接口
int compare(T o1, T o2);
可通过重写该方法来自定义排序规则
排序算法
冒泡排序
选择排序
插入排序
Map<K,V> 接口
Map 查找表 Map是JAVA集合框架中的一员。但是并不继承自java.util.Collection接口!!!! Map体现的样子是一个多行两列的表格,其中左列为key,右列为value,key不能重复,一个key对应一个value。 * Map总是根据key-value对的形式保存数据,且总是根据key获取对应的value * Map中可以使用null做为key或value。 * Map要求key是不允许重复的(equals比较)
put(K key, V value);
V 返回之前的value
向Map中添加一组键值对。 由于Map中的key不允许重复,所以如果使用Map已有的key存放新的value时,则是替换value操作 并将被替换的value返回。如果没有任何替换时,返回值为null。
注意:如果Map的value是包装类类型时,要用包装类定义的变量接收返回值,避免使用基本类型因自动拆箱导致空指针!
get(Object key);
V 根据给定的key获取对应的value 如果给定的key不存在,则返回值为null
获取键值对
remove(Object key);
V 删除Map中指定的key对应的这组键值对。返回值为这个key对应的value。
size();
int 返回当前Map中的元素个数。每组键值对算一个元素。
containsKey(Object key)
boolean 判断当前Map是否包含给定的key
containsValue(Object value)
boolean 判断当前Map是否包含给定的value
clear()
清空
keySet()
Set<K> 将当前Map中所有的key以一个Set集合形式返回。遍历这个集合就等同于遍历了所有的key
entrySet()
Set<Map.Entry<K, V>> 将当前Map中每一组键值对以一个Entry形式表示,并最终以Set集合形式返回所有键值对
values()
Collection<V> 将当前Map中所有的value以一个集合形式返回
forEach(Consumer<? super T> action)
JDK8之后集合框架的成员都支持了基于lambda表达式遍历元素的方法:forEach() map.forEach((k,v)-> System.out.println(k+":"+v));
interface Entry<K,V>
Entry提供了两个常用方法: K getKey() V getValue() 来获取其表示的这组键值对的key和value
K getKey()
V getValue()
HashMap
基于散列算法实现的Map,也称为散列表。当今查询速度最快的数据结构。
JDK1.8之前 JDK1.8 之前 HashMap 底层是 数组和链表 结合在一起使用也就是 链表散列。HashMap 通过 key 的 hashCode 经 过扰动函数处理过后得到 hash 值,然后通过 (n - 1) & hash 判断当前元素存放的位置(这里的 n 指的是数组的 长度),如果当前位置存在元素的话,就判断该元素与要存入的元素的 hash 值以及 key 是否相同,如果相同的 话,直接覆盖,不相同就通过拉链法解决冲突。
所谓扰动函数指的就是 HashMap 的 hash 方法。使用 hash 方法也就是扰动函数是为了防止一些实现比较差的 hashCode() 方法 换句话说使用扰动函数之后可以减少碰撞。
所谓 “拉链法” 就是:将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。
JDK1.8之后 相比于之前的版本, JDK1.8之后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。
HashMap 默认的初始化大小为16。之后每次扩充,容量变为原来 的2倍。
为了能让 HashMap 存取高效,尽量较少碰撞,也就是要尽量把数据分配均匀。
在多线程下,进行 put 操作会导致 HashMap 死循环,原因在于 HashMap 的扩容 resize()方法。由于扩容是新建一个数组,复制原数据到数组。
ConcurrentHashMap
分段锁、乐观锁
JDK1.8 之前,使用了Segment的概念,将数组分割,每一把锁只锁一部分数据
JDK1.8 的时候已经摒弃了Segment的概念,直接用 Node 数组+链表+红黑 树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。
synchronized只锁定当前链表或红黑二叉树的首节点
TreeMap
基于二叉树实现的Map。
线程池
限制和管理资源
还维护一些基本统计信息
降低资源消耗。 通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
提高响应速度。 当任务到达时,任务可以不需要的等到线程创建就能立即执行。
提高线程的可管理性。 线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
Executor 线程池接口
execute(Runnable command)
方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;
线程池指派任务
submit()方法用于提交需要返回值的任务。线程池会返回一个future类型的对象,通过这个future对象可以判断 任务是否执行成功,并且可以通过future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用 get(long timeout,TimeUnit unit) 方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行 完。
ExecutorService 接口
shutdown()
线程池关闭,线程结束任务后销毁
List<Runnable> shutdownNow()
线程池关闭,线程销毁
Executors
newFixedThreadPool(int nThreads)
static ExecutorService
Date
Date( )获取当前时间 Date( long time) 时间戳为time时的日期
getTime() 获取时间戳
setTime(long ) 设置时间戳
before(long time)
boolean 判断时间先后
after...
getYear()
@Deprecated注解标记 过期注解,可以运行但不建议使用
int 获取当前年
UUID
static UUID randomUUID()
32位随机生成16进制唯一标识对象,用于文件存储命名,一般使用toString将其转化为字符串使用。
Arrays
操作数组的工具类
copyOf(U[] original, int newLength, Class<? extends T[]> newType)
static <T,U> T[] 将数组U复制到新的数组T中再指定长度以达到增删的效果
n*sort()
static 数组排序
asList()
数组转换为集合
StringJoiner
快速高效的组合字符串
new StringJoiner(delimiter, prefix, suffix)
组合字符串
StringJoiner add(CharSequence newElement)
添加
Objects
重写Object的工具包
Optional<T>
可以理解为只保存一个元素的集合,指定的泛型就是保存元素的类型
T get()
.function
Consumer<T>
遍历器
stream
Stream<T>
JDK1.8 流操作
Stream<T> filter(Predicate<? super T> predicate);
过滤,根据条件过滤掉集合中的元素
Stream<T> peek(Consumer<? super T> action);
映射,操作集合中的元素并返回新的元素
<R, A> R collect(Collector<? super T, A, R> collector);
结束操作,stream流无存储,所以需要返回操作结束后的集合
.concurrent.atomic.
原子类
AtomicInteger
利用乐观锁实现的整型包装类
AtomicInteger(int initialValue)
构造器初始化赋值
final int get()
获取当前值
final boolean compareAndSet(int expect, int update)
先比较后修改 参数为:旧值 要修改后的值
旧值
修改后的值
java.io
跨出内存的限制,访问硬盘。 参照点是我们写的程序。 java将IO比喻为"流",即:stream. 就像生活中的"电流","水流"一样,它是以同一个方向顺序移动的过程.只不 过这里流动的是字节(2进制数据).所以在IO中有输入流和输出流之分,我们理解他们是连接程序与另一端 的"管道",用于获取或发送数据到另一端. 文件流 文件流是一对低级流,用于读写文件数据的流.用于连接程序与文件(硬盘)的"管道".负责读写文件数据.
IOException
JDK1.7 自动关闭
语法:
try( 定义需要在finally中调用close()方法关闭的对象. ){ IO操作 }catch(XXXException e){ ... }
上述语法中可在try的"()"中定义的并初始化的对象必须实现了java.io.AutoCloseable接口,否则编译不通
旨在IO操作中可以更简洁的使用异常处理机制完成最后
的close操作(主要就是简化了finally关闭流的操作)
该特性是编译器认可的,并非虚拟机。
6*File(String pathname)
File类的每一个实例可以表示硬盘(文件系统)中的一个文件或目录(实际上表示的是一个抽象路径) 使用File可以做到: 1:访问其表示的文件或目录的属性信息,例如:名字,大小,修改时间等等 2:创建和删除文件或目录 3:访问一个目录中的子项 但是File不能访问文件数据.
length()
long 文件的字节数(单位为b)
canRead()
boolean
canWrite()
boolean
isHidden()
boolean 判断是否隐藏
createNewFile()
将文件对象创建下来
getName()
绝对路径在代码转移后会出现找不到的情况,在编写过程中并不推荐使用。 相对路径中的"./"表达当前目录 在相对路径中最开始的"./"可以忽略不写,默认就是从"./"开始
String 获取文件名
exists()
boolean 判断当前File表示的位置是否已经实际存在该文件或目录
delete()
delete方法在删除目录时要求必须是一个空目录,否则无法删除
mkdir()
make:做 directory:目录 mkdir也是linux中创建目录的命令 mkdir()方法创建目录时要求该目录所在的目录必须存在,否则 创建失败。
mkdirs()
mkdirs()在创建该目录的同时会将所有不存在的父目录一同创建 出来,推荐使用该方法 ".": 表示在当前目录下创建一个目录
boolean
isFile()
boolean 判断当前File表示的是否为一个文件
isDirectory()
boolean 判断当前File表示的是否为一个目录
2*listFiles(FileFilter filter)
File[] 获取当前File对象表示的目录中的所有子项集
FileFilter 文件过滤器接口
accept(File file)
boolean true则过,false则一边子去
输入流
字节流
节点流
节点流: 俗称"低级流",特点:真实连接我们程序和另一端的"管道",负责实际读写数据的流
InputStream
所有字节输入流的超类(抽象类),里面定义了读取字节的相关方法。 * 所有字节输入流都继承自它
3*read(byte[] data, int off, int len)
块int read(byte[] data) 一次性从文件中读取给定的字节数组总长度的字节量,并存入到该数组中。 返回 值为实际读取到的字节量。若返回值为-1则表示读取到了文件末尾。 块写操作 void write(byte[] data) 一次性将给定的字节数组所有字节写入到文件中 void write(byte[] data,int offset,int len) 一次性将给定的字节数组从下标offset处开始的连续len个字节 写入文件 int read() 读取1个字节并以int型整数返回读取到的字节内容,返回的int值中对应的2进制的"低八位" 就是读取到的数据。如果返回的int值为整数-1(这是一个特殊值,32位2进制全都是1)表达 的 是流读取到了末尾了。 int read(byte[] data) 文件输入流重写了上述两个方法用来从文件中读取对应的字节。 fos.dat文件中的数据: 00000001 00000010 ^^^^^^^^ 第一次读取的字节 当我们第一次调用: int d = fis.read();//读取的是文件中第一个字节 该int值d对应的2进制: 00000000 00000000 00000000 00000001 |------自动补充24个0-------| ^^^^^^^^ 读取到的数据 而该2进制对应的整数就是1. fos.dat文件中的数据: 00000001 00000010 ^^^^^^^^ 第二次读取的字节 当我们第二次调用: d = fis.read();//读取的是文件中第二个字节 该int值d对应的2进制: 00000000 00000000 00000000 00000010 |------自动补充24个0-------| ^^^^^^^^ 读取到的数据 而该2进制对应的整数就是2. fos.dat文件中的数据: 00000001 00000010 文件末尾 ^^^^^^^^ 没有第三个字节 当我们第三次调用: d = fis.read();//读取到文件末尾了! 该int值d对应的2进制: 11111111 11111111 11111111 11111111 该数字是正常读取1个字节永远表达不了的值。并且-1的2进制格式好记。因此用它表达读取 到了末尾。 String提供了构造方法可以将一个字节数组还原为字符串 String(byte[] data,Charset charset) 将给定的字节数组data中所有字节按照给定的字符集转换为字符串。 String(byte[] data,int offset,int len,Charset charset) 将给定的字节数组data从下标offset处开始的连续len个字节按照指定的字符集转换为字符 串
int 4*8位
flush()
2*FileInputStream(String pathname/File file, boolean append)
两种构造器都会在创建时将该文件创建出来(如果该文件不存在才会这样做),自动创建 该文件的前提是该文件所在的目录必须存在,否则会抛出异常。
处理流
处理流:俗称"高级流" 特点: 1:不能独立存在(单独实例化进行读写操作不可以) * 2:必须连接在其他流上,目的是当数据"流经"当前流时,可以对其做某种加工操作,简化我们的 工作、 * 流的连接: 实际开发中经常会串联一组高级流最终到某个低级流上,对数据进行流水线式的加工读写。
2*BufferedInputStream(InputStream in, int size)
缓冲流 java.io.BufferedOutputStream和BufferedInputStream. 缓冲流是一对高级流,作用是提高读写数据的效率. 缓冲流内部有一个字节数组,默认长度是8K.缓冲流读写数据时一定是将数据的读写方式转换为块读写来 保证读写效率. 缓冲输出流写出数据时的缓冲区问题 通过缓冲流写出的数据会被临时存入缓冲流内部的字节数组,直到数组存满数据才会真实写出一次 创建一个size指定大小(单位是字节)缓冲区 的缓冲字节输出流,并连接到参数指定的字节输出流上。
ObjectInputStream(InputStream in)
对象反序列化: 将一组字节还原为java对象(前提是这组字节是一个对象序列化得到的字节)
readObject()
该方法会进行对象的反序列化,如果对象流通过其连接的流读取的字节分析并非 是一个java对象时,会抛出异常:ClassNotFoundException
Object
字符流
字符流 java将流按照读写单位划分为字节流与字符流. java.io.InputStream和OutputStream是所有字节流的超类 而java.io.Reader和Writer则是所有字符流的超类,它们和字节流的超类是平级关系. Reader和Writer是两个抽象类,里面规定了所有字符流都必须具备的读写字符的相关方法. 字符流最小读写单位为字符(char),但是底层实际还是读写字节,只是字符与字节的转换工作由字符流 完成
Reader
InputStreamReader(InputStream in, Charset cs)
转换流 java.io.InputStreamReader和OutputStreamWriter 它们是字符流非常常用的一对实现类同时也是一对高级流,实际开发中我们不直接操作它们,但是它们在流 连接中是非常重要的一环. 使用转换输出流向文件中写入文本数据 转换流的意义: 实际开发中我们还有功能更好用的字符高级流.但是其他的字符高级流都有一个共通点:不能直接连接在字 节流上.而实际操作设备的流都是低级流同时也都是字节流.因此不能直接在流连接中串联起来.转换流是 一对可以连接在字节流上的字符流,其他的高级字符流可以连接在转换流上.在流连接中起到"转换器"的作 用(负责字符与字节的实际转换)
read()
int
一次读两个字节并补充两个字节; 可强转为char,-1时结尾。
BufferedReader(Reader in)
使用缓冲字符输入流按行读取字符串 该高级流的主要作用: 1:块读文本数据加速(内部有一个默认8k的char数组) 2:可以按行读取字符串
readLine()
提供了一个独有的方法:readLine() 作用: 读取一行字符串。连续读取若干字符直到遇到了换行符位置,并将换行符之前的 内容返回。 注意:返回的字符串里不包含最后的换行符。 特殊情况: 如果这一行只有一个换行符,那么返回值为空字符串:"" 如果读取到了流的末尾,那么返回值为null。 实际运行时: 当我们第一次调用readLine()方法时,缓冲字符输入流实际会一次性读取8k的char 回来并存入内部的char数组中(块读文本操作)。 readLine方法只将char数组中从头 开始一直到第一个换行符位置的内容以一个字符串形式返回。
String
interface Serializable
java规定: 封装类只有实现该接口,其对象才可以被序列化。 序列化的意义: 对象持久化 可进行网络传输
interface AutoCloseable
close()
/注意!流使用完毕后要关闭,来释放底层资源。
interface Closeable
输出流
字节流
节点流
OutputStream
所有字节输出流的超类(抽象类),里面定义了读取字节的相关方法。 * 所有字节输出流都继承自它
3*write(byte[] data, int off, int len)
写出一个字节,将给定的参数int值对应的2进制的"低八位"写出。 文件输出流继承OutputStream后就重写了该方法,作用是将该字节写入到文件中。 向文件中写入1个字节 fow.write(1) 将int值的1对应的2进制的"低八位"写如到文件第一个字节位置上 1个int值占4个字节,每个字节是一个8为2进制 int 1的2进制样子: 00000000 00000000 00000000 00000001 ^^^^^^^^ 写出的字节 write方法调用后,fos.dat文件中就有了1个字节,内容为: 00000001 再次调用: fos.write(5) int 5的2进制样子: 00000000 00000000 00000000 00000101 ^^^^^^^^ 写出的字节 write方法调用后,fos.dat文件中就有了2个字节,内容为: 00000001 00000101 上次写的 本次写的 (len = fis.read(buf)) String提供方法: byte[] getBytes(String charsetName) 将当前字符串转换为一组字节 参数为字符集的名字,常用的是UTF-8。 其中中文字3字节表示1个,英文1字节表示1个。
int
flush()
2*FileOutputStream(String pathname/File file, boolean append)
默认 覆盖模式: 文件流在创建是若发现该文件已存在,则会将该文件原内容全部删除。然后 在陆续将通过该流写出的内容保存到文件中。 当第二个参数传入true时, 文件流为追加模式: 指定的文件若存在,则原有数据保留,新写入的数据 会被顺序的追加到文件中
处理流
2*BufferedOutputStream(OutputStream out, int size)
ObjectOutputStream(OutputStream out)
对象序列化: 将写出的对象按照其结构转换为一组字节的过程。 序列化时可能出现异常: java.io.NotSerializableException:io.Person 注:冒号后面的io.Person是指序列化的就是这个类的实例出现的错误。 原因: 对象输出流在进行序列化对象时,要求该对象所属的类必须实现接 口:java.io.Serializable接口 并且该类中所有引用类型属性也必须实现该接口,否则会抛出上述异常。 需要进行序列化的类必须实现接口:java.io.Serializable 实现序列化接口后最好主动定义序列化版本号这个常量。 这样一来对象序列化时就不会根据类的结构生成一个版本号,而是使用该固定值。 那么反序列化时,只要还原的对象和当前类的版本号一致就可以进行还原。 transient关键字可以修饰属性,用于在进行对象序列化时忽略不必要的属性,达到对象瘦身的目的
writeObject(Object obj)
字符流
Writer
OutputStreamWriter(OutputStream out, Charset cs)
6*write(String str)
BufferedWriter(Writer out)
PrintWriter(String fileName,boolean autoFlush)
按行写,自动行刷新。 BW OSW FOS //文件输出流(字节流,低级流):负责将字节写入到文件中 FileOutputStream fos = new FileOutputStream("pw.txt"); //转换输出流(字符流,高级流):1负责衔接字节与字符流 2负责将写出的字符转换为字节 OutputStreamWriter osw = new OutputStreamWriter(fos); //缓冲字符输出流(字符流,高级流):负责块写文本数据加速 BufferedWriter bw = new BufferedWriter(osw); //PrintWriter(字符流,高级流):1按行写出字符串 2自动行刷新 PrintWriter pw = new PrintWriter(bw); 这里可以按照指定的字符集写出字符串到文本文件中。但是字符集只能以字符串形式 表达。因此注意拼写。字符集不区分大小写。 但是如果字符集名字拼写错误,会抛出异常: UnsupportedEncodingException 不支持的 字符集 异常 如果实例化PW时第一个参数传入的是一个流,则此时可以再传入一个boolean型的参数,此值为true时 就打开了自动行刷新功能。 即: 每当我们用PW的println方法写出一行字符串后会自动flush
println(String x)
java.net
需要注意的几个点: 1:当客户端不再与服务端通讯时,需要调用socket.close()断开链接,此时会发送断开链接的信号给服务 端。这时服务端的br.readLine()方法会返回null,表示客户端断开了链接。 2:当客户端链接后不输入信息发送给服务端时,服务端的br.readLine()方法是出于阻塞状态的,直到读 取了一行来自客户端发送的字符串。 多客户端链接 之前只有第一个连接的客户端可以与服务端说话。 原因: 服务端只调用过一次accept方法,因此只有第一个客户端链接时服务端接受了链接并返回了Socket,此时 可以与其交互。 而第二个客户端建立链接时,由于服务端没有再次调用accept,因此无法与其交互。
Socket(InetAddress address, int port)
Socket(套接字)封装了TCP协议的通讯细节,是的我们使用它可以与服务端建立网络链接,并通过 它获取两个流(一个输入一个输出),然后使用这两个流的读写操作完成与服务端的数据交互我们可以把Socket想象成是一个电话,电话有一个听筒(输入流),一个麦克 风(输出流),通过它们就可以与对方交流了。 实例化Socket时要传入两个参数 参数1:服务端的地址信息 可以是IP地址,如果链接本机可以写"localhost" 参数2:服务端开启的服务端口 我们通过IP找到网络上的服务端计算机,通过端口链接运行在该机器上 的服务端应用程序。 实例化的过程就是链接的过程,如果链接失败会抛出异常: java.net.ConnectException: Connection refused: connect win+R cmd 输入:ipconfig,查询网络ip
InputStream getInputStream()
通过该方法获取的字节输入流读取的是远端计算机发送过来的数据。
OutputStream getOutputStream()
该方法会获取一个字节输出流,通过这个输出流写出的字节数据会通过网络发送给对方。
ServerSocket(int port)
ServerSocket运行在服务端,作用有两个: 1:向系统申请服务端口,客户端的Socket就是通过这个端口与服务端建立连接的。 2:监听服务端口,一旦一个客户端通过该端口建立连接则会自动创建一个Socket,并通过该Socket与客 户端进行数据交互。 如果我们把Socket比喻为电话,那么ServerSocket相当于是某客服中心的总机。 创建ServeSocket时要指定服务端口,该端口不能与服务端所在 计算机上其它应用程序开启的端口重复,否则会抛出异常: java.net.BindException:address already in use 解决办法: 1:更换端口,直到可用 2:杀死使用该端口的进程(程序),仅在咱们重复启动服务端导致时使用
accept()
这个方法是一个阻塞方法,调用后方法"卡住",此时开始等待客户端的链接,直到一个客户端链接,此时该方法会立即返回一个Socket实例 通过这个Socket就可以与客户端进行交互了。 可以理解为此操作是接电话,电话没响时就一直等。
Socket
URLDecoder
decode(String s, String enc)
static String 转换编码方式
URL
Internet上的每一个网页都具有一个唯一的名称标识,通常称之为URL(Uniform Resource Locator, 统一资源定位器)。它是www的统一资源定位标志,简单地说URL就是web地址,俗称“网址”。
toURI()
URI
URI,统一资源标志符(Uniform Resource Identifier, URI),表示的是web上每一种可用的资源,如 HTML文档、图像、视频片段、程序等都由一个URI进行标识的。
客服
Server
创建服务器,申请端口
等待插口socket
并发解决聊天室问题

ClientHandler
实现服务端发送消息给客户端
服务端转发消息给所有客户端

服务端解决多线程并发安全问题
Client
客户端解决收发消息的冲突问题
ServerHandler
Maven(手动搭建SpringBoot)
Maven是一个项目管理工具,它包含了一个对象模型。 配置在.m2文件夹下 一组标准集合,一个依赖管理系统。和用来运行定义在生命周期阶段中插件目标和逻辑。 核心功能 Maven的核心功能是合理叙述项目间的依赖关系,通俗点 就是通过pom.xml文件的配置获取jar包不用手动的去添加jar包。 maven项目,就是在java项目和web项目上裹了一层maven,本质上java项目还是java项目,web项目还是web项目,但是包裹了maven之后,就可以使用maven提供的一些功能,即通过pom.xml添加jar包
pom.xml文件中添加大量的依赖信息 还需要有对应的配置文件, 在配置文件中要书写大量的配置代码
Appliaction
框架竣工
1.单独提炼出框架部分
1.1 定义注解,利用反射改写代码,使框架可以访问到加上注解的类、方法等。
1.2 将最终版本独立为一个项目。
1.3 将main方法,改名为run方法,并加入一个参数Class primarySourse(使用该框架时定义的类)。
启动类前移
2.打包
Maven-项目名-lifectcle-install 保存于C:/用户/用户名/.m2/repository/com/tedu/项目名
3.框架使用
3.1 Maven 文件中添加要使用的框架 依赖 id
若没有包,点击刷新下载。
3.2 写下网络应用主类MyWebApplication
在main方法中调用BirdBootApplication.run(MyWebApplication.class,args);
自此,即可使用这个自己写的框架。
java.nio.charset.
StandardCharsets
US_ASCII
ISO_8859_1
UTF_8
UTF_16BE
UTF_16LE
UTF_16
java.time
ZonedDateTime
LocalDateTime
当前时区的日期时间
static LocalDateTime now()
当前时间
LocalDateTime minusMonths(long months)
减几月
java.math
BigDecimal
字符串整数运算(非小数数值)
小数运算
javax.activation
MimetypesFileTypeMap
String getContentType(File f)
javax.annotation.
PostConstruct
拦截器:被其标记的方法,可以在初始化前给对象赋值
sun.misc
Unsafe
直接访问内存的乐观锁
命令、编译等工具
文本编辑工具
eclipse
idea
包 package com.groupname.projectname(家)
根包名:小写,层次结构 cn.cj.game
访问控制修饰符
private < (defult) < protected < public 本类 同包类 同包+继承类 所有类
默认同包访问
private修饰后只允许本类中访问
protect修饰后允许其子类中访问
public修饰后在所有类中都可以访问
final 变量/方法/类
最终的 无法改变、重写、继承
变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改; 如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。
方法无法被重写
类,无法被继承,final类中的所有成员方法都会被隐式地指定为final方法。
接口 interface 接口名
接口名:大驼峰命名法 AirPlane 部分共有 由interface定义,是一种引用数据类型 接口没有构造器,不能被实例化,即不能new对象,可使用向上造型。 需要被继承/实现,接受类必须重写方法。 多接口,逗号分隔。 若又继承又实现时,应先继承后实现,接口可以继承接口
默认public常量
(public final)int OK = 200;
默认public抽象方法
(public abstract) int insert();
default 修饰的方法有方法体
default void addCorsMappings(CorsRegistry registry) { }
java8之后的版本可以定义静态方法,包含方法体
enum 枚举
常量穷举
OK(200), FAIL()
对应的值是相对有限的,是可以穷举的,则可以使用枚举来解决问题!
相当与静态的方法可以调用构造器但没有方法体,全大写命名,枚举之间有逗号分隔
需要声明常量属性的类型,并生成get方法;同时还需要根据类型生成有参构造器。
@interface 注解
int value() default 1;
在写注解的时候value=可以不写
abstract 类
抽象类:
abstract修饰,包含抽象方法的类必须是抽象类,不能被实例化
抽象类是需要被继承的,派生类:重写所有抽象方法---------变不完整为完整
抽象方法:
abstract修饰,只有方法的定义,没有具体的实现(连{}都没有) 意味着画对象的行为为共有行为。
意义:代码复用、向上造型、可以包含抽象方法,为所有派生类提供统一的入口(能点出来),强制必须重写
类 class 类名
类名:大驼峰命名法 AirPlane 一类个体:封装对象的属性和行为
超类/父类
共有的属性和行为
继承类/子类
特有的属性和行为 访问权限要高于父类
super
super可以理解为父类的,super可以用来调用属性、方法、构造器
1.super.变量名
2.super.方法名
3.super()
this
哪个对象调用就是哪个对象的引用类型
1.this.data; //访问属性
2.this.func(); //访问方法
3.this(); //调用本类中其他构造方法
transient 属性,用于在进行对象序列化时忽略不必要的属性,达到对象瘦身的目的
volatile 可以禁止 JVM 的指令重排
(挎包)导包中的类库
import 包名.类名
对象的属性
(对象的属性)数据类型
8种基本数据类型(整数默认int, 浮点数默认double, boolean默认false)
boolean:布尔型,存储true或false,占用1个字节
byte:字节型,用于存储整数的,占用1个字节,范围-128到127 2^7
0000 0000
1为负
0为正
short:短整型,用于存储整数的,占用2个字节,范围-32,768到32,767 2^15
char:字符型,采用Unicode字符编码格式,存储单个字符,占用2个字节
0000 0000 0000 0000
int:最常用的整型,用于存储整数的,占用4个字节,范围-2^31到2^31-1
long:长整型,用于存储较大的整数,占用8个字节,范围-2^63到2^63-1
float:单精度浮点数,用于存储小数的,占用4个字节,精度为6~7位 不能表示精确的值
double:双精度浮点数,最常用的存储小数的类型,占用8个字节,精度为15~16位 不能表示精确的值
数据类型转换
强制转换 (目标类型)
(小到大)自动转换
引用数据类型
数组
[ ]
[ ][ ]
封装类
变量/引用
封装数据
变量名/标识符:小驼峰命名法
字母(严格区分大小写)
数字(不能在第一位)
_
$
局部变量(可以与成员变量同名、重名就近原则)
运算符(栈)
算数
+ , - , * ,/ ,% ,++ ,--
+ 字符串连接 以后出现的数据自动转换为字符串类型
<< 左移
2<<3
>> 右移
~ 取反
结果为boolean类型
关系
==, >, <
逻辑
短路与 && 短路或 ||
逻辑与 & 逻辑或 |
逻辑非 !
^ 异或
条件
boolean? 结果1(true) : 结果2(false)
赋值(栈——堆)
=
栈
类名(){ }
本类的构造器 用来创建对象,以在其他类中调用其属性和方法。
对象 new Boy()
由实例变量组成的单个个体 创建几个就有几个 每创建一个实例, Java 虚拟机就会为实例变量分配一次内存。
向上造型 小 Object o = new Person()
超类型的引用指向派生类对象
只能访问超类或接口的属性和方法
由于使用的是派生类的构造器,实现的是派生类的属性和方法
造型的类型有:超类、接口。
内部类 对于抽象超类或接口(需要重写方法)或者想要重写超类某个方法时, Object o = new Object(){ 内部类 }
类中套类,外面的称为外部类,里面的称为内部类 内部类只服务于外部类,对外不具备可见性 内部类对象通常在外部类中创建 内部类中可以直接访问外部类的成员(包括私有的), 内部类中有一个隐式的引用指向了创建它的外部类对象--------外部类名.this
1. 成员内部类:应用率不高
何时用:若一个类A只被另一个类B使用,还想访问B中的成员,可以设计为成员内部类,访问更加方便
2. 匿名内部类:应用率高,不定义类名,只需要编写属性和方法即可。 何时用:若想创建一个类(派生类)的对象,并且对象只被创建一次,可以设计为匿名内部类 匿名内部类中不能修改外面局部变量的值,因为在此处该变量会默认为final的
第一种线程创建方式
集合排序器
集合遍历器
lambda表达式
JDK8之后推出的一个新特性:lambda表达式 初衷:可以面向函数式编程 Thread t1 = new Thread(){ public void run(){ for(int i=0;i<10000;i++){ System.out.println("min"); } } }; 直观感受:可以用更简洁的语法创建匿名内部类 lambda表达式是编译器认可的,最终会将其改为内部类编译到class文件中 当要写的内部类中只有一个方法可以替换为使用lambda表达式创建。 直观的表现为,只保留参数和方法体 语法: (o1, o2)->{ 方法体 } 1.参数只有一个时,可省略() 2.如果方法体只有一行代码,那么方法体的"{}也"可以忽略不写 如果该行代码含有return关键字时,那么在忽略{}的同时也要 一同忽略return关键字
向下转型 大 Boy b = (Boy)new Person()
强转
父子判断 小范围 instanceof 大范围
成功的条件
1.对象为该类型
2.继承了该类或实现了接口
堆
(对象的行为 方法区)方法
封装具体的逻辑功能的实现 方法名:小驼峰命名法 addNew
静态变量
属于类级别
Java 虚拟机只为静态变量分配一次内存,加载类过程中完成静态变量的内存分配。 全局唯一,内存中唯一,静态变量可以唯一标识某些状态。
在类的内部,可以在任何方法内直接访问静态变量
其他类中可以通过类名访问该类中的静态变量,无需创建本类对象即可直接使用,使用方便。
静态块
static{ }
为静态变量赋值
静态方法
同静态变量
不属于类的对象。
静态方法中,不能使用 this 关键字,也不能直接访问所属类的实例变量和实例方法;
静态方法中,可以直接访问所属类的静态变量和静态方法。
同this 关键字,super 关键字也与类的实例相关,静态方法中不能使用 super 关键字。
构造方法/构造器
public 类名 (){ }
无返回值类型,方法名与类同名(默认存在)
普通方法
修饰词 返回值类型 方法名(参数列表){ 方法体; }
局部变量
形参(parameter):
全称为"形式参数" ,由于它不是实际存在变量,所以又称虚拟变量。
实参(argument):
全称为"实际参数",是在调用时传递给函数的参数。 实参可以是常量、变量、表达式、函数等, 无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值, 以便把这些值传送给形参。
native
native方法在JVM中运行时数据区也和其它方法不一样,它有专门的本地方法栈。native方法主要用于加载文件和动态链接库,由于Java语言无法访问操作系统底层信息(比如:底层硬件设备等),这时候就需要借助C语言来完成了。被native修饰的方法可以被C语言重写。
结构
顺序结构(程序运行的顺序:由上到下,由左到右)
分支结构
swith(int data){ case1: default: }
switch可以作用于哪几种数据类型?
支持:int、枚举
btye、 stort、char
long转为int是会造成精度损失,所以不支持
jdk1.7 String
if(boolean){ 语句块1 }else if(boolean){ 语句块2 }else{ }
break;
跳出一个循环
contiune;
跳过下面的步骤
谨慎使用 不够优雅
循环结构
声明 循环条件 变化 注意:变量依照变化顺序依次列出
for(;;){ 循环体 }
while(boolean){ }
do{ }while()
return 数据/(null);
返回值为void时,方法直接返回。
成员变量
实例变量 静态变量
实例变量
方法区
侵入性
侵入性:
当我们使用某个类提供的方法时,
该方法反过来要求我们为其修改其他额外地方的代码,此为侵入性.
若某方法具有侵入性,则不推荐使用
关键字(小写)
前中后功能
继承 extends
特点:类单继承 多接口实现 传递性 作用:代码复用。
(跨类)创建对象及赋值
注释
//
/* */
/** */
文档注释
类
方法
常量
接口实现 implements
多实现
逗号分隔
方法的重载
参数列表
方法的重写
设计规则
将所有派生类所共有的属性和行为,抽到超类中-------------抽共性 若派生类的行为/代码都一样,设计为普通方法 若派生类的行为/代码都不一样,设计为抽象方法 将部分派生类所共有的属性和行为,抽到接口中 接口是对继承的单根性的扩展-------------------------------------实现多继承 接口相当于制定了一个标准、规范 实现了接口,意味着就能干那个事,不实现接口,就干不了那个事
面向对象
以对象为核心来思考问题、解决问题的一种设计思想, 将我们的程序模块化,从而严格定义类型。
三大属性
封装
类:封装的是对象的属性和行为
方法:封装的是具体的业务逻辑功能实现
访问控制修饰符:封装的是具体的访问权限
继承
作用:代码复用 特点:单一继承、多接口实现,具有传递性
超类/父类:所有派生类所共有的属性和行为
接口:部分派生类所共有的属性和行为
派生类/实现类/子类:派生类所特有的属性和行为
多态
多态:多种形态 意义:对象多态(所有对象都是多态的) 行为多态(所有抽象方法都是多态的)
对象多态:
向上造型:
超类型的引用指向派生类的对象(前面是超类型,后面是派生类型)
能点出来什么,看引用的类型
能向上造型成为的类型有:超类+所实现的接口
强制类型转换/向下转型,成功的条件只有如下两种:
强转若不满足如上条件,则发生ClassCastException类型转换异常 建议:在强转之前先通过instanceof来判断引用指向的对象是否是该类型
引用所指向的对象,就是该类型
引用所指向的对象,实现了该接口或继承了该类型
行为多态:方法重写
行业标准
变量私有化,行为公开化
数据私有,设计getter/setter的原因:
1.很多框架都是基于getter/setter来配置获取数据的,可以理解为它是一种行为的标准
2.getter/setter时可以对数据进行控制,而public权限时无法对数据进行控制
getter: getX()
alt+insert 在下面生成 非核心代码
int
setter: setY(int data)
准备静态素材(图片、视频、音频)、get获取、add画上
调错
https://www.csdn.net/?spm=1001.2101.3001.4476
注释测功能,锁定位置
行号之前,打断点(红点)
进入debug调错模式
下一步
进入方法
出方法
回退
打桩 System.out.println( );
完成某项功能
先查阅是否有参考,或已有API
无参考的话只能自己试错创造了
做新功能的套路
先写行为/方法:
若为某个对象所特有的行为,就将方法设计在特定的类中
若为所有对象所共有的行为,就将方法设计在超类中
窗口调用:
若为定时发生的,则在定时器run中调用
若为事件触发的,则在侦听器中调用
项目版本迭代
Module 模块
大版本号1.0.0
小版本号1.1.0
修正版本号1.1.1
1.2
...
2.0.0
3.0.0
...
大版本更改 应用场景
有重大框架改动
有重大bug要处理
小版本版本更改 应用场景
添加某项重要功能
bug太多
修正版本更改 应用场景
添加某项功能
修复某些bug
搬家原则
项目包下(除启动类之外)的文件
资源文件
源码
低耦合、高内聚
JAVA23种设计模式
创建型
用于创建对象, 为设计类实例化新对象提供指南
单例模式(Singleton) public class Singleton{ }
单态类:确保一个类只有一个实例,并提供一个访问它的全局访问点。
1. 私有化构造器,杜绝外界通过new来创建实例 private Singleton { }
2. 定义一个私有的,静态的当前类属性并初始化(这个过程等于就实例化了一次对象) private static Singleton instance = new Singleton();
private static Singleton instance = null;
不实例化实现懒汉模式
private volatile static Singleton uniqueInstance;
volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。 1. 为 uniqueInstance 分配内存空间 2. 初始化 uniqueInstance 3. 将 uniqueInstance 指向分配的内存地址 但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2。
3. 定义一个公开的静态方法,可以获取当前类实例(外界通过该方法始终拿到同一个对象) public static Singleton getInstance(){ return instance; }
public static synchronized Singleton getInstance(){ if(instance == null){ instance = new Singleton(); return instance; } }
调用此方法时才会生成对象 !!!需要注意的是, 此方法存在并发安全问题导致生成的对象并非单例, 因此需要加锁。
工厂模式
产品接口
产品接口实现类
工厂类
工厂方法(Factory Method) public class Factory{ public static Sample creator(int which){ //getClass 产生 Sample 一般可使用动态类装载装入类。 if (which==1) return new SampleA(); else if (which==2) return new SampleB(); } }
定义了创建对象的接口,让子类决定实例化哪个类。
抽象工厂(Abstract Factory) public abstract class Factory{ public abstract Sample creator(); public abstract Sample2 creator(String name); }
public class SimpleFactory extends Factory{ public Sample creator(){ ......... return new SampleA } public Sample2 creator(String name){ ......... return new Sample2A } }
public class BombFactory extends Factory{ public Sample creator(){ ...... return new SampleB } public Sample2 creator(String name){ ...... return new Sample2B } }
构建器(Builder)
把复杂对象的创建和部件的创建分别开来, 分别用 Builder 类和 Director 类来表示
public interface Builder { //创建部件 A 比如创建汽车车轮 void buildPartA(); //创建部件 B 比如创建汽车方向盘 void buildPartB(); //创建部件 C 比如创建汽车发动机 void buildPartC(); //返回最后组装成品结果 (返回最后装配好的汽车) //成品的组装过程不在这里进行,而是转移到下面的 Director 类 中进行. //从而实现了解耦过程和部件 Product getResult(); }
创建一个个部件
public class ConcreteBuilder implements Builder { Part partA, partB, partC; public void buildPartA() { //这里是具体如何构建 partA 的代码 }; public void buildPartB() { //这里是具体如何构建 partB 的代码 }; public void buildPartC() { //这里是具体如何构建 partB 的代码 }; public Product getResult() { //返回最后组装成品结果 }; }
public interface Part { }
public class Director { private Builder builder; public Director( Builder builder ) { this.builder = builder; } // 将部件 partA partB partC 最后组成复杂对象 //这里是将车轮 方向盘和发动机组装成汽车的过程 public void construct() { builder.buildPartA(); builder.buildPartB(); builder.buildPartC(); } }
构建最后的复杂对象
public interface Product { }
原型(Prototype)
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象.
public abstract class AbstractSpoon implements Cloneable { String spoonName; public void setSpoonName(String spoonName) {this.spoonName = spoonName;} public String getSpoonName() {return this.spoonName;} public Object clone() { Object object = null; try { object = super.clone(); } catch (CloneNotSupportedException exception) { System.err.println("AbstractSpoon is not Cloneable"); } return object; } }
public class SoupSpoon extends AbstractSpoon { public SoupSpoon() { setSpoonName("Soup Spoon"); } }
结构型
用于处理类或对象的组合, 对类如何设计以形成更大的结构提供指南
适配器(Adapter)
转化接口
将一个类的接口转换成希望的另外一个接口,是的原本不兼容的接口可以协同工作。
public abstract class Coffee { CoffeeImp coffeeImp; public void setCoffeeImp() { this.CoffeeImp = CoffeeImpSingleton.getTheCoffeImp(); } public CoffeeImp getCoffeeImp() {return this.CoffeeImp;} public abstract void pourCoffee(); }
public abstract class CoffeeImp { public abstract void pourCoffeeImp(); }
桥接(Bridge)
继承树拆分
将类的抽象部分与它的实现分离,使它们都可以独立地变化。
组合(Composite)
树形目录结构
将对象组合成树形结构以表示“部分-整体”的层次感,同时对单个对象和组合对象的使用保持一致。
装饰(Decorator)
动态附加职责
动态地给对象添加一些额外的职责。
外观(Facade)
为子系统中的一组接口提供一个统一的接口
享元(Flyeweight)
运用共享对象有效地支持大量细粒度的对象 避免大量拥有相同内容的小类的开销(如耗费内存),使大家共享一个类(元类).。 汉字编码
public interface Flyweight { public void operation( ExtrinsicState state ); }
public class ConcreteFlyweight implements Flyweight { private IntrinsicState state; public void operation( ExtrinsicState state ) { //具体操作 } }
ConcreteFlyweight 必须是可共享的,它保存的任何状态都必须是内部(intrinsic), 也就是说,ConcreteFlyweight 必须和它的应用环境场合无关.
//用于本模式的抽象数据类型(自行设计) public interface ExtrinsicState { }
public class FlyweightFactory { //Flyweight pool private Hashtable flyweights = new Hashtable(); public Flyweight getFlyweight( Object key ) { Flyweight flyweight = (Flyweight) flyweights.get(key); if( flyweight == null ) { //产生新的 ConcreteFlyweight flyweight = new ConcreteFlyweight(); flyweights.put( key, flyweight ); } return flyweight; } }
Flyweight factory 负责维护一个 Flyweight 池(存放内部状态),当客户端请求 一个共享 Flyweight 时,这个 factory 首先搜索池中是否已经有可适用的,如果 有,factory 只是简单返回送出这个对象,否则,创建一个新的对象,加入到池中,再返回送 出这个对象.池
代理(Proxy)
快捷方式
为控制对象的访问而提供的代理对象
行为型
描述类或对象的交互以及职责的分配, 并对其提供指南。
职责链(Chian of Responsibility)
传递职责
讲对象连成一条链,并沿着这条链传递请求,知道有对象处理它。
命令(Command)
日志记录,可撤销
将请求封装为对象,可保存、传递命令支持可撤销的操作。
解释器(Interpreter)
虚拟机的机制
可以解释自定义语法表示的解释器。
迭代器(Iterator)
数据集
提供一个方法可以顺序访问集合中的各种元素,又不需要暴露该集合的内部表示。
中介者(Mediator)
不直接引用
用一个中介对象封装一系列的对象交互。
备忘录(Memento)
游戏存档
捕获一个对象的内部状态,并在对象之外保存这个状态
观察者(Observer)
订阅、广播、联动
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。
状态(State)
状态变成类
允许对象再起内部状态改变时改变它的行为。
策略(Strategy)
多方案切换
定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。
模板方法(Template Method)
框架
定义一个操作中的算法骨架,使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤
访问者(Visitor)
数据与操作分离
表示一个作用于某对象结构中的各元素的操作,使你可以再不改变各元素的前提下定义作用于这些元素的新操作。
maven
maven仓库
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5LiN5Lya56eD5aS055qE5bCP6b2Q,size_20,color_FFFFFF,t_70,g_se,x_16
23种设计模式
问题集
问题及新功能实现
发布微博时如何插入时间和发布者的id
Weibo w = new Weibo(); //将用户输入的信息添加到实体类 BeanUtils.copyProperties(weibo,w); w.setUser_id(u.getId()); w.setCreated(new Date()); mapper.insert(w);
null和""的区别
多个图片路径没保存到数据库
“/xxx?url=”+
weibo_id 没保存到数据库 详情页id显示异常
+location.search
不明系统错误 bean?
UnsatisfiedDependencyException
ConfigServletWebServerApplicationContext
controller交互逻辑不符合spring标准
主题
主题
框架
Lombok框架
pojo类 用于 在编译期自动生成 相关代码的, 例如:Setters & Getters、hashCode()、equals()、toString()、 无参数构造方法、全参数构造方法等。
<!-- Lombok的依赖项,主要用于简化POJO类的编写 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.20</version> <scope>provided</scope> </dependency>
问题:IntelliJ IDEA等开发工具并不能直接识别出这些方法,在编码时无法给出提示,甚至,直接写出相关代码还会报错,但是,由于执行的是编译后的.class文件,所以并不影响运行。
原因:由于lombok是在编译期生成相关代码,在源代码是没有例如Setters、Getters等方法的。
解决方案:为了使得IntelliJ IDEA能提示相关代码,并且不报错,需要在开发工具中安装lombok插件。
lombok.
@Data:添加在类上,用于生成Setters & Getters、hashCode()、equals()、toString(),使用此注解时,必须保证当前类的父类存在无参数构造方法
@Setter:添加在属性上,用于生成此属性的Setter方法,或者,添加在类上,用于生成此类中所有属性的Setter方法
@Getter:添加在属性上,用于生成此属性的Getter方法,或者,添加在类上,用于生成此类中所有属性的Getter方法
@EqualsAndHashCode:添加在类上,用于生成基于此类中所有属性的hashCode()和equals()方法,生成的方法将保证:如果2个对象的类型相同,且所有属性值相同,则hashCode()返回相同的结果,且equals()对比结果为true,否则,hashCode()返回不同的结果,且equals()对比结果为false
@ToString:添加在类上,用于生成基于此类中所有属性的toString()方法
概要
@NoArgConstructor:添加在类上,用于生成无参数构造方法
@AllArgsConstructor:添加在类上,用于生成基于此类中所有属性的全参数构造方法
extern.slf4j.
@Slf4j:SLF4j日志
类上添加@Slf4j注解,则lombok框架会在编译期在类中声明名为log的变量,通过此变量可以调用输出日志的方法。
spring-boot-starter的项目中,已经包含了SLF4j日志的相关依赖项。
application.properties
# 日志的显示级别 logging.level.cn.tedu.csmall=trace
log
trace:跟踪信息
debug:调试信息
info:一般信息
默认的日志显示级别
warn:警告信息
error:错误信息
字符串中使用{}作为占位符,表示某个变量的值, 然后,从第2个参数开始,依次传入各占位符对应的值。
在测试类中,测试类必须添加@SpringBootTest注解,才会识别application.properties中的配置,并且,如果没有添加此注解,日志的默认显示级别是debug。
experimental.
@Accessors(chain = true)
把对象作为返回值,使方法可以链式调用
Validation框架
服务端检查请求参数的基本有效性 所有检查注解都可以配置检查不通过后的提示文本
@NotNull(message = "必须提交相册名称")
spring-boot-starter-validation
子主题
org.springframework.validation.
BindException
约束
FieldError getFieldError()
String getDefaultMessage()
FieldError getFieldErrors()
String getDefaultMessage()
获取检查不通过后的提示文本
javax.validation.
ConstraintViolationException
违反限制
Set<ConstraintViolation<?>> getConstraintViolations()
ConstraintViolation<T>
String getMessage()
获取检查不通过后的提示文本
Valid
Validated
检查一个对象(对象里的属性需要加注解)
表示此参数是需要通过Validation框架进行检查的!
constraints
NotNull
NotEmpty
Validator
org.hibernate.validator.constraints
Range
@Range(max = 255, min = 0, message = "添加相册失败,排序序号必须是0~255之间的值!")
限制属性的值
服务器端的检查是必要的
所有由客户端提交过来的请求参数都应该视为“不可靠的”! 以上列举的“服务器端不信任客户端提交的请求参数”的原因都是小概率事件
客户端程序是运行在用户的设备上的,是可能被篡改的
某些项目可能是多客户端的(既有网页端,又有手机端等其它客户端),可能不统一
某些用户没有及时更新客户端软件的版本,旧版的客户端软件的检查规则与新版服务器的规则并不对应
客户端仍有必要对即将提交的请求参数进行检查
客户端的程序都是在用户的设备上离线运行的,能够更快的响应用户的操作,在客户端的检查对于用户的体验来说是更好的
可以阻止绝大部分错误的请求提交到服务器,能够缓解服务器的压力
检查
ERR_BAD_REQUEST
检查POJO类型的请求参数
@Valid
@Validated
表示此参数是需要通过Validation框架进行检查的!
此参数的属性上添加检查注解
无法通过检查规则 400
GolbolExceptionHander
@ExceptionHandler public JsonResult handleConstraintViolationException(ConstraintViolationException e) { Set<ConstraintViolation<?>> constraintViolations = e.getConstraintViolations(); String delimiter = ","; StringJoiner stringJoiner = new StringJoiner(delimiter); for (ConstraintViolation<?> constraintViolation : constraintViolations) { stringJoiner.add(constraintViolation.getMessage()); } return JsonResult.fail(ServiceCode.ERR_BAD_REQUEST, stringJoiner.toString()); }
关于响应的消息文本
@NotEmpty(message = "添加相册失败,必须提交相册名称!")
@NotNull(message = "添加相册失败,必须提交相册简介!")
在处理异常时,应该将此处配置的文本响应到客户端去,可以通过异常对象获取以上文本!
检查未封装的请求参数
无法通过检查规则 500
首先,需要在当前Controller类上添加@Validated注解
在请求参数上添加检查注解
@Range(max = 255, min = 0, message = "添加相册失败,排序序号必须是0~255之间的值!")
configration
ValidationConfiguration
Validation框架有快速失败的机制, 默认是未开启的,当客户端提交的请求参数有多种错误时,会进行全部的检查,发现所有错误!如果开启了快速失败,当检查出第1个错误时,就会停止检查!
需要创建Valiator类型的对象,通过此对象进行配置,并且,此对象必须被保存在Spring容器中,框架会自动应用它!
@Bean public javax.validation.Validator validator() { return Validation.byProvider(HibernateValidator.class) .configure() // 开始配置 .failFast(true) // 配置快速失败 .buildValidatorFactory() // 构建Validator工厂 .getValidator(); // 从Validator工厂中获取Validator对象 }
Knife4j在线API文档
一款基于Swagger 2的在线API文档框架。
<!-- Knife4j Spring Boot:在线API --> <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-spring-boot-starter</artifactId> <version>2.0.9</version> </dependency>
knife4j.enable=true
开启此框架的增强模式(Knife4j定义的概念)
Knife4jConfiguration
注意:务必检查以上配置类中的basePackage属性的值,必须是当前项目中控制器类所在的包!
启动项目,打开浏览器,通过 /doc.html 即可访问在线API文档。
配置API文档信息
@ApiOperation("删除类别") @ApiOperationSupport(order = 200) @ApiImplicitParams({ @ApiImplicitParam(name = "id", value = "类别ID", required = true, dataType = "long"), @ApiImplicitParam(name = "userId", value = "用户ID", required = true, dataType = "long") }) @PostMapping("/delete") public void delete(Long userId, Long id) {}
@Api:添加在控制器类上
此注解的tags属性,可配置模块名称,可以在模块名称中使用编号,例如:01. 品牌管理模块,最终将按照编号升序排列是@ApiImplicitParam注解的数组类型,当需要使用
@ApiOperationSupport:添加在控制器类中处理请求的方法上
此注解的order属性(int类型),可配置业务的排序序号,最终将升序排列
@ApiModelProperty:添加在POJO类型的属性上
此注解的value属性,可配置参数名称(说明)
此注解的required属性,可配置是否必须提交此参数
此注解的example属性,可配置此请求参数的示例值
@ApiImplicitParam:添加在控制器类中处理请求的方法上,用于对未封装的请求参数添加说明(例如Long id参数)
必须配置此注解的name属性,取值为方法的参数名称,表示当前注解对哪个参数进行说明
此注解的value属性,可配置参数名称(说明)
此注解的required属性,可配置是否必须提交此参数
此注解的dataType属性,可配置参数的数据类型(例如取值为"long")
此注解的example属性,可配置此请求参数的示例值
@ApiImplicitParams:添加在控制器类中处理请求的方法上,此注解的value属性
@ApiImplicitParam对多个未封装参数进行说明时,需要将多个@ApiImplicitParam注解的配置作为当前注解的参数
MyBatis 框架(持久层)
只需要通过注解或xml配置文件的方式提供好需要执行的SQL语句, 框架会自动根据SQL语句以及一些相关指令生成对应的JDBC代码. 是当前主流的数据库连接框架。 属于ORM框架, Object Relational Mapping 对象关系映射, 指Java对象和数据库中表的关系, Mybatis框架就是通过两者之间的关系生成的JDBC代码, 使用Mybatis框架需要定义好Mapper映射接口, 在接口中定义两者之间的关系
mysql driver
<!-- MySQL的依赖项 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency>
<!-- scope标签:配置此依赖项的作用域 -->
<!-- scope值为test:作用域为“测试”,将不参与项目的打包、部署等 -->
<!-- scope值为runtime:作用域为“运行时”,即编写源代码时并不需要使用,但运行时需要 -->
<!-- scope值为provided:表示此依赖并不具备传递性,且需要由环境提供此依赖项 -->
mybatis framework
勾选了Mybatis Framework框架, 需要在application.properties里面配置连接数据库的信息
<!-- Mybatis整合Spring Boot的依赖项 --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.2</version> </dependency>
添加了数据库编程的依赖,就会自动读取连接数据库的配置
还需要完成首次使用Mybatis时的一次性配置 ,主要有2处:
1. 使得Mybatis明确Mapper接口的位置 【推荐】在根包下,且添加了@Configuration的类,就是配置类。 在配置类上使用@MapperScan配置Mapper接口的根包。 【不推荐】在各Mapper接口上添加@Mapper注解 2. 使得Mybatis明确配置SQL语句的XML文件的位置
mybatis: mapper-locations: - classpath:mapper/*.xml
设置配置文件的位置
方式1:
在注解参数上添加sql语句
org.apache.ibatis
annotations
@interface Mapper
@Mapper 类注解 设置当前接口为映射接口,供MyBatis框架生成JDBC代码,依据为在接口中定义的方法和书写的SQL语句。
@interface Insert?增
@Insert("insert into product values(null,#{title},#{price},#{number})") 方法注解 加数据相关的SQL语句#{属性名(对应数据库里的字段名)}代替?
@interface Delete 删
@interface Update 改
@interface Select 查
Param("参数名")
形参
在一些非springboot主流的项目中, java代码编译完成后参数名(局部变量)会丢失,此注解会保留参数名
在Spring Boot中,干预了编译过程,保留了参数名称,所以,在配置SQL语句时,可以在#{}中写参数的名称
为了保证代码良好的兼容性,即使使用Spring Boot项目,在Mapper接口中的抽象方法有多个参数时,仍建议使为方法的每个参数添加@Param注解以配置参数名称。
plugin.
Interceptor
拦截器
session
SqlSessionFactory
sql容器
Mybatis的缓存机制
缓存
将数据临时存储在某个位置,可能这个操作并不是必要的,但是,所使用的临时存储的位置,相比原本获取数据的机制要更便于获取数据,所以,从临时位置获取数据的效率会更高一些。
Mybatis框架有2级缓存机制,也就是说,会有2种缓存的做法,分别把数据存储在不同的位置,并且,这2种做法是不同的,分别称之为一级缓存和二级缓存。 无论是一级缓存还是二级缓存,Mybatis的处理流程中最核心的部分都是:执行查询后,将结果缓存下来,如果下次执行相同的查询,将不再连接数据库去查询数据,而是将缓存的结果直接返回! 使用了缓存后,可以避免所有的查询都是向数据库查询数据,从缓存中查询是更加高效的,所以,提高了查询效率,同时,由于减少了数据库的查询量,还在一定程度上保护了数据库,避免数据库因为无法承受高并发而死机!
一级缓存
关于一级缓存,也称之为会话缓存,是Mybatis自动开启的,无法关闭,
并且,必须保证是同一个会话(SqlSession)、同一个Mapper、同一个查询、相同的参数,才可以使用缓存!
并且,当前Mapper执行任何写操作(增、删、改),无论是否真的写了数据(有没有数据发生变化),都会导致Mybatis自动清除缓存中的数据!或者,你也可以显式的调用SqlSession对象的clearCache()方法来手动清除缓存!
二级缓存
Mybatis在处理查询时,会优先尝试从二级缓存中获取结果,如果未命中,则会尝试从一级缓存中获取结果,如果仍未命中,将连接到数据库执行查询。
关于二级缓存,是基于namespace的,也称之为namespace缓存,在Spring Boot项目中,默认是全局开启的,但是各namespace默认是未开启的。相比一级缓存,namespace缓存并不要求是同一个会话的,无论是哪个会话,只要是执行同一个namespace中的同一个查询,且参数相同,就可以使用此前缓存的结果!在配置SQL语句的XML中,添加<cache/>标签即可开启当前namespace的二级缓存!另外,在每个<select>标签上都可以配置useCache属性,以表示当前查询是否启用二级缓存,此属性的默认值为true,所以,一旦在XML中添加了<cache/>标签,当前XML中所有的查询都是启用了二级缓存的,如果部分查询不需要使用二级缓存,你应该显式的在<select>标签上配置useCache="false"以禁用二级缓存!当启用二级缓存时,封装查询结果的类型必须实现序列化接口!与一级缓存相同,只要当前namespace执行了任何写操作,都会清除二级缓存!
方式2:.xml文件
在独立的配置文件中 添加sql语句
核心配置文件
1.1 <configuration>
这是配置文件的根元素, 所有的其他元素都要在这个标签下使用.
1.2 <environments>
用于管理所有的环境, 并可以指定默认使用哪个环境. 通过default 属性来指定.
1.3 <environment>
用于配置环境. id 属性用于唯一标识当前环境
1.4 <transactionManager>
用于配置事务管理器
1.4.1 type 属性
用于指定 MyBatis 采用何种方式管理事务 a) JDBC: 表示 MyBatis 采用与原生 JDBC 一致的方式管理事务
b) MANAGED: 表示将事务管理交给其他容器进行, 例如 Spring
1.5 <DataSource>
用于配置数据源, 设置 MyBatis 是否使用连接池技术, 并且配置数据 库连接的四个参数
1.5.1 type 属性
用于设置 MyBatis 是否使用连接池技术 a) POOLED, 表示采用连接池技术
b) UNPOOLED, 表示每次都会开启和关闭连接, 不使用连接池技术
c) JNDI, 使用其他容器(例如 Spring)提供数据源
1.6 <property>
用于配置数据库连接参数(driver, url, username, password)
1.7 <mappers>
用于扫描 mapper 信息
mapper.xml 动态SQL语句
映射配置文件
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper></mapper>
根元素
nameplace="mapper接口的完全限定名"
匹配其对应的Mapper接口 用于指定命名空间, mybatis 是通过 namespace+id 的方式来定位 SQL 语句的, 所以必须指定namespace. 通常namespace被配置为全限定路 径
<insert></insert>
useGeneratedKeys="true"
开启自增键
keyProperty="id"
指定自增字段
<delete></delete>
<update></update>
<set></set>
生成SET关键字,末端的逗号会删除
<if>字段名=#{ }</if>
test="boolean" 为true才执行,实现部分修改
<select></select>
or
resultType="返回值类型"
用于设定查询返回的数据类型, 要写类型的全限定路径. 如果返回的 是集合类型, 要写集合的泛型的类型.
resultMap="ResultMap" 结果映射
id="方法名"
用于唯一表示 SQL 语句, 类似于方法的方法名
命名不一致处理
方法一:
<resultMap></resultMap>
注意顺序,否则会有交叉
id="ResultMap"
type="VO类的完全限定名"
<id/>
<result/>
配置复杂对象 注意:当存在“非单表”查询时,即使column与property的值相同,也必须配置
column="数据库字段名"
property="类的属性名"
<collection property="permissions" ofType="java.lang.String"> <constructor> <arg column="value"/> </constructor> </collection>
collection标签:用于封装List结果,也可理解为1对多的结果,例如1个管理员有多个权限,则权限需要通过此标签来配置
property属性:取值为封装结果中的类的属性名
ofType属性:取值为List集合属性中的元素类型的全限定名,如果是java.lang包下的类,可以不写包名
子级:配置如何创建出ofType的对象,可以通过constructor标签配置简单的对象如何创建,也可以使用id标签和若干个result标签配置较复杂的对象
方法二:
字段设置别名
缺点是不能复用
方法三:
在配置文件(profile)中加入
mybatis.configuration.map-underscore-to-camel-case=true
将字段名格式设置驼峰命名法格式 如果仅仅是因为命名规范不一致,可以做application.properties里面添加以下配置内容
动态SQL
可以根据具体的参数条件,来对SQL语句进行动态拼接。
<foreach>#{对象. 变量名}</foreach> 循环遍历标签,实现批量操作
collection="list/array"
item="单项对象"
separator="分隔符"
可自行添加分隔符, 末尾不添加
set
if
where
trim
bind
mybatis的动态SQL都是用OGNL表达式进行解析的,如果需要创建OGNL表达式以外的变量,可以用bind标签。
choose(when otherwise)
<sql></sql>
重复使用的SQL语句片段
id="XxxQueryFields"
<include/>
替代重复使用的SQL语句
refid="XxxQueryFields"
占位符
e.g. @Param("usename") String username
#{}
先执行词法分析、语义分析、编译,然后再将参数值代入到编译好的SQL语句中执行,这种做法也称之为预编译
select * from ams_admin where username=#{username}
单参数时内容可随意填写
在SQL语句中,预定义的关键词、预定义的标点符号、数值、布尔值都是可以被正确区分,即MySQL知道它们表示的是什么意义,
select * from ams_admin where username='root'
由于是执行(词法分析)语义分析,再将值代入执行,由于语义在执行之前已经固定,所以,传入的值是不可能改变原SQL语句的语义的!则完全不存在SQL注入的风险!
但是使用也是受限的,它只能用于表示某个值,不可以用于表示SQL语句中的其它片段,
${}
先将参数值替换掉占位符,再执行词法分析、语义分析、编译、执行
select * from ams_admin where username=${username}
其它直接写出来的名称,都会被当成数据库名称、数据表名称、字段名(查询时称之为列名)等其它设计时定义的名称,例如:
select * from ams_admin where username=root
Unknown column 'root' in 'where clause'错误,因为root被当作列名了
要解决以上查询出错的问题,可以在SQL语句的${username}的两端添加单引号,或在传入的参数值的两端添加单引号。
select * from user where ${xx}
由于是先传值,再执行(词法分析)语义分析,所以,传入的值是可能改变原SQL语句的语义的!则存在SQL注入的风险!
${}是不受限制的,只要将值替换掉占位符后的SQL语句是合法的,就可允许的,使用更加灵活!
转义字符
某些字符可能与莫写标签产生冲突, 这时就需要用到转义字符。
&?;
gt : great than
>
lt : less than
<
ge: great equal
>=
le: less equal
<=
ne: not equal
!=
eq: equal
=
config.XxxConfig
org.mybatis.spring.annotation
@interface MapperScan
传入要扫描的接口所在包的完全限定名,扫描mapper
XxxMapper 接口
使用Mybatis框架需要创建一个Mapper接口,此Mapper接口的作用是配置Java对象和数据库表的对应关系, Mybatis框架会根据此对应关系生成JDBC代码,
XxxServiceImpl实现类l
IXxxService接口
规则:暴露出来的一定是接口
XxxController
在Controller类中使用Spring框架的@Autowired注解时, Spring框架和Mybatis框架结合根据Mapper接口创建出一个实现类,并且实例化了该实现类赋值给了mapper变量, 然后mapper则可以直接使用,因为在实现类中已经实例化的接口中的抽象方法, 而实例化的抽象方法中就是JDBC代码.
pojo包
POJO:Plain Ordinary Java Object,简单的Java对象,通常,表现为更关注属性的那些类型,例如实体类。
entity实体类
实体类里边的属性与数据库表中的字段一致
dto(Data Transport Object)数据传输对象
接收页面输入的属性,用于参数
vo(Value Object)值对象
时间属性注解:@JsonFormat(pattern = "yyyy/MM/dd HH:mm:ss" ,timezone = "GMT+8")
作为返回值传递给页面
POJO类的规范: 1. 所有属性应该是私有的 2. 所有属性都应该有对应的Setter方法、Getter方法 3. 应该生成基于所有属性的hashCode()方法、equals()方法 需要保证:如果2个对象的类型相同,且所有属性值相同,则hashCode()返回相同的结果,且equals()对比结果为true,否则,hashCode()返回不同的结果,且equals()对比结果为false 4. 应该实现Serializable接口 可以不关心是否生成序列化版本ID
MyBatisPlus
JPA(Java Persistence API)
1、JPA可移植性好,支持Hibernate方言。 2、提供单表CRUD方法,减少sql语句的编写,开发效率高,。 3、面向对象开发思想,对象化程度更高。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency>
Druid数据库连接池
德鲁伊数据源 阿里提供的连接池.连接池的主要作用: 1.重用连接 ,不会反复的建立和断开连接 2.控制连接数量 使用时可以跳过JDBC的1,2步
<!-- 数据库连接池 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.21</version> </dependency>
1、节省资源,如果每次访问数据库都创建新的连接,创建和销毁都浪费系统资源 2、响应性更好,省去了创建的时间,响应性更好。 3、统一管理数据库连接,避免因为业务的膨胀导致数据库连接的无限增多。 4、便于监控。
com.alibaba.druid.pool
DruidDataSource
setUsername(String username)
setPassword(String password)
setUrl(String jdbcUrl)
setInitialSize(int initialSize)
初始化连接数
setMaxActive(int maxActive)
最大连接数
DruidPooledConnection getConnection()
JDBC (Java DataBase Connectivity: java数据库连接)
JDBC事务
<!-- 连接MySQL数据库的依赖 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.15</version> </dependency>
db.driver=com.mysql.cj.jdbc.Driver db.url=jdbc:mysql://localhost:3306/mysql?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true db.username=root db.password=root db.maxActive=10 db.initialSize=2
com.mysql.cj.jdbc.Driver
java.sql
1st: driver加载驱动: Class.forName("不同数据库厂商提供的Driver类的完全限定名")
SQLException SQL语句编译错误
DriverManager
2nd: connection与数据库建立连接: DriverManager.getConnection()方法需要传入三个参数 1:连接数据库的路径(不同的数据库路径格式不完全相同) URL格式-> jdbc:不同数据库有自己格式的部分/数据库名?参数 2:被连接数据库的用户名 3:被连接数据库的密码
getConnection(String url,String user, String password)
static Connection 该方法返回值为一个Connection的实例
1:连接数据库的路径(不同的数据库路径格式不完全相同)
URL格式-> jdbc:不同数据库有自己格式的部分/数据库名?参数
2:被连接数据库的用户名
3:被连接数据库的密码
Connection 接口
3rd: statment准备执行SQL语句 PrepareStatment("SQL") JDBC中的一个核心接口,用于表示一个与数据库的连接。 不同的数据库厂商在驱动包里提供了对应的实现类
createStatement()
Statement 直接使用的话,拼接的SQL语句SQL注入攻击
防止的方法
(1)永远不要信任用户的输入,要对用户的输入进行校验,可以通过正则表达式,或限制长度,对单引号和双"-"进行转换等。
(2)永远不要使用动态拼装SQL,可以使用参数化的SQL或者直接使用存储过程进行数据查询存取。
(3)永远不要使用管理员权限的数据库连接,为每个应用使用单独的权限有限的数据库连接。
(4)不要把机密信息明文存放,请加密或者hash掉密码和敏感的信息。
(5)应用的异常信息应该给出尽可能少的提示,最好使用自定义的错误信息对原始错误信息进行包装,把异常信息存放在独立的表中。
prepareStatement(String sql) ?
PreparedStatement 执行预编译SQL语句
优点: 1:避免了繁琐的拼接SQL语句 2:避免了被SQL注入的问题 3:大批量执行相同语义的SQL(语义相同,值不同)时性能好 实际开发中我们的CRUD都建议使用预编译SQL来执行
Statement 接口
4th. 调用方法执行SQL语句 5th. 结果处理和判断
execute(String sql)
boolean 执行任意分类的SQL,一般DDL,返回值有无查询结果集
executeUpdate(String sql)
int 专门用来执行DML语句的方法,返回值为执行该SQL后影响了表中多少条记录
executeQuery(String sql)
ResultSet 专门用户来执行DQL语句的方法。返回的ResultSet表示执行该DQL后 产生的查询结果集。通过遍历该结果集得到查询内容
PreparedStatement 预编译SQL接口
setString(int parameterIndex, String x) 从第一个?(0)开始,设置字符串类型的值
ResultSet 结果集接口
next()
boolean 判断是否查询到记录 一般配合while循环使用
getInt(int columnIndex/String columnLabel)
int 获取下标或标签名为?的int类型值
getString(int columnIndex/String columnLabel)
String 获取下标或标签名为?的String类型值
DataSource 数据源接口
Connection getConnection()
获取与数据库的连接对象,会执行连接到数据库的操作
springboot MVC设计
逆向工程(扒软件)
每一步截屏
m(Model 模型)
mappers
.xml SQL语句配置文件
config 配置
pojo包
entity实体类
实体类里边的属性与数据库表中的字段一致
dto(Data Transport Object)数据传输对象
接收页面输入的属性,用于参数
vo(Value Object)值对象
时间属性注解:@JsonFormat(pattern = "yyyy/MM/dd HH:mm:ss" ,timezone = "GMT+8")
作为返回值传递给页面
mapper层
Mapper接口
使用Mybatis时,如果执行的是增、删、改操作,始终使用int作为返回值类型,表示“受影响的行数”
关于方法的名称,阿里巴巴的《Java开发手册》提出了参考:
1) 获取单个对象的方法用 get 做前缀。
2) 获取多个对象的方法用 list 做前缀。
3) 获取统计值的方法用 count 做前缀。
4) 插入的方法用 save/insert 做前缀。
5) 删除的方法用 remove/delete 做前缀。
6) 修改的方法用 update 做前缀。
关于参数列表
该根据SQL语句中的参数来设计,如果需要执行的SQL语句中的参数较多,且具有相关性,应该进行封装,
持久层
service层
Service是项目中用于处理业务逻辑的
这些规则是用于保障数据的有效性、安全性的,使得数据可以随着我们设定的规则而产生或发生变化!
IXxxService接口
关于抽象方法的名称:可以完全自定义
关于抽象方法的参数列表:大多参数是由客户端提交到控制器,再由控制器调用时传递过来的参数,另外,也可能是控制器处理出来的某些数据(例如Session中的当前登录用户信息),
关于抽象方法的返回值类型:仅以操作成功为前提来设计返回值类型,如果操作失败,将抛出异常。
XxxServiceImpl实现类
web
jsonResult 响应
业务逻辑层
c (Controller 控制器)
controller层
spring-boot-starter-web依赖项,否则,当前项目并不具备Web应用程序开发所需的依赖!
此依赖项中内置了Tomcat,当启用项目时,会自动将当前项目编译、打包、部署到内置的Tomcat上,并启动Tomcat。
ex
关于异常的捕获处理和抛出,在典型的服务器端项目中,Mapper、Service都不能处理异常,因为它们不与客户端直接交互,如果它们处理了异常,Controller将不知道出现过错误,反而响应“成功”的结果到客户端,但客户端的请求是没有达到预期的目标的,而Controller有义务处理异常,因为它是与客户端交互的组件
hander
Spring MVC框架提供了统一处理异常的机制!
关于统一处理异常的方法: 注解:必须添加@ExceptionHandler,表示此方法是处理异常的方法
访问权限:应该使用public
返回值类型:参考处理请求的方法
方法名称:自定义
参数列表:至少有1个异常类型的参数,表示被处理的异常,另外,可以按需添加HttpServletRequest、HttpServletResponse等少量特定类型的参数,不可以随意添加其它参数,例如不可以添加HttpSession等,如果有多个参数,各参数可以不区分先后顺序
关于处理异常的方法的执行特点:
允许同时存在多个处理异常的方法,只要这些方法处理的异常不完全相同即可
多个处理异常的方法,其处理的异常类型允许存在继承关系
例如某方法处理NullPointerException,另一个方法处理RuntimeException,是正确的当出现子级异常时,将执行处理子级异常的方法
如果将处理异常的方法定义在控制器类中,则这些方法只能作用于当前控制器类中处理的请求
可以自定义类,在类上添加@RestControllerAdvice注解,将处理异常的方法声明在这个类中,可以作用于当前项目中所有处理请求时方法
GlobalExceptionHandler
@RestControllerAdvice public class GlobalExceptionHandler { /** * 兜底(避免500) * @param e 所有异常 * @return 响应99999和message */ @ExceptionHandler public JsonResult handleThrowable(Throwable e){ log.debug("handleThrowable"); e.printStackTrace(); String message = "服务器忙,请稍后尝试!"; return JsonResult.fail(ServiceCode.ERR_UNKNOWN, message); } }
控制层
v (View视图)
请求
同步请求
异步请求
响应
共筑世界生命树
life
clifetree-client
客户端
框架
vue脚手架
axios.js
elementui.js
qs.js
clife-edit
后台管理
life-tree
主体树 10010
passport
访问控制 10000
life-commons
网络交互必备
gateway
网关 9999
search
搜索,未使用ELR无法实现数据同步
部署
虚拟机
7.0.x版本
VirtualBox
1.在任务管理器的性能上检查计算机的虚拟化状态
适配器
2.共享网络
internet 连接共享
3.加载虚拟机镜像rockyLinux
建议启动Virtualbox时使用单击右键->管理员方式运行
管理员权限设置
USB1.1
网卡启用网络
网卡1
主机网络
网卡2
桥接网卡
启动
鼠标被虚拟机捕获,使用右侧Ctrl键解除
用户名密码默认都是rockylinux
这个用户并不具备系统所有权限,所以后面的内容可能因为权限不足受阻
ping www.baidu.com
如果有周期响应,证明网络畅通,虚拟机可以使用当前计算机的网络功能
Ctrl+C可以随时退出当前运行的程序 返回到命令符
sudo su -
切换到root用户
passwd
为root用户设置一个密码,有了密码才能登录root用户 建议不要使用数字做密码,推荐学习过程中就使用root做密码
ifconfig
自己虚拟机的ip地址
| more
逐行显示信息
192.168.56.101
172.18.6.21
Username: root password: root hostport: 172.18.6.21:20
vmware
编辑虚拟网络编辑器
更改设置获取管理员特权
修改VMnet8网段,后面两个值改成64.0
宿主机跟虚拟机通过NET网络进行通信
管理 更改硬件兼容性,选择安装的版本
虚拟机镜像
得到ip地址以及用户名和密码
shell客户端
远程链接linux的客户端 moboxterm xshell powershell
Bitvise SSH Client
将得到的IP地址、用户名以及密码输入完成连接
连接成功后 就可以用这个软件提供的terminal界面来操作Linux了
yum install -y yum-utils
RockyLinux支持使用yum命令安装各种程序 安装yum-utils包,实现更方便的安装"应用商店"中提供的程序
高并发、高可用问题
动态解决服务器忙闲不均、资源浪费?
部署多台服务器?
一个服务器发生错误不能影响到其他服务器,如何实现隔离性问题?
保证开发环境(windows)与部署环境(linux)一致性问题?
服务器版本
配置问题
docker
1.指定下载源
Docker
基于go语言,遵从Apache2.0协议开源的容器引擎
特征
将应用程序与基础结构分离,以便快速交付软件
更快速的应用交付和部署:
传统的应用开发完成后,需要提供一堆安装程序和配置说明文档,安装部署后需根据配置文档进行繁杂的配置才能正常运行。 Docker化之后只需要交付少量容器镜像文件,在正式生产环境加载镜像并运行即可,应用安装配置在镜像里已经内置好,大大节省部署配置和测试验证时间。
更便捷的升级和扩缩容:
随着微服务架构和Docker的发展,大量的应用会通过微服务方式架构,应用的开发构建将变成搭乐高积木一样,每个Docker容器将变成一块“积木”,应用的升级将变得非常容易。 当现有的容器不足以支撑业务处理时,可通过镜像运行新的容器进行快速扩容,使应用系统的扩容从原先的天级变成分钟级甚至秒级。
更简单的系统运维:
应用容器化运行后,生产环境运行的应用可与开发、测试环境的应用高度一致,容器会将应用程序相关的环境和状态完全封装起来,不会因为底层基础架构和操作系统的不一致性给应用带来影响,产生新的BUG。当出现程序异常时,也可以通过测试环境的相同容器进行快速定位和修复。
更高效的计算资源利用:
Docker是内核级虚拟化,其不像传统的虚拟化技术一样需要额外的Hypervisor [管理程序] 支持,所以在一台物理机上可以运行很多个容器实例,可大大提升物理服务器的CPU和内存的利用率。
名词
宿主机(host)
宿主机就是我们调用命令使用镜像创建容器的服务器(linux)。
客户端(docker-client)
调用docker命令,操作镜像,容器的进程。只要能链接宿主机,操作docker的进程都是docker-client。
镜像服务器(registry)
镜像仓库占用的服务器,这里注意一个镜像服务器未必只有一个仓库,可以有很多仓库,每个仓库又保管的是不同镜像。
镜像仓库(repository)
一个用来容纳多个镜像的仓库,可以链接仓库获取你想要的内部镜像,一般一个镜像仓库中包含多个不同tag的镜像。
镜像(image)
镜像(Image)就是一个只读的模板文件。镜像可以用来创建 Docker 容器,一个镜像可以创建很多容器。 就好似 Java 中的 类和对象,类就是镜像,对象就是容器!也可以把镜像看成是模具,而镜像创建出来的容器就是通过这个模具创建的一个一个的实际产品。
容器(container)
首先需要了解什么是容器,容器就是一个进程,内部是独立运行的一个或者是一组应用。它可以被启动、开始、停止、删除。每个容器都是相互隔离的,保证安全的平台。
yum-config-manager \ --add-repo \ https://download.docker.com/linux/centos/docker-ce.repo
国际
yum-config-manager \ --add-repo \ http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
阿里
yum -y install docker-ce docker-ce-cli containerd.io
2.执行安装Docker
卸载
加速器
clear
指令清空
systemctl start docker
3. 运行docker
镜像
docker run hello-world
一个专门测试Docker功能的镜像
docker images docker image ls
查看当前本地镜像仓库的功能
内容
REPOSITORY:镜像仓库名,也叫作镜像名。
TAG:标签,常用版本号标识仓库,如果是latest就是最新版本。
IMAGE ID:镜像id。
CREATED:创建镜像时间。
SIZE:大小。
-a: 显示所有信息
-q: 只显示镜像id,在镜像较多的时候比较常用
docker search [镜像名称(mysql)]
在拉取镜像之前,我们要先从镜像仓库明确正确的镜像名称
NAME:镜像名称。
DESCRIPTION:镜像描述。
STARS:镜像星级,越高表示越热,使用人越多。
OFFICIAL:是否官方镜像。
AUTOMATED:是否支持自动化部署。
docker pull [镜像名称:标签]
将镜像拉取到本地仓库
docker tag 标签名 镜像名
为镜像贴标签
docker stop [镜像id]
停止运行的镜像
docker rmi feb5d9fea6a5
remove image 正在使用的镜像无法删除
-f:强制删除这个镜像,无论是否正在使用。
# 导出镜像
docker save benwang6/cerebro:0.9.4 rabbitmq:management | gzip > a.gz
# 导入镜像
docker load -i a.gz
dockerfile
制作镜像
RUN
执行命令行指令
容器
docker ps docker container ls
查看当前docker中运行的所有容器的状态 options list
标题内容
container id:容器id,很多操作容器命令都需要用到的参数。
image:容器创建使用的镜像。
command:容器中在运行的进程或者命令。
created:创建时间。
status:容器状态。
ports:容器的端口映射情况,这里没有用到端口。
names:容器的名字,启动没有指定--name选项,会默认使用一个名字。
-a:显示所有容器,如果不加只显示正在启动运行的容器,停止的不会显示。
-l:显示最近的启动创建的容器。
-n=[数字]:显示最近n个容器。
-q:只显示容器id。经常和-a一起使用,获得当前宿主机所有容器id参数集合。
创建容器
mysql:5.7.35
docker run --name mysql -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 -v /root/data:/var/lib/mysql mysql:5.7.35
启动之后,我们就可以使用数据库连接工具访问数据库了
1: --name mysql:该容器启动后的名字:(自定义命名)如果没有设置,系统会自动设置一个,毕竟如果开启太多的容器,记不住就很尴尬,建议加上见名知意。
2:-d 代表后台启动该服务
3:-p 3306(这是liunx的端口号,我习惯说成宿主机,如果我们想要远程服务的话,访问的端口就是这个端口):3306(docker容器的端口,每一个容器都是独立的,可理解成操作系统层面的系统),访问这个端口就是先通过远程访问宿主机的端口,再映射到docker容器的端口访问mysql。
4:-e MYSQL_ROOT_PASSWORD=123456 这是说mysql启动需要的开机密码,默认的账号是root ,密码就是上面设置的:123456
5:-v /root/data:/var/lib/mysql /root/data/:这是宿主机的数据存放路径(你也可以自定义), /var/lib/mysql:这是mysql容器存放数据的地方。也是为了同步数据,防止,容器被删除以后,数据就不存在了。
--rm 容器关闭后即销毁
6:启动成功后就返回一个容器ID
docker run --name mysql -d -p 3307:3306 -e MYSQL_ROOT_PASSWORD=123456 -v /root/data:/var/lib/mysql mysql:5.7.35
docker run --name mysql -d -p 3308:3306 -e MYSQL_ROOT_PASSWORD=123456 -v /root/data:/var/lib/mysql mysql:5.7.35
docker run --name mysql -d -p 3309:3306 -e MYSQL_ROOT_PASSWORD=123456 -v /root/data:/var/lib/mysql mysql:5.7.35
redis
默认下载最新版本
# redis默认使用6379端口,使用6380端口启动redis docker run redis redis-server --port 6380
docker run -itd --name redis-test -p 6379:6379 redis
ctrl+c退出
docker stop [容器id]
docker start [容器]
docker rm [容器id]
-f:强制删除容器,无论是否运行都可以删除该容器,如果不加,运行的容器无法删除。
# 删除所有容器 docker rm -f $(docker ps -a -q)
防火墙
systemctl stop firewalld
关闭防火墙 如果当前windows系统要连接Linux中的资源,一般都要关闭Linux的防火墙 实际开发中,不会彻底关闭防火墙,而是开放指定的端口号 system:系统 ctl:control:控制
systemctl start firewalld
firewall-cmd --list-ports
进入容器
docker exec -it [redis-test 容器名/id] redis-cli
本地访问客户端
exit
退出
复制
docker cp [容器名]:目录名
获取copy目录
cd
change dir
ls -l
查看文件
cat
查看文件内容
ll
vim 文件
编辑文件
子主题
alt+p 可将静态资源拖拽过来 ,默认进入linux桌面
docker cp [静态文件名] [容器名]:目录名
将文件复制到
数据卷
容器之间共享和重用 修改之后会立马修改
dovker-volumn 文件夹
将数据卷复制到文件夹下
cd 文件
pwd
PrintWorkingDirectory 打印当前路径
docker run
-v 数据卷路径:容器路径
微服务项目部署
nacos 整理好配置
maven intall 打包好jar包
将jar包配置到linux目录下
定制镜像
MobaXterm
运维评测:推荐
session - SSH连接
项目开发
开发组结构
java有比较长时间的演化和积淀 形成了Spring庞大的生态体系,在Spring中几乎可以找到所有业务的支持 java开发的项目一般都是中大型以上的项目 开发周期长,开发人数多,java开发的项目能够有最好的稳定性和安全性,也能够在微服务的支持下有更好的性能
一般java项目组人员组成 6~8人
一个项目经理(leader) 25K
一个技术总监 25K
两三个中坚力量 15-20K
两个初级程序员 8K
开发周期6~8月
1.设计 1月
2.开发 2月
3.测试 3月
4.上线调试 1到2个月
100->200
一线城市报价160万
二线 120~130之间
三线 100~110之间
项目开发流程
运营部分
发起项目策划提出项目的功能或特色,交由产品经理
产品经理
细化功能,提出需求交给开发部门
需求分析
需求分析文档
开发部分的操作
架构师
根据需求分析结果,做数据库设计
针对业务做概要设计
找前端开发人员做一个页面原型
详细设计
可选
开发阶段
测试
部署上线
开发计划
1.分类搜索
数据库mall_pms的category表使用自关联实现了三级分类
id:主键
name:显示在页面上的分类名称
parentId:父分类的id 如果是一级分类父分类id为0
depth:分类深度,当前项目就是3级分类,1\2\3 分别代表它的等级
keyword:搜索关键字
sort:排序依据 正常查询时,根据此列进行排序,数字越小越出现在前面(升序)
icon:图标地址
enable:是否可用
isparent:是否为父分类 0 假 1真
isdisplay:是否显示在导航栏 0不显示 1显示
构建三级分类树结构
首次Redis访问
List<T> categories
开发过程中,Redis一般不使用List做值 原因:Redis保存对象的效率更高 另一方面保存List容易出现数据冗余,导致内存浪费
FrontCategoryTreeVO
包含树结构的treeVO对象
CategoryStandardVO+children属性(子分类的list集合)=FrontCategoryEntity
先将CategoryStandardVO类型对象转换为FrontCategoryEntity
然后在进行正确的父子关联
以分类id作为Key,这个分类对象包含的所有子分类List作为Value,保存到Map中
子分类对象添加到对应的分类对象的children属性中
实例化FrontCategoryTreeVO类型对象,给它的list属性赋值(firstLevels)并返回即可
递归实现
分页查询
所谓分页,就是查询结果数据较多时,采用按页显示的方法,而不是一次性全部显示
优点
服务器:一次性查询所有信息,服务器压力大,分页查询服务器压力小
客户端:一次性显示所有信息,需要更多流量,加载时间也会更长,分页显示没有这个问题
用户体验上:一般最有价值的信息都会在前几页显示,也方便用户记忆,多查询出来的数据使用几率很低
PageHelper框架
实现我们提供页码和每页条数,自动实现分页效果,收集分页信息
原理就是在程序运行时,在sql语句尾部添加limit关键字,并按照分页信息向limit后追加分页数据(无需编写分页语句)
seata支持时已经添加了pagehepler依赖
<dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> </dependency>
com.github.pagehelper.
PageHelper
static <E> Page<E> startPage(int pageNum, int pageSize)
标注
设置分页
page是页码,pageSize是每页条数
设置了分页条件,sql语句中会自动出现limit关键字
PageInfo(List<T> list)
标注
信息会在PageInfo对象实例化时自动计算,并赋值到PageInfo对象中
PageInfo<T> of(List<T> list)
既包含分页数据,又包含分页信息
//当前页
private int pageNum;
//每页的数量
private int pageSize;
//当前页的行数量
private int size;
//当前页面第一个元素在数据库中的行号
private int startRow;
//当前页面最后一个元素在数据库中的行号
private int endRow;
//总页数
private int pages;
//前一页页号
private int prePage;
//下一页页号
private int nextPage;
//是否为第一页
private boolean isFirstPage;
//是否为最后一页
private boolean isLastPage;
//是否有前一页
private boolean hasPreviousPage;
//是否有下一页
private boolean hasNextPage;
//导航条中页码个数
private int navigatePages;
//所有导航条中显示的页号
private int[] navigatepageNums;
//导航条上的第一页页号
private int navigateFirstPage;
//导航条上的最后一页号
private int navigateLastPage;
将分页的返回值统一为一个类型
我们在开发的过程中,可能使用不同技术对不同的数据库进行分页查询 例如我们使用SpringDataElasticsearch分页查询ES,会得到返回值类型Page 我们使用PageHelper分页查询数据库,会得到返回值类型PageInfo 这两个返回值类型中,有类似的属性(当前页,每页条数,总页数,总条数等) 但是他们的属性名不同,导致返回给前端时,同样是分页业务,前端程序员需要用不同的属性来控制 给前端的工作带来额外的复杂度
自定义类
自定义属性
自定义静态转换方法restPage
2.商品列表
按分类id分页查询spu列表
Standard Product Unit (标准化产品单元) 用户会根据分类树中的分类的名称,查询它需要的商品类别 点击商品分类名称时,实际上我们获得了它的分类id(categoryId)
3.商品详情
选中商品后,会显示这个商品的详情信息
根据spuId查询spu信息 根据spuId查询spuDetail详情 根据spuId查询当前Spu包含的所有属性 根据spuId查询对应的sku列表
1.根据spu_id去pms_spu表查询category_id
2.根据category_id去pms_category表查询分类对象
3.根据category_id去pms_category_attribute_template表查询attribute_template_id
4.根据attribute_template_id去pms_attribute_template表查询attribute_template数据行
5.根据attribute_template_id去pms_attribute表查询对应所有属性信息行
SELECT pa.id , pa.template_id, pa.name, pa.description , pa.type, pa.value_list , pa.unit FROM pms_spu ps JOIN pms_category pc ON ps.category_id=pc.id JOIN pms_category_attribute_template pcat ON pc.id=pcat.category_id JOIN pms_attribute_template pat ON pcat.attribute_template_id=pat.id JOIN pms_attribute pa ON pat.id=pa.template_id WHERE ps.id=4
4.购物车(cart)管理
新增sku到购物车
Stock Keeping Unit(库存量单位)
判断用户是否登录,只有登录后才能将商品新增到购物车
验证购物车信息的完整性(SpringValidation)
业务逻辑层要判断新增的sku是否在当前用户的购物车表中已经存在
如果不存在是新增sku流程
如果已经存在,是修改数量的流程
开发查询购物车功能
删除\清空购物车
修改购物车的商品数量
5.新增订单和订单列表(order)
要收集订单信息(sku商品信息,价格信息,优惠和运费信息等)然后才能执行生成订单操作
对应oms_order表执行新增,也就是创建一个订单(多个商品)
这个方法中利用Dubbo远程调用了product模块的数据库操作,有分布式事务需求 所以使用注解激活Seata分布式事务的功能 @GlobalTransactional
第一部分是信息的收集
主要是参数类型数据的完整性验证,计算以及转换
order对象的所有属性赋值
第二部分是数据库操作
开始整理收集参数orderAddDTO中包含的订单项集合:orderItems属性
根据订单项
减少库存
删除购物车
新增订单
新增订单项
第三部分是收集需要的返回值
我们新增订单成功后,要返回给前端一些信息,例如订单号,实际支付金额等
在新增订单成功后,我们还要将订单中的每种商品和订单关系添加在oms_order_item表中
6.搜索商品
Elasticsearch加载数据
确认实体类
将要传入到ES中的实体
XxxForElastic
持久层
XxxForElasticRepository
业务逻辑层
将数据库中的数据加载到ES中
搜索功能的实现
数据库中的数据和Elasticsearch中的数据还存在同步问题
1.在所有对spu表进行增删改的操作代码运行后,也对ES中的数据进行相同的操作
但是会有比较多的代码要编写,而且有比较明显的事务处理问题
2.实际上业界使用Elasticsearch有一个组合叫ELK,其中L(logstash)可以实现自动同步数据库和ES的信息
7.高并发秒杀商品
高并发在微服务项目中的处理流程
开发查询秒杀商品列表功能
根据SpuId查询秒杀Sku列表信息
根据当前时间查询正在进行秒杀的商品
根据SpuId查询秒杀商品信息
查询所有秒杀商品的SpuId
缓存预热
在即将发生高并发业务之前,我们将一些高并发业务中需要的数据保存到Redis中,这种操作,就是"缓存预热",这样发生高并发时,这些数据就可以直接从Redis中获得,无需查询数据库了
我们要利用Quartz定时的将每个批次的秒杀商品,预热到Redis
例如每天的12:00 14:00 16:00 18:00进行秒杀
那么就在 11:55 13:55 15:55 17:55 进行预热
1. 我们预热的内容是将参与秒杀商品的sku查询出来,根据skuid将该商品的库存保存在Redis中还要注意为了预防雪崩,在向Redis保存数据时,都应该添加随机数
2. (待完善).在秒杀开始前,生成布隆过滤器,访问时先判断布隆过滤器,如果判断商品存在,再继续访问
3. 在秒杀开始之前,生成每个商品对应的随机码,保存在Redis中,随机码可以绑定给Spu,保存在前端页面,用户提交时,验证随机码的正确性,只有正确的随机码才能购买商品
防止有人直接访问服务器
开发查询秒杀商品详情的功能
1.页面上显示秒杀价和剩余秒杀时间等信息
2.判断请求的spuId是否在布隆过滤器中(后续完成)
3.判断Redis 中是否包含商品信息
4.如果一切正常在返回详情信息前,要为url属性赋值,其实就是固定路径+随机码
创建流控和降级的处理类
秒杀业务肯定是一个高并发的处理,并发数超过程序设计的限制时,就需要对请求的数量进行限流
执行秒杀
1.判断用户是否为重复购买和Redis中该Sku是否有库存
2.秒杀订单转换成普通订单,需要使用dubbo调用order模块的生成订单方法
3.使用消息队列(RabbitMQ)将秒杀成功记录信息保存到success表中
秒杀成功信息用于统计秒杀数据,是秒杀结束后才需要统计的 所以在秒杀并发高时,消息队列的发送可以延缓,在服务器不忙时,再运行(削峰填谷)
4.秒杀订单信息返回
success秒杀成功信息的处理
编写一个接收消息的监听器类来完成这个操作
根据skuId减少库存数
新增Success类型对象到数据库
如果上面两个数据库操作发送异常, 引发了事务问题
// 1.如果不要求精确统计,不处理也可以
// 2.如果要求精确统计,首先可以编写try-catch块进行连库操作重试
// 如果重试再失败,可以将失败的情况汇总后,提交到死信队列
// 因为死信队列是人工处理,所以效率不能保证,实际开发中要慎重的使用死信队列
读写分离
负责读的服务器可使用乐观锁。 以提高读取的效率。
ShardingSphere
Leaf
数据库一写多读(多台读写分离服务器)。 若要使用两套,写入的服务也需要同步,但使用自增列ID时会发生并发(两条线程同时分别访问A,X)的覆盖问题。 Leaf是美团公司开源的一个分布式序列号(id)生成系统
工作原理
Leaf底层支持通过"雪花算法"生成不同id 需要给leaf创建一个指定格式的数据库表运行过程中会从数据库表获取信息我们当前的信息保存在leafdb.leaf_alloc表中 我们使用的是单纯的序列要想使用,需要事先设置好leaf的起始值(max_id)和缓存id数(step) 举例,从1000开始缓存500 也就是从id1000~1499这些值,都会保存在Leaf的内存中,当有服务需要时,直接取出下一个值 取出过的值不会再次生成,当缓存的数据取完时,会往后再缓存500个,从1500-1999
在Github网站上下载项目直接使用, 因需要设置一些配置,需要额外占用一个模块的位置。
static Long getDistributeId(String key)
得到分布式序列号(id)
静态资源服务器
我们无论做什么项目,都会有一些页面中需要显示的静态资源,例如图片,视频,文档等 我们一般会创建一个单独的项目,这个项目中保存静态资源 其他项目可以通过我们保存资源的路径访问
静态资源服务器可以将项目需要的所有图片统一管理起来 当其他模块需要图片时,可以从数据库中直接获得访问静态资源的路径即可 方便管理所有静态资源
Quartz
一个当今市面上流行的高效的任务调度管理工具 是java编写的,我们使用时需要导入依赖即可。 所谓"调度"就是制定好的什么时间做什么事情的计划 由OpenSymphony开源组织开发 Symphony:交响乐 我们使用过的最简单的调度方法就是Timer 但是Timer的调度功能过于单一,只能是指定时间的延时调用和周期运行 而Quartz可以更详细的指定时间,进行计划调用
任务:job
Quartz 实现过程中是一个接口,接口中有一个方法execute(执行的意思) 我们创建一个类,实现这个接口,在方法中编写要进行的操作(执行具体任务) 我们还需要一个JobDetail的类型的对象,Quartz每次执行job时 会实例化job类型对象,去调用这个方法,JobDetail是用来描述Job实现类的静态信息, 比如任务运行时在Quartz中的名称
调度器:Scheduler
一个可以规定哪个触发器绑定哪个job的容器 在调度器中保存全部的Quartz 保存的任务 SpringBoot框架下,添加Quartz依赖后,调度器由SpringBoot管理,我们不需要编写
触发器:Trigger
能够描述触发指定job的规则,分为简单触发和复杂触发 简单触发可以使用SimplTrigger实现类.功能类似timer 复杂触发可以使用CronTrigger实现类,内部利用cron表达式描述各种复杂的时间调度计划
Cron表达式
能够制定触发时间的一个格式
通配符
, 是个分割符如果秒字段我想20秒和40秒时触发两次就写 20,40
- 表示一个区间 秒字段5-10 表示 5,6,7,8,9,10
/ 表示递增触发 秒字段 5/10表示5秒开始每隔10秒触发一次
日字段编写1/3表示从每月1日起每隔3天触发一次
概要
* 表示任何值,如果在分的字段上编写*,表示每分钟都会触发
? 表示不确定值, 因为我们在定日期时,一般确定日期就不确定是周几,相反确定周几时就不确定日期
L 表示last最后的意思,我们可以设置当月的最后一天,就会在日字段用L表示,
周字段使用L表示本月的最后一个周几,一般会和1-7的数字组合
例如6L表示本月的最后一个周五
W (work)表示最近的工作日(单纯的周一到周五) 如果日字段编写15W表示
每月15日最近的工作日触发,如果15日是周六就14日触发,如果15日是周日就16日触发
LW通常一起使用,表示本月的最后一个工作日
# 表示第几个,只能使用在周字段上 6#3表示每月的第三个周五
如果#后面数字写大了,是一个不存在的日期,那就不运行了 适合设计在母亲节或父亲节这样的日期运行
<!-- Spring Boot 整合 Quartz的依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId> </dependency>
org.quartz
Job
void execute(JobExecutionContext var1) throws JobExecutionException;
执行任务
JobBuilder
任务创建类
static JobBuilder newJob(Class<? extends Job> jobClass)
创建新的job
参数为Job实现类的反射
JobBuilder withIdentity(String name)
命名,不重复即可
JobBuilder storeDurably()
默认情况下JobDetail对象生成后没有触发器绑定会被移除 此方法可避免被移除。
JobDetail build()
完成job详情的创建
JobDetail
CronScheduleBuilder
创建Cron表达式日程
static CronScheduleBuilder cronSchedule(String cronExpression)
参数为Cron表达式
TriggerBuilder<T extends Trigger>
创建触发器
static TriggerBuilder<Trigger> newTrigger()
TriggerBuilder<T> forJob(JobDetail jobDetail)
与任务绑定
TriggerBuilder<T> withIdentity(String name)
TriggerBuilder<SBT> withSchedule(ScheduleBuilder<SBT> schedBuilder)
设定触发日程
T build()
XxxJob
1.编写一个任务类方法体中写要执行的任务
QuartzConfigration
将它封装为JobDetail对象保存在Spring容器中
创建一个Trigger(设置要运行的时机)与JobDetail绑定,也保存到Spring容器中
消息队列(MQ)
消息队列(Message Queue)简称MQ,也称:"消息中间件" 消息队列是采用"异步(两个微服务项目并不需要同时完成请求)"的方式来传递数据完成业务操作流程的业务处理方式
Dubbo调用的性能问题
这些Dubbo调用全部是同步的操作 这里的"同步"指:消费者A调用生产者B之后,A的线程会进入阻塞状态,等待生产者B运行结束返回之后,A才能运行之后的代码
Dubbo消费者发送调用后进入阻塞状态,这个状态表示该线程仍占用内存资源,但是什么动作都不做 如果生产者运行耗时较久,消费者就一直等待,如果消费者利用这个时间,那么可以处理更多请求,业务整体效率会提升 实际情况下,Dubbo有些必要的返回值必须等待,但是不必要等待的服务返回值,我们可以不等待去做别的事情 这种情况下我们就要使用消息队列
特征
利用异步的特性,提高服务器的运行效率,减少因为远程调用出现的线程等待\阻塞时间
削峰填谷:在并发峰值超过当前系统处理能力时,我们将没处理的信息保存在消息队列中,在后面出现的较闲的时间中去处理,直到所有数据依次处理完成,能够防止在并发峰值时短时间大量请求而导致的系统不稳定
消息队列的延时:因为是异步执行,请求的发起者并不知道消息何时能处理完,如果业务不能接受这种延迟,就不要使用消息队列
接收消息队列中信息的模块运行发生异常时,怎么完成事务的回滚?
我们可以向消息的发送者(order)发送消息,然后通知发送者(order)处理,消息的发送者(order)接收到消息后,一般要手写代码回滚,如果回滚代码过程中再发生异常,就又要思考回滚方式,如果一直用消息队列传递消息的话,可能发生异常的情况是无止境的 所以我们在处理消息队列异常时,经常会设置一个"死信队列",将无法处理的异常信息发送到这个队列中 死信队列没有任何处理者,通常情况下会有专人周期性的处理死信队列的消息
消息队列软件
Kafka:性能好\功能弱:适合大数据量,高并发的情况,大数据领域使用较多
Kafka是一个结构相对简单的消息队列(MQ)软件 Kafka是由Apache软件基金会开发的一个开源流处理平台,由Scala和Java编写。该项目的目标是为处理实时数据提供一个统一、高吞吐、低延迟的平台。Kafka最初是由LinkedIn开发,并随后于2011年初开源。
Kafka Cluster(Kafka集群)
Producer:消息的发送方,也就是消息的来源,Kafka中的生产者
Consumer:消息的接收方,也是消息的目标,Kafka中的消费者
Topic:话题或主题的意思,
消息的收发双方要依据同一个话题名称,才不会将信息错发给别人
Record
消息记录,就是生产者和消费者传递的信息内容,保存在指定的Topic中
<!-- SpringBoot整合Kafka的依赖 --> <dependency> <groupId>org.springframework.kafka</groupId> <artifactId>spring-kafka</artifactId> </dependency> <!-- google提供的可以将java对象和json格式字符串转换的工具 --> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> </dependency>
spring: kafka: bootstrap-servers: localhost:9092 # consumer.group-id是Spring-Kafka框架要求的必须配置的内容,不配置启动会报错 # 作用是给话题分组,防止不同项目恰巧相同的话题名称混淆 # 本质上,在当前项目发送给kafka消息时,会使用这个分组作为话题名称的前缀 # 例如发送一个message的话题名称,实际上会发送的话题名称是csmall_message consumer: group-id: csmall
org.springframework.kafka.
core
KafkaTemplate<[话题类型],[消息类型]>
配置Spring-Kafka框架提供的能够连接kafka操作的对象(以存在于Spring容器中) 指定泛型<String,String>
ListenableFuture<SendResult<K, V>> send(String topic, @Nullable V data)
发送给队列软件
对象话题命名
对象json字符串
annotation
EnableKafka
启动kafka功能 常配置在启动类。
KafkaListener
监听器
String[] topics() default {};
org.apache.kafka.clients.consumer.
ConsumerRecord
接受到的信息
V value()
获取信息
com.google.gson.
Gson
String toJson(Object src)
将对象转换为json格式字符串
实现消息队列
1. 启动类添加注解启动操作kafka的功能
kafka.
Producer
组件类:消息发送
Consumer
组件类:消息接收
RabbitMQ:功能强\性能一般:适合发送业务需求复杂的消息队列,java业务中使用较多
RabbitMQ 是一个由 Erlang 语言开发的 AMQP 的开源实现。 AMQP :Advanced Message Queue,高级消息队列协议。 它是应用层协议的一个开放标准,为面向消息的中间件设计,基于此协议的客户端与消息中间件可传递消息,并不受产品、开发语言等条件的限制。 RabbitMQ 最初起源于金融系统,用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。
特征
1.可靠性(Reliability) RabbitMQ 使用一些机制来保证可靠性,如持久化、传输确认、发布确认。
2.灵活的路由(Flexible Routing) 在消息进入队列之前,通过 Exchange 来路由消息的。对于典型的路由功能,RabbitMQ已经提供了一些内置的 Exchange 来实现。针对更复杂的路由功能,可以将多个Exchange 绑定在一起,也通过插件机制实现自己的 Exchange 。
3.消息集群(Clustering) 多个 RabbitMQ 服务器可以组成一个集群,形成一个逻辑 Broker
4.高可用(Highly Available Queues) 队列可以在集群中的机器上进行镜像,使得在部分节点出问题的情况下队列仍然可用。
5.多种协议(Multi-protocol) RabbitMQ 支持多种消息队列协议,比如 STOMP、MQTT 等等。
6.多语言客户端(Many Clients) RabbitMQ 几乎支持所有常用语言,比如 Java、.NET、Ruby 等等。
7.管理界面(Management UI) RabbitMQ 提供了一个易用的用户界面,使得用户可以监控和管理消息 Broker 的许多方面。
8.跟踪机制(Tracing) 如果消息异常,RabbitMQ 提供了消息跟踪机制,使用者可以找出发生了什么。
9.插件机制(Plugin System) RabbitMQ 提供了许多插件,来从多方面进行扩展,也可以编写自己的插件。
<!-- RabbitMQ的依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency>
spring: rabbitmq: host: localhost port: 5672 username: guest password: guest # 设置虚拟host 单机模式下固定编写"/"即可 virtual-host: /
org.springframework.amqp.rabbit.
高级消息队列协议 (Advanced Message Queuing Protocol)
core.
DirectExchange(String name)
交换机
构造器参数为交换机名称
Queue(String name)
队列
构造器参数为队列名称
BindingBuilder
设定绑定信息
static BindingBuilder.DestinationConfigurer bind(Queue queue)
绑定(队列)
参数为队列对象
BindingBuilder.DirectExchangeRoutingKeyConfigurer to(DirectExchange exchange)
到(交换机)
参数为交换机对象
Binding with(String routingKey)
设置路由key后完成绑定
参数为路由key
Binding
绑定
RabbitTemplate
其单例位于spring容器中, 可进行自动装配
convertAndSend(String exchange, String routingKey, Object object)
指定交换机
指定路由key
指定要加入队列的对象
annotation.
RabbitListener
RabbitMQ监听器注解要求写在类上
String[] queues() default {}; 参数配置为队列名
RabbitHandler
当前队列有消息时,运行@RabbitHandler标记的方法 每个类只允许编写一个@RabbitHandler标记的方法 注解下编写方法,参数直接编写传递的对象即可
quartz.config.RabbitMQConfig
1. 交换机换机\路由Key\队列,以及它们的绑定关系 要保存到Spring容器管理
交换机实体对象,保存到Spring容器
队列实体对象,保存到Spring容器
路由Key不是实体,是交换机和队列对象的绑定关系,所以编写比较特殊
QuartzJob类中输出时间的代码后继续编写代码
RabbitMQ发送消息
quartz
RabbitMQConsumer
接收信息
RocketMQ:阿里的
ActiveMQ:前几年流行的,老项目可能用到
.....
服务器端程序的演进过程
静态服务器
早期的服务器状态,编写好一些固定内容,让用户访问 功能单一,如果不修改代码,内容是不会变的,只能做信息的呈现或输出
阶段二:普通动态服务器
网页中的数据可能来自数据库,数据库中的数据可以在后台中进行修改 实现不修改页面代码,但是变化页面内容的效果 因为有了数据库的支持,动态网站开始支持登录注册,增删改查功能
阶段三: 以用户共享内容为主的互联网生态
随着互联网的普及,个人的社交需求提升 出现了很多由用户贡献内容的网站 微博,抖音,淘宝,大众点评或类似的网站
阶段四: 微服务时代(有高并发需求或特征的网站)
随着用户的增加,各种并发的增高,要求我们的服务器在繁忙的情况下,也需要快速的做出响应 用户体验必须保证,这样就要求我们的项目有下面三个目标 高并发,高可用,高性能 高并发:很多用户同时访问这个服务器,这个服务器不能失能 高可用:全年365天每天24小时都可以访问,不能因为个别服务器的异常,导致整个项目的瘫痪 高性能:当用户访问服务器时,响应速度要尽量的快,即使并发高,也要有正常的响应速度 微服务的"三高"
分工
架构师
框架配置
接口
定义规范和标准交由别人人来实现
java软件工程师
实现类
软件测试工程师
java服务器项目分类
1.企业级项目 一般指一个企业或机构内部使用的网站或服务器应用程序 使用的人群比较固定,并不向全国乃至全世界开放 例如,商业,企事业单位,医疗,金融,军事,政府机关等 所以这个项目没有代替品,对"三高"没有强烈要求 企业级项目一般会在权限和业务流程方面设计的比较复杂
2.互联网项目 能够向全国乃至全世界开放的网站或服务器应用程序 我们手机中安装的app大部分都是互联网应用 微信,支付宝,京东,淘宝,饿了么,美团,抖音,qq音乐,爱奇艺,高德地图等 它们因为商业竞争等原因,对服务器的性能有非常高的要求,就是我们之前提到的"三高" 但是互联网应用一般没有权限和业务非常复杂的需求 综上所述,企业级应用和互联网应用的偏重点不同 在当今java开发业界中,基本规律如下 如果开发的是企业级应用,使用单体架构的情况比较多 如果开发的是互联网应用,使用微服务架构的情况比较多
黑科技网站(面试必备)
资料
跳槽
https://gitee.com/JasonCN2008/bigfactory.git
面试必备
https://joonwhee.blog.csdn.net/article/details/109171171
选择一家公司
面试题
https://blog.csdn.net/v123411739/article/details/115364158?spm=1001.2014.3001.5502
java基础
jvm
https://blog.csdn.net/v123411739/article/details/79692477
https://blog.csdn.net/v123411739/article/details/79671697
https://joonwhee.blog.csdn.net/article/details/115712641
集合
https://blog.csdn.net/v123411739/article/details/110009966
Spring
https://blog.csdn.net/v123411739/article/details/116109674
Redis
https://gitee.com/xiaolaoshi2021/mybatis-study-base.git
2208分支 Mybatis源码讲解
https://gitee.com/xiaolaoshi2021/springboot-study-base.git
2208分支 springboot源码讲解
https://gitcode.net/weixin_38305440/2209
项目部署
https://gitcode.net/weixin_38305440/job
算法,数据结构, redis
项目管理工具:禅道
https://gitcode.net/smlzhang/chandao
力扣
https://leetcode.cn/
视觉
https://excalidraw.com/
http://www.jiniannet.com/Page/allcolor
取色
二维码
https://cli.im/
Spring
Spring Framework
Spring框架
主要解决了创建对象、 管理对象的相关问题。
org.springframework.
.context.annotation.
spring-context依赖项 创建对象
Spring Boot项目中,默认已经包含此依赖项(可能版本不是以上5.3.20,但这并不重要)。
其实,任何被实际应用的Spring Xxx框架都包含spring-context,例如Spring MVC、Spring JDBC、Spring Validation、Spring Security等。
组件扫描
在开发实践中,当需要Spring管理某个类的对象时, 通常,如果这个类是自定义的,优先使用组件扫描的做法,
@ComponentScan
元注解
可以开启组件扫描,一旦这个配置类被加载,就会扫描特定的包及其子孙包中的所有类,如果扫描到的类是组件类,就会自动创建这些类的对象。
@ComponentScan({"cn.tedu.csmall.passport.controller", "cn.tedu.csmall.passport.service"})
注解的value参数是String[]类型的,所以,也可以指定多个需要扫描的包
如果使用@ComponentScan时没有配置注解参数,则默认扫描添加了此注解的配置类所在的包。
启用类
@SpringBootApplication
此注解的元注解就包括@ComponentScan且没有配置包名,所以,在Spring Boot中,是已经存在组件扫描机制的,且扫描的包就是启用类所在的包!
配置类
一个特殊的组件注解:@Configuration,添加了此注解的类将会被视为“配置类”
@Configuration
Spring框架会通过CGLib代理来处理此类(你可以不关心代理的实现特征)。
@Bean方法
如果这个类不是自定义的,只能使用@Bean方法的做法!
在配置类中,可以自定义某个方法,此方法可以返回某个对象,然后,在方法上添加@Bean注解,则Spring加载此配置类时,就会自动执行此方法,从而实现创建对象的效果!
@Configuration public class XxxConfiguration { @Bean public AdminController adminController() { return new AdminController(); } }
stereotype.
组件类
添加了组件注解的类
典型的组件注解
@Component:通用组件注解
@Repository:添加在数据存储库类上的组件注解
@Service:添加在业务逻辑类上的组件注解
@Controller:添加在控制器类上的组件注解
scheduling.annotation.
EnableScheduling
自带的定时任务工具 将其添加到启动类上可启用此工具
Scheduled
long fixedRate() default -1; 参数配置时间间隔,单位为ms
AOP
面向切面编程,解决横切关注的问题 (执行多个不同的方法时,都需要执行相同的代码)
spring-boot-starter-aop
非特有,很好的支持
连接点(JoinPoint):数据处理的某个节点,可能是调用了方法,或抛出了异常
切入点(PointCut):选择一个或多个连接点的通配表达式
org.aspectj.lang.
annotation
Aspect
将当前类标记为切面类
Around
表示“包裹”,也称之“环绕”,使用此注解时,方法的参数必须是ProceedingJoinPoint类型的
@Around"execution(通配符表达式)"
* cn.tedu.csmall.product.service.*.*(..)
* 可以匹配1次任意内容 在返回值位置使用星号,表示此方法可以是任意返回值,包括:void / int / String ...
.. 可以匹配0~n次任意内容,仅能用于包名和参数列表部分
Before
之前执行代码
After
之后执行代码
AfterReturning
返回结果之后执行代码
AfterThrowing
抛出异常之后执行代码
ProceedingJoinPoint
连接点(JoinPoint):数据处理过程中的某个节点,可能是调用了方法,或抛出了异常
Object proceed() throws Throwable;
// 注意:调用的proceed()方法会抛出Throwable,此时必须抛出,不可以使用try...catch捕获并处理 // 注意:调用proceed()方法时必须获取返回值,此返回值就是连接点对应的方法的返回值,必须作为当前切面方法的返回值
本质就是代表了执行了连接点对应的方法
Object getTarget();
获取匹配的连接点类
Signature getSignature();
获取方法签名
Object[] getArgs();
获取方法参数
beans
spring-beans依赖项
BeanUtils
static void copyProperties(Object source, Object target) throws BeansException
对象重复属性复制
对象
目标对象
factory.annotation
Autowired
自动装配机制
Spring的自动装配机制表现为:当类中的某个属性需要值时(若属性值为空会出现空指针),或被Spring调用的方法的参数需要值时,通过特定的语法表现(通常是添加注解),Spring可以从容器中找出合适的对象,为属性或方法参数赋值。 提示:自动装配的前提是当前类是由Spring创建对象的!
属性需要被自动装配值时, 可以有3种方法
属性注入
构造方法注入
setter方法注入
@Autowired
典型表现是在需要值的属性上添加@Autowired注解 在Spring框架中,默认的装配机制是@Autowired的机制
@Autowired注解是Spring框架的注解,是先在Spring容器中查找匹配类型的对象的数量,如果存在多个类型匹配的,将按照名称来匹配
@Autowired的机制
查询匹配类型的Bean的数量
0个:取决于@Autowired注解的required属性
true(默认):无法装配,导致装配失败,启动项目时就会报错
false:放弃装配,则属性值为null,后续可能导致NPE(空指针)
1个:直接装配,且装配成功
多个:将按照名称进行匹配,如果存在名称匹配的Spring Bean, 则装配此Spring Bean,如果不存在,则装配失败,启用项目时就会报错
无论使用以上哪种注解,如果存在同类型的多个Spring Bean,且名称与被装配的属性都不匹配,都会导致无法装配,项目启动失败。 在开发实践中,绝大部分被装配的属性,在Spring容器中都只有1个匹配类型的对象,所以,无论使用以上哪种注解,都是可以成功装配的。
匹配Spring Bean的名称
可以修改尝试被装配的属性名或方法的参数名,使之与某个Spring Bean的名称相同
可以修改Spring Bean的名称,使之与尝试被装配的属性名或方法的参数名相同
@Qualifier
提示:@Qualifier注解可以添加在属性上,也可以添加在方法的参数上。
可以在尝试被装配的属性或方法的参数上添加@Qualifier注解,通过此注解的参数指定Spring Bean的名称,例如:
@Autowired @Qualifier("adminServiceImpl") private IAdminService adminService;
子主题
@Resource
可以实现同样的效果
@Resource注解是javax包中的注解,是优先按照Bean Name查找合适的对象(要求Bean Name与被装配的属性名相同),如果没有符合的,则按照类型查找合适的对象
无法解决被自动装配参数在容器中有多个的问题
当Spring尝试创建一个通过组件扫描找到的组件类的对象时,会自动调用此组件类的构造方法,关于构造方法的调用:
单元测试?
如果类中仅有1个构造方法,无论它是无参数的,还是有参数的,Spring都会尝试调用它,如果是有参数,Spring会自动通过自动装配机制为此参数赋值
如果类中存在无参数的构造方法,无论还有没有其它构造方法,默认情况下,Spring都会自动调用无参数的构造方法
希望Spring调用某个构造方法是,只需要在构造方法上添加@Autowired注解
Value
@Value("${jwt.tokenHead}")
读取配置文件中指定位置的值
Spring MVC
主要解决了接收请求、响应结果的相关问题 (其实,并没有关注MVC中的M)
spring-webmvc 依赖项(注意版本号一致)
提供了更多的组件注解
此依赖项中内置了Tomcat,当启用项目时,会自动将当前项目编译、打包、部署到内置的Tomcat上,并启动Tomcat。
子主题
服务器
就是一台高性能的电脑 在这台电脑上安装了提供XXX服务的软件,就称为XXX服务器
举例:
安装了邮件收发服务的软件, 邮件服务器
安装了提供文件上传下载服务的软件, FTP服务器
安装了数据库软件, 就称为数据库服务器
安装了Web服务软件, Web服务器
Web服务软件 不具备任何业务功能, 可以理解为是一个容器, 用来装实现具体业务功能的组件
负责搭建底层的网络连接
负责根据客户端请求的静态资源路径找到对应的静态资源文件,并响应给客户端
负责根据客户端请求的动态资源路径找到对应的Controller并调用里面的方法
web服务器
Tomcat
常规情况下,一个tomcat并发数在100多一点
Netty
网关Gateway项目使用Netty服务器,Netty服务器内部是NIO的所以性能更好
LVS/F5
贵
Nginx
Nginx ("engine x") 是一个高性能的 HTTP 和 反向代理 服务器,也是一个IMAP/POP3/SMTP 代理服务器。 Nginx 是由 Igor Sysoev 为俄罗斯访问量第二的Rambler.ru 站点开发的,第一个公开版本 0.1.0 发布于 2004 年 10 月 4 日。其将源代码以类 BSD 许可证的形式发布,因它的稳定性、丰富的功能集、示例配置文件和低系统资源的消耗而闻名。
优势
1. 高并发响应性能非常好,官方 Nginx 处理静态文件 5万/秒
Nginx为什么快
Nginx使用NIO来实现,是它能快速的主要原因之一 从Nginx内部的结构上和运行流程上,它内部是一个主进程(Master)多个工作进程(Worker) Master负责统筹管理配置和Worker的分工 Worker来负责处理请求,作出响应 而且使用NIO既非阻塞式的,异步的来完成工作 简单来说,就是一个Worker接到从Master分配来的一个请求后,会立即对请求进行处理,但是在请求发送完成后,还没有返回响应前,Worker会继续处理别的请求,直到返回响应时,这个Worker才会去处理响应,最终每条Worker进程全程无阻塞
静态资源服务器
因为Nginx优秀的静态内容并发性能 我们常常使用它做静态资源服务器 在Nginx中保存图片,文件视频等静态资源 经常和FastDFS组合使用
FastDFS是一个开源的轻量级分布式文件系统,它对文件进行管理,功能包括:文件存储、文件同步、文件访问(文件上传、文件下载)等,解决了大容量存储和负载均衡的问题。特别适合以文件为载体的在线服务,如相册网站、视频网站等等。
2. 反向代理性能非常强。(可用于负载均衡)
实际开发中,Nginx可以用于反向代理服务器, 实际处理请求的是Tomcat服务器
3. 内存和 cpu 占用率低。(为 Apache(也是一个服务器) 的 1/5-1/10)
javax.
websocket
Session
WsSession
servlet.
尤指 Java 语言中在服务器上运行的)小型应用程序
http
HttpServletRequest
处理HTTP请求
getParameter(String 网页输入内容);
String
HttpServletResponse
addCookie(Cookie cookie);
将cookie发出
HttpSession
Session存储
setAttribute(String name, Object value);
增
removeAttribute(String name);
删
Object getAttribute(String name);
查
Cookie(String name, String value)
创建一个Cookie
setMaxAge(int expiry)
设置保存时间
annotation
tomcat ?
org.springframework.web
client.
RestTemplate远程调用
需要单独设置负载均衡 SpringCloudNetflix系统下,RestTemplate实现有一个别名叫Ribbon 如果说"Ribbon"调用,指的就是RestTemplate对象的调用过程
Dubbo功能也有限制,如果我们想调用的方法不是我们当前项目的组件或功能,甚至想调用的方法不是java编写的,那么Dubbo就无能为力了
只要能够使用浏览器访问的路径,我们都可以使用RestTemplate发送请求,接收响应
RestTemplate调用时请求以get方法居多,post方法调用代码比较繁琐,
在Application启动类,向Spring容器注入一个该类型对象
RestTemplate
T getForObject(String url, Class<T> responseType, Object... uriVariables)
// 1.第一个参数:请求的url
e.g. String url="http://localhost:20003/base/stock/reduce/count?" + "commodityCode={1}&reduceCount={2}";
// 2.第二个参数:调用的方法的返回值类型,要求编写类型的反射
// 3.第三个参数开始:往后的每一次参数都是在给url中{x}的占位符赋值
// 第三个参数赋值给{1},第四个参数赋值给{2} 以此类推
.bind.annotation.
@Controller
控制器类 Controller
添加了@Controller注解的类,才算是控制器类!
@RestController
它使用了@Controller和@ResponseBody作为元注解,所以,同时具有这2个注解的效果,
@ControllerAdvice
添加在某个类上,则此类中特定的方法(例如处理异常的方法)将作用于整个项目每次处理请求的过程中
@RestControllerAdvice
类似的还有@RestControllerAdvice,同时具有@ControllerAdvice和@ResponseBody的效果。
请求
@RequestMapping("")
可以添加在控制器类上,也可以添加在处理请求的方法上,主要用于配置请求路径,还可以配置其它与请求相关的设置,例如通过此注解的method属性来限制请求方式
注解标注方法的返回值表示的是“视图组件的名称”,接下来,会由Spring MVC框架的其它组件根据这个视图名称找到对应的视图组件(例如某个html页面),最后,会将此视图组件响应到客户端去!
@PostMapping("")
添加在处理请求的方法上,主要用于配置请求路径,及其它与请求相关的参数,是将请求方式限制为POST的@RequestMapping
@GetMapping("")
与@PostMapping类似,区别在于限制请求方式为GET
@PutMapping("")
与@PostMapping类似,区别在于限制请求方式为PUT
@DeleteMapping("")
与@PostMapping类似,区别在于限制请求方式为DELETE
@RequestParam("")
添加在处理请求的方法的参数上,用于:配置请求参数的名称、要求必须提交此参数(不允许为null)、设置请求参数的默认值(不提交时视为某值)
@PathVariable("")
添加在处理请求的方法的参数上,当设计URL时使用了占位符,则必须在方法的参数上通过此注解来获取占位符对应的值
defaultValue = WebConsts.DEFAULT_PAGE
设置默认值
响应
@ResponseBody
在前后端分离的做法中,服务器端并不会处理视图,当处理完某个请求后,向客户端响应必要的数据即可,至于这些数据如何呈现出来,应该是由客户端软件负责的!
在处理请求的方法上添加@ResponseBody,则此方法的返回值就不再是视图组件的名称,而是要响应到客户端的数据!所以,此注解也称之为“响应正文”的注解,
也可以将此注解添加在控制器类上,则默认情况下,此类中所有处理请求的方法都将响应正文!
@ExceptionHandler
添加在处理异常的方法上
servlet.config.annotation
WebMvcConfigurer
default void addCorsMappings(CorsRegistry registry)
添加跨域映射设置
CorsRegistry
CorsRegistration addMapping(String pathPattern)
路径样式
CorsRegistration allowedOriginPatterns(String... patterns)
表示支持的源,Spring 5.3 引入的属性,默认为空,与origins二选一,支持通配符的形式配置origins,比如https://*.domain1.com,该字段为list,也就是可以配置多个。
CorsRegistration allowedMethods(String... methods)
配置允许的请求类型
CorsRegistration allowedHeaders(String... headers)
配置允许的消息头
CorsRegistration allowCredentials(boolean allowCredentials)
是否需要发送过来凭证(true需要,默认false不需要)
CorsRegistration maxAge(long maxAge)
设置“预检”结果的缓存时间,单位秒,默认1800s
multipart
MultipartFile
复合文件
transferTo(File dest)
将文件生成出来
SSM(Spring SpringMVC Mybatis)
Test 核心容器(Core Container) AOP(Aspect Oriented Programming)、设备支持(Instrmentation)、消息(Messaging) 数据访问与集成(Data Access/Integeration) Web
Spring一个轻量级,依赖于反射的java开发框架。 用于企业级的应用开发,为开发Java应用程序提供全面的基础架构支持
核心特性
依赖注入(dependency injection,DI)
为对象的依赖项注入值,通常表现为自动装配
Inversion of Control,即控制反转,即:将对象的控制权交给框架
Spring容器
面向切面编程(aspect-oriented programming,AOP)
减少样板式代码
容器
依赖注入
Spring Beans
Spring Boot
主要解决了依赖管理、自动配置的相关问题。 简化重复代码 框架引入脚本, 以打钩的方式引入框架 SpringServer ServletURL: https://start.spring.io/ https://start.springboot.io/ https://start.aliyun.io/
在开发实践中,需要使用到的依赖项(框架、各种工具,等等)比较多,如果依赖项A依赖于B,依赖项C也依赖于B,但是,它们依赖的依赖项B的版本却不同,则无法正常使用!
在没有Spring Boot框架之前,每创建一个新的项目,或添加新的依赖,可能都需要做大量的配置,而各个不同的项目中,使用相同的依赖时,需要编写的配置可能是高度相似,甚至完全相同的!
Spring Boot希望它是“开箱即用的(Out Of Box)”,它自动的处理掉了许多可预测的配置,同时,它是希望遵循“约定大于配置”的思想的,即:各开发者不必关心Spring Boot是如何配置的,只需要知道Spring Boot把哪些配置项配置成什么值即可,然后,开发者只需要按照这些配置值的“约定”去写代码就行!例如,Spring Boot将组件扫描的包配置为启用类所在的包,开发者只需要将各组件类声明在此包或其子孙包下即可,根本不需要关心Spring Boot在哪里或通过什么方式配置了组件扫描!
Spring Boot指定了一系列特定名称的配置属性,例如spring.datasource.url、server.port等,按照这些名称编写的配置,会被自动应用
server: port: 10010 servlet: encoding: charset: UTF-8 spring: datasource: url: jdbc:mysql://${my.server.addr}:3306/life_tree?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&allowMultiQueries=true&allowPublicKeyRetrieval=true username: root password: root
配置服务器端口 配置数据库地址
Spring Boot支持Profile配置,任何以application-自定义名称.properties作为文件名的配置文件,都会被视为Profile配置,例如application-dev.properties、application-dev-jack.properties等
spring-boot-starter基础依赖项
Spring Boot提供了一系列的spring-boot-starter-???依赖项,这些依赖项中都包括了主流的相关依赖项,以spring-boot-starter-web为例,其中就包含了Spring MVC框架的核心依赖项spring-webmvc,也包含了响应JSON时需要使用到的jackson-databind,等等,并且,管理了这些依赖项的版本,以至于各个开发者只需要添加spring-boot-starter-web即可,由Spring Boot来决定其依赖的spring-webmvc、jackson-databind等依赖项的版本,并保证是兼容可用的!
任何以spring-boot-starter作为前缀的依赖项(例如spring-boot-starter-test、spring-boot-starter-web等),都使用spring-boot-starter作为基础依赖项
在spring-boot-starter中,包含了SLF4j日志框架的依赖项,所以,任何一个Spring Boot项目,都可以直接使用SLF4j日志框架
在spring-boot-starter中,包含了snakeyaml工具包,可以解析YAML语法的配置,所以,在Spring Boot项目中,可以使用.yml作为扩展名的配置文件,取代.properties配置文件
Spring Boot项目的启用类上添加了@SpringBootApplication注解,每个项目中只能有1个类添加此注解
启用类上的@SpringBootApplication注解还使用了@SpringBootConfiguration作为元注解,且@SpringBootConfiguration使用@Configuration作为元注解,所以,启动类本身也是一个配置类
启用类上的@SpringBootApplication注解还使用了@ComponentScan作为元注解,且没有配置组件的包,所以,默认执行组件扫描时,会扫描启用类所在的包
org.springframework.boot.autoconfigure.
AutoConfigureAfter
spring-boot-starter-test依赖项
在src/test/java下编写的测试类,当需要加载项目的各种配置和Spring环境时,需要在测试类上添加@SpringBootTest注解,且这样的测试类所在的包,与src/main/java下的组件扫描的包必须是一致的
mybatis-spring-boot-starter
当添加了数据库编程的相关依赖项时(例如spring-boot-starter-jdbc,或包括此依赖项的其它依赖,例如mybatis-spring-boot-starter中就包含了spring-boot-starter-jdbc),启用项目时,Spring Boot会自动读取连接数据库的配置(即从配置文件中读取spring.datasource.url属性的值),如果在配置文件中并未配置此属性,或此属性的值不是以jdbc:开头的,会导致项目启动失败
当添加了数据库编程的相关依赖项时,会自动配置DataSource对象
org.mybatis.spring.boot.autoconfigure.
MybatisAutoConfiguration
spring-boot-starter-web依赖项
此依赖项中包含一个嵌入式的Tomcat,当启用项目时,会自动将当前项目部署到此Tomcat上并启用Tomcat,默认占用8080端口,可通过配置文件中的server.port属性修改端口号
在Spring MVC项目中,当需要响应JSON格式的正文时,需要添加jackson-databind依赖,在spring-boot-starter-web中也包含了依赖,所以,在基于Spring Boot的Web开发中,可以直接响应JSON格式的正文
会将src/main/resources/static配置为默认的静态资源文件夹,如果需要允许访问静态资源,将相关文件放在此文件夹中即可
在创建项目时勾选了Web选项,会自动创建此static文件夹,如果未勾选,只要添加了依赖项,可自行创建此文件夹
另外还有src/main/resources/templates文件夹也可作为静态资源文件夹,但不推荐,此文件夹应该是用于存放模板视图文件的(非前后端分离项目才需要使用)
org.springframework.boot
test.context.
SpringBootTest
测试类
autoconfigure.
@SpringBootApplication
标记类为启动类
SpringApplication
服务器 运行后启用 URL(统一资源定位): http://localhost:8080/xxx.html 协议名称 主机地址信息(ip地址+端口) 资源抽象路径 DNS域名解析服务 域名 (大众) ip+端口 www.xxx.com 255.255.255.255:8080
由spring框架自动生成的 Spring项目启动类
run(MyFirstSpringBootApplication.class, args);
运行Spring框架应用,启用http://localhost:8080/xxx.html
org.junit.jupiter.
juniit: 一个Java语言的单元测试框架
before
测试前运行代码
api.
Test
可运行的测试方法
Spring Data
Spring Data是Spring提供的一套连接各种第三方数据源的框架集 我们需要使用的是其中连接ES的Spring Data Elasticsearch 官网中列出了SpringData支持连接操作的数据源列表
<!-- Spring Data Elasticsearch 整合SpringBoot的依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-elasticsearch</artifactId> </dependency>
在ES的原生状态下,我们java代码需要使用socket访问ES,但是也是过于繁琐,我们可以使用SpringData框架简化
# 配置ES的ip地址和端口号 spring.elasticsearch.rest.uris=http://localhost:9200 # 设置日志门槛 logging.level.cn.tedu.search=debug # SpringDataElasticsearch框架中日志输出的专用类也要设置日志门槛 logging.level.org.elasticsearch.client.RestClient=debug
org.springframework.data.
annotations.
@Document(indexName = "items")
标记当前类是ES框架对应的实体类
indexName 指定ES中对应索引的名称,运行时若不存在SpringData会自行创建
@Id
标记当前实体类的主键属性
@Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_max_word")
标记普通属性的注解
type用于指定属性的类型,FieldType是一个枚举,
Text
指支持分词的字符串类型
Keyword
指不分词的字符串类型
index = false就是不创建索引的设置
虽然不创建索引,但是ES还是保存这个数据的
如果是需要中文分词的,还需要设置中文分词器
elasticsearch.
repository.
ElasticsearchRepository
连接/操作ES,自动生成指定实体类类型对应索引的基本增删改查
接口后面跟的泛型含义<[实体类类型],[实体类主键类型]>
<S extends T> S save(S entity);
添加文档
Iterable<S> saveAll(Iterable<S> entities)
批量添加
Optional<T> findById(ID id)
单查
Iterable<T> findAll();
全查
annotations
Query
自定义ES查询
domain
Pageable
分页操作
AbstractPageRequest
PageRequest
static PageRequest of(int page, int size)
要查询的页码?age-1
注意:0为第一页
非人哉
每页条数的设置
Page<T>
是Iterable的子接口除了list信息外 还包含了分页信息
int getTotalPages();
long getTotalElements();
总条数
int getNumber(); +1
当前页
boolean isFirst();
boolean isLast();
尾页
实用功能一: 实现查询功能
创建和ES关联的实体类
entity.Item
作为操作ES的数据载体
全参构造,链式
创建操作ES的持久层
规范名称为repository(仓库) Repository这个单词就是对Spring家族持久层包名,类名,接口名的规范
repository.ItemRepository
测试ES
SearchServiceImpl
添加到es
从es中查询
SpringData自定义查询
编写的查询要遵循SpringData给定的格式,来定义一个方法名,SpringData会根据方法名自动推测出查询意图,生成能够完成该查询的语句
Page<Item> queryItemsByTitleMatchesOrBrandMatchesOrderByPriceDesc( String title, String brand, Pageable pageable);
单条件查询
query(查询):表示当前方法是一个查询方法,类似sql语句中的select
Item/Items:表示要查询的实体类名称,返回的如果是集合需要在实体类名称后加s
By(通过\根据):表示开始设置查询的条件,等价于sql语句中的where
Title:表示要查询的字段,可以设置任何Item类中的字段
Matches(匹配):表示执行查询的条件,matches是匹配字符串的关键字,如果字符串是支持分词的,就按照分词匹配,类似于sql语句中的like
多条件查询
多个条件时,方法名会按照规则编写多个条件,参数也要根据条件的数量来变化
声明参数时,要按照方法名中需要参数的次序声明对应的参数,参数对应规则和参数名称无关
多个条件之间我们要使用and或or来分隔,以表示多个条件间的逻辑运算关系
排序查询
方法名称中添加OrderBy关键字,指定排序的字段和排序的方向
分页查询
返回值方面:分页查询时,我们返回给前端的数据不但要包含查询到的当前页数据,还要包含分页信息
// 分页信息指:当前页码,每页条数,总页数,总条数,有没有上一页有没有下一页...等
// 返回值修改为Page类型,就能满足这个要求
参数方面:执行分页查询必须指定要查询的页码,和每页的条数
// 这两个数据可以封装在Pageable类型的参数中,框架规定,这个参数必须放在参数列表中最后一个
分页处理,统一分页规则
SearchController
Spring Security
解决认证和授权的问题
sping-boot-starter-security依赖项
输入任何请求必须登录,会重定向到“/login”
此登陆页面为框架自带
默认用户名:user
启动项目时会在日志中随机生成一个UUID作为默认的密码
登录成功后,"/logout"退出登录,之后会重定向到登陆页面
登陆成功后,Get请求允许自由访问,Post请求不允许访问403
为了防止伪造的跨域攻击
org.springframework.security
authentication
AuthenticationException
认证不通过
AuthenticationServiceException
InternalAuthenticationServiceException
用户
BadCredentialsException
密码
UsernamePasswordAuthenticationToken(Object principal, Object credentials)
1.获取认证
当事人: 用户名
认证信息: 密码
AbstractAuthenticationToken
AuthenticationManager
2.执行认证 需要放入spring容器
Authentication authenticate(Authentication authentication) throws AuthenticationException;
.config.annotation.web.
configuration.
WebSecurityConfigurerAdapter
configure(HttpSecurity http)
要求所有的请求都需要认证
builders.
HttpSecurity
CsrfConfigurer<HttpSecurity> csrf()
防止“伪造的跨域攻击“防御机制(框架默认开启)
伪造的跨域攻击
源自服务器对客户端浏览器的信任
主流浏览器为多选项卡,在一个选项卡中登录成功其他选项卡也都是登陆状态
默认认证机制基于Session
登录成功后,会携带SessionID,只要是在相同浏览器中无论在哪个选项卡中都携带相同的SessionID,会视为“已登录”状态。
应对
再次输入密码
手机接收验证码
B disable()
禁用
core.
userdetails.
UsernameNotFoundException
User
User(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities)
用户名
密码
启用
true
true
true
权限
UserDetails
信息获取
UserDetailsService
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
Spring Security在处理认证时,会自动调用该方法
Spring Security会根据表单中提交的用户名来调用此方法,并获得用户详情,接下来,由Spring Security去判断用户详情中的信息,例如密码是否正确、账号状态是否正常等。
Authentication
认证
authority
SimpleGrantedAuthority
权限
context.
SecurityContextHolder
static SecurityContext getContext()
static void clearContext()
清空信息
SecurityContext
存储认证信息
setAuthentication(Authentication authentication)
与Session绑定,存入认证信息
Authentication getAuthentication();
获取认证信息
crypto.
password
PasswordEncoder
密文编码器接口
密码
密码加密
加密算法(只用于保证传输过程的安全)
消息摘要算法
SHA(Secure Hash Algorithm)
SHA-1(160位算法)
SHA-256(位算法)
384(位算法)
512(位算法)
(Message Digest)
MD2(位算法)
4(位算法)
5(位算法)
String encodePassword = DigestUtils.md5DigestAsHex(rawPassword.getBytes());
加“盐值salt”
随机盐需要记录,否则无法验证
验证码
恶意访问客户端禁止访问机制
封IP
封账号
非常慢的加密算法
加上依赖项,项目暂时跑不起来
BCryptPasswordEncoder
encode(String rawPasssword) 编码,会保存随机盐作为密码一部分
matchs(rawPasssword,encodePassword) 验证
解密
约简函数
预计算的哈希链
牺牲时间,换取空间
如:彩虹表
穷举式的暴力破解
bcrypt
BCryptPasswordEncoder
此类可以用于处理Bcrypt算法的编码、验证密码,推荐使用这种密码编码器
String encode(CharSequence rawPassword)
标注
原文编码为密文
boolean match(rawPassword,encodedPassword)
原文与密文匹配
access
AccessDeniedException
没有权限
prepost
PreAuthorize
@PreAuthorize("[权限名称]")
判断登录的用户是否具备这个权限,其实主要作用还是判断用户是否登录
SpringSecurity在运行被其标记的方法之前进行核查
如果不具备这个权限会返回403状态码
功能一: 实现jwt单点登录
configuration
SecurityConfiguration
将编码器对象 放入到spring容器中
无论你认为密码是否需要加密,Spring Security处理认证的过程中,在验证密码是否正确时,都会自动调用此接口对象的matches()方法,如果在Spring容器中没有此接口的对象,将无法验证密码。 所以,即使使用没有加密的密码,也需要配置NoOpPasswordEncoder。
@Bean public PasswordEncoder passwordEncoder() { // return NoOpPasswordEncoder.getInstance(); // NoOpPasswordEncoder是“不加密”的密码编码器 return new BCryptPasswordEncoder(); }
将认证管理器对象 放入到spring容器中
重写 AuthenticationManager authenticationManagerBean() 并加上@Bean注解
重写 configure(HttpSecurity http)
删除调用的父类方法
两种配置语法
对于配置不同的内容,可以分开来配置,即使用多条语句,每条语句都调用参数http的方法
如果要使用链式语法,当“打点”后不能调用相关的配置方法,就调用and()方法,此方法会返回当前参数对象,即HttpSecurity对象,然后继续“打点”进行其它配置。
配置步骤
设置白名单请求
http.authorizeRequests()//请求认证授权
.mvcMatchers(urls)//匹配请求路径
String urls ={}
浏览器F12 网络请求状态
*:单级通配符
**:若个级通配符
"/**/*.css", "/**/*.js",
.permitAll()//不需要认证
出现在前 优先级高
.anyRequest()//所有请求
注意:以上anyRequest()其实表示的是 “任何请求”或者“所有请求”,并非“其它任何请求”! 以上配置的机制是优先原则
.authenticated();//需要认证
出现在后 优先级低
将“防止伪造的跨域攻击”防御机制禁用
解放post请求
http.csrf().disable;
开启登录表单
设置白名单步骤中被禁用
http.formLogin();
security
使用自定义的用户名和密码 完成后,再次启用项目,在控制台可以看到Spring Security不再生成随机的UUID密码了,所以,原本的user临时账号已经不再可用,必须使用以上类中配置的账号密码才可以登录!
UserDetails
UserDetailsServiceImpl
UserDetails loadUserByUsername(String s)
用户存在, 返回详情 (当用户认证通过后,此信息会作为principal记录于认证中)
信息来自于数据库 字段包括:用户id(在User的基础上拓展) 用户名,密码,是否锁定,是否过期 凭证是否过期,权限
功能
前后端分离登陆
添加登录功能
ServiceImpl
获取认证
执行认证,认证完成后用户详情信息会作为principal记录于认证中
制成jwt,返回给前端
前端在每次请求时将JWT Token放入HTTP请求头中的Authorization属性中
请求URL放行白名单
功能
管理员(用户)的权限设计
RBAC 的设计原则
Role-Based Access Controller: 基于角色的访问控制
用户
角色
权限
实现访问控制
查询管理员权限
向对象中封装查询出来的权限信息
.authorities(loginInfo.getPermissions().toArray(new String[]{}))
通过Spring Security框架来检查登录的用户是否具有权限发起某些请求!
SecurityConfiguration
@EnableGlobalMethodSecurity(prePostEnabled = true)
启用方法级别的安全检查(即权限检查)
Controller
@PreAuthorize("hasAuthority('/ams/admin/delete')")
配置权限规则
hasRole('yyy')
专门用来判断数组中是否具备某个角色的
实际上判断的是在数组中是否有ROLE_yyy的资格
用户无此权限时,将抛出异常
AccessDeniedException
在GlobalExceptionHandler中添加处理以上异常的方法
forbidden 40300
添加管理员时添加角色
DTO List<Long> roleIds
遍历对象的角色编号集合
添加管理员编号和角色编号到关联表
功能
Jwt的发放和解析
Token
1. 客户端使用用户名和密码请求登录 2. 服务端收到请求,验证用户名和密码 3. 验证成功后,服务端会签发一个token,再把这个token返回给客户端 4. 客户端收到token后可以把它存储起来,比如放到cookie中 5. 客户端每次向服务端请求资源时需要携带服务端签发的token,可以在cookie或者 header中携带 6. 服务端收到请求,然后去验证客户端请求里面带着的token,如果验证成功,就向客户端返回请求数据
JWT(json web token)
重新组织数据
<!-- JJWT(Java JWT) --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency>
io.jsonwebtoken
ExpiredJwtException
JWT过期
MalformedJwtException
JWT格式错误
Jwts
static JwtBuilder builder()
1.开始创建
JwtBuilder setHeaderParam(String name, Object value);
2.设置头部参数
JwtBuilder setClaims(Map<String, Object> claims)
3.设置承载,即记录的用户信息。
id
username
权限的json字符串
JwtBuilder setExpiration(Date exp);
4.设置到期时间
JwtBuilder signWith(SignatureAlgorithm alg, String base64EncodedSecretKey)
5.签名信息,接收后可以校验是否被篡改
署名算法:SignatureAlgorithm.HS256
签名密钥
String compact();
6.压缩成型
static JwtParser parser()
1.解析
JwtParser setSigningKey(String base64EncodedKeyBytes);
2.输入密钥
Jws<Claims> parseClaimsJws(String claimsJws)
3.传入jwt
Jwt
B getBody();
4. 获得claims
Claims
T get(String claimName, Class<T> requiredType)
获取 ID username 权限
jwt存储
写入JWT之前,将权限列表转换成JSON格式的字符串
fastjson
实现对象与JSON的相互转换
<!-- fastjson:实现对象与JSON的相互转换 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.75</version> </dependency>
String jwt = Jwts.builder()//构建器 .setHeaderParam("alg","HS256") .setHeaderParam("typ","jwt") .setClaims(claims) .setExpiration(date) .signWith(SignatureAlgorithm.HS256, secretKey) .compact();
Header部分固定为36字符
Payload根据存入的数据长度来决定
Signature部分固定为43字符
JWT数据的总长度至少113字符
jwt解析
当客户端成功通过认证(登录成功)后,客户端将得到服务器端响应的JWT,在后续的访问中,客户端有义务携带JWT来向服务器端发起请求,如果客户端未携带JWT,即使此前成功通过认证,服务器端也将视为“未通过认证(未登录)”。
服务器端应该尝试接收客户端携带的JWT数据,并尝试解析,并将解析得到的数据(例如管理员的id、用户名等)用于创建认证对象(Authentication),将此认证对象存入到SecurityContext中。
业内惯用的做法是:
服务器端会在请求头中名为Authorization的属性中获取JWT,则客户端应该按照此标准来提交请求。
在服务器端,应该在接收到任何请求的第一时间,就尝试获取JWT,则可以选用**过滤器(Filter)**来实现此效果。
org.springframework.web.filter
OncePerRequestFilter
提前对请求过滤
javax.servlet
FilterChain
doFilter(ServletRequest request, ServletResponse response)
放行
filter
JwtAuthorizationFilter
doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
从请求头中获取jwt
判断得到JWT的基本有效性。
StringUtils.hasText()
当接收到的JWT为null时,肯定没有必要尝试解析,同理,如果JWT为""空字符串,或仅有空白(空格、TAB制表位等)组成的字符串,都可以视为是无效的,可以通过StringUtils.hasText()方法进行检查。
长度不小于113
尝试解析JWT
Claims claims = Jwts.parser()//解析器 .setSigningKey(secretKey) .parseClaimsJws(jwt).getBody();
获取请求
将登录信息创建为Authentication认证对象
当事人(Principal)
权限(Authorities)
解析JWT得到的id和username封装起来,用于创建Authentication对象
不必包含凭证(Credentials)
将Authentication对象存入到SecurityContext中
Spring Security框架始终根据SecurityContext中有没有认证信息来判断是否通过认证(是否已成功登录),也通过此认证信息来检查权限。
放行
处理解析JWT时可能出现的异常
解析JWT是发生在过滤器中,过滤器是最早接收到请求的组件,此时其它组件(包括控制器、全局异常处理器等)都还没有开始处理此请求,而全局异常处理器只能处理由控制器抛出的异常,所以,此处并不适用,只能使用try...catch进行处理。
关于复杂请求的预检(PreFlight)
单点登录(SSO,Single Sign On)
在分布式项目中,用户只需要在某一服务上登陆, 其他服务器也能够识别用户身份。
共享Session
可达到类似的效果
但不可以设置较长的有效期
适用于小型的,用户量不大的微服务项目
将登录成功的用户信息共享给Redis 其他模块根据sessionId获得Redis中保存的用户信息即可 这样做最大的缺点就是内存严重冗余,不适合大量用户的微服务项目
JWT单点登录
其他服务器只需要具备解析JWT,并将解析成功的认证信息存入到SecurityContext中即可。
spring-boot-starter-security, jjwt, fastjson
SecurtyConfigration
ServiceCode
JwtAuthorizationFilter
LoginPrincipal
Spring Web Services
Spring Cloud
SpringCloud是由Spring提供的一套能够快速搭建微服务架构程序的框架集
Netflix
alibaba
微服务的注册中心
Nacos
Nacos是Spring Cloud Alibaba提供的一个软件 这个软件主要具有注册中心和配置中心(课程最后讲解)的功能
<!-- Nacos注册依赖 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency>
spring: application: #设置当前应用的名称,提交到Nacos作为模块名称 name: nacos-stock cloud: nacos: discovery: #配置nacos的位置 server-addr: localhost:8848 # ephemeral 设置当前项目启动时注册到nacos的类型true(默认):临时实例false:永久化实例 ephemeral: true
Nacos心跳机制
注册到Nacos的微服务项目都遵循心跳机制
持久化实例(永久实例)
持久化实例启动时向nacos注册,nacos会对这个实例进行持久化处理
心跳包的规则和临时实例一致,只是不会将该服务从列表中剔除
临时实例
目的是判断微服务模块的运行状态
项目启动后
每5s会向Nacos发送一个心跳包
这个心跳包中包含了当前服务的基本信息
Nacos接收到这个心跳包
如果发现这个服务的信息不在注册列表中,就进行注册,
如果这个服务的信息在注册列表中就表明这个服务还是健康的
如果Nacos15秒内没接收到某个服务的心跳包,Nacos会将这个服务标记为不健康的状态
如果30秒内没有接收到这个服务的心跳包,Nacos会将这个服务从注册列表中剔除
父项目(父认子)
子项目
子项目
子项目
org.springframework.cloud.client.
loadbalancer.
LoadBalanced
启动负载均衡的注解
配置中心
:在微服务的环境下,将项目需要的配置信息保存在配置中心,需要读取时直接从配置中心读取,方便配置管理的微服务工具
使用配置中心的原因就是能够达到高效的修改各模块配置的目的 一个微服务项目有很多子模块,这些子模块可能在不同的服务器上,如果有一些统一的修改,我们要逐一修改这些子模块的配置,由于它们是不同的服务器,所以修改起来很麻烦 如果将这些子模块的配置集中在一个服务器上,我们修改这个服务器的配置信息,就可以修改所有子模块的信息,这个服务器就是配置中心
Nacos做配置中心,支持各种格式\类型的配置文件 properties\yaml(yml)\txt\json\xml等
<!-- Nacos配置中心的依赖 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency> <!-- 支持SpringCloud项目加载\读取系统配置的依赖 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bootstrap</artifactId> </dependency>
spring: cloud: nacos: config: # 设置配置中心的ip和端口 server-addr: localhost:8848 # namespace: 是可以设置命名空间的,默认public,可以省略 # group默认DEFAULT_GROUP默认也可以省略 group: DEFAULT_GROUP # 指定配置文件的后缀名 file-extension: yaml # 配置中心约定,当确定命名空间和分组名称以及后缀名之后 # 当前项目会从配置中心中自动读取配置文件[模块注册名称].[配置的后缀名]的配置信息 # 当前项目注册到nacos的名称是nacos-cart,后缀名为yaml # 所以会自动从配置中心读取配置名称为nacos-cart.yaml的信息
微服务间的调用
Dubbo
Dubbo是一套RPC框架。既然是框架,我们可以在框架结构层面,定义Dubbo中使用的通信协议,使用的序列化框架技术,而数据格式由Dubbo定义,我们负责配置之后直接通过客户端调用服务端代码。 可以说Dubbo就是RPC概念的实现 Dubbo是SpringCloudAlibaba提供的框架 能够实现微服务相互调用的功能!
RPC(Remote Procedure Call, 远程过程调用)
实现远程调用的一套标准
实现两台或多台计算机相互调用方法的解决方案
通信协议:远程调用的通信方式
序列化协议:通信内容的格式。
发展历程
Dubbo框架支持多种通信协议和序列化协议,可以通过配置文件进行修改
支持的通信协议
dubbo协议(默认)
rmi协议
hessian协议
http协议
webservice
.....
支持的序列化协议
hessian2(默认)
java序列化
compactedjava
nativejava
fastjson
dubbo
fst
kryo
Dubbo默认情况下,支持的协议有如下特征
采用NIO单一长链接
优秀的并发性能,但是处理大型文件的能力差
模块在同一注册中心,支持的有
Nacos(推荐)
Redis
zookeeper
。。。
服务发现
消费端自动发现服务列表的能力
微服务之间在无需IP地址和端口号的情况下实现通信
流程
子主题
子主题
配置
<!--Dubbo在SpringCloud中使用的依赖--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-dubbo</artifactId> </dependency>
dubbo: protocol: #-1 自动寻找端口的功能 #生成端口号的规则:从20880开始寻找可用端口号,若被占用就往后+1,直到找到可用端口号。 port: -1 #链接名称 name: dubbo registry: #注册中心和地址 address: nacos://localhost:8848 consumer: #当前项目启动时检查所有服务可用,(不检查)减少因服务不可用而导致的错误 check: false
org.apache.dubbo.
config.
spring.context.annotation.
EnableDubbo
启动类上添加,服务启动时服务才能正确注册到Nacos
annotation.
DubboService
将标记的逻辑层实现类中的所用方法注册到Nacos(注册中心),使之可以被其他模块调用
DubboReference
找到nacos里的实现类,消费其他模块的服务
Dubbo生产者功能改写
.service
业务逻辑层接口
.webapi
提供服务的业务逻辑层实现类要添加@DubboService注解
SpringBoot启动类要添加@EnableDubbo注解
Dubbo生产消费者功能改写
添加额外的消费业务逻辑层接口的依赖
.service
业务逻辑层接口
.webapi
提供服务的业务逻辑层实现类要添加@DubboService注解
SpringBoot启动类要添加@EnableDubbo注解
Dubbo消费者实现微服务调用
项目均要开启 启动顺序一般启动生产者,后启动消费者
业务逻辑层远程调用前,模块使用@DubboReference注解获取业务逻辑层实现类对象
@DubboReference private IStockService stockService;
常见错误
RpcException或RemotingException异常
R
电脑的防火墙\杀毒软件不让Dubbo访问某些网络资源导致的
也有和无线wifi网卡驱动冲突造成的
S
如果不影响运行,是可以无视掉的
(disabled, not registered or in blacklist)
R
消费者无法在指定的位置找到需要的服务
S
检查调用目标的服务是否启动(上面示例中可能是因为stock模块没有启动导致的)
检查被调用的目标服务SpringBoot启动类是否编写的@EnableDubbo注解
检查被调用的模块的业务逻辑层实现类是否编写了@DubboService注解
负载均衡(Loadbalance)
在实际项目中,一个服务基本都是集群模式的,也就是多个功能相同的项目在运行,这样才能承受更高的并发 这时一个请求到这个服务,就需要确定访问哪一个服务器,那么问题来了,哪个服务器会被访问呢?
Dubbo框架内部支持负载均衡算法,能够尽可能的让请求在相对空闲的服务器上运行
在不同的项目中,可能选用不同的负载均衡策略,以达到最好效果
Dubbo内置负载均衡策略算法
实际运行过程中,每个服务器性能不同 在负载均衡时,都会有性能权重,这些策略算法都考虑权重问题
random loadbalance:随机分配策略(默认)
生成随机数 在哪个范围内让哪个服务器运行
假设我们当前3台服务器,经过测试它们的性能权重比值为5:3:1 下面可以生成一个权重模型
优点
算法简单,效率高,长时间运行下,任务分配比例准确
缺点
偶然性高,如果连续的几个随机请求发送到性能弱的服务器,会导致异常甚至宕机
round Robin Loadbalance:权重平均(平滑)分配
1>A 2>A 3>A 4>A 5>A 6>B 7>B 8>B 9>C 10>A ......
上面的安排中,连续请求一个服务器肯定是不好的,我们希望所有的服务器都能够穿插在一起运行
Dubbo2.7之后更新了这个算法使用"平滑加权算法"优化权重平均分配策略 服务器运行完成后该服务器减权(原始权数-所有服务器的总权数) 减权完成后再按照原本的权重给各个服务器赋权,以确定谁来处理下个请求。
优点
能够尽可能的在权重要求的情况下,实现请求的穿插运行交替运行 ,不会发生随机策略中的偶发情况
缺点
服务器较多时,可能需要减权和复权的计算,需要消耗系统资源
leastactive Loadbalance:活跃度自动感知分配
记录每个服务器处理一次请求的时间 按照时间比例来分配任务数,运行一次需要时间多的分配的请求数较少
计时器更耗资源,且服务器本身出现波动很正常
consistanthash Loadbalance:一致性hash算法分配
根据请求的参数进行hash运算 以后每次相同参数的请求都会访问固定服务器 因为根据参数选择服务器,不能平均分配到每台服务器上 使用的也不多
微服务的分布式事务
Seata
Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务 也是Spring Cloud Alibaba提供的组件 Seata官方文档 https://seata.io/zh-cn/ 更多信息可以通过官方文档获取
解决的问题
我们之前学习了单体项目中的事务 使用的技术叫Spring声明式事务 能够保证一个业务中所有对数据库的操作要么都成功,要么都失败,来保证数据库的数据完整性 但是在微服务的项目中,业务逻辑层涉及远程调用,当前模块发生异常,无法操作远程服务器回滚 这时要想让远程调用也支持事务功能,就需要使用分布式事务组件Seata Seata保证微服务远程调用业务的原子性 Seata将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
运行原理
事务协调器TC(Coordination)
事务管理器TM
资源管理器RM
我们项目使用AT(自动)模式完成分布式事务的解决
AT模式的运行有一个非常明显的前提条件,这个条件不满足,就无法使用AT模式 这个条件就是事务分支都必须是操作关系型数据库(Mysql\MariaDB\Oracle) 因为关系型数据库才支持提交和回滚,其它非关系型数据库都是直接影响数据(例如Redis) 所以如果我们在业务过程中有一个节点操作的是Redis或其它非关系型数据库时,就无法使用AT模式
AT模式运行过程
1.事务的发起方(TM)会向事务协调器(TC)申请一个全局事务id,并保存
2.Seata会管理事务中所有相关的参与方的数据源,将数据操作之前和之后的镜像都保存在undo_log表中,这个表是seata组件规定的表,没有它就不能实现效果,依靠它来实现提交(commit)或回滚(roll back)的操作
3.事务的发起方(TM)会连同全局id一起通过远程调用,运行资源管理器(RM)中的方法
4.RM接收到全局id,去运行指定方法,并将运行结果的状态发送给TC
5.如果所有分支运行都正常,TC会通知所有分支进行提交,真正的影响数据库内容,
反之如果所有分支中有任何一个分支发生异常,TC会通知所有分支进行回滚,数据库数据恢复为运行之前的内容
TCC模式
简单来说,TCC模式就是自己编写代码完成事务的提交和回滚(业务入侵)
在TCC模式下,我们需要为参与事务的业务逻辑编写一组共3个方法
prepare
准备
prepare方法是每个模块都会运行的方法
当所有模块的prepare方法运行都正常时,运行commit
commit
提交
当任意模块运行的prepare方法有异常时,运行rollback
rollback
回滚
优点
虽然代码是自己写的,但是事务整体提交或回滚的机制仍然可用 仍然由TC来调度
缺点
每个业务都要编写3个方法来对应,代码冗余,而且业务入侵量大
SAGA模式
SAGA模式的思想是对应每个业务逻辑层编写一个新的类,可以设置指定的业务逻辑层方法发生异常时,运行当新编写的类中的代码 相当于将TCC模式中的rollback方法定义在了一个新的类中
这样编写代码不影响已经编写好的业务逻辑代码
一般用于修改已经编写完成的老代码
缺点是每个事务分支都要编写一个类来回滚业务,
会造成类的数量较多,开发量比较大
XA模式
支持XA协议的数据库分布式事务,使用比较少
配置Seata
数据库 undo_log表
Srata开始工作时,会将方法相关对象,序列化后保存在该表中
序列化的方式支持很多中,常见的jackson格式序列化的情况下,不支持java对象LocalDataTime类型的序列化
InvalidDefinitionException
1.将序列化过程中LocalDataTime类型转换为Date
2.将Seata序列化转换为kryo类型,但是需要在pom文件中添加依赖
<!--解决seata序列化问题--> <dependency> <groupId>io.seata</groupId> <artifactId>seata-serializer-kryo</artifactId> </dependency>
#seata服务端 seata: tx-service-group: csmall_group service: vgroup-mapping: csmall_group: default grouplist: default: ${my.server.addr}:8091 client: undo: log-serialization: kryo
-- 注意此处0.7.0+ 增加字段 context CREATE TABLE `undo_log` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `branch_id` bigint(20) NOT NULL, `xid` varchar(100) NOT NULL, `context` varchar(128) NOT NULL, `rollback_info` longblob NOT NULL, `log_status` int(11) NOT NULL, `log_created` datetime NOT NULL, `log_modified` datetime NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
<!-- Seata和SpringBoot整合依赖 --> <dependency> <groupId>io.seata</groupId> <artifactId>seata-spring-boot-starter</artifactId> </dependency>
<!-- Seata 完成分布式事务的两个相关依赖(Seata会自动使用其中的资源) --> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> </dependency>
(RM)连接数据库要加的依赖
seata: # 定义事务的分组名称,一般是以项目为单位,用于区分其他不同项目用的 tx-service-group: csmall_group service: vgroup-mapping: # 设置csmall_group分组使用seata默认(default)的配置内容 csmall_group: default grouplist: # 设置seata的ip地址和端口号 default: localhost:8091
io.seata.spring.
annotation.
GlobalTransactional
激活Seata功能非常简单,只要在起点业务的业务逻辑方法上添加专用的注解即可
设置了分布式事务的起点,相当于AT事务模型中TM(事务管理器) 最终效果就是当前方法开始运行后,所有远程调用操作数据库和本模块操作数据库的业务会被管理在同一个事务中,也就是这些数据库操作要么都执行要么都不执行
他调用的所有远程模块都是RM
先启动nacos,再启动seata
然后按顺序启动四个服务
cart\stock\order\business
微服务的限流
Sentinel
Sentinel英文翻译"哨兵\门卫" Sentinel也是Spring Cloud Alibaba的组件 随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。 限流针对的是控制器方法 我们找一个简单的模块来测试和观察限流效果
为了保证服务器运行的稳定性,在请求数到达设计最高值时,将过剩的请求限流,保证在设计的请求数内的请求能够稳定完成处理
丰富的应用场景
双11,秒杀,12306抢火车票
完备的实时状态监控
可以支持显示当前项目各个服务的运行和压力状态,分析出每台服务器处理的秒级别的数据
广泛的开源生态
很多技术可以和Sentinel进行整合,SpringCloud,Dubbo,而且依赖少配置简单
完善的SPI扩展
Sentinel支持程序设置各种自定义的规则
<!-- Sentinel整合SpringCloud依赖 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency>
spring: cloud: sentinel: transport: # 配置Sentinel的提供的仪表台的位置 dashboard: localhost:8080 # 执行限流的端口号,每个项目必须设置不同的限流端口号,例如cart模块要限流就不能和下面端口号重复 port: 8721
com.alibaba.csp.sentinel.
slots.block.
BlockException
被限流
annotation.
SentinelResource
标记在控制层方法上,
在该方法运行一次后,会被Sentinel仪表台检测到,并显示在仪表台中,
如果这个方法不运行,仪表台中不会显示这个方法
value参数配置会作为方法的名称出现在仪表台上
blockHandler = "blockError"
自定义限流方法
// Sentinel自定义限流方法定义规则
// 1.访问修饰符必须是public
// 2.返回值类型必须和被限流的控制器方法一致
// 3.方法名必须和控制器限流注解中blockHandler属性定义的方法名称一致
// 4.方法参数,先编写和控制器方法一致的参数信息,再额外添加BlockException类型的参数
fallback = "fallbackError"
自定义降级方法
这个方法会在原有控制器方法运行发生异常时触发 (实际开发中,发生异常后可能会调用一些老版本代码,所以称之为降级)
自定义降级方法和自定义限流方法的格式基本是一致的
// 区别是降级方法参数额外的异常类型要使用Throwable
簇点链路
流控
QPS:是每秒请求数
单纯的限制在一秒内有多少个请求访问控制器方法
并发线程数:是当前正在使用服务器资源请求线程的数量
T 限制的是使用当前服务器的线程数
并发线程数测试可能需要阻塞当前控制器方法一段时间,方便测试
编写完毕后,需要重新启动stock,并运行一遍方法
然后设置并发线程数的限流规则
打开两个测试窗口,第一个窗口发送测试阻塞后,切换到第二个创建发送请求测试,就会被限流
微服务的网关
SpringGateway网关
Gateway框架不是阿里写的,是Spring提供的
奈非框架
现在还有很多旧项目维护是使用奈非框架完成的微服务架构
Nacos对应Eureka都是注册中心
Dubbo对应Ribbon+feign都是实现微服务远程RPC调用的组件
Sentinel对应Hystrix都是做项目限流熔断降级的组件
Gateway对应Zuul都是网关组件
指网络中的关口\关卡 网关就是当前微服务项目的"统一入口" 程序中的网关就是当前微服务项目对外界开放的统一入口 所有外界的请求都需要先经过网关才能访问到我们的程序 提供了统一入口之后,方便对所有请求进行统一的检查和管理
网关的主要功能有
将所有请求统一经过网关
网关可以统一将所有请求路由到正确的模块\服务上
网关可以对这些请求进行检查
网关方便记录所有请求的日志
<dependencies> <!-- SpringGateway的依赖 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <!-- Nacos依赖 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!-- 网关负载均衡依赖 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-loadbalancer</artifactId> </dependency> </dependencies>
这个项目也要注册到Nacos注册中心,因为网关项目也是微服务项目的一个组成部分
server: port: 9000 spring: application: name: gateway cloud: nacos: discovery: # 网关也是微服务项目的一个模块,需要注册到Nacos才能生效 server-addr: localhost:8848 gateway: # routes是路由的意思,表示开始配置路由设置,它是一个数组的类型 routes: # 数组类型赋值时,每个数组元素都要以"-"开头,一个"-"之后的所有内容,都是这个元素包含的内容 # id表示当前路由的名称,这个名称和之前我们学习的任何名称没有关联, # 唯一的要求就是不能和之后出现的路由id重复 - id: gateway-beijing # uri是设置当前路由配置的目标服务器名称,"beijing"就是目标服务器注册到nacos的名称 # lb就是LoadBalance的缩写,意思是当前路由支持负载均衡 uri: lb://beijing # predicates是断言的意思,断言指满足某些条件时执行某些操作的设置 predicates: # predicates属性也是数组类型的设计,赋值也要以"-"开头 # 这个断言的含义就是如果访问当前网关项目路径以/bj/开头,那么就路由访问beijing服务器 # ↓ P要大写!!!!! **是通配任何路径 - Path=/bj/**
网关多路由配置[{},{}]
- id: gateway-shanghai
当前路由的名称,这个名称和之前我们学习的任何名称没有关联, 唯一的要求就是不能和之后出现的路由id重复
# 数组类型赋值时,每个数组元素都要以"-"开头,一个"-"之后的所有内容,都是这个元素包含的内容
uri: lb://shanghai
uri是设置当前路由配置的目标服务器名称,"beijing"就是目标服务器注册到nacos的名称
# lb就是LoadBalance的缩写,意思是当前路由支持负载均衡
predicates: - Path=/sh/**
# predicates是断言的意思,断言指满足某些条件时执行某些操作的设置
# predicates属性也是数组类型的设计,赋值也要以"-"开头
# 这个断言的含义就是如果访问当前网关项目路径以/sh/开头,那么就路由访问shanghai服务器
# ↓ P要大写!!!!! **是通配任何路径
内置过滤器
内置过滤器允许我们在路由请求到目标资源的同时,对这个请求进行一些加工或更改
filters: - AddRequestParameter=age,18
作用是在请求中添加参数和它对应的值
过滤器的存在,控制器可以获取网关过滤器添加的参数值
优先级低
其他内置过滤器和自定义过滤器的使用,同学们可以查阅相关文档自己了解
使用网关路由访问
localhost:9000/bj/show 等价于访问 localhost 9001/bj/show
http://localhost:9000/bj/show可以访问beijing服务器的资源
http://localhost:9000/sh/show可以访问shanghai服务器的资源
以此类推,再有很多服务器时,我们都可以仅使用9000端口号来将请求路由到正确的服务器
就实现了gateway成为项目的统一入口的效果
动态路由
网关项目的配置会随着微服务模块数量增多而变得复杂,维护的工作量也会越来越大 所以我们希望gateway能够设计一套默认情况下自动路由到每个模块的路由规则 这样的话,不管当前项目有多少个路由目标,都不需要维护yml文件了 这就是我们SpringGateway的动态路由功能
gateway: discovery: locator: # 开始SpringGateway动态路由功能的配置 enabled: true
# 动态路由的默认路由规则: 在网关路径端口号后,先编写要路由的目标服务器名称——也就是注册到Nacos的名称,再编写要求访问的具体路径
#例如:要访问localhost:9001/bj/show->localhost:9000/beijing /bj/show
nacos注册名
内置断言
否则发生404错误拒绝访问,断言之间是“与”的关系
After
设置必须在指定时间之后访问
支持 ZonedDateTime.now()的输出格式
包含时区信息
Before
设置必须在指定时间之前访问
Between
设置必须在指定时间之间访问
- Between=2022-12-29T09:51:35.345+08:00[Asia/Shanghai],2022-12-29T09:51:50.345+08:00[Asia/Shanghai]
Cookie
Header
Host
Method
Path
Query
判断是否包含指定的参数名称,包含参数名称才能通过路由
- Query=username
Remoteaddr
黑名单
路由规则解释
路由规则一定是在开发之前就设计好的
一般可以使用约定好的路径开头来实现的
springfox.documentation.swagger.web
SwaggerResourcesProvider
org.springframework.cloud.gateway.filter.factory
AbstractGatewayFilterFactory
gateway网关子项目
修改子项目pom文件的依赖并添加配置
网关项目的knife4j配置
配置网关之后,在使用knife4j测试时 就不来回切换端口号了 我们需要在网关项目中配置Knife4j才能实现 而这个配置是固定的, 只要是网关项目配置各个子模块的knife4j功能,就直接复制这几个类即可
config
SwaggerProvider
controller
SwaggerController
filter
SwaggerHeaderFilter
Gateway和SpringMvc依赖冲突问题和解决
这两个依赖在同一个项目中时,默认情况下启动会报错 SpringMvc框架依赖中自带一个Tomcat服务器 而SpringGateway框架中自带一个Netty的服务器 在启动项目时,两个框架中包含的服务器都想占用相同端口,因为争夺端口号的主动权而发生冲突 导致启动服务时报错
spring: main: web-application-type: reactive
添加这个配置之后,会Tomcat服务器会变成非阻塞的运行