springmv执行流程
1、DispatcherServlet表示前置控制器,是整个SpringMVC的控制中心。用户发出请求,DispatcherServlet接收请求并拦截请求。
2、HandlerMapping为处理器映射。DispatcherServlet调用HandlerMapping,HandlerMapping根据请求url查找Handler。
3、返回处理器执行链,根据url查找控制器,并且将解析后的信息传递给DispatcherServlet
4、HandlerAdapter表示处理器适配器,其按照特定的规则去执行Handler。
5、执行handler找到具体的处理器
6、Controller将具体的执行信息返回给HandlerAdapter,如ModelAndView。
7、HandlerAdapter将视图逻辑名或模型传递给DispatcherServlet。
8、DispatcherServlet调用视图解析器(ViewResolver)来解析HandlerAdapter传递的逻辑视图名。
9、视图解析器将解析的逻辑视图名传给DispatcherServlet。
10、DispatcherServlet根据视图解析器解析的视图结果,调用具体的视图,进行试图渲染
11、将响应数据返回给客户端
bean生命周期
1.实例化,反射机制推断构造函数进行实例化
2.属性赋值,DI的体现,@autowired,循环依赖
3.初始化,回调beanfactoryaware,实现了aop,回调动态代理
4销毁,spring容器关闭时进行销毁
springboot启动顺序
一、加载启动类Spring Boot:应用程序的启动类必须使用
@SpringBootApplication注解标记,该注解包含了
@SpringBootConfiguration、@EnableAutoConfiguration和@ComponentScan三个注解的功能。Spring Boot通过扫描启动类所在的包和子包,自动装配配置类和Bean定义。
二、加载配置文件:Spring Boot应用程序默认从applicationproperties或application.vml文件中加载配置属性,也可以通过在启动类上使用@PropertySource注解指定其他的属性文件。
三、创建Spring容器Spring Boot使用SpringApplication类创建Spring容器,SpringApplication是Spring Boot的核心类,它提供了各种配置和管理Spring应用程序的方法。
SpringApplication会创建一个嵌入式的Tomcat或Jetty服务器,或者使用外部Tomcat或Jetty服务器。
四、加载自动配置:Spring Boot通过@EnableAutoConfiguration注解和spring-boot-autoconfigure项目提供了大量的自动配置,根据classpath中的jar包和Bean的装配情况,自动装配相应的Bean。
五、运行Spring Boot应用程序:当Spring容器准备就绪后,Spring Boot就会启动嵌入式的Web服务器并运行应用程序。如果使用的是外部Web服务器,Spring Boot就会将应用程序打包成一个可执行的jar文件,并启动外部Web服务器。
正确的答案是:#{}是预编译处理,${}是字符串替换。
(1)mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值。
(2)mybatis在处理${}时,就是把${}替换成变量的值。
(3)使用#{}可以有效的防止SQL注入,提高系统安全性。原因在于:预编译机制。预编译完成之后,SQL的结构已经固定,即便用户输入非法参数,也不会对SQL的结构产生影响,从而避免了潜在的安全风险。
(4)预编译是提前对SQL语句进行预编译,而其后注入的参数将不会再进行SQL编译。我们知道,SQL注入是发生在编译的过程中,因为恶意注入了某些特殊字符,最后被编译成了恶意的执行操作。而预编译机制则可以很好的防止SQL注入。
1. 程序本质:代码是如何被执行的?CPU、操作系统、虚拟机各司何职?
先把Java代码编译成字节码也就是把.java类型的文件字节码生成器编译成.class类型的文件(语法,语义,抽象语法树)
我们执行的时候,会把class文件放置到Java虚拟机,
Java虚拟机使用类加载器,装载class文件
类加载完成后,JVM解释器会把字节码翻译成与机器码交给操作系统执行。当然了虚拟机本身也支持JIT动态编译器。
2. 基础语法:从CPU角度看变量、数组、类型、运算、跳转、函数等语法
大小,精度,优先级,方法
3. 引用类型:同样都是存储地址,为何Java引用比C/C++指针更安全?
java引用有类型检查(可以强转)
4. 基本类型:既然Java一切皆对象,那又为何要保留int等基本类型?
因为八种基本类型都有对应的包装类(自动拆箱和自动装箱),再有就是基本类型定义的变量创建和销毁很快,而类定义的变量还需要JVM去销毁。
6. 浮点数:计算机如何用二进制表示浮点数?为何0.1+0.1不等于0.2?
十进制转换成二进制(计算机只认识二进制)的时候,需要近似相等,所以加完了以后就不准确了
https://baijiahao.baidu.com/s?id=1680396399018883986&wfr=spider&for=pc
7. 字符:为何C/C++中char占1个字节,而Java中char占2个字节?
C/C++用 ASCII 编码标准中,每个字符只占用一个字节,Java 采用了 16 位编码的Unicode 编码标准
8. 字符串:请解释String类用到的三大技术:压缩、常量池、不可变\
String类又称作不可变Unicode字符序列。
final修饰(工艺品)
修改只是拷贝个新的
String s = "aaaaa"会进入到常量池,new String(...)在堆上创建对象,不会入池,属于优化部分
10. 关键字:静态内部类实现的单例如何做到线程安全且可延迟加载?
单例模式:懒汉,饿汉,枚举,静态内部类,延迟加载,减少内存开销,访问成本低且线程安全
第一次new的时候会被加载,节约性能,建造者模式
11. 容器:为什么不推荐在项目中使用Vector、Stack、HashTable?
vector是线程安全的
Vector的空间扩容是一倍,内存不可复用,而ArrayList是一半 (C++ Made Easier: How Vectors Grow)
Vector分配内存是连续的存储空间
然后是stack:Stack 是 JDK 1.0 的产物。它继承自 Vector。
当初 JDK1.0 在开发时,可能为了快速的推出一些基本的数据结构操作,所以推出了一些比较粗糙的类。比如,Vector、Stack、Hashtable等。这些类中的一些方法加上了 synchronized 关键字,容易误导。
13. HashMap(上):为何HashMap中数组的大小必须是2的幂次方?
首先,HashMap的初始化的数组长度一定是2的n次的,每次扩容仍是原来的2倍的话,就不会破坏这个规律,每次扩容后,原数据都会进行数据迁移,根据二进制的计算,扩容后数据要么在原来位置,要么在【原来位置+扩容长度】,这样就不需要重新hash,效率上更高。扩容后
load factory=0.75的真正原因,在java7、8等中均有注释,负载因子太小了浪费空间并且会发生更多次数的resize,太大了哈希冲突增加会导致性能不好,所以0.75只是一个折中的选择,和泊松分布没有什么关系。原来位置还是在原脚标位+扩容长度的位置
1:当添加某个元素后,数组的总的添加元素数大于了 数组长度 * 0.75(默认,也可自己设定),数组长度扩容为两倍。(如开始创建HashMap集合后,数组长度为16,临界值为16 * 0.75 = 12,当加入元素后元素个数超过12,数组长度扩容为32,临界值变为24)
16. 迭代器:为什么使用迭代器遍历容器的同时修改容器会出错?
在用迭代器遍历容器的时候,试图去修改容器中的元素,可能会引起并发修改异常(遍历的时候,相当于一个指针从0索引开始移动,当试图修改某个数据时,系统无法去操作这个指针)
迭代器是依赖于集合而存在的,在判断成功之后,集合当中增加了新的元素,而迭代器却不知道,所以就报错了,这个就叫并发修改异常。
17. 异常(上):在项目开发中如何正确的定义、处理、打印异常?
枚举,定义异常类,
18. 异常(下):高并发下异常太多导致程序变慢的核心原因是什么?
CPU、内存、IO 设备的读写速度差异巨大,表现为 CPU 的速度 > 内存的速度 > IO 设备的速度。
20.nio类库:BIO、NIO、AIO三种Java I/O模型的实现原理和区别
BIO
Java BIO即Block I/O , 同步并阻塞的IO。
BIO就是传统的java.io包下面的代码实现。
NIO
什么是NIO? NIO 与原来的 I/O 有同样的作用和目的, 他们之间最重要的区别是数据打包和传输的方式。原来的 I/O 以流的方式处理数据,而 NIO 以块的方式处理数据。
面向流 的 I/O 系统一次一个字节地处理数据。一个输入流产生一个字节的数据,一个输出流消费一个字节的数据。为流式数据创建过滤器非常容易。链接几个过滤器,以便每个过滤器只负责单个复杂处理机制的一部分,这样也是相对简单的。不利的一面是,面向流的 I/O 通常相当慢。
一个 面向块 的 I/O 系统以块的形式处理数据。每一个操作都在一步中产生或者消费一个数据块。按块处理数据比按(流式的)字节处理数据要快得多。但是面向块的 I/O 缺少一些面向流的 I/O 所具有的优雅性和简单性。
AIO
Java AIO即Async非阻塞,是异步非阻塞的IO。
21. 高速I/O(上):普通的I/O读写流程都存在哪些性能问题?
多次内存复制 阻塞
Java NIO 提供了与标准 IO 不同的 IO 工作方式:
Channels and Buffers(通道和缓冲区):标准的 IO 基于字节流和字符流进行操作,而 NIO 是基于通道(Channel)和缓冲区(Buffer)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。
非阻塞模式:Java NIO 是非阻塞的,每一次数据读写调用都会立即返回,并将目前可读(或可写)的内容写入缓冲区或者从缓冲区中输出,即使当前没有可用数据,调用仍然会立即返回并且不对缓冲区做任何操作。
Selectors(选择器):Java NIO 引入了选择器的概念,选择器用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个的线程可以监听多个数据通道。
23. 泛型:为什么C++泛型支持int等基本类型而Java泛型不支持呢?
基本数据类型不是对象
24. 反射:为什么通过反射创建对象要比使用new创建对象慢?
首先new 这是一个指令 系统接到这人命令,立马就是申请内存空间(堆空间,接下来就是调用无参构造,来初始化等一系列操作,因为你new明确的指定了是哪个类需要创建,而反射不同,他的先去你在某个包下类,找到了之后还要做解析,看看类里有什么东西,再去找构造方法,找到了还去执行这个方法,底层应该是通过一个角invoke的方法,接下来就是开辟内存空间,加载,初始化等,这能不慢嘛.
25. 注解:注解的配置方式相对于XML配置文件有什么优缺点?
Spring注解配置优点
1、使用注解配置可以减少XML配置文件,使得项目更加简洁,可读性更高,当项目变得越来越复杂时,可以更加简洁的管理项目;
2、注解配置可以减少大量XML配置文件中的重复代码,可以提高项目的可维护性,也可以提高开发效率;
3、注解配置可以更加直观的表达程序的功能,可以更加清晰的表达程序的本质;
4、注解配置可以更加方便的管理Spring容器中的Bean,可以更加灵活的进行Bean的定义;
Spring XML配置优点
1、XML配置使得项目更加简单,可读性更高,当项目变得越来越复杂时,可以更加简洁的管理项目;
2、XML配置可以减少大量注解配置中的重复代码,可以提高项目的可维护性,也可以提高开发效率;
3、XML配置可以更加直观的表达程序的功能,可以更加清晰的表达程序的本质;
4、XML配置可以更加方便的管理Spring容器中的Bean,可以更加灵活的进行Bean的定义;
26.动态代理:为什么基于JDK实现的动态代理要求原始类有接口?
因为 Java 是单继承的,因此在代理类 $Proxy0 继承了 Proxy 后,其只能通过实现目标接口的方式来实现方法的扩展,达到我们增强目标方法逻辑的目的
28. 线程概述:有了进程为什么还要有线程?线程越多执行就越快吗?
从资源上来讲:线程是一种非常"节俭"的多任务操作方式。而进程的创建
需要更多的资源。
从切换效率上来讲:运行于一个进程中的多个线程,它们之间使用相同的
地址空间,而且线程间彼此切换所需时间也远远小于进程间切换所需要的时间。
据统计,一个进程的开销大约是一个线程开销的 30 倍左右。
从通信机制上来讲:对不同进程来说,它们具有独立的数据空间,要进行
数据的传递只能通过进程间通信的方式进行,这种方式不仅费时,而且很不方便。
线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可
以直接为其他线程所用,这不仅快捷,而且方便。
50. 分段加锁:HashMap线程不安全原因及ConcurrentHashMap实现原理
1.7之前是头插法,多线程导致.next找不到了,1.8采用尾插法,多线程导致尾部丢失覆盖,实现原理是多片分区
51. 线程状态:为何synchronized和Lock这两种锁对应的线程状态不同?
52. 线程中断:如何安全地提前终止正在执行业务逻辑的线程?
volatile 关键字
使用 interrupt() + isInterrupted()来中断线程
使用 interrupt() + InterruptedException来中断线程
53. 线程池:线程池开多大最合适?为什么Redis单线程执行命令?
官方解释如下:因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了。
上面的解释不是很好理解,我就简单说一说我自己的理解吧。我们知道,Redis将数据存放在内存当中,这也就意味着,Redis在操作数据时,不需要进行磁盘I/O。磁盘I/O是一个比较耗时的操作,所以对于需要进行磁盘I/O的程序,我们可以使用多线程,在某个线程进行I/O时,CPU切换到当前程序的其他线程执行,以此减少CPU的等待时间。而Redis直接操作内存中的数据,所以使用多线程并不能有效提升效率,相反,使用多线程反倒会因为需要进行线程的切换而降低效率。
除此之外,使用多线程的话,多个线程间进行同步,保证线程的安全,也是需要开销的。尤其是Redis的数据结构都是一些实现较为简单的集合结构,若使用多线程,将会频繁地发生线程冲突,线程的竞争频率较高,反倒会拖慢Redis的响应速度。
54.线程执行框架:如何获取一个线程所执行的代码的运行结果?
Thread的join方法实现
CountDownLatch
ExecutorService.submit
FutureTask
CompletableFuture
57. 类加载:双亲委派加载机制存在的意义是什么?
通过委派的方式,可以避免类的重复加载,当父加载器已经加载过某一个类时,子加载器就不会再重新加载这个类。
通过双亲委派的方式,还保证了安全性
被不同的类加载器加载的类也不一样
59. 可达性分析:虚拟机是如何判断一个对象是否可以被回收的?
引用计数法 可达性分析算法
60.spring事务的传播机制
bean创建流程
第一步:要进行bean创建,首先要有定义bean的配置,可以是注解、xml、json等配置方式
第二步:有了配置,要去读取配置(读取过程稍后细讲),并且将配置bean信息存储起来,spring使用beanDefinition进行存储
第三步:spring提供了BeanFactoryPostProcessor对beanDefinition信息做增强
第四步:当bean信息BeanDefinition处理操作完成,开始根据beanDefinition进行bean的实例化,这个过程创建了空的bean对象,只是分配了内存空间、创建了实例对象
第五步:有了实例对象开始对bean对象进行属性信息填充及依赖对象注入
第六步:对bean对象进行增强,包含Aware增强和spring提供的BeanPostProcessor增强
第七步:上述所有操作完成,即是一个完整地bean对象,此时将该bean对象缓存进ioc容器中。