1.Java基础
1.1 数据类型
java 的8种基本数据类型
boolean byte char short int long float double
非基本数据类型
String Enum Array Object
基本类型的长度和表示范围
int 32 -2^32 - 2^32 -1 ; long 64 ; float 32 ;double 64
浮点数 (+/-) 1.xxx * 2^y ; 符号位 | 基数部分 | 指数部分
1.2 == ,equals,按值传递、hashcode
== 对于基本数据类型来说,是用于比较 “值”是否相等的;而对于引用类型来说,是用于比较引用地址是否相同的。
equals,默认equlas 比较的是引用地址是否相同。
//Object
public boolean equals(Object obj) {
return (this == obj);
}
//Integer
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
对象 操作对象的引用 传递的是引用 会改变对象 包装类和字符串不可变性
若 equals 相等 > hashcode 相等,反之不能
1.4 重写和重载
重写 override 之类方法重写父类方法
重载 overload 函数名和返回值相同 函数参数列表不同
1.5 接口
编译器强制的模块间协作的合约
没有成员变量,没有实现的函数(Java8后可以有实现的函数*)
只能继承一个抽象类,可以实现多个接口
1.6 反射
反射是动态加载对象 反射是自我剖析 反射可以知道类的所有方法和属性
rpc 远程调用;ORM 框架; 拷贝属性值; 简单工厂; 数据库驱动注册
1.7 面向对象
封装、继承、多态
封装,即隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读和修改的访问级别;将抽象得到的数据和行为(或功#### 能)相结合,形成一个有机的整体,也就是将数据与操作数据的源代码进行有机的结合,形成“类”,其中数据和函数都是类的成员。
多态 通过分离做什么和怎么做,从另一角度将接口和实现分开。多态不仅可以改善代码的组织结构和可读性,还能创建出可以拓展的程序。
继承 基类和派生类 (对象可以作为自身的类型使用,也可以作为它的基类使用)视为统一种类型,允许多种类型视为同一类型,同一份代码可以无差别运行在不同类型之上,方法的调用表现出的与其他类型不同的区别(绑定)
(后期绑定)运行时进行类型绑定,动态绑定或运行时绑定,Java 中除了final和static方法,private 属于final 方法,都属于动态绑定,(模糊说,继承后的构造函数,会从父类到基类,逐级调用),(调用之类方法,之类没有覆写时,会向上找父类方法,调用父类的方法,也是印证了动态绑定)
1.8 String类型
1.8.1 String内部存储结构为char数组
String 有4个重要的构造方法
public String(String original)
public String(char value[])
public String(StringBuffer buffer)
public String(StringBuffer buffer)
使用equals 比较两个字符串是否相等
把两个字符串都转换为 char 数组对比
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
3 compareTo() 比较两个字符串
public int compareTo(String anotherString) {
int len1 = value.length;
int len2 = anotherString.value.length;
int lim = Math.min(len1, len2);
char v1[] = value;
char v2[] = anotherString.value;
int k = 0;
while (k < lim) {
char c1 = v1[k];
char c2 = v2[k];
if (c1 != c2) {
return c1 - c2;
}
k++;
}
return len1 - len2;
}
5 String 和 StringBuilder、StringBuffer
String 类型是不可变的 拼接效率低
StringBuffer,它提供了 append 和 insert 方法可用于字符串的拼接,它使用 synchronized 来保证线程安全
public synchronized StringBuffer append(...)
public synchronized StringBuffer append(...)
StringBuilder,它同样提供了 append 和 insert 的拼接方法,但它没有使用 synchronized 来修饰,因此在性能上要优于 StringBuffer
6 string 的创建
String s1 = "Java“
在 JDK 1.8 中,变量 s1 会先去字符串常量池中找字符串 “Java”,如果有相同的字符则直接返回常量句柄,如果没有此字符串则会先在常量池中创建此字符串,然后再返回常量句柄;
2.Java集合
0.数组
多年前学C语言的时候,非常简单清晰的东西,现在却有点困惑
创建
数组所有元素初始化为默认值,数组一旦创建后,大小就不可改变
创建时候要‘指明’数组的长度
int[] input = new int[]{9,5,7};
int[] input2 = new int[10];
数组排序
Arrays.sort(res);
// 先对原数组进行克隆
// 方法1
int[] res = new int[input.length];
System.arraycopy(input,0,res,0,input.length);
// 方法2
int[] res = Arrays.copyOf(input,input.length);
数组 转 List
Integer[] input = new Integer[]{9,5,7};
//1. **java.lang.UnsupportedOperationException**
List<Integerres = Arrays.asList(input);
//2.
ArrayList<Integerres2 = new ArrayList<>(Arrays.asList(input));
//3.
ArrayList<Integerres3 = new ArrayList<>();
Collections.addAll(res3,input);
int[] ,int[] 比较特殊
1. ArrayList 、LinkedList
ArrayList 基于数组实现的非线程安全的自动扩容列表。特点是实现了RandomAccess接口,随机访问效率高,但需要维护内存空间的连续性,内存利用率低。集合在添加创建更大的数组,采用数组复制的方式把原有数据拷贝到新数组,完成自动扩容。
LinkedList :本质上是双向链表,比较值得关注的是其还实现了Deque接口,使其同时具备了队列和栈的性质。LinkedList主要包括3个成员first、last和size。size是指双向链表实际结点的个数,first和last是分别指向双向链表的第一个和最后一个结点的引用。
查找的时间复杂度 ArrayList O(1) ,LinkedList O(n)
LinkedList 查找第n个元素,会判断n在前半部分还是后半部分
2 不是实现了Iterable,ListIterator 接口
而是Collection 内部包含 Iterable
快速hash-,有序tree-,快速访问array,修改linked-
Iterator 可以遍历 Set 和 List 集合,而 ListIterator 只能遍历 List。
Iterator 只能单向遍历,而 ListIterator 可以双向遍历(向前/后遍历)。
使用 Collections. unmodifiableCollection(Collection c) 方法来创建一个只读集合
Iterator<String> iterator = arrayList.iterator();
while(iterator.hasNext()){
String next = iterator.next();
if("foo".equals(next)){
iterator.remove();
}
}
2.HashMap、Set
for (Map.Entry<String, Object> entry : map.entrySet()) {
// entry.getKey() entry.getValue()
}
- 哈希算法 计算地址
哈希冲突 哈希碰撞
开放定址法,再散列函数法,链地址法
- JDK hashMap 存储结构
JDK 1.7 数组、链表 ;JDK1.8 数组、链表、红黑树(链表长度大于8,容量大于64)
一旦链表过长 严重影响HashMap 性能,红黑树 解决链表过长时操作时较长;load factor 负载因子 0.75
-
查询
落在 first = tab[(n - 1) & hash]
first = tab[(n - 1) & hash] static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); } // null 的 hashcode 为 0 public native int hashCode();(假流程)
检查散列后的位置的节点 == null (散列后的位置是否存储有节点) ? null : 继续
判断第一个节点是否是要查询的元素 ? 返回 : 继续
判断是否有下一个节点 ? 继续: null
判断树结构 还是 链表结构,查询
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
-
新增
表为空则创建表
key 的哈希值计算出要插入的数组索引
如果 table[i] 等于 null,则直接插入
不为null ,判断第一个元素key是否相等, 已经存在了,直接覆盖 value
不相等,判断树结构 还是 链表结构
是树结构,更新插入到树中
是链表结构,key 已经存在直接覆盖 value,不存在插在队尾,检查是否需要转为树结构
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
-
扩容
扩大容量为当前容量的两倍
高位运算(e.hash & oldCap)
高一位为0,当结果为0时表示元素在扩容时位置不会发生任何变化,
高一位为 1,当结果为 1 时,表示元素在扩容时位置发生了变化,新的下标位置等于原下标位置 + 原数组长度
4.多线程
并发: 速度;设计可管理性
想要一个程序运行更快,将其断开为多个片段,在多个单独的处理器上执行;
但是并发通常是提高单处理器的程序的性能 ,增加了上下文切换的代价,但是
避免了阻塞,从性能角度,如果没有阻塞,单处理器并发没有任何意义。
线程
1 一组线程可以共享一块内存和一组资源
JMM 内存模型 不是真实存在
2 线程 线程的状态
NEW ,
RUNNABLE,可以运行,排队等待处理器时间
BLOCKED,等待执行Synchronized 代码块
WAITING,等待另一个线程执行(wait()) - notify()
3 join()
在一个线程中调用 other.join() ,这时候当前线程会让出执行权给 other 线程
源码中可以看出 join() 方法底层还是通过 wait() 方法来实现的。
4 yield、线程优先级
yield() 方法表示给线程调度器一个当前线程愿意出让 CPU 使用权的暗示,但是线程调度器可能会忽略这个暗示
Executors、Executors
[ɪɡˈzekjətər]
Executors 用于生成不同类型的线程池
newCachedThreadPool 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool 创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
newSingleThreadExecutor 创建单线程的 Executor,以队列方式来运行该线程,保证所有任务按照指定顺序执行。
ExecutorService
execute(Runnable) 、submit(Runnable) 返回一个Future对象、submit(Callable) 返回一个Future对象
、shutdown() 平缓的关闭线程池
future isDone() get() 阻塞
ThreadPoolExecutor
corePoolSize 线程池的常驻核心线程数
maximumPoolSize 等待队列满后,最大可以创建的线程数
keepAliveTime * 如果没有特殊设置,表示多创建出的线程的存活时间
unit 表示存活时间的单位
workQueue 线程等待队列
threadFactory 表示线程的创建工厂
RejectedExecutionHandler 表示指定线程池的拒绝策略
(伪 流程)
当前工作的线程数是否小于核心线程数 ? 创建新线程执行 : 添加到等待队列
等待队列是否满 ? 执行3 : 加入
满了,检查maximumPoolSize,如果小于: 创建线程 : 执行拒绝策略。
拒绝策略
AbortPolicy,终止策略,线程池会抛出异常并终止执行,它是默认的拒绝策略;
CallerRunsPolicy,把任务交给当前线程来执行;
DiscardPolicy,忽略此任务
DiscardOldestPolicy,忽略最早的任务
死锁
1 两个线程各自持有不同的锁
2 两个线程各自试图获取对方已持有的锁
3 双方无限等待下去,导致死锁
避免死锁
线程获取锁的顺序要一致
模拟一个死锁
ExecutorService executorService = Executors.newFixedThreadPool(5);
executorService.execute(() -> {
while (true) {
synchronized (lock1) {
synchronized (lock2) {
System.out.println("1>2");
}
}
}
});
executorService.execute(() -> {
while (true) {
synchronized (lock2) {
synchronized (lock1) {
System.out.println("2>1");
}
}
}
});
java.util.concurrent
reentrantlock
每一个java对象都有一个monitor监视器对象,加锁的过程是竞争monitor的过程。
synchronized 独占
ReentrantLock 替代 synchronized
ReentrantLock 获取锁更安全
必须使用try…finally 确保正确获得锁和释放锁
2 ReadWriteLock
我们期望 ( 容许多个线程同时读,但是如果有一个线程在写,其他线程就必须等待)
volatile 本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取; synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
volatile仅能实现变量的修改可见性,不能保证原子性,volatile不会造成线程的阻塞
锁
5.设计模式
设计模式
创建型模式
工厂模式(Factory Pattern)
抽象工厂模式(Abstract Factory Pattern)
单例模式(Singleton Pattern)
建造者模式(Builder Pattern)
结构型模式
适配器模式
过滤器模式
装饰器模式
代理模式
行为模式
命令模式
观察者模式
策略模式
Singleton /单例模式
Singleton [ˈsɪŋɡltən]
构造器私有,使用静态变量保存
饿汉式
public class Singleton0 {
private static Singleton0 instance = new Singleton0();
private Singleton0() {
}
public static Singleton0 getInstance() {
return instance;
}
}
懒汉式
public class Singleton1 {
private static Singleton1 instance = null;
private Singleton1() {
System.out.println("o");
}
public static Singleton1 getInstance() {
if(instance == null){
instance = new Singleton1();
}
return instance;
}
}
简单加锁 在getInstance上加锁
public class Singleton1 {
private static Singleton1 instance = null;
private Singleton1() {
System.out.println("o");
}
public static synchronized Singleton1 getInstance() {
if(instance == null){
instance = new Singleton1();
}
return instance;
}
}
双重检查锁 双重锁提供性能 volatile 防止指令重排
public class Singleton2 {
private volatile static Singleton2 instance;
private Singleton2(){
System.out.println("0");
}
public static Singleton2 getInstance(){
if(instance == null){
synchronized (Singleton2.class){
if(instance == null){
instance = new Singleton2();
}
}
}
return instance;
}
}
代理模式
为其他对象提供一种代理以控制对这个对象的访问。代理对象起到中介作用,可以对方法进行增强。
静态代理
动态代理 JDK代理(有接口) 和 Cglib
建造者模式
Gson、MongoDB Client、
6.Spring
Spring
是轻量级框架;是模块合集;
Spring中的设计模式
工厂模式/工厂方法
单例模式 bean
适配器模式 handler
代理模式 AOP
模版方法模式 jdbcTemplate
IOC/DI/控制反转
手动创建对象和解决对象之间的依赖(自行创建并销毁对象),交给Spring管理
- Spring IoC 容器
Spring 管理这些Bean的容器,称为Spring IoC 容器 ,包含两个基本的功能
1通过“描述” 管理Bean,包括发布和获取bean;#2 通过“描述”完成Bean之间的依赖关系。
beanfactory接口 > application context 接口 > 实现 FileSystemXmlApplicationContext、ClassPathXmlApplicationContext、AnnotationConfigApplicationContext
- 注册Bean
1 在XML中进行显式配置 XML;2 在Java中进行显式配置 java config;3 隐式的bean发现机制和自动装配 component scanning + autowire
- 作用域
singleton:唯一bean实例,Spring中的bean默认都是单例的。
prototype:每次请求都会创建一个新的bean实例。
request:每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效。
session:每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP session内有效。
application: 全局的 Web 作用域,类似于 Servlet 中的 Application。
生命周期
实例化,注入对象属性
执行Aware ()
注册回调方法,Bean使用,执行销毁方法
注入方式
1 构造方法 #2 Setter #3接口
AOP
AOP(Aspect-Oriented Programming,面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可扩展性和可维护性。
Spring AOP是基于动态代理的,如果要代理的对象实现了某个接口,那么Spring AOP就会使用JDK动态代理去创建代理对象;而对于没有实现接口的对象,就无法使用JDK动态代理,转而使用CGlib动态代理生成一个被代理对象的子类来作为代理。
@AspectJ
事务
隔离级别和传播行为
1 事务
事务四个特性 ACID ;原子性、一致性、隔离性、持久性;
序列化,可以保证ACID,但是性能太低
2 三个问题 脏读 > 不可重复读 > 幻读
4 四种隔离级别 (隔离级别高 > 性能低 没有安全问题,反之 隔离级别低 > 性能高 各种安全问题)
未提交读 > 读写提交 (解决脏读) > 可重复读(解决脏读、不可重复读) > 串行化 (全解决)
未提交读 : 允许一个事务读取另一个事务没有提交的数据
典型问题 脏读(其他两种问题也存在)
(太快了 事务1还没有做完 就读了事务1处理后的数据 事务1可能不做了 库存多减少了1)
事务2 可以 在事务1 执行期间 读写
读写提交:只能读取已提交的数据
典型问题 不可重复读 (克服了脏读)
(i -- , 事务2 只能读到事务1 执行前的数据 ,(1 减了两次),出现-1库存
事务2 可以 事务2 执行期间 读
可重复读:
典型问题 幻读(克服了不可重复读)
(情形 删除前 检查预计删除多少条数据 ,(有事务在插入删除),执行删除后 发现影响条数和预期不一致)
事务2 可以 事务1 执行期间 null
序列化:
(可重复读 ,对读写加了的‘锁’,克服“所有" 日常(多线程) 问题,但是换种角度
可重复读 隔离级别可能太高了。
(1 日常使用读写提交隔离级别,2 日常使用 不用考虑和设置级别吧 )
Spring Security
Spring MVC
springmvc 流程
用户发送请求到 前段控制器 DispatcherServlet
前段控制器 调用 处理器映射器
处理器映射器 根据具体的配置 (xml 或者 注解) 生成 处理器对象及处理器拦截器
处理器适配器 调用 处理对象 返回ModelAndView
视图解析器 返回具体的View
前段控制器 相应用户
Spring Boot
优点:
更快速的构建能力 (starter)
独立运行(内嵌容器)、简化配置(starter)、自动配置、无代码生成和XML配置、应用监控
Starters可以理解为启动器,它包含了一系列可以集成到应用里面的依赖包,你可以一站式集成 Spring 及其他技术,而不需要到处找示例代码和依赖包
约定大于配置
@SpringBootApplication
=
@SpringBootConfiguration
@EnableAutoConfiguration
7.微服务
微服务CAP原理 ;C 一致性 A 可用性 P 分区容忍性 ;选择AP 放弃强一致性
远程调用
HTTP http请求 testful 接口 Template
RPC
1.服务的注册与发现
服务将自身服务注册到注册中心,服务请求注册中心获取依赖的服务
2.负载均衡
工作负载到多个服务来提高性能和可用性
3.熔断
下游服务故障,暂时切断对下游服务的访问
4.链路追踪
对一次请求设计的多个服务链路追踪进行日志记录和性能监控
5.API网关
跨域和单独的身份验证,安全,流量,路由
8.数据库
索引
索引是帮助MySQL高效获取数据的排好序的数据结构
索引的结构
二叉树、红黑树、Hash表、B-Tree
- 二叉树,
问题: 数据是递增时,二叉树变的像“链表”
- 红黑树 平衡/自旋 二叉平衡树,
问题: 数据量很大
- B-Tree
每一个节点很大,存储多个索引和数据
叶子节点具有相同的深度,叶子节点的指针为空
索引不重复 (b+)
节点内数据索引从左到右递增排列
- B+ Tree
非叶子节点不存储数据,只存储索引,(全是都是冗余索引)
叶子节点存储数据,叶子节点存储所有数据
叶子节点用指针连接,提供区间访问
- 存储引擎
myisam 索引(非聚集索引)
文件和数据文件是分离的
innoDB 索引 (聚集索引)
表数据文件本身就是安装B+Tree组织的一个索引文件
聚集索引 : 叶节点包含完整数据记录
必须有主键,推荐使用整型的自增主键 (占空间少 比较快)
SQL
查询重复的邮箱
SELECT email FROM public.t_user group by email having count(email) > 1 ;
HAVING
在 SQL 中增加 HAVING 子句原因是,WHERE 关键字无法与聚合函数一起使用。
HAVING 子句可以让我们筛选分组后的各组数据。
HAVING 语法
SELECT column_name, aggregate_function(column_name)
FROM table_name
WHERE column_name operator value
GROUP BY column_name
HAVING aggregate_function(column_name) operator value;
删除重复邮箱只保留uid最小
delete from public.t_user where uid not in(select min(uid) from public.t_user p1 group by email) ;
SPU和SKU
数据库表设计1
数据库表设计2
8.Redis
介绍
C 语言 基于内存的 key-value 数据库 单线程。
- 对比
磁盘IO性能低下,数据关系复杂,拓展性差,不便于集群
内存存储,仅存储数据
- 数据库使用场景
商品信息 mysql; 商品附加信息(评论、描述) mongodb; 图片 分布式文件系统 fastdfs;搜索 es;热点信息 redis .
- 应用
热点数据加速查询 ?;任务队列 秒杀 抢购 排队购票 ?;即时信息 排行榜 访问问题 在线人数 ?;时效控制 验证码;分布式数据共享 session;消息队列;分布式锁
数据类型和指令
string(字符串)、list(链表)、set(集合)、zset(sorted set –有序集合)和hash(哈希类型)。
3.1 KEY
DEL key 删除KEY; EXISTS key 检查KEY是否存在;EXPIRE key seconds 设置KEY 过期时间;
- String
GET ; SET ;DEL ; MSET ;APPEND ;STRLEN ;
- Hash
HSET / HGET / HGETALL / HDEL
HMSET / HMGET / HLEN / HEXISTS / HEYS / HVALS
- List
LPUSH ; RPUSH ; LPOP ; RPOP;
LRANGE ; LINDEX ; LLEN ;LREM
- SET
SADD ; SMEMBERS ; SREM ;SCARD ; SISMEMBER ;(佛系命名)
SINTER ; SUNION ; SDIFF (交并差)
- zset
持久化
RDB
存储数据快照;
存储效率高,适用于数据备份,恢复速度快,无法实时持久化,可能丢失数据,资源消耗高
策略: 满足限定时间范围内key的变化数量达到指定数量即进行持久化
save second changes
save;basave;
AOF
存储操作过程;
存储效率低,恢复速度慢,资源消耗低,
策略: always 每次、everysec 每秒、no 系统控制
AOF重写规则
超时不再写入;忽略无效指令;合并多条命令;
bgrewriteaof;
选择:
- 数据敏感: AOF
使用everysecond,依旧能保持不错性能,最多丢失1s内数据,
体积较大,恢复速度慢,
- 数据呈现阶段有效性:RDB
恢复速度较快,高频的RDB持久化,降低性能
同时开启 RDB 和 AOF
事务
单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增加任何维持原子性的机制,所以 Redis 事务的执行并不是原子性的。
事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做。
删除策略
三种策略
-
定时策略
创建定时器,消耗CPU处理器时间,时间换空间
-
惰性删除
不处理,下次访问时,过期删除,空间换时间
-
定期删除
每秒执行N次,对个expires[*]逐一进行检查,
随机跳出W个Key,如果大于 25%,循环该过程,
否则检查下一个expires[*]。
8.缓存
EhCache
Redis
Spring 整合 Redis 缓存
spring:
cache:
cache-names: redisCache
启用缓存功能 @EnableCaching
参数是 SpEL 表达式,
@CachePut(value = "redisCache", key = "'redis-cache-my' + #result.name")
@Cacheable(value = "redisCache", key = "'redis-cache-my' + #uid")
缓存
缓存问题
1 缓存穿透
问题: DB中不存在数据,每次都穿过缓存查DB,
解决方案: 放入一个不存在的对象
2 缓存击穿
问题 在(一个)缓存失效的瞬间大量请求,造成DB的压力瞬间增大
解决方案:使用分布式锁锁住服务,防止请求穿透直达DB
3 缓存雪崩
问题: 大量缓存设置了相同的失效时间,同一时间失效,造成服务瞬间性能急剧下降
解决方案:缓存时间使用基本时间加上随机时间
9.消息
RocketMQ
消息中间件 作用
解耦 ,异步(提高性能和吞吐量),流量削峰
队列模式 和 订阅(主题)模式
消息发送失败问题
1 队列模式 消费掉才会消失 不存在消息丢失
2 订阅模式 配置持久订阅 使用数据库储存
内置数据库 自己配置数据库
防止消息重复接收
1 消息状态表
处理消息前进行查询
JMS 规范
消息模式 (2)
\1. 队列模式
生产者和消费者
队列中的消息只能被一个消费者消费
消费者可以随时消费队列中的消息
\2. 主题模式
发布者和订阅者
被所有的订阅者消费
消费者不能消费订阅之前就发送到主题的消息
RocketMQ
优点 解耦,削峰,数据分发
- 系统可用性降低
问题 MQ宕机
MQ集群
- 复杂度
消息重复消费,消息丢失,消息的顺序
- 消息的一致性
启动RocketMQ
1 启动Name S
Name Server 集群 (管理Broker)
Broker 主 / 从 (Broker 向 NameServer 注册 )
NameServer
NameServer 无状态节点,节点间无信息同步,
Broker
分为Master 与 Slave
主/从
start mqbroker.cmd -n 127.0.0.1:9876 autoCreateTopicEnable=true
1 RocketMQ
DefaultMQPushConsumer defaultMQPullConsumer = new DefaultMQPushConsumer("g1");
多个不同consumer 为一个组的时候,要设置 setInstanceName
defaultMQPullConsumer.setInstanceName("a2");
defaultMQPullConsumer.setMessageModel(MessageModel.BROADCASTING); 队列模式
defaultMQPullConsumer.setMessageModel(MessageModel.CLUSTERING); 广播模式
2 落到同一个broker
10.搜索
12 ElasticSearch
1 倒排索引 反向索引
2 对数据库数据 事先生成文档列表 和 倒排索引区域
3 lucence / solr / es
lucence 开源类库 底层 API ,不是现成的搜索产品,全文检索引擎工具包
4 kibana
nohup xxx &
5 IK分词器
- 基本概念
索引 = 数据库;文档 = 行;字段 = 列;
- 创建索引
PUT /demo
- 创建映射字段
数据类型
字符串类型
PUT /索引库名/_mapping/
{
}
类型
10. 白板代码
- 二分查找 左右界限+-1 while 条件<=
private static int binarySearch(int[ final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
} final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
} final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
} final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}] nums, int target) {
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
return mid;
} else if (nums[mid] > target) {
right = mid -1;
} else if (nums[mid] < target) {
left = mid + 1;
}
}
return -1;
}
- 排序
冒泡排序
public static void sort1(int[] res){
for (int i = 0; i < res.length - 1; i++) {
for (int j = 0; j < res.length - 1 - i; j++) {
if(res[j] > res[j+1]){
swap(res,j,j+1);
}
}
}
}
小 插入排序
大 快速排序
- 递归思想
先一般 后特殊
问题规模-1
NodecreateLinkedList(List<Integer>raw){
if(raw.isEmpty()){
return null;
}
Nodefirst=newNode(raw.get(0));
Nodenext=createLinkedList(raw.subList(1,raw.size()));
first.setNext(next);
return first;
}
11.JVM
运行时数据区
方法区、堆、虚拟机栈、本地方法栈、程序计数器。
线程共享(方法区、堆)
线程隔离(虚拟机栈、本地方法栈、程序计数器)
方法区:(永久代/元空间)存储类的元数据的,如虚拟机加载的类信息、编译后的代码
堆: 存储对象、数组等,分为年轻代(Eden区、From Survivor、To Survivor)、老年代
虚拟机栈: 虚拟机栈生命周期与线程相同,方法执行创建一个栈帧,
存储局部变量表(基本数据类型、对象引用)、操作栈、动态链接、方法出口
程序计数器: 正在执行的虚拟机字节码指令的地址
本地方法栈: Native方法
垃圾回收
垃圾判断算法
引用计数法;可达性分析(GC ROOT)
回收算法
标记-清除 (碎片);复制 (只能用一半);标记-整理(向一端移动)
分代收集
新生代Eden区中分配, Minor GC
Survivor又分为2个区,一个是From区,一个是To区
13.分布式文件系统
FastDFS
上传
Client通过Tracker server查找可用的Storage server。
Tracker server向Client返回一台可用的Storage server的IP地址和端口号。
Client直接通过Tracker server返回的IP地址和端口与其中一台Storage server建立连接并进行文件上传。
上传完成,Storage server返回Client一个文件ID,文件上传结束。
@Autowired
private FastFileStorageClient storageClient;
StorePath storePath = this.storageClient.uploadFile(
new FileInputStream(file), file.length(), "png", null);
下载
Client通过Tracker server查找要下载文件所在的的Storage server。
Tracker server向Client返回包含指定文件的某个Storage server的IP地址和端口号。
Client直接通过Tracker server返回的IP地址和端口与其中一台Storage server建立连接并指定要下载文件。
下载文件成功。
14 Docker 容器
概念
镜像:相当于是一个 root 文件系统
容器:镜像是静态的定义,容器是镜像运行时的实体
仓库:镜像仓库
转换
仓库: 仓库 > 镜像 pull ;仓库 < 镜像 push;
容器: 镜像 > 容器 run;镜像 < 容器 commit;
Tar: 镜像 > tar save; tar > 镜像 load;
dockerfile: dockerfile > 镜像 build;
运行停止容器
docker run -d -p 80:80 nginx (-d 后台 -p端口映射)
-v $PWD/conf:/etc/mysql/conf.d (-v 设置数据卷)
docker ps -a
docker stop id
docker exec –it c_mysql /bin/bash (bash)
- dockerfile ; docker compose 编排
14.项目梳理
项目梳理
分为 认证服务 、购物车、注册中心、网关、
Item、订单、搜索、短信、上传、用户。(End)
各部门功能
认证服务: 登录验证,验证用户名和密码后,生成JWT,写入cookies。
注册中心: 无任何特殊配置。
网关: 传递cookies、敏感头、白名单、服务映射。
搜索: 略过 结合ES
上传: 略过 结合FastDFS
短信: 略过 结合MQ
用户: 注册用户
Item: 无
购物车: redis
订单: 无 订单雪花算法
数据库表设计
-
品牌表
id、品牌名。
-
分类表
id、类名、父级别id、是否是父级别。
-
品牌-分类表
品牌id、分类id。
订单表
订单详情表
订单状态表
支付日志表
SKU表
-
两张表 类似SKU属性 我们可以用jsonb
(使用jsonb 依旧需要补一张表 不知道jsonb里面字段)
-
SPU
123级品类,品牌,标题,
SPU 某些详细信息表 (存储html页面片段)
-
库存表 (SKU中也有库存信息
可秒杀库存、秒杀总数量、库存数量
-
用户表
加密密码 和 盐
其他
Thymeleaf实现静态化
Redis 存储 购物车数据
消息
用户注册-发送短信;
插入更新STO,更新ES 和 静态化的页面
- 流程
3.1用户服务
检查用户名和手机号,是否可用 (并发下是否有问题 ?)
获取验证码,生成验证码,存储在Redis,发送‘发送短信’消息到消息中心
用户注册, (用户名,密码,手机号,验证码),检查验证码,存储加密后密码
密码传输安全问题 (采用HTTPS + 明文 + 或加密后的密码 ) 暂时没有疑问
3.2JWT,用户信息问题、单点登录
用户登录在认证服务,而不是用户服务。
用户登录,认证后,认证服务会在Cookies内存储JWT,
还需要一下技术配合才能实现完整实现
cookies、二级域名、单点登录、Nginx、Access-Control-Allow-Origin
网关解析JWT是否有效,(没有转存发用户信息)、只是登录拦截
后台服务, 在拦截器中、解析JWT获取用户信息
传递给Controller,两种方式: 1 类似authmgr *注入,2 ThreadLocal **
- 一种新的结构设计
微服务分为interface 和 service
interface 有 pojo、 DTO(Data Transfer Object、和对外可以调用的接口
public interface GoodsApi {
@GetMapping("/spu/detail/{spuId}")
SpuDetail queryDetailBySpuId(@PathVariable("spuId") Long spuId);
}
// 在其他微服务中
package com.java.order.feign;
@FeignClient("item-service")
public interface GoodsApiFeign extends GoodsApi {
}
15 缺失知识
乐观锁 悲观锁 AQS
双亲委托 JVM
关键字实现
redis 持久化
方法区 (永久代)
在方法区中,存储了每个类的信息(包括类的名称、修饰符、方法信息、字段信息)、类中静态变量、类中定义为final****类型的常量、类中的Field信息、类中的方法信息以及编译器编译后的代码等。
堆
堆空间分为老年代和年轻代。刚创建的对象存放在年轻代,而老年代中存放生命周期长久的实例对象。年轻代中又被分为Eden区和两个Survivor区(From Space和To Space)。新的对象分配是首先放在Eden区,Survivor区作为Eden区和Old区的缓冲,在Survivor区的对象经历若干次GC仍然存活的,就会被转移到老年代。 当一个对象大于eden区而小于old区(老年代)的时候会直接扔到old区。 而当对象大于old区时,会直接抛出OutOfMemoryError(OOM)。
栈
。Java栈中存放的是一个个的栈帧,每个栈帧对应一个被调用的方法,在栈帧中包括局部变量表(Local Variables)、操作数栈(Operand Stack)、指向当前方法所属的类的运行时常量池的引用(Reference to runtime constant pool)、方法返回地址(Return Address)和一些额外的附加信息。当线程执行一个方法时,就会随之创建一个对应的栈帧,并将建立的栈帧压栈。当方法执行完毕之后,便会将栈帧出栈。
程序计数器
为了能够使得每个线程都在线程切换后能够恢复在切换之前的程序执行位置,每个线程都需要有自己独立的程序计数器,并且不能互相被干扰,否则就会影响到程序的正常执行次序。因此,可以这么说,程序计数器是每个线程所私有的。
本地方法栈
JVM采用本地方法堆栈来支持native方法的执行,此区域用于存储每个native方法调用的状态。本地方法栈与Java栈的作用和原理非常相似。区别只不过是Java栈是为执行****Java****方法服务的,而本地方法栈则是为执行本地方法(****Native Method****)服务的。
16.Part-2
Swagger 的使用
\1. 导入依赖
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.7.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.7.0</version>
</dependency>
\2. 启用Swagger
@EnableSwagger2
\3. 配置Swagger2
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.cute.demo.controller"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("SwaggerUI")
.description("cute-project")
.version("1.0")
.build();
}
注解说明
@Api:用在请求的类上,表示对类的说明
tags="说明该类的作用,可以在UI界面上看到的注解"
value="该参数没什么意义,在UI界面上也看到,所以不需要配置"
@ApiOperation:用在请求的方法上,说明方法的用途、作用
value="说明方法的用途、作用"
notes="方法的备注说明"
@ApiImplicitParams:用在请求的方法上,表示一组参数说明
@ApiImplicitParam:用在@ApiImplicitParams注解中,指定一个请求参数的各个方面
name:参数名
value:参数的汉字说明、解释
required:参数是否必须传
paramType:参数放在哪个地方
· header --> 请求参数的获取:@RequestHeader
· query --> 请求参数的获取:@RequestParam
· path(用于restful接口)--> 请求参数的获取:@PathVariable
· body(不常用)
· form(不常用)
dataType:参数类型,默认String,其它值dataType="Integer"
defaultValue:参数的默认值
@ApiResponses:用在请求的方法上,表示一组响应
@ApiResponse:用在@ApiResponses中,一般用于表达一个错误的响应信息
code:数字,例如400
message:信息,例如"请求参数没填好"
response:抛出异常的类
@ApiModel:用于响应类上,表示一个返回响应数据的信息
(这种一般用在post创建的时候,使用@RequestBody这样的场景,
请求参数无法使用@ApiImplicitParam注解进行描述的时候)
@ApiModelProperty:用在属性上,描述响应类的属性
\5. 遗留问题
1 上面注解的使用的细节 @PathVariable @RequestBody 的情况下的使用
2 不配置上面的注解 也能完成基本的功能
3 在pojo上注解@ApiModel 也能提供一些信息
Redis 五种数据类型
0 KEY
DEL key 删除KEY EXISTS key 检查KEY是否存在 EXPIRE key seconds 设置KEY 过期时间
\1. String
GET / SET (类似一个全局的map , 作用域内变量 get/ set )
SET key value [EX seconds] [PX milliseconds] [NX|XX] GET key
2 Hash
存储到一个HashMap (声明一个map )
hashmap.add(key,value)
HMSET key field value [field value ...] key = hashmap 实例名,像map里面放置对象, 需要指明key-value (迷之为什么会困惑) HGET key field
3 List
4 SET
zset
Redis 简单事务机制 保证高并发下的一致性
Lua 语言
Spring 使用 Redis
1 序列化
2 操作
1 操作
2 绑定
3 redis 事务
单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增加任何维持原子性的机制,所以 Redis 事务的执行并不是原子性的。
事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做。
redis 流水线
pipeline 适合大量数据的执行
redis 发布订阅
redis lua
redis 调用失败,自定义缓存管理器
4 Spring 整合 Redis 缓存
spring: cache: cache-names: redisCache
不需要做复杂的配置
启用缓存功能 @EnableCaching
放入缓存
参数是 SpEL 表达式,
@CachePut(value = "redisCache", key = "'redis-cache-my' + #result.name")
@Cacheable(value = "redisCache", key = "'redis-cache-my' + #uid")
缓存
雪崩 击穿 穿透
分布式缓存 分布式锁
redis
消息中间件
本地事务
分布式事务
搜索
oauth2 jwt
1 倒排索引 反向索引
2 对数据库数据 事先生成文档列表 和 倒排索引区域
3 lucence / solr / es
lucence 开源类库 底层 API ,不是现成的搜索产品,全文检索引擎工具包
4 kibana
nohup xxx &
5 IK分词器
Java源文件经编译成字节码程序,通过JVM将每一条指令翻译成不同平台机器码,通过特定平台运行。
JVM 体系结构
- 类装载器ClassLoader
用来装载.class文件
- 执行引擎
执行字节码,或者执行本地方法
- 运行时数据区
方法区、堆、Java栈、程序计数器、本地方法栈
方法区、堆 -- 线程共享
Java栈、程序计数器、本地方法栈 -- 非线程共享
方法区 (永久代)
在方法区中,存储了每个类的信息(包括类的名称、修饰符、方法信息、字段信息)、类中静态变量、类中定义为****final****类型的常量、类中的Field信息、类中的方法信息以及编译器编译后的代码等。
堆
堆空间分为老年代和年轻代。刚创建的对象存放在年轻代,而老年代中存放生命周期长久的实例对象。年轻代中又被分为Eden区和两个Survivor区(From Space和To Space)。新的对象分配是首先放在Eden区,Survivor区作为Eden区和Old区的缓冲,在Survivor区的对象经历若干次GC仍然存活的,就会被转移到老年代。 当一个对象大于eden区而小于old区(老年代)的时候会直接扔到old区。 而当对象大于old区时,会直接抛出OutOfMemoryError(OOM)。
栈
。Java栈中存放的是一个个的栈帧,每个栈帧对应一个被调用的方法,在栈帧中包括局部变量表(Local Variables)、操作数栈(Operand Stack)、指向当前方法所属的类的运行时常量池的引用(Reference to runtime constant pool)、方法返回地址(Return Address)和一些额外的附加信息。当线程执行一个方法时,就会随之创建一个对应的栈帧,并将建立的栈帧压栈。当方法执行完毕之后,便会将栈帧出栈。
程序计数器
为了能够使得每个线程都在线程切换后能够恢复在切换之前的程序执行位置,每个线程都需要有自己独立的程序计数器,并且不能互相被干扰,否则就会影响到程序的正常执行次序。因此,可以这么说,程序计数器是每个线程所私有的。
本地方法栈
JVM采用本地方法堆栈来支持native方法的执行,此区域用于存储每个native方法调用的状态。本地方法栈与Java栈的作用和原理非常相似。区别只不过是Java****栈是为执行****Java****方法服务的,而本地方法栈则是为执行本地方法(****Native Method****)服务的。
3.1 堆
3.2 栈
3.3 本地方法栈
3.4 方法区(元空间)
3.5 程序计数器
2 字节码执行引擎
3.2 栈
栈帧 FILO
每一个方法都会有一个栈帧
栈帧
(局部变量表 )
(操作数栈)
(动态链接)
(方法出口)
3.3 方法区
Swagger 的使用
\1. 导入依赖
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.7.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.7.0</version>
</dependency>
\2. 启用Swagger
@EnableSwagger2
\3. 配置Swagger2
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.cute.demo.controller"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("SwaggerUI")
.description("cute-project")
.version("1.0")
.build();
}
注解说明
@Api:用在请求的类上,表示对类的说明
tags="说明该类的作用,可以在UI界面上看到的注解"
value="该参数没什么意义,在UI界面上也看到,所以不需要配置"
@ApiOperation:用在请求的方法上,说明方法的用途、作用
value="说明方法的用途、作用"
notes="方法的备注说明"
@ApiImplicitParams:用在请求的方法上,表示一组参数说明
@ApiImplicitParam:用在@ApiImplicitParams注解中,指定一个请求参数的各个方面
name:参数名
value:参数的汉字说明、解释
required:参数是否必须传
paramType:参数放在哪个地方
· header --> 请求参数的获取:@RequestHeader
· query --> 请求参数的获取:@RequestParam
· path(用于restful接口)--> 请求参数的获取:@PathVariable
· body(不常用)
· form(不常用)
dataType:参数类型,默认String,其它值dataType="Integer"
defaultValue:参数的默认值
@ApiResponses:用在请求的方法上,表示一组响应
@ApiResponse:用在@ApiResponses中,一般用于表达一个错误的响应信息
code:数字,例如400
message:信息,例如"请求参数没填好"
response:抛出异常的类
@ApiModel:用于响应类上,表示一个返回响应数据的信息
(这种一般用在post创建的时候,使用@RequestBody这样的场景,
请求参数无法使用@ApiImplicitParam注解进行描述的时候)
@ApiModelProperty:用在属性上,描述响应类的属性
\5. 遗留问题
1 上面注解的使用的细节 @PathVariable @RequestBody 的情况下的使用
2 不配置上面的注解 也能完成基本的功能
3 在pojo上注解@ApiModel 也能提供一些信息