不记文档,白忙一场
每日课程
1> java相关 -> 设计模式 23种名称+划分类型都过一遍,细节每日过5种 2>
0、java相关
0、基础知识大牛
1> 基础中基础(数篇):https://blog.csdn.net/u014634338/article/details/81434327 2> ClassLoader不在面试准备之列,最好有时间写一个小Demo 3> java之注解源码(自定义注解)不在面试准备之列,最好有时间写一个小Demo
1、设计模式
详见"学习资料 -> 后端 -> java" 1> 学习地址一 地址:菜鸟教程 -> 工厂模式 网址:https://www.runoob.com/design-pattern/factory-pattern.html 2> 学习地址二 地址:优秀博客 网址:https://www.cnblogs.com/onetwo/p/9933417.html
2、java8的新特性
Lambda 表达式
格式: (params) -> expression (params) -> statement (params) -> { statements } ------------------------------------------------------------------------------------------- 场景: 将函数当成参数传递给某个方法 ------------------------------------------------------------------------------------------- 例子(匿名内部类)1: 1> 排序:new ArrayList<>().sort(Comparator<? super E> c) 2> 迭代:new ArrayList<>().forEach(n -> System.out.println(n)); 3> 排序:Collections.sort(List<T> list, Comparator<? super T> c) 4> 线程:new Thread( () -> System.out.println("In Java8") ).start(); ------------------------------------------------------------------------------------------- 例子(配合函数式接口Predicate)2: filter(List<String> names, Predicate<String> condition) if(condition.test(name)) 调用: filter(Arrays.asList("1","2"), (String str)->str.startsWith("J")); ------------------------------------------------------------------------------------------- 例子(更多): https://www.cnblogs.com/coprince/p/8692972.html
函数式接口(Lambda 表达式配合函数式接口,将函数当成参数传递给某个方法 )
概述: 函数式接口,用Lambda表达式赋值,实现了将函数当成参数传递给某方法 ------------------------------------------------------------------------------------------- 例子: 1> Function<T,R> T 作为输入,返回的 R 作为输出 Function<String,String> function = (x) -> {return x "Function";}; System.out.println(function.apply("hello world")); // hello world Function 注:既有输入,也有输出,apply方法 2> Predicate<T> T 作为输入 ,返回 boolean 值的输出 Predicate<Integer> predicate = (x) ->{return x % 2 == 0 ;}; System.out.println(predicate.test(1)); // false 注:test方法 3> Consumer<T> T 作为输入 ,没有输出 Consumer<String> consumer = (x) -> {System.out.println(x);}; consumer.accept("hello world "); // hello world 注:消费者,只有输入,accept【接收】方法 4> Supplier<R> 没有输入 , R 作为输出 Supplier<String> supplier = () -> {return "Supplier";}; System.out.println(supplier.get()); 注:提供者,只有输出,get【提供的第几个】方法 注:你可以把其当成正常的接口使用,由用户使用 Lambda 传入
《流》
1> 概述 1> 应用在Stream流上的操作,可以分成两种:Intermediate(中间操作)和Terminal(终止操作)。 2> 中间操作的返回结果都是Stream,故可以多个中间操作"叠加"; 3> 终止操作用于返回我们最终需要的数据,只能有一个终止操作 4> 使用流可以实现并行操作,不需要程序猿生成线程,只专心逻辑代码就好 2> 生成流的方法 1> List/Set(Collection接口的 ):stream()或parallelStream()方法 2> Arrays:Arrays.stream(array, from, to) 3> 静态的Stream.of()、Stream.empty()方法 4> 静态的Stream.generate()方法生成无限流,接受一个不包含引元的函数 注:集合有1个,数组有1个,Stream静态方法有2个 3> 基础接口BaseStream包含如下方法: 1> boolean isParallel(); //判断是否是并行化流 2> S sequential(); //将流串行化 3> S parallel(); //将流并行化 4> S unordered(); //解除有序流的顺序限制,发挥并行处理的性能优势 4> 流的Intermediate方法(中间操作) 0> sorted(Comparator) 排序(将流元素按Comparator排序) 1> filter(Predicate) 过滤(将结果为false的元素过滤掉) 2> distinct() 去重 3> limit(n) 保留前n个元素 4> skip(n) 跳过前n个元素 5> map(fun) ===> 重要 注:转换元素的值,可以用方法引元或者lambda表达式(python也有此功能) 5> 流的Terminal方法(终止操作) max(Comparator) min(Comparator) count() findFirst() 返回第一个元素 findAny() 返回任意元素 anyMatch(Predicate) 任意元素匹配时返回true allMatch(Predicate) 所有元素匹配时返回true noneMatch(Predicate) 没有元素匹配时返回true reduce(fun) ===> 重要 注:reduce -> 从流中计算某个值,接受一个二元函数作为"累积器",从前两个元素开始持续应用它,累积 器的中间结果作为第一个参数,流元素作为第二个参数(python也有此功能) 6> 基本类型流 vs 对象流 1> Stream、IntStream、LongStream、DoubleStream都是BaseStream的子接口 2> IntStream、LongStream、DoubleStream直接存储基本类型,可以避免自动装/拆箱,效率会更高一些 7> 更多: https://blog.csdn.net/sanri1993/article/details/101176712 https://www.cnblogs.com/coprince/p/8692972.html
3、深浅拷贝
相同点: 都是实现Cloneable接口,重写clone方法 1> 浅拷贝 只拷贝目标对象,目标对象中如果还有引用,则原来对象和拷贝生成对象都指向一个内存。 2> 深拷贝 目标对象和目标对象中引用都进行拷贝。 注:深拷贝实现 1、两个类都实现了cloneable接口,重写clone方法时,super.clone()返回对象,点内部引用再点 clone()方法,将内部引用也拷贝。 2、还有一种没有试验的方法,实现Serializable接口(这个接口的作用是实现序列化) 注:https://blog.csdn.net/riemann_/article/details/87217229
4、集合
HashMap - 源码
1> 底层数据结构 1> jdk1.7采用数组+链表 2> jdk1.8采用数组+链表+红黑树 2> 为什么要加链表(和红黑树),而不是直接用数组存储 1> 因为有哈希碰撞的问题,数组中每个元素都是链表 2> 数组查找快,插入删除效率低;链表查找需要遍历慢,插入删除直接重新指向下一节点效率高 数组解决查询问题,链表解决插入删除问题 3> 红黑树解决单向链表过长查询效率问题 3> 关键变量 1> table 1> 类型是Entry<K,V>[],是哈希表中的数组 2> Entry<K,V> 1> 表示链表节点,jdk1.7名称为Entry,1.8改名为Node,红黑树节点为TreeNode 2> 是静态内部类,成员变量有:key,value,hash值(整数),next(Entry<K,V>对象) 3> capacity 1> 数组容量,初始值(定义了常量),值1<<4(1左移4位),也就是16 2> 扩容一次,容量变为原来2倍 4> load_factor 1> 负载因子,初始值(定义了常量),值0.75 5> threshold 1> 扩容阈值,等于capacity*load_factor,数组元素个数大于等于该值,开始扩容 4> put操作 1> 根据key计算哈希值 1> jdk1.7中,一堆的异或和>>>运算 2> jdk1.8中,(key==null)? 0 : (h = key.hashCode()) ^ (h>>>16) 注:key.hashCode()先将值赋值给h变量,再将新的h值无符号右移16位,最后将两边做异或运算。 即key的hashCode值,高16位和低16位进行异或运算。 1010 0011 1100 1111 -> 无符号右移之后 -> 0000 0000 1010 0011 前后做异或运算 1010 0011 1100 1111 0000 0000 1010 0011 ------------------- 1010 0011 0110 1100 注:jdk1.8运算方法,是作者的一个经验,这样做使hash值分布比较均匀,碰撞概率比较低 ------------------------------------------------------------ 注释掉第2步:判断table变量(即HashMap的数组)是否为null,如果为null, 则初始化HashMap的数组,大小为16,扩容阈值为12,元素类型为Node<K,V> ------------------------------------------------------------ 3> 计算数组下标 (n-1) & hash 注:数组容量为n,第一步计算的哈希值为hash 注:n-1转为二进制,不论和什么值做&运算,肯定小于n-1,保证下标就在HashMap数组容量内 4> 判断下表所在位置是否有元素 1> 若没有,则新建一个链表节点,放在该下标位置 2> 若已有元素,则进行进一步判断 1> 如果已有元素的hash值等于当前hash值且key相等或者key的equals相等,则 还是保持原来的元素(后面代码将数组元素的value值重新赋值为当前value) 2> 如果已有元素的类型是TreeNode,则 将当前元素执行挂载红黑树下操作 3> 其余情况(是链表) 1> 循环链表所有节点 2> 在循环中判断节点的下一节点是否为null 3> 如果是null,就时链表的尾巴了,新建一节点,让最后一个节点的next引用指向新节点 4> 判断如果元素个数大于等于转红黑树的一个阈值8,则执行链表转红黑树操作 注:其余情况中,就return了,不会执行下面代码进行++size > threshold判断 是否容量超过扩容阈值。即挂在链表下的不算HashMap数组容量。 5> get操作 1> 根据key计算哈希值 1> jdk1.7中,一堆的异或和>>>运算 2> jdk1.8中,(key==null)? 0 : (h = key.hashCode()) ^ (h>>>16) 2> 计算数组下标 (n-1) & hash 注:jdk1.7和1.8计算数组下表方法一致 3> 判断下表所在位置是否有元素,如果有则 1> 如果hash值和当前hash值一样,且key相等或key的equal相等,则返回第一个节点对象 2> 如果第一个节点的next属性不为null,则 1> 第一个节点类型为TreeNode,则从红黑树取 2> 否则,循环链表节点,比对"hash值和当前hash值一样,且key相等或key的equal相等", 并返回值
Map - 面试
1> HashMap的数据结构 2> HashMap的hash运算是如何实现的?为什么这样实现 1> 提高性能:异或和位移运算,对CPU更友好,减少系统开销 2> 减少hash碰撞:通过异或和位移运算,让hash值变得更复杂,进而影响hash的分布 3> HashMap默认容量是多少?如何扩容 1> 默认16,也可以在构造时传入capacity初始值和load_factor初始值。最大1<<30,即2的30次方 2> 就容量左移1位。即<<1,即扩大为原来的2倍 4> HashMap的put方法的过程 见"源码" 5> HashMap链表节点过深时,为什么选择红黑树 优化查找数据性能:O(n) --> O(logN) 6> 什么是hash碰撞,发生碰撞怎么办 1> 键值的hash值一样,进而计算数组下表一样,导致hash碰撞 2> HashMap中使用的是拉链法 7> JDK1.8对HashMap做了哪些改进 1> 链表长度大于等于8,那么链表将转换为红黑树 2> 发生hash碰撞时,1.7在链表头部插入,1.8在尾部插入 3> 链表节点由Entry转换为Node类(换了一个类名) 4> hash的实现,1.8是(key==null)? 0 : (h = key.hashCode()) ^ (h>>>16); 1.7中是一堆的异或和>>>运算 8> HashMap和HashTable的区别 1> HashMap是线程不安全的;HashTable是线程安全的。 因为HashTable的put和get方法直接加了synchronized。HashTable效率小于HashMap 3> HashMap允许一个key值为null,多个value值为null;HashTable不允许。 因为HashMap的hash方法,如果key为null,返回0,计算数组下标用(n-1)&hash肯定是0,放在0位置 4> HashMap默认大小16,HashTable是11,扩容前者是两倍,后者是两倍+1 5> HashMap的hash值:(key==null)? 0 : (h = key.hashCode()) ^ (h>>>16); HashTable的hash值:直接用key.hashCode() 9> HashMap和ConcurrentHashMap的区别 1> ConcurrentHashMap在java.util.concurrent包下,线程安全;HashMap线程不安全。 (1.7用分段锁,1.8用CAS+Synchronized -- 不需要记) 2> CurrentHashMap的键值对都不允许有空值; 10> 我们能否让HashMap实现同步(线程安全)? 调用工具类Collections的synchronizeMap方法:Map map = Collections.synchronizedMap(hashMap
Map - 待解决
1> 来源:https://www.cnblogs.com/zengcongcong/p/11295349.html 2> HashMap在java8之后的特性 初始数据量 数据量扩展到多大,由数组展开为链表 3> 执行put/get方法,下表是根据(n-1)&hash计算的,也就是数组不是顺序放置节点,怎么保证数组全部 用完再扩容。 数组存在的空白的地方,怎么处理 4> 1<<4 1> 位移运算符 << : 左移运算符,num << 1,相当于num乘以2(num表示的二进制数左移一位,低位补0) >> : 右移运算符,num >> 1,相当于num除以2(num表示的二进制数右移一位) >>> : 无符号右移,忽略符号位,空位都以0补齐 2> 来源:https://www.cnblogs.com/hongten/p/hongten_java_yiweiyunsuangfu.html 5> ^ 1> 异或,两个值相异,则结果为真。 2> 二进制运算中,两值相等,则结果为1,不等,则结果为0。比如1^0=1 3> 只有二进制运算有异或运算,所以十进制两个数,需要换算为二进制再进行计算 4> 面试题 int[] numarry = new int[]{1,2,2}; int aim = numarry[0]; for(int i = 1; i < 3; i++) { aim = aim ^ numarry[i]; } System.out.println("最后:"+aim); 解决思路:异或运算的结合律 -> a ^ b ^ c = a ^ (b ^ c) = (a ^ b) ^ c 注:结合律这样的运算规律是数学家几代人,得出来的,记住即可。1^2^2 = 1^(2^2)=1^0=1 来源:https://blog.csdn.net/guanminwei/article/details/104022161 6> & 运算规则:两个数都转为二进制,然后从高位开始比较,如果两个数都为1则为1,否则为0。
集合整体框架 - Collection
来源:https://www.cnblogs.com/bingyimeiling/p/10255037.html 1> 概述: 1> Collection派生出了三个子接口:List、Set、Queue 2> 其中List代表了有序可重复集合,可直接根据元素的索引来访问; Set代表无序不可重复集合,只能根据元素本身来访问; Queue是队列集合; Map代表的是存储key-value对的集合,可根据元素的key来访问value。 2> List接口 1> 概述: List接口常用的实现类有: ArrayList类,实现了List接口; LinkedList类,同时实现了List接口和Deque接口; Vector类,实现了List接口; Stack类,继承自Vector类; 2> ArrayList类: 1> ArrayList是一个动态数组,初始容量(10),果我们明确所插入元素的多少,最好指定一个初始 容量值,避免过多的进行扩容操作而浪费时间、效率。 2> 在每次向容器中增加元素的同时都会进行容量检查,当快溢出时,就会进行扩容操作。 3> LinkedList类: 1> 除了可以根据索引访问集合元素外,LinkedList还实现了Deque接口,可以当作双端队列来使用, 也就是说,既可以当作"栈"使用,又可以当作队列使用 2> LinkedList的实现机制与ArrayList的实现机制完全不同,ArrayLiat内部以数组的形式保存集合 的元素,所以随机访问集合元素有较好的性能;LinkedList内部以链表的形式保存集合中的元 素,所以随机访问集合中的元素性能较差,但在插入删除元素时有较好的性能 4> Vector类: 与ArrayList相似,但是Vector是同步的。所以说Vector是线程安全的动态数组。它的操作与 ArrayList几乎一样 5> Stack类: Stack继承自Vector,实现一个后进先出的堆栈,基本的push和pop 方法 3> Set接口 1> 概述 1> Set集合不允许存储相同的元素,所以如果把两个相同元素添加到同一个Set集合,则添加操作失 败,新元素不会被加入,add()方法返回false 2> Set接口常用的实现类有: HashSet类,实现了Set接口; LinkedHashSet类,继承自HashSet类,同时实现了Set接口; TreeSet类,实现了SortedSet接口,SortedSet接口继承自Set接口; EnumSet抽象类,实现了Set接口;、 2> HashSet类 1> HashSet不是线程同步的,如果多线程操作HashSet集合,则应通过代码来保证其同步。(HashSet 是按照hash算法来存储元素的,因此具有很好的存取和查找性能。) 2> HashSet存储原理: 1> 位置:当向HashSet集合存储一个元素时,调用该对象的hashCode()方法得到其hashCode 值,然后根据hashCode值决定该对象的存储位置 2> 位置是否已有值:两个对象通过equals()方法比较返回true,两个对象的hashCode()方法返 回值相等。HashSet会以链式结构将两个对象保存在同一位置,这将导致性能下降
5、泛型
来源:https://www.jianshu.com/p/986f732ed2f1 来源:https://www.cnblogs.com/lixuwu/p/10829368.html 1> 概述 1> 泛型的本质是参数化类型,也就是说使用泛型类或者泛型方法,所操作的数据类型被指定为一个参数 注:什么是参数化类型 Collection<String>或LinkedList<String> Collection<E>中的类型也作为了参数。 2> 为什么要引入泛型 1> 在jdk1.5引入泛型之前,使用 Object 来实现通用,缺点 1> 强制转换:每次使用时都需要强制转换成想要的类型 2> 安全:在编译时编译器并不知道类型转换是否正常,运行时才知道,不安全 2> 所以泛型好处 1> 消除强制类型转换:泛型的一个附带好处是,使用时直接得到目标类型 2> 类型安全:编译时期就可以检查出因 Java 类型不正确导致的 ClassCastException 异常 3> 类型的上界和下界(限定泛型类型变量) extends 关键字声明了类型的上界(理解为:类型都需要继承于xxx) super 关键字声明了类型的下界(理解为:类型都需要超越于xxx) 4> 通配符类型 1> 无限制通配符 < ?>: Object 有些相似,用于表示无限制或者不确定范围的场景。 2> 有限制通配形式:< ? super E> 和 < ? extends E> 3> 为了获得最大限度的灵活性,要在表示 生产者或者消费者 的输入参数上使用通配符,使用的规则就是: 生产者有上限、消费者有下限 4> < ? super E> 用于灵活写入;< ? extends E> 用于灵活读取 注:泛型的参数类型还可以是通配符类型,例如 Class(参见"json字符串转对象") 5> 泛型的类型擦除 1> 当编译器对带有泛型的java代码进行编译时,会将类型替换为它的上级类型,如果是没有限定的泛型参数 类型,就会被替换为Object。所以生成的是普通的不带泛型的字节码。 2> Java中泛型在运行期是不可见的,Java 虚拟机运行时对泛型基本一无所知。 6> 泛型的使用场景 1> 当类中要操作的引用数据类型不确定的时候,过去使用 Object 来完成扩展,JDK 1.5后推荐使用泛型来 完成扩展,同时保证安全性。 7> 数组中不能使用泛型 建议使用 List 来代替 Array,因为 List 可以提供编译期的类型安全保证,而 Array 却不能。 8> Java 中 List 和原始类型 List 之间的区别? 1> 在编译时编译器不会对原始类型进行类型安全检查,却会对带参数的类型进行检查 2> 通过使用Object作为类型,可以告知编译器该方法可以接受任何类型的对象,比如String 或 Integer 3> 你可以把任何带参数的类型传递给原始类型 List,但却不能把 List< String> 传递给接受 List< Object> 的方法,因为泛型的不可变性,会产生编译错误。 9> 如何获取泛型的类型 1> 这里的Type指java.lang.reflect.Type 2> Type superclass = genericType.getClass().getGenericSuperclass(); 10> 一些常用的泛型类型变量 E:元素(Element),多用于java集合框架 K:关键字(Key) N:数字(Number) T:类型(Type) V:值(Value) n> 使用经验(jackson定义工具类) import com.fasterxml.jackson.databind.ObjectMapper; public class JsonUtil<T> { //对象转json字符串 public String obj2Json(T clazz) { ObjectMapper mapper = new ObjectMapper(); String jsonStr = null; try { jsonStr = mapper.writeValueAsString(clazz); } catch (JsonProcessingException e) { e.printStackTrace(); } return jsonStr; } //json字符串转对象 public T json2Obj(String jsonStr,Class<T> clzz) {//通配符类型,例如 Class ObjectMapper mapper = new ObjectMapper(); T jsonObj = null; try { //{"code":"1","data":"1#123456","listenPort":"8881"} jsonObj = mapper.readValue(jsonStr, clzz); } catch (IOException e) { e.printStackTrace(); } return jsonObj; } }
6、JVM
概述
来源:https://www.bilibili.com/video/BV1kJ411S7Ku?p=1 来源(本地方法栈):https://www.cnblogs.com/yanl55555/p/12624519.html 购买:https://www.javaxxz.com/forum-167-1.html JVM整体架构 JVM类加载器 JVM内存结构 JVM执行引擎
JVM内存结构(JVM内存模型)
注:元空间详解https://blog.csdn.net/yuan_qh/article/details/100176771 注:https://blog.csdn.net/m0_37510446/article/details/104121744 **方法区只是一规范,在不同的虚拟机中的实现是不一样的,例如永久代和元空间。 1> JVM组成结构 由3个主要的子系统组成 1> 类加载子系统(ClassLoader) 2> 运行时数据区(内存结构)(Runtime Data Area) 3> 执行引擎(Execution Engine) 2> 运行时数据区组成结构 1> java栈 2> 本地方法栈 3> 程序计数器 4> 方法区 5> 堆 注:方法区和堆是线程共享的,java栈、本地方法栈、程序计数器每个线程都会开辟自己独立的空间。 3> java栈组成结构 1> 线程中每个方法都会在java栈中分配一个空间,是内存单元,叫栈帧 2> java栈和数据结构中栈特性一模一样,先进后出 3> 栈帧中存放方法的数据(包括:见点"4") 4> 栈帧组成结构 1> 局部变量表 2> 操作数栈 3> 动态链接 4> 方法出口 5> 程序计数器 就是一个指针,指向用来存储指向下一条指令的地址 6> 本地方法栈 native方法,会录入本地方法栈中,而不是java栈中。也是开辟一个栈帧 7> 动态链接 null 8> 执行引擎中包含了垃圾回收器 9> 怎么从理论 -> 验证 1> javap -c Math.class > Class.txt(字节码反编译JVM指令) 2> jvisualvm 10> 垃圾回收 1> java栈、本地方法栈、程序计数器都是"线程独有"的,线程结束,自动释放空间,不需要GC管。 2> JVM调优,指的就是堆空间GC优化 11> 元空间呢 jdk1.6,有永久代,常量池在方法区 jdk1.7,有永久代,常量池在"堆"; jdk1.8,无永久代,常量池在"元空间"(独立于JVM,直接内存) 注:方法区是一种逻辑概念,元空间、永久代都是jdk对这个概念的代码实现 12> 堆空间分为:年轻代(1/3)和老年代(2/3); 年轻代分为:Eden区(8/10)、Survivor区(2/10); Survivor区分为:Survivor 0区和Survivor 1区 注:minor GC 轻GC 注:Eden就是伊甸园的意思 13> GC机制 1> 老年代都满了,就会触发full GC 2> STW(stop the work): 14> java是解释型语言 15> 编译器类型 1> JIT编译器:just in time,即时编译器 2> 字节码解释器 3> mixed mode 16> JVM运行机制 1> javac将java文件转为class字节码文件 2> java运行将调用JVM的"类装载子系统"加载到方法区 17> 如果是超大对象,直接让Eden区和Surivior区都装不下。则会直接放在老年代中。 18> 国外试验数据结构的网站?
JVM类加载器
1> 类加载过程 1> 加载 -> 链接 -> 初始化 2> 链接包括:校验 -> 准备 -> 解析 1> 加载:在硬盘上查找并通过IO读入字节码文件 2> 校验:校验字节码文件的正确性 3> 准备:给类的静态变量分配内存,并赋予默认值 4> 解析:类装载器装入类所引用的其他所有类 5> 初始化:对类的静态变量初始化为指定的值,执行静态代码块 2> 类加载器种类 1> 启动类加载器:负责加载JRE的核心类库,比如jre目录下的rt.jar,charsets.jar等 注:启动类加载器是c++写的,调用类名.getClassLoader.getClass.getName得到null 2> 扩展类加载器:负责加载jre目录下的ext目录中的jar包 3> 系统类加载器:负责加载ClassPath路径下的类包 4> 用户自定义加载器:负责加载用户自定义路径下的类包 3> 类加载机制 1> 全盘负责委托机制 2> 双亲委派机制: 3> 为什么要用"双亲委派机制"? 1> 沙箱安全机制: 2> 避免类的重复加载: 4> 查看类加载过程 1> 类启动加参数:-verbose:class 2> 如果是IDEA:Run -> Edit Configurations -> VM -> -verbose:class 5> 在线画图工具 1> draw.io 2> processon.com 6> JVM加载jar包是否会将包里所有类都加载进内存? 1> 不会 2> java命令是增加参数-verbose:class -> 如:java -verbose:class MathTest 3> JVM对class文件是按需加载(运行期间动态加载),所以如果类中有对象引用, 外部类加载 -> 连接 -> 初始化,执行到外部引用,才会开始加载内部对象类
JVM调优 - 命令
注:jdk目录下的bin目录下自带的工具 1> jinfo 注:查看JVM相关参数 1> 查看某线程:jinfo -flag 进程号 2> 查看所有java信息:jinfo -sysprops 进程 2> jstat 1> 查看堆内存使用量,加载类的数量 2> jstat -class 进程号 3> 垃圾回收统计:jstat -gc 进程号 4> 堆内存统计:jstat -gccapacity 进程号 5> 新生代垃圾回收统计:jstat -gcnew 进程号 6> 新生代内存统计:jstat -gcnewcapacity 进程号 7> 老年代垃圾回收统计:jstat -gcold 进程号 8> 老年代内存统计:jstat -gcoldcapacity 进程号 9> 元空间统计:jstat -gcmetacapacity 进程号 3> jmap 1> 用来查看内存信息 2> 实例个数以及占用内存大小:jmap -histo 进程号 > ./log.txt (可用jhat动态分析) 3> 堆总体信息:jmap -heap 进程号 4> 导出dump文件:jmp ... -> 装入jvisualvm图形界面分析,文件->装载->选地址 注:留下案发现场 -> 启动加参数 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./(路 径)。然后拿到dump文件,装载进jvisualvm图形界面进行分析。 4> jstack 1> 排查死锁:jstack 进程号(中间会有线程状态等信息,最后会打印死锁信息) 2> 远程连接jvisualvm:需要手动在项目启动时开JMX端口 1> 启动jar程序开启配置:java -Dcomxxxxx -jar foo.jar 2> tomcat的JMX端口配置:JAVA_OPTS=xxxx 注:jvisualvm远程连接服务器需要在远程服务器上配置host(连接ip主机名),并且要关闭防火墙 3> 查找占用cpu最高的堆栈信息 1> top命令,找到pid进程号 2> 按H,找到tid线程号 3> tid转十六进制 4> 执行statck tid
JVM垃圾收集
1> 如果是超大对象,直接让Eden区和Surivior区都装不下。则会直接放在老年代中。 2> java启动加参数: 1> 打印GC日志:-XX:+PrintGCDetails 2> 进入老年代阈值:-XX:MaxTenuringThreshold 3> 如何判断对象可以被回收? 1> 引用计数法:引用计数器的数量为0时(Math m1 = new Math();Math m2=m1; --> 引用计数器为2) 如果:m1 = null; m2 = null;则垃圾回收器可以对对象进行呢回收。 1> 效率高,初期jdk版本使用,目前主流都不用 2> 不能解决对象之间相互循环引用的问题 2> 可达性算法: 1> 通过"GC Roots"根节点,向下搜索。当一个对象没有和任何"GC Roots根节点"相连的话,就说明 对象不可用。 2> GC Roots根节点:类加载器、static成员、本地变量表、常量引用、本地方法栈的变量、Thread 4> finalize使用 1> 是Object类的一个方法,保证类在垃圾回收之前完成对特定资源的回收。 2> 可达性算法之后,还要经过再标记过程 1> 第一次标记:查看对象是否重写了finalize方法(是Object的方法),没有就回收 2> 有重写finalize方法,则放入一个队列中,之后会有专门线程触发该方法,但不保证方法执行完 成。如果执行finalize方法之后,对象重新有了引用,则移出队列。没有,则被回收。 5> 垃圾回收算法 1> 清除-标记算法 2> 复制算法 3> 清除-整理算法 注:年轻代可以用复制算法;老年代可以用清除-标记算法、清除-整理算法 6> 垃圾回收器 1> serial回收器:最开始串行 2> parNew回收器:最开始并行 3> parallelGC回收器:JDK1.8默认 1> 类似parNew回收器 2> 更大吞吐量,更高效利用CPU 4> CMS收集器: 1> 第一款真正意义的"并发收集器",之前的是"并行收集器" 2> 步骤:初始标记 -> 并发标记 -> 重新标记等,目的是让STW的时间尽可能短 5> G1回收器:JDK1.9默认(JDK1.7已出现) 1> G1将java堆划分为多个大小相等的独立区域(Region),虽然保留新生代和老年代的概念,但不再 是物理隔阂,它们都是不连续的区域。 2> 新增Humongous区,分配大对象,直接进Humongous区。 3.1> 可预测的停顿,是G1相对CMS最大的优势。内部建立了可预测的停顿时间模型,可指定垃圾回收 在一个时间段(毫秒)内完成。 3.2> G1收集器在后台维护了一个优先列表,每次根据允许的收集时间,有限选择回收价值最大的 Region 4> G1有"YoungGC"和"MixedGC"(MixedGC收集所有新生代和部分老年代); 如果没有了region可用了,还是会产生"FullGC" 6> ZGC回收器:JDK11默认 回收1T以上的堆空间 总结: 1> 如果老年代->CMS,则新生代->ParNew 2> 如果老年代->Parallel Old,则新生代->Parallel Scavenge 3> 如果老年代->Serial Old,则新生代->Serial 7> 如何选择垃圾回收器 1> 官方推荐: 1> 新生代:Serial、ParNew、Parallel Scavenge 2> 老年代:Serial Old、Parallel Old、CMS 注:配合使用,怎么配合,官方有图 3> 如果选用G1收集器可以同时用在新生代和老年代 2> 原则: 1> 优先调整堆的大小,让服务器自己来选择 2> 如果内存小于100M,使用串行收集器 3> 如果是单核,没有停顿时间,使用串行 4> 如果允许停顿时间超过1s,使用并行 5> 如果响应时间最重要,使用并发
JVM调优 - 实战
1> JVM调优的两个指标 1> 停顿时间:STW时间。 -XX:MaxCGPauseMills 2> 吞吐量:垃圾收集时间和总时间的占比:1/(1+n),吞吐量:1-1/(1+n)。 -XX:CGTimeRatio=n 注:-XX:+PrintGCDetails输出的PSYoungGen就是Parallel Scavenge收集器 2> JVM调优步骤 1> 打印GC日志:-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:./test.txt 参数(将GC日志输出到文件) 2> 分析日志 3> 调优JVM参数 3> 参数(使用不同GC收集器) 1> Parallel Scavenge收集器 + Parallel Old收集器: 2> ParNew收集器 + CMS收集器: 3> Serial收集器 + Serial Old收集器: 4> G1收集器:-XX:+UseG1GC 4> 分析工具 1> gceasy(国外网gceasy.io) 2> gcviewer(客户端)
7、Mysql相关
索引底层数据结构与算法
1> 磁盘存取原理 1> 寻道(速度慢、耗时) 2> 旋转(速度较快) 2> 索引使用的数据结构及原因 1> 为什么不用"二叉树" 1> 什么是二叉树(Binary Tree) 二叉树是每个节点最多有两个子节点的树。 2> 数据量大的时候,深度会很大,可能会出现不平横的情况 2> 为什么不用"红黑树" 1> 什么是红黑树(Red-Black Trees) 1> 红黑树属于平衡二叉树 2> 做了平衡,但是数据量大的时候,深度会很大(高度不可控) 3> 每个节点下最多两个子节点,所以第二行最多2个,第三行最多4个,依次类推。大数据量,很大的 3> 为什么不用"Hash" 1> 定位查找,只需要把字段做hash运算,一次运算就可以知道结果 2> 但是没有办法做范围查询 3> MySQL索引类型包括:B+Tree和Hash 4> 为什么不用"B树" 1> 度(Degree):节点的数据存储个数 2> 可以有效控制深度(度越大,深度越小) 3> 问题:那么度(Degree)是不是设置的越大越好? 4> 度大小的设置,磁盘和内存之间一次交互数据量,是以"页"为单位,"页"的整数倍,1"页"大 小"4K"。MySQL会根据计算机硬件条件,设置"度"为一次交换数据量的大小。 注:预读:磁盘一般会顺序向后读取一定长度的数据(页的整数倍)放入内存 5> 使用"B+树" 1> 非叶子节点不存储data,只存储key,可以增大度 MySQL底层已经固定确定了度(Degree)的大小,所以单个数据大小,就决定了一个节点可以 存储的数据多少 2> 叶子节点之间有指针指向(方便范围查询) 3> 数据会有冗余,因为非叶子节点不存储data, 4> B Trees节点的大小设为一页,每次新建节点直接申请一个页的空间,这样就保证一个节点 物理上也存储在一个页里,就实现了一个节点的读入只需要一次I/O 3> MyISAM索引实现(非聚集) 1> 索引文件和数据文件是分离的 2> 一张表有3个文件(.fmt、.myd、.myi即format数据结构、mydata数据,myindex索引) 3> 先到.myi文件中找到所查找数据的地址 4> 再到.myd文件中找到对应数据 5> primary key(主键索引)和secondary key(非主键索引)的存储方式一样 注:二级索引,即非主键索引 4> InnoDB索引实现(聚集) 1> 数据文件本身就是索引文件 2> 聚集索引(主键索引) - 叶节点包含了完整的数据记录 3> 二级索引,叶节点只包含了该字段和主键两个值 4> 第三条解释了,为什么InnoDB表必须要有主键 5> 二级索引,叶子节点存储主键值,是为了一致性和节省空间。(否则维护两个索引文件) 注:二级索引,即非主键索引 注:二级索引,先找到主键,再到主键索引中匹配主键,拿所有数据 (因为主键索引存所有数据,非主键索引只存主键) 5> 联合索引 每个节点中的单个数据,都是多个字段组成的,查找的时候,先比较第一个字段,如果不是,不再比较第二 第三个字段,直接往下比较。
Explain查询
1> 各字段含义 1> id列: 数字越大,越先执行。如果id值相等,则从上往下执行,越靠上优先级越高。 2> select_type列: 1> simple:简单查询。查询不包含子查询和union 2> primary:复杂查询中最外层的select 3> subquery:包含在select中的子查询 4> derived:包含在from中的子查询。(MySQL会将查询结果存放在一个临时表中,也称为派生表) 5> union:在union中的第二个和随后的select 6> union result:从union临时表检索结果的select 3> table列: 表名 4> type列(优化时常用): 1> 标识关联类型或访问类型(即MySQL执行引擎决定怎么查找,查找哪些范围) 2> 效率优先级:system > const > eq_ref >ref > range > index > ALL 1> system:表里总共就1条数据 2> const:由条件查询到结果集就1条数据 3> eq_ref:equal reference(主键/唯一关联)primary key或unique key索引的所有部分 被连接使用,最多只会返回一条符合条件的记录。 除const之外最好的连接类型。 如:表A主键左连接表B字段,A一条对应B多条 4> ref:相比eq_ref,关联字段(或where条件字段)不是唯一索引,而是普通索引。 ref因为普通索引字段,可能查出来多条字段; eq_ref因为主键索引,只会查出一条字段。 5> rang:有between、in、>等 6> index:扫描全表索引,通常比ALL要快(表字段都建了索引) 7> ALL:全表扫描。(表中有字段没有索引,通常需要增加索引进行优化) 注:一般来说,查询级别达到range就可以,最好达到ref 5> ※possible_keys: 执行引擎 分析可能会用到的索引,最终用到的索引是key列,两列可能一样可能不一样 6> ※key列(优化时常用): 最终用到的索引是key列 7> ※key_len列: 用到了几个索引(字节加起来),比如一个表联合索引的字段都是int,则int类型是4个字节。 key_len就是8 1> 字符串: 1> char(n):n字节长度 2> varchar(n):如果是utf-8,则长度3n+2。 (2个字节,是备注字符长度的,int值) (如果允许NULL,则3n+2+1。多出一个字节,是char,备注是否允许为null) 8> ※ref列: 索引关联的字段。比如用到了联合索引中的第一个索引,右关联到一张表的主键,ref列的值就是"一张 表的主键"。如果只是联合索引表自己使用,ref列的值会是联合索引值。 9> rows列: MySQL执行引擎估计会扫描的行数。 10> Extra列(优化时常用): 1> 附加信息,比较复杂 2> Using index: 覆盖索引,查询的列被索引覆盖。比如表联合索引有3个索引字段,查找的两个字段就在联合索引 中,就会直接从索引中拿值。如果是查询select *则还有其余不是索引的字段。就不会是 Using index 注:where条件中字段是索引的前导列(联合索引最左边的列),是性能高的表现 3> Using where 查询的列未被索引覆盖,where字段不是索引的前导列 4> Using where;Using index: where条件中字段不是索引的前导列,无法利用索引查找(最左前缀),但是查找数据在索引中。 因为通过配置,索引可以加载在内存中,可以不用到硬盘中拿数据。 5> NULL: where条件是索引的前导列,但是查询字段都没有被索引覆盖。(需要回表来实现) 6> Using index condition: where条件中是一个前导列的范围,查询列不完全被索引覆盖 如:where file_id > 10 7> Using temporary: 一些语句,比如select distinct,MySQL执行引擎会建一张临时表,效率较低。 如果distinct字段有索引,被索引覆盖,就不会建临时表 8> Using filesort: order by语句的字段,没有索引的情况。 如果有索引(索引是排好序的数据结构B+Tree底层已经排序了),则Using index 2> explain extended 1> 语法:select语句前加explain extended,第二行写show warnings; 2> 结果:MySQL引擎分析之后,查询优化,得到的最终执行SQL(复杂sql是没办法优化的) 3> 最左前缀原则 注:和select * from db.table_1 order by col1,col2,col3 desc一模一样。 就相当于把上述sql查询结果提前放在了B+Tree结构中。 所以where col2=,col3=或者where col1=,col3=都不会使用索引。 因为抛开col1,col2的顺序就是乱的。 注:只要包含第一个索引,且其余索引连续,比如一共3个索引,where key1=,key2,则内部也是会进行优 化的。也会使用索引。 但是如果没有包含第一个索引,或者使用了第一个索引但是中间有隔开的,都不能使用索引。 4> 最佳实践 1> 全值匹配 联合索引3个字段,则where col1=,col2=,col3= 顺序随便写,MySQL执行引擎会进行优化,顺序不影响引用的使用。 2> 最左前缀原则 详见第三点 3> 不在索引上做任何操作(会导致索引失效) 4> MySQL执行引擎不能使用索引中范围条件右边的列 比如第二个字段用了范围操作,第三个索引就用不上了。它是把第二个索引符合的都搂出来,再循环第 三个字段,已经用不到索引了。 5> 尽量使用覆盖索引 反例:select * from table_1 where col1=,col2=,col3=,则是Using index condition 因为如果索引在内存中,虽然col1,col2,col3是联合索引,顺序也对,但是找到之后,B+Tree 中存储的是索引字段+主键,还需要到主键索引表中,拿数据。 正例:Using index 6> 索引字段使用!=或者<> 联系B+Tree底层结构,没办法走索引。 也会导致全表查询 7> 索引字段使用is null或者is not null 联系B+Tree底层结构,没办法走索引。 也会导致全表查询 8> like加%通配符 1> like '%xx',不走索引 优化方法:把select * 改为select col1,col2,col3,则Using where;Using index: 2> like 'xx%',会走索引 注:联系B+Tree底层结构 9> 字符串不加''单引号,索引失效 10> 少用or,索引会失效 少用or或in,用它时,非主键字段的引用会失效,主键索引有时失效,有时失效,和数据量有关。
实战
1> 表有3个索引,where后面3个字段都有,顺序不按联合索引的字段顺序,会使用索引吗 答:会,mysql执行引擎会做优化。而且只用两个索引,只要有第一个索引,即where 1,2或者where 2,1 2> where col1=,col2>,col3=,则col2会使用索引吗,col3呢 答:col2会,col3不会。 3> where c1=,c2=,c4>,c3=,哪些字段会走索引? 答:都会走。即第一点和第二点综合起来。MySQL执行引擎会优化顺序,c3到c4前,而且范围会走索引。 4> where c1>,c2=,c3=,哪些字段会走索引? 答:都不走。很奇怪,记住结论吧。(第一个索引就用范围) 5> 是否where字段是索引,就一定会走索引 答:不是。mysql会优化,如果觉得走索引还没有全表查询快,就不走索引 =========================================================================================== 注:where中索引用以过滤,order by中索引用以排序(都是利用B+Tree已排序好的数据结构) 注:如果没有使用索引,Extra列会有Using filesort值 注:mysql有两种排序,自带的filesort排序,还有是index索引排序 6> order by 题:where c1=,c2,c4 order by c3,则哪些字段会走索引? 答:c3会,c4不会。索引都帮忙排好序,mysql肯定不会重新搞一个filesort文件去排序。 但是c4不会,因为"精确定位已经断掉了",只能是先根据c1,c2精确定位,再排序,再循环找出c4 7> order by 题:where c1=,c2= order by c4,则哪些字段会走索引? 答:c4不会,因为最左前缀原理 8> order by 题:where c1=,c4= order by c2,c3,则哪些字段会走索引? 答:会 9> order by 题:where c1=,c4= order by c3,c2,则哪些字段会走索引? 答:c2和c3不会,因为最左前缀原理 10> order by 题:where c1=,c2= order by c2,c3,则哪些字段会走索引? 答:会 11> order by 题:where c1=,c2=,c5= order by c2,c3,则哪些字段会走索引? 答:会 12> order by 题:where c1=,c2=,c5= order by c3,c2,则哪些字段会走索引? 答:会。因为c2已经是固定值了,mysql经过了优化估算。 13> order by 题:order by c1 asc,c2 desc,则哪些字段会走索引? 答:不会。因为B+Tree是联合起来做了升序,一升一降已经无法满足了 =========================================================================================== 注:group by底层是先对group by后字段进行order by 注:如果没有使用索引,Extra列会有Using temporary(当然也会有Using filesort)值 注:where的性能高于having,能在where中限定条件就不要去having中限定 13> group by 题:where c1=, c4= group by c2,c3,则哪些字段会走索引? 答:会。 14> group by 题:where c1=, c4= group by c3,c2,则哪些字段会走索引? 答:不会。因为底层会先排序。会产生Using filesort,Using temporary,极度恶劣 15> group by 题:where c1> group by c1,则哪些字段会走索引? 答:不会。第一个字段要避免范围查询。 16> group by 题:where c1 in(,) group by c2,c3,则哪些字段会走索引? 答:不会 =========================================================================================== 17> in和exists区别 1> in里面有数量限制 之前在oracle测试过,超过500好像,版本忘了,一条数据都查不出来。 用exists代替解决了 2> 规则就是"小表驱动大表" 1> select * from table where col in(),先执行in里面子查询,需要子查询是小表,外面大表 2> select * from table where exists (select 1),先执行外面,需要小表,exists里是大表
锁
1> 锁分类 1> 性能分:乐观锁和悲观锁 2> 对数据库操作:读锁和写锁 3> 对数据操作粒度:表锁和行锁 2> 读锁(共享锁)、写锁(排他锁) 1> 读锁,会阻塞写,不会阻塞读 注:共享锁 - 用来数据迁移,锁起来,都不能写,都可以读。 只能读,不能写(包括自己和其他session都不能写,自己报错,其他阻塞) 2> 写锁,会把读和写都阻塞 注:排他锁 - 用来增删改查前,锁起来; 锁起来,只能自己读+写(其他session读、写都不能做) 3> 表锁(偏读 - 打开表,尽量批量读) 注:MyISAM引擎偏向于表锁 1> 手动给表加锁(读锁/写锁) lock table xxx read lock table xxx write 2> 查看加过锁的表 show open tables 3> 删除表锁 unlock tables 注:读锁常用在数据迁移,给表加上锁,可以读,但是自己和其他session都不能写。 注:MyISAM在执行SELECT语句时,会给涉及到的表加读锁;执行增删改查前,会给涉及的表加写锁。 4> 行锁(偏写 - 逐行写,逐行关) 注:InnoDB引擎偏向于行锁 注:行锁支持事务 1> 开启事务 begin; 2> 关闭事务 commit; 3> 回滚事务 rollback; 注:一个session开启事务,update一行数据,没有commit之前,会给该行数据加行锁。 其他session不能update这行数据,阻塞。但是不影响update其他行数据。 5> 事务(事务特性+并发中事务出现问题) 1> 事务特性: ACID,即原子性、一致性、隔离性、持久性 2> 事务让并发变为可能(但是仍会出现问题) 脏读、幻读、不可重读、更新丢失 注:脏读,为什么一个session没有提交的数据,另一个session会读到。 因为有"隔离级别"的不同。 1> 脏读:一个事务读取了另一个事务还没有提交的数据 2> 不可重复读:一个事务读取了另一个事务已经提交的修改数据 3> 幻读:一个事务读取了另一个事务提交的新增数据 6> 事务隔离级别(mysql如何机制解决并发事务问题) 注:事务隔离级别底层可以帮我们解决脏读、幻读、不可重复读的问题(都是读一致性问题) 1> 不同的事务隔离级别 隔离级别 脏读 不可重复读 幻读 读未提交 可能 可能 可能 读已提交 不可能 可能 可能 可重复读 不可能 不可能 可能 可串行化 不可能 不可能 不可能 2> 命令 1> 查看当前事务隔离级别:select @@tx_isolation from dual;(tx-transaction) 2> 设置新的事务隔离级别:set tx_isolation='read-uncommitted'(比如设置读未提交) 3> 可重复读(MySQL默认隔离级别) 1> 现象:字段原来是450,两个事务同时开启,A事务update字段为400,B事务读字段还是450; 但是B事务update col=col-50,结果却变为350。 注:可以这么记 - 可重复读,强调的是"读"是没问题的,没有说可以用来"写"。 2> 解释:可重复读隔离级别下使用了MVCC(multi-version concurrent control)机制, select操作不会更新版本号,是读快照(历史版本);insert/update/delete会更新版本号, 是当前读(当前版本) 3> 所以更新"库存"sql应写成update table set balance=balance-10。而不能先在代码中计算出 减法之后,再直接update减后的数字。 4> 现象:一共4条数据,事务A插入一条数据,事务B去select,结果还是4条。(因为读的是快照) 但是事务B去update事务A新插入的数据,是可以成功的。(因为MVCC机制,和幻读没有解决) 4> 可串行化(Serializable) 已经没有并发了,一个事务完了,在开始另一个事务。这压根没有效率 5> 解决幻读(非代码级别,用MySQL解决) 间隙锁:通过范围去加锁,即使记录不存在,也会预先加上锁。 共5要记录,事务A更新id=5:update table set col = where id>4 and id<=20 事务B执行insert语句:insert into values (6,'',''),会等待阻塞,超时之后报错。 7> 死锁 session 1:begin; 开启事务 session 2:begin; 开启事务 session 1:select * from table where id=1 for update; 第一行加锁 session 2:select * from table where id=2 for update; 第二行加锁 session 1:select * from table where id=2 for update; 第二行加锁(阻塞,等待s2放锁) session 2:select * from table where id=1 for update; 第一行加锁(阻塞,等待s1放锁) 结果:第四行命令执行完,立马报错 Deadlock found when trying to get lock; try restarting transaction 解释:两个事务相互等待时,会发生死锁 命令:杀掉死锁 v$locked_object v$session
8、Spring相关
预备
1> 备注: 1> SqlSessionFactoryBean 2> 注入方式:https://www.cnblogs.com/icehand1214/p/9708174.html 3> idea创建spring项目:https://www.cnblogs.com/chenyulin/p/creatSpringproject.html 2> 快捷键: 1> IDEA生成图谱: ctrl + alt + u,详细图谱 ctrl + alt + shift + u 来源:https://www.cnblogs.com/deng-cc/p/6927447.html 2> IDEA在debug时evaluate: alt + f8 3> 查找接口的实现类: ctrl+h 4> 查看类中所有方法 ctrl + f12 注:过滤结果搜索,不需要ctrl+f等,直接按键盘即可。 5> 全局搜索 edit -> find -> find in path ctrl + shift +f 6> 条件断点
IoC容器设计理念
前提: "IoC中如果没有依赖注入",那这个框架就只能帮我们构建一些简单的Bean,而之前所说的复杂Bean 的构建问题将无法解决,Spring框架就不可能像现在这样成功。 1> 没有Spring的时候,怎么创建"依赖对象" 1> 外部new出来,传入调用对象 1> 构造方法传入 2> 属性set方法传入 3> 调用方法时,动态传入 2> 内部自己new出来 1> 属性赋初始值直接new 2> 初始化方法中直接new 3> 其他业务方法中直接new 比如:外部new =================== Test t = null; public Test2 (Test t) { this.t = t; } =================== Test t = new Test(); Test2 t2 = new Test2(t); 2> 为什么使用Spring 注:如果是一个简单的对象,用不用Spring没有太大区别。 传统方法 Goods water = new Water(); Train train = new Train(water); Spring方法 context.getBean("train"); 1> 来源: https://zhuanlan.zhihu.com/p/70147855 基于POJO的轻量级和最小侵入性编程; 通过依赖注入和面向接口实现松耦合; 基于切面和惯例进行声明式编程; 通过切面和模板减少样板式代码。 2> 解释: 1>================================================================================ 没有Spring的时候,每个对象负责管理着依赖的对象引用 注:因为Water水和coal煤都可以实现Goods物品接口,都实现doSomthing方法 public class Train implements Transport{ private Goods goods; public Train() { goods = new Water(); } public void catchGoods(){ goods.doSomthing(); //水的话,是用来浇灌庄稼 } } 注:上面创建"依赖对象"方法是 - "内部自己new出来"。这样就造成这两个对象的紧耦合,这个火车 可以运水来浇灌农田,但是如果让这个火车来运煤供暖,可能就不太符合了。 因为Water水和coal煤都可以实现Goods物品接口,如果想运送煤炭,就需要修改代码。 2>================================================================================ 所以,为了解决紧耦合,可以采用创建"依赖对象"另一种方法,"外部new出来,传入调用对象" public class Train implements Transport{ private Goods goods; public Train(Goods goods) { this.goods = goods; } public void catchGoods(){ goods.doSomthing(); } } 注:外部创建对象 Goods coal = new Coal(); 外部对象传入 Train train = new Train(coal); 或者 外部创建对象 Goods water = new Water(); 外部对象传入 Train train = new Train(water); 实现了"松耦合" 3>================================================================================ 但是,虽然实现了松耦合,还有问题。(利用"外部new出来,传入调用对象") 如果一个对象具有"复杂的依赖关系",在调用对象的时候,就需要在代码中写繁琐的创建对象代码。 比如A依赖B,B依赖C,C依赖D 创建A对象: D d = new D(); C c = new C(d); B b = new B(c); A a = new A(a); 注:需要代码来维护"复杂的依赖关系" 所以,可以使用Spring的"依赖注入",让Spring来管理复杂的依赖关系,调用时从上下文对象中拿出 即可。 3> 实体Bean的构建 1> 基于Class构建(无参构造器) 2> 构造方法构建(有参构造器) 3> 静态工厂方法构建(工厂方法创建对象) 4> FactoryBean构建(实现FactoryBean方法,不是有构造器/工厂类创建对象的情况) =========================================================================== 1> 基于Class构建: <bean class="com.guozi.HelloWorld"></bean> 注:底层基于class属性,反射进行构建(无参构造器) 2> 构造方法构建: <bean id="user1" class="com.boss.domain.User1"> <constructor-arg name="username" value="李四" /> <constructor-arg index="1" value="25" /> </bean> 3> 静态工厂方法构建 <bean class="com.boss.domain.User1" factory-method="build"> <constructor-arg name="username" value="李四" /> </bean> 4> FactoryBean构建 注:bean配置和普通bean一样 class类实现FactoryBean接口 3> 依赖注入 1> 概述 试想IoC中如果没有依赖注入,那这个框架就只能帮我们构建一些简单的Bean,而之前所说的复杂Bean 的构建问题将无法解决,Spring框架就不可能像现在这样成功。 2> 依赖注入的方式 1> set方法注入 2> 构造方法注入 3> 自动注入(byName,byType) =========================================================================== 1> "通过set方法"注入 <bean id="userDao" class="com.boss.dao.imp.UserDaoimpl" /> <bean id="userService" class="com.boss.service.imp.UserServiceImpl"> <property name="userDaoImpl" ref="userDao" /> </bean> 注:底层基于class属性,反射进行构建(有参构造器) 2> "通过构造器方法"注入 <bean id="user1" class="com.boss.domain.User1"> <constructor-arg name="userDao" ref="userDao" /> </bean> 3> 自动注入 <!--自动注入 byType--> <bean class="com.guozi.HelloIoc"/> <bean id="helloSpring" class="com.guozi.HelloSpring" autowire="byType"/> public class HelloSpring { private HelloIoc helloIoc; } <!--自动注入 byName--> <bean id="helloIoc" class="com.guozi.HelloIoc"/> <bean id="helloSpring" class="com.guozi.HelloSpring" autowire="byName"/> public class HelloSpring { private HelloIoc helloIoc; } 注:byName是根据class中属性名,和xml中id和name想匹配知道,并自动注入的。 注:还有一种是default,需要<beans>标签加属性default-autowired 注: 1> set注入,需要property标签,类中需要set/get方法 2> 构造器注入,需要constructor-arg标签,类中需带参要构造器 3> 自动注入,xml和class都不需要配置。Spring自动判别 4> bean的基本特性 1> 作用范围 1> 类型有:prototype(getBean获取到的是多例)、singleton(getBean获取到的是单例) 2> singleton的话,会缓存在IoC的容器中 3> "bean标签中的属性":<bean class="com.HelloSpring" scope="singleton"></bean> 注: @Controller的Bean默认是单例的,所以避免加属性成员变量 2> 生命周期 1> Bean对象的创建、初始化、销毁就是Bean的生命周期。 2> "bean标签中的属性":init-method、destroy-method指定初始化和销毁时调用的方法 3> 加载机制 1> 懒加载:使用的时候,才创建Bean 2> 非懒加载:容器启动时即创建对象 3> 懒加载容器启动更快,非懒加载可以在容器启动时更快发现程序中的错误。 4> "bean标签中的属性":<bean class="com.HelloSpring" lazy-init="false"></bean> 注: 当scope="prototype" (多例)时,默认以懒加载的方式产生对象。 当scope="singleton" (单例)时,默认以非懒加载的方式产生对象。
源码学习-IoC
注:学习目标 1> Bean工厂如何生产Bean 2> Bean依赖关系由谁来解决 3> Bean工厂和应用上下文的区别 1> Bean工厂如何生产Bean 注:目标 1> xml配置的bean转变为哪个Class对象 2> 把bean标签变为Class对象,是谁来完成的 3> 这些Class对象都保存在了哪儿 1> xml配置的bean转变为哪个Class对象 1> BeanDefinition(Bean定义) 1> 概述: xml的Bean信息最后都将保存在BeanDefinition对象中, 每个Bean创建一个BeanDefinition对象。 2> xml和BeanDefinition属性对应关系 XML-Bean BeanDefinition class beanClassName scope scope lazy-init lazyInit constructor-arg ConstructorArgument property MutablePropertyValues factory-method factoryMethodName 3> id属性作为Bean的key注册到BeanDefinitionRegistry注册器中。 name属性作为别名key注册到AliasRegistry注册中心。 最后都是指向其对应的BeanDefinition。 2> BeanDefinitionRegistry(Bean注册器) 1> "注册进去" id放在了BeanDefinitionRegistry接口中,如下方法: void registerBeanDefinition( String beanName, BeanDefinition beanDefinition) 第一个参数,就是id值 第二个参数,就是Bean的承载对象 2> "取出来" 所以,一个id只有一个Bean,一个Bean可以有多个id还是注册类中,我们可以通过id获取 到BeanDefinition对象: BeanDefinition getBeanDefinition(String beanName) 注:name属性值放在了接口AliasRegistry中,即注册接口继承的接口 3> BeanDefinitionReader(Bean读取、创建、注册总操控) 代码模拟Spring构建Bean public static void main(String[] args) { //注册中心 BeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry(); //读取器 XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(registry); //资源加载器 DefaultResourceLoader loader = new DefaultResourceLoader(); Resource resource = loader.getResource("spring-cfg.xml"); //装在构建Bean reader.loadBeanDefinitions(resource); //所有构建出来的Bean String[] allBeanDefinitions = registry.getBeanDefinitionNames(); System.out.println(Arrays.toString(allBeanDefinitions)); //注册器中通过拿name别名 registry.getAliases("helloWorld"); //注册器中通过id拿Bean registry.getBeanDefinition("helloWorld"); } 注:BeanDefinition对象只能通过id来获取 2> 把bean标签变为Class对象,是谁来完成的 概述:有了bean定义就相当于有了配方,现在缺工厂来进行加工生产出Bean 2> Bean依赖关系由谁来解决
Spring事务
1> Spring事务特点 1> 在数据库事务基础上进行封装扩展 2> 加入事务传播的概念 3> 提供声明式事务,让业务代码和事务分离 2> Spring提供API实现事务控制 1> TransactionDefinition(事务定义) 2> PlatformTransactionManager(事务管理) 3> TransactionStatus(事务运行时状态) 4> 示例代码(Spring提供事务使用接口): 注:spring提供了@transactionTemplate 方法级别的事务注解,使用方便 但是 方法级别的事务,粒度不够细。 因此 可以使用spring提供的TransactionTemplate 工具类来控制细粒度事务。 public static void main(String[] args) { //创建TransactionTemplate final DataSource ds = new DriverManagerDataSource(url, username, pwd); TransactionTemplate template = new TransactionTemplate(); template.setTransactionManager(new DataSourceTransactionManager(ds)); //使用TransactionTemplate template.execute(new TransactionCallback<Object>() { public Object doInTransaction(TransactionStatus status) { Connection conn = DataSourceUtils.getConnection(ds); try { PreparedStatement ps = conn.prepareStatement( "insert into dushu values (?,?,?,?)"); ps.setInt(1,1); ps.setString(2,"淳熙严州图经"); ps.setString(3,"宋·陈公亮"); ps.setString(4,"暂缺简介..."); ps.executeUpdate(); int i = 1/0; } catch (Exception e) { status.setRollbackOnly(); System.out.println("回滚操作:"+e.getMessage()); } return null; } }); } 注:通过Connection conn = DataSourceUtils.getConnection(ds);后去链接是同一个 3> 声明式事务 1> 概述: 第三点通过调用API来实现对事务的控制,非常繁琐,与直接操作JDBC事务没有多大改善,所以Spring 提出了声明式事务。让业务代码和事务分离,不需要程序猿关心事务 2> 示例: 配置文件中,配置bean - DataSourceTransactionManager,这个类实现了 PlatformTransactionManager接口,所以有事务提交和回滚的方法。 最重要:添加事务的注解驱动 <tx:annotation-driven transaction-manager="txManager"></tx:annotation-driven> 注:txManager就是DataSourceTransactionManager这个Bean 4> 事务传播机制 注:这些定义,都是占在内层方法,即被调用方法的出发点来定义的。 比如required,就是内层方法发现外层方法已有事务,则加入该事务,如果发现没有,就只给自己创 建一个新事务:Creating new transaction with name(内层事务创建) 1> 类别和特性 类别 事务传播类型 说明 支持当前事务 required(必须的) 先探测事务,若已有,则加入,若没有,为自己新建一个 supportes(支持) 先探测事务,若已有,则加入,若没有,以非事务执行 mandatory(强制) 先探测事务,若没有,否则报错 不支持当前事务 requires_new(隔离) 无论如何,先新建事务,若已有,则挂起,完成后resume not_supported(不支持)无论如何,非事务执行,若已有,则挂起,完成后resume never(强制非事务) 无论如何,非事务执行,若已有,则挂起,就抛异常 套事务 nested(嵌套事务) 若已有,则在嵌套事务内执行 2> 测试示例 不要看视频的,看如下博客:https://blog.csdn.net/qq_26323323/article/details/81908955 自己做示例,外层required,内层not_supported 1> Creating new transaction with name(外层事务创建) 2> Executing prepared SQL statement [insert into dushu (执行外层操作) 3> Suspending current transaction (内层挂起外层事务) 4> Executing prepared SQL statement [insert into dushu (执行内层操作) 5> Resuming suspended transaction after completion of inner transaction(还原事务) 注:所以,如果内层报错(在update之后报错),内层插入数据库成功,外层失败回滚。 3> 引出声明式事务的底层原理 示例:如果外层required,内层required_new,外层报错。 本来结果应该是,内层成功,外层回滚。 但如果内外层方法,都放在一个类中,外层调用内层,结果却是:都失败 引出:声明式事务底层原理是,通过aop动态代理,内部相当于this.add()不会动态代理
SpringMVC
1> 组件初始化 DispatcherServlet类中的initStrategies方法中 2> HandlerMapping 1> 目前主流的三种mapping 1> BeanNameUrlHandlerMapping 2> SimpleUrlHandlerMapping 3> RequestMappingHandlerMapping 2> 在IoC中实例化这些类之后,DispatherServlet就会通过getHandler方法查找对应Handler,但是返回 的是一个Object类型,还需要适配器adaptor适配这个Object调用哪种handler。 3> HandlerAdaptor 1> spring mvc通过适配器模式来适配调用指定的Handler 2> Handler和HandlerAdaptor对应关系如下: Handler类型 对应适配器 描述 Controller SimpleControllerHandlerAdaptor 标准控制器,返回ModelAndView HttpRequestHandler HttpRequestHandlerAdaptor 业务自行处理,不需返回ModelAndV Servlet SimpleServletHandlerAdaptor 基于标准的Servlet处理 HandlerMethod RequestMappingHandlerAdaptor 基于@RequestMapping对应方法 注: 源码较简单,后面一定要看一下
SpringAOP
1> 概述 aop是要实现的目的,SpringAOP是它的一种实现。 2> 应用场景 1> 日志记录 2> 权限验证 3> 效率检查 4> 事务管理 3> SpringAOP底层原理 1> JDK动态代理 2> CGLIB代理 4> SpringAOP和AspectJ关系(使用) 注:SpringAOP提供两种,基于xml和基于注解 1> 加入jar包aspectjweaver 2> 打开aspectj自动代理(xml配置或者写java配置类:@Configuration和@EnableAspectJAutoProxy) 3> 定义Aspect(@Aspect切面,必须交给IoC容器管理) 4> 定义PointCut(@PointCut切点,springel有好多种写法) 5> 定义Advice(@Before/@After通知) 5> 源码分析 1> 一个类,没有实现接口,则IoC容器中保存的是类名.class, UserServcieImpl userServcieImpl = context.getBean(UserServcieImpl.class); userServcieImpl.buyService(); 注:UserServcieImpl是类,发现userService类型CglibAopProxy 2> 一个类,实现了接口,则IoC容器中保存的是接口名.class, UserServcie userServcie = context.getBean(UserServce.class); userServcie.buyService(); 注:UserServce是接口,发现userService类型JdkDynamicAopProxy@2662 注:因为Jdk动态代理,底层已经继承了Proxy类,所以只能用过实现接口来完成代理。 3> 关键类 1> 单例对象转变为"代理类对象" AbstractBeanFactory.java:245 Object sharedInstance = getSingleton(beanName); 2> 多例对象转变为"代理类对象" AbstractBeanFactory.java:330 prototypeInstance = createBean(beanName, mbd, args);
Spring5新特性
9、MyBatis相关
10、StringBuilder
https://www.cnblogs.com/shamao/p/10942203.html 1> 可变与不可变 String是不可变字符串对象,StringBuilder和StringBuffer是可变字符串对象(其内部的字符数组长度 可变)。 2> 是否多线程安全 String中的对象是不可变的,也就可以理解为常量,显然线程安全。StringBuffer与StringBuilder中的 方法和功能完全是等价的,只是StringBuffer中的方法大都采用了synchronized关键字进行修饰,因此是 线程安全的,而StringBuilder没有这个修饰,可以被认为是非线程安全的。 3> 执行效率 StringBuilder > StringBuffer > String 当然这个是相对的,不一定在所有情况下都是这样。比如 String str = "hello" + "world"; 的效率就 比 StringBuilder builder = new StringBuilder().append("hello").append("world"); 要高。 4> 因此,这三个类是各有利弊,应当根据不同的情况来进行选择使用: 当字符串相加操作或者改动较少的情况下,建议使用String这种形式; 当字符串相加操作较多的情况下,建议使用StringBuilder,如果采用了多线程,则使用StringBuffer。 注: 1> String类其实是通过char数组来保存字符串的。所有对String类的操作都不是在原有的字符串上进行 的,而是重新生成了一个新的字符串对象 2> 每当我们创建字符串常量时,JVM会首先检查字符串常量池。如果该字符串已经存在常量池中,那么就直 接返回常量池中的实例引用。如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池 中,同时返回对该实例的引用
11、数据结构
1> 大纲: https://blog.csdn.net/yeyazhishang/article/details/82353846#2%E6%A0%88 1、数组 数组的大小固定后就无法扩容了 2、栈 先进后出(入栈、出栈) 3、队列 先进先出(入队、出队) 4、链表 数据域 + 指针域 5、树 在日常的应用中,我们讨论和用的更多的是树的其中一种结构,就是二叉树 6、散列表 散列表,也叫哈希表 7、堆 可以被看做一棵树的数组对象 因为堆有序的特点,一般用来做数组中的排序,称为堆排序。 8、图 图是一种比较复杂的数据结构,在存储数据上有着比较复杂和高效的算法 1> 红黑树特性 https://zhuanlan.zhihu.com/p/31805309 1> 二叉查找树的3个特性 2> 红黑树除了具有二叉查找树的特性,还具有的3个特性(节点是红色/黑色、根节点是黑色不算) 3> 增加节点导致打破平衡,调整的2种方法
12、注解
java实现自定义注解 来源:https://blog.csdn.net/zt15732625878/article/details/100061528
13、内存机制
注:详见"JVM",下面内容不需看 https://www.jianshu.com/p/8624e27ae45d https://www.cnblogs.com/shamao/p/10938028.html
14、ClassLoader类装载器
注:详见"JVM",下面内容不需看 来源:https://segmentfault.com/a/1190000008491597 大牛:https://blog.csdn.net/hupoling/article/details/90938342 面试:写一个自定义的类加载器 + 哪些情况需要自定义类加载器 ------------------------------------------------------------------------------------------- 1> 为什么要自定义ClassLoader 因为系统的ClassLoader只会加载指定目录下的class文件,如果你想加载自己的class文件,那么就可以自定 义一个ClassLoader 2> 如何自定义ClassLoader
15、native本地方法
1> 因为你会c语言,肯定会问你native方法 java语言和c语言交互的方法 2> JNI是Java Native Interface的缩写,中文为JAVA本地调用 3> DLL的全称是Dynamic Link Library,中文叫做“动态链接文件” 4> JNIEXPORT和JNICALL宏定义: https://blog.csdn.net/shulianghan/article/details/104072587 window平台: 方法返回值前加:JNIEXPORT ,方法名前加:JNICALL 4> java调用c接口: https://blog.csdn.net/WeiHao0240/article/details/99568579 注:window和linux具体实现不太一样 注: "注意":写一个Java类是为了生成.h和.c的格式 其中,java的类名,包名,方法名,都必须和项目中java类保持一致 1> 如何把c文件编译成dll gcc -shared -o demo.dll demo.c 2> java调用c(单独java文件) https://blog.csdn.net/u011181989/article/details/92242435 生成so文件不需要,直接用第一点,生成dll文件 还需要注意一点,找jni文件,https://blog.csdn.net/WeiHao0240/article/details/99568579 还需要注意一点,java -d 和 javah 的命令都写错了,文件名都应该是HelloMyJni.java 还需要注意一点,javah生成的.h文件,include <jni.h>应该改为"jni.h" 还需要注意一点,接上面一点,把java中jni.h和jni_md.h都和java文件放一起。所以"", <>是系统文件的意思 还需要注意一点,如果只是简单调用,生成的dll文件和java文件放一起就可以了。 若果是系统中随处可调用,就需要放在 window:java安装目录下的bin目录 1> 报错: 如果文件第一行是package xx,指定了包名则: 编译:需要指定路径加-d参数,值为当前即点 --> javac -d . HelloMyJni.java 运行:需要包名.类名 --> java com.guozi.test.HelloMyJni 头文件:需要包名.类名 --> javah -jni com.guozi.test.HelloMyJni 3> java调用c(项目) 前提: 比如我的项目中,工具类HelloMyJni.java放在目录com.guozi.test下。 外部写单个java类测试: 1> java类必须加package com.guozi.test System.loadLibrary("dll名称"); 2> 编译:javac -d . HelloMyJni.java 3> 生成头文件:javah -jni com.guozi.test.HelloMyJni 头文件:com_guozi_test_HelloMyJni.h 方法名:Java_com_guozi_test_HelloMyJni_helloWorld 可知:如果模拟项目应用java调c,java类必须加package,因生成文件名和方法名不一样 注:将.h文件中include <jni.h>改为include "jni.h" 4> 写c文件: 1> #include "jni.h" 2> #include "com_guozi_test_HelloMyJni.h" 3> JNIEXPORT void JNICALL Java_com_guozi_test_HelloMyJni_helloWorld(JNIEnv * env, jobject obj) 4> 生成dll文件:gcc -shared -o HelloWorld.dll HelloWorld.c 5> dll文件位置:放在java目录bin下(其实随便path扫到的目录下即可) 好像会先找java.class.path,如果找不到,再找系统的路径。 项目中应用: 定义工具类,写入HelloMyJni内容,向外提供调用方法。完成! 注:HelloMyJni的所在目录必须是和"前提"中说的一样 --> com.guozi.test
16、java/c/python区别
1> C语言是一门面向过程设计语言; 2> Java是一门面向对象编程语言,而Java语言是从C语言衍生而来,它吸收了C++语言的各种优点,并且摒弃了 C++里难以理解的多继承、指针等概念。 3> python和java的区别: Java是一种静态类型语言,Python是一种动态类型语言 Java中的所有变量需要先声明(类型)才能使用,Python中的变量不需要声明类型 (Java编译以后才能运行,Python直接就可以运行) 注: 1> 面向对象编程和面向过程编程的区别 https://www.cnblogs.com/qianxiaoruofeng/p/11561188.html 2> 静态语言和动态语言的区别 https://www.cnblogs.com/raind/p/8551791.html 像Java,c,c++这种就属于静态语言。静态语言(强类型语言) 像JavaScript,Python这种就属于动态语言。动态语言(弱类型语言) 注:java是动态语言还是静态语言,见第二个"注" 3> 解释型语言和编译性语言的区别 见:https://www.zhihu.com/question/19608553/answer/27896401 "温悦"用户回答 注: 1> c语言经过编译之后变成的是是"机器码",二进制语言,机器可识别的 2> java是解释型语言还是编译性语言 不好严格区分 一般认为是"解释型语言" JIT技术将运行频率很高的字节码直接编译为机器指令执行以提高性能 java的编译结果是被jvm“解释执行”的, https://blog.csdn.net/weixin_34114823/article/details/88675337 3> java是动态语言还是静态语言 不好严格区分 一般认为是"静态语言" 但是(interface,reflection和dynamic class loading)运行时才能确定的特性 注: 1> python效率低的原因 https://blog.csdn.net/lmseo5hy/article/details/81875518 执行时:Python解释器将源代码转换为字节码,然后再由Python解释器来执行这些字节码。 其每次运行都要进行转换成字节码,然后再有虚拟机把字节码转换成机器语言,最后才能在硬件 上运行。 并不是和java一样,先显示编译为字节码,存在磁盘或内存,可以复用。 2> AOT和JIT JIT:后端编译/即时(JIT)编译 AOT:静态提前编译(Ahead Of Time,AOT编译) 目前来看,流行框架太依赖反射和各种奇门优化,这东西应用于后台开发还不太现实 AOT牺牲了跨平台性,直接由源码搞到了机器码https://www.zhihu.com/question/54842396
17、创建对象方式
1> 概述 我们还可以使用反射机制(Class类的newInstance方法、使用Constructor类的newInstance方法)、使用 Clone方法、使用反序列化等方式创建对象 2> 地址: https://blog.csdn.net/justloveyou_/article/details/72466416
18、看过哪些源码
1> HashMap的部分源码:put和get方法 2> Spring IoC的部分源码 3> Spring AOP的部分源码 4> Spring MVC的部分源码 5> Ribbon & Feign
19、线程池+多线程
20、zookeeper相关
21、类加载顺序
来源1:https://blog.csdn.net/yy339452689/article/details/104014732?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.edu_weight&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.edu_weight 来源2:https://blog.csdn.net/u014745069/article/details/82655339 父类的静态字段——>父类静态代码块——>子类静态字段——>子类静态代码块——> 父类成员变量(非静态字段)——>父类非静态代码块——>父类构造器——>子类成员变量——>子类非静态代码块——>子类构造器
22、Dubbo源码
23、CGLIB和Java动态代理
1> Java动态代理只能够对接口进行代理,不能对普通的类进行代理(因为所有生成的代理类的父类为Proxy, Java类继承机制不允许多重继承); Java动态代理使用Java原生的反射API进行操作,在生成类上比较高效; Java反射机制 2> CGLIB能够代理普通类; CGLIB使用ASM框架直接对字节码进行操作,在类的执行过程中比较高效 它允许我们在运行时对字节码进行修改和动态生成 注: 1> ASM详解:https://www.cnblogs.com/zt007/p/6377789.html 2> Proxy详解:https://www.cnblogs.com/incognitor/p/9759987.html 1> java静态代理: 设计模式中的代理模式 静态代理的缺点是冗余,因为一个代理类只能代理一个接口 2> java动态代理: 主要方法:Proxy.newProxyInstance 接口方法调用实质是InvocationHandler.invoke()方法 注:两种对比 https://www.cnblogs.com/CarpenterLee/p/8241042.html
24、编码格式相关
搞清楚了ASCII、Unicode和UTF-8的关系 ASCII编码是1个字节,而Unicode编码通常是2个字节(如果要用到非常偏僻的字符,就需要4个字节) 注* 存储和传输 上就十分不划算 本着节约的精神,又出现了把Unicode编码转化为“可变长编码”的UTF-8编码 常用的英文字母被编码成1个字节,汉字通常是3个字节,只有很生僻的字符才会被编码成4-6个字节 在计算机内存中,统一使用Unicode编码,当需要保存到硬盘或者需要传输的时候,转换为UTF-8编 码。
25、通信协议相关
26、多线程
1> 线程排查死锁的方法 1> jsp找到对应线程号 2> jstack 线程号 2> 线程的所有状态 查看图片:H:\desktop\面试相关\面试\2019年面试\7、thread 来源:https://blog.csdn.net/dupengshixu/article/details/83689719 1> yield Thread.yield()方法作用是:暂停当前正在执行的线程对象,并执行其他线程。 即:主动放弃自己线程得到的cpu的"cpu时间片时间片" yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态转 到可运行状态,但有可能没有效果。 2> wait和notify 等待池:假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁后,进入到了该对 象的等待池,等待池中的线程不会去竞争该对象的锁。 锁池:只有获取了对象的锁,线程才能执行对象的 synchronized 代码,对象的锁每次只有一个线程 可以获得,其他线程只能在锁池中等待(唤醒之后,b、c争夺锁) 区别: notify() 方法随机唤醒对象的等待池中的一个线程,"进入锁池"; notifyAll() 唤醒对象的等待池中的所有线程,进入锁池。 来源:https://blog.csdn.net/meism5/article/details/90238268 3> join thread.Join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。 比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。 注:当前线程,即"main线程",main线程调用了a线程的join方法,直到线程a执行完毕后,才会继续 执行线程main。即b/c线程的start方法。 3> 线程池的所有状态 RUNNING :能接受新提交的任务,并且也能处理阻塞队列中的任务; SHUTDOWN:关闭状态,不再接受新提交的任务,但却可以继续处理阻塞队列中已保存的任务。在线程池处于 RUNNING 状态时,调用 shutdown()方法会使线程池进入到该状态。(finalize() 方法在执行过程 中也会调用shutdown()方法进入该状态); STOP:不能接受新任务,也不处理队列中的任务,会中断正在处理任务的线程。在线程池处于 RUNNING 或 SHUTDOWN 状态时,调用 shutdownNow() 方法会使线程池进入到该状态; TIDYING:如果所有的任务都已终止了,workerCount (有效线程数) 为0,线程池进入该状态后会调用 terminated() 方法进入TERMINATED 状态。 TERMINATED:在terminated() 方法执行完后进入该状态,默认terminated()方法中什么也没有做。 4> 线程实现方式 1> 实现Runnable接口 2> 继承Thread类 3> 实现Callable接口,实现call方法(java1.5之后提供的方式) 第1/2种方式,需重写/实现run方法,该方法没有返回值 Callable接口有泛型 call方法有返回值,值类型是Callable接口标识的泛型 <源码有点复杂,FuturTask是实现接口实现类,源码复杂,暂时不看> 5> java中有几种线程池 来源:https://baijiahao.baidu.com/s?id=1652900816694167505&wfr=spider&for=pc 1> 阻塞队列 ArrayBlockingQueue:有界队列(基于数组的) LinkedBlockingQueue:有/无界队列(基于链表的,传参就是有界,不传就是无界) SynchronousQueue:同步移交队列(需要一个线程调用put方法插入值, 另一个线程调用take方法删除值) 2> handler:拒绝处理策略 ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常 注:抛异常 ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常 注:扔掉新的 ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务 注:扔掉旧的(重复此过程) ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务 3> 常用的线程池 1> newCachedThreadPool() 核心线程数为0,最大线程数几乎无限,阻塞队列使用的是 SynchronousQueue:同步移交队列 注意事项:使用时要注意控制线程的数量,防止因线程数太多导致OOM。 2> newFixedThreadPool(int nThreads) 创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程,如果工作线程数 量达到线程池初始的最大数,则将提交的任务存入到池队列中。 FixedThreadPool是一个典型且优秀的线程池,它具有线程池提高程序效率和节省创建线程时所 耗的开销的优点。但是,在线程池空闲时,即线程池中没有可运行任务时,它不会释放工作线 程,还会占用一定的系统资源。 线程数量固定,传入的即是核心线程数,也是最大值;阻塞队列用的是无界的。 注意:使用时要注意控制无界队列的大小,防止因队列中等待的线程数太多导致OOM。 3> newSingleThreadExecutor() 核心线程数和最大线程数都是1,阻塞队列是无限的(单一线程,一次执行一个线程) 4> ScheduledThreadPoolExecutor 来源:https://www.cnblogs.com/despacito/p/11171383.html 从构造函数可以看出ScheduledThreadPoolExecutor还是调用的父类ThreadPoolExecutor的构造方 式,唯一区别是工作队列固定为DelayedWorkQueue。 6> 线程池源码(核心) 1> 主线程调用ThreadPoolExecutor类的submit方法,submit方法内部直接调用了execute方法, execute方法是顶层接口Executor定义,在ThreadPoolExecutor类中实现 2> execute方法中,调用addWork方法,会new一个Work对象,后面调用work的start方法。 3> work是ThreadPoolExecutor类的内部类,实现了Runnable接口,实现了run方法,方法内直接调用了 runWork方法 4> run方法中,通过getTask方法拿到任务,getTask方法是从workQueue队列中poll方法拿到任务。 5> workQueue队列是在创建ThreadPoolExecutor类对象的构造器中定义的 6> 从队列中拿到任务之后,会调用任务对象的run方法 7> 核心线程和非核心线程的区别 来源:https://blog.csdn.net/qq_30596077/article/details/88658264 1> 核心线程 : 固定线程数 可闲置 不会被销毁 ThreadPoolExecutor的allowCoreThreadTimeOut属性设置 为true时,keepAliveTime同样会作用于核心线程 2> 非核心线程数: 非核心线程闲置时的超时时长,超过这个时长,非核心线程就会被回收 注: 1> 核心线程池不会销毁,可以重复使用的,所以优先判断核心线程是否满了。 2> 如果核心线程满了,就往队列中放,因为还是正常排队使用核心线程工作。 3> 如果队列都满了,不得已再看非核心线程是否满了。(因为这家伙会销毁,耗费性能) 8> 并发原子类AtomicInteger https://baijiahao.baidu.com/s?id=1647621616629561468&wfr=spider&for=pc 9> CAS机制 https://baijiahao.baidu.com/s?id=1647620168550407271&wfr=spider&for=pc
0、Spring
0、概念
1.Spring是什么? 概述:Spring是一个'轻量级'的'IoC和AOP'的'容器框架'。 目的:用于'简化'企业应用程序的开发,它使得开发者只需要关心业务需求 主要模块有: 1> Spring Core:提供IOC服务;(核心类库) 2> Spring AOP:提供AOP服务; 3> Spring MVC:提供面向Web应用的Model-View-Controller'实现'。 4> Spring ORM:提供对现有的ORM框架的支持; 来源:https://blog.csdn.net/a745233700/article/details/80959716
1、优点
2.Spring 的优点? 1> spring属于'低侵入式'设计,代码的污染极低; 2> spring的'DI机制'将对象之间的依赖关系交由框架处理,减低组件的'耦合性'; 3> Spring提供了AOP技术,支持将一些'通用任务进行集中式管理',从而提供更好的复用。 如安全、事务、日志、权限等 4> spring对于'主流的应用框架'提供了集成支持。(略)
2、AOP理解
3.Spring的AOP理解 注* 一个概念,一个原理。优点不谈。 1> 概述: a. OOP面向对象,允许开发者定义纵向的关系,但并不适用于定义横向的关系,导致了大量代码的重复 b. AOP,一般称为面向切面,作为面向对象的一种补充。 c.切面: 用于将那些'与业务无关,但却对多个对象产生影响'的公共行为和逻辑,抽取并封装为一个可重用 的模块,这个模块被命名为'切面'(Aspect), 2> 优点:(略) 减少重复代码, 降低耦合度,(模块间的) 提高可维护性。 3> 原理: AOP实现的关键在于 '代理模式', AOP代理主要分为静态代理和动态代理。 静态代理的代表为AspectJ;动态代理则以Spring AOP为代表 4> 静态代理和动态代理理解(略) a. 静态代理: 就是AOP框架会在'编译阶段'生成AOP代理类。 他会在编译阶段将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象 b. 动态代理 AOP框架不会去修改字节码,而是'每次运行'时在内存中临时为方法生成一个AOP对象 这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法 5> Spring AOP中的动态代理主要有两种方式(略) a JDK动态代理 b CGLIB动态代理 注* 目标对象有实现接口,spring会自动选择“JDK代理” // 目标对象没有实现接口, spring会用“cglib代理” 来源:https://www.cnblogs.com/guzhou-ing/p/6445159.html 6> 静态代理和动态代理的区别(略) 上接4> 相对来说AspectJ的静态代理方式具有更好的性能 但是AspectJ需要特定的'编译器'进行处理
3、IoC理解
4.Spring的IoC理解 注* 一个概念,一个原理。优点不谈。 1> 概述: IOC就是控制反转,是指创建对象的控制权的转移。以前创建对象的主动权和时机是由自己把控的,而现 在这种权力转移到Spring容器中,并由容器根据配置文件去创建实例和管理各个实例之间的依赖关系。 2> 优点: 对象与对象之间松散耦合 也利于功能的复用 3> 原理: 使用java的'反射机制',根据配置文件在运行时'动态的'去创建对象以及管理对象,并调用对象的方法 的。 4> 注入方式: 基于XML的注入: 子来源:https://blog.csdn.net/a909301740/article/details/78379720 a.构造方法注入 <!-- 注册userService --> <bean id="userService" class="com.lyu.spring.service.impl.UserService"> <constructor-arg ref="userDaoJdbc"></constructor-arg> </bean> <!-- 注册jdbc实现的dao --> <bean id="userDaoJdbc" class="com.lyu.spring.dao.impl.UserDaoJdbc"></bean> b.setter注入 <!-- 注册userService --> <bean id="userService" class="com.lyu.spring.service.impl.UserService"> <property name="userDao" ref="userDaoMyBatis"></property> </bean> <!-- 注册mybatis实现的dao --> <bean id="userDaoMyBatis" class="com.lyu.dao.impl.UserDaoMyBatis"></bean> 注* 如果通过set方法注入属性,那么spring会通过默认的空参构造方法来实例化对象 c.静态工厂注入 d.实例工厂注入 基于注解的注入: 子来源:https://www.cnblogs.com/wangbin2188/p/9014400.html 描述依赖关系主要有两种: @Resource 默认以byName的方式去匹配与属性名相同的bean的id, 如果没有找到就会以byType的方式查找。 如果byType查找到多个的话,使用@Qualifier注解指定某个具体名称的bean @Autowired 默认是以byType的方式去匹配类型相同的bean 注* 实现步骤 1 导入AOP的Jar包。因为注解的后台实现用到了AOP编程。 2 需要更换配置文件头,即添加相应的约束。 3 需要在Spring配置文件中配置组件扫描器,用于在指定的基本包中扫描注解。
4、自动装配
9.Spring的自动装配 0> 来源: https://blog.csdn.net/tanga842428/article/details/54694484 1> 概述: Spring注入中byType和byName的总结 区分清楚什么是byType,什么是byName 2> 示例: <bean id="userServiceImpl" class="cn.com.bochy.service.impl.UserServiceImpl" autowire="byName"> </bean> <bean id="userDao" class="cn.com.bochy.dao.impl.UserDaoImpl"></bean> 3> 详解: 比如说如上这段代码,byName就是通过Bean的id或者name,byType就是按Bean的Class的类型。 若autowire="byType"意思是通过 class="cn.com.bochy.dao.impl.UserDaoImpl"来查找 UserDaoImpl下所有的对象。 代码autowire="byName"意思是通过id="userDao"来查找Bean中的userDao对象 4> @Autowired和@Resource之间的区别 a.@Autowired默认是按照'类型'装配注入的 b.@Resource默认是按照'名称'来装配注入的,只有当找不到与名称匹配的bean才会按照类型来装配注 入。
5、用到的设计模式
10.Spring 框架中都用到了哪些设计模式? 1> 工厂模式:BeanFactory就是简单工厂模式的体现,用来创建对象的实例; 2> 单例模式:Bean默认为单例模式。 3> 代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术; 4> (略) 模板方法:用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate。 5> (略) 观察者模式:定义对象间一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象 都会得到通知被制动更新,如Spring中listener的实现--ApplicationListener。
6、事务
11.Spring事务的实现方式和实现原理 1> 概述: Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持, spring是无法提供事务功能的。真正的数据库层的事务提交和回滚是通过binlog或者redolog实现的。 2> Spring事务的种类 Spring支持'编程式'事务管理和'声明式'事务管理两种方式 3> 声明式事务的优缺点 原理: 声明式事务管理建立在'AOP'之上的。其本质是通过AOP功能,对'方法前后'进行'拦截',将事务 处理的功能'编织'到拦截的方法中.也就是在目标方法开始之前加入一个事务,在执行完目标方法 之后根据执行情况提交或者回滚事务。 优点: 不需要在业务逻辑代码中'掺杂'事务管理的代码 这正是spring倡导的'非侵入式'的开发方式 缺点: '最细粒度'只能作用到'方法级别',无法做到像编程式事务那样可以作用到'代码块级别'。
7、其他
5.BeanFactory和ApplicationContext有什么区别(略) 0> 概述: BeanFactory和ApplicationContext是Spring的两大核心接口,'都可以当做Spring的容器'。 其中ApplicationContext是BeanFactory的子接口。 1> 区别: a.BeanFactroy采用的是'延迟加载'形式来注入Bean的,即只有在使用到某个Bean时(调用 getBean()),才对该Bean进行加载实例化。 注* 配置问题不易发现。 b.ApplicationContext,它是在容器'启动'时,一次性创建了所有的Bean 注* 占用内存空间。当应用程序配置Bean较多时,程序启动较慢。 6.请解释Spring Bean的生命周期?(略) 1> 实例化Bean 对于BeanFactory容器 对于ApplicationContext容器 2> 设置对象属性(依赖注入) 3> 处理Aware接口 4> BeanPostProcessor 5> InitializingBean 与 init-method 6> BeanPostProcessor 7> DisposableBean 当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean这个接口,会调用其实现的 destroy()方法; 8> destroy-method 最后,如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法 7.解释Spring支持的几种bean的作用域。(略) 1> singleton 默认,'每个容器'中只有一个bean的实例 2> prototype 为'每一个bean请求'提供一个实例 3> request 4> session 5> global-session 8.Spring如何处理线程并发问题? ThreadLocal 早在JDK 1.2的版本中就提供java.lang.ThreadLocal, ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。 使用这个工具类可以很简洁地编写出优美的多线程程序。 12.spring的事务传播行为(略) spring事务的传播行为说的是,当多个事务同时存在的时候,spring如何处理这些事务的行为。 13.Spring中的隔离级别(略) 14.Spring框架中有哪些不同类型的事件(略) 15.解释一下Spring AOP里面的几个名词(待完善) 1> 切面(Aspect) 2> 连接点(Join point) 3> 通知(Advice) 4> 切入点(Pointcut) 5> 引入(Introduction) 6> 目标对象(Target Object) 7> 织入(Weaving) 16.面向切面编程 1> 来源: https://blog.csdn.net/seashouwang/article/details/80232811 springboot-aop面向切面编程 2> 来源: https://www.cnblogs.com/guzhou-ing/p/6445159.html -- 代码 https://blog.csdn.net/MrLeeYongSheng/article/details/78643671 -- 需要所有jar包 https://www.cnblogs.com/hzg110/p/6760653.html -- 需要jar包 spring基于注解和XML实现AOP编程 注* 面向切面编程,创建一个最简单的Spring项目 来源:https://www.cnblogs.com/hzg110/p/6760653.html 注意:application.xml报错,是因为jar包不够,引入来源中的jar包之后,报错消失。 问题1:ClassPathXmlApplicationContext加载多个XML文件 来源:https://blog.csdn.net/weinichendian/article/details/72842929
1、SpringMVC
0、流程
2.SpringMVC的流程? 1> 用户发送请求至'前端控制器'DispatcherServlet; 2> DispatcherServlet收到请求后,调用HandlerMapping'处理器映射器',请求获取Handle; 3> 处理器映射器HandlerMapping根据请求url'找到'具体的处理器Handler,生成处理器对象及处理器拦截 器(如果有则生成)一并返回给DispatcherServlet; 4> DispatcherServlet 调用 HandlerAdapter'处理器适配器'; 5> HandlerAdapter 经过适配'调用' '具体处理器'(Handler,也叫后端控制器); 6> Handler执行完成返回ModelAndView; 7> HandlerAdapter将Handler执行结果ModelAndView返回给DispatcherServlet; 8> DispatcherServlet将ModelAndView传给ViewResolver'视图解析器'进行解析; 9> ViewResolver解析后返回'具体View'; 10> DispatcherServlet对View进行'渲染'视图(即将模型数据填充至视图中) 11> DispatcherServlet响应用户。
1、优点
3.Springmvc的优点(略) 1> 可以支持各种视图技术,而不仅仅局限于JSP; 2> 与Spring框架集成(如IoC容器、AOP等); 3> 清晰的角色分配:前端控制器(dispatcherServlet) , 请求到处理器映射(handlerMapping), 处理 器适配器(HandlerAdapter), 视图解析器(ViewResolver)。 4> 支持各种请求资源的映射策略。
2、和Struts2的区别
5.springMVC和struts2的区别有哪些? 1> 入口 springmvc的入口是一个servlet即前端控制器(DispatchServlet), 而struts2入口是一个filter过虑器(StrutsPrepareAndExecuteFilter)。 2> 开发原理 springmvc是基于'方法'开发(一个url对应一个方法),请求参数传递到方法的形参。 struts2是基于'类'开发,传递参数是通过类的属性。 3> 请求/响应的数据存储 a. Struts采用'值栈'存储请求和响应的数据,通过OGNL存取数据, b. springmvc通过'参数解析器'是将request请求内容解析,并给方法形参赋值,将数据和视图封装成 'ModelAndView对象',最后又将ModelAndView中的模型数据通过reques域传输到页面。
3、函数返回值类型
17.SpringMvc中函数的返回值是什么? 子来源:https://blog.csdn.net/qq_35192741/article/details/79085815 1> ModelAndView ModelAndView mv = new ModelAndView(); mv.addObject("content", "springMVC HelloWorld:"+param); mv.setViewName("springMVC/helloWorld"); return mv; 2> String //返回值为String类型的,视图名就是返回值 @RequestMapping(value="/returnString",method=RequestMethod.GET) public String returnString(Model model) { model.addAttribute("test", "return string!!!"); System.out.println("springMVC测试:helloWorld;"); return "return/returnString"; } 3> void //返回值是void类型的,由于没有返回值 //它默认的展示视图名和url中的一段是一样的即returnVoid.jsp @RequestMapping("/returnVoid") public void returnVoid(Model model){ model.addAttribute("test", "return void!!!"); } 4> Map //返回值是void类型的,由于没有指定视图名 //它默认的展示视图名和url中的一段是一样的即returnVoid.jsp @RequestMapping("/returnModel") public Model returnModel(Model model){ model.addAttribute("test", "return Model!!!"); return model; } 5> Model //返回值是void类型的,由于没有指定视图名 //它默认的展示视图名和url中的一段是一样的即returnVoid.jsp @RequestMapping("/returnMap") public Map returnMap(Model model){ Map map = new HashMap<String,String>(); map.put("test", "return map!!!"); return map; }
来源:https://blog.csdn.net/a745233700/article/details/80963758 0.对Spring MVC的理解 ? 注* 一个概念,一个原理(即流程)。优点不谈。 1.什么是Spring MVC ? 1> Spring MVC是'轻量级'的实现了'MVC设计模式'的'Web框架', 2> (略) 通过把Model,View,Controller分离,把复杂的web应用分成逻辑清晰的几部分, 3> (略) 简化开发,减少出错,方便组内开发人员之间的配合。 4.Spring MVC的主要组件?(略) 注意:Handler和View 1> 前端控制器 DispatcherServlet(不需要程序员开发) 接收请求、响应结果,相当于转发器,有了DispatcherServlet 就减少了其它组件之间的耦合度。 2> 处理器映射器HandlerMapping(不需要程序员开发) 根据请求的URL来查找Handler 3> 处理器适配器HandlerAdapter 在编写Handler的时候要按照HandlerAdapter要求的规则去编写,这样适配器HandlerAdapter才可以 正确的去执行Handler。 4> 处理器Handler(需要程序员开发) 5> 视图解析器 ViewResolver(不需要程序员开发) 进行视图的解析,根据视图逻辑名解析成真正的视图(view) 6> 视图View(需要程序员开发jsp) View是一个接口,它的实现类支持不同的视图类型(jsp,freemarker,pdf等等) 6.SpringMVC怎么样设定重定向和转发的? 1> 转发:在返回值前面加"forward:",譬如"forward:user.do?name=method4" 2> 重定向:在返回值前面加"redirect:",譬如"redirect:http://www.baidu.com" 7.SpringMvc怎么和AJAX相互调用的? 通过Jackson框架就可以把Java里面的对象直接转化成Js可以识别的Json对象。具体步骤如下 : 1> 加入Jackson.jar 2> (略) 在配置文件中配置json的映射 3> 在接受Ajax方法里面可以直接返回Object,List等,但方法前面要加上@ResponseBody注解。 8.如何解决POST请求中文乱码问题,GET的又如何处理呢? 1> 解决post请求乱码问题: 在web.xml中配置一个CharacterEncodingFilter过滤器,设置成utf-8; <filter> <filter-name>CharacterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter </filter-class> <init-param> <param-name>encoding</param-name> <param-value>utf-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> 2> get请求中文参数出现乱码解决方法有两个: 方法1: 修改tomcat配置文件添加编码与工程编码一致,如下: <ConnectorURIEncoding="utf-8" connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443"/> 方法2: 另外一种方法对参数进行'重新编码': String userName = new String(request.getParamter("userName").getBytes("ISO8859- 1"),"utf-8") 注* ISO8859-1是tomcat默认编码,需要将tomcat编码后的内容按utf-8编码。 9.Spring MVC的异常处理 ? 子来源:https://www.cnblogs.com/junzi2099/p/7840294.html 一共三种方法 方法1:使用 @ ExceptionHandler 注解 使用该注解有一个不好的地方就是:进行异常处理的方法必须与出错的方法在同一个Controller里面。 使用如下: @Controller public class GlobalController { //处理异常 @ExceptionHandler({MyException.class}) public String exception(MyException e) { System.out.println(e.getMessage()); e.printStackTrace(); return "exception"; } @RequestMapping("test") public void test() { throw new MyException("出错了!"); } } 可以看到,这种方式最大的缺陷就是不能全局控制异常。每个类都要写一遍。 方法2:实现 HandlerExceptionResolver 接口 这种方式可以进行全局的异常控制。例如: @Component public class ExceptionTest implements HandlerExceptionResolver{ public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { System.out.println("This is exception handler method!"); return null; } } 方法3:使用 @ControllerAdvice+ @ ExceptionHandler 注解(略) 10. SpringMvc的控制器是不是单例模式,如果是,有什么问题,怎么解决? 子来源(大神文章-通俗易懂):https://www.cnblogs.com/eric-fang/p/5629892.html 解决: 1、不要在controller中定义成员变量。 2、万一必须要定义一个非静态成员变量时候,则加注解@Scope("prototype"),将其设置为多例模式 11. SpringMVC常用的注解有哪些? 1> @RequestMapping 用于处理请求 url 映射的注解,可用于类或方法上。用于类上,则表示类中的所有响应请求的方法都 是以该地址作为父路径。 2> @RequestBody 注解实现接收http请求的json数据,将json转换为java对象。 3> @ResponseBody 注解实现将conreoller方法返回对象转化为json对象响应给客户。 12.SpingMvc中的控制器的注解一般用那个,有没有别的注解可以替代? 一般用@Conntroller注解,表示是表现层,不能用别的注解代替。 13.如果在拦截请求中,我想拦截get方式提交的方法,怎么配置? 可以在@RequestMapping注解里面加上method=RequestMethod.GET 14.怎样在方法里面得到Request,或者Session? 直接在方法的形参中声明request,SpringMvc就自动把request对象传入。 15.如果想在拦截的方法里面得到从前台传入的参数,怎么得到? 方法1:直接在形参里面声明这个参数就可以,但必须名字和传过来的参数一样。 方法2:使用@RequestParam注解 16.如果前台有很多个参数传入,并且这些参数都是一个对象的,那么怎么样快速得到这个对象? 直接在方法中声明这个对象,SpringMvc就自动会把属性赋值到这个对象里面。 或者 使用@RequestBody注解 18.SpringMvc用什么对象从后台向前台传递数据的? 通过Model或者Map对象,可以在这个对象里面调用put方法,把对象加到里面,前台就可以通过el表达式拿到。 model --> model.addAttribute("test", "return Model!!!"); map --> map.put("test", "return map!!!"); 19.怎么样把Model/Map里面的数据放入Session里面? 子来源:https://blog.csdn.net/u012325167/article/details/52426523 可以在类上面加上@SessionAttributes注解,里面包含的字符串就是要放入session里面的key。 20.SpringMvc里面拦截器是怎么写的: 子来源:https://blog.csdn.net/binggetong/article/details/78831681 有两种写法: 一种是实现HandlerInterceptor接口, 另外一种是继承适配器类,接着在接口方法当中,实现处理逻辑; 然后在SpringMvc的配置文件中配置拦截器即可 配置文件示例: <mvc:interceptors> <!-- 使用bean定义一个Interceptor,直接定义在mvc:interceptors根下面的Interceptor将拦截 所有的请求 --> <bean class="com.host.app.web.interceptor.AllInterceptor"/> <mvc:interceptor> <mvc:mapping path="/test/number.do"/> <!-- 定义在mvc:interceptor下面的表示是对特定的请求才进行拦截的 --> <bean class="com.host.app.web.interceptor.LoginInterceptor"/> </mvc:interceptor> <mvc:interceptor> <!-- 授权拦截 --> <mvc:mapping path="/**" /> <bean class="cn.itcast.interceptor.PermissionInterceptor"></bean> </mvc:interceptor> </mvc:interceptors> 21.注解原理:(略) 子来源(强的一批): https://www.cnblogs.com/yangming1996/p/9295168.html 来源: 注解本质是一个继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动态代理类。我们通过反 射获取注解时,返回的是Java运行时生成的动态代理对象。通过代理对象调用自定义注解的方法,会最终调 用AnnotationInvocationHandler的invoke方法。该方法会从memberValues这个Map中索引出对应的值。 而memberValues的来源是Java常量池。
####