Java面试题第一季
- 1.自增变量
- 2.单例设计
- 2.1 什么是Singleton
- 2.2 代码示例
- 3.类初始化
- 3.1 代码
- 3.2 考点
- 3.3 Override(重写)和Overload(重载)区别
- 4.方法的传递机制
- 4.1 代码
- 4.2 考点
- 5.递归与迭代
- 6.成员变量与局部
- 6.1 代码
- 6.2 考点
- 6.3 局部变量与成员变量的区别
- 7.Spring Bean的作用域之间有什么区别
- 8.Spring支持的常用数据库事务传播属性和事务隔离级别
- 8.1 什么是事务
- 8.2 事务的四个特性(ACID)
- 8.3 事务的传播行为(propagation)
- 8.4 事务的隔离级别(isolation)
- 8.4.1 不隔离出现的三个问题
- 8.4.2 解决:设置事务隔离级别
- 8.5 面试题
- 9.SpringMVC
- 9.1 SpringMVC概述
- 9.1.1 SpringMVC简介
- 9.1.2 SpringMVC优点
- 9.2 SpringMVC执行流程
- 9.2.1流程图
- 9.2.2流程图简单分析
- 9.3 SpringMVC核心技术
- 9.3.1 请求重定向和转发
- 9.3.2 异常处理
- 9.3.3 拦截器
- 10.Mybatis
- 10.1 Mybatis简介
- 10.2 面试题
- 11.Linux常用服务类相关命令
- 12.Git分支相关命令和实际应用
- 13.redis持久化
- 14.MySQL索引
- 14.1 简介
- 14.2 适合建立索引的情况
- 14.3 不适合建立索引的情况
- 15.JVM
- 16.redis在项目中的使用场景
- 17.es(Elasticsearch)和solr的区别
- 18.单点登录的实现过程
- 19.购物车实现过程
- 20.消息队列在项目中的使用
1.自增变量
public static void main(String[] args) {
int i = 1;
i = i++;
int j = i++;
int k = i + ++i * i++;
System.out.println("i=" + i);
System.out.println("j=" + j);
System.out.println("k=" + k);
}
说明:先计算赋值号右边,把i的值压入操作数栈,此时操作数栈中的值为1,自增自减都是不经过操作数栈的,因此i变量自增后的值(即局部变量表中的值)为2,最后进行赋值操作,把操作数栈中的值赋给i,局部变量表中的原来的值2被覆盖掉,变为1.
说明:先计算赋值号右边,把i的值压入操作数栈,此时操作数栈中的值为1,自增自减都是不经过操作数栈的,因此i变量自增后的值(即局部变量表中的值)为2,最后进行赋值操作,把操作数栈中的值赋给j,局部变量表中的j值为1.
说明:先计算赋值号右边,i经过上面的计算,现在的值为2,把i的值压入操作数栈;++i自增运算是不经过操作数栈的且是先加,此时i的值为3,把i的值压入操作数栈;i++是后加,因此先把i的值(此时值i的值为3)压入操作数栈,自增后i的值变为4。
说明:根据运算符的优先级,此时应该进行乘法运算,因此把操作数栈中的前两个弹出求乘积后可得3*3=9;再把结果压入操作数栈。
说明:把操作数栈中的值弹出求和9+2=1,最后进行赋值可得k的值为11。
//结果
i=4
j=1
k=11
小结:
赋值=,最后计算
=右边的从左到右加载值依次压入操作栈
实际先算哪个,看运算符优先级
自增、自减操作都是直接修改变量的值,不经过操作数栈
最后的赋值之前,临时结果也是存储在操作数栈中
2.单例设计
2.1 什么是Singleton
1.Singleton:在java中即指单例设计模式,它是软件开发中最常用的设计模式之一。
单:唯一
例:实例
单例设计模式,即某个类在整个系统中只能有一个实例对象可被获取和使用的代码模式。
2.要点:
一是某个类只能有一个实例;
构造器私有化
二是它必须自行创建这个实例;
含有一个该类的静态变量来保存这个唯一的实例
三是它必须自行向整个系统提供这个实例
对外提供获取该实例对象的方式:
(1)直接暴露
(2)用静态变量的get方法获取
3.几种常见形式
饿汉式:直接创建对象,不存在线程安全问题
直接实例化饿汉式(简洁直观)
枚举式(最简洁)
静态代码块饿汉式(适合复杂实例化)
懒汉式:延迟创建对象
线程不安全(适用于单线程)
线程安全(适用于多线程)
静态内部类形式(适用于多线程)
2.2 代码示例
/*
饿汉式:在类初始化时直接创建对象,不管你是否需要这个对象都会创建,不存在线程安全问题
(1)构造器私有化
(2)自行创建,并且用静态变量保存
(3)向外提供这个实例
(4)强调这是一个单例,可以用final修改
*/
public class Singleton1{
public static final Singleton1 INSTANCE = new Singleton1();
private Singleton(){
}
}
/*
饿汉式:枚举式(最简洁)
枚举类型:表示该类型的对象是有限的几个
我们可以限定为一个,就成了单例
*/
public enum Singleton2{
INSTANCE
}
/**
饿汉式:静态代码块(适合复杂实例化)
*/
public class Singleton3 {
public static final Singleton3 INSTANCE;
static {
INSTANCE = new Singleton3();
}
}
/*
懒汉式:延迟创建实例对象对象
线程不安全(适用于单线程)
(1)构造器私有化
(2)用一个静态变量保存这个唯一的实例
(3)提供一个静态方法,获取这个实例对象
*/
public class Singleton4{
private static Singleton4 instance
private Singleton4(){
}
public static Singleton4 getInstance(){
if(instance == null){
instance = new Singleton4();
}
return instance;
}
}
/*
懒汉式:延迟创建实例对象对象
(加了锁)
线程安全(适用于多线程)
(1)构造器私有化
(2)用一个静态变量保存这个唯一的实例
(3)提供一个静态方法,获取这个实例对象
*/
public class Singleton5 {
private static Singleton5 instance;
private Singleton5() {}
public static Singleton5 getInstance() {
if (instance == null) {
synchronized (Singleton5.class) {
if (instance == null) {
instance = new Singleton5();
}
return instance;
}
}
return instance;
}
}
/*
懒汉式:延迟创建实例对象对象
静态内部类形式(适用于多线程)
在内部类被加载和初始化时,才创建INSTANCE实例对象
静态内部类不会自动随着外部类的加载和初始化而初始化,它是单独去加载和初始化的
因为是在内部类加载和初始化时,创建的,因此是线程安全的
*/
public class Singleton6 {
private Singleton6() {}
public static class Inner() {
private static Singleton6 INSTANCE = new Singleton6();
}
public static Singleton6 getInstance(){
return Inner.INSTANCE;
}
}
3.类初始化
3.1 代码
/*
父类
父类的初始化<clinit>():
(1)j = method();
(2)父类的静态代码块
父类的实例化方法:
(1)super()(最前)
(2)i = test();
(3)父类的非静态代码块
(4)父类的无参构造(最后)
非静态方法前面其实有一个默认的对象this
this咋子构造器(或<init>)它表示的是正在创建的对象,因为这里是创建Son对象,所以test()执行的是子类重写的代码(面向对象多态)
这里i=test()指向的是子类重写test()方法
*/
public class Father {
private int i = test();
private static int j = method();
static{
System.out.print("(1)");
}
Father() {
System.out.print("(2)");
}
{
System.out.print("(3)");
}
public int test(){
System.out.print("(4)");
return 1;
}
public static int method() {
System.out.println("(5)");
return 1;
}
}
/*
子类
先初始化父类:(5)(1)
初始化子类:(10)(6)
子类的初始化<clinit>():
(1)j = method();
(2)子类的静态代码块
子类的实例化方法:
(1)super()(最前) (9)(3)(2)
(2)i = test(); (9)
(3)子类的非静态代码块 (8)
(4)子类的无参构造(最后) (8)
因为创建了两个Son对象,因此子类的实例方法<init>执行了两次
(9)(3)(2)(9)(8)(7)
*/
public class Son extends Father {
private int i = test();
private static int j = method();
static {
System.out.print("(6)");
}
Son() {
//super();//写或不写都在,在子类构造器中一定会调用父类的构造器
System.out.print("(7)");
}
{
System.out.print("(8)");
}
public int test(){
System.out.print("(9)");
return 1;
}
public static int method() {
System.out.print("(10)");
return 1;
}
public static void main(String[] args) {
Son son = new Son();
System.out.println();
Son son1 = new Son();
}
}
/*
运行结果
*/
(5)(1)(10)(6)(9)(3)(2)(9)(8)(7)
(9)(3)(2)(9)(8)(7)
3.2 考点
1.类初始化过程
(1)一个类要参加实例需要先加载并初始化该类
main方法所在类需要先加载和初始化
(2)一个子类要初始化需要先初始化父类
(3)一个类初始化就是执行<clinit>()方法
<clinit>()方法是由静态类变量显示赋值代码和静态代码块组成
类变量显示赋值代码和静态代码块代码从上到下顺序执行(注意细节)
<clinit>()方法只执行一次
2.实例初始化过程
(1)实例初始化就是执行<init>()方法
<init>()方法可能重载有多个,有几个构造器就有几个<init>()方法
<init>()方法由非静态实例变量显示赋值代码和非静态代码块、对应构造器代码组成
非静态实例变量显示赋值代码和静态代码块代码从上到下执行,而对应构造器的代码最后执行(注意细节)
每次创建实例对象,调用对应构造器,执行的就是对应的<init>()方法
<init>()方法的首行是super()或super(实参列表),即对应父类的<init>()方法
3.方法的重写
(1)哪些方法不可以被重写
final方法
静态方法
private等子类中不可见的方法
(2)对象的多态性
子类如果重写了父类的方法,通过子类对象调用的一定是子类重写过得代码
非静态方法默认的调用对象是this
this对象在构造器或则说<init>方法中就是正在创建的对象
3.3 Override(重写)和Overload(重载)区别
1.Overload表示同一个类中可以有多个名称相同的方法,但这些方法的参数列表各不相同,即参数个数或类型不同
Overload注意事项:
Overload是指我们可以定义一些名称相同的方法,通过定义不同的输入参数来区分这些方法,然后再调用时,VM就
会根据不同的参数样式,来选择合适的方法执行。在重载时要注意以下几点:
(1)在使用重载时只能通过不同的参数样式。例如,不同的参数类型,不同的参数个数,不同的参数顺序(当然,
同一方法内的几个参数类型必须不一样,例如可以是get(int,float),但是不能为get(int,int);
(2)不能通过访问权限、返回类型、抛出的异常进行重载;
(3)方法的异常类型和数目不会对重载造成影响;
(4)对于继承来说,如果某一方法在父类中是访问权限是priavte,那么就不能在子类对其进行重载,如果定义的
话,也只是定义了一个新方法,而不会达到重载的效果。
2.Override表示子类中的方法可以与父类中的某个方法的名称和参数完全相同,通过子类创建的实例对象调用这个方法时,将调用子类中的定义方法,这相当于把父类中定义的那个完全相同的方法给覆盖了,这也是面向对象编程
的多态性的一种表现。子类覆盖父类的方法时,只能比父类抛出更少的异常,或者是抛出父类抛出的异常的子异常,因为子类可以解决父类的一些问题,不能比父类有更多的问题。子类方法的访问权限只能比父类的更大,不能更小。如果父类的方法是private类型,那么,子类则不存在覆盖的限制,相当于子类中增加了一个全新的方法
Override注意事项:
Override是覆盖了一个方法并且对其重写,以求达到不同的作用。对我们来说最熟悉的覆盖就是对接口方法的实现。
另外,我们在继承中也可能会在子类覆盖父类中的方法。在覆盖时要注意以下几点:
(1)覆盖的方法的标志必须要和被覆盖的方法的标志完全匹配,才能达到覆盖的效果;
(2)覆盖的方法的返回值必须和被覆盖的方法的返回值一致;
(3)覆盖的方法所抛出的异常必须和被覆盖方法的所抛出的异常一致,或者是其子类;
(4)被覆盖的方法不能为private,否则在其子类中只是新定义了一个方法,并没有对其进行覆盖。
4.方法的传递机制
4.1 代码
/*
*/
public class Exam4 {
public static void main(String[] args) {
int i = 1;
String str = "hello";
Integer num = 200;
int[] arr = {1,2,3,4,5};
MyData my = new MyData();
change(i,str,num,arr,my);
System.out.println("i= " + i);
System.out.println("str= " + str);
System.out.println("num= " + num);
System.out.println("arr= " + Arrays.toString(arr));
System.out.println("my.a= " + my.a);
}
public static void change(int j, String s, Integer n, int[] a, MyData m) {
j += 1;
s += "world";
n += 1;
a[0] += 1;
m.a += 1;
}
}
class MyData {
int a = 10;
}
//运行结果
i = 1
str = hello
num = 200
arr = [2, 2, 3, 4, 5]
my.a = 11
4.2 考点
1.形参是基本数据类型:byte short int long float double boolean char
传递数据值
2.实参是引用数据类型:类(例如String,Integer) 接口 数组 null
传递地址值
特殊类型:String、包装类等对象不可变性
5.递归与迭代
有n步台阶,一次只能上1步或2步,共有多少种走法?
方法一:递归
优点:大问题转换为小问题,可以减少代码量,同时代码精简,可读性好;
缺点:递归调用浪费了空间,而且递归太容易造成堆栈的益处
/*
递归重要是分析出递归的核心,不要沉迷于递归的过程
分析:
用f(i)表示 表示当 n=i 时,共有多少种走法
n=1 ->走一步 ->f(1) = 1
n=2 -> (1)一步一步走(2)直接走两步 ->f(2) = 2
n=3 -> (1)先到达f(1),然后从f(1)直接走两步
-> (2)先到达f(2),然后从f(2)走一步 ->f(3) = f(1) + f(2)
n=4 -> (1)先到达f(2),然后从f(2)直接走两步
-> (2)先到达f(3),然后从f(3)走一步 ->f(4) = f(2) + f(3)
..........
n=i -> (1)先到达f(i-2),然后从f(i-2)直接走两步
-> (2)先到达f(i-1),然后从f(i-1)走一步 ->f(i) = f(i-2) + f(i-1)
*/
public class SumStep1{
public int f(int n){
//递归的终止条件
if(n == 1 || n== 2){
return n;
}
return f(n-2) + f(n-1);
}
}
方法二:迭代
优点:代码运行效率好,因为时间只因循环次数的增加而增加,而且没有额外的空间开销;
缺点代码没有递归简洁,没有递归的可读性好
/*
分析:
用f(i)表示 表示当 n=i 时,共有多少种走法
用one保存最后走一步,two保存最后走两步
n=1 ->走一步 ->f(1) = 1
n=2 -> (1)一步一步走(2)直接走两步 ->f(2) = 2
n=3 -> (1)先到达f(1),然后从f(1)直接走两步
-> (2)先到达f(2),然后从f(2)走一步
->f(3) = two + one
->f(3) = f(1) + f(2)
->two = f(1);one = f(2)
n=4 -> (1)先到达f(2),然后从f(2)直接走两步
-> (2)先到达f(3),然后从f(3)走一步
->f(4) = two + one
->f(4) = f(2) + f(3)
->two = f(2);one = f(3)
..........
n=i -> (1)先到达f(i-2),然后从f(i-2)直接走两步
-> (2)先到达f(i-1),然后从f(i-1)走一步
->f(i) = two + one
->f(i) = f(i-2) + f(i-1)
->two = f(i-2);one = f(i-1)
*/
public class SumStep2{
public int loop(int n){
if(n == 1 || n== 2){
return n;
}
int two = 1;//初始化为走到第一节台阶的走法
int one = 2;//初始化为走到第二节台阶的走法
int sum = 0;//保存总的走法
for(int i=3;i<=n;++i){
//最后跨两步 + 最后跨一步的走法
sum = two + one;
two = one;
one = sum;
}
return sum;
}
}
6.成员变量与局部
6.1 代码
/**
*/
public class Exam5 {
static int s;
int i;
int j;
{
int i = 1;
i++;
j++;
s++;
}
public void test(int j) {
j++;
i++;
s++;
}
public static void main(String[] args){
Exam5 obj1 = new Exam5();
Exam5 obj2 = new Exam5();
obj1.test(10);
obj1.test(20);
obj2.test(30);
System.out.println(obj1.i + "," + obj1.j + "," + obj1.s);//2,1,5
System.out.println(obj2.i + "," + obj2.j + "," + obj2.s);//1,1,5
}
}
6.2 考点
1.就近原则
要先考虑作用域
2.变量的分类
(1)成员变量:类变量、实例变量
(2)局部变量
3.非静态代码块的执行:每次创建实例对象都会执行
4.方法的调用规则:调用一次执行一次
6.3 局部变量与成员变量的区别
1.声明的位置:
局部变量:方法体{}中,形参,代码块{}中
成员变量:类中方法外
类变量:有static修饰
实例变量:没有static修饰
2.修饰符
局部变量:final
成员变量:public protected private final static volatile transient
3.值存储的位置
局部变量:栈
实例变量:堆
类变量:方法区
(1)堆:此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。这一点在java虚拟机
规范的描述是:所有的对象实例即数组都要在堆上分配。被所有的线程共享。
(2)栈:是指虚拟机栈。虚拟机栈用于存储局部变量表等。局部变量表存放了编译期
可知长度的各种基本数据类型(boolean、byte、 char、short、 int、 float、long、double) 、对象引用
(reference 类型,它不等同于对象本身,是对象在堆内存的首地址)。方法执行完, 自动释放。栈是独立的,每个
线程包含一个栈区
(3)方法区:又叫静态区,用于存储已被虛拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
运行时常量池都分配在 Java 虚拟机的方法区之中。被所有的线程共享。
总结:
堆区:只存放类对象,线程共享;
方法区:又叫静态存储区,存放class文件和静态数据,线程共享;
栈区:存放方法局部变量,基本类型变量区、执行环境上下文、操作指令区,线程不共享;
4.作用域
局部变量从声明处开始,到所属的 } 好结束
实例变量:在当前类中’‘ this.’‘ (有时 ’‘this.’‘ 可以缺省),在其他类中 “对象名.” 访问
类变量:在当前类中 ”类名.”( 有时类名. 可以缺省),在其它类中“类名.” 或 “对象名.” 访问
5.生命周期
局部变量:每一个线程,每一次调用执行都是新的生命周期
实例变量:随着对象的创建而初始化,随着对象的被回收而消亡,每一个对象的实例变量都是独立的
类变量:随着类的初始化而初始化,随着类的卸载而消亡,该类的所有对象的类变量是共享的
当局部变量与XX变量重名时,如何区分:
(1)局部变量与实例变量重名
在成员变量前面加 “this.”
(2)局部变量与类变量重名
在类变量前面加” 类名.“
7.Spring Bean的作用域之间有什么区别
类别 | 说明 |
singleton | 在SpringIOC容器中仅存在一个Bean实例,Bean以单实例的方式存在 |
prototype | 每次调用getBean()时都会返回一个新的实例 |
request | 每次HTTP请求都会创建一个新的Bean,该作用域仅适用于WebApplicationContext环境 |
session | 同一个HTTP Session 共享一个Bean,不同的 HTTP Session 使用不同的Bean。该作用域仅使用于WebApplicationContext环境 |
8.Spring支持的常用数据库事务传播属性和事务隔离级别
8.1 什么是事务
事务是数据库操作最基本单元,逻辑上一组操作,要么都成功,如果有一个失败,所有操作都失败
8.2 事务的四个特性(ACID)
特性 | 描述 |
原子性(Atomicity) | 一个事务(transaction)中的所有操作,或者全部完成,或者全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。即,事务不可分割、不可约简。 |
一致性(Consistency) | 在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设约束、触发器、级联回滚等。 |
隔离性(Isolation) | 数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括未提交读(Read uncommitted)、提交读(read committed)、可重复读(repeatable read)和串行化(Serializable)。 |
持久性(Durability) | 事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。 |
8.3 事务的传播行为(propagation)
传播属性 | 描述 |
REQUIRED | 如果有事务在运行,当前的方法就在这个事务内运行,否则就启动一个新的事务,并在自己的事务内运行 |
REQUIRED_NEW | 当前方法必须启动事务,并在它自己的事务内运行,如果有事务正在运行,应该将他挂起 |
SUPPORTS | 如果有事务在运行,当前的方法就在这个事务内运行,否则他可以不运行在事务中 |
NOT_SUPPORTE | 当前的方法不应该运行在事务中,如果有运行的事务,将他挂起 |
MANDATORY | 当前的方法必须运行在事务内部,如果没有正在运行的事务,就抛出异常 |
NEVER | 当前方法不应该运行在事务中,如果有运行的事务,就抛出异常 |
NESTED | 如果有事务在运行,当前的方法就应该在这个事物的嵌套事务内运行,否则,就启动一个新的事务,并在它自己的事务内运行 |
8.4 事务的隔离级别(isolation)
8.4.1 不隔离出现的三个问题
1.脏读:一个未提交事务读取到另一个未提交事务的数据
2.不可重复读:一个未提交事务读取到另一提交事务修改数据
3.虚读:一个未提交事务读取到另一提交事务添加数据
8.4.2 解决:设置事务隔离级别
隔离级别 | 脏读 | 不可重复读 | 虚读 |
READ UNCOMMITTED(读未提交) | 有 | 有 | 有 |
READ COMMITTED(读已提交) | 无 | 有 | 有 |
REPEATABLE READ(可重复读) | 无 | 无 | 有 |
SERIALIZABLE(串行化) | 无 | 无 | 无 |
8.5 面试题
请简单介绍Spring支持的常用数据库事务传播属性和事务隔离级别?
事务的属性:
1.propagation:用来设置事务的传播行为
事务的传播行为:一个方法运行在了一个开启了事务的方法中时,当前方法是使用原来的事务还是开启一个新的事务
-Propagation.REQUIRED:默认值,使用原来的事务
-Propagation.REQUIRES_NEW:将原来的事务挂起,开启一个新的事务
2.isolation:用来设置事务的隔离级别
-Isolation.REPEATABLE_READ:可重复读,MySQL默认的隔离级别
-Isolation.READ_COMMITTED:读已提交,Oracle默认的隔离级别,开发时通常使用的隔离级别
9.SpringMVC
9.1 SpringMVC概述
9.1.1 SpringMVC简介
- SpringMVC 也叫 Spring web mvc。是 Spring 框架的一部分,是在 Spring3.0 后发布的。
9.1.2 SpringMVC优点
1.基于MVC架构
基于MVC架构,功能分工明确。解耦合
2.容易理解,上手快;使用简单。
就可以开发一个注解的 SpringMVC 项目,SpringMVC 也是轻量级的,jar很小。不依赖的特定的接口和类。
3. 作为Spring框架一部分, 能够使用Spring的IoC和Aop。方便整合Strtus,MyBatis,Hiberate,JPA 等其他框架。
4.SpringMVC强化注解的使用,在控制器,Service,Dao 都可以使用注解。方便灵活。
使用@Controller创建处理器对象,@Service创建业务对象,@Autowired或者@Resource在控制器类中注入Service, Service 类中注入 Dao。
9.2 SpringMVC执行流程
9.2.1流程图
9.2.2流程图简单分析
1.浏览器提交请求到中央调度器
2.中央调度器直接将请求转发给处理器映射器
3.处理器映射器会根据请求,找到处理该请求的处理器,并将其封装为处理器执行链后返回给中央调度器
4.中央调度器根据处理器执行链中的处理器,找到能够给执行该处理器的处理器适配器
5.处理器适配器调用执行处理器
6.处理器将处理结果即要跳转的试图封装到一个对象ModelAndView中,并将其返回给处理器适配器
7.处理器适配器直接将结果返回给中央调度器
8.中央调用器调用试图解析器,将 ModelAndView中视图名称封装为视图对象
9.视图解析器将封装了的视图对象返回给中央调度器
10.中央调度器调用视图对象,让其自己进行渲染,即进行数据填充,形成响应对象
11.中央调度器响应浏览器
9.3 SpringMVC核心技术
9.3.1 请求重定向和转发
- 当处理器对请求处理完毕后,向其它资源进行跳转时,有两种跳转方式:请求转发与重定向。而根据所要跳转的资源类型,又可分为两类:跳转到页面与跳转到其它处理器。
- 注意,对于请求转发的页面,可以是WEB-INF中页面;而重定向的页面,是不能为WEB-INF中页的。因为重定向相当于用户再次发出一次请求,而用户是不能直接访问 WEB-INF 中资源的。
- SpringMVC 框架把原来 Servlet 中的请求转发和重定向操作进行了封装。现在可以使用简单的方式实现转发和重定向
forward:表示转发,实现 request.getRequestDispatcher(“xx.jsp”).forward()
rediret:表示重定向,实现 response.sendRedirect(“xxx.jsp”)
9.3.2 异常处理
- SpringMVC 框架处理异常的常用方式:使用@ExceptionHandler注解处理异常。
- 使用注解@ExceptionHandler 可以将一个方法指定为异常处理方法。该注解只有一个可选属性 value,为一个 Class<?>数组,用于指定该注解的方法所要处理的异常类,即所要匹配的异常。
- 而被注解的方法,其返回值可以是 ModelAndView、String,或 void,方法名随意,方法参数可以是 Exception 及其子类对象、HttpServletRequest、HttpServletResponse 等。系统会自动为这些方法参数赋值。
- 对于异常处理注解的用法,也可以直接将异常处理方法注解于 Controller 之中。
9.3.3 拦截器
- 1.一个拦截器的执行
- 2.多个拦截器的执行
10.Mybatis
10.1 Mybatis简介
- mybatis框架
1.一个框架,早期叫做ibatis,代码在github。
2.mybatis是 MyBatis SQL Mapper Framework for Java (sql映射框架)
(1)sql mapper :sql映射
可以把数据库表中的一行数据 映射为 一个java对象。
一行数据可以看做是一个java对象。操作这个对象,就相当于操作表中的数据
(2)Data Access Objects(DAOs) : 数据访问 , 对数据库执行增删改查。
- mybatis提供了哪些功能:
1. 提供了创建Connection ,Statement, ResultSet的能力 ,不用开发人员创建这些对象了
2. 提供了执行sql语句的能力, 不用你执行sql
3. 提供了循环sql, 把sql的结果转为java对象, List集合的能力
4. 提供了关闭资源的能力,不用你关闭Connection, Statement, ResultSet
- 开发人员做的是: 提供sql语句
最后是: 开发人员提供sql语句—>mybatis处理sql—>开发人员得到List集合或java对象(表中的数据) - 总结:
mybatis是一个sql映射框架,提供的数据库的操作能力。增强的JDBC,
使用mybatis让开发人员集中精神写sql就可以了,不必关心Connection,Statement,ResultSet
的创建,销毁,sql的执行。
10.2 面试题
- Mybatis中当实体类中的属性名和表中的字段名不一样,怎么办?
解决方案:
1.写sql语句的时候起别名
方式一:使用as
select last_name AS 姓,first_name AS 名 from employees;
方式二:使用空格
select last_name 姓,first_name 名 from employees;
2.在Mybaits的全局配置文件中开启驼峰命名规则
可以将数据库中的下划线映射为驼峰命名
例如:last_name 可以映射为lastName
<settings>
<setting name="mapUnderscoreToCameCase" value="true">
</settings>
3.在Mapper映射文件中自定义文件中使用resultMap自定义映射
<resultMap type="com.atguigu.pojo.Employee" id="myMap">
<!-- 映射主键 -->
<id cloumn="id" property="id"/>
<!-- 映射其他列 -->
<result column="last_name" property="lastName" />
<result column="email" property="email" />
<result column="salary" property="salary" />
<result column="dept_id" property="deptId" />
</resultMap>
- 个人总结:核心就是能够使实体类中的属性名和表中的字段名能够一一对应
11.Linux常用服务类相关命令
- CentOS是免费的、开源的、可以重新分发的开源操作系统 ,CentOS(Community Enterprise Operating System,中文意思是社区企业操作系统)是Linux发行版之一。
- 1.service(centos6)
注册在系统中的标准化程序
有方便统一的管理方式(常用的方法)
service 服务名 start
service 服务名 stop
service 服务名 restart
service 服务名 reload
service 服务名 statuse
查看服务的方法 /etc/init.d/服务名
通过chkconfig 命令设置自启动
查看服务 chkconfig --list|grip xxx
chkconfig --level 5 服务名 on
- 2.运行级别runlevel(centos6)
开机
BIOS
/boot
init进程
运行级别
运行级对应服务
查看默认级别: vi/etc/inittab
Linux系统有七种运行级别:常用的是级别3和级别5
级别 | 说明 |
运行级别0 | 系统停机状态,系统默认运行级别不能设为0,否则不能正常启动 |
运行级别1 | 单用户工作状态,root权限,用于系统维护,禁止远程登录 |
运行级别2 | 多用户状态(没有NFS),不支持网络 |
运行级别3 | 完全的多用户状态(有NFS),登录后进入控制台命令模式 |
运行级别4 | 系统未使用,保留 |
运行级别5 | X11控制台,登录后进入图形GUI模式 |
运行级别6 | 系统正常关闭并重启,默认运行级别不能设为6,否则不能正常启动 |
- 3.systemctl(centos7)
注册在系统中的标准化程序
有方便的统一的管理方式(常用的方法):
systemctl start 服务名(xxx.service)
systemct restart 服务名(xxxx.service)
systemctl stop 服务名(xxxx.service)
systemctl reload 服务名(xxxx.service)
systemctl status 服务名(xxxx.service)
查看服务的方法 /usr/lib/systemd/system
查看服务的命令
systemctl list-unit-files
systemctl --type service
通过systemctl命令设置自启动
自启动systemctl enable service_name
不自启动systemctl disable service_name
12.Git分支相关命令和实际应用
- 1. 分支
分支 | 命令 |
创建分支 | git branch <分支名> git branch -v 查看分支 |
切换分支 | git checkout <分支名> 一步完成: git checkout -b <分支名> |
合并分支 | 先切换到主干 git checkout master git merge <分支名> |
删除分支 | 先切换到主干 git checkout master git branch - D <分支名> |
- 2.Git工作流
master分支:开发人员接触不到
develop:开发主线分支,可以创建对个。
出现bug时可以创建临时分支,bug解决后可以把临时分支融合到master分支。
13.redis持久化
- 1.redis提供了两个不同形式的持久化方式(两种方式互补)
- RDB (Redis DataBase)
- (1)在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里。
- (2)备份是如何执行的:
- Redis会单独创建(fork) -个子进程来进行持款化,铣将数据写入到一个临时文件中,待玖化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何I0操作的,这就确保了极高的性能如果需要进行大规模数据的恢复,肘于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。
- (3)优点:
- 节省磁盘空间
- 回复速度快
- (4)缺点:
- 虽然Redis在fork时使用了写时拷贝技术,但是如果数据庞大时还是比较消耗性能。
- 在备份周期在一定间隔时间做一-次备份, 所以如果Rediq意外down掉的话,就会丢失最后一-次快照后的所有修改。
- AOF (Append Of File)
- (1)以日志的形式来记录每个写操作,将Redis执行过的所有写指令记录下来(读操作不记录),只许追加文件但不可以改写文件,Redis启动之初会读取该文件重新构建数据,换言之,Redis重启的话就根据日志文件的内容将写指令从前到后执行一-次以完成数据的恢复工作。
- (2)优点:
- 备份机制更稳健,丢失数据概率更低。
- 可读的日志文本,通过操作AOF稳健,可以处理误操作。
- (3)缺点:
- 比起RDB占用更多的磁盘空间。
- 恢复备份速度要慢。
- 每次读写都同步的话,有一定的性能压力。
- 存在个别Bug,造成不能恢复。
14.MySQL索引
14.1 简介
- MySQL官方对索引的定义为:
- 索引(Index)是帮助MySQL高效获取数据的数据结构。
- 可以得到索引的本质:索引是数据结构
14.2 适合建立索引的情况
- 主键自动建立唯 一 索引
- 频繁作为查询条件的字段应该创建索引
- 查询中与其它表关联的字段,外键关系建立索引
- 频繁更新的字段不适合创建索引,因为每次更新不单是更新了记录还会更新索引
- 单键组索引的选择问题,who? 在高并发下领向创建组合索引
- 意询中排序的字段,排序字段若通过索引法访问将大大提高排序速度
- 查询中统计或者分组字段
14.3 不适合建立索引的情况
- 表记录太少
- 经常增删改的表或则字段
- Where条件里用不到的字段不创建索引
- 过滤性不好的不适合创建索引
15.JVM
- 面试题:JVM垃圾回收机制、GC发生在JVM哪部分,有几种GC,他们的算法是什么
1.GC发生在堆区
2.GC就是分代收集算法:
次数上频繁收集Young区 (Minor GC )
次数上较少收集old区 ( Full GC)
基本不动元空间(Perm区)
3.GC四大算法:
(1)引用计数法
(2)复制算法
(3)标记清除
(4)标记压缩
- JVM体系结构
16.redis在项目中的使用场景
数据类型 | 使用场景 |
String | 比如说,我想知道什么时候封锁一个 IP 地址。Incrby 命令 |
Hash | 存储用户信息[ id, name , age] Hset( key ,field, value) Hset( key ,id, 101) Hset( key ,name, admin) Hset( key ,age, 23) --------修改案例------- Hget(userKev,jd) Hset(userKey,id,102) 为什么不使用String类型来存储 Set(userKey;用信息的字符串) Get(userKey) 不建议使用String 类型。 |
List | 实现最新消息的排行,还可以利用 List 的 push 命令,将任务存在list集合中,同时使用另一个命令[ pop ],将任务从集合中取出。 Redis - List 数据类型来模拟消息队列。【例如电商中的秒杀就可以采用这种方式来完成一个秒杀活动】。 |
Set | 特殊之处:可以自动排重。比如说微博中将每个人的好友存在集合( Set) 中,这样求两个人的共通好友的操作。我们只需要求交集即可。 |
Zset | 以某一个条件为权重,进行排序。 京东:商品详情的时候,都会有一个综合排名,还可以按照价格进行排名 |
17.es(Elasticsearch)和solr的区别
- 背景:
他们都是基于 Lucene 搜索服务器基础上开发,一款优秀的,高性能的企业级搜索服务器。【是因为他们都是基于分词技术构建的倒排索引的方式进行查询】 - 开发语言:java语言开发
- 诞生时间:
- Solr:2004年诞生
- Es:2010年诞生。
- Es更新【功能越强大】
- 区别:
- 当实时建立索引的时候solr会产生io阻塞,而es则不会 ,所以es的查询性能要高于solr
- 在不断动态添加数据的时候,solr的检索效率会变得低下,而es则没有什么变化
- Solr利用zookeeper进行分布式管理,而es自身带有分布式系统管理功能。Solr一般都要部署到web服务器上,比如tomcat。启动tomcat的时候需要配置tomcat与solr的关联。【solr的本质是一个动态的web项目】
- Solr支持更多的格式数据[xml,json,csv等],而es仅支持json文件格式
- Solr是传统搜索应用的有力解决方案,但是es更适用于新兴的实时搜索应用。
- 单纯的对已有数据进行检索的时候,solr效率更好,高于es
- Solr官网提供的功能更多,而es本身更注重于核心功能,高级功能多有第三发插件
- Zookeeper集群
- SolrCloud集群
- Elasticsearch集群
18.单点登录的实现过程
- 单点登录: 一处登录多处使用!
- 前提:单点登录多使用在分布式系统中
- Demo:
参观动物园流程
检票员=认证中心模块
1.我直接带着大家进动物园,则会被检票员拦住【看我们是否有票】,没有【售票处买票】
登录=买票
2.我去买票【带着票,带着大家一起准备进入动物园】 检票员check【有票】
Token = piao
3.我们手中有票就可以任意观赏动物园的每处景点
京东:单点登录,是将 token 放入到 cookie 中
案例:将浏览器的 cookie 禁用,则在登录京东则失效!无论如何登录不了
19.购物车实现过程
购物车:
- 1、购物车跟用户的关系 ?
- (1)一个用户必须对应一个购物车【一个用户不管买多少商品,都会存在属于自己的购物车中】
- (2)单点登录一定要在购车前
- 2、跟购物车有关的操作有那些?
- (1)添加购物车
- 用户未登录状态
1.添加到什么地方?未登录将数据保存到什么地方?
(1) Redis? ----京东
(2) Cookie?--- 自己开发项目的时候【如果浏览器禁用Cookie】
- 用户登录状态
1.Redis 缓存中 【读写速度快】
(1)Hash: Hset(key,field,value)
Key:user:userId,cart
Hset(key,skuId,value)
2.存在数据库中 【Oracle,mysql】
- (2)展示购物车
- 未登录状态显示
1.直接从 cookie 中取得数据展示即可
- 登录状态
1. 用户一旦登录,必须显示数据库【redis】 + cookie 中的购物车的数据
(1)Cookie 中有三条记录
(2)Redis 中有五条记录
(3)真正展示的时候应该是八条记录
20.消息队列在项目中的使用
- 背景:
在分布式系统中是如何处理高并发的。 - 由于在高并发的环境下,来不及同步处理用户发送的请求,则会导致请求发生阻塞,比如说,大量的 insert,update 之类的请求同时到达数据库 MySQL, 直接导致无数的行锁表锁,甚至会导致请求堆积很多。从而触发 too many connections 错误。使用消息队列可以解决 【异步通信】
- 1.异步
- 2.并行
- 3.排队
- 消息队列在电商中的应用场景
- 消息队列的弊端
- 消息的不确定性:延迟队列,轮询技术来解决该问题即可
- 消息队列推荐大家使用activemq!环境都是java。
- ActiveMQ是一种开源的,实现了 JMS1.1 规范的,面向消息(MOM)的中间件,为应用程序提供高效的、 可扩展的、稳定的和安全的企业级消息通信。
- 具体使用哪一款的消息队列还需要根据具体的项目需求