导图社区 Java知识总结 (最新)
这是一篇关于屈媛媛 知识总结 (最新)的思维导图。主要包含第一阶段Java编程基础、第二阶段Web前端基础、第三四阶段框架及分布式。
编辑于2024-06-14 11:57:25Java
第一阶段Java编程基础
JVM:java虚拟机<JRE:java运行环境<JDK:java集成开发环境
二、八、十、十六进制之间的转换,原码、反码、补码,数据类型与变量、运算符代码规范与注释
数据类型(和变量配合使用,用于规范或者指定该变量的值是什么类型)
基本数据类型
整形
byte:1个字节,范围-128到127
位(bit):是计算机内部数据储存的最小单位,11001100是一个八位二进制数。字节(byte):是计算机中数据处理的基本单位,习惯上用大写B来表示1B(byte,字节)=8bit(位)字符:是指计算机中使用的字母,数字,字和符号1bit表示1位1Byte表示一个字节 1B=8b1024B=1KB1024KB=1M1024M=1G1024G=1TB
short:短整型,2个字节
int:整形(默认数据类型,通常整形推荐使用int),4个字节
long:长类型整形(只有当int类型装不下了,才使用long类型),8个字节
浮点型
float:单精度浮点型,4个字节,有效位8位,数值范围:-3.40E+38~3.40E+38
double:双精度浮点型(默认的浮点型,小数都推荐使用double),后面的值也可以跟整数,虚拟机在执行的时候,会自动加上.0,8个字节,有效位16位,数值范围:-1.79E+308~-1.79E+308
如果不声明,默认小数为double类型,所以如果要用float的话,必须进行强转例如:float a=1.3; 会编译报错,正确的写法 float a = (float)1.3;或者float a = 1.3f;(f或F都可以不区分大小写)注意:float是8位有效数字,第7位数字将会四舍五入 原文链接:https://blog.csdn.net/weixin_30725113/article/details/114189267
字符型
char:字符,使用单引号括起来,只能有一个符号,可以和int直接转换,后面的值可以跟整数,2个字节
布尔型
boolean:使用的频率非常高,都是用在判断语句的地方。值true或者false,1个字节
包装类数据类型
String:字符串,用于记录多个字符,使用双引号括起来,每一个字符串都有对应下标,从左往右,从第一个字符下标为0开始计算。后面的值,只认双引号,双引号里面的内容不管
float与double区别:字节不同、有效位不同,数值范围不同,在程序中处理速度不同:一般来说,CPU处理单精度浮点数的速度比处理双精度浮点数快
java为很多基本类型的包装类/字符串都建立常量池常量池:相同的值只储存一份,节省内存,共享访问https://blog.csdn.net/qq_45388628/article/details/101151109
public class IntegerTest { public static void main(String[] args) { Integer n1 = 127; Integer n2 = 127; System.out.println(n1 == n2); Integer n3 = 128; Integer n4 = 128; System.out.println(n3 == n4); Integer n5 = new Integer(127); System.out.println(n1 == n5); }}
在这里第一个输出为true,第二个和第三个为false因为第一个的两个Integer类型的对象的创建方式为常量式(字面量)赋值创建且赋值范围在常量池范围中,内存共享,两对象指向同一对象。第二个虽然创建方式为常量式(字面量)赋值创建,但赋值范围超出了Integer包装类的常量池范围,所以两对象未被常量化,指向的不是同一对象。第三个的n5创建方式为new对象进行创建,放在堆内存,而n1为常量式(字面量)赋值创建放在栈内存中,指向的地址不同
数据类型的转换
隐式转换
将小范围的值赋值给大范围数据类型声明的变量,不用刻意记忆,因为不需要程序处理
显示转换(强制转换)
大范围的数据类型赋值给小范围的数据类型,需要强制转换
举例:long a = 1000L;int b =(int) a;
运算符
赋值运算符
一个=,将右边的值或者右边带有值的变量,赋值给等号左边比如:int a = 10;将10的值,赋值给a变量int b = a;//将变量a的值,赋值给bint c = d;//错误,因为d本身就没有值,也没有定义,不能使用
算术运算符
+,-,*,/,%(除了+号,其他的运算符,只能计算数值运算,或者只能计算值为数值的变量);int a = 10;int b = 20;a-b;//可以String i = "abc";boolean j = true;i * j//错误,-,*,/,%只能用于数值运算
+号两个作用,当+号两端都是纯数字的时候,则做为加法运算符,计算两个数的和,得到的结果是整形或者浮点型当+号两端任意一端出现了字符串,则作为拼接符,将其连接,得到的结果是字符串举例:int a = 10;int b = 20;a + b;结果为30String i = "10";int j = 20;i + j;结果为“1020”
自运算:++,--
每一次将自身的值增加1,或者减少1
当自运算符放在变量名的前面,则是先自运算(加1或者减1),再使用或者赋值举例:int a = 10;int b = ++a;运行之后:a的值为11,b的值为11
当自运算符放在变量名的后面,则是先赋值或者使用,再自运算(加1或者减1)举例:int a = 10;int b = a++;结果:a的值为11,b的值为10
比较运算符
>,>=,<,<=,==,!=(特别注意等于,是==)
逻辑运算符
与运算&&
当两个条件,同真才为真,其余全为假
或运算||
当两个条件,同假才为假,其余全为真
非运算!
非假为真,非真为假,取反的意思
三元运算符
语法:数据类型 变量名 = 判断语句?值1:值2;boolean b = 10 > 20?true;false;b的结果只为false改造成分支语句:boolean b;if(10 > 20){ b = true;}else{ b = false;}
分支语句、循环语句
分支语句
if分支语句
情况1:if(判断语句){ 当判断语句为真的时候,执行的代码}
情况2:if(判断语句){ 当判断语句为true的时候,执行代码;}else{ 当判断语句为false的时候,执行的代码}
情况3:if(判断语句1){ 当判断语句1为true的时候,执行的代码}else if(判断语句2){ 当判断语句2为true的时候,执行代码}else{ 当前面的所有判断语句都为false的时候,执行的代码}
switch分支语句
应用场景:比较准确的数值,并且没有范围,情况比较少的时候,可以使用switch,其余情况建议使用if分支语句。
语法:定义变量并赋值;switch(变量){ case 值1: 当变量和值1匹配的时候,执行的代码; break; case 值n: 当变量和值n匹配的时候,执行的代码; break; default: 当前面全部没有匹配成功的时候,执行的代码,和if当中的else作用一致。}
break:当在switch分支语句中,遇到了break;则结束switch代码的执行,直接结束
default:和if当中的else作用一致。
循环
重点:for循环语句
语法:for(定义变量并赋值;判断语句;改变变量的值){ 当判断语句为真的时候,执行的循环代码}
举例:int sum = 0;for(int i = 0;i < 10;i++){ sum = sum + i;}
while循环
语法:定义变量并赋值;while(判断语句){ 循环的代码; 改变变量的值;(防止死循环)}
举例:int sum = 0;int i = 0;while(i < 10){ sum = sum + i; i++;}
do...while循环
语法:定义变量并赋值;do{ 循环的代码; 改变变量的值;}while(判断语句);
举例:int sum = 0;int i = 0;do{ sum = sum + i; i++;}while(i < 10);
while和do...while的区别
while是先判断语句是否为真,再决定是否执行循环代码;而do...while,是先执行循环代码,再判断条件是否正确。
跳出循环
continue
结束本次循环,继续下一次循环
举例:for(int i = 0;i < 10;i++){ if(i == 5){ continue; } System.out.println("i = "+i);}
重点:break
跳出整个循环,执行循环以下的代码
举例:for(int i = 0;i < 10;i++){ if(i == 5){ break; } System.out.println("i = "+i);}
数组
定义1:数据类型[] 数组名 = new 数据类型[数组长度]
定义2:数据类型[] 数组名 = {元素1,元素2,元素3.....};
数组的操作:
增加
数组名[下标] = 元素;
删除
将有元素的值,修改成默认值,对应有数据类型
修改
将已经有元素的位置重复覆盖:数组名[下标] = 新元素;
查询
直接数组名[下标];查询单个元素值
数组的遍历
普通for循环,通过操作下标,进行查找
示例代码:for(int i = 0;i < 数组名.length;i++){ System.out.println(数组名[i]);}
增强for循环,通过直接操作元素进行查找
示例代码:for(数据类型 变量名 :数组名){ System.out.println(变量名);}
从数组中找到最值
思路:1、将数组自身的第一个元素假设为最大值,定义一个变量记录最大值max2、循环遍历数组,从第1个下标处开始遍历,取到每一个元素。3、在循环里面,用max和每一个元素进行比较,如果max的值小于了其中某个元素,则用该元素值覆盖max原有的值。最终循环完之后,max就为最大值。
数组倒置
package mytest;public class test2 { public static void main(String[] args) { //1. 将数组 倒置 as: 1,2,0,4,5 => 5,4,0,2,1 //2.准备一个数组 int [] array = {1,2,0,4,5}; /** * 思路 : 将一个数组 倒置过来, 无非就是把他们的下标从头到尾 * 互相换个位置 第一个 和 最后一个 进行 交换 第二个 后 倒数第二个换 * 在循环中 进行 交换 就行了 ; * 注意 : 因为是互相换,所以只能 换 长度除以2次 */ //3. 第一位下标 int head = 0; //4. 最后一位下标 int end = array.length-1; //5.只循环 长度的一半 int center = array.length/2; //center结果为 2.5 因为精度丢失 所以结果为 2 for (int i = 0; i < center; i++) { //先把第一位给一个临时变量 int index =array[head]; //把最后一位放在第一位 array[head] = array[end]; //最后再把第一位给最后一位交换 array[end] = index; head++; end--; } //交换之后 遍历看结果 for (int i = 0; i < array.length; i++) { System.out.println(array[i]); } } }
冒泡排序
定义:用相邻的两个元素比较,以升序为例,如果前面的比后面的值大,则两者交换,每一轮比较之后,会找出最大值。需要元素个数-1轮循环才能完成排序完毕。
步骤:1、外层循环,控制要循环比较多少轮次才能够完全排序完毕。i<num.length-12、内层循环,控制每一轮相邻的两个比较,选出本轮中的最大值。j<num.length-1-i减i的原因是每多一轮的执行,则少比较一个元素。3、比较相邻的两个元素值,如果前面元素比后面元素大,则交换。这是升序4、在比较里面进行两个元素的交换,要使用第三个中间变量。
package day0515;public class demo_sort { public static void main(String[] args) { //冒泡排序算法 int[] numbers=new int[]{1,5,8,2,3,9,4}; int i,j; for(i=0;i<numbers.length-1;i++) { for(j=0;j<numbers.length-1-i;j++) { if(numbers[j]>numbers[j+1]) { int temp=numbers[j]; numbers[j]=numbers[j+1]; numbers[j+1]=temp; } } } System.out.println("从小到大排序后的结果是:"); for(i=0;i<numbers.length;i++) System.out.print(numbers[i]+" "); }}
选择排序
定义:从第0个下标开始,依次取到每一个元素,然后和取到该元素之后,和它后面的每一个元素进行比较。以升序为例,如果前面比后面的值大,则交换。每一轮选出最小值。
步骤:1、使用外层循环,依次取到每一个元素值。2、使用内层循环,从外层循环取到的元素下标开始,往后取到每一个元素。3、比较外层循环取到的下标对应的元素值和内层循环取到的下标对应的元素值4、如果外层比内层的元素值大,则交换。需要中间变量
//选择排序public class SelectionSort { public static void main(String[] args) { int[] arr={1,3,2,45,65,33,12}; System.out.println("交换之前:"); for(int num:arr){ System.out.print(num+" "); } //选择排序的优化 for(int i = 0; i < arr.length - 1; i++) {// 做第i趟排序 int k = i; for(int j = k + 1; j < arr.length; j++){// 选最小的记录 if(arr[j] < arr[k]){ k = j; //记下目前找到的最小值所在的位置 } } //在内层循环结束,也就是找到本轮循环的最小的数以后,再进行交换 if(i != k){ //交换a[i]和a[k] int temp = arr[i]; arr[i] = arr[k]; arr[k] = temp; } } System.out.println(); System.out.println("交换后:"); for(int num:arr){ System.out.print(num+" "); } }}
折半查找(二分法)
/** * 使用递归的二分查找 *title:recursionBinarySearch *@param arr 有序数组 *@param key 待查找关键字 *@return 找到的位置 */public static int recursionBinarySearch(int[] arr,int key,int low,int high){ if(key < arr[low] || key > arr[high] || low > high){ return -1; } int middle = (low + high) / 2; //初始中间位置 if(arr[middle] > key){ //比关键字大则关键字在左区域 return recursionBinarySearch(arr, key, low, middle - 1); }else if(arr[middle] < key){ //比关键字小则关键字在右区域 return recursionBinarySearch(arr, key, middle + 1, high); }else { return middle; } }
测试:public static void main(String[] args) { int[] arr = {1,3,5,7,9,11};int key = 4;//int position = recursionBinarySearch(arr,key,0,arr.length - 1); int position = commonBinarySearch(arr, key); if(position == -1){ System.out.println("查找的是"+key+",序列中没有该数!"); }else{ System.out.println("查找的是"+key+",找到位置为:"+position); } }
/** * 不使用递归的二分查找 *title:commonBinarySearch *@param arr *@param key *@return 关键字位置 */public static int commonBinarySearch(int[] arr,int key){ int low = 0; int high = arr.length - 1; int middle = 0; //定义middle if(key < arr[low] || key > arr[high] || low > high){ return -1; } while(low <= high){ middle = (low + high) / 2; if(arr[middle] > key){ //比关键字大则关键字在左域 high = middle - 1; }else if(arr[middle] < key){ //比关键字小则关键字在右域 low = middle + 1; }else{ return middle; } } return -1; //最后仍然没有找到,则返回-1}
测试:public static void main(String[] args) { int[] arr = {1,3,5,7,9,11};int key = 4;//int position = recursionBinarySearch(arr,key,0,arr.length - 1); int position = commonBinarySearch(arr, key); if(position == -1){ System.out.println("查找的是"+key+",序列中没有该数!"); }else{ System.out.println("查找的是"+key+",找到位置为:"+position); } }
二维数组
定义:数组中再存一个数组
语法1:数据类型[][] 数组名 = new 数据类型[数组长度][数组长度];
语法2:数据类型[][] 数组名 = {{元素1,元素2},{元素1,元素2,元素3}.....};
遍历二维数组
示例代码:for(int i =0;i < 数组名.length;i++){ for(int j = 0;j < 数组名[i].length;j++){ System.out.println(数组名[i][j]); }}
动态扩展数组
每当存储的数据多了,数组存放不下去之后,要对数组进行扩容,建议每次扩容一半
步骤:1、定义一个新的数组,数组的数据类型和原数组要一致,数组长度为原数组长度的1.5倍。2、将原数组中的元素复制到新的数组中。3、将新建的数组复制给原数组名。
函数:public访问修饰符、static静态关键字、返回值数据类型、函数名、参数列表(形参和实参)、实现特定功能的代码块、return返回值
面向对象总结
类与对象
类:使用class定义,具有相同属性和行为的一类事物的描述,将具有共同特征的一类方法定义在一个类中。
对象:从类中实例化出来的单个实际存在的实例,使用关键字new获得对象;特别说明:如果想要调用一个类中的成员属性和方法,首先需要创建该类对应的对象。用对象调用。
成员属性和成员方法
成员属性:在类中,方法以外,叫做成员属性,成员属性可以有4种访问修饰符对其进行定义。
成员方法:在类中的普通方法都成为成员方法。
全局变量和局部变量
作用范围
全局变量:可以在整个类中使用
局部变量:只能在对应的大括号中使用
存储内存
全局变量:随着对象的创建存在堆内存中
局部变量:随着方法的调用,存储在栈内存中
生命周期
全局变量:从对象的创建而存在于堆内存中,随着没有任何对象名指向之后,成为垃圾直到被gc才消失。
局部变量:随着方法的调用而存在于栈内存中,随着方法执行完毕消失。
使用修饰符
全局变量:可以使用四种访问修饰符进行定义
局部变量:不可以使用访问修饰符
冲突(特别说明)
1、在同一个类中,不能出现相同的成员变量名。
2、在同一个函数或者同一个大括号中,不能出现相同的局部变量名。
3、在同一个类中,可以存在全局变量和局部变量同名,首先使用局部变量。
函数重载
定义:在同一个类中,具有相同的函数名,有着不同的参数列表,称为方法重载。
参数列表不同:参数个数不同,参数数据类型不同,参数数据类型的顺序不同。
内存
栈内存
特点:每当方法执行完毕,则直接被垃圾处理机制给gc
堆内存
特点:随着对象的创建而开辟空间,里面都是存的成员属性,并且有默认值。
方法区
存放静态区,加载每个类的方法。
内存泄漏与内存溢出:内存泄露是由于GC无法及时或者无法识别可以回收的数据进行及时的回收,导致内存的浪费;内存溢出是由于数据所需要的内存无法得到满足,导致数据无法正常存储到内存中。内存泄露的多次表现就是会导致内存溢出。(https://blog.csdn.net/LYC1439997881/article/details/121370179)
值传递和引用传递
值传递:实际参数如果是基本数据类型,那么是准确的值
引用传递:实际参数没有具体的数值,而是存有数值的堆内存地址。
静态
关键字:static
静态包括:静态成员函数,静态成员变量,静态代码块
静态代码块:静态代码块在类加载时首先被执行,然后才是主方法。这意味着静态代码块可以在类加载之前进行一些必要的初始化工作。静态代码块只会执行一次,即使类被实例化多次,静态代码块也只会在类加载时执行一次。public class MyClass { // 静态代码块 static { System.out.println("静态代码块被执行"); // 执行其他初始化操作... } public static void main(String[] args) { System.out.println("主方法被执行"); // 调用其他方法... }}
特点:优先于对象存在,随着类的加载而加载,随着类的消失而消失
注意点:静态成员方法中不能直接调用非静态的成员函数或者成员变量。
如果是静态成员函数或者静态成员属性,多了一种调用方式,可以直接使用类名.函数名()或者类名.成员属性;
访问修饰符
public:共有的,所有的类,只要有对象,就可以调用该方法,范围最大
protected:受保护
defalut:默认的修饰符
private:私有的,范围最小,只有本类或者其内部类可以使用。
https://blog.csdn.net/u013654373/article/details/106650606
封装
将不想让外部其他类访问的属性和方法,将其私有化,能私有化就私有化。只向外提供公共访问方法。
代码体现:失血模型,将所有的属性私有化,为每一个私有化的属性添加一个set和get方法
失血模型
一个类中只有属性,并且将属性进行私有化,为每一个属性编写一个getXXX()方法和setXXX()方法。
this关键字
1、在成员变量和局部变量发生冲突的时候,用来区分,带有this.的为成员变量
2、本质为对象,在方法中,那个对象调用了该方法,那么这个this就代表这个对象。
3、所有跟static相关的都不能使用this,因为static优先于对象
构造函数
1、在创建对象的时候,就会调用构造函数,如果一个类程序员没有写构造方法,则虚拟机默认添加一个无参的构造函数,如果已经写了,则虚拟机不再分配。
2、一个类中可以存在多个构造函数,这种成为函数的重载
作用:初始化成员属性的值。
构造函数执行之前,如果有构造代码块,先执行构造代码块。
https://blog.csdn.net/weixin_67614925/article/details/124320379:使用static修饰的构造代码块我们称之为静态代码块当类加载的时候执行静态代码块,而且静态代码块仅执行一次不可多次执行。 而构造代码块是在类实例化的过程中执行的。执行优先级:静态代码块—>构造代码块—>构造方法
import lombok.Getter;import lombok.Setter;@Getter@Setterpublic class Person06 { private String name; private int age; private int gender; public Person06() { System.out.println("我是无参构造方法"); } public Person06(String name, int age, int gender) { System.out.println("我是有参构造方法"); this.name = name; this.age = age; this.gender = gender; } static{ System.out.println("我是静态代码块"); } { System.out.println("我是构造代码块"); }}//测试类调用import org.junit.jupiter.api.Test;public class Person06Test { @Test public void test(){ new Person06(); new Person06("小明",20,1); }}
运行结果:我是静态代码块我是构造代码块我是无参构造方法我是构造代码块我是有参构造方法
继承
关键字:extends
子类继承父类的非私有的属性和方法,如果一个子类继承类父类,那么子类对象可以调用到父类的非私有的属性和方法。
注意:java中都是单继承,一个子类只能有唯一的直接父类,一个父类可以同时存在多个子类。
super关键
1、在子类中,每一个构造方法的第一行都是默认的super(),用于调用父类的构造方法
2、super代表父类对象,在子类中,可以使用super.属性或者super.方法名();来调用父类的方法和属性。
重写
在子类中,有着和父类一模一样的方法,成为方法的重写。重写的前提,要么是继承关系,要么是实现关系。
抽象
关键字abstract
被abstract关键字修饰的类称为抽象类,抽象类不能够new对象,但是存在构造方法,也可以存在普通的成员属性和普通成员方法。普通的成员属性和方法只能通过子类对象进行调用。
被abstract关键字修饰的方法称为抽象方法,抽象方法不能存在主体,只能定义
如果一个子类继承的父类为抽象类,要么将自己变成抽象类,要么将父类中的所有抽象方法重写。
作用:规范子类的行为
接口
使用interface定义
接口中全部为抽象方法, 并且不能获取对象,连构造函数都没有,抽象方法不需要用abstract继续修饰。
接口中的变量:静态常量,public static final 数据类型 变量名 = 值;
接口只能被类实现,接口与接口之间是继承,实现使用关键字implements
继承只能单继承,实现可以多实现
final关键字
如果修饰变量,则为常量,最终量,只能初始化的时候赋值,不能重修修改值
如果修饰方法,则为最终方法,不能被子类重写,抽象方法不能用final修饰
如果修饰类,则为最终类,不能被继承
多态
定义:父类声明的引用指向子类的实例
转型
向上:父类声明的引用指向子类的实例
向下:用子类声明,将原本是子类对象只不过被父类声明的对象强制转换成子类
动态绑定
普通成员方法:编译看父类是否存在,运行先看右边子类
普通成员属性:编译和运行都是看左边父类
静态成员方法:编译和运行都是看左边父类
静态成员属性:编译和运行都是看左边父类
匿名对象
没有名字的对象,直接new 类名();因为没有名字,只能使用一次
1、确定只用一次对象,则可以使用匿名对象
2、最为实际参数传递给下层函数使用。
内部类
在类中再定义一个类,并且这个类属于外部类的一个成员,因此可以使用任何修饰符进行修饰
创建内部类对象:外部类.内部类 对象名 = new 外部类().new 内部类();
内部类中可以直接访问到外部类的私有成员属性
内部类中要访问到外部类的成员属性,需要使用外部类名.this.成员属性名;
执行顺序
静态代码块
构造代码块
构造方法
String类:字符串
比较字符串:==(既比较值又比较地址);equals(只比较值)
常用方法:charAt(int index)、codePointAt()、concat(String str)、contains(String str)、endsWith(String str)、eqauls(String str)、getBytes()、indexOf()、isEmpty()、length()、replace(String old,String newStr)、split(String str)、satrtWith(String str)、subString(int begin,int end)、toUpperCase()函数将字符串小写字母变成大写、toLowerCase()、trim()函数移除字符串两侧的空白字符或其他预定义字符。
正则表达式:是一个规则,用其他字符串和其比较,符合规则返回true,不符合返回false
字符缓冲区:StringBuffer和StringBuilder
String和StringBuffer及StringBuilder的区别:1、String类的内容是不可改变的。能改变的只是其内存指向。2、String对象不可修改指的是对象本身不可修改,而不是引用不可修改。3、StringBuffer类的对象内容是可以修改的。4、String可以直接通过赋值的方式实现对象实例化,而StringBuffer只能通过构造方法的方式对象实例化。5、StringBuffer在处理字符串的时候,不会生成新的对象。从内存这个角度来说,StringBuffer要比String更好。6、StringBuffer是线程安全的,速度慢。StringBuilder是线程不安全的,不能同步访问,执行速度快。 原文链接:https://blog.csdn.net/qq_57181249/article/details/124958261
https://blog.csdn.net/qq_27623455/article/details/96103541:String是不可变类,所以是线程安全的。1、所有不可变类都是线程安全的,线程安全的类不一定是不可变类,如StringBuffer是可变类,靠锁实现线程安全。2、StringBuffer方法上都加了synchronized,StringBuilder没有,StringBuilder在多线程情况下是会出现问题
随机类:Random,常用方法:nextInt(int n):返回一个0-n不包括n的随机数
包装类:Byte、Short、Integer、Long、Double、Float、Boolean、Character、String
日期类和日历类:
日期类:
Date date=new Date();
SimpleDateForMat sdf=new SimpleDateForMat("yyyy年MM月dd日 E HH:mm:ss")
String time=sdf.format(date)
日历类:
Calendar c=Calendar.getInstance()
int year=c.get(Calendar.Year)
int month=c.get(c.MONTH)
集合框架
Collection接口
List接口
ArrayList
底层数据结构为数组,因此对于元素的增加和删除效率低,修改和查询效率高,ArrayList初始化大小为10,扩容规则:扩容后的大小= 原始大小*1.5
常用方法
增加
add(Element e):在容器的末尾添加元素
add(int index,Element e):在容器的任意位置添加元素
addAll(Collection c):将c容器中的所有元素全部添加到新的集合容器中
addAll(int index,Collection c):在容器任意位置开始将c集合容器中的元素全部添加到新的集合容器中。
删除
clear():清空一个集合容器中的所有元素
remove(Object obj):删除指定元素
remove(int index):得到该下标的元素,并删除该元素。如果该下标没有元素,抛出异常
修改
set(int index,Element e):将指定索引出的元素修改为e
查询
get(int index):根据下标查找到指定元素
indexOf(Element e):查询指定元素所在的索引值
iterator():迭代器
listIterator();列表迭代器
判断或者操作
contains(Object obj):判断该集合容器中是否包含obj这个元素
isEmpty():判断该集合容器中是否存在元素
size():获取该集合容器中的实际元素个数
toArray():将集合容器转换成一个数组。
subList(int startIndex,int endStart):截取集合容器中的一部分元素,根据下标判定。得到的是新的集合容器。
LinkedList
底层数据结构为链表结构,因此对于元素的增加和删除效率高,修改和查询效率低
常用方法
新增
add(Element e):在集合容器的尾部添加元素
add(int index,Element e):在指定索引位置添加元素
addAll(Collection c):将c集合容器中的所有元素值添加到新的集合容器中
addAll(int index,Collection c):在指定索引位置开始,将c集合容器中的元素添加到新的集合容器中
addFirst(Element e):在集合容器的第一个位置添加元素。
删除
remove(Object obj):删除集合容器中的obj元素。
remove(int index):删除指定索引出的元素
clear():清空集合容器
removeFirst():删除集合容器中的第一个元素
修改
set(int index,Element e):将集合容器中指定的索引位置元素修改为e
查询
get(int index):获取指定索引出的元素值
indexOf(Object obj):查询出obj元素在集合容器中的索引值
iterator():迭代器
listIterator():列表迭代器
判断或操作
contains(Object obj):判断集合容器中是否包含obj元素
isEmpty():判断集合容器中是否为空
size();获取集合中的元素个数
toArray():将集合容器转换成数组
子主题
Vector
底层和ArrayList一致,只是比ArrayList多了线程安全(线程同步),相对于ArrayList效率低。
Set接口
HashSet
元素不可重复,没有索引,无序,底层数据结构为哈希表,实现底层为HashMap
常用方法
增加:
add(Element e):新增元素,无序,不可重复,如果元素重复,将不存入
删除:
remove(Object obj):删除集合容器中的obj元素
修改:
没有特定的修改方法
查询:
iterator():迭代器
判断或操作:
contains(Object obj):判断obj元素是否存在于集合容器中
isEmpty():判断该集合容器中是否包含元素。
size():判断集合容器中的元素个数。
toArray():将集合容器转换成一个数组。
TreeSet
底层数据结构为树,按照自然顺序进行排序,里面的元素都是有序的,升序
常用方法
新增
add(Element e):在集合容器中添加元素,不一定是尾部。
删除
clear():移除集合容器中的所有元素
remove(Object obj):删除集合容器中obj元素
修改
暂无
查询
first():获取集合容器中第一个元素,如果是数字,则是获取最小数。
last();获取集合容器中的最后一个元素,如果是数字,则是获取最大数
ceiling(Element e):获取大于等于e元素的最小值
floor(Element e):获取小于等于e元素的最大值
判断或操作
isEmpty():判断集合容器中是否有元素
size():获取集合容器中的元素
Map
用于存储键值对,将键和值一组数据封装到entry对象中,再将entry对象存入Map的实现类集合中。
HashMap:底层数据结构为哈希表,HashMap初始化大小为16,扩容因子默认为0.75(可以指定初始化大小和扩容因子的)HashMap 的容量必须是2的N次方,HashMap 会根据我们传入的容量计算一个大于等于该容量的最小的2的N次方,例如传new HashMap<>(9); 容量大小为16。扩容机制.(当前大小 和 当前容量 的比例超过了 扩容因子,就会扩容,扩容后大小为 一倍。例如:初始大小为 16 ,扩容因子 0.75 ,当容量为12的时候,比例已经是0.75 。触发扩容,扩容后的大小为 32.)原文链接:https://blog.csdn.net/qq_45720234/article/details/126580580
常用方法
增加
put(Object key,Object value);添加一组key值和value值
putAll(Map m):将map的实现类集合容器中的所有元素添加到新的集合容器中。
修改
在HashMap集合容器中没有特别的修改方法。当key的值一样的时候,会将key对应的原有的值替换。
删除
clear():清空集合容器中的所有元素
remove(Object key):根据对应的key的值,将整个键值对删除。
查询
get(Object key):根据key的值,查询出value的值
entrySet():将map集合容器中的所有封装有key值value值的entry对象映射到set集合容器中,遍历获取到每一个entry对象,再调用getKey()和getValue()的方法获取key和value。
keySet():将map集合容器中的所有key值映射成set集合,再对set集合进行遍历,可以获取到所有的key,再根据key值获取value值。
可以使用增强for循环for(Entry<K,V> i : 集合容器对象.entrySet())
判断或者操作
containsKey(Object key):判断key值是否存在
containsValue(Object value):判断value值是否存在
isEmpty():判断集合容器中是否为空
Hashtable:底层存储数据结构为哈希表,相对于HashMap中,Hashtable不能存放null作为key和value,否则报空指向异常。
常用方法
增加
put(Object key,Object value):添加一组key值和value值
putAll(Map m):将map集合容器中的所有键值对添加到新的集合容器中,必须都是map子类所创建的容器。
修改
没有特别的修改方法,但是当key的相同的时候了,直接将key对应的value值进行替换,相当于修改。
删除
clear():清除集合容器中的所有元素。
remove(Object key):根据key的值删除键值对
查询
get(Object key):根据key的值获取对应的value值。
entrySet():获取所有的entry对象
keySet():获取所有的key
elements():获取所有的value值,放到枚举(理解成迭代器)
keys():获取所有的key值,放到枚举中。
判断和操作
contains(Object value):判断传入的值是否有对应的键
containsKey(Object key):判断集合容器中是否存在key值
containsValue(Object value):判断集合容器中是否存在value
重写了toString();
TreeMap:key值按照自然顺序排列。底层数据结构为树
常用方法
新增:
put(Object key,Object value):添加一组键值对
putAll(Map m):将m容器中的所有元素添加到新的集合容器中,新的集合容器必须是map的子类对象。
修改
没有特别的修改方法,但是当key的相同的时候了,直接将key对应的value值进行替换,相当于修改。
删除
clear():清空集合容器中的所有元素
remove(Object key):根据key的值删除键值对
pollFirstEntry():删除第一个键值对,因为TreeMap是根据键的自然顺序排列,删除键为最小的键值对。
pollLastEntry():删除最后一个键值对,因为TreeMap是根据键的自然顺序排列,删除键为最大的键值对。
查询
get(Object key):根据key的值查找对应value值。
entrySet():entry对象映射到set集合中
keySet():将key的值映射到set集合容器中
firstEntry():获取第一个键值对,TreeMap根据key的自然顺序排列,获取键为最小的键值对。
lastEntry():获取最后一个键值对,TreeMap根据key的自然顺序排列,获取键为最大的键值对。
firstKey():获取第一个key的值,最小的key值
lastKey():获取最后一个key的值,最大的key值
判断或操作
containsKey():判断key值是否存在
containsValue():判断value值是否存在
isEmpty():判断是否为空
size():获取元素个数
问题,处理异常类,自定义异常类
问题:当程序运行期间,出现了问题,导致代码终止运行
异常处理:捕获:try{}catch(异常类 变量名){}final{};抛:在方法的下括号后面加上throws异常类
java哪些异常不用捕捉_Java异常基础知识解析:https://blog.csdn.net/weixin_27127993/article/details/114922773
内存泄露是jvm回收不成功,内存泄漏是申请空间不足,内存泄漏多了会导致内存溢出
文件File类,IO流(按流向可分输入和输出流,按数据类型可分字节和字符流)
File file=new File("文件路径/文件名.后缀名")
IO流:四大基类:字符输入流(Reader)、字符输出流(Writer)、字节输入流(InputStream)、字节输出流(OutputStream)
字符流:字符输出流:FileWriter;字符输入流:FileReader
字符缓冲流:字符缓冲流输出流BufferedWriter、字符缓冲输入流BufferedReader
字节流:字节输出流:FileOutputStream;字节输入流:FileInputStream
字节缓冲流:字节缓冲输出流:BufferedOutputStream、字节缓冲输入流:BufferedInputStrea
对象流:对象输出流:ObjectOutputStream;对象输入流:ObjectInputStream
进程和线程(线程生命周期、常用方法、线程安全加锁、单例模式、分层分包、键盘监听、线程池)
线程的创建共有四种方式分别有:继承于Thread类,实现Runnable接口,实现Callable接口,使用线程池
线程生命周期:新建、就绪、运行、阻塞、死亡
线程常用方法:currentThread()、getName()、setName()、sleep(long millis)、yield():主动出让cpu的执行权,cpu可能拒绝、getPriority():获取线程优先级,范围1-10,越大优先级越高、setPriority(:设置线程优先级)、setDaemon(boolean on):将该线程标记为守护线程或用户线程(守护线程是后台提供通用服务的线程,当其他线程全部死亡时,该线程自动死亡)、join():让两个线程交替执行
wait和sleep区别:sleep是线程中的方法,但是wait是Object中的方法;sleep方法不会释放lock,但是wait会释放,而且会加入到等待队列中;sleep方法不依赖于同步器synchronized,但是wait需要依赖synchronized关键字;sleep不需要被唤醒(休眠之后退出阻塞),但是wait需要(不指定时间需要被别人中断)。
线程安全加锁:https://blog.csdn.net/2301_78008478/article/details/131406914
对象(万物皆是对象,包括类和实例对象)锁/内部锁,通过synchronized,具备重入机制,引用计数管理,并发有缺陷,有5种状态:无锁、偏向锁、轻量级锁、重量级锁、GC标志(https://blog.csdn.net/u012485698/article/details/105850749/),可修饰代码块、方法、静态方法、BaseClass的class对象,修饰静态方法的叫类锁,修饰其它3种的叫对象锁
public class BaseClass { private static Object lock = new Object(); public void do() { synchronized (lock) { } } public synchronized void doVoid() { } public synchronized static void doStaticVoid() { } public static void doStaticVoid() { synchronized (BaseClass.class) { } } }
显示锁,由java.util.concurrent.locks.Lock派生,解决并发
ReentrantLock
ReadWriteLock
ReentrantReadWriteLock
死锁:https://blog.csdn.net/qq_41126178/article/details/124190649
产生4条件:互斥条件、不可剥夺条件、请求与保持条件、循环等待条件
处理方法:预防(破坏4个条件(实行资源预先分配策略,一次性分配一个进程所需要的资源,不能完全分配资源就释放占有的部分资源后等待;实行顺序资源分配法,只要进程提出申请分配资源Ri,则该进程在以后的资源申请中,只能申请编号大于Ri的资源))、避免(银行家算法)、检测(环路)、解除(进程回退、挂起或强制撤销进程并抢占或剥夺其资源)
线程同步的主要方法:https://blog.csdn.net/yuqing2015/article/details/82788041
synchronized关键字修饰方法或者同步代码块
package com.xhj.thread; /** * 线程同步的运用 * * @author XIEHEJUN * */ public class SynchronizedThread { class Bank { private int account = 100; public int getAccount() { return account; } /** * 用同步方法实现 * * @param money */ public synchronized void save(int money) { account += money; } /** * 用同步代码块实现 * * @param money */ public void save1(int money) { synchronized (this) { account += money; } } } class NewThread implements Runnable { private Bank bank; public NewThread(Bank bank) { this.bank = bank; } @Override public void run() { for (int i = 0; i < 10; i++) { // bank.save1(10); bank.save(10); System.out.println(i + "账户余额为:" + bank.getAccount()); } } } /** * 建立线程,调用内部类 */ public void useThread() { Bank bank = new Bank(); NewThread new_thread = new NewThread(bank); System.out.println("线程1"); Thread thread1 = new Thread(new_thread); thread1.start(); System.out.println("线程2"); Thread thread2 = new Thread(new_thread); thread2.start(); } public static void main(String[] args) { SynchronizedThread st = new SynchronizedThread(); st.useThread(); } }
使用特殊域变量(volatile)实现线程同步
//只给出要修改的代码,其余代码与上同 class Bank { //需要同步的变量加上volatile private volatile int account = 100; public int getAccount() { return account; } //这里不再需要synchronized public void save(int money) { account += money; } }
使用重入锁实现线程同步
//只给出要修改的代码,其余代码与上同 class Bank { private int account = 100; //需要声明这个锁 private Lock lock = new ReentrantLock(); public int getAccount() { return account; } //这里不再需要synchronized public void save(int money) { lock.lock(); try{ account += money; }finally{ lock.unlock(); } } }
线程池
创建方法:总共有 7 种,但总体来说可分为 2 类:通过 ThreadPoolExecutor 创建的线程池;通过 Executors 创建的线程池。(其中 6 种是通过 Executors 创建的,1 种是通过ThreadPoolExecutor 创建的)https://blog.csdn.net/qq_41821963/article/details/125341789
1. Executors.newFixedThreadPool:创建⼀个固定⼤⼩的线程池,可控制并发的线程数,超出的线程会在队列中等待;2. Executors.newCachedThreadPool:创建⼀个可缓存的线程池,若线程数超过处理所需,缓存⼀段时间后会回收,若线程数不够,则新建线程;3. Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执⾏顺序;4. Executors.newScheduledThreadPool:创建⼀个可以执⾏延迟任务的线程池;5. Executors.newSingleThreadScheduledExecutor:创建⼀个单线程的可以执⾏延迟任务的线程池;6. Executors.newWorkStealingPool:创建⼀个抢占式执⾏的线程池(任务执⾏顺序不确定)【JDK1.8 添加】。7. ThreadPoolExecutor:最原始的创建线程池的⽅式,它包含了 7 个参数可供设置,后⾯会详细讲。
实现原理:当提交一个新任务到线程池时,线程池的处理流程如下。 1)线程池判断核心线程池里的线程是否都在执行任务。如果不是,则创建一个新的工作线程来执行任务。如果核心线程池里的线程都在执行任务,则进入下个流程。 2)线程池判断工作队列是否已经满。如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程。 3)线程池判断线程池的线程是否都处于工作状态。如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务。如右图:原文链接:https://blog.csdn.net/2302_79149131/article/details/131767775
7大核心属性
线程池默认大小:CPU密集型:CPU核心线程数 + 1IO密集型:CPU核心线程数 * (1 + IO耗时 / CPU耗时)
单例模式:私有化构造方法;自己new自己;提供公共的访问接口
饿汉式:
class Banks{ //1.私有化类的构造器 private Banks() { } //2.内部创建类的对象 //4.要求此对象也必须声明为静态的 private static Banks instance = new Banks(); //3.提供公共的静态的方法,返回类的对象 public static Banks getInstance() { return instance; }}
懒汉式(线程不安全的):
class Order{ //1.私有化类的构造器 private Order() { } //2.声明当前类对象,没有初始化 private static Order instance = null; //3.声明public、static的返回当前类对象的方法 public static Order getInstance() { if (instance == null) { instance = new Order(); } return instance; }}
优化:使得懒汉式安全class Bank{ private Bank() { } private static Bank instance = null; public static Bank getInstance() { //方式一:效率稍差// synchronized (Bank.class) {// if (instance == null) {// instance = new Bank();// }// return instance;// } //方式二:效率更高 if (instance == null) { synchronized (Bank.class) { if (instance == null) { instance = new Bank(); } } } return instance; }}
数据库
按照类型分类:关系型数据库:DB2 Oracle mysql access sql-server非关系型数据库:采用键值对方式
完整查询语句语法:Select */字段列表 from 数据源 [where子句][group by子句][having子句][order by子句][limit子句]
where与having区别:where后面不能使用聚合函数(https://blog.csdn.net/weixin_43231592/article/details/107271295,https://blog.csdn.net/Hudas/article/details/124120288)
多表数据源
连接查询分类:from 表A [连接类型] join 表B [on 连接条件]https://blog.csdn.net/weixin_41692221/article/details/130764737
交叉连接(会产生笛卡尔积重复无效数据不常用):它不使用任何匹配或者选取条件,而是直接将一个数据源中的每个行与另一个数据源的每个行 一 一 匹配
内连接(可用where等值连接替换):表 A [inner] join 表B on 连接条件
外连接
左外连接(除了查出所有符合连接的结果外,如果左表中有多余数据,会让其显示与null结合):表A left [outter] join 表B on 连接条件
右外连接(除了查出所有符合连接的结果外,如果右表中有多余数据,会让其显示与null结合):表A right [outter] join 表B on 连接条件
Union:联合:将两条语句的查询结果合并成一个表,两个表的字段数一样(查询语句1 Unnion 查询语句2),与union all区别是去重,union all不去重
外键:foreign key
外面的键,一张表中的某一个字段是另一张表的主键,这个字段叫做外键;拥有外键的表叫做子表,拥有主键的表叫做父表
视图:view,将查询出来的结果当成一张新的表,虚拟表(只存在结构,不存在数据),这个表叫做视图
create view 视图名称 as select语句
触发器:trigger,事先为某张表绑定一段执行代码,当达到某种条件时自动触发这段代码,触发器,有两种常见类型的触发器:BEFORE触发器和AFTER触发器,可以与数据库的不同事件关联,如INSERT、UPDATE、DELETE等
delimiter $create trigger 触发器名称before|after insert|update|deleteon 表名for each rowbegin触发器要执行的功能;end$delimiter ;
事务:将要发生的一系列操作
事务作用:用来保证数据的完整性(事务里面的操作应该看成一个整体,要么都成功,要么都失败)
事务的操作原理:用户所有的实务操作会先保存到实物日志中,当用户确认所有操作执行成功后,将事务日志中的数据持久化的更新到数据库中,同时清空事务日志
事务操作步骤:开启事务;执行事务的操作;提交事务
事务的四大特性:ACID
原子性,atomic;一致性,consistency:事务操作前后,保证数据一致性;隔离性,isolation;持久性:durability
隔离四个级别:读未提交、读已提交、可重复度、串行化读
事务失效8大场景:https://download.csdn.net/blog/column/12302624/131502536
未指定回滚异常;异常被捕获;方法内部直接调用(调用有事务注解的);异步多线程;使用了错误的事务传播机制;方法被private或者final修饰 ;当前类没有被Spring容器托管;数据库不支持事务
函数
调用函数:select 函数名(参数)
系统函数:avg()、sum()、count()、max()、min()、求字符串的长度:length()、字节数:char_Length()、group_concat()、substring_index()、row_count():判断Update或Delete影响的行数、found_rows():判断Select得到的行数用found_rows()函数,使用sql_calc_found_rows 与 found_rows()组合,可以查询到去除limit限制后返回的总行数(https://www.cnblogs.com/JennyYu/p/16888090.html)
自定义函数:sql中,所有函数必须有返回值
Create function 函数名(参数列表)returns 返回值类型Begin 函数体End
参数列表:变量名 数据类型,变量名 数据类型...... 函数体结束之后必须要有return值;
过程(procedure):没有返回值的函数,过程里面可以编写sql语句
创建过程: Create prcedure 过程名(参数列表) Begin 过裎体; End
调用过程: Call 过程名(实参);
参数列表:除学过的3种类型(数值型、字符串、时间日期类型)外,还额外定义了3中类型:In、Out、Inout
游标(cursor):Sql处理数据的一种方式,我们将查询的数据一条一条的取出,进行逻辑处理;类似于Java中的迭代器;
使用:
1、声明游标:一般在过程中声明。Declare 游标名 cursor for select 语句;(select需要写出具体字段)
2、打开游标:open 游标名
3、一条一条的取数据:fetch 游标名 into 变量1,变量2......;
4、关闭游标:close 游标名
索引
普通索引可重复,唯一索引和主键一样不能重复,只是主索引与唯一索引的唯一区别是:前者在定义时使用的关键字是PRIMARY而不是UNIQUE
复合索引:字段为a,b,c三个字段组成的复合索引,按照索引的最左匹配原则,该复合索引可以看成包含a;a,b;abc;三个索引。
EXPLAIN SELECT * FROM test WHERE a = 1 AND b = 1 AND c = 1;只要用到了最左侧a列,和顺序无关 都会使用 索引。https://blog.csdn.net/qq_43012298/article/details/135852142
a = 1 AND b = 2 AND c = 3 ; 使用索引c = 1 AND b = 2 AND a = 3 ; 使用索引a = 1 AND b = 2 ; 使用索引a = 1 AND c = 3 ; 使用索引c = 1 AND a = 2 ; 使用索引不包含最左侧的 a 的不使用索引c = 3 ; 未使用索引b = 2 ; 未使用索引b = 2 AND c = 3 ; 未使用索引c = 1 AND b = 2 ; 未使用索引OR 不使用索引a = 1 AND b = 2 OR c = 3 未使用索引a = 1 OR b = 2 AND c = 3 未使用索引a = 1 OR b = 2 OR c = 3 未使用索引最左侧的‘a’列 被大于,小于,不等于比较的 ,不一定使用索引.看比较后结果集是否足够小.———————————————— 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.csdn.net/qq_43012298/article/details/135852142
聚簇索引就是按照每张表的主键构造一颗B+树,同时叶子节点中存放的就是整张表的行记录数据,也将聚集索引的叶子节点称为数据页。这个特性决定了索引组织表中数据也是索引的一部分,每张表只能拥有一个聚簇索引。Innodb通过主键聚集数据,如果没有定义主键,innodb会选择非空的唯一索引代替。如果没有这样的索引,innodb会隐式的定义一个主键来作为聚簇索引。优点:.数据访问更快;缺点:插入速度严重依赖于插入顺序。https://blog.csdn.net/xixibuchi/article/details/123673414
聚集索引和唯一索引聚集索引就是主键索引,查询的时候遵顼聚集索引的查询方式。聚集索引是唯一索引,只不过唯一索引的值可以为null,可以在创建索引的时候增加 UNIQUE 选项代表创建唯一索引。聚集索引一定是唯一索引,唯一索引不一定是聚簇索引。聚集索引不可以为null,但是唯一索引可以为null,但是只允许有1个。主键只能有一个,但是唯一索引可以有多个。原文链接:https://blog.csdn.net/weixin_42798851/article/details/115356354
索引失效场景:
1.使用OR操作符:当WHERE语句中使用OR操作符并且OR两边的条件涉及到至少两个字段时,MySQL无法使用索引,会转向全表扫描。2.隐式类型转换:当查询条件中对索引列进行了隐式类型转换时,数据库可能无法利用索引,导致索引失效。例如,如果索引是在整数类型的字段上建立的,但是查询时传入的是字符串类型的值,那么索引可能会失效。3.对索引列使用函数或表达式:在查询条件中对索引列使用函数或进行表达式计算,会导致索引失效。因为数据库无法直接利用已经计算好的索引值,而需要重新计算表达式的值。4.复合索引未正确使用:如果使用了复合索引,但查询时未使用索引的第一列,索引也会失效。复合索引的排序规则决定了其使用方式,未按照规则使用则无法利用索引。5.LIKE查询使用不当:当使用LIKE查询时,如果通配符(如%)出现在查询字符串的开头,索引将无法生效。6.查询条件包含不等于操作符:在查询条件中使用不等于操作符(!=或<>),索引通常无法被利用,因为数据库需要扫描全表来找到所有不等于给定值的记录。7.索引设计不合理:例如,数据量级太小、更新频繁的字段建立索引、区分度低的字段建立索引等,都可能导致索引失效或降低查询效率。8.查询优化器选择:有时即使存在可用的索引,查询优化器也可能基于统计信息和其他因素选择不使用索引,而是进行全表扫描,特别是当优化器认为全表扫描比使用索引更快时。
第二阶段Web前端基础
html:img、ol、ul、audio、video、b、i、u、datalist、radio、fieldset、legend标签;get请求数据直接出现在地址栏,只能发送字符串,post请求数据不会出现在地址栏,任何类型都可以;
css:选择器(标签、id、类、多类名、伪类、连接伪类、组合、通配符、结构伪类)、继承与层叠特性(继承过来的样式<标签选择器<类选择器/伪类选择器<id选择器<行内样式)、div(边距、浮动、边界合并、边界塌陷、定位于偏移)、BootStrap
Js:ES(输入与输出、变量、数据类型、运算符、分支)、DOM、BOM;
数组为变长数字组,可以任何数据类型,增强for循环循环出来为下标;常用方法有push()、join()、reverse()、shift()、unshift()、splice(参数1,参数2)、pop();函数和变量的与解析/提升(提升声明,提升赋值/调用,先提升变量,后提升函数);js对象
js获取对象方式:id、className、标签;点击事件:单击、双击、鼠标移入移出、获取/失去焦点、内容改变、文档加载;创建元素:document.createElement(),将元素添加到页面对应位置:document.body.appendChild();定时器:setInterval(函数,时间毫秒值):每隔多少秒重复执行、setTimeOut(函数,时间毫秒值):延迟多少秒执行函数一次,清除定时器:clearInterval(定时器名)、clearTimeOut(定时器名);innerHTML、innerText;json数组;getAttribute("属性名")、setAttribute("属性名","属性值")、removeAttribute("属性名")
Jquery-->js:根据下标取出元素,js-->Jquery:将js对象放入大$(js对象);attr("属性名")、attr("属性名","属性值");.html("修改的内容"),.text("修改的内容");val()、val("修改的内容");prop("属性名")、prop("属性名",false/true);addClass("类名")、remove("类名")、hasClass("类名")、toggleClass("类名");eq(元素下标)、siblings()、nextAll()、next()、prev()、prevAll()、parent()、children();hover(函数1)、hover(函数1,函数2);remove():自杀、empty():清空元素内容;append():末尾添加元素、html():修改元素里面的内容;事件委托:on(参数1,参数2,参数3):参数1为事件类型、参数2为元素、参数3为事件内容;动画:animate(参数1,参数2,参数3):参数1为json格式的样式、参数2为事件毫秒值、参数3为回调函数;轮播图
网络编程:TCP/IP协议、UDP协议、http协议、网络结构模式、JDBC连接(Statement、PreparedStatement)、Dao模式、数据库连接池、Tomcat与Servlet(体系、生命周期、常用对象);页面跳转;JSP(JSP分页);过滤器;监听器;;Servlet上传;Aajax;反射;泛型;注解;Xml解析;自定义框架ORM和Mvc框架;项目的生命周期;版本控制工具;支付宝沙箱环境;操作系统;linux;服务器集群的搭建;负载均衡;
TCP协议:基于连接,安全性高,(数据不易丢失,)效率低,应用在安全要求高,效率要求不高的场景,比如:文件上传
UDP传输协议:不基于连接,安全性相对较低,效率高,应用在实时性要求较高的场景,比如:视频聊天,视频通话,在线看视频,QQ聊天(网络不好容易卡顿丢失数据)
http协议:超文本传输协议,应用层的协议,只是定义了传输数据的格式,并没有取传输数据,传输数据是由他的下层协议TCP取传输的。http协议是无状态的协议(无标识,访问了服务器一次后面访问也不认识)。
http协议的工作模式是请求/响应模式,一个请求对应一个响应,请求响应成对
session和cookie的区别:1、数据存放位置不同:cookie数据存放在客户的浏览器上,session数据放在服务器上。2、安全程度不同:cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗,考虑到安全应当使用session。3、性能使用程度不同:session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,考虑到减轻服务器性能方面,应当使用cookie。4、数据存储大小不同:单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie,而session则存储与服务端,浏览器对其没有限制。
HTTPS 是带有加密的 HTTP。两种协议的区别在于 HTTPS 使用 TLS(SSL)来加密正常的 HTTP 请求和响应。因此,HTTPS 比 HTTP 安全得多。使用 HTTP 的网站在其 URL 中有HTTP:// ,而使用 HTTPS 的网站有HTTPS:// 。
网络结构模式:BS模式:浏览器(browser)服务器(server)模式;CS模式:客户端(client)服务器(server)模式
JDBC:Java database connection, Java数据库连接,jdbc api由Java api提供,就是Java程序可以通过调用jdbc api所提供的接口、方法来连接、操作、查询数据库
数据库驱动:就是由数据库提供商提供的数据库驱动程序
附:架包是编译后字节码文件
步骤:注册驱动;创建连接;创建statement,执行sql语句;处理结果集;释放资源
Statement:拼接SQL后,无法防止SQL注入,传入的参数拼接到SQL中后,让拼入的字符串成为了SQL的一部分
PreparedStatement:是Statement接口的子接口,对于Statement的扩展,他可以防止SQL注入,因为他在传入参数之前对SQL进行了预编译,所以在传入的参数只能当成字面量的数据来使用,而不能当成SQL语句来
总结:当传递的是普通参数时,需要使用 #{} 的方式,而当传递的是 SQL 命令或 SQL 关键字时如按某个字段分组或者传递升序降序关键字,需要使用 ${} 来对 SQL 中的参数进行直接替换并执行。
Dao模式:把对象的属性和数据库表里面的字段联系在一起,就是优化jdbc
软件结构:三层架构,对应一个包,包括控制层、业务逻辑层、数据访问层控制层(controller/servlet):负责处理请求、页面跳转业务逻辑层:负责处理数据访问层返回的数据数据访问层:负责访问数据库,获取数据总结:代码的分工明确,维护性好,灵活性高,但项目结构复杂
数据库连接池:数据库的连接是稀缺资源,数据库连接的创建跟销毁比较消耗系统资源,造成系统性能下降。采用数据库连接池技术,在数据库连接池中初始化一些数据库连接对象,不配置默认0个,那么当需要使用连接的时候就直接从连接池中获取连接,用完之后再将连接返回给连接池管理即可。
C3p0数据库连接池,dbcp数据库连接池
Druid:德鲁伊数据库连接池,Druid为监控而生的数据库连接池,它是阿里巴巴开源平台上的一个项目。Druid是Java语言中最好的数据库连接池,Druid能够提供强大的监控和扩展功能.它可以替换DBCP和C3P0连接池。Druid提供了一个高效、功能强大、可扩展性好的数据库连接池。当使用连接池之后,代码里也要关闭连接,这个关闭只是将连接对象归还给连接池。
常用参数DriverClassName:驱动类的权限类名jdbcUrl:数据库URLusername:数据库用户名password:数据库的密码initialSize:数据库连接池初始的连接数量,默认0个maxActive:数据库连接池的最大的连接数量minldle:最小的空闲的连接数(重要性不大)maxWait:排队的连接申请等待的最长时间,过期时间
Tomcat与Servlet(体系、生命周期、常用对象):
Tomcat:Javaweb服务器,tomcat,开源免费,Java开发,它可以部署静态资源,也可以部署动态资源(JSP/servlet),处理静态资源的请求效率较低。Apache组织,提供了很多开源免费的java类库、中间件、插件等。Tomcat就是Apache组织提供的。处理静态资源效率高的有Apache、nginx,特别是nginx(俄罗斯程序员提供的,不是Apache提供的)
Servlet:是运行在服务器中,用于处理请求相应的java程序/类
体系:Servlet是一个接口,所有直接或间接实现了该接口的类都是servlet
总结:GenericServlet实现了Servlet的顶层接口Servlet,在该抽象类中实现了init(ServletConfig config)方法和destroy方法,并且提供了一个供开发者覆盖的init方法,并没有实现service方法,HttpServlet抽象类继承了GernricServlet,在该类中,就实现了service()方法,并且定义了一些处理请求方法,比如doGet、doPost、doDelete、doPut方法,然后在service方法中判断请求提交的方式,如果是get请求就调用doGet,如果是post请求就调用doPost方法,其他类似,所以开发者在自定义servlet的时候,就可以继承HttpServlet类,然后重写它的doGet或doPost方法即可
生命周期:Servlet的生命周期:1、当服务器启动后,浏览器第一次访问servlet的时候,servlet被实例化2、实例化之后,马上调用init方法对servlet进行初始化3、初始化之后调用servlce方法处理请求、返回响应,多次访问都是由一个servlet对象来处理4、当服务器卸载该servlet的时候,调用destroy方法销毁servlet对象
Servlet api提供的几个常用的对象:HttpServletRquest、HttpSession、ServletContext:上下文,都有setAttribute()跟getAttribute()
页面的跳转方式
1、请求转发:一个请求发送到servlet或JSP的时候,该servlet或JSP就把该请求转发给服务器内部的另一个servlet或JSP去处理,处理完成之后又后边的servlet或JSP响应。发送了一个请求,请求在服务器内部被转发,客户端不知道服务器内部的事情,浏览器的URL不会发生变化,无法转发到外部资源。不可以防止数据的重复提交。
2、重定向:一个请求发送到servlet或JSP的时候,该servlet或JSP就告诉客户端一个目标资源,让客户端去访问指定的目标资源,处理客户端的请求,所以,客户端就又重新向指定的另一个servlet或JSP发送了一个请求。发送了两个请求,客户端行为,客户端就知道重定向的事情,所以URL会发生改变,保存在第一个请求作用域中的数据就丢失了,可以跳转到外部资源。可以防止数据的重复提交。
JSP:Java server page,运行在服务器上的页面,动态页面,动态的渲染数据在网页中,JSP有跟servlet一样的功能,JSP本质是html跟servlet的合体,通过Java代码实现。包括:小脚本(Java代码),HTML,jstl标签库,el表达式,动作标签,指令标签
指令标签:page,默认生成,定义页面的全局属性,taglib,引用外部的标签库,Include,包含其他页面
EL表达式:在JSP页面中获取数据,${键}格式获取值
Jstl标签库:JSP标准标签库,提供了一些实用的JSP标签,用于替换Java脚本,这样使得JSP中没有java代码,更适用前端工作
1、forEach标签:用来遍历元素,items属性表示遍历的目标,var给遍历出来的元素取的名字2、if标签:用来进行选择判断,test属性表示判断的条件。如果为true,就在页面上输出标签中间的内容,如果为false就不在页面输出标签中间的内容3、choose、when、otherwise标签:用来进行多重选择的,功能相当于Java中if-else-if4、redirect标签:用来进行重定向,URL属性定义的是重定向的路
<jsp:forward>是动作标签
总结:JSP的内置对象:request,response,session,application(上下文),page(代表当前页面对象),pageContext(页面作用域对象),out(在页面中输出信息的对象),exception(异常对象),cogfig(配置对象);
servlet中也可以使用out,但不能直接使用,需要获取,效果和jsp中一样
Jsp相当于Servlet嵌入到html中,Java server page1、将java脚本从JSP中分离出来2、把分离出来的代码进行组装,把分离出来的代码组装成一个完整的servlet3、编译servlet4、执行该servlet,处理请求返回响应
JSP分页:当展示的数据比较多的时候,电脑一屏不能显示所有的数据,这个时候为了查看方便,我们可以将所有的数据分成若干页,数据一批一批的展示
1、查询所有的数据出来,分批显示。(现在很少使用,占用内存影响效率)
2、分批查询,分批显示(广泛应用)
过滤器:运行在服务器端,目标资源之前,用来拦截请求或响应,对请求或响应做出一定的处理后再放行的Java类或web主键,多个这种Java类组成的链条叫做过滤器链
放行方法:chain.doFilter(request,response)
监听器:运行在服务器端,用来监听请求、上下文、会话对象的生命周期或属性的变化的web组件,本质上就是一个Java类
请求:监听请求对象生命周期,监听请求属性变化
生命周期:ServletRequestListener接口
属性:ServletRequestAttributeListener接口
会话:监听会话对象生命周期,监听会话属性变化,感知型的监听器:监听当前对象在会话上的绑定跟解绑,监听当前对象所绑定的会话的钝化跟活化
生命周期:HttpSessionListener接口
属性:HttpSessionAtrribute
实现了HttpSessionBindingListener接口类的对象,能够感知自己被绑定到会话上跟从会话上移除的操作。
实现类HttpSessionAactivationListener‘接口的类的对象,能够感知绑定自己的会话的钝化跟活化操作。
上下文:监听上下文对象生命周期,监听上下文对象属性变化
生命周期:ServletContextListener接口
属性:ServletContextAttributeListener接口
Servlet上传:本质上就是复制粘贴,实际上就是io操作。
1、定义一个表单,选择要上传的文件,定义数据提交的格式
2、在Servlet中接收数据,通过第三方类库操作数据,实现上传,类库:commons-fileupload.jar commons-io.jar
a、判断表单是以什么格式提交的b、获取上传的路径,目标路径c、解析请求,得到一个集合(FileItem泛型,第三方的类库中提供的一个接口,一个FileItem类型的对象表示一个表单域即input标签)d、遍历集合,判断每一个FileItem是不是普通的表单域,如果是就按照普通表单域处理,否者按照文件域操作e、如果是文件域,获取源文件名(包括路径),创建一个file对象,调用方法获取文件名,然后把文件名跟服务端的路径拼接就是一个完整的服务端文件路径f、调用类库(FileItem)提供的write方法直接完成上传操作类库:ServletFileUpLoad:提供几个方法操作上传 FileItem:封装表单域 FileItenmFactory:ServletFileUpLoad类的依赖的工厂类
Aajax:异步刷新技术,发送异步请求,http请求实际上是同步请求(前一个请求必须响应后一个才能响应),Aajax改善用户体验(前一个请求不响应不影响后一个响应,不会堵住),不是新技术,是Javascript提供的(09年就出现)比http多了XMLHttpRequest对象即Aajax引擎处理
原生的Ajax,不怎用
jQuery Ajax:经常用,封装的了原生的Ajax,更优化
$.ajax方法参数:url:定义ajax请求提交的路径type:定义求情提交的方式data:定义请求提交的参数或数据dataType:定义服务端响应回来的数据的格式success:定义请求正常处理并正常响应时调用的方法error:定义请求发生错误的时候调用的方法complete:定义请求完成之后调用的方法,无论是否发生错误
再优化AJAXget(url,data,回调函数,dataType);post(url,data,回调函数,dataType);getJSON(url,data,回调函数);load(url,data,回调函数):jQuery对象调用,响应回来的数据直接加载到该jQuery对象中,data,回调函数可以省略,返回的响应数据为htmlAjax传递数据的格式:text(默认),JSON(常用),xml(少用),html前面两个常用
展示对象用JSON类型的响应数据或者getJSON()方法,用JSON时ajax直接把后端的json数组字符串在前端响应变成JSON数组
serialize():form表单对象调用此方法,可以直接获取到所有域中的键值
附件(自己网上搜的):传给web前端的json数据有时需要按照一定的格式输出时间字段,虽然这个可以在js部分完成,但是js完成时间格式化的缺点在于new Date时会受客户端系统时间影响,因此不推荐使用。在服务端输出json数据时按照一定的格式输出时间字段,fastjson支持两种方式:1.使用JSON.toJSONStringWithDateFormat方法2.JSON.toJSONString方法增加SerializerFeature.WriteDateUseDateFormat参数第一种方法的缺点在于:如果在反序列化时没有调用JSON.DEFFAULT_DATE_FORMAT = "yyyy-MM-dd HH:mm"; 之类设置时间格式,反序列化就会失败!
反射:是在Java代码编译或运行期间对程序进行获取或操作(对字节码进行修改)反射api是由Javaapi提供的通过反射可以从字节码中获取属性、构造器、方法、注解、实现的接口、加载器等类的组件在反射中用Class类表示字节码文件Class,Field,Method,Parameter,Constractor
//通过类权限定名获取字节码文件Class clazz1=Class.forName("com.woniuxy.entity.Person");//通过类名.class方法获取字节码文件Class clazz2=Person.class;//通过类的对象.getClass()获取字节码文件Class clazz3=new Person().getClass();//通过字节码文件获取属性,getFields()获取所有的公共的属性Field[] fields=clazz1.getFields();for(Field field:fields) { System.out.println(field);}System.out.println("------------------------");//获取所有声明的属性,getDeclaredFileds()获取所有声明的属性Field[] fields2=clazz2.getDeclaredFields();for(Field field:fields2) { System.out.println(field);}System.out.println("------------------------");//获取指定的属性,getDeclaredField(属性名)Field field=clazz3.getDeclaredField("name");System.out.println(field);
Class clazz1=Class.forName("com.woniuxy.entity.Person");//获取所有的公共的方法(包含父类中的公共方法)Method[] methods=clazz1.getMethods();for(Method method:methods) { System.out.println(method);}System.out.println("------------");//getDeclaredMethods()获取所有的声明的方法(只有本类的)Method[] methods2=clazz1.getDeclaredMethods();for(Method method:methods2) { System.out.println(method);}//获取指定的方法getDeclaredMethod(指定方法名)Method method=clazz1.getDeclaredMethod("show",String.class, int.class);//获取类的对象Object obj=clazz1.newInstance();//调用show方法Object result=method.invoke(obj,"张三丰",100);System.out.println("result:"+result);
Class clazz1=Class.forName("com.woniuxy.entity.Person");Field field=clazz1.getDeclaredField("name");//System.out.println(field);Class type=field.getType();//返回字段访问修饰符类型int num=field.getModifiers();String name=field.getName();System.out.println(type);System.out.println(num);System.out.println(name);System.out.println("----------------");Method method=clazz1.getDeclaredMethod("show", String.class,int.class);////返回方法访问修饰符类型int mnum=method.getModifiers();System.out.println(mnum);Class returnType=method.getReturnType();System.out.println(returnType);String mname=method.getName();System.out.println(mname);Parameter[] params=method.getParameters();for(Parameter parameter:params) { System.out.println(parameter); System.out.println(parameter.getName()+":"+parameter.getType());}
eclaipse1.91后不能直接用字节码获取对象要用构造器获取对象
泛型:数据类型的参数化(例如:List<E>-----List<String>-----add(String)),在定义泛型接口、泛型类、泛型方法的时候,我们在类或接口或泛型方法中传入具体的数据类型,在代码编译的时候,泛型能规避掉一些数据类型转化的异常。编译后,泛型就会被擦除,所以在代码运行的时候,是没有泛型。
泛型接口(用在接口那里),泛型类:就是在声明接口或类的时候在类名或接口名之后添加<泛型形参:可以用任意一个大写字母表示,然后可以写多个,多个之间用逗号隔开>,在该类或接口中的任意位置,包括成员变量、局部变量、成员方法的返回值、成员方法的参数等都可以使用声明的类型,在实例化该接口或类的时候,传入泛型实参(具体的数据类型),那么改接口或类中所有用泛型代表的数据类型都变成了传入的类型
泛型方法:(一个方法的返回值类型不确定,那么在类上面声明泛型,有点局部问题扩大化)如果一个类或接口中只有极少数的方法是返回值类型或参数类型不确定,这个时候在类或接口上声明泛型不够灵活,我们可以在该方法上声明泛型,这个泛型只在该方法中有效,这种方法就是泛型方法
上边界:extends(可以获取,不可以插入);下边界super(可以插入,不可以获取)https://blog.csdn.net/weixin_43935927/article/details/109001615
上边界:当泛型T给定形如 的A类型到A类型任何子类的限制域,可以匹配任何在此限制域中的类型,此种表示叫上边界通配符。? extends Fruit。 上界<? extends T>不能往里存,只能往外取,因为编译器只知道传入的是T的子类,但具体是哪一个编译器不知道,他只标注了一个占位符,当?传过来时,他不知道这能不能和占位符匹配,所以set不了。
下边界:当泛型T给定形如 的A类型到A类型任何父类的限制域,可以匹配任何在此限制域中的类型,此种表示叫下边界通配符。? super Apple。 下界<? super T>不影响往里存,但往外取只能放在Object对象里,因为下边界已经限制了?的粒度,他只可能是T本身或者是T的父类。我们想想,我想要一个T,你却返回给我一个比T小的Object,这样我们就因为精度损失而拿不到想要的数据了。
注解:本身就是一个标注在类、方法、成员变量、属性、参数等上边的标记(本身不具备功能,扫描类才具备功能)
1、Java api内置的注解(警告,提示,没功能):就是jdk自带的注解比如:@Override,重写的标记,起到提示、警告等作用
2、元注解:就是标注在注解上的注解@Documented:标注了此注解的自定义注解的相关信息会被加入到Javadoc文档中@Ingerited:标注了这个注解的自定义注解可以被所标注的类的子类所继承@Target:定义被这个元注解标注的自定义注解的使用的位置@Retention:定义了被标注的自定义注解的生命周期(自定义注解在Java代码的那个阶段起总用
3、自定义注解:开发者自定义的注解创建一个Java类,然后把class关键字改为@interface,那么这个类就变成了一个自定义注解,然后在通过标注元注解定义 他的一些特征,如果需要属性就在{}中间定义属性,属性的语法格式:访问修饰符 数据类型 属性名() [defult 值]注解属性支持的数据类型:1、所有的基本数据类型2、字符串3、Class类型4、注解类型5、枚举类型6、以上各种类型的数组注意:如果属性名字叫value,那么在赋值的时候就可以省略属性名
枚举:用关键字enum来定义的,规定一个类中只有有限的对象,而且在类的外部不能创建对象,在类的内部私有化构造器,使用有限的对象表示相应的实体,如果开发者在开发时赋值错误,不能够正常赋值(用在数量死的,值也是死的,保证数据安全)
Xml解析:Xml:可扩展标记语言,xml可以用浏览器打开,可以由开发者自定义里边的标签,只要遵循一定的规范即可。配置文件解析,在后期修改、维护的较多的项目或中间件等中,引入xml配置文件,提高项目的可维护性、灵活性、可扩展性
1、DOM解析:Java api提供,可以随意的访问文件中的任意节点,把整个xml文档一次性加载到内存中,根据配置信息创建一个文档树结构,这个树结构就一直保存在内存中,所以要访问哪个节点可以直接访问,访问节点比较方便,但是当文档内容较多的时候,就会消耗较多的内存,甚至影响服务器性能,可能造成内存溢出,常用
2、5AX解析,不常用没讲
3、JDOM解析,不常用没讲
4、DOM4j解析:第三方提供的内库实现xml解析,将配置xml文件中的节点逐层解析, 不需要把整个文档加信息载到内存中,所以占用内存较少,效率较高,工作常用
自定义框架ORM和Mvc框架
Orm:Object relationship mapping:对象关系映射,把对象跟关系型数据库进行映射关联(就像JDBC),持久化操作封装一个session类,在该类中封装对单表的增删改查的方法(Hibernate通过xml配置文件或注解来实现对象的属性跟表的字段的映射
Mvc框架:基于mvc架构开发出来的用来做逻辑、流程控制的框架(封装servlet,orm封装jdbc)M:模型(service层、dao层)C:控制(控制层servlet)V:视图(JSP,html,css,js)自定义mvc框架的架构(模仿springmvc)主要封装服务器
项目的生命周期:可行性研究、需求分析、设计(概要设计和详细设计)、编码、测试、部署(交付)、维护
版本控制工具:
CVS:比较老没讲
SVN:集中式项目代码管理工具,在公司的服务器中安装一个svn服务端程序,在服务器中创建版本库,项目代码就保存在版本库中,有svn服务器管理,每个程序员就可以通过svn客户端从服务器检出项目代码,在当前的代码基础上开发,开发完成后,再将当天开发的新代码提交到服务器的版本库中
Git:分布式的项目代码管理工具,在每个程序员的电脑上都有一个git服务端,服务端中包括了项目的所有的代码,开发的功能要提交的时候,先将代码提交到本地服务端,然后再将代码推送给项目组其他成员的电脑上的服务端中,相较于svn安全,麻烦
支付宝沙箱环境:提供两个虚拟的账号,一个买家,一个卖家。
1、https://docs.open.alipay.com/270/106291/访问下载java版本的代码示例
2、https://openhome.alipay.com/platform/developerIndex.htm访问点击研发服务
操作系统:就是应用软件跟电脑硬件之间的桥梁,操作系统提供了一套接口供应用软件调用,从而使应用软件可以间接的操作硬件,本质上他也是一个软件(程序)
常见操作系统:Windows、Mac os,LinuxWindows;闭源,收费,主要应用在个人电脑上,他的服务器版本Windows Server市场占有率较低。用户体验好一些,图形界面做的比较好Linux:开源,免费,安全性、稳定性较高,主要应用在服务器上,特别是在中国。易用性较差,图形界面做的不太好
linux系统
一、安装虚拟机
二、linux常用目录介绍Bin:存放的是公用的(普通用户)命令文件,root管理员账号,他的某些命令普通用户是没有的,那些管理员有并且普通用户也有的命令脚本文件就保存在此目录Dev:linux系统中把所有的硬件包括外接设备使用文件表示,那么这些代表了硬件的文件保存在该目录下Home:普通用户的家(根目录),那么每新建一个用户,系统就会在该目录下在该目录下创建一个用户目录(名字为用户名)Var:存放一些日志文件的目录Sbin:存放管理员的命令文件Etc:存放系统的各种配置文件,包括系统环境变量的配置文件、防火墙、网络等的配置文件Lib:存放一些类库Opt:存放用户暂时使用的应用软件的目录Root:是管理员的家(管理员的根目录)。Usr:存放用户的自定义的常用的软件,各种应用软件的linux版本安装包默认的安装目录会在usr目录下Tmp:存放系统中软件运行的时候使用到的临时文件
三、网络配置
1、进入/etc/sysconfig/network-scripts/目录:cd /etc/sysconfig/network-scripts/
2、打开ifcfg-eth0文件:vi ifcfg-eth0
3、敲击键盘a或i键,进入文件编辑模式:
4、在文件中配置一些信息Onboot=yes #是否在系统启动的时候启动网络Bootproto=static #将网络ip地址改为静态的IPADDR=ip地址 #指定静态的ip地址NETMASK=255.255.255.0 #子网掩码GATEWAY=192.168.80.1 #网关
5、敲击esc键,退出编辑模式,输入:wq命令,保存退出
6、启动网卡,完成:service network restart
四、常用命令:
1、ls:显示指定目录下的文件及文件夹ls:显示指定目录下的文件及文件夹蓝色为文件夹,其余为文件ls -l:展示文件及文件夹的详细ls -a:展示当前目录下所有的文件及文件夹,包括隐藏的文件或文件夹ls 具体目录:展示指定目录下的文件及文件夹
2、cd切换目录cd具体目录切换到指定目录cd..返回上层目录
3、ifconfig查看ip地址
4、dhclient动态获取ip地址
5、操作文件的命令
1、创建文件a、vi 文件名:指定的文件名存在就打开,没有就创建并打开a或i:开启文件编辑模式Esc:退出文件编辑模式:w:保存:q:退出:wq:保存加退出:wq!:强制保存退出注意:在vi命令打开文件后,输入命令:set nu可以显示文件中的行号b、touch文件名:直接创建一个文件(后缀名随意)c、cat>文件名:创建文件,可以直接在文件中输入内容,常使用在查看文件注意:cat文件名:查看文件内容
2、删除文件rm 文件名:删除指定的文件
3、复制文件cp 源文件 目标路径cp 源文件 目标路径/重命名
4、移动文件mv 源文件 目标路径mv 源文件 目标路径/重命名
5、创建快捷方式ln -s 源文件 快捷方式
6、目录操作
a、创建目录:mkdir 目录名mkdir 目录名1 目录名2 目录名3........同时创建多个目录mkdir -p父目录/子目录/孙目录 递归创建目录
b、删除目录rmdir 目录名 删除空目录rm -rf 目录名 强行删除非空目录
c、复制目录:cp -r 源目录 目标目录
d、移动目录mv 源目录 目标目录mv 源目录 新目录:改目录名字
e、权限操作:
1)、用户组:groupadd 用户名(自定义编号从500开始)查看用户组:cat /etc/group删除用户组:groupdel 用户组名修改用户组:groupmod -n 新名字 旧名字
2)、用户useradd 用户名 创建一个用户,系统会在home目录下自动创建一个跟用户名同名的目录,还要创建一个同名的用户组查看用户cat etc/passwduserdel 用户名 删除用户名 ,删不掉home下的用户目录
3)、修改用户的用户组usermod -g 新用户组 用户名
4)、查看用户所在的用户组groups 用户名(cat etc/password查询没有变)
5)权限编辑chmod 指定权限 文件或目录u:文件或目录的所有者g:文件或目录的所有者的同组用户o:文件或目录的所有者及同组用户之外的用户a:所有用户-d:表示目标是目录-:表示目标是一个普通文件-l:表示目标是一个快捷方式r:表示读权限w:表示写权限x:表示执行
方式一:chmod u+rwx,g+x,o+r 目标文件或目录切换登录用户:su - 用户名步骤:在一组的成员的文件夹里比如li用户的文件夹里面建一个文件给文件和li这个文件夹设置权限,让同一组的人能够操作,同一组的切换用户需要管理员设置密码用password 用户名 命令后输密码切换
注意:如果什么权限都没有就为0
方式三: chmod u=rwx,g=rx,o=r目标文件或目录
9、查看当前所在的目录pwd
7、文件搜索find 起始目录 参数(查找类型) 指定条件find .(当前目录) -name “test2.d/*test*(模糊查询)”
8、查看系统运行的进程ps -ef 查看所有运行的进程ps -ef|grep tomcat(指定的进程)kill -9 pid(进程id) 强制关闭指定的进程加-9是强制杀死进程
五、常用软件安装:
1、三种安装方式:
a、绿色安装,解压缩安装,安装好之后,如果卸载,可以非常干净、方便的卸载(删除解压文件)tar -cvf 压缩包名字 被压缩的文件或目录名 压缩命令tar -xvf 压缩包名 解压命令tar -xvf 压缩包名 -C 目录名 解压到指定目录下
b、通过运行安装文件的形式安装,在linux中,卸载的时候比较麻烦(只能安装一个)rpm -ivh 安装包名字(默认安装usr下)
c、在线安装,不用下载任何的安装包或安装程序文件,直接在线安装。本质上是rpm,不用下载后缀为rpm的安装文件yum ( -y ) install 软件名 加了-y不用安装中途会答yes,一次直接安装
2、安装jdk
3、tomcat 绿色安装
4、mysql安装
5、nginx安装
服务器程序:
Tomcat:servlet容器,运行servlet(动态),还可以处理静态资源的请求,但是tomcat处理静态资源的请求效率低,Tomcat并发处理请求的量级在百位数(不可以作为负载均衡器)
Nginx:http服务器,处理http请求,可以处理静态资源的请求,而且效率非常高,不能运行servlet,并发性特别好,并发处理请求的量级在万位数(一般五万),搭建服务器集群的最佳选择,实现动静态分离部署的最佳选择(可以作为反向代理服务器做负载均衡)
Apache:http服务器,处理静态资源请求,而且效率较高,不能运行servlet,并发性较好Weblogic、JBoss:应用服务器,只能运行动态资源,收费。
6、svn安装
服务器集群的搭建:随着互联网项目的兴起,对于系统服务端的高并发、高可用等要求越来越高,单体应用部署在一台tomcat上,可以处理的并发请求书较低,当在某些特定时段,访问量瞬间提高,可能造成服务器的响应效率低下,甚至宕dang机。那么有一种解决方案就是通过将多台服务器组合到一起,共同分担服务器负载,按照一定的比例合理地分配请求,从而提高系统整体的并发性、可用性、安全性。附:云服务,提供服务的公司把世界上空闲的服务器整合起来给需要的人使用
a、纵向扩展系统,提高服务器硬件的配置,通过安装其他的并发性能高的服务器(安装花钱性能更高的服务器软件,一般不安装tomcat)
b、横向扩展系统,通过使用多台普通的服务器,搭建一个负载均衡的服务器集群,每个服务器上安装一个tomcat,每个tomcat中部署一个相同的项目,处理相同的请求,只是每台分担一定数量的请求附:分布式,分隔功能做成集群,集群一般至少两个,按照一定的比例
搭建:
① 、修改tomcat的端口号,保证两个tomcat的所有端口号都不重复。
② 、修改nginx配置文件:注册tomcat服务器:upstream test.com{server 192.168.80.175:8080;server 192.168.80.175:18080;} 配置.jsp,.do等动态请求: location ~ \.(jsp|jspx|do|action)?$ { proxy_pass http://test.com;#指定负载均衡的方式跟服务器 proxy_redirect default; } 配置静态请求的目标资源目录:location ~ .*\.(html|js|css|ico|jpg|jpeg|png|JPG|JPEG|PNG|eot|svg|ttf|woff) { root html; }配置没有后缀的动态资源请求:location ~ .*$ {#没有后缀的请求 proxy_pass http://test.com;#指定负载均衡的方式跟服务器 proxy_redirect default; }
③ 、在tomcat上部署web项目,重启tomcat,重启nginx。./nginx –s reload
搭建一个服务器集群,session共享问题通过ip_hash方式解决,但是这种方式随机性较高,如果我们要较好的控制请求的分发(负载均衡),我们可以采用轮询的方式进行负载均衡,通过redis存储并管理session的方式实现session共享,这个是目前比较推荐session共享解决方案。具体实现起来非常简单,下载相应的插件,配置一下,导几个jar包就可以了。
步骤:通过插件tomcat-cluster-redis-session-manager实现在redis中共享session:a 、下载类库https://github.com/ran-jit/tomcat-cluster-redis-session-manager/wikib 、解压c、jar包放入tomcat的lib目录中d 、再把配置文件配置后放入tomcat的conf目录下e、在tomcat的context.xml配置文件中添加以下两条配置信息:<Valve className="tomcat.request.session.redis.SessionHandlerValve" /> <Manager className="tomcat.request.session.redis.SessionManager" />f 、实现了session共享
总结:系统采用服务器集群架构对系统影响a、并发量提高b、系统的稳定性(安全性)提高c、系统扩展性(灵活性)提高d、开发的时候需要注意一些问题,比如session共享的问题
负载均衡:就是让代理服务器分发请求是按照一定的规则来分发,不至于使集群中的有些服务器负载过大,而另外一些服务器又几乎不承担负载。
在nginx+tomcat组成的集群中要实现负载均衡,可以通过nginx的配置文件配置不同的规则去实现。负载均衡的规则:
a、轮询:默认的规则;
b、加权轮询,按照一定的权重来进行轮询
c、热备份,就使用一台运行状态的服务器做备份,一但服务器出现故障,备份的服务器马上自动顶替上去,处理请求,用户感受不到服务端的故障
d、ip_hash,一个用户访问系统的时候,第一个请求进入了tomcat1,tomcat1为其创建会话,第二个请求进入Tomcat2又会为其创建一个会话,这样用户就无法使用同一个会话,就会造成错误(比如登陆过的用户无法识别登陆过),ip_hash根据ip地址的前三位进行计算得到一个结果,那么第一次请求过后,计算的结果被保存,然后第二次请求进来的时候,就根据ip地址前三位来计算,如果结果一样,就把该请求分发到同一台服务器。加权:权重越大概率越大
第三四阶段框架及分布式
Struts2、Hibernate(HQL语言)、Spring、SpringMVC、SpringBoot、SSH、Mybatis、SSM、Maven、权限系统、VUE
struts2:mvc框架,功能相当于servlet
执行流程
Helloworld:导包-配置web.xml(mvc)-配置strut2的配置文件-编写处理器类-测试
两种访问servlet api的方式
a、解耦合的方式通过ActionContext类来获取相应的servlet api中的接口对象获取请求:get(String key)获取会话:getSession()获取上下文:getApplication( )
b、耦合的方式通过ServletActionContext类的方法:获取请求:getRequest();获取上下文:getServletContext();
类型转换器:struts2有很多基础数据类型转换的类型转换器,这些内置类型转换器就将大多数的数据类型进行自动转换但是由于日期数据类型格式多变,所以大多数的框架都不会把日期的类型转换硬编码在程序中,一般都会提供一个可扩展的接口,由开发者自定义类型转换器
自定义类型转换器a、继承一个类叫strutsTypeConverter类b、重新类型转换的方法c、配置自定义类型转换器
大量使用拦截器:struts2大量使用拦截器,将action请求拦截下来,做出相应的操作,然后放行
自定义拦截器:a、创建一个java类,继承AbstractInterceptor类b、重写一些抽象方法c、配置自定义拦截器
总结:通过一个核心控制器StrutsPrepareAndExecuteFilter(过滤器)拦截请求,截取请求url中最后的部分,解析配置文件,将截取的url部分跟配置文件中解析出来的url映射去匹配,如果匹配上就获取该映射关系中的类跟方法信息,调用该类中的方法处理请求,返回一个字符串,再根据该字符串跟结果映射匹配,如果匹配上,就按照该结果映射的视图进行页面跳转。其中前后端的数据交互通过控制器类的属性进行,那么控制器类中就会存在大量的成员变量,这样的话,如果控制器类使用单例模式,就会造成线程不安全,如果不使用单例模式,又会造成内存的大量消耗,也不够优化,所以struts的市场占有率在近几年互联网项目兴起的大环境下,逐渐降低
Hibernate:持久层框架,数据持久化,就是利用xml解析、自定义注解、反射、jdbc api封装出来的程序的半成品,提高持久层开发的效率,替换之前dao层的代码,它就是一个orm框架
Helloworld:导包-创建并配置hiberbate全局变量-创建实体类,并且创建映射文-编写代码-单元测试
saveOrUpdate():传入的对象没有指定id的时候就执行插入操作,如果指定了id就修改,id必须在数据库里面要有,如果没有就要报错
HQL语言:Hibernate query language,面向对象的思想来设计的,在这个语言中使用类名、属性名等。Hibernate 通过hql编写查询的代码,如果后期需要跟换数据库,只需在全局配置文件更改方言跟连接数据库的参数即可,代码不需要修改,编程的时候使用hql,框架在运行的时候会自动把hql自动翻译成相应数据库的SQL语句,比Mybaits的数据库移植性要好
setParameter():相当于是setObject();setproperties():通过传对象来传几个参数,但是后面的名字必须和实体类的参数属性名字一样
与mybaitis区别:相同点:两者都是通过Configuration配置文件,sqlSessionFactoryBuilder来创建sqlSessionFactory来创建session,通过session来执行sql以及事物。不同点:1.hibernate是全自动ORM框架,mybatis是半自动ORM框架。因为前者关联对象与关联集合和实体对象的映射关系是通过实体关系模型来自动生成的;而后者需要自己手动填写sql以及映射关系。2.hibernate的日志操作比mybatis更加全面。3.hibernate的数据库移植性比mybatis好。前者的sql是自动生成的,在一定程度上降低了与程序之间的耦合度;而后者的sql是手动添加的,所以耦合度直接取决于程序员写sql的方法。4.mybatis在使用上比hibernate更加灵活。5.mybatis比hibernate更容易优化。因为前者的sql是直接写在xml文件里的,可以直接动手修改维护;而后者的sql是自动生成的,所以无法直接维护。6.缓存机制上hibernate比mybatis好点。原文链接:https://blog.csdn.net/Bly000/article/details/106226473
Spring是一款开源的免费框架,是一个轻量级控制反转(IOC)和面向切面(AOP)的容器框架
代理模式:java23种设计模式之一,核心结构是使用代理对象持有目标对象,对目标对象原有的方法进行增强
静态代理:每一个代理类只能代理一个业务层类由于静待代理需要我们人为地为每一个目标对象定义代理类,在实际的开发过程用处不大。
动态代理:可以通过JAVA自动地为目标对象创建代理类。(JAVA根据我们的实际需求,自动创建了一个类)。分JDK动态代理(使用JDK动态代理的步骤:1、创建InvocationHandler类,封装代理类中需要执行的方法使用JDK的API创建代理对象调用方法)和CGLIB动态代理。
IOC(Inverse Of Control)是Spring核心功能之一,“控制反转”(IOC) 将应用程序的配置和依赖性规范与实际的应用程序代码分开即依赖注入(DI)。
配置applicationContext.xml(bean标签配置、property标签配置、配置连接数据库)
1.创建bean的三种方式:调用构造器创建Bean;调用静态工厂方法创建Bean;调用实例工厂方法创建Bean
2.bean的生命周期:实例化(Instantiation)、属性设置(populate)、初始化(Initialization)、销毁(Destruction)
3.bean的6大作用域:singleton(单例作用域):Bean在IoC容器中只存在着一个实例、prototype(原型作用域、多例作用域):每次对Bean的请求都会创建一个新的实例、request(请求作用域)、session(会话作用域)、application(全局作用域)、websocket(HTTP WebSocket作用域)
注解IOC(自定义类能用,第三方类不能用加注解):@Component 该注解需要定义在类上;@Controller 该注解需要定义在控制层的类上;@Service 注解需要定义在业务层的类上;@Repository 该注解需要定义在数据层的类上;@Autowired和@Resource 该注解需要定义在类的属性之上
@Autowired 和 @Resource 都是用来实现依赖注入的注解(在 Spring/Spring Boot 项目中),但二者却有着 5 点不同:来源不同:@Autowired 来自 Spring 框架,而 @Resource 来自于(Java)JSR-250;依赖查找的顺序不同:@Autowired 先根据类型再根据名称查询,而 @Resource 先根据名称再根据类型查询;支持的参数不同:@Autowired 只支持设置 1 个参数,而 @Resource 支持设置 7 个参数;依赖注入的用法支持不同:@Autowired 既支持构造方法注入,又支持属性注入和 Setter 注入,而 @Resource 只支持属性注入和 Setter 注入;编译器 IDEA 的提示不同:当注入 Mapper 对象时,使用 @Autowired 注解编译器会提示错误,而使用 @Resource 注解则不会提示错误。原文链接:https://blog.csdn.net/q66562636/article/details/126690329
为什么更推荐使用 @Resource ?:是因为其可以指定是通过 name 还是 type 的注入方式,而@Autowired注解本身自己是不能实现这个效果的,要和@Qualifier一起用才可以!https://blog.csdn.net/weixin_44421461/article/details/130838329
AOP(Aspect Oriented Programming),即面向切面编程
通知:前置(MethodBeforeAdvice)、后置(实现AfterReturningAdvice接口)、环绕(实现MethodInterceptor接口)、异常类(实现ThrowsAdvice接口)
生命周期:
spring的生命周期:实例化、属性赋值、初始化、使用、销毁。简化来讲,主要分为两个阶段:Bean的创建和Bean的销毁。Bean的创建:通过构造函数创建Bean的实例。为Bean的属性设置值和对其他Bean的引用。调用Bean的初始化方法(如果有指定的话)。如果Bean实现了BeanPostProcessor接口,调用postProcessBeforeInitialization()方法。调用Bean的初始化方法(如果有指定的话)。如果Bean实现了BeanPostProcessor接口,调用postProcessAfterInitialization()方法。Bean现在已经准备好使用了。Bean的销毁:当Bean不再需要时,如果Bean实现了DisposableBean接口,调用destroy()方法。如果有指定destroy-method,调用该方法。
bean的创建方式和注入方式:https://blog.csdn.net/weixin_45759791/article/details/106877036
创建方式:构造方法创建;普通工厂中的方法创建;工厂中的静态方法创建;注解创建对象(@Conmponent、@Controller web层、@Service 业务层、@Repository 持久层);
注入方式:构造器注入;setter方式注入(更常用);注解方式注入(Autowired、Qualifier、Resource、Value)
bean的作用域:Spring框架定义了以下6种作用域: 原文链接:https://blog.csdn.net/qq_62274623/article/details/132679139
1.singleton:单例作用域,所有对该Bean的请求都返回同一个Bean实例2.prototype:原型作用域,每次请求时都创建一个新的Bean实例。3.request:请求作用域,每个HTTP请求都会创建一个新的Bean实例,该Bean实例仅在当前请求内有效。4.session:会话作用域,每个HTTP会话都会创建一个新的Bean实例,该Bean实例仅在当前会话内有效。5.application:全局作用域,一个bean 定义对应于单个ServletContext 的生命周期。6.websocket: HTTP WebSocket 作用域,一个bean 定义对应于单个websocket 的生命周期。选择适当的作用域可以提高应用程序的性能和可维护性。
Mybatis缓存
一级缓存:默认开启,sqlsession对象中有个集合键值对
二级缓存:sqlsessionfactory,一个应用/库里面通常一个,多套数据库可能有多个键以命名空间作为键
一级缓存和二级缓存同时生效:先查一级,再查二级都不存在才会查询,查询出来先存到一级里面,关闭连接才将一级中的数据写入到二级
SpringMVC
参数获取
基本数据类型:默认情况下,http请求中的参数名称必须和方法形参名称一致才能进行参数绑定,但是可以使用@RequestParam注解进行手动的参数绑定
实体类型:为了便于后续执行过程中参数的传递,通常我们会将多个参数封装称为一个实体类型的对象,在形参列表中定义一个实体类型的形参,在发送http请求时直接使用实体类的属性名称作为参数即可。
注意:如果参数中有字符串类型需要转为日期类型,那么需要执行日期类型的转换格式不然会抛出异常(@DateTimeFormat(pattern="yyy-MM-dd"))
当表单中传递多个相同名称的参数时,将http请求中包含的字符串数组数据封装为数组http请求中包含了数组数据,例如用多选框传递的参数hobby=music,hobby=read那么在形参中需要定义名为hobby的字符串数组来绑定参数。
如果是Ajax请求,那么我们需要使用@RequestBody这个注解来将前端的数据转为后端的集合。
响应视图
1.以字符串的方式响应网页
视图解析器
2.响应ModelAndView对象
setViewName(view)方法用于指定视图名称,可以是物理视图的地址也可以是逻辑视图名称;addObject(key,value)方法用于指定要保存到request作用域中的数据,key最终会作为request作用域中数据的键
3.响应JSON
1.导入jackson相关包
2.需要将什么数据使用json的方式响应给客户端,就将方法的返回值定义为该数据类型
3.在方法上添加注解,@ResponseBody表示该方法的返回值将采用json的形式返回给客户端
补充:如果一个业务控制器中的所有方法都采用json的方式响应数据,那么可以在类上使用全局的设置将@Controller注解替换为@RestController,如此就不需要在每一个方法上都添加@ResponseBody注解
在开发过程中,由于前后端分离开发。后端响应给前端的数据应该拥有统一的格式,利于开发。所以后端应该使用一个类专门来封装返回给前端的数据(Result)
统一的异常处理
普通http请求发生错误时,用响应错误页面的方式处理异常
mav不能使用重定向,只能使用请求转发,response重定向也会报错
如果是ajax请求将以json的形式响应错误信息
导入fastjson包
2.新建全局异常处理器在全局异常处理器中,通过FastJsonJsonView对象来定义json视图,通过键值对保存json数据,将fastsjonjsonview封装为ModelAndView并返回。
参数校验步骤
1.添加jar包
2.在springmvc配置文件中加载参数校验器,不加也可以。在springmvc检测到项目中存在hibernate-validator包,就会自动加载参数校验器。
3.在实体类的属性中使用注解添加验证规则以及错误信
4.在控制器中接收User类型的参数时,添加注解进行校验,添加@Validated表示在springmvc对user参数绑定时进行校验,校验信息写入BindingResult中,在要校验的实体类对象后边添加BingdingResult, 一个BindingResult对应一个实体类对象,且BingdingResult放在实体类对象的后边。
RESTFUL的设计风格
GET方式发送 /users请求时,表示查询users的所有数据DELETE方式发送/users请求时,则表示删除users数据POST方式发送/users请求时,则表示要新增一个用户数据PUT方式发送/users请求时,则表示要更新一个用户数据PATCH方式发送/users请求时,则表示要更新一个用户数据中的部分字段
如果是PUT请求,需要添加一个额外的过滤器,控制器中才能获取到传递的参数。org.springframework.web.filter.HttpPutFormContentFilter
SpringMVC在进行请求地址映射时,不但会匹配请求地址的名称,而且还会匹配发送请求的方式,也就是说只有当请求地址和请求方式都匹配时
SpingMVC在请求地址中可以提供占位符,而且占位符中的数据可以在方法中获取到,这就提供了一种非常简洁的传参方式,@PathVariable
在Servlet中/可以拦截除.jsp结尾以外的所有请求。/*可以拦截所有请求。在Filter中/*拦截所有请求
拦截器:类似于Servlet 开发中的过滤器Filter,用于对处理器进行预处理和后处理
1.拦截器定义:实现HandlerInterceptor接口
preHandle方法在执行处理器之前执行,可以用于登录验证、权限验证等等postHandler方法在出行完处理器之后,进入视图解析器之前执行,可以对视图进行处理afterCompletion方法在响应视图之前执行,主要用于关闭资源
2.配置拦截器:在springmvc的配置文件中添加标签加载拦截器
Springmvc不会拦截.jsp的请求,但是会拦截静态资源请求,如果不想让用户直接访问jsp页面,需要将jsp页面放到web-inf中,对于静态资源需要提供静态资源处理器以及通过 <mvc:exclude-mapping path="/js/**"/>对静态资源放行。
3.与过滤器区别:过滤器 是基于函数回调的,拦截器 则是基于Java的反射机制(动态代理)实现的;过滤器Filter是在请求进入容器后,但在进入servlet之前进行预处理,请求结束是在servlet处理完以后。拦截器 Interceptor 是在请求进入servlet后,在进入Controller之前进行预处理的,Controller 中渲染了对应的视图之后请求结束......https://blog.csdn.net/nigulasizp/article/details/125322507
文件上传和下载
上传步骤:加载多部件解析器;页面采用表单提交或者AJAX提交;SpringMVC上传可以使用commons-upload的包来简化操作;Controller中处理文件
文件下载:文件下载核心代码:// 设置响应头,响应类型为附件,浏览器将会用下载的方式处理响应的信息response.setHeader("content-disposition", "attachment;filename="+ URLEncoder.encode(filePath, "UTF-8"));
子主题
Maven是通过POM(项目对象模型)来描述项目基本信息,从而达到构建项目、生成测试报告、生成项目文档、打包、发布等等功能的综合性项目管理工具。
Maven的仓库有两大类:本地仓库和中央仓库
使用Maven命令构建java工程
普通java项目
在任意目录创建项目:在任意目录命令:mvn archetype:generate
编译项目:在项目所在的文件夹中打开控制台输命令:mvn clean compile,class文件会保存到target文件夹中。
执行测试:在项目所在的文件夹中打开控制台输命令:mvn clean test
项目打包:在项目所在的文件夹中打开控制台输命令:mvn clean package(注意在打包时,默认情况下Maven并没有对主类进行配置,所以我们需要在项目的pom文件中,引入一个jar插件,同时指定程序的主类。)
执行java程序包:在项目的target目录中,运行java程序包。命令:java -jar 程序包名
使用命令创建WEB项目
命令:mvn archetype:generate,创建时注意选择web项目的模板,创建好的JavaWeb项目中目前只有src/main/resources目录,因此还需要手动添加src/main/java、src/test/java、src/test/resources
项目打包执行:mvn package,构建成功后,myWebApp目录目录下多了一个target目录,在这个目录下会打包成myWebApp目录.war,把这个war包拷贝到Tomcat的发布目录下就可以运行了。
Eclipse中使用Maven构建java工程
自定义Maven步骤:conf文件夹的setting文件加<profiles>标签修改在新建maven项目时,设置默认的jdk版本为1.8
Maven依赖管理(pom.xml)
定义属性properties标签,可以定义大项目架包全局版本号
项目的聚合
1.创建多模块项目的载体(可以理解为父项目),作为一个载体并不需要任何的业务代码和包,它只是作为所有模块的项目容器。使用maven先创建一个普通的maven项目。创建完成之后保留pom文件和jre其余的文件夹全部删除
2.右键父项目new--maven module,模块根据需求可以选择web模板或者其他的java模板,通常前台和后台都是web项目,所以在创建前台和后台模块时还是选择web模板。最终直接将父项目部署到服务器运行,父项目中会包含所有的子模块。
3.在父项目中统一定义项目依赖包,然后在子项目中使用<parent>标签来引用父项目中的pom配置
附:跨项目new对象调方法导包,把项目当成依赖导入
Spring-boot:内置Tomcat的方式的方式来运行web程序,服务器跟随项目的jar包一起发布;Spring-Boot并不能直接完成Spring的工作。准确的说Spring-Boot只是一个整合Spring各项功能的中间件,几乎可以实现0配置开发的目的。
使用Spring-Boot快速搭建WEB应用
1.在Eclipse中新建Maven项目,注意选择原型时需要选择普通的java项目原型,当然web项目也可以,但是Spring-Boot更推荐使用普通java项目来进行开发
2.新建好项目以后,在main文件夹中新建resources文件夹,并转为资源目录,与以往不同的是resources文件夹不仅是存放配置文件的目录,同时也是存放html网页和静态资源的地址。
3.配置SpringBoot项目的依赖
4.新建SpringBoot启动器:在SpringBoot项目中需要添加主类作为SpringBoot启动器,启动器最好放到所有控制层、业务层、数据层的父包中,因为SpringBoot对于Spring的扫包地址默认为启动器所在的目录下。
5.运行启动器,测试。
注意:由于SpringBoot默认项目在服务器中的访问路径为/,所以访问项目不再需要项目名,如果你要修改项目的访问路径,可以在application.properties中添加配置server.servlet.context-path=/spring-boot-demo在SpringBoot2.x以下的版本,该属性为server.context-path=/spring-boot-demo。但是根据实际需求,项目的访问路径最好设置为/,因为这样设置更利于网页中使用绝对路径来请求资源。
使用SpringBoot集成Thymeleaf模板引擎步骤
1.新建Maven工程,采用普通java项目模板,和以前一样在main文件夹下新建resources文件夹
2.在pom.xml中引入SpringBoot的相关依赖包,和以前的SpringBoot项目相比需要多引入一个spring-boot-starter-thymeleaf依赖。
3.在resources中新建application.properties资源文件
4.使用mybatis-generator插件完成反向工程,根据业务需求新建控制层类,业务层类,启动器类,并添加相应的注解。
5.在templates文件夹中新建html文件,该文件作为模板文件来使用,在html中使用thymeleaf提供的指令和插值进行数据渲染
补充:thymeleaf指令
内容替换:th:text 替换标签的文本内容th:utext 支持html标签的内容替换th:value 替换表单控件的value值th:attr 设置属性的值,语法 th:attr=”属性名称=值或者表达式”,可用于设置单选款或者多选款选中,例如:th:attr=”checked=${user.sex==1}”th:checked 设置单选框是否选中 th:checked=”${实体属性==单选框的value值}”<input type=”radio” th:checked=”${user.sex==1}”>男<input type=”radio” th:checked=”${user.sex==2}”>女th:selected 设置下拉框选中,语法th:selected="${user.majorid eq major.id}"。th:onclick 添加点击事件,在点击事件的函数中可以使用表达式传参除了上述指令外,还可以使用th:属性名称设置html的属性值,例如超链接的href、图片的src等等
内容拼接:要讲动态数据与字符串进行拼接的方式:th:onclick=”|del(${user.id})|”,写在两个竖线之间的内容可以直接进行静态数据和动态数据的拼接
if指令:th:if=”${条件表达式}” 条件成立渲染该标签th:unless=”${条件表达式}” 条件不成立渲染该标签
循环指令:th:each=”变量名 状态值名称:集合” 例如:th:each=”user ,i:${list}”,其中user表示循环产生的对象,i表示状态值,状态值中包含了序号,集合长度等信息:i.index:当前迭代对象的index(从0开始计算)i.count: 当前迭代对象的index(从1开始计算)i.size:被迭代对象的大小i.current:当前迭代变量i.even/odd:布尔值,当前循环是否是偶数/奇数(从0开始计算)i.first:布尔值,当前循环是否是第一个i.last:布尔值,当前循环是否是最后一个
使用SpringBoot整合Swagger2生成接口文档
1.首先使用SpringBoot搭建出一个RESTFUL风格的web项目
2.添加Swagger2的依赖
3.在启动器上添加通过@EnableSwagger2注解来启用Swagger2。然后在启动器中定义一个方法用来创建API清单对象,并添加@bean注解,在启动器中添加了@bean注解的方法所返回的返回值将会交给spring容器来管理。创建完清单以后执行build就会去构建API文档。
4.在控制层的方法上添加注解来描述接口信息和参数信息。如果参数类型不是对象,并且提交方式是get方式,那么我们需要在控制层方法上添加一段注解,主要目的是修改参数的类型。所谓的参数类型指客户端传参方式,分为header、body(默认)、query(地址栏拼接)、path(地址栏参数)。修改了参数类型以后,在swagger提供的在线文档上才能正常的传参测试。
@ApiOperation:用在方法上,说明方法的作用@ApiImplicitParams:用在方法上包含一组参数说明@ApiImplicitParam:用在@ApiImplicitParams注解中,指定一个请求参数的各个方面paramType:参数放在哪个地方header-->请求参数的获取:@RequestHeaderquery-->请求参数的获取:@RequestParampath(用于restful接口)-->请求参数的获取:@PathVariable
如果采用的是post提交方式,且参数是一个对象。为了在线文档中便于测试,需要将参数类型定义为body,测试时采用json的方式提交数据。采用json的方式提交数据,需要了解参数的json格式,需要在实体类上添加注解。
控制层方法上不需要添加参数相关注解,参数前添加一个注解@ApiParam和@RequestBody。
配置完上述信息以后,在启动项目时就会同时生成一个接口文档,该文档是一个HTML网页,访问地址为swagger-ui.html
SpringBoot中starter原理:Spring Boot之所以能够帮我们简化项目的搭建和开发过程,主要是基于它提供的起步依赖和自动配置。https://blog.csdn.net/weixin_45511500/article/details/124571380
起步依赖,其实就是将具备某种功能的坐标打包到一起,可以简化依赖导入的过程。例如,我们导入spring-boot-starter-web这个starter,则和web开发相关的jar包都一起导入到项目中了
自动配置的关键几步以及相应的注解总结如下:1、@Configuration与@Bean:基于Java代码的bean配置2、@Conditional:设置自动配置条件依赖3、@EnableConfigurationProperties与@ConfigurationProperties:读取配置文件转换为bean4、@EnableAutoConfiguration与@Import:实现bean发现与加载
自动装配原理:@SpringBootApplication 是一个复合注解,大概就可以把 @SpringBootApplication 看作是 @Configuration(允许在上下文中注册额外的 bean 或导入其他配置类,作用与 applicationContext.xml 的功能相同)、@EnableAutoConfiguration(启用 SpringBoot 的自动配置机制)、@ComponentScan(扫描包下的类中添加了@Component (@Service,@Controller,@Repostory,@RestController)注解的类 ,并添加的到spring的容器中,可以自定义不扫描某些 bean) 注解的集合,https://blog.csdn.net/m0_59749089/article/details/131280769
SSH三大框架的整合其实就是通过Spring的IOC来创建Struts2中的Action对象以及Hibernate中的SessionFactory对象
整合步骤
一、在WEB项目中加载Spring框架:Spring提供了一个监听器ContextLoaderListener,该监听器实现了ServletContextListener接口,在服务器启动时,该监听器就会执行,执行该监听器的目的是读取applicationContext.xml文件,并根据文件的配置创建项目中所需要的对象并完成依赖注入。
1.导入spring框架中所需的jar包
2.在web.xml中加载spring提供的监听器
3.ContextLoaderListener在执行时会默认去读取WEB-INF下的applicationContext.xml文件,如果想自定义配置文件名称需要使用<context-param>标签配置上下文参数,配置的上下文参数在监听器中可以获取。参数名称contextConfigLocation,参数值classpath:文件名称,表示该文件处于src目录之下。
二、使用Spring框架整合Struts2
1.导入Struts2所需的jar包,本次要导入到项目中的jar包包括Struts2本身所需要的包、JSON的相关包、Struts2-Spring的插件包
2.在web.xml中加载Struts2的核心控制器
3.在src下新建struts.xml文件,并配置action标签,和以前单独使用Struts2时相比有两点不同,首先Action类以前是Struts2框架负责创建,使用Spring以后需要将该对象的创建工作交给Spring来负责,所以需要在Action类上添加@Controller注解或者在applicationContext.xml中使用<bean>标签来创建Action对象。
注解方式:@Scope(“prototype”)是设置Spring创建Action对象时采用非单例模式注解方式不要忘记需要在applicationContext.xml文件中配置扫包地址
标签配置方式
第二点不同在于struts.xml文件中,单独使用Struts2时,action标签中的class属性表达的是处理该请求时需要执行的类的名称,Struts2会创建该类型的对象,但是一旦和Spring整合之后该类对象已经被Spring创建,这时在class中直接写Spring容器中对象的beanid即可。
三、使用Spring框架整合Hibernate:由于在数据层实现类中需要使用Session对象来执行Hibernate的增删改查,这是我们可以使用Spring的IOC来帮助我们创建Session对象并将Session对象注入到数据层对象中。
1.导入Hibernate的必要包、C3P0的包、数据库驱动包,如果有使用Hibernate二级缓存的需求还需要导入第三方的二级缓存依赖包。
2.新建测试实体类Users,并添加Hibernate的必要注解。
3.创建Session对象依赖于SessionFactory对象,创建SessionFactory对象依赖于数据源对象,本案例中采用C3P0连接池作为数据源,如果要换其他的连接池,只需要更改数据源的配置即可,其他模块保持不变。
4.使用SpringAOP实现声明式事务管理
在使用声明式事务以后,在业务层中不需要在手动的处理事务,也不能在业务层中进行异常处理,如果在业务层处理了异常,异常增强处理会以为捕获不到异常而失效,事务也就不会回滚。在选择openSession和getCurrentSession时,如果执行增删改使用getCurrentSession,执行查询使用openSession,注意openSession获取到的是全新的session对象,并且开启了自动提交。getCurrentSession会从session容器中获取已将创建好的Session对象,该Session对象在一个线程中都是唯一的,并且关闭了自动提交。
5.使用OpenSessionInView延迟加载后session的关闭问题:Hibernate为了降低数据库压力,在设计时对于数据的查询采用了延迟查询的方式,也就是当数据被真正使用时,Hibernate才会去执行查询,但是在WEB项目中数据真正使用往往发生在JSP解析阶段或者是格式化JSON的阶段。在这时session往往已经关闭了,这时程序就会出现异常。这时我们可以采用Spring提供的OpenSessionInView的方式将session的关闭工作放在执行完控制层代码以后。Spring提供了一个过滤器来实现该功能。
VUE:是一款MVVM框架,MVVM是Model-View-ViewModel的简写,Model(模型)是指数据,View(视图)是指网页元素,ViewModel是一个中间件,负责对模型和视图进行双向的绑定,换句话说ViewModel负责把Model的数据同步到View显示出来,还负责把View的修改同步回Model。这就是所谓的双向绑定。
第一个VUE案例
1.在网页导入vue.js文件
2.初始化Vue对象:VUE通过其定义的Vue类型的对象来实现标签的数据绑定,事件绑定,数据监听等工作,每一个Vue对象都有一个挂载的网页标签,所谓的挂载标签是指,该Vue对象中的数据,函数都只能在挂载的标签内来使用。Vue挂载的标签通常是一个div。
3.运行项目结果
数据绑定
文本:数据绑定最常见的形式就是使用双大括号的文本插值
上述写法还可以使用v-text完成相同的效果
通过使用 v-once 指令,你也能执行一次性地插值,当数据改变时,插值处的内容不会更新。
HTML:双大括号会将数据解释为普通文本,而非 HTML 代码。为了输出真正的 HTML,你需要使用 v-html 指令
属性:{{}}不能作用于属性之上,如果要给标签的属性设值,需要通过v-bind指令。上述v-bind指令可以简写为:href=”a.href”
双向绑定:在输入框控件上可以实现数据的双向绑定,你可以用 v-model 指令在表单 <input>、<textarea> 及 <select> 元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。
逻辑指令:VUE提供了一些逻辑指令,例如v-if,v-else-if,v-else,v-show,v-for,可以用于条件渲染判断以及循序渲染标签。
选择指令(v-if):条件成立则渲染标签,否则不渲染
条件显示指令(v-show):条件成立则显示标签,否则隐藏标签
选择指令和条件显示指令区别是条件指令如果条件不成立,则不会生成标签,但是条件显示指令无论如何都会生成后标签,区别是是否会显示而已。
循环指令(v-for):如果不需要序号,可以写做v-for=”user in list”。
生命周期钩子函数:钩子是VUE中的一个概念,它的含义是指在某个特定的点去执行的操作,VUE的对象从创建到销毁的整个过程被称为生命周期,整个生命周期中的每一个阶段都可以去执行一些函数。
这里面最重要的是created和mounted这个钩子,这个钩子将会在VUE的初始化阶段页面渲染之前执行,如果我们要向页面中渲染一些动态数据,那么这里是一个很好的执行时机。但是created时标签和数据都还没有完成渲染,所以是无法获取这些标签对象也无法操作他们的。而mounted这个钩子,是在dom元素与数据都完成渲染之后执行的。所以在此钩子函数中可以使用动态加载的这些标签。
绑定事件:可以用 v-on 指令监听 DOM 事件,并在触发时运行一些 JavaScript 代码。v-on:click可以简写为@click。
侦测和计算:VUE提供了对数据的监听机制,当VUE中的数据发生更改时,监听对应的函数将会执行,这种机制被称为侦测
使用watch可以非常方便的完成前端校验等功能。Vue提供了可以用data中的数据通过计算得到新的数据,并且该数据会随着data中的数据随动,例如购物车中的总价跟随数量而动,就可以使用该属性来完成。
组件:Vue的两大核心分别是数据绑定和组件,所谓的组件化开发就是将一个网页中的各个组成部分拆分称为独立的组件,一个组件由模板、样式、脚本三部分组成。要在网页中使用组件主要分为两步,定义组件和引用组件。
定义组件方式:https://blog.csdn.net/ply6686/article/details/120623992
全局组件:通过Vue.component(“组件名称”,{//组件的各种属性})函数创建的组件被称为全局组件,可以在任何的Vue实例中使用。如下:定义一个名为component-a的组件,该组件所包含的html是一个span标签。动态绑定了一个hello world的值。
局部组件:在创建Vue对象时,通过Vue对象的components属性定义组件,局部组件只能在该Vue绑定的标签中使用。例如我们要定义一个名为component-b的组件
和JSP或模板引擎区别:一个渲染是在前端,一个渲染是在后端
SSM整合步骤
1.导包:Spring的包,包含了SpringMVC的包
2.加载Spring:加载Spring需要在web.xml中加载Spring的监听器
3.加载SpringMVC:在web.xml中配置核心控制器
4.整合mybatis:整合mybatis就像将原本由Mybatis负责创建的数据层代理对象委托给Spring来创建,所以我们必须提供数据源、SqlSessionFactory,以及一些mybatis的属性。
5.Spring扫描业务层的包创建业务层对象以及声明式事务
权限系统
权限粒度:粗颗粒度的:按钮级别的权限,例如张三能够操作商品的查询和新增,但是不能执行更新和删除细颗粒度的:数据级别的权限,例如张三只能操作商品信息中类型为电器的商品
数据库设计
根据用户的权限在网页中动态生成菜单和按钮
1.登陆时,在判断登陆成功以后将当前用户所拥有的权限菜单查询出来
2.将权限菜单集合保存到Session中
3.在首页发送Ajax请求获取Session中的权限菜单,生成标签
4.登陆成功跳转到首页,在首页中发送ajax请求获取一级菜单和二级菜单的权限集合,使用前端框架生成1级菜单和二级菜单
5.在生成二级菜单时,需要使用超链接访问菜单资源,超链接需要使用菜单的resource字段作为href属性值,href属性值后面需要将当前菜单的id作为地址栏参数
6.在菜单访问的资源网页中,获取地址栏中的菜单编号参数,通过菜单编号,查询当前菜单下该用户的按钮权限,通过前端操作,确定按钮是否需要显示
SpringBoot+SSM+Shiro+Thymeleaf实现权限认证
Shiro
Shiro将目标集中于Shiro开发团队所称的“四大安全基石”-认证(Authentication)、授权(Authorization)、会话管理(Session Management)和加密(Cryptography)
Shiro重要组件
Subject::即"用户",外部应用都是和Subject进行交互的,subject记录了当前操作用户,将用户的概念理解为当前操作的主体,可能是一个通过浏览器请求的用户,也可能是一个运行的程序。 Subject在shiro中是一个接口,接口中定义了很多认证授权相关的方法,外部程序通过subject进行认证授权,而subject是通过SecurityManager安全管理器进行认证授权(Subject相当于SecurityManager的门面)。
SecurityManager:即安全管理器,它是shiro的核心,负责对所有的subject进行安全管理。通过SecurityManager可以完成subject的认证、授权等。
Authentication:是一个对用户进行身份验证(登录)的组件。
Authorization:即授权器,用户通过认证器认证通过,在访问功能时需要通过授权器判断用户是否有此功能的操作权限。就是用来判断是否有权限,授权,本质就是访问控制,控制哪些URL可以访问.
Realm:即领域,用于封装身份认证操作和授权操作,如果用户身份数据在数据库那么realm就需要从数据库获取用户身份信息。
在使用Shiro之前首先要明确的Shiro工作内容,Shiro只负责对用户进行身份认证和权限验证,并不负责权限的管理,也就是说网页中的按钮是否显示、系统中有哪些角色、用户拥有什么角色、每个角色对应的权限有哪些,这些都需要我们自己来实现,换句话说Shiro只能利用现有的数据进行工作,而不能对数据库的数据进行修改。
使用Shiro实现认证流程图
步骤
1.新建SpringBoot项目
2.引入项目依赖
3.在application.properties文件中配置各项信息
4.使用mybatis反向工程生成相关类和映射文件,定义业务层,在业务层中定义方法,业务方法的目的是通过用户名查询用户信息和用户角色信息和角色权限信息,角色信息和权限信息是为了在授权操作时使用,便于操作我们可以在User类中封装Role类型集合,Role类中封装Permission类的集合,然后使用mybatis的一对多关联映射进行查询。
5.新建Realm类封装认证操作和授权操作
1)继承AuthorizingRealm类,重写方法
2)重新的两个方法,doGetAuthenticationInfo方法用于身份认证,doGetAuthorizationInfo方法用于进行授权操作。
3)在身份认证方法中,从参数token中取出用户名调用业务层的方法获取用户对象,如果用户存在则返回用户认证信息,交给Shiro去进行密码比对,如果用户不存在则直接返回null,Shiro将会抛出用户名不存在的异常,查询到用户信息可以将用户信息保存到Session中。
大家这里可能会有个疑惑,UserRealm类上为什么没有添加@Component注解,按照Spring的规则只有将对象交给Spring容器来管理,Spring才会完成依赖注入,这里不添加@Component注解,那注入能成功吗?事实上我们会在另外一个位置用另外一种方式将UserRealm对象交给Spring容器来管理。
6.初始化Shiro,初始化Shiro的Realm、SecurityManager、和过滤器工厂对象。Realm:我们自定义的UserReaml,该对象在此处进行初始化并交给Spring容器来管理SecurityManager:安全管理器,用于管理所有的认证信息。ShiroFilterFactoryBean:过滤器工厂对象,Shiro依赖过滤器多层的过滤器来实现身份认证判断,权限校验等,通过ShiroFilterFactoryBean可以规定过滤器的执行顺序和地址匹配规则。
7.新建控制层UserController,定义方法处理登录请求。
1)封装UsernamePasswordToken对象,该对象用于保存用户登录的用户名和密码
2)使用SecurityUtils.getSubject()获取主体对象,只有在登录成功后才会在Shiro中保存用户主体(登录成功后在Shiro中保存的用户信息)
3)判断主体是否为空,如果不为空说明已经登录过,就不需要执行登录认证,直接转发到首页。
4)判断主体为空,说明当前还未有用户登录,就需要执行登录认证。在执行登录认证的过程中,Shiro会将登录结果以异常的方式抛出,用户名不存在和密码错误都会抛出异常,如果登陆成功将不会抛出异常,我们在控制层中直接返回登录成功后的逻辑视图即可,那么登录失败的响应在何处进行呢?既然是抛出了异常,那么我们完全可以定义一个全局的异常处理器,针对异常种类进行判断,然后来确定响应的逻辑视图信息。
8.编写首页和登录网页,测试登录认证。
认证过程详细执行过程
加密
常规的加密手段有如下几种:对称加密、非对称加密、摘要算法加密
对称加密:对称加密指通过一段密钥可以将原文进行加密得到密文,也可用使用同样的一段密钥将密文解密为原文。常见的对称加密有如下几种方式:DES、IDEA、RC2、RC4、SKIPJACK、RC5、AES
非对称加密:非对称加密的密钥分为公钥和私钥,通过公钥加密的密文可以通过私钥解密为原文,通过私钥加密的密文可以通过公钥解密为原文。非对称加密中的常见加密方式是RSA
摘要算法加密:摘要算法加密的重要特性是不可逆,也就是通过摘要算法对原文进行加密后是无法逆向解密为原文的。摘要算法加密中最主要的两种加密方式为MD5和SHA
添加RememberMe的功能
1.在Shiro配置类中注册Cookie管理器,在Cookie管理器中需要定义Cookie数据的加密密钥以及注册Cookie组件。
2.在Shiro的过滤器中将放行规则由原本的authc修改为user,authc是指只有在用户通过认证的情况下才能访问资源,user是指用户在认证通过或者RememberMe的情况下都能访问资源。
3.在控制层中调用Shiro的登录方法时,封装UsernamePasswordToken类时在构造函数中多传一个boolean数据,true表示开启RememberMe。
Spring-Cloud、搭建Eureka-Server的集群、服务调用、负载均衡、Hystrix 、Zuul服务网关(服务路由、服务聚合、统一鉴权)、服务划分、小工具(Lombok、JPA、mybatis-plus)、Redis(jedis、spring-boot集成redis、使用redis作为缓存组件、分布式session、redis的集群)、RabbitMQ工作模式(分简单、工作、路由、订阅、主题模式)、Mycat(主从分离、分表分库)、Git仓库
spring-cloud
分布式:项目分布在局域网中,互相通过网络通信协调
微服务架构:Spring Cloud 用来构建一个微服务架构的服务框架,提供了开箱即用的微服务架构必须的支持功能
服务发现: 找到你需要调用的服务的地址。称为基础设施
spring-cloud-netflix-eureka-server : 服务发现的注册表的实现
建项目,勾选必要的组件(eureka-server /eureka-client)加注解 @EnableEurekaServer / @EnableEurekaClient
搭建Eureka-Server的集群
高可用 HA high availability
备份: 冷备( 备2起1 切换需要启动,很慢) | 热备(备2起2用1 切换需要时间,慢)
集群:多台服务同时启动同时服务,数据共享
-> 管理: ①谁都不是领导(eureka-server) | ②自主选举领导(zookeeper) | ③设置一个专门的领导(redis 哨兵)
eureka-server集群 ① 互相注册、互相同步数据 => peer-awareness 节点感知(每一个eureka-server本身也是一个eureka-client)
步骤
1. 备份eureka-sever项目
2. 备份application.properties
3. 新建并配置application.yml
4运行 : a. mvn package -Dmaven.test.skip=true 打包b. java -jar target/xxx.jar --spring.profiles.active=config1 java -jar target/my-eureka-server-0.0.1-SNAPSHOT.jar --spring.profiles.active=config0 java -jar target/my-eureka-server-0.0.1-SNAPSHOT.jar --spring.profiles.active=config1 java -jar target/my-eureka-server-0.0.1-SNAPSHOT.jar --spring.profiles.active=config2
5.补救
修改hosts : C:\Windows\System32\drivers\etc\hosts
修改 eureka-server的hostname,每个profile都有独立的hostname
修改product的注册地址为 eureka.client.service-url.defaultZone=http://peer0:9000/eureka
为了解决某个服务挂掉的问题,所有的服务注册表地址都是3个url: eureka.client.service-url.defaultZone=http://peer0:9000/eureka,http://peer1:9001/eureka,http://peer2:9002/eureka
修改配置,使得服务器对服务的生存敏感
服务调用
RestTemplate类OpenFeign 声明式Rest请求客户端
步骤
1. 添加依赖
2.application类上加@EnableFeignClients
3.编写feign的client
4.调用
作用:1. 熔断,停止调用2. 恢复调用
eruka与nacos区别 1、相同点(1)、都支持服务注册和服务拉取。(2)、都支持服务提供者心跳方式做健康检测。2、不同点(1)、Eureka对服务提供者是每30秒一次心跳检测来检测服务健康,Nacos则把服务分为临时服务和非临时服务,对于临时服务,Nacos采取策略与Eureka相同,对于非临时服务,Nacos不会对其进行心跳检测,而是会主动调用该服务查看是否正常,若不正常会把该服务标记为不健康,不会把该服务从服务列表中去掉。(2)、Eureka会定时向注册中心定时拉去服务,如果不主动拉去服务,注册中心不会主动推送。Nacos中注册中心会定时向消费者主动推送信息 ,这样就会保持数据的准时性。(3)、Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP方式。 (4)、eureka是使用定时发送和服务进行联系,属于短连接;nacos使用的是netty和服务直接进行连接,属于长连接 原文链接:https://blog.csdn.net/jackenwjk/article/details/131755118
分布式事务实现方式:1、2pc(两段式提交)2、3pc(三段式提交)3、TCC(Try、Confirm、Cancel)4、最大努力通知5、XA6、本地消息表(ebay研发出的)7、半消息/最终一致性(RocketMQ)https://blog.csdn.net/qq_36963950/article/details/108909780
负载均衡:将业务负载压力均衡的分配给不同的服务器来处理的过程
集中式的负载均衡: 使用一个独立的负载均衡器,请求通过负载均衡器进行分发,负载均衡器的压力很大,需要额外部署大量的负载均衡器,维护成本高
客户端的负载均衡:在消费者一端,加入负载均衡组件实现逻辑,不需要额外的负载均衡器
spring cloud netflix 提供的负载均衡组件名为ribbon
案例:mall -> product1 product2 product3
product ==>
1. 修改product的配置,分别加入三个profile,对应三个端口
2.编写一个controller,返回当前的端口信息
3.终端分别运行三个product
mall==>
1.添加负载均衡功能到restTemplate
2.创建controller,调用服务集群
3.添加负责均衡的策略,application类中加入全局随机规则
4.为某个服务定制规则,在application.properties中添加配置
feign 默认集成ra自带负载均衡功能
规则:- 轮询(Round Robin) : 将请求均匀分配给后端服务器 0-1-2-0-1-2...- 加权轮询(Weighted Round Robin) : 根据权重将请求按比例分配给后端服务器- 随机Random:随机分配请求- IP哈希 IP Hash:可以对请求IP或者目标服务器IP进行hash算法计算,得出hash值,每次值相同的发送到同一个服务器,保证了同一个IP的请求由同一台服务器处理- 最小连接数 Least Connection:请求分配给活动连接数最小的后端服务器,需要维护后端服务器列表- 最短响应时间(Least Response Time):请求分配给平均响应时间最短的后端服务器,需要记录服务器响应时间
Hystrix
- 服务降级- 熔断- 监控
服务降级: 当某个服务不可用时,调用备用的服务接口
操作步骤:1. 添加hystrix的依赖2. Application类上加入注解@EnableHystrix 启动hystrix2. 为服务加上降级回退的注解及方法
某个feign客户端服务降级1. 创建一个类实现feignClient的业务接口2.feignclient指定要使用回退3.添加application.properties的配置,启用feign的hystrix支持 `feign.hystrix.enabled=true`4.正常的服务调用
熔断器(漏电保护器)
流程
1. 请求成功 或者 失败未达到熔断器的阈值,保持关闭
2. 请求失败次数达到了熔断器的阈值,打开熔断器,在熔断器开启的时间窗口内,请求不会下发给服务提供者
3. 当熔断器的时间窗口结束时,熔断器处于半开状态,尝试性的发送一笔请求给底层服务,如果服务调用成功,关闭熔断器。如果服务调用失败,继续保持熔断器的打开状态,直到下一个时间窗口结束
操作步骤
1. 添加spring-boot-actuator,用于输出熔断器状态
2.反复调用错误的服务,可以使熔断器开启默认熔断器被触发的规则默认为10s内20次失败,所以当我们关闭服务提供者之后,频繁访问达到阈值,熔断器就会被打开,当后续请求成功时则会自动关闭【当服务调用失败时,熔断器不会立即打开,但是服务降级会实时进行】
3.开启底层服务,使得调用成功,熔断器即关闭
熔断器打开的窗口时间配置项: hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds=5000所有配置项: https://github.com/Netflix/Hystrix/wiki/Configuration
监控,数据统计(dashboard)
操作步骤:
1.添加依赖
2.加注解,启动hystrix的监控面板 @EnableHystrixDashboard
3.打开http://localhost:10002/hystrix 填入 http://localhost:10002/actuator/hystrix.stream 看到实时的调用情况统计图
服务网关是netflix的Zuul
使用场景:l验证与安全: 识别面向各类资源的验证要求并拒绝那些与要求不符的请求。l审查与监控: 在边缘位置追踪有意义数据及统计结果,从而为我们带来准确的生产状态结论。l动态路由: 以动态方式根据需要将请求路由至不同后端集群处。l压力测试: 逐渐增加指向集群的负载流量,从而计算性能水平。l负载分配: 为每一种负载类型分配对应容量,并弃用超出限定值的请求。l静态响应处理: 在边缘位置直接建立部分响应,从而避免其流入内部集群。
操作步骤:1. 创建项目,选择 spring-web\Eureka-client\zuul2. 在Application类上加入注解@EnableZuulProxy - @EnableZuulProxy(在server基础上实现了一些常用的功能,开箱即用) - @EnableZuulServer(单纯的server)3. 添加必要的配置
服务路由4. 创建user项目,选择eureka-client、web,编写服务 1. @EnableEurekaClient 2. 服务的必要配置 3. 编写controller5. 在网关应用的配置文件中配置路由6. 请求 http://localhost:9100/api/user/findUserById?userId=1 ,验证网关是否转发请求给对应的服务
服务聚合需求:用户最近10浏览的商品详情</del>user 提供的 最近浏览的商品id列表,没有详情 提供接口product 提供了查询商品详情的接口 提供接口在网关中,需要调用对应的接口来聚合成一个合用的接口(定义feignclient、调用服务、聚合)
### 统一鉴权Zuul 1.0 架构,围绕filter实现的。Zuul Servlet 接受请求Zuul Filter Runner filter的运行器Zuul Filters 过滤器 负责实现具体的业务功能,比如,鉴权,数据统计...Zuul Filter Loader 过滤器加载器,加载过滤器。zuul定时扫描某些指定的文件夹,动态的加载过滤器文件
Filter分类- PRE: 路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等- ROUTING: 将请求路由到微服务- POST: 微服务调用以后执行,可用来为响应添加标准的 HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等- ERROR:在其他阶段发生错误时执行该过滤器
操作步骤1. 创建AuthFilter继承zuul的ZuulFilter,@Component注解2. 实现四个方法 - filterType 过滤器类型 - filterOrder 执行顺序 - shouldFilter 过滤条件 - run 过滤逻辑
网关服务回退
服务划分
单体应用 =拆=> 微服务架构
服务分类:- UI服务 , 跟用户有交互的,带页面的系统- 聚合服务,将单纯服务合并用以支撑具体的业务场景- 单纯服务
小工具
Lombok自动代码生成1. 安装插件2. 添加依赖
JPAJava-persistence-api : java持久化API =》 技术委员会 Gavin King (hibernate的作者)hibernate 实现spring 封装了jpa spring-data-jpa spring-data项目是对数据相关项目的封装
mybatis-plushttps://mp.baomidou.com/
Redis:是一个开源的内存数据结构存储,可以用作数据库,缓存,消息中间件。支持ashes, lists, sets, sorted sets with range queries, bitmaps, hyperloglogs, geospatial indexes with radius queries and streams等数据结构
Redis是一款NoSQL数据库
Redis是一个内存数据库,Key-Value形式存储数据
Redis拥有丰富的特性- 性能极高:Redis能读的速度是110000次/s,写的速度是81000次/s - 丰富的数据类型 – Redis支持二进制的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。- 原子 – Redis的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来。- 丰富的特性 – Redis还支持 publish/subscribe, 通知, key 过期等等特性。- 支持持久化
持久化
RDB 将内存中的数据间隔一段时间备份到一个文件中。优势:单个文件,便于灾难的恢复和重启,性能更佳 劣势:可能数据丢失
AOF 对每一条修改数据的操作记录日志,当重启时会根据日志重建数据库 优势:数据不会丢失 劣势:重建数据库的时间较长
两种方式都不启用或者都启用,重启时会使用rdb的文件来进行恢复
key操作:keys 查看rename 重命名type 类型del 删除expire 过期 set {key} {value} EX {秒数}
String操作:set、get
Hash类型数据相关命令:hset、hmset、hget、hmget、hgetall
List类型数据相关命令:lpush、rpush、lrange
Set类型数据相关命令:sadd、smembers
SortSet类型数据相关命令:zadd、zrange
jedis
1、建项目 maven quickstart 加依赖
2、代码
spring-boot集成redis
1、建spring-boot项目,选择 spring-data-redis \ web
2、添加配置,redis连接配置
3、注入 StringRedisTemplate进行redis操作
使用redis作为缓存组件
1、添加依赖
2、添加数据源及缓存配置
3、启用缓存 `@EnableCaching`
4、缓存使用注解- @Cacheable 新增- @CachePut 更新- @CacheEvict 清除
分布式session
1、加依赖
2、yml
3、编写测试代码
4、为了测试,将端口改成随机端口
5、运行启动一台,通过打包(mvn clean package -Dmaven.test.skip=true),cmd运行另外一台。在某一台调用set,从另一台调用get,能取出,表示session已经共享了cd targetjava -jar springbootredis-0.0.1-SNAPSHOT.jar
Redis的集群
目的:高可用;提高性能;
主从结构主:从,存在的缺陷就是如果主机关了,从机无法继续服务,需要重新选择一台从机作为主机哨兵,负责监控主机是否正常,如果主机已挂,选择从机并重新配置集群
安装redis:- 调整网络 vi /etc/sysconfig/network-scripts/ifcfg-eth0- 修改 IPADDR 和 GATEWAY,改成你自己的网络- service network restart- 上传压缩包到虚拟机- tar -zxf xxx.tar - cd 文件夹- make 安装- 将redis-server 和 redis-cli 设置为全局命令[ln -s /redis的安装目录/redis-4.0.8/src/redis-server /usr/bin/redis-server][ln -s /redis的安装目录/redis-4.0.8/src/redis-cli /usr/bin/redis-cli]- cd ~; mkdir redis30- cp redis安装目录/redis.conf ~/redis30
配置sentinel集群- 配置文件位于 /etc/redis.conf- 复制多份配置文件,建议以端口取名,我们将6380看做master,6381 和 6382 看做slave- 修改所有master和slave的配置文件,改端口和pid- 修改2个salve配置文件,指定master为6380- 启动 master(6380) salve1(6377) salve2(6378),命令为 `redis-server {redisxx.conf} &` 加入&符号是为了后台运行,日志在 /var/log/redis/redis.log- redis-cli -p {端口} 登录某个redis实例,然后输入 `info replication` 查看 在集群中的角色 以上步骤完成了主从配置,接下来配置sentinel,配置文件位于 `/etc/redis-sentinel.conf`- 添加监听的master地址,后面的1是指多少台sentinel确认为master不可用后才切换master,我们只有1台就填1- 启动sentinel `redis-sentinel /etc/redis-sentinel.conf &` - 查看日志 `tailf /var/log/redis/sentinel.log`- 此时部署已经完成,接下来测试是否会自动failover- 关闭master 6380,然后查看sentinel的日志,观察到会自动将master切换为另外一台- 此时6378机器被升级为了master,可以登录6378之后查看info replication 可以看到role已经变为了master
redis缓存实现和数据库一致方式:1.先删除缓存,再更新数据库;2.先更新数据库,再删除缓存;3.给所有的缓存一个失效期;4.加锁,使线程顺序执行;5.采用双删(先删除缓存,再更新数据库,当更新数据后休眠一段时间再删除一次缓存);6.异步更新缓存(基于订阅binlog的同步机制) https://blog.csdn.net/csdn_life18/article/details/129555077
Redis三大缓存问题及解决方法(雪崩、击穿、穿透)https://blog.csdn.net/m0_63512120/article/details/136151230
缓存雪崩:当大量缓存数据在同一时间过期(失效)或者Redis故障宕机时,如果此时有大量的用户请求,都无法在Redis中处理,于是全部请求都直接访问数据库,从而导致数据库的压力骤增,严重的会造成数据库宕机,从而形成一系列连锁反应,造成整个系统崩溃
解决方法:1.设置过期时间:大量的数据设置不同的过期时间,避免同时过期。也可以选择给过期时间加上一个随机数,这样就可以不会在同一时间过期。2.互斥锁:发现访问的数据不在Redis中,加上互斥锁,保证同一时间内只有一个请求来构建缓存( 从数据库读取数据,再将数据更新到 Redis 里),当缓存构建完成后,再释放锁。未能获取互斥锁的请求,要么等待锁释放后重新读取缓存,要么就返回空值或者默认值
缓存击穿:如果缓存中某个热点数据过期了,此时大量的请求访问了该热点数据,就无法从缓存中读取,直接访问数据库,数据库很容易被高并发的请求冲垮,这就是缓存击穿问题。
解决方法:1.互斥锁方案,保证同一时间只有一个业务线程更新缓存,未能获取互斥锁的请求,要么等待锁释放后重新读取缓存,要么就返回空值或者默认值。2.不给热点数据设置过期时间,由后台异步更新缓存,或者在热点数据准备要过期前,提前通知后台线程更新缓存以及重新设置过期时间;
缓存穿透:当用户访问的数据,既不在缓存中,也不在数据库中,导致请求在访问缓存时,发现缓存缺失,再去访问数据库时,发现数据库中也没有要访问的数据。那么此时大量的请求都落在数据库中,导致数据库压力骤增,这就是缓存穿透问题。
解决办法:1.限制非法请求(进行参数校验,对于不合法的参数请求直接抛出异常返回给客户端。)2.缓存空值或者默认值(当发现缓存穿透的现象时,可以针对查询的数据,在缓存中设置一个空值或者默认值,这样后续请求就可以从缓存中读取到空值或者默认值,返回给应用,使其不会继续查询数据库)3.使用布隆过滤器(我们在写入数据库数据时,使用布隆过滤器做个标记,然后用户发起请求后,判断请求值是否存在于布隆过滤器,如果不存在那这个请求直接无效,如果存在那就在缓存中查找对应的数据。这样当出现大量请求的时候,会先查询布隆过滤器和Redis,不会对数据库造成压力。)
实现分布式锁的三种方式:基于数据库(基于主键id和唯一索引)的分布式锁;基于Redis的分布式锁;基于Zookeeper的分布式锁https://blog.csdn.net/m0_64210833/article/details/126203287
rabbitmq:消息队列 message queuequeue: 先进先出 FIFOmessage: 消息 存储信息1)应用解耦 + 异步调用 : 服务调用 vs 消息队列: 同步 ==> 异步 - 下单付款成功(同步:资金修改,订单状态的修改) --> 短信 | 站内消息 | 邮件 | 等级升级2)流量削峰: - 秒杀:1万用户 ,请求秒杀接口 消息 存储信息
Erlang
1、先安装erlang并配置环境变量
2、安装rabbitmq并配置环境变量
3、激活rabbitmq_management
4、启动RabbitMQ服务
访问地址 http://localhost:15672 用户名/密码 : guest / guest
核心概念
Message 消息,消息是不具名的,它由消息头和消息体组成。消息体是不透明的,而消息头则由一系列的可选属性组成,这些属性包括routing-key(路由键)、priority(相对于其他消息的优先权)、delivery-mode(指出该消息可能需要持久性存储)等。
Publisher 消息的生产者,也是一个向交换器发布消息的客户端应用程序。
Exchange 交换器,用来接收生产者发送的消息并将这些消息路由给服务器中的队列。
Binding 绑定,用于消息队列和交换器之间的关联。一个绑定就是基于路由键将交换器和消息队列连接起来的路由规则,所以可以将交换器理解成一个由绑定构成的路由表。
Queue 消息队列,用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息可投入一个或多个队列。消息一直在队列里面,等待消费者连接到这个队列将其取走。
Connection 网络连接,比如一个TCP连接。
Channel 信道,多路复用连接中的一条独立的双向数据流通道。信道是建立在真实的TCP连接内地虚拟连接,AMQP 命令都是通过信道发出去的,不管是发布消息、订阅队列还是接收消息,这些动作都是通过信道完成。因为对于操作系统来说建立和销毁 TCP 都是非常昂贵的开销,所以引入了信道的概念,以复用一条 TCP 连接。
Consumer 消息的消费者,表示一个从消息队列中取得消息的客户端应用程序。
Virtual Host 虚拟主机,表示一批交换器、消息队列和相关对象。虚拟主机是共享相同的身份认证和加密环境的独立服务器域。每个 vhost 本质上就是一个 mini 版的 RabbitMQ 服务器,拥有自己的队列、交换器、绑定和权限机制。vhost 是 AMQP 概念的基础,必须在连接时指定,RabbitMQ 默认的 vhost 是 / 。
Broker 表示消息队列服务器实体。
RabbitMQ工作模式(分简单、工作、路由、订阅、主题模式)
Mycat:传统的数据存储 关系型数据库 : 分库分表(数据超过500w分表,超过5000w分库)
mysql压缩版安装步骤主1: 3406 主2:3506从1: 3407 从2:3507
主机
1. 解压
2. 用记事本修改配置my-default.ini 添加
#log_binserver_id=1log_bin=master-binlog_bin-index=master-bin.index...#server_id=...basedir = "D:\app\mycat\mysql-1"datadir = "D:\app\mycat\mysql-1\data"port = 3406
3.安装成windows服务,在bin目录下打开cmd,输入以下命令:mysqld install mysql1 --defaults-file=D:\app\mycat\mysql-1\my-default.ini
4.启动服务 `net start mysql1`
从机
1.解压
2.用记事本修改配置
[mysqld]# log_binserver_id=2relay-log-index=slave-relay-bin.indexrelay-log=slave-relay-bin....# server_id = .....basedir = "D:\app\mycat\mysql-2"datadir = "D:\app\mycat\mysql-2\data"port = 3407
3.安装成windows服务,在bin目录下打开cmd,输入以下命令:mysqld install mysql2 --defaults-file=D:\app\mycat\mysql-2\my-default.ini
4.启动服务 `net start mysql2`
主从关联
1.在主机上执行 `show master status;`master-bin.000001 120
2.在从机上执行
# 将从机连上主机CHANGE MASTER TOMASTER_HOST='localhost',MASTER_USER='root',MASTER_PASSWORD='',master_port=3406,MASTER_LOG_FILE='master-bin.000001',MASTER_LOG_POS=120;# 重启slave服务stop slave;start slave;# 查看slave状态show slave status;
Mycat安装及配置- 解压- 修改配置 warpper.conf- bin目录下,运行cmd,执行命令 `mycat install` 安装服务- `mycat console | start` 启动mycat- 修改配置,重启使配置生效- 连接mycat 用户名和密码为server.xml配置 端口 8066
分表:逻辑表 => 三个物理表
1. 建表
2.修改配置rule.xml 增加规则配置;schema.xml 增加表配置;重启mycat;测试,编码,配置数据库连接到mycat,插入数据
读写分离:(读写比例: 8:2)一个库负责写入,另一个库负责读取为了提升数据库集群的性能的常用手段使用mysql数据库的主从来实现数据同步,分离读和写mycat支持读写分离
分库:- 准备好两组主从机(主1:3406、从1:3407、主2:3506、从2:3507)- 在两个主机上创建表,从机自动同步- 配置schema,加入两个主机- 配置表- 测试插入
水平分表(解决数据量大)和垂直分表(解决Mysql的InnoDB引擎1.0压缩页导致跨页而需要页多行)原文链接:https://blog.csdn.net/MAKABAKA__ZJC/article/details/124647600
水平分表就是指以行为单位对数据进行拆分,一般意义上的分库分表指的就是水平分表。分表之后,所有表的结构都是一样的。
一般可以有范围法和hash法来进行水平分表。假设现在有30万行数据,需要对它们进行水平分表:范围法很好理解,可以让第1-100000行数据存放在表1,第100001-200000行数据存放在表2,第200001-300000行数据存放在表3,就完成了水平分表。hash法也不难理解,就是选择一个合适的hash函数,比如说使用取模操作(%),把%3结果为0的数据存放在表1,结果为1的存放在表2,结果为2的存放在表3即可。
垂直分表就是把一张表按列分为多张表,多张表通过主键进行关联,从而组成完整的数据。分表之后,每张表的结构都不相同。
一般来说,垂直分表并不会把列平分到2个表中,而是会将一些重要的字段单独剥离成小表,把剩余的不太重要的字段放在大表中。比如,把查询、排序时需要的字段,高频访问的小字段放在小表而把低频访问字段以及一些大字段放在大表中
MySQL:主从同步(https://blog.csdn.net/ql_gome/article/details/132427107)
Git仓库:版本管理工具: SVN 、Git
功能:- 协作:同一个项目下编写代码(增删改查)- 版本管理: version control 每次提交就会形成一个版本(代码变更、作者、备注、日期)- 追溯:提交日志- 备份
版本管理: - 集中式 SVN- 分布式 Git
使用:
下载安装 : https://git-scm.com/download/win
参考书籍:https://git-scm.com/book/zh/v2
公网服务器:- 全球通用:https://github.com- 国内通用:https://gitee.com/- 自建: https://gogs.io/ 、 https://about.gitlab.com/
注册码云账号 (码云git教程:https://gitee.com/help/articles/4105)
创建仓库
常用命令 - git clone 仓库地址 首次下载整个仓库- git remote -v 查看本地仓库地址- 创建项目(指定地址是仓库所在的本地文件夹)- git status 查看当前文件夹下的文件状态- git add 将文件添加到git的监控下 `git add *` 添加所有文件- git commit 提交到本地仓库 `-m` 参数 提供提交信息- git log 查看提交日志- git push 推送到远端的服务器- git clone 首次下载- git pull 后续更新
代码合并(礼仪:先商量,再动手,尊重彼此)- 用你的覆盖别人的 Accept Yours- 用别人的覆盖你的 Accept Theirs- 商量着合并 Merge
idea快捷键- ctrl+k 提交 git commit -m- ctrl+shift+k 推送 git push- ctrl+t 更新 git pull- 合并
提交的规范- 每天提交3次以上- 提交前先更新- 提交标准 - 编译不报错 - 可以正常运行,不一定要完成功能 - 不影响其他人的编码工作
每个项目一个仓库- 前台- 后台管理系统- app- 分布式多个