1. Java基础
基本概念
1. 面向对象的特点及理解
2. JDK 和 JRE 有什么区别?
- JDK:Java Development Kit 的简称,java 开发工具包,提供了 java 的开发环境和运行环境。
- JRE:Java Runtime Environment 的简称,java 运行环境,为 java 的运行提供了所需环境。
JDK 其实包含了 JRE,同时还包含了编译 java 源码的编译器 javac,还包含了很多 java 程序调试和分析的工具。简单来说:如果你需要运行 java 程序,只需安装 JRE 就可以了,如果你需要编写 java 程序,需要安装 JDK。
方法签名的理解:
- java中的方法是必须依赖于类的,方法由 方法名、返回值类型,形参列表、方法体 构成
- 方法签名,顾名思义,方法的签名,用来区分不同方法的标示符.由方法名+形参列表构成
- 即方法名和形参列表可以唯一的确定一个方法,与方法的返回值一点关系都没有,这是判断重载重要依据
3. 重载与重写是什么?有什么区别?
重载是一个类中多态性的表现,表现为一个类中的多个同名不同参的函数,与返回值类型无关;
重写是父类与子类之间的多态性的表现,表现为子类对父类方法进行重新定义;
重写(Override)是父类与子类之间的多态性的表现实质是对父类方法进行重新定义,理解为重写方法体
两同: 方法名和参数类型相同
两小:返回值类型为引用类型变量时,子类返回值类型应小于父类方法的返回值类型,子类方法抛出异常类型应小于等于父类方法抛出异常类型
一大:子类访问权限应大于等于父类方法访问权限
(OCP原则:面向修改关闭,面向拓展开放)
重载与重写是 Java 多态性的不同表现。
4.类加载器
String类
1. String,StringBuilder,StringBuffer三者之间的区别
-
String 表示字符串,底层是char[]数组,长度是不可变的,任何对String的改变都会引发新的String对象的生成,所以多次变更的串不要用String定义
-
StringBuffer和StringBuilder是可变的字符序列。字符串的改变不会产生新的对象,两个的方法和功能完全是等价的,使用于经常性操作字符串的场景
- 提供了一组可以对字符内容修改的方法
- 常用append()来代替字符串做字符串连接”+”
- 内部字符数组默认初始容量是16:super(str.length() + 16);
- 如果大于16会尝试将扩容,新数组大小原来的变成2倍+2,容量如果还不够,直接扩充到需要的容量大小。int newCapacity = value.length * 2 + 2;
- StringBuffer 1.0出道,该类的方法大都加锁,线程安全,StringBuilder1.5出道线程不安全
在单线程程序下,StringBuilder的效率更快,因为它不需要加锁,StringBuffer每次都要判断锁,效率相对较低,因此StringBuffer 常用于多线程环境
2. == 和 equals 的区别是什么
== 对于基本类型来说是值比较,对于引用类型来说是比较的是引用;
equals 默认情况下是引用比较,只是很多类重写了 equals 方法,比如 String、Integer 等把它变成了值比较,所以一般情况下 equals 比较的是值是否相等。
3. String str=“i” 与 String str = new String(“ i ”)一样吗?
不一样,因为内存的分配方式不一样。
String str="i"的方式,java 虚拟机会将其分配到常量池中;
String str=new String(“i”) 则会被分到堆内存中。
4. String 类的常用方法都有那些?
- charAt()—获取指定下标处位置上的字符
- lastIndexOf()-某个字符最后一次出现的位置
- substring()-截取子串,如果参数有两个左闭右开[1,5)
- equals()-判断两个串是否相等,注意String重写了Object的此方法,所以内容相同就返回true
- startsWith()-判断是不是以参数开头
- endsWith()–判断是不是以参数结尾
- split()—以指定字符分割
- trim()-去掉首尾两端的空格
- getBytes()-把串转换成数组
- toUpperCase()-变成全大写
- toLowerCase()-变成全小写
5. Files的常用方法都有哪些?
- Files.exists():检测文件路径是否存在。
- Files.createFile():创建文件。
- Files.createDirectory():创建文件夹。
- Files.delete():删除一个文件或目录。
- Files.copy():复制文件。
- Files.move():移动文件。
- Files.size():查看文件个数。
- Files.read():读取文件。
- Files.write():写入文件。
容器
1. Collection 和 Collections 有什么区别
- java.util.Collection 是一个集合接口(集合类的一个顶级接口)。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式,其直接继承接口有List与Set。
- Collections则是集合类的一个工具类/帮助类,其中提供了一系列静态方法,用于对集合中元素进行排序、搜索以及线程安全等各种操作。
2.谈谈ArrayList和LinkedList的区别
- ArrayList底层基于数组实现,LinkedList底层基于链表实现的
- ArrayList更适合随机查找,LinkedList更适合添加和查询
ArrayList:
底层基于数组实现,可根据下表快速查找遍历,适合随机查找和遍历,不适合插入和删除。(提一句实际上)
默认初始大小为10,当数组容量不够时,会触发扩容机制(扩大到当前的1.5倍),需要将原来数组的数据复制到新的数组中;当从 ArrayList 的中间位置插入或者删除元素时,需要对数组进行复制、移动、代价比较高。
LinkedList:
底层基于双向链表实现,适合数据的动态插入和删除;
内部提供了 List 接口中没有定义的方法,用于操作表头和表尾元素,可以当作堆栈、队列和双向队列使用。(比如jdk官方推荐使用基于linkedList的Deque进行堆栈操作)
ArrayList与LinkedList区别:
都是线程不安全的,ArrayList 适用于查找的场景,LinkedList 适用于增加、删除多的场景
3.HashMap原理是什么,在jdk1.7和1.8中有什么区别
HashMap 底层是数组+链表;即哈希表,
实现了Map接口,初始数组长度为16,加载因子为0.75
Entry
类型的 主数组 table 用于存放一个一个的Entry类型的数组对象【】,Entry对象包含Key,Value,hash码(k的),还有下一个元素的地址,k值相同时产生hash碰撞,替换原有value,k值不同的情况产生hash碰撞,7上8下。
存储数据时根据键的 hashCode值对应数组中元素的位置
根据键的 hashCode 值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历顺序却是不确定的。
- HashMap最多只允许一条记录的键为null,允许多条记录的值为 null。
- HashMap 非线程安全,即任一时刻可以有多个线程同时写 HashMap,可能会导致数据的不一致。如果需要满足线程安全,可以用 Collections 的 synchronizedMap 方法使 HashMap 具有线程安全的能力,或者使用 ConcurrentHashMap定义Map。
线程与进程
1. 多线程的特性
随机性:理论上,在同一个时刻,只能有一个程序在执行,表面上看到多个程序在
同时执行,这是因为CPU高效的切换着,所以感觉是多个程序同时执行
2. 线程状态理解
线程的生命周期总共有5种状态
-
新建状态(new):当线程对象被创建后,进入新建状态
-
就绪状态(Runnable):当调用线程的start()方法时,线程进入就绪状态,处于就绪状态的线程,说明线程已经做好了准备,随时等待CPU调度执行,并不是执行start()方法之后就会立即执行
-
运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才能开始执行,就绪状态是运行状态的唯一入口,也就是,线程要想进入运行状态执行,就必须处于就绪状态
-
阻塞状态(Blocked):处于运行状态的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,进入阻塞状态,直到其再次进入就绪状态,才有机会被CPU再次调用进入到运行状态
-
死亡状态(Dead):线程执行完或者因异常退出run()方法,该线程就结束生命周期
-
阻塞状态的分为三种:
等待阻塞:运行状态中的线程执行wait()方法,使本线程进入等待阻塞状态
同步阻塞:线程在获取Sunchronized同步锁失败后,因为锁被其他线程占用,它会进入同步阻塞状态
其他阻塞:通过调用线程的sleep()和Join()或者发起I/O请求时,线程会重新转入就绪状态
3.什么是线程池
设计池的核心思想(目的)是为了对象能够更好的进行重用
线程池是为了线程的重用(创建线程的代价很高),通过池中的线程并发的去处理任务,减少处理器单元的闲置时间,增加处理器单元的吞吐能力。
线程池技术关注 缩短或调整 创建和销毁线程时间的技术,从而提高服务器程序性能的。它把线程的创建和销毁分别安排在服务器程序的启动和结束的时间段或者一些空闲的时间段,这样在服务器程序处理客户请求时,不会有这些开销。
一个线程池包括以下四个基本组成部分:
1、线程池管理器(ThreadPool):用于创建并管理线程池,包括 创建线程池,销毁线程池,添加新任务;
2、工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;
3、任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;
4、任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。
4. 常见四种线程池
①newSingleThreadExecutor
单个线程的线程池,即线程池中每次只有一个线程工作,单线程串行执行任务
②newFixedThreadExecutor(n)
固定数量的线程池,没提交一个任务就是一个线程,直到达到线程池的最大数量,然后后面进入等待队列直到前面的任务完成才继续执行
③newCacheThreadExecutor(推荐使用)
可缓存线程池,当线程池大小超过了处理任务所需的线程,那么就会回收部分空闲(一般是60秒无执行)的线程,当有任务来时,又智能的添加新线程来执行。
④newScheduleThreadExecutor
大小无限制的线程池,支持定时和周期性的执行线程
阿里规范不允许使用Executors静态工厂构建线程池,规避资源耗尽的风险
Executors返回的线程池对象的弊端如下:
1:FixedThreadPool 和 SingleThreadPool:
允许的请求队列(底层实现是LinkedBlockingQueue)长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM
2:CachedThreadPool 和 ScheduledThreadPool
允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。
5.阿里规范线程池创建
ThreadPoolExecutor–阿里规范,最灵活的一种方式
五大参数:
- corePoolSize 核心线程数 ,池中的任务没有达到核心线程数阈值时,每来一个任务,都会新建一个线程 常用值:2CPU+磁盘数,考虑到I/O操作,所以两倍cpu。
- maximumPoolSize 最大线程数
- keepAliveTime 最大空闲时间
- TimeUnit.SECONDS 时间单位
- workQueue 阻塞式队列对象:通过此队列存储排队等待执行的一些任务对象(排队等待)
BlockingQueue<Runnable> workQueue=new ArrayBlockingQueue<Runnable>(2);
ThreadPoolExecutor tPool=new ThreadPoolExecutor(2, 3,60, TimeUnit.SECONDS,workQueue);
吞吐量:最多可以并发处理的请求数 = 最大线程数 + 阻塞式队列 即3+2=5
核心—队列—新建线程 ,超出吞吐量就会拒绝执行,
通过池中的线程执行任务
tPool.execute(new Runnable() {
public void run() {
String tName=Thread.currentThread().getName();
System.out.println(tName+" execute task01 ");
try{Thread.sleep(5000);}catch(Exception e){}
}
});
2. 数据库
SQL优化
1. 优化思想
- 最大化利用索引;
- 尽可能避免全表扫描;
- 减少无效数据的查询;
2.SQL执行顺序
SELECT语句 - 语法顺序:
1. SELECT
2. DISTINCT <select_list>
3. FROM <left_table>
4. <join_type> JOIN <right_table>
5. ON <join_condition>
6. WHERE <where_condition>
7. GROUP BY <group_by_list>
8. HAVING <having_condition>
9. ORDER BY <order_by_condition>
10.LIMIT <limit_number>
SELECT语句 - 执行顺序:
FROM
<表名> # 选取表,将多个表数据通过笛卡尔积变成一个表。
ON
<筛选条件> # 对笛卡尔积的虚表进行筛选
JOIN <join, left join, right join…>
<join表> # 指定join,用于添加数据到on之后的虚表中,例如left join会将左表的剩余数据添加到虚表中
WHERE
<where条件> # 对上述虚表进行筛选
GROUP BY
<分组条件> # 分组
<SUM()等聚合函数> # 用于having子句进行判断,在书写上这类聚合函数是写在having判断里面的
HAVING
<分组筛选> # 对分组后的结果进行聚合筛选
SELECT
<返回数据列表> # 返回的单列必须在group by子句中,聚合函数除外
DISTINCT
#数据除重
3.避免不走索引的场景
- 尽量避免在字段开头模糊查询,会导致数据库引擎放弃索引进行全表扫描。如下:
SELECT * FROM t WHERE username LIKE '%陈%'
优化方式:尽量在字段后面使用模糊查询。如下:
SELECT * FROM t WHERE username LIKE '陈%'
如果需求是要在前面使用模糊查询,
使用MySQL内置函数INSTR(str,substr) 来匹配,作用类似于java中的indexOf(),查询字符串出现的角标位置,可参阅《MySQL模糊查询用法大全(正则、通配符、内置函数等)》
使用FullText全文索引,用match against 检索
数据量较大的情况,建议引用ElasticSearch、solr,亿级数据量检索速度秒级
当表数据量较少(几千条儿那种),别整花里胡哨的,直接用like ‘%xx%’。
- 尽量避免使用in 和not in,会导致引擎走全表扫描。如下:
SELECT * FROM t WHERE id IN (2,3)
优化方式:如果是连续数值,可以用between代替。如下:
SELECT * FROM t WHERE id BETWEEN 2 AND 3
如果是子查询,可以用exists代替。详情见《MySql中如何用exists代替in》如下:
– 不走索引
select * from A where A.id in (select id from B);
– 走索引
select * from A where exists (select * from B where B.id = A.id);
- 尽量避免使用 or,会导致数据库引擎放弃索引进行全表扫描。
SELECT * FROM t WHERE id = 1 OR id = 3
优化方式:可以用union代替or。如下:
SELECT * FROM t WHERE id = 1
UNION
SELECT * FROM t WHERE id = 3
- 尽量避免进行null值的判断,会导致数据库引擎放弃索引进行全表扫描。如下:
SELECT * FROM t WHERE score IS NULL
优化方式:可以给字段添加默认值0,对0值进行判断。如下:
SELECT * FROM t WHERE score = 0
- 尽量避免在where条件中等号的左侧进行表达式、函数操作,会导致数据库引擎放弃索引进行全表扫描。
-- 全表扫描
SELECT * FROM T WHERE score/10 = 9
-- 走索引
SELECT * FROM T WHERE score = 10*9
- 当数据量大时,避免使用where 1=1的条件。通常为了方便拼装查询条件,我们会默认使用该条件,数据库引擎会放弃索引进行全表扫描。如下:
SELECT username, age, sex FROM T WHERE 1=1
优化方式:用代码拼装sql时进行判断,没 where 条件就去掉 where,有where条件就加 and。
- 查询条件不能用 <> 或者 !=
使用索引列作为条件进行查询时,需要避免使用<>或者!=等判断条件。如确实业务需要,使用到不等于符号,需要在重新评估索引建立,避免在此字段上建立索引,改由查询条件中其他索引字段代替。 - where条件仅包含复合索引非前置列
如下:复合(联合)索引包含key_part1,key_part2,key_part3三列,但SQL语句没有包含索引前置列"key_part1",按照MySQL联合索引的最左匹配原则,不会走联合索引。详情参考《联合索引的使用原理》。
select col1 from table where key_part2=1 and key_part3=2
- 隐式类型转换造成不使用索引
如下SQL语句由于索引对列类型为varchar,但给定的值为数值,涉及隐式类型转换,造成不能正确走索引。
select col1 from table where col_varchar=123;
- order by 条件要与where中条件一致,否则order by不会利用索引进行排序
-- 不走age索引
SELECT * FROM t order by age;
-- 走age索引
SELECT * FROM t where age > 0 order by age;
SELECT语句其他优化
避免出现select *
避免出现不确定结果的函数
多表关联查询时,小表在前,大表在后。
JDBC
JDBC 连接数据库查询数据过程?
1.注册数据库驱动(利用反射加载,自动注册,交给DriverManager管理)
2.获取数据库连接(getConnection方法需要传入数据源参数)
3.获取传输器(createStatement()方法,用于发送sql语句)
4.发送SQL到服务器执行并返回结果集rs
5.处理结果(查询sql需要将rs对象中的数据遍历,通过循环next()方法获取每一行数据)
6.释放资源
JDBC 跟 Mybatis的区别?
1.Mybatis是一种支持SQL的持久层框架,底层仍然是JDBC。
2.Mybatis相对于直接使用JDBC, 代码大大简化,比如能够直接将ResultSet中的数据转换成所需要的Java bean对象等。
3.MyBatis把SQL统一放到配置文件中进行管理,不用将SQL语句分散在各个java类中,方便代码的维护。
4.JDBC代码相对繁琐但访问速度更快,比如使用JDBC批处理等方式效率比Mybatis要高。
Mybatis
使用MyBatis的mapper接口调用时有哪些要求?
1、Mapper 接口方法名和mapper.xml 中定义的每个 sql 的 id 相同
2、Mapper 接口方法的输入参数类型和 mapper.xml 中定义的每个 sql 的 parameterType 的类型相同
3、Mapper 接口方法的输出参数类型和 mapper.xml 中定义的每个 sql 的 resultType 的类型相同
4、Mapper.xml 文件中的 namespace 即是 mapper 接口的类路径
为什么要使用连接池来访问数据库?
1.Tomcat与数据库通信,底层基于TCP协议, TCP协议是面向连接的协议,每次与数据库通信建立连接和释放连接的过程是非常耗时,三次握手建立连接,四次挥手释放连接
2.而使用连接池,在服务器启动时就创建一批连接,置于池中需要时在池中获取连接对象,用完连接后不用释放连接,而是返回池中.这样一来池中连接得以复用,减少了创建和释放的次数,避免了大量的创建和销毁代价.
Mybatis封装查询结果的过程
- 找到sql语句的定位标识(namespace+id) 对应接口方法
- 读取并执行sql语句得到数据,根据ResultType属性规定的封装类型封装数据
- 如规定封装到Emp对象中,则通过反射创建Emp对象实例,通过数据库中对应表的字段生成set方法(setName)
- 如果Emp对象中有对应的set方法,则通过set方法为对应属性赋值
- 如果没有找到对应set方法,则根据库中表字段寻找对象实例中有无同名属性,如果有就通过暴力反射将查询结果赋值给对应属性
$ { } 与 # { } 区别
1.参数传递
- #{}占位符在对前端传递来的参数进行拼接时,并不是简单将占位符替换为参数,#{}占位符会在字符串或日期类型的参数两边加上单引号,做转义处理
- $ {}占位符是为SQL语句中的某一个SQL片段进行占位,参数传递时直接将$ {}占位符替换
直接拼接,可能会引发SQL注入攻击,因此不推荐大量使用!
2.参数封装
- 如果在SQL语句中 #{ } 占位符只有一个, 占位符名称没有要求,但不能为空; 参数可以直接传递,不用封装;
- 如果通过Map集合来封装SQL参数值,#{}占位符中名称要和Map集合的key保持一致!因为在mybatis底层是通过占位符名称作为key到map中获取对应的value
- 如果通过POJO对象来封装SQL参数值,占位符名称要在POJO对象中有对应的getXxx方法,或者有对应的变量
- 只有一个${}占位符,参数也必须得先封装到Map或者POJO对象中,再把Map或者POJO对象传递过去,规则和#{}占位符一致
什么是视图、索引 、 约束?
索引
索引是一种特殊的文件(InnoDB数据表上的索引是表空间的一个组成部分),它们包含着对数据表里所有记录的引用指针。
索引存在的目的是为提高查询执行的速度。
视图(view)
存储的SELECT语句,基于基表的查询结果。视图也叫虚表。一般不建议将内容插入或更新视图,因为视图是受到基表的字段限制。在mysql中用处不是很大。
约束
域约束:数据类型约束
外键约束:引用完整性约束
主键约束:主键是某字段能唯一标识此字段所属的实体,并且不允许为空。符合这个条件的被称为候选主键。一个表只能有一个主键。不允许2个实体在主键上出现相同值。
唯一性约束:每一行的某字段都不允许出现相同值。但是可以为空。表中可出现多个。
检查性约束: 自定义约束条件。mysql上功能比较薄弱。
常见索引失效情况
如果是同样的sql如果在之前能够使用到索引,现在使用不到索引,有以下几种主要情况:
随着表的增长,where条件出来的数据太多,大于15%,使得索引失效(会导致CBO计算走索引花费大于走全表)
统计信息失效 需要重新搜集统计信息
索引本身失效 需要重建索引
-
单独引用复合索引里非第一位置的索引列
假如有INDEX(a,b,c), 当条件为a或a,b或a,b,c时都可以使用索引, 但是当条件为b,c时将不会使用索引。
复合索引遵守“最左前缀”原则,即在查询条件中使用了复合索引的第一个字段,索引才会被使用。因此,在复合索引中索引列的顺序至关重要。如果不是按照索引的最左列开始查找,则无法使用索引。
-
对索引列运算,运算包括(+、-、*、/、!、<>、%、like’%_’(%放在前面)、or、in、exist等),导致索引失效。
错误的例子:select * from test where id-1=9; 正确的例子:select * from test where id=10;
注意!!
mysql sql 中如果使用了 not in , not exists , (<> 不等于 !=) 这些不走
< 小于 > 大于 <= >= 这个根据实际查询数据来判断,如果全盘扫描速度比索引速度要快则不走索引 。
-
对索引应用内部函数,这种情况下应该建立基于函数的索引。
select * from template t where ROUND(t.logicdb_id) = 1 此时应该建ROUND(t.logicdb_id)为索引。
-
类型错误,如字段类型为varchar,where条件用number。
例:template_id字段是varchar类型。 错误写法:select * from template t where t.template_id = 1 正确写法:select * from template t where t.template_id = ‘1’
-
如果MySQL预计使用全表扫描要比使用索引快,则不使用索引
-
like的模糊查询以%开头,索引失效
-
索引列没有限制 not null,索引不存储空值,如果不限制索引列是not null,oracle会认为索引列有可能存在空值,所以不会按照索引计算
如何让MyBatis千万级数据查询中,避免内存溢出?
流式查询指的是查询成功后不是返回一个集合而是返回一个迭代器,应用每次从迭代器取一条查询结果。流式查询的好处是能够降低内存使用。
注意:流式查询的过程当中,数据库连接是保持打开状态的,因此要注意的是:执行一个流式查询后,数据库访问框架就不负责关闭数据库连接了,需要应用在取完数据后自己关闭数据库连接。方案一:SqlSessionFactory ;方案二:@Transactional 注解
MyBatis 流式查询接口
MyBatis 提供了一个叫 org.apache.ibatis.cursor.Cursor 的接口类用于流式查询,这个接口继承了 java.io.Closeable 和 java.lang.Iterable 接口,由此可知:
Cursor 是可关闭的;
Cursor 是可遍历的。
除此之外,Cursor 还提供了三个方法:
-
isOpen():用于在取数据之前判断 Cursor 对象是否是打开状态。只有当打开时 Cursor 才能取数据;
-
isConsumed():用于判断查询结果是否全部取完。
-
getCurrentIndex():返回已经获取了多少条数据
因为 Cursor 实现了迭代器接口,因此在实际使用当中,从 Cursor 取数据非常简单:
cursor.forEach(rowObject -> {…});
方案一:SqlSessionFactory
我们可以用 SqlSessionFactory 来手动打开数据库连接
@GetMapping("foo/scan/1/{limit}")
public void scanFoo1(@PathVariable("limit") int limit) throws Exception {
try (
SqlSession sqlSession = sqlSessionFactory.openSession(); // 1
Cursor<Foo> cursor =
sqlSession.getMapper(FooMapper.class).scan(limit) // 2
) {
cursor.forEach(foo -> { });
}
}
上面的代码中,1 处我们开启了一个 SqlSession (实际上也代表了一个数据库连接),并保证它最后能关闭;2 处我们使用 SqlSession 来获得 Mapper 对象。这样才能保证得到的 Cursor 对象是打开状态的。
方案二:@Transactional 注解
@GetMapping("foo/scan/3/{limit}")
@Transactional
public void scanFoo3(@PathVariable("limit") int limit) throws Exception {
try (Cursor<Foo> cursor = fooMapper.scan(limit)) {
cursor.forEach(foo -> { });
}
}
它仅仅是在原来方法上面加了个 @Transactional 注解。这个方案看上去最简洁,但请注意 Spring 框架当中注解使用的坑:只在外部调用时生效。在当前类中调用这个方法,依旧会报错。
3. 框架
3.1Spring
1. Spring的组成部分
Spring框架主要由七部分组成,分别是 Spring Core、 Spring AOP、 Spring ORM、 Spring DAO、Spring Context、 Spring Web和 Spring Web MVC。
Spring主要作用是将其他框架进行整合,以一种统一的通用的方式进行管理框架对象(框架的大管家)
2. IOC实现原理
-
将创建对象的权利交给Spring管理,由Spring管理对象的生命周期(实例化对象(创建对象),初始化操作 (一般对对象的属性赋值),用户使用对象(调用其中的方法),对象销毁 (一般都是释放资源))
-
IOC有专门一个容器来创建这些对象,即由Ioc容器来控制对象的创建
-
反转:由容器来帮忙创建及注入依赖对象
Spring 中的 IoC 的实现原理就是工厂模式加反射机制。
6. Spring容器是如何创建对象的
- 当Spring程序执行时,首先会根据配置文件的内容进行解析
- 当程序解析到Bean标签时,则会根据反射机制,调用对象的无参构造方法,实例化对象
3. IOC容器 BeanFactory和ApplicationContext区别
4. 单例与多例的区别
单例与多例问题是指,当多个用户访问某个类时,系统是为每个用户创建一个该类实例,还是无论整个系统多少用户访问,只创建一个该类实例。
5. 多例模式有没有安全性问题?
线程是一个任务,对象是任务当中的一个个节点,两者没有本质关系,多例对象没有安全性问题
安全性问题:多个资源对共享数据进行操作,容易出现安全性问题,而java当中,调用对象目的是调用对象的方法,多个资源调用对象的方法是在不同线程下执行,相互之间并不干扰,所以多例模式并没有安全性问题
7. 依赖注入和工厂模式之间有什么不同?
1.虽然两种模式都是将对象的创建从应用的逻辑中分离,但是依赖注入比工厂模式更清晰
2.通过依赖注入,你的类就是POJO,它只知道依赖而不关心它们怎么获取
3.使用工厂模式,你的类需要通过工厂来获取
8. Spring 框架中都用到了哪些设计模式?
- 工厂模式:BeanFactory就是简单工厂模式的体现,用来创建对象的实例;
- 单例模式:Bean默认为单例模式。
- 代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术;
- 观察者模式:定义对象间一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得 到通知被制动更新,
9. 工厂模式(静态/动态)
通过String容器创建的对象一般是通过反射机制调用,但是有时候业务需要实例化抽象类/复杂的接口
String提供了工厂模式用于实例化复杂对象
3.2 SpringAOP
1.JDK动态代理执行流程
- 代理对象不会凭空产生,JDK进行了封装,会调用即可.
- Proxy.newProxyInstance实例化代理对象,需要3个参数
- 目标对象的类加载器ClassLoader
- Class<?>[ ] interfaces:JDK要求代理对象与目标对象实现同一接口,java中的接口可以多实现,所以要求传递的是目标对象的 类型接口 数组~
- 回调执行器,InvocationHandler ,相当于钩子函数,目标方法执行,就会触发回调invoke()
2.JDK代理与CGliB代理的区别
-
JDK代理要求目标对象与代理对象实现统一接口,他们两个像是兄弟关系
CGLIB代理不管目标对象是否实现接口都可以创建代理对象,但要求代理对象必须继承目标对象,他们两像是父子关系 -
JDK代理创建代理速度快,运行时稍慢,因为目标对象实现接口创建迅速,代理运行时中间调用诸多方法所以速度较慢
CGLIB代理创建速度稍慢,运行时快,内部有增强器,增强器通过大量反射去应用目标对象,所以创建时较慢,一旦代理对象创建完成后,其实就是父子级间的调用.所以运行时很快
3.SpringAOP实现原理
我的理解是AOP是对动态代理的一种优化,在程序运行期对CSD任意层级的方法进行全方位的监控和扩展的一种技术,就像是程序执行中必须经过的一道关卡,通过Spring提供的五大通知 和 四种方式定义的切入点表达式,形成一道关卡,只要满足了定义的切入点表达式的方法就可以进入切面进行扩展,实现一些公共的业务。常见:事务控制,权限控制,日志操作等
4.AOP名词
-
连接点(JoinPoint): 可能被扩展还未执行的方法[ addUser/deleteUser]—方法是连接点
Spring通过joinPoint对象进行数据的传递,动态获取目标对象的数据,如果通知方法的参数需要添加joinPoint对象,必须在第一位。 -
切入点(Pointcut): 实际扩展的方法,被代理对象调用之后proxy.addUser()变成切入点,切入见名知意即进入代理层执行的方法即为切入点
-
通知: 扩展方法的具体实现,也就是动态代理里面写的内容(@before)
-
切面(@Aspect): 将通知应用到切入点的过程
5.通知类型
针对于目标方法Object result = method.invoke(target);执行前后,通知方法如果需要JoinPoint,他必须在参数列表的第一位,ProceedingJoinPoint 只能应用于around通知方法中
-
before通知: 目标方法执行前执行
-
afterReturning 通知: 目标方法执行之后(返回结果时)执行
-
afterThrowing 通知: 目标方法执行之后,抛出异常时执行
-
after通知 无论程序是否执行成功,都要最后执行的通知 (类似与异常控制中的finally(关闭资源))
-
around通知:: 在目标方法执行前后 都要执行的通知(完美体现了动态代理模式),功能最为强大 只有环绕通知可以控制目标方法的执行
-
@Around环绕通知
规则: 在目标方法执行前后都要执行
实际作用: 可以控制目标方法是否执行.
参数: ProceedingJoinPoint 通过proceed方法控制目标方法执行.
ProceedingJoinPoint is only supported for around advice
ProceedingJoinPoint extends JoinPoint 只支持Around通知
proceed方法作用:
- 执行目标方法
- 接收返回值 (固定形参result)
- catch异常信息
- 执行下一通知
只需要注解即可实现动态代理
总结:
1.环绕通知是处理业务的首选. 可以修改程序的执行轨迹
2.另外的四大通知一般用来做程序的监控.(监控系统) 只做记录
多个通知作用于同一方法,可以对切入点表达式进行抽取,用@Pointcut注解标识
6.切入点表达式
- bean(“bean的ID”) 根据beanId进行拦截 只能匹配一个效率低,主要看需求
- within (包名.类名) 可以使用通配符*? 能匹配多个.
粒度: 上述的切入点表达式 粒度是类级别的. 粗粒度
- execution(返回值类型 包名.类名.方法名(参数列表…)) —万能表达式
粒度: 方法参数级别. 所以粒度较细. 最常用的.
- @annotation(包名.注解名) 只拦截注解.
粒度: 注解是一种标记 根据规则标识某个方法/属性/类 细粒度
@Aspect //表示切面类
@Order //切面类注解通过Order注解实现控制切面的执行的顺序
@Component //将类对象交给容器管理
@EnableAspectJAutoProxy(proxyTargetClass=false)
//配置类表示启动aop注解创建代理对象,默认启用JDK动态代理(目标对象没有实现接口时启用CGLIB)
6. Spring容器回调执行原理
7. Spring事务失效的几种常见情况
1.spring的事务注解@Transactional只能放在public修饰的方法上才起作用,如果放在其他非public(private,protected)方法上,虽然不报错,但是事务不起作用
2.如果采用spring+spring mvc,则context:component-scan重复扫描问题可能会引起事务失败。
如果spring和mvc的配置文件中都扫描了service层,那么事务就会失效。
原因:因为按照spring配置文件的加载顺序来讲,先加载springmvc配置文件,再加载spring配置文件,
我们的事务一般都在srping配置文件中进行配置,如果此时在加载srpingMVC配置文件的时候,
把servlce也给注册了,但是此时事物还没加载,也就导致后面的事物无法成功注入到service中。
所以把对service的扫描放在spring配置文件中或是其他配置文件中。
3、如使用mysql且引擎是MyISAM,则事务会不起作用,原因是MyISAM不支持事务,可以改成InnoDB引擎
4、在业务代码中如果抛出RuntimeException异常,事务回滚;但是抛出Exception,事务不回滚;
解决方法@Transactional改为@Transactional(rollbackFor = Exception.class)
5、如果在加有事务的方法内,使用了try…catch…语句块对异常进行了捕获,而catch语句块没有throw new RuntimeExecption异常,事务也不会回滚
6、在类A里面有方法a 和方法b, 然后方法b上面用 @Transactional
加了方法级别的事务,在方法a里面 调用了方法b, 方法b里面的事务不会生效。原因是在同一个类中的方法互相调用,切面无效 ,而不仅仅是事务。这里事务之所以无效,是因为spring的事务是通过aop实现的。
3.3 SpringMVC
1.servlet生命周期
实例化对象
初始化对象
调用对象
销毁
2.MVC是什么?
MVC设计模式的好处有哪些mvc是一种设计模式。
(model)持久层,代码与数据库进行交互的代码
(view)视图层,一般指用户看到的内容
(control)控制层 完成某项业务的具体操作过程,三层架构的设计模式。
3.SpringMVC调用步骤
四大核心组件:前端控制器,处理器映射器,处理器适配器,视图解析器,整个调用过程秉承松耦合思想
1.用户发起请求时,首先被前端控制器拦截
2.Tomcat服务器启动时,处理器映射器会加载所有@RequestMapping注解,将请求路径与方法绑定存入一个Map< /请求路径 , 方法路径>,将查找到的方法信息回传给前端控制器,进行后续的调用
3.前端控制器将查询到的方法交给处理器适配器,适配合适的处理器去执行方法
4.挑选到合适的处理器之后,程序开始真正执行业务方法,经过根据注入的依赖进行三层调用,业务执行成功之后,返回统一的ModelAndView对象,其中Model代表服务器数据, View代表页面逻辑名称
5.前端控制器拿到ModelAndView对象,交给视图解析器,视图解析器干两个活:
- 解析View对象的逻辑名称,根据Yml配置,动态拼接前后缀最终形成用户展现页面的全路径
- 将Model数据渲染到页面,最后将页面交给前端控制器
6.前端控制器将得到的完整页面响应给浏览器进行展现
4.对象的方式接收参数
如果有大量的页面的提交数据,如果采用单独的参数接收,必然导致Controller方法结构混乱,不便于理解.所以采用对象的方式封装
//POJO实体对象中 “必须” 使用包装类型
//规则说明: 1.基本类型有默认值 包装类型默认值为null
// 2. 基本类型中没有多余的方法 对后续代码取值有问题
5.SpringMvc 的控制器是不是单例模式,如果是,有什么问题,怎么解决?
答:是单例模式,所以在多线程访问的时候有线程安全问题,不要用同步,会影响性能的,解决方
案是在控制器里面不能写字段。
6.SpringMVC 怎么样设定重定向和转发的?
答:在返回值前面加"forward:“就可以让结果转发,譬如"forward:user.do?name=method4” 在
返回值前面加"redirect:“就可以让返回值重定向,譬如"redirect:http://www.baidu.com”
3.4 Springboot
1.Spring Boot优点
1.简化开发,提高整体生产力 Spring Boot 使用 JavaConfig 配置类 有助于避免使用 XML配置
2.开箱即用,避免大量的Maven导入和各种版本冲突 ,只需要在pom文件中导入启动类,它会根据mavenjar包的依赖性,去导入,
SpringBoot 引导的应用程序可以很容易地与 Spring 生态系统集成
3.Spring Boot 应用程序提供嵌入式HTTP服务器,如Tomcat和Jetty,可以开发和测试web应用程序,不需要再启动Tomcat
4.Spring Boot 提供命令行接口工具,用于开发和测试应用程序
2. Spring Boot 的自动配置是如何实现的?
在主启动类添加 @SpringBootApplication 注解实现自动配置,@SpringBootApplication由三个注解组成:
-
@Configuration
-
@ComponentScan
-
@EnableAutoConfiguration
3.Springboot处理CORS跨域请求的三种方法
同源策略:浏览器解析页面时,当页面中有ajax请求,要求页面的URL地址与ajax请求的地址必须满足 请求协议,请求域名,请求端口都相同,才可正常通信。
CORS(Cross-origin resource sharing)全称是”跨域资源共享”是一个W3C标准。它通过服务器增加一个特殊的Header[Access-Control-Allow-Origin]来告诉客户端跨域的限制,如果浏览器支持CORS、并且判断Origin通过的话,就会允许XMLHttpRequest发起跨域请求。
- 方法一:在Controller层需要跨域的类或者方法上加上@CrossOrigin注解,需要加的Controller类多的话,可以加在Controller公共父类(PublicUtilController)中,需要的Controller继承就可以了。
@CrossOrigin
public class PublicUtilController {}
- 方法二 :修改配置类
增加一个配置类,CrossOriginConfig.java。继承WebMvcConfigurerAdapter或者实现WebMvcConfigurer接口,项目启动时,会自动读取配置。
@Configuration
public class CorsConfig extends WebMvcConfigurerAdapter {}
- 方法三:采用过滤器(filter)的方式
增加一个CORSFilter 类,并实现Filter接口即可,接口调用时,会过滤跨域的拦截。
@Component
public class CORSFilter implements Filter {}
3.4 MybatisPlus
Sql转化原理
关键点是获取表名,字段名。 表名在对应实体类的注解中
描述前端发送请求到后台接受请求执行过程
1.main.js:程序的入口文件,初始化了vue实例、加载使用需要的插件、加载各种公共组件
put post delete
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import './plugins/element.js'
import './assets/css/global.css'
import './assets/ali-icon/iconfont.css'
import VueQuillEditor from 'vue-quill-editor'
/* 导入富文本编辑器对应的样式 */
import 'quill/dist/quill.core.css' // import styles
import 'quill/dist/quill.snow.css' // for snow theme
import 'quill/dist/quill.bubble.css' // for bubble theme
/* 导入axios包 */
import axios from 'axios'
/* 设定axios的请求根目录 */
//axios.defaults.baseURL = 'http://localhost:8091/'
axios.defaults.baseURL = 'http://manage.jt.com/'
/* 向vue对象中添加全局对象 以后发送ajax请求使用$http对象 */
Vue.prototype.$http = axios
//定义格式化价格的过滤器
Vue.filter('priceFormat',(price)=>{
return (price/100).toFixed(2)
})
Vue.config.productionTip = false
/* 将富文本编辑器注册为全局可用的组件 */
Vue.use(VueQuillEditor)
new Vue({
router,
render: h => h(App)
}).$mount('#app')
4.架构
概念题
1.分布式优点
扩容
高可用
2.从浏览器输入url到数据加载完成,整个过程发生了什么?
- 浏览器会先查看浏览器缓存–系统缓存–路由缓存,如有存在缓存,就直接显示。如果没有
- 会将(域名)发送至DNS域名解析服务器(常用114),将网址解析为ip地址[通过IP地址找到网络中的计算机(服务器),通过端口号访问服务器中对应的应用(如:Tomcat常用端口为80,数据库常用端口为3306)]
- 浏览器基于http协议向服务器发起请求
- 服务器(Tomcat)响应请求,分配线程处理请求
- 服务器基于TCP协议,与数据库建立连接进行通讯,获取数据,响应至浏览器
- 浏览器接收响应,读取页面内容,解析html源码,生成DOm树,解析css源码,进行页面渲染
3.什么是跨域请求?
浏览器解析页面时,当页面中有ajax请求,则要求页面的URL地址,与Ajax请求的地址必须满足
请求协议 ,请求域名,请求的端口,都相同.也就是同源策略,违反同源策略,浏览器不能正常解析数据,则称为跨域请求
4.Tcp和Http协议关系
下个月你就要结婚了,打个电话给老王。
你先打开电话本,查找“老王”的电话号码:老王 -> 13987654321(DNS解析)。
然后你用手机拨打了13987654321(IP)。
你的手机连接到了联通的基站(路由器),联通(你自己的网关)发现这是个移动的手机号,通过移动的帮助,找到了老王的手机(MAC),老王的手机开始响铃。
“嘟……”(TCP握手)
-“喂?老绿啊!”“哈哈,是我啊,老王!(连接建立)下个月我结婚,你有空吗?”(开始传输数据,先说事)“噢,有啊!”
-“那你记一下地址啊(再说时间地点)!北京市东长安街16号午门太和殿。”“诶,你慢点说(流量控制),我记一下。”
-“北京市”(缩小了滑动窗口)“嗯,然后呢?”(ACK)“东长安街16号”“东……16号”(ACK)
-“午门太和殿”“午门(ACK“午门”)什么殿?你再说一遍,我没听清!”(“太和殿”传输失败,数据包丢失)
-“太和殿”(重传)“好!记下了”(传输成功)
-“那回见啊!”(准备断开连接)“好嘞!”
【通话已结束】(连接断开)
刚才的例子实际上传递了一段信息:我下个月结婚。
HTTP只是信息的载体,刚才说的“汉语”就是HTTP。你也可以用其他协议传递,比如“I’m getting married next month”,只要对方能理解协议,那就是同一个意思。
电话线路相当于一个TCP连接。TCP提供了流量控制、数据重传等机制保证了数据可靠顺序传输。当然这个例子中,可靠传输是靠人来保证的。
HTTP和TCP的联系,是HTTP(要结婚的消息)使用TCP(电话)提供的“传输能力”。除此之外,没了。
很多答案都提到了,网络协议是一个栈,提供对等通信。这里解释下对等。
老王和老绿只与电话发生交互,他们不理解声音如何转换成电信号,也不理解电磁波如何传播。他们只知道,我对着电话说汉语,对方就能听到汉语。老王和老绿就是一个对等的协议层。两部电话也是对等的协议层,它们给上面的老王和老绿提供传输语音的服务。而电话提供的服务,底层又依赖电磁波传递无线信号。最后形成了一个个服务层,完成了打电话这个事情。再举个栗子,两个老板要约时间谈生意,他们会跟自己的秘书下达指令,让秘书搞定约会这个事。双方的秘书会互相联系,约好时间地点和司机,最后搞定这个问题。老板们不会跟对方的秘书联系(除非是小老板对马老板,那他们实际上就是不对等的),因为他们不是一个层的。
把上文层次的概念,放到计算机网络中,就是协议栈。协议栈的每一层,都专注于自己层的事情:HTTP专注于要传输的信息(HTTP是信息的载体,所以在协议里面会标注信息长度,信息类型等),TCP专注于传输的可靠(为了可靠传输,TCP会给自己层的包标注大小和顺序,并且有确认机制),IP负责因特网传输(IP也有自己的格式,自己查吧),再下面的层负责与局域网和硬件打交道。
简单来说,网络协议栈里面,每层都解决了计算机通信流程的某一环节的问题。它们使用下层提供的能力跟对方机器的相应协议层通信,给上层提供自己的服务。
最后,TCP的下层是IP。“我下个月结婚”这几个字,你也可以通过短信(UDP)或微信(其他传输协议)来发送。虽然传输层协议不一样(电话 vs 短信),底层还是依赖IP协议(发短信仍然需要知道对方的电话号码)。
SpringCloud
单点登录怎么实现的
cookie实现
浏览器第一次发送请求访问tomcat服务器时,tomcat服务器创建会话,验证用户名和密码,如果用户名密码正确,则设置会话状态
响应会话id:jsessionid 有浏览器cookie保存
cookie是有限制的,这个限制就是cookie的域(通常对应网站的域名),浏览器发送http请求时会自动携带与该域匹配的cookie,而不是所有
同域名共享cookie方式的弊端
- 第一:应用群域名得统一;
- 第二:应用群各系统使用的技术(至少是web服务器)要相同,不然cookie的key值(tomcat为JSESSIONID)不同,无法维持会话,共享cookie的方式是无法实现跨语言技术平台登录的,比如java、php、.net系统之间;
- 第三:cookie本身不安全。
因此,我们需要一种全新的登录方式来实现多系统应用群的登录,这就是单点登录
单点登录原理
单点登录全称Single Sign On(以下简称SSO)
sso需要一个独立的认证中心,用户登录子系统,校验发现未登录,携带子系统的地址和用户登录的信息跳转到sso认证中心接受用户的用户名密码等安全信息,核心关键点就是授权令牌的创建 和 全局会话与局部会话的关系。
其他系统接受认证中心的间接授权。间接授权通过令牌实现,sso认证中心验证用户的用户名密码没问题,创建授权令牌,(令牌与注册系统地址通常存储在redis中,redis可以为key设置有效时间也就是令牌的有效期。)在接下来的跳转过程中,授权令牌作为参数发送给各个子系统,子系统拿到令牌,可以借此创建局部会话,局部会话登录方式与单系统的登录方式相同。这个过程,也就是单点登录的原理
- 令牌与注册系统地址通常存储在redis中的原因
redis运行在内存中,速度非常快,正好认证中心不需要持久化任何数据。存储起来的目的是用户注销时,认证中心根据存储的对应关系来获取判断该全局会话中有哪些子系统创建了局部会话。 - 如何实现注销
sso认证中心一直监听全局会话的状态,一旦全局会话销毁,监听器将通知所有注册系统执行注销操作
sso-client需将当前会话id与令牌绑定,表示这个会话的登录状态与令牌相关,此关系可以用java的hashmap保存,保存的数据用来处理sso认证中心发来的注销请求
具体过程
1)用户访问应用群中的系统的受保护资源,系统发现用户未登录,跳转至sso认证中心,并将自己的地址作为参数
2)sso认证中心发现用户未登录,将用户引导至登录页面
3)用户输入用户名密码提交登录申请
4)sso认证中心校验用户信息,创建用户与sso认证中心之间的全局会话,同时创建授权令牌
5)sso认证中心带着令牌跳转回最初的请求地址(系统1)
6)系统1拿到令牌,去sso认证中心校验令牌是否有效
7)sso认证中心校验令牌,返回有效,注册系统1
8)系统1使用该令牌创建与用户的局部会话,并响应受保护资源
9)用户访问系统2的受保护资源
10)当用户访问另一子系统时,发现用户未登录,跳转至sso认证中心,并将自己的地址作为参数
11)sso认证中心发现用户已登录,跳转回系统2的地址,并附上令牌
12)系统2拿到令牌,去sso认证中心校验令牌是否有效
13)sso认证中心校验令牌,返回有效,注册系统2,系统2使用该令牌创建与用户的局部会话,返回受保护资源
用户登录成功之后,会与sso认证中心及各个子系统建立会话,用户与sso认证中心建立的会话称为全局会话,用户与各个子系统建立的会话称为局部会话,局部会话建立之后,用户访问子系统受保护资源将不再通过sso认证中心,全局会话与局部会话有如下约束关系
局部会话存在,全局会话一定存在。
全局会话存在,局部会话不一定存在。
全局会话销毁,局部会话必须销毁。
注销设计:在一个子系统中注销,所有子系统的会话都应该被销毁
sso认证中心一直监听全局会话的状态,一旦全局会话销毁,监听器将通知所有注册系统执行注销操作
sso-client需将当前会话id与令牌绑定,表示这个会话的登录状态与令牌相关,此关系可以用java的hashmap保存,保存的数据用来处理sso认证中心发来的注销请求
1)用户向系统1发起注销请求。
2)系统1根据用户与系统1建立的会话id拿到令牌,向sso认证中心发起注销请求。
3)sso认证中心校验令牌有效,就会销毁全局会话,同时取出所有用此令牌注册的系统地址。
4)sso认证中心向所有注册过系统发起注销请求。
5)各注册系统接收sso认证中心的注销请求,销毁局部会话。
6)sso认证中心引导用户至登录页面。
实现细节
sso认证中心与sso客户端通信方式有多种,这里以简单好用的httpClient为例,web service、rpc、restful api都可以
请求的方式有servlet、filter、listener三种方式,我们采用Filter,LoginFilter.java类并实现Filter接口,在doFilter()方法中
sso-client需将当前会话id与令牌绑定,表示这个会话的登录状态与令牌相关,此关系可以用java的hashmap保存,保存的数据用来处理sso认证中心发来的注销请求
注销过程
用户向子系统发送带有“logout”参数的请求(注销请求),sso-client拦截器拦截该请求,向sso认证中心发起注销请求
sso认证中心也用同样的方式识别出sso-client的请求是注销请求(带有“logout”参数),sso认证中心注销全局会话
sso认证中心有一个全局会话的监听器,一旦全局会话注销,将通知所有注册系统注销
JSP的九大xxxx
Servlet的四大生命周期
四大常用连接池 Druid c3p0
四大搜索引擎
四大索引类型:主键,唯一,全文,普通,联合
五大约束:
数据库三大范式
Git
Fork项目保持同步更新
1. 把fork的项目克隆到本地仓库中
git clone + fork项目在自己仓库的地址
2. Configuring a remote for a fork
相当于在源项目和你fork的项目之间建立一个通信管道,下面看看如何通信更新。
git remote -v
-- 查看远程状态
git remote add upstream +fork的项目的源地址
-- 给fork远程添加用于同步的上游仓库
git remote -v
-- 再次查看是否配置成功
3. Syncing a fork
- 从上游仓库 fetch 分支和提交点,传送到本地,并会被存储在一个本地分支
upstream/master
git fetch upstream
- 切换到本地主分支
git checkout master
- 把
upstream/master
分支合并到本地master
上,这样就完成了同步,并且不会丢掉本地修改的内容。如果源项目有更新而你fork的项目没更新的话,就会把源项目同步更新到你的本地仓库中了。
git merge upstream/master
- 更新到远程层库fork
git push origin master
Git分支相关命令
- 创建分支+切换分支一步完成:
git checkout -b <分支名>
- 想要将b分支上代码合并到a分支:
-- 先切换到a分支
git checkout a
-- 合并b分支
git merge b
- 删除分支
git branch -D <分支名>
Git工作流
master
主线分支:与线上系统版本保持一致,所有上线代码develop
开发分支:开发项目分支,在该分支下创建多个分支并行操作,避免团队开发过程中互相冲突影响,可并行开发多个模块,开发子分支完成后,合并到develop
分支release
测试分支:用于测试,出现bug解决完成后合并到master
分支用于上线,同时测试分支也要合并到devlop
分支,避免版本不一致hotfix
(热修复补丁):线上系统出现bug
,创建临时分支用于解决bug
,解决后再合并至master
分支,并且将临时分支合并到develop分支,保证master
与devlop
分支版本保持一致