远程桌面登陆 vue laravel listview sharepoint junit angular material uicollectionview vue教程 vue遍历 java并发编程视频 mysql倒序 拼接json字符串 hadoop组件 html下拉框默认选中 mysql建表主键自增长 车载u盘 python计算器 python中count python简易教程 如何配置python环境 linux配置python环境 java开发 java8的新特性 java八种基本数据类型 java8函数式编程 php实例代码 按钮制作 bash命令 hexworkshop win10有哪些版本 猫眼电影票 vue引入第三方js wegame更新失败 证书小精灵 windowsjs延时函数 粉碎文件工具 中文微信小程序API adb安装 ae渲染设置
当前位置: 首页 > 学习教程  > 编程语言

为爱而面

2021/2/13 17:07:17 文章标签: 测试文章如有侵权请发送至邮箱809451989@qq.com投诉后文章立即删除

为爱而面 软件工程 软件生命周期 1.问题定义 2.可行性研究 3.需求分析 4.总体设计 5.详细设计 6.编码测试 7.验收 8.运维 Java基础 java三大特性 封装: 将类的某些信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法来实现对…

为爱而面

软件工程

软件生命周期

1.问题定义

2.可行性研究

3.需求分析

4.总体设计

5.详细设计

6.编码测试

7.验收

8.运维

Java基础

java三大特性

封装:

将类的某些信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法来实现对隐藏信息的操作和访问,常见的实现方式就是:getter、setter。

封装遵循了“开闭原则”,禁止外部直接访问和修改类的信息。

继承:

继承是类与类的一种关系,子类拥有父类的所有属性和方法(除了private修饰的属性不能拥有)从而实现了实现代码的复用。

多态:

多态主要指引用多态和方法多态

引用多态:父类引用指向子类对象

方法多态:方法重写、重载

对象的概念

万物皆可为对象,对象是类的实例,什么是类呢,类就是拥有相等功能和相同的属性的对象的集合是一个抽象的概念,假如说人是一个类,那你我就是一个对象他具体到了真实世界存在的一个实体。

创建对象

1.使用new关键字创建对象

Student s = new Student();

2.使用Class类的newInstance方法(反射)

Student s = (Student)Class.forName("Student包名.类名").newInstance();
或者
Student s = Student.class.newInstance();

3.使用Constructor类的newInstance方法(反射)

Constructor<Stundent> constructor = Stundent.class.getConstructor.newInstance("构造方法的参数");
Student s = constructor.newInstance();

4.使用ClassLoader类的loadClass转class的newInstance方法(反射)

ClassLoader cl = this.getClass().getClassLoader();
Class c = cl.loadClass("com.smartyouth.controller.Person");
Person p = (Person)c.newInstance();

5.使用Clone方法创建对象(要实现Cloneable接口重写clone方法)

public class Student implements Cloneable{

    private int id;

    public Student(Integer id) {
        this.id = id;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        // TODO Auto-generated method stub
        return super.clone();
    }

    public static void main(String[] args) throws Exception {

        Constructor<Student> constructor = Student.class
                .getConstructor(Integer.class);
        Student stu3 = constructor.newInstance(123);
        Student stu4 = (Student) stu3.clone();
    }
}

6.使用(反)序列化机制创建对象(必须实现Serializable接口)

public class Student implements Serializable {

    private int id;

    public Student(Integer id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return "Student [id=" + id + "]";
    }

    public static void main(String[] args) throws Exception {

        Constructor<Student> constructor = Student.class
                .getConstructor(Integer.class);
        Student stu3 = constructor.newInstance(123);

        // 写对象
        ObjectOutputStream output = new ObjectOutputStream(
                new FileOutputStream("student.bin"));
        output.writeObject(stu3);
        output.close();

        // 读对象
        ObjectInputStream input = new ObjectInputStream(new FileInputStream(
                "student.bin"));
        Student stu5 = (Student) input.readObject();
        System.out.println(stu5);
    }
}

基本类型与封装类

byte(8),short(16),int(32),long(64),float(32),double(64),boolean,char(16位unicode)

区别

基本类型只能按值传递,封装类都是按引用传递

封装类可以更精确的对其变量控制,具有基本类型不具备的方法例如xxxValue(),toString(),Integer.parseInt()

类型转换

byte,short,char—> int — > long—> float —> double

低转高自动类型转换;高转低强制类型转换(xxx)

String,StringBuilder,StringBuffer

区别

String:值是不可变的,每次操作都会生成新的String对象,效率低

StringBuffer:值可变,是线程安全的字符串操作类

StringBuilder: 值可变,线程不安全效率快

String常用方法

charAt(int index) 返回指定索引处的值

concat(String str) 将指定字符串连接到该字符串的末尾

contains(CharSequence cs) 字符串包含指定的char值序列时返回true

equals(Object anObject) 将此字符串与指定对象进行比较

getBytes(String chatsetName) 使用给定的编码将String编码为字节序列,存储到新的字节数组中

replace(char oldChar,char newChar) 返回替换后的字符串

split(String regex,int limit) 将此字符串拆分为数组

substring(int beginIndex,int endIndex) 返回两个索引之间的字符串

toLowerCase() toUpperCase() 小写 大写展示

trim() 删除前后空格

ValueOf(int i) 返回int参数的字符串其他类型相同

StringBuffer、StringBuilder常用方法:

append(String str) 将String类型的字符串添加到序列中

insert(int index,String str) 在指定序列添加字符串

reverse() 将字符串序列反转

Date格式化

String format = "yyyy-MM-dd";
String str = "20090909";
DateFormat df = new SimpleDateFormat("yyyyMMdd");
DateFormat df2 = new SimpleDateFormat(format);
try {
    Date d = df.parse(str);
    System.out.println(df2.format(d));
} catch (ParseException e) {
    e.printStackTrace();
}

组合与继承

组合将所有部分类对象创建在组合类中,具有良好的扩展性,松耦合

继承子类继承了父类的接口,子类依赖父类高耦合

凡是需要向上转型的必须使用继承

向上转型

父类引用指向子类对象

public class Father{
    public static void test(Father father){
        System.out.println("子类对象向上转型为父类");
    }
}
public class Son extends Father{
    public static void main(String args[]){
        Son son = new Son();
        Father.test(son);
    }
}

接口与类的区别

  • 接口不能实例化。
  • 接口没有构造方法。
  • 接口中所有的方法必须是抽象方法。
  • 接口不能包含成员变量,除了 static 和 final 变量。
  • 接口不是被类继承了,而是要被类实现。
  • 接口支持多继承(接口的继承)。

抽象类和接口的区别

  • 抽象类中的方法可以有方法体,就是能实现方法的具体功能,但是接口中的方法不行。
  • 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的。
  • 接口中不能含有静态代码块以及静态方法(用 static 修饰的方法),而抽象类是可以有静态代码块和静态方法。
  • 一个类只能继承一个抽象类,而一个类却可以实现多个接口。

java内存机制

img

img

程序计数器(线程私有)

一块较小的内存空间, 是当前线程所执行的字节码的行号指示器,每条线程都要有一个独立的程序计数器,这类内存也称为“线程私有”的内存。

正在执行 java 方法的话,计数器记录的是虚拟机字节码指令的地址(当前指令的地址)。如果还是 Native 方法,则为空。

这个内存区域是唯一一个在虚拟机中没有规定任何OutOfMemoryError 情况的区域。

虚拟机栈(线程私有)

是描述java 方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧(Stack Frame) 用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

栈帧( Frame)是用来存储数据和部分过程结果的数据结构,同时也被用来处理动态链接(Dynamic Linking)、 方法返回值和异常分派( Dispatch Exception)。栈帧随着方法调用而创建,随着方法结束而销毁——无论方法是正常完成还是异常完成(抛出了在方法内未被捕获的异常)都算作方法结束。

本地方法区(线程私有)

本地方法区和Java Stack 作用类似, 区别是虚拟机栈为执行 Java 方法服务, 而本地方法栈则为Native 方法服务, 如果一个VM 实现使用C-linkage 模型来支持Native 调用, 那么该栈将会是一个C 栈,但 HotSpot VM 直接就把本地方法栈和虚拟机栈合二为一。

堆(Heap-线程共享)运行时数据区

是被线程共享的一块内存区域,创建的对象和数组都保存在 Java 堆内存中,也是垃圾收集器进行垃圾收集的最重要的内存区域。由于现代 VM 采用分代收集算法, 因此 Java 堆从GC 的角度还可以细分为: 新生代(Eden From Survivor To Survivor )和老年代

方法区/永久代(线程共享)

即我们常说的永久代(Permanent Generation), 用于存储被JVM 加载的类信息、常量、静态变量、即时编译器编译后的代码等数据. HotSpot VM 把GC 分代收集扩展至方法区, 即使用Java堆的永久代来实现方法区, 这样 HotSpot 的垃圾收集器就可以像管理 Java 堆一样管理这部分内存, 而不必为方法区开发专门的内存管理器(永久带的内存回收的主要目标是针对常量池的回收类型的卸载, 因此收益一般很小)。

运行时常量池(Runtime Constant Pool)是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述等信息外,还有一项信息是常量池

(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。 Java 虚拟机对 Class 文件的每一部分(自然也包括常量池)的格式都有严格的规定,每一个字节用于存储哪种数据都必须符合规范上的要求,这样才会被虚拟机认可、装载和执行。

内部类

如果你需要生成对外部类对象的引用,可使用外部类的名字后面紧跟圆点和this。这样产生的引用自动地具有正确的类型。

public class Outer{
    class Inner{
        Outer getOut(){
            return Outer.this;
        }
    }
}

如果你想告知其他对象,去创建其某个内部类的对象,要实现此目的必须在new表达式中提供对其他外部类的引用,这就需要使用.new语法。(但是你这个内部类是个嵌套类的话(静态内部类)正常创建即可)

public class Outer{
    class Inner{
    }
    public static void main(String[] args){
        Outer o  = new Outer();
        Outer.Inner i = o.new Inner();
    }
}

数组与集合

区别

1.数组是大小固定的,一旦创建无法扩容;集合大小不固定,

2.数组的存放的类型只能是一种,集合存放的类型可以不是一种(不加泛型时添加的类型是Object);

3.数组是java语言中内置的数据类型,是线性排列的,执行效率或者类型检查,都是最快的.

LinkedList与ArrayList的区别

ArrayList基于动态数组实现,LinkedList基于链表实现

LinkedList增加、删除快(类中包含了 first 和 last 两个指针(Node)。Node 中包含了上一个节点和下一个节点的引用,这样就构成了双向的链表),相反ArrayList增删必须重新copy数组效率低下

ArrayList随机访问查询快(其实就是对数组的封装,通过数组下标)

HashSet、LinkedHashSet和TreeSet区别

数据是唯一的(这是因为HashSet中定义了一个HashMap对象,调用add方法的时候用的HashMap将set中的值当做key来存储,问题来到Map的key值为什么是唯一的因为key是用hash值来对比的,相同的后边的会将前边的替换,所以当set存储对象和map存储key键对象的时候,那个类必须要重写equals和hashcode否则会将相同对象都添加到集合当中去产生hash冲突)

HashSet存储的数据过程中是无序的(但是我之前发现过它存储的数据是根据hash值大小排列的)

LinkedHashSet(继承了HashSet类)存储的数据是按照插入顺序来排序的

HashSet增删改查效率都是相对快的(LinkedHashSet毕竟又增添了一层链表数据结构)

TreeSet其实是个TreeMap底层是一个红黑树是一棵二叉搜索树,如果需要一个排序的Set,选择TreeSet

HashMap、LinkedHashMap和TreeMap的区别

Map中作为对象作为key键来存储必须重写equals和hashcode

HashMap中k的值没有顺序(其实内部是按hash值排序的),常用来做统计,查询快。

LinkedHashMap继承了HashMap。它内部有一个链表,保持Key插入的顺序。迭代的时候,也是按照插入顺序迭代,而且迭代比HashMap快。

循环遍历

1.在 for 循环中使用 entries 实现 Map 的遍历(最常见和最常用的)。

Map<String, String> map = new HashMap<String, String>();
    map.put("Java入门教程", "http://c.biancheng.net/java/");
    map.put("C语言入门教程", "http://c.biancheng.net/c/");
    for (Map.Entry<String, String> entry : map.entrySet()) {
        String mapKey = entry.getKey();
        String mapValue = entry.getValue();
        System.out.println(mapKey + ":" + mapValue);
    }

2.使用 for-each 循环遍历 key 或者 values,一般适用于只需要 Map 中的 key 或者 value 时使用。性能上比 entrySet 较好。

Map<String, String> map = new HashMap<String, String>();
map.put("Java入门教程", "http://c.biancheng.net/java/");
map.put("C语言入门教程", "http://c.biancheng.net/c/");
// 打印键集合
for (String key : map.keySet()) {
    System.out.println(key);
}
// 打印值集合
for (String value : map.values()) {
    System.out.println(value);
}

3.使用迭代器(Iterator)遍历

Map<String, String> map = new HashMap<String, String>();
map.put("Java入门教程", "http://c.biancheng.net/java/");
map.put("C语言入门教程", "http://c.biancheng.net/c/");
Iterator<Entry<String, String>> entries = map.entrySet().iterator();
while (entries.hasNext()) {
    Entry<String, String> entry = entries.next();
    String key = entry.getKey();
    String value = entry.getValue();
    System.out.println(key + ":" + value);
}

4.通过键找值遍历,这种方式的效率比较低,因为本身从键取值是耗时的操作。

for(String key : map.keySet()){
    String value = map.get(key);
    System.out.println(key+":"+value);
}

Collections

fill(List<? super T> list, T obj) 用指定元素代替指定列表的所有元素

shuffle(List<List<?> list) 打乱排序

sort(List<List<?> list) 排序

swap(List<?> list,int i,int j) 交换指定列表中指定位置的元素

synchronizedXxx(Xxx xx) 返回线程同步集合

集合算法

排序、洗牌、搜索、常规数据操作、构成、找极值

集合数据结构

动态数组\哈希表\链表\红黑树\二叉树

异常和错误

异常类的根类为Throw

Error:应用程序非常严重的错误,不可修复

Exception:程序正常运行可预料的,可修复的。

Exception又分为

“受检查的异常”例如io异常会自动提醒你抛出异常。

“不受检查的异常”例如数组下标越界 空指针等异常。

常见的Error:

OutOfMemoryError 内存不足

ThreadDeath 线程死亡

VirtualMachineError 虚拟机错误

常见的Exception:

NullPointerException 空指针异常 ArrayIndexOutOfBoundsException 数组下标越界异常, ArithmaticException 算数异常 如除数为零 IllegalArgumentException 不合法参数异常

ParseException 解析异常

ConcurrentModificationException异常

初始化迭代器会对ArrayList中的迭代器里的modCount赋值,由于add()在其之后会为modCount+1,当用it.next()会检查modCount的值,不同就会抛出此异常

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UJVvAYoI-1613205663130)(https://i.loli.net/2021/02/13/FHIihz6wLE3jSrf.png)]

IO

字符流与字节流的差异

字符流处理的单元为2个字节的Unicode字符,分别操作字符、字符数组或字符串,而字节流处理单元为1个字节,操作字节和字节数组。所以字符流是由Java虚拟机将字节转化为2个字节的Unicode字符为单位的字符而成的,所以它对多国语言支持性比较好!如果是音频文件、图片、歌曲,就用字节流好点,如果是关系到中文(文本)的,用字符流好点

读文件的差异

当每次读取的数据量很小时,FileInputStream每次都是从硬盘读入,而BufferedInputStream大部分是从缓冲区读入。读取内存速度比读取硬盘速度快得多,因此BufferedInputStream效率高。
BufferedInputStream的默认缓冲区大小是8192字节。当每次读取数据量接近或远超这个值时,两者效率就没有明显差别了。

多线程

线程的生命周期

线程要经历新建、就绪、运行(活动)、阻塞和死亡五种不同的状态。

这五种状态都可以通过Thread类中的方法进行控制。① 新建状态 、使用new 操作符创建一个线程后,该线程仅仅是一个空对象,这时的线程处于创建状态。② 就绪状态 、使用start()方法启动一个线程后,系统为该线程分配了除CPU外的所需资源,使该线程处于就绪状态。③ 运行状态 、系统真正执行线程的run()方法。④ 阻塞和唤醒线程阻塞状态 、使用sleep(),wait()方法进行操作。⑤ 死亡状态 、线程执行了interrupt()或stop()方法,那么它也会以异常退出的方式进入死亡状态。

同步式线程调度 VS 抢占式线程调度

协同式线程调度抢占式线程调度
控制权线程本身(线程执行完后,主动通知系统切换)系统决定
优点1.切换操作线程已知,控制简单 2.不存在线程同步问题线程执行时间可控,不会因为一个线程耽误整个进程
缺点执行时间不可控,一个线程可能耽误整个进程1.切换控制复杂 2.存在线程同步问题

创建线程的方式

1.继承Thread类

public class ThreadSon extends Thread{
    public static void main(String[] args) {
        ThreadSon ts = new ThreadSon();
        ts.start();
    }
}

2.实现Runable接口

public class RunableImpl implements Runnable{
    @Override
    public void run() {
        System.out.println("RunableImpl");
    }

    public static void main(String[] args) {
        Thread t = new Thread(new RunableImpl(),"RunableImpl");
        t.start();
    }
}

3.实现Callable接口(带有返回值)

public class CallableImpl implements Callable<String> {
    @Override
    public String call() throws Exception {
        return "CallableImpl";
    }
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CallableImpl ci = new CallableImpl();
        FutureTask<String> ft = new FutureTask<>(ci);
        new Thread(ft,"CallableImpl").start();
        /*ExecutorService executorService = Executors.newCachedThreadPool();
        executorService.submit(ft);//实现Callable接口的只能用submit*/
        System.out.println(ft.get());
        //executorService.shutdown();
    }
}

4.通过线程池

public class ExecutorServiceTest {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        //executorService.execute(()-> System.out.println("Lambda Create Thread"));
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程池");
            }
        });
        executorService.shutdown();
    }
}

volatile(内存可见性)

当多个线程操作共享数据时,可以保证内存中的数据可见。用这个关键字修饰共享数据,就会及时的把线程缓存中的数据刷新到主存中去(他可直接操作内存中的数据,而不是拿到自己线程去单独修改再放回主存去)

原子变量

在java.util.concurrent.atomic包下

  • 有volatile保证内存可见性。
  • 用CAS算法保证原子性。
public class TestIcon {
    public static void main(String[] args){
        AtomicDemo atomicDemo = new AtomicDemo();
        for (int x = 0;x < 10; x++){
            new Thread(atomicDemo).start();
        }
    }
}

class AtomicDemo implements Runnable{
    private int i = 0;
    public int getI(){
        return i++;
    }
    @Override
    public void run() {
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(getI());
    }
}
//会有重复值打印
//改造
 //private int i = 0;
 AtomicInteger i = new AtomicInteger();
 public int getI(){
     return i.getAndIncrement();
 }

CAS算法

CAS算法是计算机硬件对并发操作共享数据的支持,CAS包含3个操作数:

  • 内存值V
  • 预估值A
  • 更新值B

当且仅当V==A时,才会把B的值赋给V,即V = B,否则不做任何操作。

synchronized

防止多个线程同一时间调用此代码块或者方法.

  • 修饰一个,其作用的范围是synchronized后面括号括起来的部分, 作用的对象是这个类的所有对象
  • 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法, 作用的对象是调用这个方法的对象
  • 修改一个静态的方法,其作用的范围是整个静态方法, 作用的对象是这个类的所有对象
  • 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码, 作用的对象是调用这个代码块的对象

锁分段

ConcurrentHashMap默认分成了16个segment,每个Segment都对应一个Hash表,且都有独立的锁。所以这样就可以每个线程访问一个Segment,就可以并行访问了,从而提高了效率。这就是锁分段。

乐观锁、悲观锁

乐观锁:每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据。这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。

悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。再比如Java里面的同步原语synchronized关键字的实现也是悲观锁。

闭锁

ContDownLatch是一个同步辅助类,在完成某些运算时,只有其他所有线程的运算全部完成,当前运算才继续执行,这就叫闭锁。

由于调用了 countDown() 方法,所以在当前计数到达零之前,await 方法会一直受阻塞。之后,会释放所有等待的线程,await 的所有后续调用都将立即返回。

Lock同步锁

Lock需要通过lock()方法上锁,通过unlock()方法释放锁。为了保证锁能释放,所有unlock方法一般放在finally中去执行。

public class TestLock {
    public static void main(String[] args) {
        Ticket td = new Ticket();
        new Thread(td, "窗口1").start();
        new Thread(td, "窗口2").start();
        new Thread(td, "窗口3").start();
    }
}
class Ticket implements Runnable {
    private Lock lock = new ReentrantLock();
    private int ticket = 100;
    @Override
    public void run() {
        while (true) {
            lock.lock();
            try {
                //synchronized (TestLock.class) {
                if (ticket > 0) {
                    try {
                        Thread.sleep(200);
                    } catch (Exception e) {
                    }
                    System.out.println(Thread.currentThread().getName() + "完成售票,余票为:" + (--ticket));
                }
                //}
            }finally {
                lock.unlock();
            }
        }
    }
}

ReadWriterLock读写锁

如果有两个线程,写写/读写需要互斥,读读不需要互斥。这个时候可以用读写锁。

public class TestReadWriterLock {
    public static void main(String[] args){
           ReadWriterLockDemo rw = new ReadWriterLockDemo();
           new Thread(new Runnable() {//一个线程写
               @Override
               public void run() {
                   rw.set((int)Math.random()*101);
               }
           },"write:").start();
           for (int i = 0;i<100;i++){//100个线程读
               Runnable runnable = () -> rw.get();
               Thread thread = new Thread(runnable);
               thread.start();
           }
    }
}

class ReadWriterLockDemo{
    private int number = 0;
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    //读(可以多个线程同时操作)
    public void get(){
        readWriteLock.readLock().lock();//上锁
        try {
            System.out.println(Thread.currentThread().getName()+":"+number);
        }finally {
            readWriteLock.readLock().unlock();//释放锁
        }
    }
    //写(一次只能有一个线程操作)
    public void set(int number){
        readWriteLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName());
            this.number = number;
        }finally {
            readWriteLock.writeLock().unlock();
        }
    }
}

死锁

死锁:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。
 由Edsger Dijkstrar提出的哲学家就餐问题是一个经典的死锁例证。要修正死锁问题,你必须明白,当以下四个条件同时满足时,就会发生死锁:

  1. 互斥条件。任务使用的资源中至少有一个是不能共享的。这里,一根Chopstick(筷子)一次就只能被一个Philosopher(哲学家)使用。
  2. 至少有一个任务它必须持有一个资源且正在等待获取一个当前被别的任务持有的资源。也就是说,要发生死锁,Philosopher必须拿着一根Chopstick并且等待另一根。
  3. 资源不能被任务抢占,任务必须把资源释放当作普通事件。Philosopher很有礼貌,他们不会从其他Philosopher那里抢占Chopstick。
  4. 必须有循环等待,这时,一个任务等待其他任务所持有的资源,后者又在等待另一个任务所持有的资源,这样一直下去,直到有一个任务在等待第一个任务所持有的资源,使得大家都被锁住。在DeadlockingDiningPhilosophers.java中,因为每个Philosopher都试图先得到右边的Chopstick,然后得到左边的Chopstick,所以发徨了循环等待。

备注:所以要防止死锁的话,只需破坏其中一个即可。防止死锁最容易的方法是破坏第4个条件。

线程池的理解

池化技术的思想主要是为了减少每次获取资源的消耗,提高对资源的利用率。

线程池提供了一种限制、管理资源的策略。 每个线程池还维护一些基本统计信息,例如已完成任务的数量。

使用线程池的好处:

  • 降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  • 提高响应速度:当任务到达时,可以不需要等待线程创建就能立即执行。
  • 提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,监控和调优。

newFixThreadPool只有核心线程(数量固定不会被回收),并且数量固定的,也不会被回收,所有线程都活动时,因为队列没有限制大小,新任务会等待执行。

newSingleThreadPool只有一个核心线程,确保所有任务都在同一线程中按顺序执行,不需要处理线程同步的问题

newCachedThreadPool只有非核心线程(闲置超时会被回收),最大线程数非常大,所有线程都活动时,会为新任务创建新线程,否则利用空闲线程(60s空闲时间,过了就会被回收,所以线程池中有0个线程的可能)处理任务。适合执行大量的耗时较少的任务

newScheduledThreadPool核心线程数固定,非核心线程数没有限制。主要用于执行定时任务以及没有固定周期的重复任务。

线程练习题

顺序打印10以内的奇偶数

public class Demo {
    private static int count = 0;
    private static final Object lock = new Object();

    public static void main (String[] args) {
        try {
        	new Thread(new TurningRunner(),"偶数").start();
            Thread.sleep(1);
            new Thread(new TurningRunner(),"奇数").start();

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    static class TurningRunner implements Runnable {
        @Override
        public void run() {
            while (count <= 10) {
                synchronized (lock) {
                    System.out.println(Thread.currentThread().getName() + ":" + count++);
                    lock.notifyAll();
                    try {
                        if (count <= 10) {
                            lock.wait();
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}
public class SortPrintNum {
    private static int count = 0;
    private static Lock lock = new ReentrantLock();
    static class TurningRunner implements Runnable {
        @Override
        public void run() {
            while (count <= 10) {
                lock.lock();
                try {
                    System.out.println(count++);
                } finally {
                    lock.unlock();
                }
            }
        }
    }
    public static void main(String[] args) {
        try {
            new Thread(new Demo.TurningRunner(),"偶数").start();
            Thread.sleep(1);
            new Thread(new Demo.TurningRunner(),"奇数").start();

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

编写一个程序开三个线程,分别为A,B,C将自己name在屏幕打印10遍,结果按顺序显示如ABCABC

同步方法

public class SortPrintDemo1 {
    public static void main(String[] args) {
        AlternationDemo ad = new AlternationDemo();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i=0;i<10;i++){
                    ad.printA();
                }
            }
        },"A").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i=0;i<10;i++) {
                    ad.printB();
                }
            }
        },"B").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i=0;i<10;i++) {
                    ad.printC();
                }
            }
        },"C").start();
    }
    static class AlternationDemo{
        private int i = 1;
        public synchronized void printA() {
            while (i!=1) {
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName());
            i = 2;
            this.notifyAll();
        }
        public synchronized void printB() {
            while (i!=2) {
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName());
            i = 3;
            this.notifyAll();
        }
        public synchronized void printC() {
            while (i!=3) {
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName());
            i = 1;
            this.notifyAll();
        }
    }
}

Lock锁

public class SortPrintDemo2 {
    public static void main(String[] args) {
        AlternationDemo ad = new AlternationDemo();
        new Thread(() -> {
            for (int i=0;i<10;i++){
                ad.printA();
            }
        },"A").start();
        new Thread(() -> {
            for (int i=0;i<10;i++) {
                ad.printB();
            }
        },"B").start();
        new Thread(()-> {
                for (int i=0;i<10;i++) {
                    ad.printC();
            }
        },"C").start();
    }
    static class AlternationDemo{
        private int flag = 1;
        private Lock lock = new ReentrantLock();
        Condition condition1 = lock.newCondition();
        Condition condition2 = lock.newCondition();
        Condition condition3 = lock.newCondition();
        public void printA() {
            lock.lock();
            try {
                while (flag != 1) {
                    condition1.await();
                }
                System.out.println(Thread.currentThread().getName());
                flag = 2;
                condition2.signal();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }

        public void printB() {
            lock.lock();
            try {
                while (flag != 2) {
                    condition2.await();
                }
                System.out.println(Thread.currentThread().getName());
                flag = 3;
                condition3.signal();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }

        public void printC() {
            lock.lock();
            try {
                while (flag != 3) {
                    condition3.await();
                }
                System.out.println(Thread.currentThread().getName());
                flag = 1;
                condition1.signal();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }
}

反射

类的加载

java文件是咱们编写的代码文件,.class文件是字节码文件是由java源文件通过JVM编译后生成的文件。

当一个类未被加载到内存中,JVM会通过加载、连接、初始化三步对类进行初始化。

1.加载:将类的class文件读入到内存中,通过类加载器加载你所需要的类并将类的信息加载到jvm方法区中,然后在堆中实例化一个class对象,并为之创建一个class对象,作为方法区中这个类信息的入口。

2.连接:细分为三步。

​ 验证:验证这个类是否合法,是否符合字节码格式、变量名是否重复

​ 准备:为类的静态变量分配内存并设为jvm的初值(int 就是0),对于非静态变量不会为他们分配内存

​ 解析:jvm会将所有的类或接口名、字段名、方法名转换为具体的内存地址。

3.初始化

  • 通过new关键字实例化对象、读取或设置类的静态变量、调用类的静态方法。
  • 通过class的newInstance()方法。
  • 使用Constructor类的newInstance方法。
  • 使用Clone方法创建对象
  • 使用(反)序列化机制创建对象

双亲委派

当一个类收到了类加载请求,他首先不会尝试自己去加载这个类,而是把这个请求委派给父类去完成

优点:使用不同的类加载器最终得到的都是同样一个 Object 对象。

获取class实例

Person person = new Person();
//1
Class c = Person.class;
//2
Class d = person.getClass();
//3
Class e = Class.forName("com.smartyouth.controller.Person");
//4
ClassLoader cl = c.getClassLoader();
Class f = cl.loadClass("com.smartyouth.controller.Person");
Person p = (Person)f.newInstance();

注解

@Override:用在方法上,表示方法重写了父类的方法,若父类没有此方法编译无法通过

@Deprecated 表示这个方法已经过期,不建议开发者使用

@SuppressWarnings({ “rawtypes”, “unused” })英文的意思是抑制的意思,这个注解的用处是忽略警告信息。
比如大家使用集合的时候,有时候为了偷懒,会不写泛型

@SafeVarargs 这是1.7 之后新加入的基本注解. 如例所示,当使用可变变量参数的时候,而参数的类型又是泛型T的话,就会出现警告。 这个时候,就使用@SafeVarargs来去掉这个警告

@FunctionalInterface这是Java1.8 新增的注解,用于约定函数式接口(接口只有一个抽象方法),主要配合Lambda表达式

网络编程

在网络通信协议下,实现网络互连的不同计算机上运行的程序间进行数据交换。

网络编程三要素

IP地址、端口、协议(UDP、TCP)

IP地址

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l0rNrE6u-1613205663132)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20200817161155326.png)]

InetAddress

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T136Xoh9-1613205663133)(https://i.loli.net/2021/02/13/nBwC8A54pPJqTo2.png)]

端口

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sPnqaEFm-1613205663134)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20200817161603435.png)]

协议

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3SUR6avl-1613205663135)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20200817161650621.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FMJIhvdP-1613205663135)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20200817161723527.png)]

UDP

  • 用户数据报协议(User Datagram Protocol)
  • UDP是无连接通信协议,在数据传输时,数据的发送端和接受端不建立逻辑连接。(即大送端不会确认接收端是否存在,就会发出数据;接收端收到数据时,也不会向发送端反馈是否收到数据。
  • UDP协议消耗资源小,通信效率高,通常会用于音频、视频和普通数据的传输。
  • UDP面向无连接性,不能保证数据的完整性,因此在传输重要数据时不建议使用UDP协议

TCP

  • 传输控制协议(Transimission Control Protocol)

  • TCP协议是面向连接的通信协议,即发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。TCP连接中需明确客户端与服务器端,每次连接的创建都需要经过“三次握手”

  • 三次握手:TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠

    第一次:客户端向服务器发出连接请求,等待服务器的确认

    第二次:服务端向客户端会送一个响应,通知客户端收到了连接请求

    第三次:客户端再次向服务器端发送确认信息,确认连接

UDP

它在通信的两端各建立一个Socket对象,但这两个Socket只是发送,接受数据的对象

Java提供了DatagramSocket类作为基于UDP协议的Socket

UDP发送数据

1.创建发送端的Socket对象(DatagramSocket)

DatagramSocket()

2.创建数据,并把数据打包(DatagramPacket)

DatagramPacket(byte[] buf, int length, InetAddress address, int port)
构造用于发送长度的分组的数据报包 length指定主机上到指定的端口号。

3.调用DatagramSocket对象的方法发送数据

send(DatagramPacket p)

4.关闭发送端

UDP接收数据

1.创建接收端的Socket对象(DatagramSocket)

DatagramSocket(int port)

2.创建一个数据包,用于接受数据

DatagramPacket(byte[] buf, int length)
构造一个 DatagramPacket用于接收长度的数据包 length 。

3.调用DatagramSocket对象的方法接收数据

receive(DatagramPacket p)

4.解析数据包(把数据在控制台显示)

5.关闭接收端

Send:
DatagramSocket ds = new DatagramSocket();
DatagramPacket dp = new DatagramPacket(bys,bys.length,inetAddress,port);
ds.send(dp);
Receive:
DatagramSocket ds = new DatagramSocket();
DatagramPacket dp = new DatagramPacket(bys,bys.length);
ds.receive(dp);

TCP

在通信的两端各建立一个Socket对象,从而在通信的两端形成网咯虚拟链路,一旦建立了虚拟的网络链路,两端的程序就可以通过虚拟链路进行通信。

java对基于TCP协议的网络提供了良好的封装,使用Socket对象来代表两端的通信端口,并通过Socket产生IO流来进行网路通信,java为客户端提供了Socket类,为服务器提供了ServerSocket类。

TCP发送数据

1.创建客户端的Socket对象(Socket)

Socket(String host,int port)

2.获取输出流,写数据

OutputStream getOutputStream()

3.释放资源

void close()

TCP接收数据

  1. 创建服务器端的Socket对象(ServerSocket)

    ServerSocket(int port)

  2. 监听客户端连接,返回一个Socket对象

    Scoket accept()

  3. 获取输入流,读数据,并把数据显示在控制台

    InputStream getInputStream()

  4. 释放资源

    void close()

Server:
ServerSocket ss = new ServerScoket(PORT);
Socket s = ss.accept();
Client:
Socket s = new Socket(IP,PORT)

JavaWeb

servlet的生命周期

初始化 连接 销毁 回收

Servlet 通过调用 init () 方法进行初始化。

Servlet 调用 service() 方法来处理客户端的请求。

Servlet 通过调用 destroy() 方法终止(结束)。

最后,Servlet 是由 JVM 的垃圾回收器进行垃圾回收的。

Servlet单实例,减少了产生servlet的开销;

Get与Post区别

安全性post高,get请求参数显示在地址栏上

携带的数据量post高,get跟url地址栏长度相关(65000)

get请求用来从服务器上获得资源,而post是用来向服务器提交数据

Session和Cookie的区别

Session保存在服务端,Cookie保存在客户端

Session的安全性要高于Cookie

session 能够存储任意的 java 对象,cookie 只能存储 String 类型的对象

Session占用服务器性能,Session过多,增加服务器压力

Session生命周期一般是半个小时,当会话域结束session也会随之消失

单个Cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个Cookie,Session是没有大小限制和服务器的内存大小有关。

Cookie应用场景判断用户是否登录网站,便于下次登录直接登录。

Forward和Redirect

转发在服务器端完成的;重定向是在客户端完成的
转发的速度快;重定向速度慢
转发的是同一次请求;重定向是两次不同请求
转发地址栏没有变化;重定向地址栏有变化
转发必须是在同一台服务器下完成;重定向可以在不同的服务器下完成

JSP有哪些内置对象?以及这些对象的作用分别是什么?

  • request:封装客户端的请求,其中包含来自GET或POST请求的参数;
  • response:封装服务器对客户端的响应;
  • pageContext:通过该对象可以获取其他对象;
  • session:封装用户会话的对象;
  • application:封装服务器运行环境的对象;
  • out:输出服务器响应的输出流对象;
  • config:Web应用的配置对象;
  • page:JSP页面本身(相当于Java程序中的this);
  • exception:封装页面抛出异常的对象。

MySql

事务四个特性

  • 原子性:一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
  • 一致性:事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。(转账金钱是不变的)
  • **隔离性:**数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。
  • **持久性:**事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

主流数据库的区别分页

Mysql

Mysql:使用limit关键字

Select * from 表名 where 条件 limit 开始位置,结束位置。通过动态的改变开始和结束位置的值来实现分页。

Oracle

提供了rownum伪列,oracle系统自动为查询返回结果的每行分配的编号

SELECT * FROM (
     SELECT A.*, ROWNUM RN 
     FROM (SELECT * FROM TABLE_NAME) A 
     WHERE ROWNUM <= 40
)
WHERE RN >= 21

sqlserver

分页方案二:(利用ID大于多少和SELECT TOP分页)效率最高,需要拼接SQL语句

分页方案一:(利用Not In和SELECT TOP分页) 效率次之,需要拼接SQL语句

select top 20 * from addressbook where id not in (select top 10 id from addressbook)

分页方案三:(利用SQL的游标存储过程分页) 效率最差,但是最为通用

防止sql注入

网页获取用户输入的数据并将其插入一个MySQL数据库,那么就有可能发生SQL注入安全的问题

通过把SQL命令插入到Web表单递交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令

  • 1.永远不要信任用户的输入。对用户的输入进行校验,可以通过正则表达式,或限制长度;对单引号和 双"-"进行转换等。
  • 2.永远不要使用动态拼装sql,可以使用参数化的sql或者直接使用存储过程进行数据查询存取。
  • 3.永远不要使用管理员权限的数据库连接,为每个应用使用单独的权限有限的数据库连接。
  • 4.不要把机密信息直接存放,加密或者hash掉密码和敏感的信息。
  • 5.应用的异常信息应该给出尽可能少的提示,最好使用自定义的错误信息对原始错误信息进行包装
  • 6.sql注入的检测方法一般采取辅助软件或网站平台来检测,软件一般采用sql注入检测工具jsky,网站平台就有亿思网站安全平台检测工具。MDCSOFT SCAN等。采用MDCSOFT-IPS可以有效的防御SQL注入,XSS攻击等。

sql语句优化

1.尽量避免使用*

MySql优化

通常以以下几种原则来进行

  1. 找出系统瓶颈,提高MySQL数据库整体的性能;
  2. 合理的结构设计和参数调整,提高数据库操作的相应速度;
  3. 最大限度节省系统资源,以便系统可以提供更大负荷的服务。

Oracale死锁处理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fY5ps2R2-1613205663136)(C:%5CUsers%5CLenovo%5CAppData%5CRoaming%5CTypora%5Ctypora-user-images%5Cimage-20210113152505027.png)]

Spring

Spring是J2EE应用程序框架,是轻量级的IOC和AOP的容器框架,主要针对JavaBean的生命周期进行管理的轻量级容器,可以单独使用,也可以和其他框架等组合使用。

作用域

Spring 容器在初始化一个 Bean 的实例时,同时会指定该实例的作用域。Spring3 为 Bean 定义了五种作用域,具体如下。

1)singleton

单例模式,使用 singleton 定义的 Bean 在 Spring 容器中只有一个实例,这也是 Bean 默认的作用域。

2)prototype

原型模式,每次通过 Spring 容器获取 prototype 定义的 Bean 时,容器都将创建一个新的 Bean 实例。

3)request

一次 HTTP 请求中,容器会返回该 Bean 的同一个实例。而对不同的 HTTP 请求,会返回不同的实例,该作用域仅在当前 HTTP Request 内有效。

4)session

在一次 HTTP Session 中,容器会返回该 Bean 的同一个实例。而对不同的 HTTP 请求,会返回不同的实例,该作用域仅在当前 HTTP Session 内有效。

5)global Session

在一个全局的 HTTP Session 中,容器会返回该 Bean 的同一个实例。该作用域仅在使用 portlet context 时有效。

IOC(控制反转)

改变了之前原有的通过new构造方式来创建对象而是转由容器来负责控制程序间的关系并来创建对象。控制权由应用代码中转到了外部容器,控制权的转移,是所谓反转

AOP(面向切面编程)

面向切面编程,它将业务逻辑的各个部分进行隔离,使开发人员在编写业务逻辑时可以专心于核心业务,从而提高了开发效率。

我们使用AOP来做:

1)事务处理:执行方法前开启事务,执行完成后关闭事务,出现异常后回滚事务

2)权限判断:在执行方法前,判断是否具有权限

3)日志:在执行前进行日志处理、

4)异常处理

核心概念

1、切面(aspect):类是对物体特征的抽象,切面就是对横切关注点的抽象(切入点和通知的结合)

2、横切关注点:对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点。

3、连接点(joinpoint):被拦截到的点,因为 Spring 只支持方法类型的连接点,所以在 Spring 中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器。

4、切入点(pointcut):指要对哪些 Joinpoint 进行拦截,对连接点进行拦截的定义

5、通知(advice):所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类。

6、目标对象:代理的目标对象

7、织入(weave):将切面应用到目标对象并导致代理对象创建的过程

8、引入(introduction):在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段。

SpringBean的生命周期

img

实例化 -> 属性赋值 -> 初始化 -> 销毁

  1. 根据配置情况调用 Bean 构造方法或工厂方法实例化 Bean,也就是我们通常说的new

  2. 利用依赖注入完成 Bean 中所有属性值的配置注入。

  3. 如果Bean实现了BeanNameAware接口,Spring将Bean的ID传递给setBeanName()方法
    (实现BeanNameAware清主要是为了通过Bean的引用来获得Bean的ID,一般业务中是很少有用到Bean的ID的

  4. 如果Bean实现了BeanFactoryAware接口,Spring将调用setBeanDactory(BeanFactory bf)方法并把BeanFactory容器实例作为参数传入。
    (实现BeanFactoryAware 主要目的是为了获取Spring容器,如Bean通过Spring容器发布事件等)

  5. 如果Bean实现了ApplicationContextAwaer接口,Spring容器将调用setApplicationContext(ApplicationContext ctx)方法,把应用上下文作为参数传入.
    (作用与BeanFactory类似都是为了获取Spring容器,不同的是Spring容器在调用setApplicationContext方法时会把它自己作为setApplicationContext 的参数传入,而Spring容器在调用setBeanDactory前需要程序员自己指定(注入)setBeanDactory里的参数BeanFactory )

  6. 如果Bean实现了BeanPostProcessor接口,Spring将调用它们的postProcessBeforeInitialization(预初始化)方法
    (作用是在Bean实例初始化之前进行增强处理,如对Bean进行修改,增加某个功能)

  7. 如果这个Bean在Spring配置文件中配置了init-method属性会自动调用其配置的初始化方法

  8. 如果Bean实现了BeanPostProcess接口,Spring将调用它们的postProcessAfterInitialization(后初始化)方法
    (作用与6的一样,只不过6是在Bean初始化前执行的,而这个是在Bean初始化后执行的,时机不同 )

    注意:以上工作完成以后就可以用这个Bean了,Bean将一直驻留在应用上下文中给应用使用,这个Bean是一个single的,所以一般情况下我们调用同一个ID的Bean会是在内容地址相同的实例

  9. 当 Bean 不再需要时,会经过清理阶段,如果 Bean 实现了 DisposableBean 这个接口,会调用那个其实现的destroy()方法;

  10. 最后,如果这个 Bean 的 Spring 配置中配置了 destroy-method 属性,会自动调用其配置的销毁方法。

注释:bean 标签有两个重要的属性(init-method 和 destroy-method)。用它们你可以自己定制初始化和注销方法。它们也有相应的注解(@PostConstruct 和@PreDestroy)。

依赖注入方式

构造器注入

/*带参数,方便利用构造器进行注入*/ public CatDaoImpl(String message){
this. message = message;
}
<bean id="CatDaoImpl" class="com.CatDaoImpl">
<constructor-arg value=" message "></constructor-arg>
</bean>

setter方法注入

public class Id { private int id;
public int getId() {	return id;	}
public void setId(int id) {	this.id = id; }
}
<bean id="id" class="com.id "> <property name="id" value="123"></property> </bean>

静态工厂注入

public class DaoFactory { //静态工厂
public static final FactoryDao getStaticFactoryDaoImpl(){ return new StaticFacotryDaoImpl();
}
}
public class SpringAction {
private FactoryDao staticFactoryDao; //注入对象
//注入对象的 set 方法
public void setStaticFactoryDao(FactoryDao staticFactoryDao) { this.staticFactoryDao = staticFactoryDao;
}
}
//factory-method="getStaticFactoryDaoImpl"指定调用哪个工厂方法
<bean name="springAction" class=" SpringAction" >
<!--使用静态工厂的方法注入对象,对应下面的配置文件-->
<property name="staticFactoryDao" ref="staticFactoryDao"></property>
</bean>
<!--此处获取对象的方式是从工厂类中获取静态方法-->
<bean name="staticFactoryDao" class="DaoFactory" factory-method="getStaticFactoryDaoImpl"></bean>

实例工厂

public class DaoFactory { //实例工厂public FactoryDao getFactoryDaoImpl(){
return new FactoryDaoImpl();
}
}
public class SpringAction {
private FactoryDao factoryDao;	//注入对象
public void setFactoryDao(FactoryDao factoryDao) { this.factoryDao = factoryDao;
}
}
<bean name="springAction" class="SpringAction">
<!--使用实例工厂的方法注入对象,对应下面的配置文件-->
<property name="factoryDao" ref="factoryDao"></property>
</bean>
<!--此处获取对象的方式是从工厂类中获取实例方法-->
<bean name="daoFactory" class="com.DaoFactory"></bean>
<bean name="factoryDao" factory-bean="daoFactory"
factory-method="getFactoryDaoImpl"></bean>

Spring自动装配的方式有哪些?

autowire属性

  • byName:根据Bean的名字进行自动装配。
  • byType:根据Bean的类型进行自动装配。
  • constructor:类似于byType,不过是应用于构造器的参数,如果正好有一个Bean与构造器的参数类型相同则可以自动装配,否则会导致错误。
  • autodetect:如果有默认的构造器,则通过constructor的方式进行自动装配,否则使用byType的方式进行自动装配。
  <bean id="personService" class="com.mengma.annotation.PersonServiceImpl"
        autowire="byName" />

BeanFactory和ApplicationContext的区别

BeanFactory无法支持spring插件,例如:AOP、Web应用等功能。

ApplicationContext是BeanFactory的子类,,以一种更面向框架的工作方式以及对上下文进行分层和实现继承,并在这个基础上对功能进行扩展:
<1>MessageSource, 提供国际化的消息访问
<2>资源访问(如URL和文件)
<3>事件传递
<4>Bean的自动装配
<5>各种不同应用层的Context实现

AOP代理

Spring 提供了两种方式来生成代理对象: JDKProxy 和 Cglib, 具体使用哪种方式生成由AopProxyFactory 根据 AdvisedSupport 对象的配置来决定。默认的策略是如果目标类是接口, 则使用 JDK 动态代理技术,否则使用 Cglib 来生成代理。

静态代理

通常使用AspectJ的编译时增强实现AOP,AspectJ是静态代理的增强,所谓的静态代理就是AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强。

基于xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="  
            http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans-2.5.xsd  
            http://www.springframework.org/schema/aop
            http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
    <!--目标类 -->
    <bean id="customerDao" class="com.mengma.dao.CustomerDaoImpl" />
    <!--切面类 -->
    <bean id="myAspect" class="com.mengma.aspectj.xml.MyAspect"></bean>
    <!--AOP 编程 -->
    <aop:config>
        <aop:aspect ref="myAspect">
            <!-- 配置切入点,通知最后增强哪些方法 -->
            <aop:pointcut expression="execution ( * com.mengma.dao.*.* (..))"
                id="myPointCut" />
            <!--前置通知,关联通知 Advice和切入点PointCut -->
            <aop:before method="myBefore" pointeut-ref="myPointCut" />
            <!--后置通知,在方法返回之后执行,就可以获得返回值returning 属性 -->
            <aop:after-returning method="myAfterReturning"
                pointcut-ref="myPointCut" returning="returnVal" />
            <!--环绕通知 -->
            <aop:around method="myAround" pointcut-ref="myPointCut" />
            <!--抛出通知:用于处理程序发生异常,可以接收当前方法产生的异常 -->
            <!-- *注意:如果程序没有异常,则不会执行增强 -->
            <!-- * throwing属性:用于设置通知第二个参数的名称,类型Throwable -->
            <aop:after-throwing method="myAfterThrowing"
                pointcut-ref="myPointCut" throwing="e" />
            <!--最终通知:无论程序发生任何事情,都将执行 -->
            <aop:after method="myAfter" pointcut-ref="myPointCut" />
        </aop:aspect>
    </aop:config>
</beans>

基于注解

//切面类
@Aspect
@Component
public class MyAspect {
    // 用于取代:<aop:pointcut
    // expression="execution(*com.mengma.dao..*.*(..))" id="myPointCut"/>
    // 要求:方法必须是private,没有值,名称自定义,没有参数
    @Pointcut("execution(*com.mengma.dao..*.*(..))")
    private void myPointCut() {
    }
    // 前置通知
    @Before("myPointCut()")
    public void myBefore(JoinPoint joinPoint) {
        System.out.print("前置通知,目标:");
        System.out.print(joinPoint.getTarget() + "方法名称:");
        System.out.println(joinPoint.getSignature().getName());
    }
    // 后置通知
    @AfterReturning(value = "myPointCut()")
    public void myAfterReturning(JoinPoint joinPoint) {
        System.out.print("后置通知,方法名称:" + joinPoint.getSignature().getName());
    }
    // 环绕通知
    @Around("myPointCut()")
    public Object myAround(ProceedingJoinPoint proceedingJoinPoint)
            throws Throwable {
        System.out.println("环绕开始"); // 开始
        Object obj = proceedingJoinPoint.proceed(); // 执行当前目标方法
        System.out.println("环绕结束"); // 结束
        return obj;
    }
    // 异常通知
    @AfterThrowing(value = "myPointCut()", throwing = "e")
    public void myAfterThrowing(JoinPoint joinPoint, Throwable e) {
        System.out.println("异常通知" + "出错了" + e.getMessage());
    }
    // 最终通知
    @After("myPointCut()")
    public void myAfter() {
        System.out.println("最终通知");
    }
}
-------------------------------------------------------
    <!--扫描含com.mengma包下的所有注解-->
    <context:component-scan base-package="com.mengma"/>
    <!-- 使切面开启自动代理 -->
    <Serviceaop:aspectj-autoproxy></aop:aspectj-autoproxy>

JDK动态代理

JDK 动态代理主要涉及到 java.lang.reflect 包中的两个类:Proxy 和 InvocationHandler。InvocationHandler 是一个接口,通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编制在一起。Proxy 利用 InvocationHandler 动态创建一个符合某一接口的实例,生成目标类的代理对象。

public class MyBeanFactory {
    public static CustomerDao getBean() {
        // 准备目标类
        final CustomerDao customerDao = new CustomerDaoImpl();
        // 创建切面类实例
        final MyAspect myAspect = new MyAspect();
        // 使用代理类,进行增强
        return (CustomerDao) Proxy.newProxyInstance(
                MyBeanFactory.class.getClassLoader(),
                new Class[] { CustomerDao.class }, new InvocationHandler() {
                    public Object invoke(Object proxy, Method method,
                            Object[] args) throws Throwable {
                        myAspect.myBefore(); // 前增强
                        Object obj = method.invoke(customerDao, args);
                        myAspect.myAfter(); // 后增强
                        return obj;
                    }
                });
    }
}

Cglib

CGLib 全称为 Code Generation Library,是一个强大的高性能,高质量的代码生成类库, 可以在运行期扩展 Java 类与实现 Java 接口,CGLib 封装了 asm,可以再运行期动态生成新的 class。和 JDK 动态代理相比较:JDK 创建代理有一个限制,就是只能为接口创建代理实例, 而对于没有通过接口定义业务方法的类,则可以通过 CGLib 创建动态代理。

Enhancer 类的 setSuperclass()

public class MyBeanFactory {
    public static GoodsDao getBean() {
        // 准备目标类
        final GoodsDao goodsDao = new GoodsDao();
        // 创建切面类实例
        final MyAspect myAspect = new MyAspect();
        // 生成代理类,CGLIB在运行时,生成指定对象的子类,增强
        Enhancer enhancer = new Enhancer();
        // 确定需要增强的类
        enhancer.setSuperclass(goodsDao.getClass());
        // 添加回调函数
        enhancer.setCallback(new MethodInterceptor() {
            // intercept 相当于 jdk invoke,前三个参数与 jdk invoke—致
            @Override
            public Object intercept(Object proxy, Method method, Object[] args,
                    MethodProxy methodProxy) throws Throwable {
                myAspect.myBefore(); // 前增强
                Object obj = method.invoke(goodsDao, args); // 目标方法执行
                myAspect.myAfter(); // 后增强
                return obj;
            }
        });
        // 创建代理类
        GoodsDao goodsDaoProxy = (GoodsDao) enhancer.create();
        return goodsDaoProxy;
    }
}

通知类型

名称说明
org.springframework.aop.MethodBeforeAdvice(前置通知)在方法之前自动执行的通知称为前置通知,可以应用于权限管理等功能。
org.springframework.aop.AfterReturningAdvice(后置通知)在方法之后自动执行的通知称为后置通知,可以应用于关闭流、上传文件、删除临时文件等功能。
org.aopalliance.intercept.MethodInterceptor(环绕通知)在方法前后自动执行的通知称为环绕通知,可以应用于日志、事务管理等功能。
org.springframework.aop.ThrowsAdvice(异常通知)在方法抛出异常时自动执行的通知称为异常通知,可以应用于处理异常记录日志等功能。
org.springframework.aop.IntroductionInterceptor(引介通知)在目标类中添加一些新的方法和属性,可以应用于修改旧版本程序(增强类)。

常用注解

@Component //泛指组件,不知如何归类

@Controller //标注控制层组件,标注在类上分发处理器扫描到检测方法是否使用@RequestMapping注解,可把Request请求header部分值绑定到方法参数上

@Repository //标注dao层,在daoImpl类上注解

@Service //标注服务层组件,在serviceImpl类上

@RestController //@Controller和@ResponseBody的结合体

@ResponseBody //异步请求,用于将Controller方法返回对象通过实地HttpMessageConverter转换指定格式写入Response对象的body数据区

@RequestMapping //用来处理请求地址映射的注解,用于类或方法上,类上表示类中所有响应请求方法都以改地址作为父路径

@PathVariable //用于将请求URL中的模板变量映射到功能处理方法的参数上,即取出url模板中的变量为参数

@requestParam //主要用在springmvc后台控制层获取参数,类似一种request.getParameter(“name”)

@RequestHeader //可以把Request请求header部分的值绑定到方法的参数上。

@Autowired //按照类型装配bean主要用于注入dao,service实例;也可配置在属性,set方法,构造方法

@Qualifier //配合Autowired使用,找到对应类

@Resource //按照name装配

@ModelAttribute //该Controller的所有方法在调用前,先执行@ModeAttribute方法,可用于注解和方法参数中,可把这个@ModelAttribute特性应用在BaseController中,所有Controller继承BaseController,即可实现在调用Controller,先执行@ModelAttribute方法

@SessionAttributes //将值放到session作用域中,写在class上面

@CookieValue //用来获取Cookie中的值

分布式事务

分布式事务(Distributed Transaction)包括事务管理器( Transaction Manager ) 和一个或多个支持 XA 协议的资源管理器 ( Resource Manager )。我们可以将资源管理器看做任意类型的持久化数据存储;事务管理器承担着所有事务参与单元的协调与控制。

两阶段提交主要保证了分布式事务的原子性:即所有结点要么全做要么全不做,所谓的两个阶段是指:第一阶段:准备阶段;第二阶段:提交阶段。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Jwsh771t-1613205664904)(file:///C:\Users\Lenovo\AppData\Local\Temp\ksohtml14588\wps1.png)]

1 准备阶段

事务协调者(事务管理器)给每个参与者(资源管理器)发送 Prepare 消息,每个参与者要么直接返回失败(如权限验证失败),要么在本地执行事务,写本地的 redo 和 undo 日志,但不提交,到达一种“万事俱备,只欠东风”的状态。

2 提交阶段:

如果协调者收到了参与者的失败消息或者超时,直接给每个参与者发送回滚(Rollback)消息;否则, 发送提交(Commit)消息;参与者根据协调者的指令执行提交或者回滚操作,释放所有事务处理过 程中使用的锁资源。(注意:必须在最后阶段释放锁资源)

将提交分成两阶段进行的目的很明确,就是尽可能晚地提交事务,让事务在提交前尽可能地完成所有能完成的工作。

SpringMVC

MVC流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TG67qmrK-1613205663138)(C:%5CUsers%5CLenovo%5CAppData%5CRoaming%5CTypora%5Ctypora-user-images%5Cimage-20201214211638820.png)]

(1) 客户端请求提交到 DispatcherServlet。

(2) 由 DispatcherServlet 控制器查询一个或多个 HandlerMapping,找到处理请求的Controller–(寻找处理器)

(3)DispatcherServlet 将请求提交到Controller–(调用处理器)

(4)Controller 调用业务逻辑处理后,返回 ModelAndView。

(5) DispatcherServlet 查询一个或多个 ViewResoler 视图解析器, 找到 ModelAndView 指定的视图。

(6)视图负责将结果显示到客户端。

参数绑定

https://www.cnblogs.com/ysocean/p/7425861.html

Mybatis

核心组件

1)SqlSessionFactoryBuilder(构造器):它会根据配置或者代码来生成 SqlSessionFactory,采用的是分步构建的 Builder 模式。

2)SqlSessionFactory(工厂接口):依靠它来生成 SqlSession,使用的是工厂模式。

3)SqlSession(会话):一个既可以发送 SQL 执行返回结果,也可以获取 Mapper 的接口。在现有的技术中,一般我们会让其在业务逻辑代码中“消失”,而使用的是 MyBatis 提供的 SQL Mapper 接口编程技术,它能提高代码的可读性和可维护性。

4)SQL Mapper(映射器):MyBatis 新设计存在的组件,它由一个 Java 接口和 XML 文件(或注解)构成,需要给出对应的 SQL 和映射规则。它负责发送 SQL 去执行,并返回结果。

Mybatis缓存

一级缓存(sqlsession)

第一次发出一个查询 sql,sql 查询结果写入 sqlsession 的一级缓存中,缓存使用的数据结构是一个 map。

key:MapperID+offset+limit+Sql+所有的入参

value:用户信息

同一个 sqlsession 再次发出相同的 sql,就从缓存中取出数据。如果两次中间出现 commit 操作(修改、添加、删除),本 sqlsession 中的一级缓存区域全部清空,下次再去缓存中查询不到所以要从数据库查询,从数据库查询到再写入缓存。

注:一级缓存最多缓存1024条信息

二级缓存(mapper)

二级缓存的范围是mapper 级别(mapper 同一个命名空间),mapper 以命名空间为单位创建缓存数据结构,结构是map。mybatis 的二级缓存是通过 CacheExecutor 实现的。CacheExecutor

其实是 Executor 的代理对象。所有的查询操作,在 CacheExecutor 中都会先匹配缓存中是否存在,不存在则查询数据库。

key:MapperID+offset+limit+Sql+所有的入参

具体使用需要配置:

\1. Mybatis 全局配置中启用二级缓存配置

\2. 在对应的 Mapper.xml 中配置 cache 节点

\3. 在对应的select 查询节点中添加 useCache=true

select,insert,update,delete

select

元素常用属性

属性名称描 述
id它和 Mapper 的命名空间组合起来使用,是唯一标识符,供 MyBatis 调用
parameterType表示传入 SQL 语句的参数类型的全限定名或别名。它是一个可选属性,MyBatis 能推断出具体传入语句的参数
resultTypeSQL 语句执行后返回的类型(全限定名或者别名)。如果是集合类型,返回的是集合元素的类型,返回时可以使用 resultType 或 resultMap 之一
resultMap它是映射集的引用,与 元素一起使用,返回时可以使用 resultType 或 resultMap 之一
flushCache用于设置在调用 SQL 语句后是否要求 MyBatis 清空之前查询的本地缓存和二级缓存,默认值为 false,如果设置为 true,则任何时候只要 SQL 语句被调用都将清空本地缓存和二级缓存
useCache启动二级缓存的开关,默认值为 true,表示将査询结果存入二级缓存中
timeout用于设置超时参数,单位是秒(s),超时将抛出异常
fetchSize获取记录的总条数设定
statementType告诉 MyBatis 使用哪个 JDBC 的 Statement 工作,取值为 STATEMENT(Statement)、 PREPARED(PreparedStatement)、CALLABLE(CallableStatement)
resultSetType这是针对 JDBC 的 ResultSet 接口而言,其值可设置为 FORWARD_ONLY(只允许向前访问)、SCROLL_SENSITIVE(双向滚动,但不及时更新)、SCROLLJNSENSITIVE(双向滚动,及时更新)

使用 Map 接口传递多个参数

Dao:
public List<MyUser> selectAllUser(Map<String,Object> param);
--------------------------------------------
Mapper:
<!-- 查询陈姓男性用户信息 -->
<select id="selectAllUser" resultType="com.mybatis.po.MyUser">
    select * from user
    where uname like concat('%',#{u_name},'%')
    and usex = #{u_sex}
</select>
//参数名 u_name 和 u_sex 是 Map 的 key
----------------------------------------------
Controller:
@Controller("UserController")
public class UserController {
    private UserDao userDao;
    public void test(){
        ...
        //查询多个用户
        Map<String,Object> map = new HashMap<>();
        map.put("u_name","陈");
        map.put("u_sex","男");
        List<MyUser> list = userDao.seleceAllUser(map);
        for(MyUser myUser : list) {
            System.out.println(myUser);
        }
        ...
    }
}

使用 Java Bean 传递多个参数

pojo:
public class SeletUserParam {
    private String u_name;
    private String u_sex;
    // 此处省略setter和getter方法
}
--------------------------------------------
Dao:
public List<MyUser> selectAllUser(SelectUserParam param);
-------------------------------------------------
Mapper:
<select id="selectAllUser" resultType="com.po.MyUser" parameterType="com.pojo.SeletUserParam">
    select * from user
    where uname like concat('%',#{u_name},'%')
    and usex=#{u_sex}
</select>
--------------------------------------------------
Controller:
SeletUserParam su = new SelectUserParam();
su.setU_name("陈");
su.setU_sex("男");
List<MyUser> list = userDao.selectAllUser(su);
for (MyUser myUser : list) {
    System.out.println(myUser);
}

注意:如果参数较少,建议选择 Map;如果参数较多,建议选择 Java Bean。

insert

属性

  • keyProperty:该属性的作用是将插入或更新操作时的返回值赋给 PO 类的某个属性,通常会设置为主键对应的属性。如果是联合主键,可以将多个值用逗号隔开。
  • keyColumn:该属性用于设置第几列是主键,当主键列不是表中的第 1 列时需要设置。如果是联合主键,可以将多个值用逗号隔开。
  • useGeneratedKeys:该属性将使 MyBatis 使用 JDBC 的 getGeneratedKeys()方法获取由数据库内部产生的主键,例如 MySQL、SQL Server 等自动递增的字段,其默认值为 false。
//主键(自动递增)回填
Mapper:
<!--添加一个用户,成功后将主键值返回填给uid(po的属性)-->
<insert id="addUser" parameterType="com.po.MyUser" keyProperty="uid" useGeneratedKeys="true">
    insert into user (uname,usex) values(#{uname},#{usex})
</insert>
-------------------------------------------------
Controller:
// 添加一个用户
MyUser addmu = new MyUser();
addmu.setUname("陈恒");
addmu.setUsex("男");
int add = userDao.addUser(addmu);
System.out.println("添加了" + add + "条记录");
System.out.println("添加记录的主键是" + addmu.getUid());
//自定义主键(例如oracle不支持主键自动递增)
<!-- 添加一个用户,#{uname}为 com.mybatis.po.MyUser 的属性值 -->
<insert id="insertUser" parameterType="com.po.MyUser">
    <!-- 先使用selectKey元素定义主键,然后再定义SQL语句 -->
    <selectKey keyProperty="uid" resultType="Integer" order="BEFORE">
    select if(max(uid) is null,1,max(uid)+1) as newUid from user)
    </selectKey>
    insert into user (uid,uname,usex) values(#{uid},#{uname},#{usex})
</insert>

update

<!-- 修改一个用户 -->
<update id="updateUser" parameterType="com.po.MyUser">
    update user set uname = #{uname},usex = #{usex} where uid = #{uid}
</update>

delete

<!-- 删除一个用户 -->
<delete id="deleteUser" parameterType="Integer">
    delete from user where uid = #{uid}
</delete>

sql标签

元素的作用在于可以定义 SQL 语句的一部分(代码片段),以方便后面的 SQL 语句引用它,例如反复使用的列名。

<sql id="comColumns">id,uname,usex</sql>
<select id="selectUser" resultType="com.po.MyUser">
    select <include refid="comColumns"> from user
</select>

resultMap和resultType区别

resultMap:是一个复杂结果集,可以返回一对多或多对多的数据

resultType:是可以找的到的一个参数类型(),数据类型引用类型,实体类都可

<resultMap id="" type="">
    <constructor><!-- 类再实例化时用来注入结果到构造方法 -->
        <idArg/><!-- ID参数,结果为ID -->
        <arg/><!-- 注入到构造方法的一个普通结果 -->  
    </constructor>
    <id/><!-- 用于表示哪个列是主键 -->
    <result/><!-- 注入到字段或JavaBean属性的普通结果 -->
    <association property=""/><!-- 用于一对一关联 -->
    <collection property=""/><!-- 用于一对多、多对多关联 -->
    <discriminator javaType=""><!-- 使用结果值来决定使用哪个结果映射 -->
        <case value=""/><!-- 基于某些值的结果映射 -->
    </discriminator>
</resultMap>

一对一关联查询

元素中通常使用以下属性。

  • property:指定映射到实体类的对象属性。
  • column:指定表中对应的字段(即查询返回的列名)。
  • javaType:指定映射到实体对象属性的类型。
  • select:指定引入嵌套查询的子 SQL 语句,该属性用于关联映射中的嵌套查询。
实体类:
public class Idcard {
    private Integer id;
    private String code;
    // 省略setter和getter方法
}
public class Person {
    private Integer id;
    private String name;
    private Integer age;
    // 个人身份证关联
    private Idcard card;
}
-------------------------------------------
mapper:
<mapper namespace="com.dao.PersonDao">
    <!-- 一对一根据id查询个人信息:级联查询的第一种方法(嵌套查询,执行两个SQL语句)-->
    <resultMap type="com.po.Person" id="cardAndPerson1">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <result property="age" column="age"/>
        <!-- 一对一级联查询-->
        <association property="card" column="idcard_id" javaType="com.po.Idcard"
        select="com.dao.IdCardDao.selectCodeByld"/>
    </resultMap>
    <select id="selectPersonById1" parameterType="Integer" resultMap=
    "cardAndPerson1">
        select * from person where id=#{id}
    </select>
    <!--对一根据id查询个人信息:级联查询的第二种方法(嵌套结果,执行一个SQL语句)-->
    <resultMap type="com.po.Person" id="cardAndPerson2">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <result property="age" column="age"/>
        <!-- 一对一级联查询-->
        <association property="card" javaType="com.po.Idcard">
            <id property="id" column="idcard_id"/>
            <result property="code" column="code"/>
        </association>
    </resultMap>
    <select id="selectPersonById2" parameterType="Integer" resultMap= "cardAndPerson2">
        select p.*,ic.code
        from person p, idcard ic
        where p.idcard_id=ic.id and p.id=#{id}
    </select>
</mapper>

一对多关联查询

实体类:
需在用户类中添加订单list属性与mapper中rensultMap映射
// 一对多级联查询,用户关联的订单
private List<Orders> ordersList;
-------------------------------------------------
<mapper namespace="com.mybatis.mapper.UserMapper">
    <!-- 一对多 根据uid查询用户及其关联的订单信息:级联查询的第一种方法(嵌套查询) -->
    <resultMap type="com.po.MyUser" id="userAndOrders1">
        <id property="uid" column="uid" />
        <result property="uname" column="uname" />
        <result property="usex" column="usex" />
        <!-- 一对多级联查询,ofType表示集合中的元素类型,将uid传递给selectOrdersByld -->
        <collection property="ordersList" ofType="com.po.Orders"
            column="uid" select="com.dao.OrdersDao.selectOrdersByld" />
    </resultMap>
    <select id="selectUserOrdersById1" parameterType="Integer"
        resultMap="userAndOrders1">
        select * from user where uid = #{id}
    </select>
    <!--对多根据uid查询用户及其关联的订单信息:级联查询的第二种方法(嵌套结果) -->
    <resultMap type="com.po.MyUser" id="userAndOrders2">
        <id property="uid" column="uid" />
        <result property="uname" column="uname" />
        <result property="usex" column="usex" />
        <!-- 对多级联查询,ofType表示集合中的元素类型 -->
        <collection property="ordersList" ofType="com.po.Orders">
            <id property="id" column="id" />
            <result property="ordersn" column="ordersn" />
        </collection>
    </resultMap>
    <select id="selectUserOrdersById2" parameterType="Integer"
        resultMap="userAndOrders2">
        select u.*,o.id, o.ordersn from user u, orders o where u.uid
        = o.user_id and
        u.uid=#{id}
    </select>
</mapper>

多对多关联查询

与一对多类似,区别在于多了一张两表的关联关系表

public class Product {
    private Integer id;
    private String name;
    private Double price;
    // 多对多中的一个一对多
    private List<Orders> orders;
    // 省略setter和getter方法
}
public class Orders {
    private Integer id;
    private String ordersn;
    // 多对多中的另一个一对多
    private List<Product> products;
    // 省略setter和getter方法
}        

懒加载

顾名思义,懒加载就是因为偷懒了,懒得加载了,只有使用的时候才进行加载。我们在用Mybatis进行一对多的时候,先查询出一方,当程序需要多方数据时,mybatis会再次发出sql语句进行查询,减轻了对我们数据库的压力。Mybatis的延迟加载,只对关联对象有延迟设置。

加载时机

  • 直接加载:执行完对主加载对象的 select 语句,马上执行对关联对象的 select 查询。
  • 侵入式延迟: 执行对主加载对象的查询时,不会执行对关联对象的查询。但当要访问主加载对象的详情属性时,就会马上执行关联对象的select查询。
<!--全局参数设置-->
<settings>
    <!--延迟加载总开关-->
    <setting name="lazyLoadingEnabled" value="true"/>
    <!--侵入式延迟加载开关-->
    <!--3.4.1版本之前默认是true,之后默认是false-->
    <setting name="aggressiveLazyLoading" value="true"/>
</settings>
  • 深度延迟: 执行对主加载对象的查询时,不会执行对关联对象的查询。访问主加载对象的详情时也不会执行关联对象的select查询。只有当真正访问关联对象的详情时,才会执行对关联对象的 select 查询。
<!--全局参数设置-->
<settings>
    <!--延迟加载总开关-->
    <setting name="lazyLoadingEnabled" value="true"/>
    <!--侵入式延迟加载开关-->
    <!--3.4.1版本之前默认是true,之后默认是false-->
    <setting name="aggressiveLazyLoading" value="false"/>
</settings>

上面都是通过在mybatis.xml文件中统一配置的深度延迟加载,倘若只希望某些查询支持深度延迟加载的话可以在resultMap中的collection或association添加fetchType属性,配置为lazy之后是开启深度延迟,配置eager是不开启深度延迟。fetchType属性将取代全局配置参数lazyLoadingEnabled的设置

#{}和${}的区别

#{}是预编译处理,${}是字符串替换

(1)mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值。

(2)mybatis在处理 时 , 就 是 把 {}时,就是把 {}替换成变量的值。

(3)使用#{}可以有效的防止SQL注入,提高系统安全性。预编译是提前对SQL语句进行预编译,而其后注入的参数将不会再进行SQL编译。我们知道,SQL注入是发生在编译的过程中,因为恶意注入了某些特殊字符,最后被编译成了恶意的执行操作。而预编译机制则可以很好的防止SQL注入。

分页

1.数组分页

2.sql分页

3.拦截器分页最为典型的PageHelper分页插件

properties配置文件
#分页插件
pagehelper.helper-dialect=mysql
pagehelper.params=count=countSql
pagehelper.reasonable=true
pagehelper.support-methods-arguments=true
---------------------------------------------
Controller
@Controller
public class PersonController {
    @Autowired
    private PersonService personService;
 
    @GetMapping("/getAllPerson")
    public String getAllPerson(Model model,@RequestParam(defaultValue = "1",value = "pageNum") Integer pageNum){
        PageHelper.startPage(pageNum,5);
        List<Person> list = personService.getAllPerson();
        PageInfo<Person> pageInfo = new PageInfo<Person>(list);
        model.addAttribute("pageInfo",pageInfo);
        return "list";
    }
--------------------------------------------------------- 
Json格式

@RequestMapping("/getGatewayList")
@ResponseBody
public List<PageInfo>  getGatewayList(Model model,GatewayInfo gatewayInfo, @RequestParam(defaultValue = "1", value = "pageNum") Integer pageNum,
			@RequestParam(defaultValue = "5") int pageSize) {
		
		try {
		
			PageHelper.startPage(pageNum, pageSize);
	        PageInfo pageInfo=new PageInfo(gmpGatewayService.selectGatewayAll());
			
			logger.info("列表信息 |数据| "+JSON.toJSONString(pageInfo.getList()));
			logger.info("分页数量: "+pageInfo.getNextPage());
			logger.info("分页总数: "+pageInfo.getPageNum());
			logger.info("分页总数: "+pageInfo.getPageSize());
			
			//直接返回json字符串
			return pageInfo.getList();
		} catch (Exception e) {
			logger.error("信息 | 查询  | error ");
			return null;    
前端
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div align="center">
    <table border="1">
        <tr>
            <th>id</th>
            <th>name</th>
            <th>sex</th>
            <th>age</th>
        </tr>
        <tr th:each="person:${pageInfo.list}">
            <td th:text="${person.id}"></td>
            <td th:text="${person.name}"></td>
            <td th:text="${person.sex}"></td>
            <td th:text="${person.age}"></td>
        </tr>
    </table>
    <p>当前 <span th:text="${pageInfo.pageNum}"></span> 页,总 <span th:text="${pageInfo.pages}"></span> 页,共 <span th:text="${pageInfo.total}"></span> 条记录</p>
    <a th:href="@{/getAllPerson}">首页</a>
    <a th:href="@{/getAllPerson(pageNum=${pageInfo.hasPreviousPage}?${pageInfo.prePage}:1)}">上一页</a>
    <a th:href="@{/getAllPerson(pageNum=${pageInfo.hasNextPage}?${pageInfo.nextPage}:${pageInfo.pages})}">下一页</a>
    <a th:href="@{/getAllPerson(pageNum=${pageInfo.pages})}">尾页</a>
</div>
</body>
   

4.RowBounds分页

SpringBoot

核心功能特点

1)独立运行的 Spring 项目

Spring Boot 可以以 jar 包的形式独立运行,运行一个 Spring Boot 项目只需通过 java–jar xx.jar 来运行。

2)内嵌 Servlet 容器

Spring Boot 可选择内嵌 Tomcat、Jetty 或者 Undertow,这样我们无须以 war 包形式部署项目。

3)提供 starter 简化 Maven 配置

Spring 提供了一系列的 starter pom 来简化 Maven 的依赖加载,例如,当你使用了spring-boot-starter-web 时,会自动加入依赖包。

4)自动配置 Spring

Spring Boot 会根据在类路径中的 jar 包、类,为 jar 包里的类自动配置 Bean,这样会极大地减少我们要使用的配置。当然,Spring Boot 只是考虑了大多数的开发场景,并不是所有的场景,若在实际开发中我们需要自动配置 Bean,而 Spring Boot 没有提供支持,则可以自定义自动配置。

5)准生产的应用监控

Spring Boot 提供基于 http、ssh、telnet 对运行时的项目进行监控。

6)无代码生成和 xml 配置

Spring Boot 的神奇的不是借助于代码生成来实现的,而是通过条件注解来实现的,这是 Spring 4.x 提供的新特性。Spring 4.x 提倡使用 Java 配置和注解配置组合,而 Spring Boot 不需要任何 xml 配置即可实现 Spring 的所有配置。

启动依赖

1.实例化SpringApplication对象,然后调用其run方法。在这之前

根据 classpath判断web类型(带REACTIVE是反应式web应用,无阻塞应用:后端将改变数据不断的推送到客户端,而不是传统的WEB应用将后端数据获得后发送给客户端显示)创建相应的ApplicationContext

SpringFactoriesLoader 在应用的 classpath 中查找并加载所有可用的 ApplicationContextInitializer。

SpringFactoriesLoader 在应用的 classpath 中查找并加载所有可用的 ApplicationListener。

推断并设置main方法定义类

2.实例初始化后,开始执行run方法逻辑启动SpringApplicationRunListeners监听,通知应用要开始执行了

3.创建并配置当前 SpringBoot 应用将要使用的 Environment(PropertySource 以及 Profile)调用SpringApplicationRunListeners监听器environmentPrepared()的方法,通知应用环境准备就绪

4.初始化Banner打印实例

5.根据webApplicationType类型进行反射实例化ApplicationContext上下文实例,决定是否使用自定义的 BeanNameGenerator,决定是否使用自定义的 ResourceLoader,将之前准备好的 Environment 设置给创建好的 ApplicationContext 使用

6.遍历调用这些 ApplicationContextInitializer 的 initialize(applicationContext)方法来对已经创建好的 ApplicationContext 进行进一步的处理。

7.SpringApplicationRunListener 的 contextPrepared()方法通知应用context准备就绪

8.将之前通过 @EnableAutoConfiguration 获取的所有配置以及其他形式的 IoC 容器配置加载到已经准备完毕的 ApplicationContext。

9.SpringApplicationRunListener 的 contextLoaded() 方法,通知ApplicationContext 装填完成。

10.调用 ApplicationContext 的 refresh() 方法并决定是否注册关闭任务

自动配置

@SpringbootAppliation注解通过@AlisaFor(用于为注解属性声明别名注解)将@EnableAutoConfiguration(自配置注解),@ComponentScan(扫描主类所在的同级包以及下级包里的Bean注解)组合起来形复合注解。自动配置主要体现在@EnableAutoConfiguration这个注解它借助了EnableAutoConfigurationImportSelector将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器,其中getCandidateConfigurations方法得到所需待自动配置的class的类名集合,配置信息就存在于META-INF/spring.factories文件中。

在这里插入图片描述

mybatis-spring-boot-starter、spring-boot-starter-web等组件的META-INF文件下均含有spring.factories文件,自动配置模块中,SpringFactoriesLoader收集到文件中的类全名并返回一个类全名的数组,返回的类全名通过反射被实例化,就形成了具体的工厂实例,工厂实例来生成组件具体需要的bean。

https://blog.csdn.net/zjcjava/article/details/84028222

部署方式(jar,war)

jar:

1.pom.xmljar

2.切到项目目录cmd运行 mvn install打出jar包

3.java -jar target/springboot-0.0.1-SNAPSHOT.jar即可运行项目

war:

1.Application新加@ServletComponentScan注解,并且继承SpringBootServletInitializer

2.pom.xml文件war;spring-boot-starter-tomcat修改为 provided方式,以避免和独立 tomcat 容器的冲突. mvn clean package表示provided 只在编译和测试的时候使用

3.切到项目目录cmd运行 mvn clean package打出war包

4.springboot-0.0.1-SNAPSHOT.war 这个文件名部署,那么访问的时候就要在路径上加上springboot-0.0.1-SNAPSHOT。 所以把这个文件重命名为 ROOT.war,然后把它放进tomcat 的webapps目录下,访问直接用/hello,root表示根路径

热部署

依赖:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <optional>true</optional> <!-- 这个需要为 true 热部署才有效 -->
</dependency>
插件:
<plugin>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-maven-plugin</artifactId>
</plugin>

端口和上下文路径

修改application.properties

spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp
server.port=8888
server.context-path=/test

多配置文件

3个配置文件:
核心配置文件:application.properties
开发环境用的配置文件:application-dev.properties
生产环境用的配置文件:application-pro.properties
这样就可以通过application.properties里的spring.profiles.active 灵活地来切换使用哪个环境了

application.properties

spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp
spring.profiles.active=pro

application-dev.properties

server.port=8080
server.context-path=/test

application-pro.properties

server.port=80
server.context-path=/

部署的话

java -jar target/springboot-0.0.1-SNAPSHOT.jar --spring.profiles.active=pro
或者

java -jar target/springboot-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev

yml

同样内容,不同写法

排序算法

冒泡排序

int[] arr = new int arr[]{1,3,5,6,2};
for(int i=0;i<arr.length-1;i++){
	for(int j=0;j<arr.length-1-i;j++){
		if(arr[j]>arr[j+1]){
			int temp = arr[j];
			arr[j] = arr[j+1];
			arr[j+1] = temp;
		}
	}
}
for(int i=0;i<arr.length;i++){
	System.out.print(arr[i]);
}

设计模式

创建型模式:工厂方法模式,抽象工厂模式,单例模式,建造者模式,原型模式

结构型模式:适配器模式,装饰器模式,代理模式,外观模式,桥接模式,组合模式,享元模式

行为型模式:策略模式,模板方法模式,观察者模式,迭代子模式,责任链模式,命令模式,备忘录模式,状态模式,访问者模式,中介者模式,解释器模式

代理模式

工厂模式

实现了创建者与调用者的分离,即将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的。

1.无工厂模式

2.简单工厂模式

用来生产同一等级结构中的任意产品(对于增加新的产品,需要修改以偶的代码;违反开闭原则

interface Car{
    public void run();
}
Class Audi implements Car{
    public void run(){
        System.out.print("Audi在跑");
    }
}
Class Hongqi implements Car{
    public void run(){
        System.out.print("Hongqi在跑");
    }
}
class CarFactory{
    public static Car getCarType(String type){
        //方式一
        if("奥迪".equals(type)){
            new Audi();
        }else if("红旗".equals(type)){
            new Hongqi();
        }else{
            return null;
        }
    }
    //方式二
    /*
    public static Car getAudi(){
    	return new Audi();
    }
    public static Car getHongqi(){
    	return new Hongqi();
    }
    */
    public static void main(String[] args){
        Car A = CarFactory.getCarType("Audi");
        //Car A = CarFactory.getAudi();
        audi.run();
    }
    
}

3.工厂方法模式

用来生产同一等级结构中的固定产品。(支持增加任意产品—)

interface Factory{
    Car getCar();
}
class AudiFactory implements Factory{
    public Car getCar(){
        return new Audi();
    }
}
class HongqiFactory implements Factory{
    public Car getCar(){
        return new Hongqi();
    }
}
  public static void main(String[] args){
       Car A = new AudiFactory.getCar();
       A.run();
    }

4.抽象工厂模式

用来生产不同产品族的全部产品(对于增加新的产品,无能为力,支持增加产品族)(比如讲车细分跑车,轿车,suv)

单例模式

饿汉式

public static void main(String[] args){
    Blank blank1 = Blank.getInstance();
    Blank blank2 = Blank.getInstance();
    //blank1 == blank2
}
Class Blank{
    //1.私有化类的构造器
    private Blank(){
    }
    //2.内部创建类的对象
    //4.要求此对象也必须声明为静态
    private static Blank instance = new Blank();
    //3.提供公共的静态的方法,返回类的对象
    public static Blank getInstance(){
        return instance;
    }
}

懒汉式

public static void main(String[] args){
    Order oreder1 = Order.getInstance();
}
Class Order{
    //1.私有化类的构造器
    private Order(){
    }
    //2.声明当前类对象,没有初始化
    //4.要求此对象也必须声明为静态
    public static Order instance = null;
    //3.提供公共的静态的方法,返回类的对象
    public static synchronized getInstance(){
        if(instance == null){
            instance = new Order();
        }
        return instance;
    }
}

饿汉式:坏处:每次调用不用在创建,直接返回实例

​ 好处:线程安全

懒汉式:好处:用if做了判断,延迟对象的创建

​ 坏处:线程不安全

前端杂项

CSS选择器

类选择器 .class

ID选择器 #id

标签选择器 p

div,p //选择所有

元素和所有

元素。

div p //选择

元素内部的所有

元素。

属性选择器 [attribute = value]

JS中Dom操作

getElementById、getElementsByName、getElementsByTagName、getElementsByClassName、getAttribute、setAttribute、

Jquery

所有 jQuery 函数位于一个 document ready 函数中:

$(document).ready(function(){
// 开始写 jQuery 代码...
});
简洁写法(与以上写法效果相同):

$(function(){      
// 开始写 jQuery 代码...  
});
$.ajax({    
type:"get", 
url:"search",    
data:{"key":$(this).val()},    
dataType:"json",    
success:function (data) {        
$("#msg").show();        
$("#msg").html(data);        
$("#msg").click(function () {            
$("#hname").val(data);            
$autocomplete.empty().hide();        
})    
}})

JSON

json是一种轻量级数据交换格式,是一种特殊的字符串,是一种名值对集合,可在跨平台场景下传递数据用。一般在ajax时候用的多

{
    "ming":"value",
    ...
}

Ajax跨域问题

1、响应头添加Header允许访问

2、jsonp 只支持get请求不支持post请求

3、httpClient内部转发

4、使用接口网关——nginx、springcloud zuul (互联网公司常规解决方案)

(1条消息) 解决ajax跨域问题【5种解决方案】_itcats_cn的博客-CSDN博客_ajax跨域的解决方案

Redis

如何防止缓存穿透、缓存击穿、缓存雪崩和缓存刷新。

**【**1】**缓存穿透:缓存穿透是说收到一个请求,但是该请求缓存中不存在,只能去数据库中查询,然后放进缓存。但当有好多请求同时访问同一个数据时,业务系统把这些请求全发到了数据库;或者恶意构造一个逻辑上不存在的数据,然后大量发送这个请求,这样每次都会被发送到数据库,最终导致数据库挂掉。
解决的办法
:**对于恶意访问,一种思路是先做校验,对恶意数据直接过滤掉,不要发送至数据库层;第二种思路是缓存空结果,就是对查询不存在的数据也记录在缓存中,这样就可以有效的减少查询数据库的次数。非恶意访问,结合缓存击穿说明。
【2】**缓存击穿:上面提到的某个数据没有,然后好多请求查询数据库,可以归为缓存击穿的范畴:对于热点数据,当缓存失效的一瞬间,所有的请求都被下放到数据库去请求更新缓存,数据库被压垮。
解决的办法
:**防范此类问题,一种思路是加全局锁,就是所有访问某个数据的请求都共享一个锁,获得锁的那个才有资格去访问数据库,其他线程必须等待。但现在大部分系统都是分布式的,本地锁无法控制其他服务器也等待,所以要用到全局锁,比如 Redis的 setnx实现全局锁。另一种思想是对即将过期的数据进行主动刷新,比如新起一个线程轮询数据,或者比如把所有的数据划分为不同的缓存区间,定期分区间刷新数据。第二个思路与缓存雪崩有点关系。
【3】**缓存雪崩:缓存雪崩是指当我们给所有的缓存设置了同样的过期时间,当某一时刻,整个缓存的数据全部过期了,然后瞬间所有的请求都被抛向了数据库,数据库就崩掉了。
解决的办法
:**解决思路要么是分治,划分更小的缓存区间,按区间过期;要么给每个 key的过期时间加一个随机值,避免同时过期,达到错峰刷新缓存的目的。

对于 Redis 挂掉了,请求全部走数据库,也属于缓存雪崩,我们可以有以下思路进行解决:
事发前:实现 Redis 的高可用(主从架构+Sentinel 或者 Redis Cluster),尽可能避免 Redis 挂掉这种情况。
事发中:万一 Redis 真的挂了,我们可以设置本地缓存(ehcache)+ 限流(hystrix),尽量避免我们的数据库被干掉。
事发后:Redis 持久化,重启后自动从磁盘上加载数据,快速恢复缓存数据。

【4】**缓存刷新:**既清空缓存 ,一般在 Insert、Update、Delete 操作后就需要刷新缓存,如果不执行就会出现脏数据。但当缓存请求的系统蹦掉后,返回给缓存的值为null。

Nginx

Nginx ,是一个 Web 服务器和反向代理服务器用于 HTTP、HTTPS、SMTP、POP3 和 IMAP 协议。

跨平台、配置简单,非阻塞、高并发连接、内存消耗小、成本低廉。

主要功能

1、正向、反向代理
2、负载均衡、分流
3、虚拟主机(绑定host)

正向代理和反向代理区别

图片

正向代理是一个位于客户端和原始服务器之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定原始服务器,然后代理向原始服务器转交请求并将获得的内容返回给客户端。代理服务器和客户端处于同一个局域网内。

比如说fanqiang。我知道我要访问谷歌,于是我就告诉代理服务器让它帮我转发。

反向代理实际运行方式是代理服务器接受网络上的连接请求。它将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给网络上请求连接的客户端 。代理服务器和原始服务器处于同一个局域网内。

比如说我要访问taobao,对我来说不知道图片、json、css 是不是同一个服务器返回回来的,但是我不关心,是反向代理 处理的,我不知道原始服务器。

Nginx如何处理HTTP请求

它结合多进程机制(单线程)和异步非阻塞方式。

1、多进程机制(单线程)

服务器每当收到一个客户端时,就有 服务器主进程 ( master process )生成一个 子进程( worker process )出来和客户端建立连接进行交互,直到连接断开,该子进程就结束了。

2、异步非阻塞机制

每个工作进程 使用 异步非阻塞方式 ,可以处理 多个客户端请求 。运用了epoll模型,提供了一个队列,排队解决。

当某个 工作进程 接收到客户端的请求以后,调用 IO 进行处理,如果不能立即得到结果,就去 处理其他请求 (即为 非阻塞 );而 客户端 在此期间也 无需等待响应 ,可以去处理其他事情(即为 异步 )。

当 IO 返回时,就会通知此 工作进程 ;该进程得到通知,暂时 挂起 当前处理的事务去 响应客户端请求 。

Nginx的master和worker是如何工作的

这跟Nginx的多进程、单线程有关。(一个进程只有一个主线程)。

为什么要用单线程?

采用单线程来异步非阻塞处理请求(管理员可以配置Nginx主进程的工作进程的数量),不会为每个请求分配cpu和内存资源,节省了大量资源,同时也减少了大量的CPU的上下文切换,所以才使得Nginx支持更高的并发。

简单过程:

主程序 Master process 启动后,通过一个 for 循环来 接收 和 处理外部信号 ;

主进程通过 fork() 函数产生 worker 子进程 ,每个子进程执行一个 for循环来实现Nginx服务器对事件的接收和处理 。

详细过程:

1、Nginx 在启动后,会有一个 master 进程和多个相互独立的 worker 进程。
2、master 接收来自外界的信号,先建立好需要 listen 的 socket(listenfd) 之后,然后再 fork 出多个 worker 进程,然后向各worker进程发送信号,每个进程都有可能来处理这个连接。
3、所有 worker 进程的 listenfd 会在新连接到来时变得可读 ,为保证只有一个进程处理该连接,所有 worker 进程在注册 listenfd 读事件前抢占 accept_mutex ,抢到互斥锁的那个进程注册 listenfd 读事件 ,在读事件里调用 accept 接受该连接。
4、当一个 worker 进程在 accept 这个连接之后,就开始读取请求、解析请求、处理请求,产生数据后,再返回给客户端 ,最后才断开连接。

Nginx常用命令

  • 启动 nginx
  • 停止 nginx -s stopnginx -s quit
  • 重启 nginx -s reloadservice nginx reload
  • 重载指定配置文件 .nginx -c /usr/local/nginx/conf/nginx.conf
  • 查看 nginx 版本 nginx -v
  • 测试配置文件 nginx -t

Nginx状态码

500

Internal Server Error 内部服务错误,比如脚本错误,编程语言语法错误。

502

Bad Gateway错误,网关错误。比如服务器当前连接太多,响应太慢,页面素材太多、带宽慢。

503

Service Temporarily Unavailable,服务不可用,web服务器不能处理HTTP请求,可能是临时超载或者是服务器进行停机维护。

504

Gateway timeout 网关超时,程序执行时间过长导致响应超时,例如程序需要执行20秒,而nginx最大响应等待时间为10秒,这样就会出现超时。

Nginx和Apache、Tomcat之间的不同点

1、Nginx/Apache 是Web Server,而Apache Tomact是一个servlet container
2、tomcat可以对jsp进行解析,nginx和apache只是web服务器,可以简单理解为只能提供html静态文件服务。

Nginx和Apache区别:

1)Nginx轻量级,同样起web 服务,比apache占用更少的内存及资源 。

2)Nginx 抗并发,nginx 处理请求是异步非阻塞的,而apache 则是阻塞型的,在高并发下nginx 能保持低资源低消耗高性能 。

3)Nginx提供负载均衡,可以做做反向代理,前端服务器

4)Nginx多进程单线程,异步非阻塞;Apache多进程同步,阻塞。

Nginx负载均衡策略

1.轮询(默认)round_robin

每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器 down 掉,能自动剔除。

2.IP哈希ip_hash

每个请求按访问 ip 的 hash 结果分配,这样每个访客固定访问一个后端服务器,可以解决 session 共享的问题。

当然,实际场景下,一般不考虑使用 ip_hash 解决 session 共享。

3.最少连接least_conn

下一个请求将被分派到活动连接数量最少的服务器

4.权重weight

weight的值越大分配到的访问概率越高,主要用于后端每台服务器性能不均衡的情况下,达到合理的资源利用率。

还可以通过插件支持其他策略

如何限流

Nginx 提供两种限流方式,一是控制速率,二是控制并发连接数。

1、控制速率

ngx_http_limit_req_module 模块提供了漏桶算法(leaky bucket),可以限制单个IP的请求处理频率。

如:

1.1 正常限流:

http {
limit_req_zone 192.168.1.1 zone=myLimit:10m rate=5r/s;
}

server {
location / {
limit_req zone=myLimit;
rewrite / http://www.hac.cn permanent;
}
}

参数解释:

key: 定义需要限流的对象。
zone: 定义共享内存区来存储访问信息。
rate: 用于设置最大访问速率。

表示基于客户端192.168.1.1进行限流,定义了一个大小为10M,名称为myLimit的内存区,用于存储IP地址访问信息。

rate设置IP访问频率,rate=5r/s表示每秒只能处理每个IP地址的5个请求。

Nginx限流是按照毫秒级为单位的,也就是说1秒处理5个请求会变成每200ms只处理一个请求。如果200ms内已经处理完1个请求,但是还是有有新的请求到达,这时候Nginx就会拒绝处理该请求。

1.2 突发流量限制访问频率

上面rate设置了 5r/s,如果有时候流量突然变大,超出的请求就被拒绝返回503了,突发的流量影响业务就不好了。

这时候可以加上burst 参数,一般再结合 nodelay 一起使用。

server {
location / {
limit_req zone=myLimit burst=20 nodelay;
rewrite / http://www.hac.cn permanent;
}
}

burst=20 nodelay 表示这20个请求立马处理,不能延迟,相当于特事特办。不过,即使这20个突发请求立马处理结束,后续来了请求也不会立马处理。

burst=20 相当于缓存队列中占了20个坑,即使请求被处理了,这20个位置也只能按100ms一个来释放。

2、控制并发连接数

ngx_http_limit_conn_module 提供了限制连接数功能。

limit_conn_zone $binary_remote_addr zone=perip:10m;
limit_conn_zone $server_name zone=perserver:10m;

server {
    ...
    limit_conn perip 10;
    limit_conn perserver 100;
}

limit_conn perip 10 作用的key 是 $binary_remote_addr,表示限制单个IP同时最多能持有10个连接。

limit_conn perserver 100 作用的key是 $server_name,表示虚拟主机(server) 同时能处理并发连接的总数。

注:limit_conn perserver 100 作用的key是 $server_name,表示虚拟主机(server) 同时能处理并发连接的总数。

拓展:

如果不想做限流,还可以设置白名单:

利用 Nginx ngx_http_geo_modulengx_http_map_module 两个工具模块提供的功能。

##定义白名单ip列表变量
geo $limit {
    default 1;
    10.0.0.0/8 0;
    192.168.0.0/10 0;
    81.56.0.35 0;
}

map $limit $limit_key {
    0 "";
    1 $binary_remote_addr;
}
# 正常限流设置
limit_req_zone $limit_key zone=myRateLimit:10m rate=10r/s;

geo 对于白名单 将返回0,不限流;其他IP将返回1,进行限流。

具体参考:http://nginx.org/en/docs/http/ngx_http_geo_module.html

除此之外:

ngx_http_core_module 还提供了限制数据传输速度的能力(即常说的下载速度)

location /flv/ {
    flv;
    limit_rate_after 500m;
    limit_rate       50k;
}

针对每个请求,表示客户端下载前500m的大小时不限速,下载超过了500m后就限速50k/s。

Git

工作流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6FTf0DfO-1613205663141)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20200922095138894.png)]

统一换行符

Windows操作系统采用两个字符来进行换行,即CRLF;Unix/Linux/Mac OS X操作系统采用单个字符LF来进行换行;另外,MacIntosh操作系统(即早期的Mac操作系统)采用单个字符CR来进行换行。

仓库中的换行符是LF,但是在更新的时候被改为自动CRLF了。

解决

用指令将其关闭

git config --global core.autocrlf false

Merge和Rebase区别

merge 是一个合并操作,会将两个分支的修改合并在一起,默认操作的情况下会提交合并中修改的内容

merge 的提交历史忠实地记录了实际发生过什么,关注点在真实的提交历史上面

rebase 并没有进行合并操作,只是提取了当前分支的修改,将其复制在了目标分支的最新提交后面

rebase 的提交历史反映了项目过程中发生了什么,关注点在开发过程上面

Git回滚撤销

当然,实际场景下,一般不考虑使用 ip_hash 解决 session 共享。

3.最少连接least_conn

下一个请求将被分派到活动连接数量最少的服务器

4.权重weight

weight的值越大分配到的访问概率越高,主要用于后端每台服务器性能不均衡的情况下,达到合理的资源利用率。

还可以通过插件支持其他策略

如何限流

Nginx 提供两种限流方式,一是控制速率,二是控制并发连接数。

1、控制速率

ngx_http_limit_req_module 模块提供了漏桶算法(leaky bucket),可以限制单个IP的请求处理频率。

如:

1.1 正常限流:

http {
limit_req_zone 192.168.1.1 zone=myLimit:10m rate=5r/s;
}

server {
location / {
limit_req zone=myLimit;
rewrite / http://www.hac.cn permanent;
}
}

参数解释:

key: 定义需要限流的对象。
zone: 定义共享内存区来存储访问信息。
rate: 用于设置最大访问速率。

表示基于客户端192.168.1.1进行限流,定义了一个大小为10M,名称为myLimit的内存区,用于存储IP地址访问信息。

rate设置IP访问频率,rate=5r/s表示每秒只能处理每个IP地址的5个请求。

Nginx限流是按照毫秒级为单位的,也就是说1秒处理5个请求会变成每200ms只处理一个请求。如果200ms内已经处理完1个请求,但是还是有有新的请求到达,这时候Nginx就会拒绝处理该请求。

1.2 突发流量限制访问频率

上面rate设置了 5r/s,如果有时候流量突然变大,超出的请求就被拒绝返回503了,突发的流量影响业务就不好了。

这时候可以加上burst 参数,一般再结合 nodelay 一起使用。

server {
location / {
limit_req zone=myLimit burst=20 nodelay;
rewrite / http://www.hac.cn permanent;
}
}

burst=20 nodelay 表示这20个请求立马处理,不能延迟,相当于特事特办。不过,即使这20个突发请求立马处理结束,后续来了请求也不会立马处理。

burst=20 相当于缓存队列中占了20个坑,即使请求被处理了,这20个位置也只能按100ms一个来释放。

2、控制并发连接数

ngx_http_limit_conn_module 提供了限制连接数功能。

limit_conn_zone $binary_remote_addr zone=perip:10m;
limit_conn_zone $server_name zone=perserver:10m;

server {
    ...
    limit_conn perip 10;
    limit_conn perserver 100;
}

limit_conn perip 10 作用的key 是 $binary_remote_addr,表示限制单个IP同时最多能持有10个连接。

limit_conn perserver 100 作用的key是 $server_name,表示虚拟主机(server) 同时能处理并发连接的总数。

注:limit_conn perserver 100 作用的key是 $server_name,表示虚拟主机(server) 同时能处理并发连接的总数。

拓展:

如果不想做限流,还可以设置白名单:

利用 Nginx ngx_http_geo_modulengx_http_map_module 两个工具模块提供的功能。

##定义白名单ip列表变量
geo $limit {
    default 1;
    10.0.0.0/8 0;
    192.168.0.0/10 0;
    81.56.0.35 0;
}

map $limit $limit_key {
    0 "";
    1 $binary_remote_addr;
}
# 正常限流设置
limit_req_zone $limit_key zone=myRateLimit:10m rate=10r/s;

geo 对于白名单 将返回0,不限流;其他IP将返回1,进行限流。

具体参考:http://nginx.org/en/docs/http/ngx_http_geo_module.html

除此之外:

ngx_http_core_module 还提供了限制数据传输速度的能力(即常说的下载速度)

location /flv/ {
    flv;
    limit_rate_after 500m;
    limit_rate       50k;
}

针对每个请求,表示客户端下载前500m的大小时不限速,下载超过了500m后就限速50k/s。

Git

工作流程

[外链图片转存中…(img-6FTf0DfO-1613205663141)]

统一换行符

Windows操作系统采用两个字符来进行换行,即CRLF;Unix/Linux/Mac OS X操作系统采用单个字符LF来进行换行;另外,MacIntosh操作系统(即早期的Mac操作系统)采用单个字符CR来进行换行。

仓库中的换行符是LF,但是在更新的时候被改为自动CRLF了。

解决

用指令将其关闭

git config --global core.autocrlf false

Merge和Rebase区别

merge 是一个合并操作,会将两个分支的修改合并在一起,默认操作的情况下会提交合并中修改的内容

merge 的提交历史忠实地记录了实际发生过什么,关注点在真实的提交历史上面

rebase 并没有进行合并操作,只是提取了当前分支的修改,将其复制在了目标分支的最新提交后面

rebase 的提交历史反映了项目过程中发生了什么,关注点在开发过程上面

Git回滚撤销

(8条消息) Git撤销&回滚操作_李刚的学习专栏-CSDN博客_git回滚命令


本文链接: http://www.dtmao.cc/news_show_700061.shtml

附件下载

相关教程

    暂无相关的数据...

共有条评论 网友评论

验证码: 看不清楚?