Java复习面试题:
Java核心基础:
1.JDK和JRE的区别:
JDK指java development kit,java开发工具,包括java开发环境和java运行环境。JDK中包含jre,java的字节编译器(javac),和java调试开发工具(jconsole等),以及java编写代码所需的文档和dome例子。JDK提供java的开发环境和运行环境,完成java的字节码的编译与运行。JDK是所有java应用程序的基础。
JRE指java runtime environment,java运行环境,用来运行java应用程序,jre包含了java虚拟机,java基础类库。他提供java的运行环境,但只能运行字节码文件,所有javaclass文件都在lib下,打包成jar。
2.==和equals的区别:
==对基本数据(值类型,字符类型,布尔类型)类型使用值比较,对于引用类型(类,接口,数组)比较的是引用。
equals方法在不重写时使用==对引用进行比较,即他的默认比较类型是引用类型,如果某些类对equals方法比较,则会使用引用类型和值类型的比较方法,首先对数据的引用进行比较,在对其值进行比较。可以进入String、Integer底层逻辑查看。
String类的equals方法:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
普通类的euqals方法:
public boolean equals(Object obj) {
return (this == obj);
}
3.equals方法和hashcode方法:
对象的hashcode方法返回的是数据在内存中的地址,为int类型,在java中如果两个对象的hashcode方法的值不相同则两个对象确实不相等,如果两个对象的hashcode值相同,两个对象不一定相同。
如果两个对象的equals方法的相同则两个对象确实相同。
hashcode方法的目的就是提高效率。在编写程序时通常采用hashcode+equals方法,先使用hashcode方法在调用equals方法。
4.final在java中的作用:
final修饰的类无法被继承,不存在重载重写,不存在多态。
final修饰的方法无法重写。
final修饰的变量交变常量,在使用变常量时必须初始化,在定义后无法改变。
5.Java中Math.round(-1.5)的值为多少:
round取四舍五入,当小数值为大于等于5时,向上取整。
6.java中基本数据类型有哪些?
基本数据类型:
数值型:byte,int,long,short,float,double
字符型:char
布尔型:boolen
引用类型:类(String,Enum),接口,数组
7.java中的String、StringBuilder、StringBuffer的区别:
在java中String、StringBuilder、StringBuffer的底层都使用采用字符数组。String的长度是固定的,在初始化时必须为对象定义长度,在后续的修改时,会重新定义一个新的内存空间。
StringBulider和StringBuffer实例化时,默认16个字符的长度,根据实际需要保存的数据长度,对内存进行修改。
StringBuffer相当于开辟一个字符缓冲区,大小可以根据实际更改,但是StringBuffer的所有方法使用synchronized关键字修饰,在多线程系统中保证数据同步,线程是安全的,但效率比较低。
StringBuilder没有实现同步,是线程不安全的,但效率高。在多线程下推荐使用StringBuffer,线程安全,在单线程系统中推荐使用StringBuilder。
(后期增加内容)有没有两个不相同的对象有相同的hashcode值:
hashCode的底层采用哈希算法,哈希表采用直接寻址和链式寻址的方法,将数据计算哈希值,分组放在单链表的末端,数据多了就会产生不同对象有相同哈希值的结果,所以要使用equals方法对hashcode()进行再判断。
8.字符串变量引用一个对象和引用一串字符一样吗:
字符串引用一串字符,会将数据放在字符串常量池中。
而引用对象时会将数据放在堆内存中。
(后期增加内容)深拷贝与浅拷贝:
拷贝是将对象的所有属性和方法拷贝到另一个同一类型的对象中
浅拷贝对基本数据类型只进行值传递,String类型没有引用类型变量,只有值传递。
浅拷贝对于引用类型,只传递地址(引用),不会开辟新的存储空间。
深拷贝会拷贝对象所有引用类型,并为其开辟新的存储空间。
9.字符串反转的方法:(三种)
第一种:
//使用CharAt方法对单个字符提取出,并重新放入新的字符串
// first plan
public static String reverse1(String s){
String str="";
for(int i=0;i<s.length();i++){
str=s.charAt(i)+str;
}
return str;
}
// second plan
public static String reverse2(String s){
char[] chArr=s.toCharArray();
String str="";
for(int i=s.length()-1;i>=0;i--){
str+=chArr[i];
}
return str;
}
第二种:
//使用StringBuilder或StringBuffer的reverse方法。
// third plan
public static String reverse3(String s){
StringBuffer sb = new StringBuffer(s);
StringBuffer reverse = sb.reverse();
return reverse.toString();
}
第三种:
//使用递归调用的折半思想。
// forth plan
public static String reverse4(String s){
int mid = s.length() / 2;
if(s.length()==1){
return s;
}
String left=s.substring(0,mid);
// 注意subString的用法,包括前面不包括后面
String right=s.substring(mid,s.length());
s=reverse4(right)+reverse4(left);
return s;
}
10.String的常用方法:
int length();输出数组的长度
boolen equals();输出是否相等,重写Object的equals方法。
boolen equalsIgnoreCase();判断是否相同,忽略大小
int compareTo();比较两个字符串,如果长度相同返回第一个字符不同时的ASCII差值,如果长度不同返回长度差值。
boolen contains();判断是否包含指定字符串。
boolen startWith():判断是否以指定内容开始
boolen endWith();判断是否以指定内容结束
int indexof();输出字符串、字符、整数在字符串指定、非指定开始位置首次出现的索引。
int lastIndexOf();返回指定字符串在该字符串中最后出现的索引。
char charAt();输出索引所在字符串的字符
String subString();提取指定索引的字符串,默认到最后一个字符。
char[] toCharArray();将指定字符串转换为字符数组。
byte[] getBytes();将指定字符串转为字节数组。
String replace();替换指定内容为指定字符串
String trim();去除前后端的空格;
String split();根据指定内容,分割字符串
boolen isEmpty();判断指定字符串是否为空。
String toLowerCase();转换为小写
String toUpperCase();转换为大写
(后续增加内容)类实例化的顺序:
第一次实例化需要先类加载实例化,加载、验证、准备、解析,初始化。
父类的静态代码块、静态属性(优先级相同,按照代码先后执行)
子类的静态代码块、静态属性(优先级相同,按照代码先后执行)
父类的普通代码块、普通属性(优先级相同,按照代码执行)
父类构造函数
子类的普通代码块、普通属性(优先级相同,按照代码执行)
子类构造函数
(后续增加内容)创建对象的方法:
new 构造函数
People people=new People();
clone
//定义一个类,实现Cloneable接口
public class Cat implements Serializable,Cloneable{
private String name;
public Cat() {
}
public Cat(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
//创建新的类
public class MainClass {
public static void main(String[] args){
try {
Cat clone =(Cat) cat.clone();
clone.setName("mao");
System.out.println(clone);
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
反射机制
// 反射机制获取新对象
// .class获取class结构
// Class<People> peopleClass = People.class;
// 使用静态方法forName
// Class peopleClass = Class.forName("People");
// 使用.getClass()
// People people1 = new People();
// Class peopleClass = people1.getClass();
// .ClassLoader
// People people1 = new People();
// ClassLoader classLoader = people1.getClass().getClassLoader();
// Class peopleClass = classLoader.loadClass("People");
People people=null;
try {
people =(People) peopleClass.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
System.out.println(people);
序列化、反序列化
//定义一个类,实现Cloneable接口
public class Cat implements Serializable,Cloneable{
private String name;
public Cat() {
}
public Cat(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
// 序列化
Cat cat = new Cat();
ObjectOutputStream catStream = new ObjectOutputStream(new FileOutputStream("C:\\Users\\13064\\Desktop\\cat.txt"));
catStream.writeObject(cat);
catStream.flush();
catStream.close();
// 反序列化
ObjectInputStream catData = new ObjectInputStream(new FileInputStream("C:\\Users\\13064\\Desktop\\cat.txt"));
Object o = catData.readObject();
System.out.println(o);
catData.close();
11.抽象类与抽象方法:
抽象类使用abstract修饰,不能直接实例化,需要先继承。抽象类不一定需要抽象方法,但是抽象方法一定定义在抽象类中。
12.普通类和抽象类的区别:
普通类可以直接实例化,也可以直接调用,但是不包含抽象方法。
抽象类可以定义抽象方法,但是不能直接实例化,需要先继承。
13.抽象类可以使用final修饰吗?
不能,final类是无法被继承,不存在重载和重写。而抽象类不能直接实例化,必须先继承,final与abstract类相互矛盾,而且编译器也会报错,所以不能用final修饰抽象类。
14.抽象类与接口的区别:
抽象类由abstract修饰,接口类由interface修饰
抽象类内部可以定义变量,方法,static、final修饰的变量和方法,抽象类与普通类的区别在抽象类可以定义抽象方法,接口内部定义的变量必须为public static final变量,方法必为public abstract方法。Java8中可以在接口中定义静态方法。
抽象类可以定义构造器,接口不能定义构造器。
类可以继承一个抽象类,但是能实现多个接口。
抽象类中可以定义main方法,接口中不能定义main方法。
抽象类可以继承抽象类,可以继承接口,但是接口不能继承抽象类。
15.重载和重写的区别:
重载与重写都是多态的实现方法。
重载是指子类继承父类的方法,方法名、方法体相同,方法参数列表参数类型,参数个数,参数顺序不同,对方法的返回值没有要求相同,但不能通过返回值判断重载。
重写是指字类继承父类的方法后修改方法体内容,方法的参数、方法名相同,方法返回值相同或兼容,作用域修饰符不能大于父类作用域,抛出的异常不能比父类抛出异常更广泛。
16.io流的分类:
根据流的方向分为输入流和输出流,以内存为参照物。
根据流的数据单元分为字节流(inputStream、outputStream)和字符流(reader,writer)。字节流操作的数据为8位字节,而字符流操作的是16位的字符。字节流一般用来处理图像/视频/音频等媒体文件,字符流用来处理文本文件。简单来说,字节流可以处理所有数据,而字符流只能处理文本文件。为了防止在传输汉字时,字节流会出现乱码,使用字符流。
根据功能分为节点流和处理流。所有直接对数据进行读写的流类(fileInputStream,fileOutputStream;inputStreamReader,outputStreamWriter;FileWriter,FileReader),处理流对已存在的流链接和封装,通过对数据处理为程序提供功能强大且灵活的读写(BufferedInputStream,BufferedOutStream;BufferedReader,BufferedWriter)。
(后续添加内容)序列化和反序列化:
序列化是指将java对象及其属性和方法全部转化为可以进行存储和传输的字节流状态,即对象持久化的过程。
反序列化是指根据保存的序列化的信息,重新构造为对象。
代码实现:
//定义一个类实现serializable接口
public class Cat implements Serializable{
private String name;
public Cat(String name){
this.name=name;
}
public Cat(){
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
'}';
}
}
//使用ObjectInputStream和ObjectOutputStream的readObject,writeObject方法序列化和反序列化
import java.io.*;
public class MainClass {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 序列化
Cat cat = new Cat();
ObjectOutputStream catStream = new ObjectOutputStream(new FileOutputStream("C:\\Users\\13064\\Desktop\\cat.txt"));
catStream.writeObject(cat);
catStream.flush();
catStream.close();
// 反序列化
ObjectInputStream catData = new ObjectInputStream(new FileInputStream("C:\\Users\\13064\\Desktop\\cat.txt"));
Object o = catData.readObject();
System.out.println(o);
catData.close();
}
}
17.BIO,NIO,AIO有什么区别:
BIO,block io就是传统的io,同步阻塞式io,它的的特点是模式简单使用方便,但是并发差。
NIO,new io就是新型同步非阻塞式IO,与传统IO不同,输入端和输出端之间通过一个channel(通道)连接,实现了多路复用。
AIO,Aysnchronous IO是NIO的升级,也叫NIO2,实现了异步非阻塞io,它是基于事件和回调机制。
18.File类常用的方法:
exists();检查文件路径是否存在。
createFile();创建文件
createDirectory();创建文件夹
delete();删除一个文件或目录
copy();复制文件
move();移动文件
size();查看文件个数
read();读取文件
write();写入文件
19.集合:
(1)Collection与Conllections的区别:
Collection是一个集合的接口,它提供对数据集合进行操作的通用方法,它是List,set,queue集合的父类接口。
Collections是封装类,其中定义了对集合操作的某些静态方法,不能实例化,比如:提供了集合的排序方法:Collections.sort(list);
(2)Set、List、Map的区别:
List集合,有序可重复
ArrayList 底层为数组,为有序可重复,线程不安全,查询快但增删改慢
LinkedList 底层为链表,为有序可重复,线程不安全,查询慢,增删改快
Vector底层为数组,为有序可重复,线程安全,查询快,增删改慢
Set集合
HashSet 底层为哈希表,为无序不重复,依靠hashcode和equals方法保持唯一性
LinkedHashSet底层为链表+哈希表,为有序不重复,依靠链表保持有序,依靠哈希表保持不重复。
TreeSet 底层为红黑树,为有序不重复,依靠比较器是否返回为0保持唯一性,依靠红黑树的自然排序保持有序。
Map集合
Hashtable底层为哈希表,是无序的,同步的线程安全但速度慢,
HashMap底层为哈希表+链表/红黑树,无序的,在1.8前采用数组+单向链表,之后采用数组+单向链表/红黑树,在链表长度超过8时采用红黑树。线程不安全但速度高。
LinkedHashMap 底层为链表+哈希表,有序,线程不安全,速度高。
TreeMap 底层为红黑树,有序,线程不安全。
ConcurrentHashMap 底层与HashMap相同,但与jdb7版本不同,没用采用segment分段锁为哈希表加表锁,采用synchronized和CAS无锁操作,实现线程安全又保证并发访问不冲突。
(3)Hashtable和HashMap的区别:
底层逻辑:hashtable的底层采用哈希表,无序;HashMap底层采用哈希表+数组+单链表+红黑树,在链表长度超过一定长度时会自动转为红黑树。
线程:Hashtable线程安全,hashMap线程不安全,但效率高
内容:Hashtable不允许null值,HashMap键值都允许
(4)HashMap和TreeMap选择:
HashMap底层为哈希表+数组+链表+红黑树,TreeMap的底层为红黑树,考虑数据操作HashMap的插入删除效率高,但如果对key集合进行遍历使用TreeMap。
(5)HashMap的实现原理:
在jdk1.8版本下,底层使用数组+链表+红黑树。使用put(key,value)向map中加入数据,利用get(key)从集合中获取数据,当存入数据输入数据时,根据数据通过哈希函数计算哈希值将数据放入哈希表中,如果在计算哈希值时发生冲突,使用链表将数据连接在相应哈希值下。如果冲突较多使用红黑树。
(6)HashSet的实现原理:
HashSet的底层为HashMap,HashSet使用HashMap的数据结构存储数据元素,利用HashMap的相关方法直接调用底层数据,但是Hashset中加入Comparator比较器,保证数据唯一。
(7)ArrayList与LinkedList的区别:
底层原理:ArrayList的底层采用数据,LinkedList的底层采用链表的结构。
存取效率:ArrayList理论上在随机访问时,效率高,但在增加数据时由于需要更改其他数据的下标,效率较低。LinkedList在随机访问时,需要遍历链表的所有数据,效率低,但在增加数据时,只对前后节点的指针进行操作,效率高。
(8)数组与List的相互转换:
//数组转List
Integer[] arr={1,3,5,1};
List<Object> arrList=new ArrayList<>(arr.length);
Collentions.addAll(list,arr);
list.add("ss");
list.foreach(System.out::print);
//List转数组
List<String> arr=new ArrayList<>();
arr.add("sssvage");
String[] string=new String[arr.size()];
String[] arrStr=list.toArray(string);
System.out.println(Arrays.toString(arrStr));
(9)ArrayList与Vector的区别:
线程安全:Vector的方法中大多采用synchronized修饰,线程同步,线程是安全的,但是效率低,ArrayList的线程不安全,但是效率高。
存储:ArrayList与Vector的底层都是通过数组实现的,但是容量扩充时Vector可以自定义扩充因子,默认为1倍,ArrayList每次扩充为50%。
(10)Array和ArrayList的区别:
Array内容可以为基本数据类型,也可以为对象,ArrayList数据只能为对象。
Array是固定大小,但是ArrayList的存储空间可以自动扩充。
Array的内置方法没有ArrayList多,比如addAll(),removeAll(),iteration等。
(11)queue的poll()与remove()有什么不同:
相同点:都是返回一个值,并且删除该值在队列中的数据。
不同点:如果队列中没有该对象,poll会返回一个null,而remove会抛出一个异常(nosuchelementException)。
(12)那些集合是线程安全的:
hashtable、vector、stack集合是线程安全的,但像HashMap这样的集合是线程不安全的,在jdk1.5后出现的java.util.concurrent并发包的出现,很多集合拥有其线程安全的版本,比如ConcurrentHashMap,CopyOnWriteArrayList。
(13)Iterator是什么:
Iterator接口提供遍历任何Collection的接口,通过Collection获取Iterator获取迭代器实例,可以在迭代中对集合中数据进行操作,但是在使用集合的方法对数据进行增加或删除时会抛出异常。代码示例:
List<Integer> arrList=new ArrayList<>();
arrList.add(234);
Iterator it=arrList.iterator();
while(it.hasNext()){
// arrList.add(222);会抛出ConcurrentModificationException并发异常。
System.out.print(it.next());
}
//iterator常用的方法
//.next() .hasNext() .remove() .
(14)Iterator与ListIterator的区别:
Iterator可以遍历List、Set、Map集合,ListIterator只可以遍历List集合。
Iterator只能单向遍历,ListIterator可以向list的前后遍历。
ListIterator集合是Iterator的子集合,但是实现了更多的方法,ListIterator除了有next(),hasNext(),remove(),还有向前遍历的previous(),hasPrevious(),add(e)在游标前插入数据,set(e)更新最后一次操作next()、previous的位置为e,nextIndex()获取游标后边元素的索引,previousIndex()获取游标前一位元素的索引。
(15)如何保证集合不被更改:
使用Collections.unmodifiableCollection(Collection c);但执行对集合c的任何操作都会抛出操作不支持异常。
(后期添加内容)描述常见的设计模式?
简单工厂模式:
创建一个实体类,在实体类中进行多种判断并创建各种类型的实体类,这些实体类之间有关联,我们可以理解为他们为一个工厂,叫简单工厂模式。
工厂方法模式:
定义一个抽象类作为工厂,由具体类继承抽象类并实现实例方法,将类的实例化放入实体类中。
抽象工厂模式:
创建抽象类,抽象类中定义需要创建不同工厂的方法,由实例工厂继承并实现抽象方法,在实例工厂中创建最终实例对像。
//抽象工厂类
public abstract class AbstractFactory {
//生产产品的工厂
public abstract Product createProduct(String productType);
//为产品上色的工厂
public abstract Color drawColor(String colorType);
}
//生产产品的工厂
public class ProductFactory extends AbstractFactory {
@Override
public Product createProduct(String productType) {
if(productType.equals("手机")){
return new Phone();
}else if(productType.equals("电脑")){
return new Computer();
}else if(productType.equals("飞机")){
return new Plane();
}else {
System.out.println("传入产品类型有误!");
}
return null;
}
@Override
public Color drawColor(String colorType) {
return null;
}
}
//产品上色的工厂
public class ColorFactory extends AbstractFactory {
@Override
public Product createProduct(String productType) {
return null;
}
@Override
public Color drawColor(String colorType) {
if(colorType.equals("红色")){
return new Red();
}else if(colorType.equals("绿色")){
return new Green();
}else if(colorType.equals("彩虹色")){
return new Rainbow();
}else {
System.out.println("传入产品类型有误!");
}
return null;
}
}
单例模式:
为了解决某个类频繁创建与销毁,该类保证了在Jvm中只有一个实例对象存在,保证构造函数私有化。有单例类创建单例对象,并为该单例创建全局访问点。
懒汉式:在调用全局访问点时才会创建这个单例。
public class LazySingle {
private static LazySingle lazySingleInstance;
private LazySingle(){}
// 全局访问点
public static synchronized LazySingle getInstance(){
if(lazySingleInstance==null)
{
lazySingleInstance=new LazySingle();
}
return lazySingleInstance;
}
}
饿汉式:在加载类时就会创建对应的实例,可以通过全局访问点直接调用该类。
public class HungrySingle {
private final static HungrySingle hungrySingleInstance=new HungrySingle();
private HungrySingle(){}
// 创建饿汉访问节点
public static HungrySingle getInstance(){
return hungrySingleInstance;
}
}
指挥者模式:
//电脑类需要被创建者
public class Computer {
private String mainFrame; //主机
private String display; //显示器
private String keyboard; //键鼠套装
public void setMainFrame(String mainFrame) {
this.mainFrame = mainFrame;
}
public void setDisplay(String display) {
this.display = display;
}
public void setKeyboard(String keyboard) {
this.keyboard = keyboard;
}
public void showMessage(){
System.out.println("这是一台电脑。");
}
}
//建造者
public class Builder extends AbstractBuilder {
@Override
public void buildMainFrame() {
computer.setMainFrame("建造好了主机");
}
@Override
public void buildDisplay() {
computer.setDisplay("建造好了显示器");
}
@Override
public void buildKeyboard() {
computer.setKeyboard("建造好了键鼠套装");
}
}
abstract class AbstractBuilder {
//创建一个电脑对象
Computer computer = new Computer();
//构造主机
public abstract void buildMainFrame();
//构造显示器
public abstract void buildDisplay();
//构造键鼠套装
public abstract void buildKeyboard();
//返回电脑对象
public Computer returnComputer(){
return computer;
}
}
//指挥者
public class Leader {
private Builder builder;
public Leader(Builder builder){
this.builder = builder;
}
public Computer build(){
builder.buildMainFrame();
builder.buildDisplay();
builder.buildKeyboard();
return builder.returnComputer();
}
}
//使用
public class BuilderMain {
public static void main(String[] args) {
//创建一个建造者
Builder builder = new Builder();
//创建一个指挥者
Leader leader = new Leader(builder);
//创造对象
Computer computer = leader.build();
computer.showMessage();
}
}
适配器模式:
//提供一个按类型充电的方法
public interface Charge {
public void charge(String type);
}
//100W充电器接口
public interface W100 {
public void charge100W();
}
//100W充电的实现类
public class W100Impl implements W100 {
@Override
public void charge100W() {
System.out.println("使用100W充电器给电脑充电");
}
}
//适配器
public class ChargeAdapter implements Charge {
W100 w100;
public ChargeAdapter(String type){
if(type.equals("100w")){
w100 = new W100Impl();
}
}
@Override
public void charge(String type) {
w100.charge100W();
}
}
//适配器
//200w充电器
public class W200 implements Charge {
ChargeAdapter chargeAdapter;
@Override
public void charge(String type) {
if(type.equals("200w")){
System.out.println("200W充电器正常充电");
}else if(type.equals("100w")) {
chargeAdapter = new ChargeAdapter(type);
chargeAdapter.charge(type);
}else{
System.out.println("功率不兼容,充电失败!!!");
}
}
}
public class AdapterMain {
public static void main(String[] args) {
W200 w200 = new W200();
//通过适配器使用100w充电器给200w电脑充电
System.out.print("过适配器使用100w充电器给200w电脑充电:");
w200.charge("100w");
//通过适配器使用300w充电器给200w电脑充电
System.out.print("过适配器使用300w充电器给200w电脑充电:");
w200.charge("300w");
}
}
代理模式:
为了控制被代理类的访问情况,使用代理类代理被代理类作为访问者与被访问者之间的中介。代理类与被代理类使用同一个接口。
//接口
public interface Product {
void show();
}
//接口的实体类
public class RealProduct implements Product {
@Override
public void show() {
System.out.println("获取到一个真实产品RealProduct!");
}
}
//接口的代理类
public class ProxyProduct implements Product {
private RealProduct realProduct;
@Override
public void show() {
if(realProduct == null){
realProduct = new RealProduct();
}
pre(); //代理增强方法,处理代理之前要做的事情
realProduct.show();
post(); //代理增强方法,处理代理之后要做的事情
}
public void pre(){
System.out.println("代理之前要做的事情");
}
public void post(){
System.out.println("代理之后要做的事情");
}
}
public class ProxyMain {
public static void main(String[] args) {
ProxyProduct proxyProduct = new ProxyProduct();
proxyProduct.show();
}
}
装饰器模式:
对一个现有对象添加新的功能,同时不改变其结构。
//装饰器
public class Decorator implements Product {
private Product product;
public Decorator(Product product){
this.product = product;
}
@Override
public void function() {
product.function();
}
}
//手机装饰器
public class PhoneDecorator extends Decorator {
public PhoneDecorator(Product product) {
super(product);
}
@Override
public void function() {
super.function();
addFunction();
}
public void addFunction(){
System.out.println("给手机增加一个发短信的功能");
}
}
原型模式:
对一个已经创建的实例为原型,对其进行复制克隆,创建一个类似的新对象。
拷贝的方法:
使用Cloneable接口的clone()方法,进行浅拷贝,或对原型对象的引用类型属性进行克隆实现深拷贝。
使用序列化反序列化(推荐)
观察者模式:
观察者模式:对象之间存在一对多的关系,当一个对象状态发生改变,其他所有的对象得到通知并自动更新,又称为发布-订阅模式。示例代码:
//抽象观察者
public abstract class Observer{
public abstract void update();
}
//定义观察者
public class A extends Observer{
public void update(){
System.out.println("观察者A做出反应");
}
}
public class B extends Observer{
public void update(){
System.out.println("观察者B做出反应");
}
}
//定义启动类
public class Subject{
private List<Observer> observers=new ArrayList<>();
private String state;
public String getState(){
return state;
}
public void setState(String state){
this.state=state;
notifyAllObserver();
}
public void add(Observer observer){
observers.add(observer);
}
public void notifyAllObserver(){
for(Observer observer:observers){
observer.update();
}
}
}
//定义主类
public void MainClass{
public static void main(String[] args){
Subject subject = new Subject();
subject.add(new A);
subject.add(new B);
System.out.println("发生改变,通知观察者");
subject.setState("change");
}
}
Java8新特性
(1)Lambda表达式:
函数式接口的实例,可以理解为匿名函数,一段可以传递的代码,由->箭头操作符和左右两侧的数据组成,->左侧为参数(),当参数只有一个时可以省略(),->右侧为方法体{},方法体中如果只有一条语句可以直接省略{},如果有return也可以省略。
(2)函数式接口:
只有一个抽象方法的接口叫函数式接口,可以通过@FunctionalInterface检查是否为函数式接口,使用Lambda表达式实例化函数式接口。
(3)方法引用:
如果lambda表达式方法体中已经有实现的方法,可以使用方法引用,直接描述方法体中的内容,也是函数式接口的一种实现方法,但是需要实现的抽象接口的方法的参数列表和返回值必须与方法引用的参数类型和返回值类型保持一致。
对象::实例方法
Cat cat1 = new Cat();
Supplier<String> supplier=cat1::getName;
System.out.println(supplier.get());
类::静态方法
Cat cat1 = new Cat();
Supplier<String> supplier=cat1::getName;
System.out.println(supplier.get());
Function<Double,Long> ron=Math::round;
System.out.println(ron.apply(11.5));
类::实例方法
Cat cat2 = new Cat();
Function<Cat,String> catGet=Cat::getName;
System.out.println(catGet.apply(cat2));
构造器引用:类名::new
方法引用的一种,要求构造器的参数列表与接口中参数列表相同,返回值为构造器对应的类对象。
数组引用:type[]::new
可以将数组看成特殊的类,使用方法与构造函数引用类似;
(4)StreamAPI:
Stream是数据渠道,用于操作源数据(集合,数组等)产生的元素序列。Stream不会保存数据,不会改变数据源,每次操作都会产生一个Stream,每次的操作只有在需要数据时才会执行,是延迟的。
Stream操作的三个步骤:
创建Stream,通过数据源获取流
//Collection接口被扩展,提供了两个获取流的方法
List<Cat> catList=new ArrayList();
//default Stream<E> stream():返回一个顺序流
Stream<Cat> catStream=catList.stream();
//default Stream<E> parallelStream:返回一个并行流
Stream<Employee> parallelStream=catList.parallelStream();
//通过数组工具类Arrays
int[] arr=new int[]{1,2,3,4,5};
IntStream stream=Arrays.stream(arr);
Cat cat1=new Cat("mimi");
Cat cat2=new Cat("huahua");
Cat[] catArr=new Cat[]{cat1,cat2};
Stream<Cat> catStream=Arrays.stream(catArr);
//通过Stream的Of静态方法
Stream<Integer> stream=Stream.of(1,2,2,3,4);
//通过Stream的iterate()和generate()方法创建
// 迭代
// public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
Stream.iterate(0,t->t+2).limit(10).forEach(System.out::println);\
// 生成
// public static<T> Stream<T> generate(Supplier<T> s)
Stream.generate(Math::random).limit(10).forEach(System.out::println);
中间操作,一个中间操作链,对数据源数据进行处理。通过多个中间操作产生一个流水线,除非执行终止操作,否则不会产生任何结果,在终止操作是进行全部处理的操作称为惰性求值
a.筛选和切片:排除某些元素,去重,根据指定长度截取流。
b.映射:将函数应用到流中的元素上,并产生新的流
c.排序:将流中的数据排序,自然排序或根据sort()方法中定义的作为比较方法的比较器进行排序。
终止操作,一旦执行终止操作,就执行操作链,并产生结果,但是一旦产生结果,操作链就不会被使用。
a.匹配和查找:检查是否匹配所有元素,并返回会指定元素。
b.归纳:将流中的数据进行某种计算,进行归纳
c.收集:将流中的数据保存为collect()参数规定的容器,比如Collector中的toSet、toList、toCollection等。
(5)Optional类:
optional是一个容器类,专门解决空指针异常,可以保存T类型,代表这个值存在,或者仅仅保存null,表示这个值不存在,现在Optional可以更好表示这个概念,可以避免空指针异常。
创建Optional对象:
.Of(T t)静态方法,t不能为空
.empty()创建空的实例
.ofNullable(T t)t可以为空
判断Optional是否包含对象:
isPresent():判断是否包含对象
isPresent(Consumer<? super T> consumer):如果有值,就执行Consumer接口的实现代码,并且该值会作为参数传给它。
获取Optional对象:
T get(): 如果调用对象包含值,返回该值,否则抛异常
T orElse(T other) :如果有值则将其返回,否则返回指定的other对象。
T orElseGet(Supplier<? extends T> other) :如果有值则将其返回,否则返回由Supplier接口实现提供的对象。
T orElseThrow(Supplier<? extends X> exceptionSupplier) :如果有值则将其返回,否则抛出由Supplier接口实现提供的异常
(6)新日期时间Api
新增一套全新的日期时间API,如localTime,LocalDate,LocalDateTime,DateTimeFormatter以及Duration和Period,其中包含许多对时间增加删除的操作,保证灵活性。
(7)接口中的默认方法和静态方法:
jdk1.8以前接口只能定义全局常量,抽象方法。
jdk1.8以后接口能定义全局常量,抽象方法,默认方法和静态方法。
(8)重复注解:@Repeatable解决一个注解对于一个方法只能使用一次。
(9)集合:
hashMap的底层原理:hashMap底层使用数组+哈希表+单链表/红黑树,在链表的长度超过8并且总容量大于64时,使用红黑树。
20.线程:
(1)并发和并行的区别
并发指多个处理器处理多个任务。
并行指单个处理器同时处理多个任务,一个处理器按细分的时间片轮流交替执行,在逻辑上,他们是同时执行。
(2)进程和线程的区别:
进程有独立的本地方法区和堆,多个线程共享进程的方法区和堆;线程有独立的本地方法栈和虚拟机栈、程序计数器,线程的切换开销小,进程的切换开销大。
进程的崩溃在保护状态下不会影响其他进程,而单个线程崩溃整个进程崩溃,进程健壮性强。
进程有独立的程序运行入口、顺序执行序列和程序执行出口。线程必须在进程中执行,有应用程序提供线程执行控制,线程和进程均可并发。
(3)什么是守护线程:
java中线程包括用户线程和守护线程,守护线程是个服务线程,用来服务用户线程,比如Java垃圾回收器,在所有线程都结束后,守护线程继续运行,jvm关闭,守护线程继续执行,守护线程不会影响进程的结束。
(4)创建线程有哪几种方式:
a.继承Thread类创建线程类:
创建Tread类的子类,重写run()方法,实例化子类对象,调用start()启动线程。
public class FirstTreadTest extends Tread{
int i=0;
public void run(){
for(;i<100;i++){
System.out.println(getName()+" "+i);
}
}
public static void main(String[] args){
for(int i=0;i<100;i++){
System.out.println(Thread.currentTread().getName()+" : "+i);
if(i==50){
new FirstThreadTest().start();
new FirstThreadTest().start();
}
}
}
}
b.通过Runnable接口创建线程类
实现Runnable接口,重写run()方法,实例化Runnable子类,创建Tread对象,将Runnable子类对象作为参数调用Tread对象的start()。
public class RunnableThreadTest implements Runnable{
private int i;
public void run()
{
for(i = 0;i <100;i++)
{
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
public static void main(String[] args)
{
for(int i = 0;i < 100;i++)
{
System.out.println(Thread.currentThread().getName()+" "+i);
if(i==20)
{
RunnableThreadTest rtt = new RunnableThreadTest();
new Thread(rtt,"新线程1").start();
new Thread(rtt,"新线程2").start();
}
}
}
}
c.通过Callable和FutureTask创建线程。
实现Callable接口,重写call()方法,创建Callable子类对象,使用Callable实例化对象最为参数,创建FutureTask子类;
创建thread子类,以FutureTask作为参数,调用start()方法。futureTask子类中封装了Callable子类中的run()返回值,可以通过FutureTask中的get()调用。
package com.nf147.Constroller;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class CallableThreadTest implements Callable<Integer> {
public static void main(String[] args) {
CallableThreadTest ctt = new CallableThreadTest();
FutureTask<Integer> ft = new FutureTask<>(ctt);
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " 的循环变量i的值" + i);
if (i == 20) {
new Thread(ft, "有返回值的线程").start();
}
}
try {
System.out.println("子线程的返回值:" + ft.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
@Override
public Integer call() throws Exception {
int i = 0;
for (; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
return i;
}
}
创建线程的三种方法的对比:
实现Runnable、Callable接口的方式创建线程,类可以继承其他父类,也可以实现多种接口,减少资源的占用,通过Thread对象同时调用一个子类创建多线程,这种方法可以将CPU,代码,数据分开,形成清晰的模型,较好的实现了面向对象的思想。
(5)线程有哪些状态:
new 使用Thread创建线程对象,执行start()进入就绪状态。
RUNNABLE:就绪状态抢夺时间片,进入运行状态(RUNNING),如果时间片失效就进入就绪状态继续争夺时间片,直到run()结束。
BLOCKED:阻塞状态,线程进入synchronized关键字修饰的方法或代码块中,阻塞状态结束重新返回就绪状态,抢夺事件片。
WAITING:等待状态,这个状态cpu不会分配时间片,他们要等待被唤醒,否则处于无限等待状态。
TIMED_WAITING:超时等待,这种状态线程处于等待状态,除非等待时间结束,否则不分配cpu时间片,一直处于等待状态。
TERMINATED:run()方法结束,线程消失。
(6)sleep()和wait()的区别:
类的不同:sleep()来自Thread,wait()来自Object。
释放锁:sleep()不释放锁,wait()释放锁。
用法不同:sleep()到时间自动恢复,wait()可以使用notify/notifyAll()直接唤醒。
(7)notify和notifyAll的区别:
notify和notify都是用来唤醒。.wait()方法进入等待池的线程,但是notify只能随机唤醒一个线程,参与锁池;notifyAll唤醒所有等待对象监视器的线程,参与锁池,参与获取锁的竞争。
(8)线程的run方法和start方法有什么区别:
start方法用于启动线程,run方法用于执行线程的运行时代码,run方法可以重复调用。
在Callable中由call()运行run()方法,但是可以由返回值而且可以抛出异常。
(9)线程池有哪些创建方式:
newSingleThreadExecutor():创建一个线程池只能有一个线程运行,所有 线程在一个无界工作队列中,每次一个线程运行,保证线程顺序执行,不允许使用者改变线程实例,修改线程数目。
newCachedThreadPool():创建一个线程池,该线程池会尝试将线程保存到缓存中重用,如果缓存中没有线程则创建新的工作线程;如果线程在缓存区超过60秒闲置,就会被移除,所以不会消耗很多资源,缓存线程池采用SynchronousQueue()工作队列,自动同步。
newFixedThreadPool(int poolSize):创建线程池,采用无界的工作队列,每次只有poolSize个工作线程,如果任务线程超过工作线程,在工作队列中会出现等待闲置的线程;如果工作线程少于poolSize,就会创建新线程。
newSingleThreadScheduledExecutor():创建一个单线程池,返回ScheduledExecutorService可以进行定时或周期的工作调度。
newScheduledThreadPool(int corePoolSize):创建一个多线程池,类似于newSingleThreadScheduledExecutor(),返回一个ScheduledExecutorService周期或定期调用线程。
newWorkStealingPool(int parallelism):java8中新加入的方法,内部构建ForkJoinPool(交叉连接线程池),利用work-Stealing算法,由完成任务的线程窃取其他线程的任务,并行执行处理任务,不保证执行顺序。
TreadPoolExecutor():最原始的线程池创建,上面newSingleThreadExecutor、newCachedThreadPool、newFixedThreadPool都是对TreadPoolExecutor的封装。
(10)线程池的状态:
RUNNING:线程池在创建后进入运行状态并保持运行,直到手动调用停止方法。
STOP:调用shutDownNow()方法,线程池不在接收任务提交,终止工作任务,放弃任务队列中的任务,进入TIDYING状态。
SHUTDOWN:调用shutDown()方法,线程池不在接收任务提交,在完成工作任务后以及任务队列中的任务后,进入TIDYING状态。
TIDYING:线程池中的任务执行完毕,线程数变为0,执行terminated()。
TERMINATED:当线程池的terminated方法执行后,线程池进入销毁状态,线程池终止。
(11)线程池的submit方法和execute方法的区别:
submit方法可以执行Runnable和Callable实现类,execute只能执行Runnable实现类。
submit可以由返回值,封装Future类,包含了run方法或call方法执行结果以及异常,通过.get()方法获得。但实际上submit的底层最终调用execute方法。
(12)java程序中如何保证线程安全:
方法1:使用java.util.concurrent中定义线程安全类,尽量减少自定义的锁的使用。
方法2:使用synchronized同步锁。
方法3:使用手动锁Lock;
Lock lock=new ReentranLock;
lock.lock();
try{
System.out.println("加锁");
}catch(Exception e){
System.out.println("出现异常"+e);
}finally{
System.out.println("释放锁");
lock.unlock();
}
(13)synchronized锁对象升级的原理:
锁对象的对象头里有一字段:threadid
首次访问锁对象时,jvm将threadid设置为当前线程的id,设置锁为偏向锁。
再次访问锁对象,将threadid与当前线程的id匹配,若一致则直接使用此对象,否则,升级偏向锁为轻量级锁。在等待锁对象中,通过自旋循环获取锁,执行一定次数后,如果还没有获取当前线程对应的锁对象,就会把轻量级锁升级为重量级锁,此过程就构成synchronized锁升级。
此过程不可逆。
锁升级的目的:为了降低锁带来的性能消耗,java6之后优化synchronized的实现方式,使用将锁升级的方法减低锁带来的性能消耗。
(14)什么是死锁:
线程A持有独占锁a,线程B持有独占锁b,而线程A在尝试获取独占锁b的同时,不释放资源,线程B在尝试获取独占锁a,就形成死锁。
防止线程死锁的方法:
使用tryLock(),在获取不到锁时,释放原有资源。
tryLock(Long time,TimeUnit unit),设置超时时间,超时退出防止死锁。
尽量使用Java.util.concurrent并发类代替手写锁。
减少使用synchronized同步代码块。
(15)ThreadLocal简介:
ThreadLocal为每个线程提供了独立的实例副本,每个线程只能访问自己的ThreadLocal实例,即在线程之间独立,在方法和类中共享。在一个线程结束时,所有的ThreadLocal实例都会被收回。
(16)Synchronized底层的实现原理:
synchronized是由一对monitorenter和monitorexit的指令实现的,monitor是同步实现的基本单元,在java6之前,monitor是完全依赖操作系统的互斥锁,由于需要用户态(用户应用程序)和内核态(操作系统程序)的转换,所以同步操作是一个无差别的重量级操作,效率低,java6对此进行大刀阔斧的改革,使用三种实现monitor的方法,即偏向锁,轻量级锁,重量级锁,大大提高效率。
(17)Synchronized和volatile的区别:
volatile是变量修饰符,而synchronized可以修饰类,方法,代码块
volatile仅能保证变量修改的可见性,不能保证原子性,synchronized能保证变量修改的原子性和可见性。
volatile标记的变量不会被编译器优化,synchronized标记的变量会被编译器优化。
volatile不会造成线程的阻塞,synchronized会造成线程阻塞。
(18)Synchronized和Lock的区别:
synchronized可以修饰类,方法,代码块,lock只能修饰代码块。
synchronized不需要手动加锁释放锁,在检查到异常就会释放锁,不会造成死锁。而lock需要手动释放锁,在疏忽调用。unlock()后会造成死锁。
lock可以查看是否获取锁,而synchronized无法查看是否获取锁。
(19)Synchronized和ReentranLock的区别:synchronized和lock的区别
(20)Atomic的原理:
Atomic在concurrent并发包中,一种保证了线程安全的并发类,采用CAS乐观锁,使用volatile和native修饰,达到线程安全的同时保证操作的原子性。
21.异常:
(1)throw和throws的区别:
throw在方法块中执行,用来抛出一个具体异常。
throws在方法声明中执行,用来声明抛出的异常类型,可以有多个。
(2)final 、finally、finallize的区别:
final是修饰符,修饰类,该类无法被继承,修饰变量,变量值无法被修改,修饰方法,方法无法被重写。
finally是处理异常中使用的关键字,在try{}catch(){}finally{}中,finally为必须执行的代码;
finallize方法为object的方法,在jvm结束代码执行时,调用对象的object的finallize进行回收,实现垃圾回收机制。
(3)try catch finally的执行顺序:
执行try中的代码,如果有异常执行catch中的代码,如果catch中的有return不执行,catch终止则执行try语句中的return,保存返回值,执行finally,即无论try和catch中是否有return都执行finally,如果finally中有return则不返回。
//测试代码 没写main
package com.p2;
public class TestTry {
static int i;
public static int testT(){
try{
i=8/1;
return 1;
}catch (ArithmeticException e)
{
System.out.println("数字型异常");
return 2;
}finally {
System.out.println("finally");
return 3;
}
}
}
(4)常见的异常类型:
ExceptionSubclass:
ClassNotFoundException,类找不到异常
FileNotFoundException 文件未找到异常
IOException IO 异常
SocketException Socket 异常
RuntimeException:
NullPointerException 空指针异常
NumberFormatException 字符串转换为数字异常
IndexOutOfBoundsException 数组下标越界异常
ClassCastException 数据类型转换异常
22.网络编程(详细内容见文章网络编程):
(1)转发和重定向的区别:
转发:
request.getRequestDispatcher("new.jsp").forward(request,response);
客户端向浏览器发送http请求,web服务器接收请求,在控制器内处理请求并完成转发,将目标资源发送给客户,转发的对象必须为同一个web容器中的url,中间request内容没发生改变。客户端不会显示转发的地址,其中客户端只进行一次访问请求。
重定向:
response.sendRedirect("new.jsp");
客户端向服务器发送http请求,服务器处理请求并返回代码302以及需要重定向的地址location,客户端在接收302后,重新发送请求给服务器URL为location地址。客户端可以看见url地址的变化。浏览器发送两次访问请求。
(2)tcp和udp的区别:
tcp是面向连接的协议,udp不用建立可靠连接。
tcp仅支持点对点服务,udp支持一对多、多对多、多对一。
tcp报头需要20字节,udp只需8字节。
tcp传输以bit为单位而udp传输以报文为单位,不能分段传输。
(3)是否可以进行两次连接?
不可以,因为可能会因为无效的客户端分组导致浪费服务器资源。客户端再第一次将请求数据发送给服务器时,数据由于某些原因在建立连接后才正常传入服务器。此时服务器会正常响应并建立连接,但是客户端并不会回应此链接,因为没有发送请求,最后浪费服务器资源。
其次,再二次握手后,服务器无法确定客户端已经接收响应数据,无法确定双方已经交换序列,因此可能无法建立连接。
(4)tcp粘包产生的原因:
tcp粘包指发送方发送的数据报在接收方缓存中首尾相连。
发送方:采用Nagle算法,将分组放入缓存不发送在接收到确认消息发送分组,造成粘包。
接收方:接收包的速度大于应用程序从接收缓存区取数据的速度。
即接收方不即使接收缓存区的数据包 。
(5)OSI参考模型:
在tcp/ip模型中,由应用层完成表示层和会话层的功能。
由数据链路层完成物理层的工作。
应用层:与客户交流的层次,进行文件的管理和电子邮箱的管理。主要包括http、ftp;
表示层:对数据进行加密和解密,编码进行转换和解析,保证应用层信息的读取。Telnet
会话层:保证网络中两节点连接通信畅通。DNS
传输层:定义数据传输的协议和端口,传输协议同时流量控制。TCP UDP
网络层:逻辑编址,分组传输。ip
数据链路层:对物理层的数据进行封装和解封,同时保证传输可靠。ppp、ethernet以太网协议
物理层:规定物理接口的设备标准,传输比特流。
(6)get与post的区别:
get的请求数据在url中,post的请求数据在请求体中。
get的请求参数只能为url编码格式必须为ascii码数据类型,post没有要求。
get的请求参数有长度限制,post没有
get侧重在服务器上获得数据,post侧重在服务器更改数据。
(7)如何解决跨域问题:
CORS跨域问题来自js的同源策略,即只有当两页面的协议+主机名(ip)+端口相同时,才能互相访问;
解决方法(未经过实践,内容由网络查询):
前端:
使用JSONP进行跨域调用。
或使用NODEJS进行代理,前端发送请求给nodejs,nodejs转发给后端。
后端:
采用nginx反向代理
在spring中多使用@CrossOrigin注解,设置origins=”*“;
(8)说一下jsonp的实现原理:
jsonp使用script的src属性将前端的js函数作为参数传入后端,后端服务器为js函数添加数据后返回,实现服务器端和客户端的信息传递。只接受使用get方法进行信息传递。
23.什么是序列化与反序列化:
序列化:把对象转换为字节序列便于存储和传输。称为序列化。
反序列化:把字节序列恢复为对象的过程称为对象的反序列化。
序列化反序列化一般用于存数据库或文件中,对实现了serializable接口的类进行的操作。
//定义一个类,实现Serializable接口
public class Cat implements Serializable,Cloneable{
private String name;
public Cat() {
}
public Cat(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
// 序列化
Cat cat = new Cat();
ObjectOutputStream catStream = new ObjectOutputStream(new FileOutputStream("C:\\Users\\13064\\Desktop\\cat.txt"));
catStream.writeObject(cat);
catStream.flush();
catStream.close();
// 反序列化
ObjectInputStream catData = new ObjectInputStream(new FileInputStream("C:\\Users\\13064\\Desktop\\cat.txt"));
Object o = catData.readObject();
System.out.println(o);
catData.close();
24.反射机制:
在程序运行中,对任意类,能获取该类的所有属性和方法,能调用任意对象的方法和属性,这种动态获取信息,动态调用对象的方法称为java的反射机制。
25.动态代理:
程序运行中,生成目标对象的代理对象,在代理目标对象过程中对目标对象功能增强的一种技术,对于动态代理类,目标对象不变,但代理类的方法为目标对象的增强。
应用场景:spring aop, hibernate的数据查询等。spring AOP动态代理,默认采用基于接口的JDK动态代理,可以手动设置为CGLIB动态代理。JDK动态代理使用Proxy对象生成代理对象,CGLIB动态代理使用Enhancer生成代理对象,基于类对象的动态代理。
public class LogProxy {
/**
* 生成对象的代理对象,对被代理对象进行所有方法日志增强
* 参数:原始对象
* 返回值:被代理的对象
* JDK 动态代理
* 基于接口的动态代理
* 被代理类必须实现接口
* JDK提供的
*/
public static Object getObject(final Object obj){
/**
* 创建对象的代理对象
* 参数一:类加载器
* 参数二:对象的接口
* 参数三:调用处理器,代理对象中的方法被调用,都会在执行方法。对所有被代理对象的方法进行拦截
*/
Object proxyInstance = Proxy.newProxyInstance(obj.getClass().getClassLoader()
, obj.getClass().getInterfaces(), new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//方法执行前
long startTime = System.currentTimeMillis();
Object result = method.invoke(obj, args);//执行方法的调用
//方法执行后
long endTime = System.currentTimeMillis();
SimpleDateFormat sdf = new SimpleDateFormat();
System.out.printf(String.format("%s方法执行结束时间:%%s ;方法执行耗时:%%d%%n", method.getName()), sdf.format(endTime), endTime - startTime);
return result;
}
});
return proxyInstance;
}
/**
* 使用CGLib创建动态代理对象
* 第三方提供的的创建代理对象的方式CGLib
* 被代理对象不能用final修饰
* 使用的是Enhancer类创建代理对象
*/
public static Object getObjectByCGLib(final Object obj){
/**
* 使用CGLib的Enhancer创建代理对象
* 参数一:对象的字节码文件
* 参数二:方法的拦截器
*/
Object proxyObj = Enhancer.create(obj.getClass(), new MethodInterceptor() {
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//方法执行前
long startTime = System.currentTimeMillis();
Object invokeObject = method.invoke(obj, objects);//执行方法的调用
//方法执行后
long endTime = System.currentTimeMillis();
SimpleDateFormat sdf = new SimpleDateFormat();
System.out.printf(String.format("%s方法执行结束时间:%%s ;方法执行耗时:%%d%%n", method.getName()), sdf.format(endTime), endTime - startTime);
return invokeObject;
}
});
return proxyObj;
}
}
//原文链接:https://blog.csdn.net/qq_16570607/article/details/118360338
26.克隆:
(1)为什么实现克隆:
类的每次实现会开辟新的堆空间,其内容为初始化内容,如果需要修改后的类对象以及其数据,可以使用克隆的方法。克隆分为深克隆和浅克隆,浅克隆会复制对象的基本数据类型的值,对对象的引用类型,只会使用原对象的引用地址。深拷贝要求对源对象的基本数据类型和所有引用类型的数据进行拷贝。
(2)如何进行深拷贝:
重写object的clone对原对象的引用类型进行迭代深拷贝。
序列化反序列化进行深拷贝。
27.JavaWeb
(1)jsp和javaweb的区别:
jsp是对javaweb的扩展和简化,servlet和jsp的区别在于,servlet的应用逻辑在java文件中,从html中完全分离出来,而jsp则将java语言与html语言结合组成一个扩展名为jsp的文件,jsp侧重视图,servlet侧重于控制逻辑。
(2)jsp的内置对象:
request:封装客户端的请求,包含get和post请求参数。
response:封装服务器的响应内容。
out:输出服务器响应的输出流对象。
pageContext:通过此对象可以获取其他对象。
application:封装服务器运行环境的对象。
session:封装用户会话的对象。
page:jsp页面本身。
config:web页面的page属性。
exception:封装页面抛出异常的对象。
(3)jsp的作用域:
page:保存与页面 相关的对象和属性。
request:保存客户端发起的一次请求的对象和属性。一个请求可能包括多个页面与多个web组件;需要在页面中显示的内容页保存在此作用域中。
session:保存与服务器建立一次会话的相关对象的属性与方法。与客户端的会话信息应该保存在对应会话中。
application:存储与web应用程序相关的对象和方法。包括多个页面、请求、会话、的全局作用域。
(4)session与cookie的区别:
session保存在服务器端,cookie保存在浏览器中
session可以保存在redis、数据库、应用程序中,cookie可以保存在浏览器。
cookie在不同节点由大小和个数的限制。
session比cookie安全性高,cookie在浏览器中容易修改、获取。
(4)session的工作原理:
在客户端与服务器第一次建立连接时,服务器会建立一个session,并将用户信息存储在session中,服务器将session的id发送给客户端,保存在cookie中为sessionid,客户端再次访问客户端时,会再次调用相应的sessionid的session。
(5)能否禁用cookie:
可以,session依赖cookie中的数据方便查找服务器中的相关信息,可以直接向服务器中发送数据保证session的获取。
(6)springMVCt与struts的区别:
springMVC是对类进行拦截,struts是对方法进行拦截。
springMVC采用aop进行拦截,struts使用interceptor进行拦截,导致struts2配置文件大。
springMVC中方法的数据相互独立,通过request和response获取数据,通过方法参数使用数据,处理结果通过ModelMap交回前端框架;struts‘2虽然方法之间也是独立但是共享action变量,会影响程序员编码和读程序。
springMVC对ajax的处理是使用@ReponseBody,就可以实现,而struts需要插件或自己写代码。
(7)如何避免依赖注入:
使用PreparedStatement.
使用正则表达式过滤字符中的特殊字符。
(8)什么是xss攻击:
即跨站脚本攻击,它是web程序的常见漏洞,攻击者向页面中嵌入脚本代码(css代码、js代码),在用户访问页面时执行脚本,从而获得用户信息、破坏页面结构、重定向页面到其他页面等。
对xss攻击最好的防御手段是对页面输入的数据进行过滤。
(9)什么是CSRF攻击,如何避免?
CSRF:cross-site request forgrey(跨站点请求伪造),可以理解未攻击者盗用用户信息发送请求,比如发送邮箱,购买商品,购买虚拟货币等。
对CSRF最有效的措施是:
关键操作添加验证码
对请求地址添加token并验证。
28.spring(根据网络教程学习)
(1)什么是spring:
一般我们所说的spring是Spring Framework,是一个生态,可以构建所有java应用所需的一切基础设施。
spring是一个轻量级框架,解决了企业级应用开发的业务逻辑层和其他层对象和对象耦合问题,主要特点是IOC和AOP,即控制反转和面向切面编程,其次在spring的容器中,包含并管理应用对象的生命周期。
(2)spring的优缺点:
优点:
由于spring采用ioc,控制反转,对象由spring创建并进行管理,不需要对类手动编写代码,保证对象耦合度,且方便维护。
spring使用aop,动态代理,为每一个对象提供代理,在不修改类代码的同时对类接口增强,提高开发效率。
spring采用@Transactional注解对事务进行控制,提高开发效率。
spring支持Junit依赖包,通过注解测试spring程序,方便高效。
通过引入依赖,直接使用其他开源框架,兼容性高,集成能力高,减低其他框架的使用难度,简化开发,简单上手。
spring中封装了众多功能接口javaeeAPI(jdbc、javamail),减低使用难度,简化开发。
缺点:
spring在经过多年的迭代,底层代码逻辑复杂代码量高,深入学习有很大困难。
(3)IOC:
a.什么是IOC,有什么优点:
控制反转,将创建对象的控制权交给spring容器,如果需要使用可以通过DI(依赖注入)@AUTOWIRED自动注入就可以使用。
使用ioc控制反转保证对象与对象之间耦合度低,方便维护。
b.IOC的实现机制:
简单来说就是简单工厂模式和反射机制,简单工厂模式需要为每个类创建标识,使用反射机制的getBean(),直接通过每个类的路径获取类的结构,实现类的实例化自动注入。
c.IOC与DI的区别:
ioc是一种编程思想,指将程序员new对象的过程交给Spring容器,由spring创建维护对象以及对象之间的关系。
DI依赖注入,指通过简单工厂模式和反射机制,利用@AUTOWIRED创建对象,是IOC的实现手段,通过这种方法,减低对象之间的耦合,简化开发。
d.关于紧耦合和松耦合:
紧耦合:类与类之间相互依赖,new实例化对象的结果
松耦合:通过单一职责、接口分离、依赖倒置的原则,减低类与类之间的相互依赖相互影响。
e.springIoC的加载过程:
概念态转定义态:
实例化一个ApplicationContext的对象。
调用invokeBeanFactoryPostProcessor bean工厂的后置处理器扫描类的定义信息,解析扫描的类型信息。
实例化一个BeanDefinition对解析的信息进行保存。
将BeanDefinition对象保存在beanDefinitionMap中缓存起来,用来实例化。
再次调用BeanFactoryPostProcesser的子类提供修改类定义信息的扩展接口。
定义态转纯净态(实例化):
spring进行一系列准备工作,注册BeanPostProcessor、验证定义信息(确认不是懒加载和单例模式,是否为抽象类),验证类的构造方法。
spring根据类的构造方法,使用反射机制实例化一个对象,此时已经创建的bean对象无法使用。
spring处理合并的beanDefinition,判断是否进行属性填充,并注入属性。
纯净态转成熟态:(初始化)
判断bean的类型回调Aware接口
调用BeanPostProcessor的postProcessorsBeforeInitialization方法为bean实例创建扩展接口
调用生命周期回调方法(回调方法标志阶段地完成),判断是否定义初始化方法,调用init-method初始化bean
调用BeanPostProcesser创建aop动态代理。
将初始化后的bean放入容器,hashMap中。
f.springIoC有哪些扩展口,在什么时候调用:
PostProcessorBeanDefinitionRegistry的beanDefinitionRegistryPostProcessor在spring将类的定义信息注册到beanDefinition中时,提供动态注册的功能的扩展接口。
BeanFactoryPostProcessor的postProcessorBeanFactory在spring将类的定义信息注册到beanDefinition中后,提供修改定义信息功能的接口。
在初始化阶段,调用XXXAware相关方法的setXXXAware为相关类提供扩展功能的接口。
初始化前,调用BeanPostProcessor的postProcessorBeforInitialization方法提供初始化前的bean扩展功能。
初始化后,调用BeanPostProcessor的postProcessorAfterInitialization提供初始化后的bean扩展功能。
(4)BeanFactory的作用:
作为spring中核心的顶层接口,通过简单工厂模式,定义getBean方法生产Bean对象。
不同BeanFactory有单一职责,最强大的是底层的defaultListableBeanFactory生产Bean。
BeanFactory作为容器,生产bean同时管理bean。
(5)BeanDefinition的作用:
存储bean的定义信息,BeanFactory根据这些信息生产Bean,比如根据class中的值进行反射获取实例,lazy决定是否在ioc加载时创建bean。
(6)ApplicationContext与BeanFactory的区别:
BeanFactory通过简单工厂设计模式生产Bean对象,提供getBean方法获取bean。
ApplicationContext通过getBeanFactory的getBean生产bean对象。applicationContext的功能不只bean的生产,会自动扫描组件(@Component)和类(@Service)注册为bean。除此之外,ApplicationContext还完成加载环境变量,支持多语言,实现事件监听,注册对外扩展点的功能。
(7)spring为容器配置元数据的方式有几种:
spring为配置元数据对应Bean的配置方式:
xml配置:
spring注解:@Component @Autowired 使用扫描bean对象
javaConfig方式:@Configuration @Bean @Improt
(8)SpringBean
a.什么是springBean:
由springIoC创建、组装并管理的对象构成spring框架的主干称为springBean,与javaBean不同,是由java代码手动生成的对象。
b.配置Bean有几种方式:
xml:<bean class=-“com.csi.user” id=“”>
注解:@Component(@Controller、@Service、@Repository)需要扫描包
javaConfig:@Configuration+@Bean,可以创建对象交给spring管理
@Import
c.spring中bean支持的作用域:
singleton,prototype,session,request,application
d.单例Bean的优势:
由于不需要生成新的bean对象:
不需要使用反射和cglib动态代理也不需要为新的对象生成存储空间,减少jvm的性能消耗。
由于jvm的垃圾回收机制调用对象的destory方法,减少了垃圾回收次数,提高jvm的性能。
获取bean在第一次生成后就不需再获取,直接再缓存中使用,提高spring的性能。
e.单例bean是否线程安全?
单例bean如果其类中有成员变量,并且有对成员变量的读写操作,就会线程不安全。
只需将类中的对象放入方法中,就是线程安全的。
f.spring如何保证线程安全?
修改bean作用域为prototype
将类的成员变量放进ThreadLocal
为方法添加synchronized修饰,但是会影响程序的吞吐量。
g.spring实例化bean方式的几种方式:
反射,springIoC通过反射获取类的结构,利用其构造方法创建bean对象。
静态工厂方式:在配置中设置属性factory-method,利用bean类中的静态工厂方法实例化bean。
实例工厂方式(@Bean):在配置bean对象时设置属性factory-bean+factory-method,利用实例bean的工厂方法实例化bean。
FactoryBean方式:利用bean工厂地getObject和getObjectType实例化bean。
h.bean的自动注入有什么限制(需要注意什么):
在对xml中的bean进行自动属性注入时,该属性必须要有set方法。
自动属性注入的内容会被和的属性内容覆盖。
自动注入不会为简单的属性(基本数据类型、字符串、类)注入,需要手动注入使用
自动注入没有显示注入精确,尽量使用显式注入。
一般推荐使用@Autowired(ref=“”)手动注入。
i.springBean的依赖注入方式有哪些:
使用xml配置文件定义并自动、手动装配属性。
使用注释自动、手动装配属性@Autowired,@Autowired的装配模式分五种包括:
no:默认不使用自动装配,需要手动定义ref属性指定需要注入的属性。
byName:通过指定属性注入属性。查找bean中set方法所设置属性,扫描属性名并注入。
byType:通过查找指定set方法参数类型相同的bean属性注入。
constructor: 通过bean类的构造函数自动装填,构造函数的参数由参数类型到形参名的顺序进行装配。
autoDetect :自动探测,通过construct方式自动装配,否则使用byType方式装配。
j.bean有哪些生命周期回调方法?有哪些实现方式?
bean的生命周期回调方法分为初始化回调和销毁回调。
使用注解:
在自定义的初始化方法和销毁方法上使用@PostConstruct和@PreDestroy
使用接口:
实现InitializingBean和disposableBean的afterPropertiesSet和destroy
使用@Bean的属性方法:
为@Bean的init-method和destroy-method属性值赋自定义的初始化和销毁方法。
k.bean的生命周期:
创建前的准备:
这个阶段的主要作用就是Bean在开始加载之前要从上下文和配置文件中查找解析Bean的有关扩展实现,比如Bean初始化时调用init-method中的方法初始化;Bean在销毁时调用的destory-method方法;
1.Bean实例化
通过反射创建Bean的实例对象,扫描和解析Bean声明的一些属性。阶段完毕后,相当于已经创建Bean对象,但是不能使用。
2.为Bean对象填充属性
依赖注入:如果Bean有对其他Bean对象的依赖,就会进行注入,比如@Autowire。
3.初始化Bean对象
扩展回调:同时在这个阶段会触发一些扩展的调用,比如说常见的扩展类BeanPostProcessors后置处理器
用来实现Bean初始化前后的扩展回调。
容器缓存:通过调用init-method等生命周期初始化回调方法,初始化Bean对象,并利用BeanPostRrocessor(AOP)进行扩展。最后将初始化的Bean对象放进容器(hashMap)。
4.使用bean
5.容器关闭,调用生命周期回调方法,DisposableBean的destory方法销毁Bean,调用Bean自定义的destorymethod销毁方法销毁相关类对象。
l.springbean解决循环依赖问题:三级缓存问题
循环依赖:beanA中属性依赖beanB,beanB中属性依赖beanA,在实例化中属性注入相互调用,导致死循环。
解决三级缓存:
一级缓存:
在springIoC后产生的完整bean放入一级缓存。
二级缓存:
为了防止在实例化和初始化时重复创建动态代理,将三级缓存中的实例化bean创建动态代理放入二级缓存,在初始化时调用代理后的bean进行初始化。二级缓存是解决循环依赖的关键。
三级缓存:
将实例化的bean放入三级缓存,但是无法调用,当需要解决循环依赖问题时,通过三级缓存中的实例化bean(加动态代理放入二级缓存)注入属性依赖,结束循环依赖。
m.spring如何避免并发导致的获取bean不完整的问题:
spring采用双重检查锁的方法,当线程1实例化bean时会对二三级缓存加锁,在对bean实例化完成后由线程2重新调用getSingleTon方法获取bean实例。
不能对一级缓存加锁,一级缓存中存放已经初始化的bean,其他线程可以直接获取bean对象无须阻塞。
n.spring中BeanDefinition的运行流程:
读取配置,BeanDefinitionReader;
扫描需要注册Bean类:ClassPathBeanDefinitionScanner,扫描.class文件,判断类是不是符合组件的标准,是不是可以实例化;
解析Bean类:由配置类的ConfigurationClassParser解析定义信息。
将定义信息放入BeanDefinition对象,并注册。
o.spring如何在所有bean创建后做扩展:
在applicationContext方法中refesh()方法中finishBeanFactoryInitialization方法循环调用getBean方法后会调用SmartInitializingSingleton接口实现一个扩展接口。
在finishBeanFactoryInitialization方法后finishRefesh方法中会
提供ContextRefeshedEvent类作为监听事件,创建监听器监听此事件作为一个扩展点。
p.spring如何在bean注册后创建扩展点:
在invokeBeanDefinitionPostProcessor对配置文件中的bean类信息解析后调用BeanFactoryPostProcessor的方法对已经注册的信息修改提供接口。
q.Bean的创建顺序是什么样的:
Bean的创建顺序是由BeanDefinition的注册顺序来决定的,当然依赖关系也会影响Bean创建顺序。
BeanDefinition的注册顺序依赖于配置或注释解析的顺序决定。
注释的解析顺序:@Configuration@Component@Import@Bean
(9)spring中配置元数据的方式注释是如何取代配置文件:
a.应用层:
springXML:
Spring容器:ClassPathXMLApplicationContext(“xml”)
配置内容:spring.xml
配置bean:
扫描包:
引入外部属性配置文件:
指定其他配置文件:
javaConfig:
spring容器:AnnotationConfigApplicationContext(javaconfig.class)
配置内容:@Configuration
配置Bean:@Bean @Scope
扫描包:@ComponentScan
引入外部配置文件:@PropertySource(“classPath:xx.properties”)
其他配置文件:@Import
b.代码:
在进行IoC的过程中,调用的spring上下文容器和注册BeanDefinition springXML与JavaConfig方法不同。
spring上下文容器:
xml配置调用ClassPathXMLApplicationContext
javaConfig调用AnnotationConfigApplicationContext
调用配置读取器:
xml读取配置文件采用xmlBeanDefinitionReader
javaConfig读取配置文件采用AnnotationBeanDefinitionReader
定义信息解析器:
xml采用loadBeanDefinition
javaConfig采用ConfigurationClassParser
(10)@Component@Controller@Repository@Service的区别:
@Controller@Repository@Service都是使用@Component注释作为bean实现标识,但是为了符合MVC设计逻辑,将该组件分为三类分别标识控制层,业务逻辑层,DAO层
(11)@Import可以有几种用法:
根据指定内容不同,可以分为三种
直接指定类:如果该类为配置类则按照配置解析方式解析,如果为普通类,解析为Bean
指定ImprotSelector类:通过实现ImportSelector可以一次注册多个,通过返回的字符串数组为每个字符串对应的类创建bean。
指定ImportBeanDefinitionRegistrar类:通过实现ImportBeanDefinitionRegistrar,一次注册多个BeanDefinition
(12)如何让自动注入没有找到依赖Bean时不报错:
对使用@Autowired自动注入的依赖Bean设置required=false属性
(13)如何让自动注入找到多个依赖Bean时不报错:
在需要重复实现的实例bean上,使用@Primary定义作为主要的实例化bean,进行自动依赖注入。
(14)关于@Autowired注解:
a.@Autowired的作用:
自动装配或手动装配属性,默认为no不采用自动装配,需要设置ref属性用来寻找需要注入的依赖。
自动装配通过byType的方法获取set方法中类名,并获取参数对应实例化对象bean为属性注入依赖。
b.@Autowired和@Resource的区别:
@Autowired为spring的注释,@Resource为jdk提供的注释
@Autowired自动装配默认先通过类型查找Bean实例,@Resource自动装配默认先通过bean实例名进行查找,后查找所属的类。
c.@Autowired的注入过程:
在创建一个spring上下文的时候在构造器中注册AutowiredAnnotationBeanPostProcessor,在bean的后置处理器中进行解析需要注入的属性。
预解析需要注入的属性的bean类型和bean实例名。
为属性注入真正的解析(根据预解析的类型和类型名,在IoC容器中匹配):首先根据属性类型进行匹配,如果查找失败,根据属性名匹配,否则抛出异常。
(15)@Configuration注解的作用及解析原理:
@Configuration注解与@Bean共同完成配置bean的过程,但是没有@Configuration也能实现标识Bean并实例化的过程。@Configuration的作用主要是使spring能调用配置类解析器解析bean的配置为其创建增强代理类。
原理解析:
创建spring上下文的时候会注册一个解析配置的处理器(ConfigurationClassPostProcessor包括BeanFactoryPostProcessor和BeanDefinitionRegistryPostProcessor);
在spring准备工作完成后,对实例化bean前需要对bean的定义信息进行注册,通过调用invokeBeanFactoryPostProcessor,调用ConfigurationClassPostProcessor的BeanFactoryPostProcessor和BeanDefinitionRegistryPostProcessor对bean类进行注册和解析。
由ConfigurationClassPostProcessor的BeanFactoryPostProcessor去创建cglib增强动态代理,保证单例模式运行。
(16)spring如何保证@Bean之间的方法调用如何保证单例:
此题使用@Configuration和@Bean之间的关系,即@Configuration的作用;
(17)如何将第三方类配置为Bean?
使用@Configuration+@Bean
@Import(xxx.class),无法操作第三方类的实例化过程,不建议使用。
@Import(xxx.class),指定类实现ImportBeanDefinitionRegistrar,直接注册beanDefinition
实现BeanDefinitionRegistryPostProcessor调用postProcessBeanDefinitionRegistry注册beanDefinition
(18)为什么@ComponentScan不设置basePackage也会扫描?
@ComponentScan不设置basePackage时会将该类所在的包作为扫描包的地址。
(19)AOP,面向切面编程
a.什么AOP,能做什么?
AOP(aspect-oriented Programming)面向切面编程,将业务处理的过程中的与业务无关但对多个对象产生影响的公共行为或逻辑抽取为一个可重用的模块,即切面。例如:日志、事务处理等。
OOP与AOP是两种完全不同的设计思想,OOP面向对象编程,将业务处理过程中涉及的实体的属性和方法进行抽象封装,以此获得更清晰的逻辑单元。AOP是对对象的补充,将业务处理中的公共行为提取出来,与业务逻辑代码相互隔离,并降低耦合度。
b.解释一下aop中的名词:
切面:在aop中的切面类,管理公共代码(通知)和切点。
连接点:需要被增强的方法。
通知:需要增加到业务代码中的公共行为,根据在需要增强的方法中的执行位置分为前置通知、后置通知、环绕通知、异常通知、返回通知。
切点:根据切点表达式确定需要增强的连接点和不需要增强的连接点。
目标对象:被增强的方法的对象。
织入(weaving):
将切面加入连接点的方法,在aspectj中独有的说法,springAOP中的织入方式为创建动态代理,通过动态代理达到增强的功能即织入。
c.spring通知有哪些类型:
在spring中aop通知根据通知连接点执行的位置不同,分为5种。
前置通知(before):在目标方法被执行前调用通知功能。
后置通知(after):在目标方法执行后调用通知,不会关心方法的输出是什么。
返回通知(after-returning):在目标方法执行成功后调用通知。
异常通知(after-throwing):在目标方法抛出异常后调用通知。
环绕通知(Around):通知包裹目标方法,在目标方法调用前和调用后执行自动义动作。
执行顺序:
正常情况:前置通知、方法执行、后置通知、返回通知
异常情况:前置通知、方法执行、后置通知、异常通知
在spring5.2.7版本后:
正常情况:前置通知、方法执行、返回通知、后置通知
异常通知:前置通知、方法执行、异常通知、后置通知
d.springAOP和aspectjAOP的区别:
AOP代理分为静态代理和动态代理。springAOP与AspectjAOP的区别就是代理的方式不同。
springAOP使用动态代理,默认使用基于接口的JDK动态代理。可以通过手动设置使用基于对象的CGLIB动态代理。
AspectJAOP使用静态代理,在编译阶段将AOP框架生成的代理类加入类编译文件.class中。运行时使用增强后的AOP对象。
e.实现AOP的几种方法:
基于接口的配置,实现AOP各通知的接口交给advisor管理;
基于XML配置,利用命名空间进行管理。
基于注释配置,引入AspectJ依赖,使用@Aspect注解。
使用AspectJ静态代理实现,需要在编译阶段织入代理类。
f.aop失效的原因及解决方法:
当被代理方法在代理类的内部调用其他代理类的方法时,aop失效。
使用CGLIB动态代理;
使用@EnableAspectJAutoProxy(exposeProxy=true)将代理类暴露在线程中,通过在线程中调用.currentProxy方法调用当前代理类,调用其方法。
在被代理类内部注入一个自己的接口,被代理方法通过此接口调用被代理类的其他方法。
g.spring在哪里创建的AOP动态代理:
spring为bean初始化后会使用BeanPostProcessor的postProcessorAfterInitilization创建aop动态代理。
spring属性注入时,如果遇到循环依赖,为了打破依赖,会为实例化的bean创建动态代理。
h.AOP的实现过程:
以JavaConfig为例:
首先由@EnableAspectJAutoProxy通过@Import注册一个BeanPostProcessor处理AOP
在初始化bean前由BeanPostProcessor解析通知和切点并保存为Advisor对象。
在初始化Bean后BeanPostProcessor调用advisor,根据切点表达式判断需要进行代理的类,创建动态代理,(jdk或cglib)
调用代理类调用代理方法,并依次执行通知。
(20)JDK动态代理和CGLIB动态代理的区别:
JDK动态代理在运行时先创建一个proxy.class的动态代理类,实现被代理类的接口,对接口中方法进行增强。在调用代理是先去调用处理类进行增强,在通过反射调用目标方法。
CGLIB动态代理会利用ASM为被代理类动态生成一个子类,重写被代理类方法进行增强。在调用动态代理时,先调用代理类进行增强,再调用父类对应的方法,实现aop。被final修饰的类无法CGLIB动态代理。
(21)如何强制使用CGLIB动态代理:
在需要配置的@Bean类配置文件上声明@EnableAspectJAutoProxy设置属性proxyTargetClass=true
(22)spring事务:
a.事务的四大特性:
原子性:事务的所有操作要么成功,要么全部失败回滚。
一致性:事务提交后数据必须与提交前保持一致。
隔离性:在多发用户并发访问同一数据时,数据库为每位用户开启事务相互隔离,互不干扰。
持久性:事务的提交数据在数据库中无法修改,不会丢失。
b.事务的管理类型以及实现方式:
编程式事务管理:通过编写代码管理事务,使用setAutoCommit();commit();rollback();方法控制事务的提交回滚。
声明式事务管理:将业务代码和事务管理分离,使用注释和xml配置事务。例如使用@Transactional注解管理事务,使用命名空间配置事务。
c.事务的传播特性:
所谓事务的传播特性指一个事务在调用另一个事务时,这个事务的进行方法。
spring默认情况采用REQUIRED,外部存在事务融入外部事物,外部不存在事务创建事务。适用于增删改查
与required完全相反的传播行为为NOT_SUPPORTED既不开启事务也不融入事务。不常用
与required类似的传播行为为SUPPORTS外部不存在事务不开启事务,外部存在事务融入事务,适用于查询
REQUIRES_NEW无论外部是否存在事务都会开启、创建新事务。
d.spring事务的隔离级别:
脏读:一个事务读取了另一个事务未提交的内容导致数据不一致。
隔离级别:读已提交:READ COMMITTED,只能读取另一个事务已经提交的数据。
不可重复读:一个事务读取另一个事务提交的数据,导致前后读取不一致。
隔离级别:可重复读:REPEATABLE READ,事务进行时禁止其他事务进行更新操作。
幻读:一个事务读取另一个事务插入的数据,导致前后读取不一致。
隔离级别;串行化:serializable,事务进行时禁止其他事务进行增删改的操作。
e.spring事务的实现原理:
解析切面:在bean初始化前,需要Bean的后置处理器先解析bean的切面信息advisor,内包含切点和通知信息,由@EnableTransactionManagement定义的配置类配置advisor。
创建动态代理:bean初始化后利用BeanPostProcessor的方法为bean创建动态代理,根据advisor中的切点信息和@Transactional标注的类是否有继承接口,判断在何处创建代理以及创建jdk动态代理还是cglib动态代理。
调用动态代理:
在spring中事务大致由aop和数据库的底层代码组成。
try{
与数据库建立连接Connection,设置autoCommit自动提交为false。
执行目标方法,进行数据库操作。
}catch{
如果目标方法出现异常,事务回滚,否则仍然提交。
}
执行完成如果没有异常执行commit方法提交事务。
f.spring的事务传播行为实现原理:
事务相互调用会产生传播行为,基于外部是否有事务,内嵌事务有种传播操作,嵌入和创建新的事务。
在spring默认的事务传播行为REQUIRED中,需要进行一下操作。
//融入行为
try{
1.外部事务:创建Connction,放入ThreadLocal,设置自动提交为false,返回事务的状态信息(TransactionInfo.newTransaction==true/connction信息/隔离级别等)
2.外部事务:执行目标方法(另一个事务)执行sql。
3.内嵌事务:判断TreadLocal是否已经拥有Connction,判断是否为内嵌事务,判断传播行为
4.融入下内嵌事务:不会创建Connction,直接返回事务信息(TransactionInfo.newTransaction==false)
5.内嵌事务:执行目标方法,进行数据库操作。
}catch(){
出现异常,回滚或提交事务。
}
6.内嵌事务:在无异常出现时,执行完成先判断事务信息newTransaction==true,执行提交,否则执行外部事务。
7.外部事务:判断事务信息newTransaction==true,获取threadLocal中Connection信息执行提交。
//创建新事务行为
try{
1.外部事务:创建Connction,放入ThreadLocal,设置自动提交为false,返回事务的状态信息(TransactionInfo.newTransaction==true/connction信息/隔离级别等)
2.外部事务:执行目标方法(另一个事务)执行sql。
3.内嵌事务:判断TreadLocal是否已经拥有Connction,判断是否为内嵌事务,判断传播行为
4.创建新事务下内嵌事务:先将外部事务信息暂存,将ThreadLocal置空,创建新Connction放入ThreadLocal,返回事务信息(TransactionInfo.newTransaction==true,外部事务暂存)
5.内嵌事务:执行目标方法,进行数据库操作。
}catch(){
出现异常,回滚或提交事务。
}
6.内嵌事务:在无异常出现时,执行完成先判断事务信息newTransaction==true,执行提交,判断事务暂存,将暂存信息放入ThreadLocal对象,否则执行外部事务。
7.外部事务:判断事务信息newTransaction==true,获取threadLcoal中Connection信息执行提交。
g.Spring多线程事务能否保证事务的一致性:
由于每个线程只能使用一个ThreadLocal所以一个线程同时只能存在一个事务,当两个事务相互嵌套一个事务失败是无法通过另一个事务操作回滚。
可以通过编程时手动控制提交和回滚或通过分布式事务思路。
h.事务失效的原因:
与AOP失效的原因类似:
如果代理方法为private,就会失效
如果目标类不是Bean会失效。
如果在事务中调用本事务的其他方法也会失效。
i.spring事件监听器是什么?核心机制是什么?
spring监听器包括事件、监听器、播放器组成。利用设计模式观察者模式进行实现。
事件(application Event):对应相应的事件监听器,用来触发事件监听器内部的逻辑。
监听器(applicationListener):对应观察者,在特定事件发生时启动相应逻辑。
事件发布器(applicationMulticaster):对应观察者模式中的被观察者。提供增删改监听器和发布事件的接口,在事件发生时通知监听器。spring内部以及定义直接通过.publishEvent()发布事件。
事件发布分为同步和异步,默认为同步,使用@Asyc+@EnableAsyc实现异步,spring通过多线程实现事件监听器的异步。
(23)spring使用了哪些设计模式:
简单工厂模式-BeanFactory
工厂方法模式-FactoryBean
单例模式-Bean实例默认
代理模式-AOP代理
观察者模式-spring事件监听器
责任链模式-AOP方法调用
装饰者模式-BeanWrapper
策略模式-excludeFilter、IncludeFilter
适配器模式-HandlerAdapter
模板方法模式-Spring几乎所有的外界扩展都采用
(24)spring是如何配置管理Mybatis中mapper的:
由于在spring中mapper都是操作xml文件中mysql语句的接口,所以spring创建动态代理使用的是JDK动态代理。但是在注册前,由invokeBeanFactoryPostProcessor解析扫描定义信息,ClassPathBeanDefinitionScanner在扫描配置类文件后会将接口定义信息排除,所以spring在BeanDefinitionRegistryPostProcessor扩展点扩展对接口的定义信息解析。
通过继承spring内部的ClassPathBeanDefinitionScanner,重写isCandidateComponent方法,BeanDefinitionRegistryPostProcessor调用自定义的扫描器的重写方法,注册接口信息到BeanDefinitionMap。
Mybatis通过FactoryBean的工厂方法模式自由控制Bean的实例化过程,通过getObject()方法创建jdk动态代理,将接口的BeanDefinition的BeanClass替换为jdk动态代理类实例。
(25)SpringMvc:
a.如何解决springMVC中get和post的乱码问题:
数据库乱码,设置数据库编码为工程编码(UTF-8);
在springMVC的配置文件中写入filter类,类型为CharacterEncodingFilter,变量值为UTF-8。
在request获得数据直接指定为UTF-8:
String username=new String(request.getParamter(“username”),“UTF-8”);
tomcat中文件乱码,在tomcat的配置文件中配置<connectorURIEncoding=“UTF-8”>
b.springMVC的控制器是不是单例,如果是怎么解决?
springMVC控制器也作为bean放入spring容器中管理,默认的spring的bean为单例的,在多线程程序中,是不安全的。
此问题类似于bean的线程安全问题,解决方法可以将bean类的注入属性放入方法中,理论上可以解决线程安全问题,但是不实用。
方法:将springBean的作用范围改为原型模式。
将控制器中的属性放入ThreadLocal中。
使用synchronized同步锁,不建议使用,影响性能。
c.springMVC中DispatcherServlet的工作流程:
dispatcherServlet为控制前端的核心控制器。
用户通过前端发送请求request
DispatcherServlet获取请求头中的地址,通过HandlerMapping处理映射器获取Handler后端方法以及拦截器返回
DispatcherServlet调用HandlerAdapter处理适配器获取相应的Handler处理器,处理前端数据请求。返回ModelAndView
DispatcherServlet调用ViewResolver视图解析器,返回view
DispatcherServlet解析view,并利用model的数据渲染为html呈现给用户。
d.springMVC如何实现AJAX相互调用?
加入jackson.jar依赖
在配置文件中配置json的消息转换器(jackson不需要配置HttpMessageConverter)
<mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>
在接收ajax数据的方法中可以直接返回Object或集合数据,但是需要加注释@ResponseBody。
e.spring和springmvc为什么需要父子容器?
划分框架边界、完成单一职责:spring管理后端的service和dao,springMVC管理前端的controller。
规范整体架构:父框架的service无法调用子框架中的controller,子框架中的controller可以调用service。
方便子容器的切换:将web层中的springMVC替换为struts,只需将配置文件springmvc.xml替换为struts.xml,不需要改变spring-core.xml.
f.springmvc能否将所有的bean注册到spring中?
不可以。springMVC控制器在获得前端的请求,HandlerMapping获取当前容器的所有beanDenifitionName来解析@RequestMapping标识的方法注册为HandlerMethod,通过前端发送的请求体中的URI找到对应的方法返回。
如果将所有bean交给父容器管理,由于没有在父容器中查找bean,无法注册@RequestMapping方法,无法根据uri返回handler。
g.如何实现零配置的springMVC?原理是:
在servlet3.0中,提供SPI扩展方式,在META-INFO文件中,自动调用ServletContainerInitializer实现类,该实现类需要实现ContextLoaderListenerSpring的上下文容器和DispatcherServlet的springMVC上下文容器。
由spring实现ServletContainerInitializer,并自动配置父子容器配置类和实现ContextLoaderListener、DispatcherServlet父子容器。
h.spring的过滤器与拦截器的区别?执行顺序?
springMVC拦截器依赖与servlet容器,过滤器不依赖与servlet容器。
springMVC拦截器对dispatcherServlet映射的请求起作用,过滤器对所有请求都起作用。
拦截器可以访问spring容器中的bean,过滤器(需要手动注册)不行。
执行顺序:
在tomcat容器中:
过滤器filter->servlet->拦截器interceptor->controller
(24)springBoot
a.对springBoot的理解,及特性
springBoot是一个用来简化新spring应用初始化搭建及开发过程的脚手架。
springboot的特性:
springboot对所有第三方依赖管理,防止依赖冲突。
springBoot中内置web容器,不需依赖外部web服务器直接引入jar包就可以启动web应用。
springBoot采用JavaConfig方式开发,省略xml配置。
springBoot提供内置Starter配合自动配置,对主流框架无配置集成。
springBoot内置监听器,对应用程序进行监听,包括线程池、内存、Http请求统计等,springBoot对默认线程进行stop操作,所有线程完毕后关闭程序。
b.spring与springBoot的区别:
spring生态中有许多框架,通常我们说的spring指springFramework,springFrame是一种容器框架,内部可以集成多个其他开源非开源框架。
springBoot不是框架,springBoot的目的是减少springframework配置其他开源框架的过程,简化开发。
c.springBoot的核心注解:
@SpringBootApplication一般用来标注启动类,由三个其他注解组合。
@SpringBootConfiguration,由@Configuration标识。
@EnableAutoConfiguration,向spring容器中导入Selector用来加载自动配置类,加载为Bean
@Conditional用来自定义应用中进行定制开发(@ConditionalOnBean,在spring容器中是否有bean类
程序是否拥有某个类才能启动@ConditionalOnClass)
d.springBoot的自动配置流程:
通过@SpringbootApplication引入@EnableAutoConfiguration。
@EnableAutoConfiguration通过@Improt引入一个deferredImportSelector,延迟导入选择器,该类保证所有自动配置类被调用是spring加载Bean后的工作,方便扩展和覆盖
springIoC加载@Impot,执行DeferredImportSelector,读取所有classpath中的/META-INF/spring.factories文件。
过滤出所有AutoConfigurationClass类
通过@ConditionalOn类型的注解排除不需配置的类。
e.为什么springBoot的jar可以直接运行:
在springBoot中有一个spring-boot-maven-plugin用于将程序打包未jar
这个jar又称为Fat jar,其中包含所有的依赖jar和spring boot Loader类。
通过Java -jar xxx启动jar,先找到jar包中的manifest文件,里面有main-class和其他启动类。
由JarLauncher创建一个LaunchedURLClassLoader加载所有boot-lib下的jar包,创建线程启动main函数。
f.springBoot的启动流程:(内容过于简化)
初始化信息:运行main方法,初始化springApplication,从spring.factories读取applicationListener和applicationContextInitializer,将main方法作为mainApplicationClass配置类保存。
运行run()方法,
通过事件读取环境变量和配置信息。
实例化spring上下文,AnnotationConfigServletWebServerApplicationContext
准备上下文,发布初始化事件,注册BeanDefinition。
运行refash()刷新IoC容器,调用invokeBeanFactoryPostProcessor读取配置类,加载自动配置。
通过onfresh(),(Tomcat)创建内置Servlet容器dispatcherServlet。
(过程中发布众多事件比如:ApplicationStartingEvent应用启动,ApplicationEnvironmentPreparedEvent环境准备事件,ApplicationContextInitializedEvent上下文初始化事件,ApplicationPreparedEvent添加监听器事件,ApplicationStartedEvent应用已启动事件,ApplicationReadyEvent应用准备完成事件,ApplicationFailedEvent应用启动失败异常事件)
g.springBoot内置tomcat的启动原理:
准备工作:
使用内置tomcat需要引入web环境启动器依赖Spring-boot-starter-web,该依赖为springBoot添加ServletWebServerFactoryAutoConfiguration servlet的自动配置类
该自动配置类通过@Import引入web服务器的配置,通过@Conditional系列注解判断所使用的服务器,使用相应的服务器工厂创建WebServer。默认使用Tomcat。
springBoot工作:
调用springApplication.run(),调用refresh(),调用invokeBeanFactoryPostProcessor加载ServletWebServerFactoryAutoConfiguration
利用AnnotationConfigServletWebServerApplicationContext上下文调用CreateWebServer创建web服务器,通过getWebServerFactory通过工厂创建webserver
创建完成后,.start()直接启动,设置为挂起状态等待用户请求
h.springBoot外置tomcat的启动原理:
启动前的准备:
将springBoot的打包方式设置为war,使用exclusion排除内置tomcat依赖。
引入web依赖,servlet容器在启动时加载在MATE-INF中service中的SpringServletContainerInitializer,SpringServletContainerInitializer在运行时将@HandlesTypes()标注的类的子类作为参数传入onStartUp方法中
实例化springBootServletInitializer,重写configure方法,将springBootApplication的启动类作为参数传入,方便后期调用run方法。
运行时:
servlet容器在启动时加载MATE-INF中的service的SpringServletContainerInitializer
springServletContainerInitializer通过@HandlesType获取SpringBootServletInitializer作为参数的onStartUp方法。
由onStartUp方法调用springBootServletInitializer的configure方法,最终调用springBootApplication的run方法。
i.springBoot配置文件的原理及配置顺序:
通过事件监听读取配置文件,由ConfigFileApplicationListener监听事件并读取配置文件。
配置顺序:根据优先级高低,高优先级取代低优先级的配置
<ul>
<li>file:./config/</li>当前jar包的相对路径里面的配置文件
<li>file:./config/xxx/</li>当前jar包中任意文件里面的配置文件
<li>file:./</li>当前jar包下的配置文件
<li>classPath:config/</li>类路径(默认为Resources)下相对路径的配置文件
<li>classPath:./</li>当前类路径下的配置文件
</ul>
j.springBoot的默认日志实现框架是什么?怎么切换:
默认日志实现框架时logback,springBoot的场景启动器spring-boot-starter默认添加starter-logging的场景启动器所以必须加入默认日志实现框架。
将logback切换为slf4j,加入slf4j的适配器和桥接器。适配器用来指向日志实现,桥接器用来转换其他日志框架的日志实现。
对于需要切换为log4j2的日志框架实现,直接使用exclusion排除logback的日志依赖,引入log4j2的场景启动器依赖即可。
(25)微服务(springcloud)
a.微服务的优缺点:
优点
分工协作:
单体:项目启动慢;每个人对整体项目有把握;业务缩减后语言不一致导致的人员流失
拆分:单个业务启动快;专人处理专事专注自己的服务;充分利用项目开发人员
并发能力:
单体:整体功能集群无法评测最大并发量,所有功能聚集一起,无法准确预估扩容的服务器。已造成系统资源浪费。
拆分:只需对单个业务进行压测能准确预测具体业务承受的并发量最高水准,从而更准确的扩容。
维护能力:
单体:由于业务的膨胀,后续功能变得牵一发而动全身,难以维护。
拆分:业务拆分责任明确,维护简单。
容错:
单体:对整体而言,一个功能出现错误OOM(栈溢出),所有功能无法使用
拆分:单个业务错误可以进行熔断隔离,防止影响其他业务。
缺点:
分布式:微服务在使用调度中心时,需要面临调用失败的风险,这对程序员的要求较高。
运维复杂性:如果单体的业务复杂度为10,在实施后复杂度变为100,即使优化后也比单体复杂。
重复劳动:多个服务可能会使用同一个功能,而这个功能没有强大到成为一个业务,导致每个开发团队重复单独开发该功能。
b.SOA、分布式、微服务之间的关系与区别:
分布式架构是将单体架构中的各个部分拆分,部署到不同的机器或进程中去,SOA和微服务都是基于分布式架构的。
SOA是面向服务的架构,系统所有服务注册到总线上,由总线查找服务信息然后调用。
微服务是更彻底的面向服务的架构,将系统的功能抽成单个应用程序,每个应用对应一个服务的架构。
c.微服务如何拆?何时拆?(内容简化)
如何拆:
高内聚低耦合,职责单一但粒度适中不能划分过细。
根据模块划分,比如用户、订单模块、商品详细模块。
根据微服务迭代优化划分业务。
拆分时机:
发现业务增加迅速,拆分业务
在业务中发现有异构的技术栈。
公司基础设施完善,但业务开发初期设计复杂。
d.微服务常用组件:
注册中心:管理服务,根据服务名动态配置服务信息方便调用(eureka、nacos、zookeeper、consul)
负载均衡器:根据负载均衡器客户端调用来自注册中心多个服务器业务。Ribbon、loadBalancer
服务调用:像调用本地方法一样调用远程服务。Feign、openFeign、Dubbo RPC
配置中心: 统一配置服务 Spring Cloud Config/Nacos Config
服务熔断:保证应用高可用,防止出现服务雪崩。Hystrix、sentinel
分布式事务:seata
网关:为客户端提供统一服务,将一些与业务本身无关的逻辑放在网关,路由转发、日志、鉴权。Zuul1/Zuul2/springcloudGateway/Linkred/Kong
链路追踪:实时追踪服务,监控状况,协助服务快速恢复。skywalking、zipKin
e.注册中心核心功能原理是什么:
服务注册:在服务启动时,会发送一个rest请求向Nacos Server注册自己的服务。
服务心跳:Nacos Client会维护一个定时心跳持续通知Nacos Server,默认5s一次,如果15秒没接收服务的心跳就会设置服务健康状态为false,在去服务器获取健康服务时忽视。
服务发现:Nacos Client会定时去服务器获取健康服务列表。
服务停止:Nacos Client会主动通过Rest请求向Nacos Server发送一个注销请求。
f.谈谈nacos的配置中心:
将所有的service的配置文件.yml的配置放入nacos Config配置文件中,比如redis、数据库的配置文件。实现对配置的集中管理提高开发效率。
对配置文件的修改不需要每个服务都修改,提高维护性和时效性,开发人员无法读取保存的配置文件中的数据提高安全性。
nacos Config的运行原理:
在nacos的client有一个定时任务,该任务会让nacos client的缓存池拉取nacos server的数据,通过与nacos client中cacheDate数据的md5编码比较,如果不同就修改CacheDate数据,由监听器执行具体的逻辑代码。
nacos 配置中心的拉的优势:
如果使用推的方式由服务器发送数据,服务器需要维持一个有效的长连接的状态,会耗费资源,而拉的方式只需要通过无状态的http请求获取服务器的数据。
g.网关的功能:
在业务比较复杂的系统中将网关分为流量网关和业务网关,业务网关就是指狭义的api网关,主要完成与业务相关的部分功能,与流量网关相比更接近系统。在简单的系统中,可以将两系统合二为一。
流量网关:完成一些与后端业务无关的部分。全局性的流量控制,日志统计,防止sql注入,防止web攻击,屏蔽工具扫描,黑白ip名单,安全证书/加密解密处理
业务网关:完成与业务系统相关的部分功能。服务级流量控制,服务降级熔断,路由与负载均衡、灰度策略、服务过滤、聚合与发现,权限验证与用户等级策略,业务规则与参数校验,多级缓存策略。
h.服务的雪崩效应:
理论来说,由于服务提供者无法承载过大的并发导致服务以及连通共享的服务奔溃,最终导致其他服务奔溃所有服务不可用的效应,就叫服务雪崩效应。
解决方法
使用熔断机制:当一个服务挂了,被影响服务能及时熔断,使用fallback数据保证流程在非关键服务不可用的情况下
通过线程池和队列机制实现异步化,允许服务快速失败,当一个服务过慢而阻塞,被影响服务可以超时快速失败,不影响调用链路。
i.服务熔断和服务降级:
都是对服务雪崩的措施。
服务熔断是指服务A调用奔溃的服务B,为了不受服务B的影响,与服务B断开连接。
服务降级是指在服务B奔溃后,采取兜底措施进行补救,直到服务B恢复正常通讯。
j.seata实现原理(以AT(Auto Transaction)为例):
seata使用二阶段提交的方式处理事务,由三个角色完成事务逻辑:
TC:Transaction Coodinator事务协调者,接收事务注册,提交和回滚。
TM:Transaction Manager事务管理者,管理TC进行事务的开始、提交、回滚。
RM:resource Manager资源管理者,管理分支事务处理资源,通过与TC互动注册并报告分支事务,驱动分支事务提交或回滚。
seata事务模式:AT、TCC、SAGA、XA
原理:以AT为例:
第一阶段:以Update secret set name=“RAS” WHERE name= "SSL"//id=1
为例
seata原理第一阶段
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5OHnH5si-1667552285721)(C:\Users\13064\Desktop\personal resume\java\seata原理第一阶段.jpg)]
TM向TC申请全局事务,全局事务定义一个XID,用来保证不同的微服务在同一个实务中。
解析SQL语句,得到执行语句的类型Update,表secret,条件where。
执行前的镜像:通过重组语句查询sql语句执行前的数据。
执行sql:更新操作。
执行后的镜像:通过前镜像查询语句,根据id获得执行后语句。
插入回滚日志:将执行前后镜像数据以及sql语句的信息保存在Undo_Log中。
RM向TC注册分支,申请表secret,id行的全局锁。
本地事务提交:将Undo_Log以及提交结果发送给TC
TM向TC发起全局提交或回滚的决议,决定阶段二的操作。
第二阶段:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DNlxJkbn-1667552285722)(C:\Users\13064\Desktop\personal resume\java\seata原理第二阶段提交.jpg)]
TC权衡全局事务下的事务提交和回滚请求。
TC开启一个任务分支,发送事务提交请求,请求保存在一个异步任务队列中,并返回提交成功。
异步任务分支在发送提交请求同时,根据第一阶段的插入回滚日志的分支id查找Undo_Log。
分批删除Undo_Log。
事务提交。
第三阶段:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BFAcq7AZ-1667552285722)(C:\Users\13064\Desktop\personal resume\java\seata原理第二阶段回滚.jpg)]
TC权衡全局事务下的提交和回滚请求。
收到TC的回滚请求,开启本地事务。
通过XID和插入回滚日志时采用的分支id查找Undo_Log。
通过比较Undo_Log中后镜像文件和当前数据,确定是否被全局事务外的事务修改,如果发生转入人工处理,否则继续执行。
根据前镜像文件和SQL信息重组更新sql语句,执行。
提交本地事务,将执行结果返回TC。
k.微服务如何快速定位排错:
使用spring boot admin可视化界面显示出现错误的服务,查看出现的异常信息,根据日志信息查看是否为数据库、注册中心,是否进行熔断。
使用skywalking定位熔断点,查看日志,获取通知。
l.Ribbon
RandomRule:随机选择一个服务实例
RoundRobinRule:线性轮询策略,按照指定顺序选择服务实例。
RetryRule:重复策略,重复使用RoundRobinRule策略获取服务实例,在deadline重复时间内没有获得服务实例就会返回null。
weightedResponseTimeRule:在ribbon刚启动时,使用RoundRobinRule选取服务实例,在获取足够信息后切换WeightedResponseTimeRule,以平均响应时间为权重,响应时间越短权重越高,被选中的概率越高。
BestAvailableRule:根据自己保存的服务实例过滤失效的服务实例,找出并发请求最小的服务实例来使用,如果没有服务实例的状态,使用父类的线性轮询策略。
ZoneAvoidanceRule:Ribbon的默认策略,根据server所在的区域性能以及server的可用性选择服务器,如果没有区域使用RandomRule选择服务实例。
m.服务如何实现降级如何限流,怎么配?
在电子商城中,客户在获取商品信息时,默认调用秒杀和积分服务,而秒杀服务的需要查看客户的爱好与需求当当访问量过高时,为了防止雪崩,可以对积分服务限流,对秒杀服务中对客户爱好的并发查看延迟使用或暂停使用(降级)。
服务发现:Nacos Client会定时去服务器获取健康服务列表。
服务停止:Nacos Client会主动通过Rest请求向Nacos Server发送一个注销请求。
f.谈谈nacos的配置中心:
将所有的service的配置文件.yml的配置放入nacos Config配置文件中,比如redis、数据库的配置文件。实现对配置的集中管理提高开发效率。
对配置文件的修改不需要每个服务都修改,提高维护性和时效性,开发人员无法读取保存的配置文件中的数据提高安全性。
nacos Config的运行原理:
在nacos的client有一个定时任务,该任务会让nacos client的缓存池拉取nacos server的数据,通过与nacos client中cacheDate数据的md5编码比较,如果不同就修改CacheDate数据,由监听器执行具体的逻辑代码。
nacos 配置中心的拉的优势:
如果使用推的方式由服务器发送数据,服务器需要维持一个有效的长连接的状态,会耗费资源,而拉的方式只需要通过无状态的http请求获取服务器的数据。
g.网关的功能:
在业务比较复杂的系统中将网关分为流量网关和业务网关,业务网关就是指狭义的api网关,主要完成与业务相关的部分功能,与流量网关相比更接近系统。在简单的系统中,可以将两系统合二为一。
流量网关:完成一些与后端业务无关的部分。全局性的流量控制,日志统计,防止sql注入,防止web攻击,屏蔽工具扫描,黑白ip名单,安全证书/加密解密处理
业务网关:完成与业务系统相关的部分功能。服务级流量控制,服务降级熔断,路由与负载均衡、灰度策略、服务过滤、聚合与发现,权限验证与用户等级策略,业务规则与参数校验,多级缓存策略。
h.服务的雪崩效应:
理论来说,由于服务提供者无法承载过大的并发导致服务以及连通共享的服务奔溃,最终导致其他服务奔溃所有服务不可用的效应,就叫服务雪崩效应。
解决方法
使用熔断机制:当一个服务挂了,被影响服务能及时熔断,使用fallback数据保证流程在非关键服务不可用的情况下
通过线程池和队列机制实现异步化,允许服务快速失败,当一个服务过慢而阻塞,被影响服务可以超时快速失败,不影响调用链路。
i.服务熔断和服务降级:
都是对服务雪崩的措施。
服务熔断是指服务A调用奔溃的服务B,为了不受服务B的影响,与服务B断开连接。
服务降级是指在服务B奔溃后,采取兜底措施进行补救,直到服务B恢复正常通讯。
j.seata实现原理(以AT(Auto Transaction)为例):
seata使用二阶段提交的方式处理事务,由三个角色完成事务逻辑:
TC:Transaction Coodinator事务协调者,接收事务注册,提交和回滚。
TM:Transaction Manager事务管理者,管理TC进行事务的开始、提交、回滚。
RM:resource Manager资源管理者,管理分支事务处理资源,通过与TC互动注册并报告分支事务,驱动分支事务提交或回滚。
seata事务模式:AT、TCC、SAGA、XA
原理:以AT为例:
第一阶段:以Update secret set name=“RAS” WHERE name= "SSL"//id=1
为例
seata原理第一阶段
TM向TC申请全局事务,全局事务定义一个XID,用来保证不同的微服务在同一个实务中。
解析SQL语句,得到执行语句的类型Update,表secret,条件where。
执行前的镜像:通过重组语句查询sql语句执行前的数据。
执行sql:更新操作。
执行后的镜像:通过前镜像查询语句,根据id获得执行后语句。
插入回滚日志:将执行前后镜像数据以及sql语句的信息保存在Undo_Log中。
RM向TC注册分支,申请表secret,id行的全局锁。
本地事务提交:将Undo_Log以及提交结果发送给TC
TM向TC发起全局提交或回滚的决议,决定阶段二的操作。
第二阶段提交:
TC权衡全局事务下的事务提交和回滚请求。
TC开启一个任务分支,发送事务提交请求,请求保存在一个异步任务队列中,并返回提交成功。
异步任务分支在发送提交请求同时,根据第一阶段的插入回滚日志的分支id查找Undo_Log。
分批删除Undo_Log。
事务提交。
第二阶段回滚:
TC权衡全局事务下的提交和回滚请求。
收到TC的回滚请求,开启本地事务。
通过XID和插入回滚日志时采用的分支id查找Undo_Log。
通过比较Undo_Log中后镜像文件和当前数据,确定是否被全局事务外的事务修改,如果发生转入人工处理,否则继续执行。
根据前镜像文件和SQL信息重组更新sql语句,执行。
提交本地事务,将执行结果返回TC。
k.微服务如何快速定位排错:
使用spring boot admin可视化界面显示出现错误的服务,查看出现的异常信息,根据日志信息查看是否为数据库、注册中心,是否进行熔断。
使用skywalking定位熔断点,查看日志,获取通知。
l.Ribbon
RandomRule:随机选择一个服务实例
RoundRobinRule:线性轮询策略,按照指定顺序选择服务实例。
RetryRule:重复策略,重复使用RoundRobinRule策略获取服务实例,在deadline重复时间内没有获得服务实例就会返回null。
weightedResponseTimeRule:在ribbon刚启动时,使用RoundRobinRule选取服务实例,在获取足够信息后切换WeightedResponseTimeRule,以平均响应时间为权重,响应时间越短权重越高,被选中的概率越高。
BestAvailableRule:根据自己保存的服务实例过滤失效的服务实例,找出并发请求最小的服务实例来使用,如果没有服务实例的状态,使用父类的线性轮询策略。
ZoneAvoidanceRule:Ribbon的默认策略,根据server所在的区域性能以及server的可用性选择服务器,如果没有区域使用RandomRule选择服务实例。
m.服务如何实现降级如何限流,怎么配?
在电子商城中,客户在获取商品信息时,默认调用秒杀和积分服务,而秒杀服务的需要查看客户的爱好与需求当当访问量过高时,为了防止雪崩,可以对积分服务限流,对秒杀服务中对客户爱好的并发查看延迟使用或暂停使用(降级)。
对服务的降级和限流可以使用QPS进行衡量(用压测),也可以使用并发量进行衡量