本文共 20385 字,大约阅读时间需要 67 分钟。
风清扬Java面试题突击100道!
https://www.zhihu.com/people/13148848
简述三者的关系
Java如何实现跨平台
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2RSnvgqj-1620227111528)(C:\Users\Sharm\AppData\Roaming\Typora\typora-user-images\image-20210501135614290.png)]
package cn.com.gs.common.test;public class EqualsTest { public static void main(String[] args) { // 引用数据类型 String s11 = new String("zs"); String s12 = new String("zs"); System.out.println(s11 == s12);// false // 基本数据类型,相加时相当于new String s21 = "zs"; String s22 = "zs"; System.out.println(s21 == s22); // true String s23 = "zszs"; String s24 = s21 + s22;// 底层使用new StringBuild创建一个新的对象,再赋值 System.out.println(s23 == s24); // false // 常量 final String s31 = "zs"; final String s32 = "zs"; final String s33 = s31 + s32;// String s34 = s31 + s32; final String s35 = s21 + s22;// 底层使用new StringBuild创建一个新的对象 System.out.println(s31 == s32); // true System.out.println(s23 == s33); // true System.out.println(s23 == s34); // true System.out.println(s23 == s35); // false }}
可以修饰类,变量,方法等。
修饰类:表示类不可继承;eg:String就是常量类
修饰变量:
基本数据类型:表示值不可更改
引用数据类型:表示引用指向不可更改,但是引用的对象里的属性值还是可变的
final Student student = new Student(1, "Andy");student.setAge(18);// 这种是可以的student = new Student(2, "Andy");// 这种是不可以的
修饰方法:表示方法不可重写
String | StringBuild | StringBuffer | |
---|---|---|---|
是否可变 | final修饰,每次声明的都是不可变变量 | final修饰,字符串支持动态修改,append方法 | final修饰,字符串支持动态修改 |
线程安全 | / | 线程不安全 | 线程安全,使用synchronized修饰 |
性能 | / | 高 | 低 |
StringBuild使用:在方法内部使用,处理字符串的拼接,此时不会有多线程安全问题。
多线程执行add方法时,每个线程都有自己独有的栈区,资源不共享时不会出问题。
只有多线程访问同一个共享资源时需要考虑安全问题。
public void add() { StringBuild sb = new StringBuild(); sb.append("zs");}
公式:N! = (n-1)!*n
例如:3! = 1 * 2 * 3
出口:n0 或 n1,return 1;
数字规律:1,1,2,3,4,8,13,21…
// Integer,装箱valueOf方法,数值在-128~127,就返回事前缓存好的数值,其他则newInteger i1 = 128;// 自动装箱Integer.valueOf(128),128超出范围,则new Integer(128)Integer i2 = 128;int i3 = 128;System.out.println(i1 == i2);// falseSystem.out.println(i1 == i3);// true,自动拆箱valueOf,比较数值System.out.println(i2 == i3);// trueInteger i4 = 127;Integer i5 = 127;System.out.println(i4 == i5);// true
重写:发生在父子类之间,子类重写父类的方法,同名同参
重载:发生在同一个类中,同名不同参
list:有序,可重复
set:无序,不可重复。无序指输出顺序!=输入顺序,和排序无关
ArrayList | LinkedList | Vector | |
---|---|---|---|
底层结构 | 数组,连续的内存空间;长度固定,不够用时自动扩容1.5倍 | 双向链表,不连续的内存空间 | |
速度(不太严谨) | 查找快:因为地址连续删除/插入慢:因为数据要移动 | 查找慢:因为需要指针一个个寻找删除/插入快:数据不需要移动,只需要改变前后节点的指针指向即可 | |
线程安全 | 不安全 | 不安全 | 安全,synchronized修饰 |
在查找速度上,需要区分场景:
在插入速度上,需要区分场景:
1. 在头部/中间位置插入:LinkedList > ArrayList2. 在末尾插入:不分上下
延伸:
答:ArrayList更省内存,因为双向链表要存储指针,指针也占内存。
初始化时就分配1000的内存,避免扩容;
第一次创建一个数组,长度为10;
当存第11个的时候,就自动创建一个长度是15的数组;
然后将所有对象迁移到新数组中。
你的理解
控制反转:将创建对象的动作交给Spring容器
解析+赋值
应用场景
具体怎么实现
配置文件实现
怎么加载配置文件?
怎么解析xml? dom4j解析
注解实现@Autowired
什么时候能找到注解?
答:启动流程:扫描所有包,拿到@controller修饰的类,再找@Autowired修饰的属性,根据类型进行依赖注入
// 先定义CC.pre = AC.next = A.next;// 再从后往前修改指针A.next.pre = C;A.next = C
HashSet
add方法底层还是使用的HashMap来存储,值作为key
延伸:
答:要保证数据的唯一性,Set可以保证数据不重复;
答:使用遍历来比对,效率太低。这时出现了Hash算法。
根据需要,重写Object的hashCode方法;
在存储对象时,计算hashCode, 再用hash值和数组长度-1进行位运算,得到要存储的位置。
如果我们要存的位置没有元素,直接存储;
如果有元素,计算equals方法,相等即同一对象不存储,不相等则形成链表(???再优化为红黑树)
答:通过hash算法,确认存储的每个对象唯一。
hash算法不可逆,输出值为定值,计算时可减小数据量。
ArrayList:不安全,效率高;
Vector:安全,效率低
HashTable:哈希表,本质是数组,数组的元素是链表
HashTable | HashMap | ConcurrentHashMap | |
---|---|---|---|
本质是数组,数组的元素是链表 | 本质是数组,put/get | 分段锁 | |
线程安全 | 安全 | 不安全,多线程操作同一个HashMap,可能出现死锁问题;解决方式1.0:Collections.synchronizedMap()方法,但是性能依旧有问题,相当于用了HashTable解决方式2.0:jdk提供了并发包concurrent,兼顾了线程和效率的问题 |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XB3uqZbo-1620227111530)(C:\Users\Sharm\AppData\Roaming\Typora\typora-user-images\image-20210501181206650.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PljfMw5t-1620227111532)(C:\Users\Sharm\AppData\Roaming\Typora\typora-user-images\image-20210501181142735.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OVMk9fs9-1620227111533)(C:\Users\Sharm\AppData\Roaming\Typora\typora-user-images\image-20210501181012036.png)]
栈的特点:底层数组,先进后出
入栈:arr[arr.length + 1] = object
出栈:
public class Node{ Node pre; Node next; T data;}
分类
如何选择字节流和字符流?
serialVersionUID什么时候出现?
答:在对象序列化到磁盘时,会根据当前类的结构生成serialVersionUID。
如果serialVersionUID由系统生成,可能导致在类更新后,反序列化失败,无法读取以往的版本。
所以一般情况下,我们会手动在类里面写一个固定的serialVersionUID值。
有什么作用?
答:用于反序列化时比对。反序列化时,先根据当前类的结构生成一个版本ID,和磁盘的版本ID进行比对,一致则反序列化成功,否则反序列化失败。
什么时候需要序列化和反序列化?
答:序列化:当想要把类信息写入磁盘时;反序列化:要从磁盘读取信息时;
Throwable:
throw:作用在方法内,用于主动抛出一个异常
throws:作用在方法声明上,可以抛出多个异常
一般我们的异常都是一级一级往上抛,最终通过异常处理机制统一处理。(方便打印日志及响应信息)。
答:因为框架制定了一系列规则,我们可能因为不会使用而导致出异常,并不属于逻辑错误。
其实后两种,严格来说是一个可执行的任务,还是需要创建Thread对象来执行,比如new Thread(runnable).start();
实际开发中,我们一般采用线程池,统一管理,节约资源。
延伸:
main也是一个线程,同时还有一个GC垃圾回收线程
新建new、就绪runnable、运行runnable、阻塞(blocked、waiting、timed waiting)、死亡terminated
详述共6种状态:getState()
new、runnable、blocked、waiting、timed waiting、terminated
blocked:当线程进入synchronize同步代码块或同步方法,但是没有获取到锁,会进入blocked状态, 直到其他线程释放锁,当前线程拿到锁,进入就绪runnable状态。
waiting:当线程调用wait()或join()时,线程进入waiting状态,
当线程调用notify()/notifyAll(),或者join的线程执行结束后,进入就绪runnable状态。
timed waiting:当线程调用wait(time)或sleep(time)时,线程进入timed waiting状态,
当线程调用notify()/notifyAll(),或者线程休眠结束后,进入就绪runnable状态。
描述:当多线程访问同一个对象时,如果不用额外的同步控制,就能得到正确的结果,那我们就说这个对象是线程安全的。
如何做到线程安全?
答:常见的是使用synchronize加锁,例如StringBuffer
sleep | wait | |
---|---|---|
所属 | 属于Thread类,Thread.sleep() | 属于Object类,o.wait() |
锁 | 不释放锁,休眠结束自动进入就绪状态 | 释放锁,等待notify()/notifyAll()唤醒 |
使用范围 | 任意代码 | 只能放在同步代码块或同步方法 |
为什么wait要定义在Object上,而不定义在Thread上?
答:Java的锁是对象级别的,我们需要对象锁来实现多线程的互斥效果
为什么wait必须用在同步代码块或同步方法中?
答:因为wait方法需要被唤醒,需要按照先wait、再唤醒的顺序来执行。用同步来保证顺序。
如果写在非同步的地方,其他线程提前调用了notify方法,那么当前线程就无法被唤醒。
ThreadLocal为每个线程创建一个副本,实现在线程的上下文传递对象。
每个线程都有一个对应的Map区域,存放键值对,键为threadLocal,在线程上下文都可获取到。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fQIE09Qj-1620227111535)(C:\Users\Sharm\AppData\Roaming\Typora\typora-user-images\image-20210502171035909.png)]
使用实例:在操作数据库时,service调用两个dao,事务要统一控制。
每一个dao层的方法,使用同一个connection连接,由底层进行connection的传递。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eAicQxJq-1620227111535)(C:\Users\Sharm\AppData\Roaming\Typora\typora-user-images\image-20210502175005881.png)]
答:JVM使用Java类的流程:
答:来源有三种
答:对于不同来源的class,有不同的加载器:
Java核心类:由BootstrapClassLoader加载。这个加载器被称为“根加载器或引导加载器”;
BootstrapClassLoader不继承ClassLoader,是JVM内部实现的。
我们无法通过Class类的getClassLoader获取。
Java扩展类:由ExtClassLoader加载,被称为“扩展类加载器”;
自己开发的类和第三方类库:由AppClassLoader加载,被称为“系统类加载器”;
// BootstrapClassLoader不继承ClassLoader,是JVM内部实现的String string = new String();System.out.println(string.getClass().getClassLoader());// null// 我们自己的Student类Student student = new Student();System.out.println(student.getClass().getClassLoader());// AppClassLoaderSystem.out.println(student.getClass().getClassLoader().getParent());// ExtClassLoaderSystem.out.println(student.getClass().getClassLoader().getParent().getParent());// null
面试场景:
我现在编写一个类,类全名为java.lang.String, 我们这个类能否替换掉JDK的String类?
答:不能。因为类加载的双亲委派机制。双亲是指AppClassLoader还有两个上级。
在加载class时,AppClassLoader委派给ExtClassLoader,ExtClassLoader又委派给BootstrapClassLoader,BootstrapClassLoader找到该类就使用该类,找不到就让委托人ExtClassLoader自己去找。
所以我们new String()时,是由BootstrapClassLoader找到了JDK的String,无法找到我们自定义的String。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7x1VjuWf-1620227111536)(C:\Users\Sharm\AppData\Roaming\Typora\typora-user-images\image-20210502183953634.png)]
异步交互、XMLHttpRequest对象、回调函数
传统模式和Ajax模式对比:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7fMzfBma-1620227111537)(C:\Users\Sharm\AppData\Roaming\Typora\typora-user-images\image-20210502210638895.png)]
JavaScript的原型有一个关键的作用:用来扩展其原有类的特性,从而使框架更易用。
eg:给String类扩展一个hello方法
var str = "abc";String.prototype.hello = function(){ alert("通过原型的方式扩展原有类的方法或属性")}str.hrllo();
JSP本质是一个Servlet,JSP—>翻译—>Servlet—>编译—>class文件
JSP主要是写HTML页面视图,也支持Java代码;Servlet主要写java逻辑代码
jsp翻译成java文件,存储路径tomcat/work/Catilina/localhost/examples/org/apache/jsp/…
Servlet是单例的,只初始化一次,用完销毁。
生命周期:创建对象—>初始化—>service()—>doGet/doPost()—>销毁
1 springmvc org.springframework.web.servlet.DispatcherServlet contextConfigLocation classpath:config/spring-servlet.xml 1 springmvc *.do
需要:因为Servlet是单例的,被多个线程共享,需要考虑安全问题。
session | cookie | |
---|---|---|
存储位置 | 服务端 | 客户端 |
存储格式 | key-value形式,value存对象 | key-value形式,value存字符串 |
存储大小 | 受服务器内存控制 | 一般来说,最大4k |
生命周期 | 服务器控制,默认30分钟 | 客户端控制,分两种情况:1.会话级cookie(默认):随浏览器关闭而消失,例如存储sessionId的cookie;2.非会话级cookie:通过有效期控制的失效时间,例如7天免登录功能;setMaxAge来设置有效期 |
关闭浏览器后,session会消失吗?
答:不会,session由服务器控制,与客户端无关。
为什么会产生session的使用?
答:因为http是无状态的协议,服务器需要有一种机制来存储用户的登录信息,以此来校验用户访问接口的权限。
转发:发生在服务器端内部的跳转,对于用户来说,只有一次请求;
重定向:发生在客户端的跳转,对于用户来说,发了两次请求。
三层架构:控制层、业务层、数据层。
MVC:将控制层分为模型、视图和控制器。
SpringMVC执行流程:
1.springmvc将所有的请求都提交给DispatcherServlet,它会委托应用系统的其他模块负责对请求进行真正的处理工作。
2.DispatcherServlet查询HandlerMapping,找到处理请求的Controller。
3.DispatcherServlet将请求提交到目标Controller。
4.Controller进行业务逻辑处理后,会返回一个ModelAndView。
5.Dispathcher查询ViewResolver视图解析器,找到ModelAndView对象指定的视图对象。
6.将页面渲染到浏览器客户端。
request、response、session、application
page、pageContext、config、out、exception
域 | 作用范围 |
---|---|
HttpServletRequest | 只能在同一次请求中使用 |
HttpSession | 只能在同一个会话使用 |
PageContext | 只能在当前jsp页面使用 |
ServletContext | 只能在同一个web应用使用 |
并发:同一个CPU执行多个任务,按时间片交替执行
并行:在多个CPU,同时处理多个任务
第一范式:列不可分;
第二范式:要有主键;
第三范式:不可存在传递依赖;
举例:商品表和商品类别表。
商品表要关联商品类别,存储一个商品类别ID字段即可;
如果此时存储了一个商品类别Name字段,即存在了传递依赖,造成了冗余,违反了第三范式。
反范式设计:(反第三范式)
上例中,违反了第三范式,正贴合了反范式思想。
反范式是为了提高查询效率,以冗余换时间,将多表关联转为单表查询,提高查询效率。
经典示例:订单表。订单表要存储订单人、订单地址等信息,而不能通过多表关联。
count、sum、max、min、avg
select count(*) from t_student; // 学生总数select sum(age) from t_student; // 年龄总和select max(age) from t_student; // 年龄最大的学生select min(age) from t_student; // 年龄最小的学生select avg(age) from t_student; // 平均年龄
count(*)和count(字段)的效率问题
左连接(left join):以左表为基础,查询右边满足关联条件的信息,查询结果条数=左表记录数
右连接(right join):以右表为基础,查询左边满足关联条件的信息,查询结果条数=右表记录数
内连接(inner join):查询两表满足关联条件的信息,查询结果条数不确定
SQL注入,是指通过字符串拼接的方式,构成了一种特殊的,和预期相悖的查询语句
常用注入字符 #(Mysql)、–(Oracle)
# 原语句select * from t_user where username = ? and password = ?# 用户输入username值为“or 1=1 #”,密码随便输入,即可登录# 拼接完的SQL如下select * from t_user where username = "or 1=1 #" and password = ?相当于select * from t_user where username = "or 1=1
解决方案:
使用预处理PreparedStatement对象,好处有:
MyBatis如何解决注入?
Mybatis在写语句时,使用#占位可解决注入,而?不行
JDBC通过Connection来控制事务,代码如下
try { connection.setAutoCommint(false); // dosomeing connection.commit();} catch(Exception e) { connection.rollback();}
事务的边界要放在业务层,因为业务层可能会调用多个dao,要用一个事物来控制。
原子性:事务中包含的操作要么一起成功,要么一起失败
一致性:数据库中的数据,在事务操作前后,都应该满足业务规则约束。比如A给B转账,转账前后,A和B的账户总金额应该是一致的。
隔离性:一个事务的执行,不能受其他事务的干扰。
持久性:事务一旦提交,结果便是永久性的。即便宕机,依然可以靠事务日志完成数据持久化。
事务日志包括回滚日志和重做日志。
当我们修改数据时,首先会将数据库变化信息记录到重做日志中,然后再对数据库中的数据进行修改。这样即便数据库崩溃,我们也可以通过重做日志进行数据恢复。
Oracle默认隔离级别:读已提交;
MySQL默认隔离级别:可重复读;
场景:夫妻两人在不同的地方用同一张卡在消费,老婆看上了一件衣服3000块,查询余额有4000块钱,于是刷卡结账,与此同时丈夫请客吃饭,花了2000块钱,导致老婆结账时失败。
脏读:
幻读:
不可重复读:查询余额4000,但是当真正结账时发现只剩2000了,重复读的数据不一致,这就叫不可重复读。
synchronized | lock | |
---|---|---|
作用位置 | 作用在代码块、方法 | 作用在代码块 |
获取锁机制 | 不需要手动获取锁,进入修饰的代码块、方法自动获取锁 | 需要手动加锁lock() |
释放锁机制 | 发生异常时会自动解锁 | 需要手动解锁unlock(),否则会死锁,一般写在finally里 |
synchronized加锁对象:
传输层协议
TCP建立连接:三次握手
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1hlXTTW9-1620227111539)(C:\Users\Sharm\AppData\Roaming\Typora\typora-user-images\image-20210503140128851.png)]
TCP断开连接:四次挥手
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-74PXpTKH-1620227111540)(C:\Users\Sharm\AppData\Roaming\Typora\typora-user-images\image-20210503143553230.png)]
线程1锁住了资源a,线程2锁住了资源b,这时线程1需要访问资源b,线程2也需要访问资源a,双方都在等待对方释放锁,从而发生死锁。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wDAjLS8q-1620227111541)(C:\Users\Sharm\AppData\Roaming\Typora\typora-user-images\image-20210503144909567.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nEL8a83s-1620227111542)(C:\Users\Sharm\AppData\Roaming\Typora\typora-user-images\image-20210503144839932.png)]
如何避免死锁?
反射是一种能力,能够在程序运行时,动态的获取当前类的所有属性和所有方法,可以动态执行方法,动态给属性赋值等操作。
举例:@Autowired根据反射自动实现赋值
原理:注解解析程序,扫描指定包下哪些属性加了@Autowired注解,加了注解的就去容器中,根据类型找到相应的实现,进行赋值。
Spring Data和Spring web模块,更方便继承其他主流框架。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XhYi9xui-1620227111542)(C:\Users\Sharm\AppData\Roaming\Typora\typora-user-images\image-20210503162025948.png)]
bean生命周期
默认情况下,bean是单例的,在多线程下如果操作对象,就需要考虑线程安全???
风清扬老师讲:bean是单例的,但是bean无状态,就是没有存储数据,没有通过数据的状态来作为下一步操作的依据,从这点看,是线程安全的。
不懂
事务的边界在service层。
如果一个事务内,调用了另外一个有事务的service方法,此时我们需要把这个事务控制在最外层,就发生了事务的传播。
Spring支持的特性
悲观锁:利用数据库本身的锁机制来实现,会锁记录。
select * from t_table where id = 1 for update
举例:synchronized
乐观锁:不锁记录,采用CAS(compare and swap)模式,采用version字段作为判断依据。
version字段可以+1,也可采用时间戳
select *,version from t_table where id = 1得到version = 1,以此查询结果作为更新的限定条件,更新时更新version字段update t_table set version = version + 1 where id = 1 and version = 1
为什么不采用其他字段呢?
首先,我们不能使用业务字段,业务字段有可能出现ABA的情况
缓存,主要作用是为了提高查询效率,减少和数据库的交互,减轻数据库的压力。
适用于读多写少的场景。
默认开启一级缓存。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cFuVQXDk-1620227111544)(C:\Users\Sharm\AppData\Roaming\Typora\typora-user-images\image-20210503171126975.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6K87pF8B-1620227111544)(C:\Users\Sharm\AppData\Roaming\Typora\typora-user-images\image-20210503171215721.png)]
一级缓存总结
二级缓存:默认关闭,图有问题
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7x3w87Rr-1620227111545)(C:\Users\Sharm\AppData\Roaming\Typora\typora-user-images\image-20210503171950193.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-77VG0HQX-1620227111546)(C:\Users\Sharm\AppData\Roaming\Typora\typora-user-images\image-20210503172054551.png)]
代码验证:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Qlf4uQOq-1620227111547)(C:\Users\Sharm\AppData\Roaming\Typora\typora-user-images\image-20210503172257444.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1P0LhJQB-1620227111547)(C:\Users\Sharm\AppData\Roaming\Typora\typora-user-images\image-20210503172318960.png)]
发现sqlsession关闭后,才会将结果放进二级缓存。
分为物理分页和逻辑分页。
分页插件能实现的原理:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oouHLsXe-1620227111548)(C:\Users\Sharm\AppData\Roaming\Typora\typora-user-images\image-20210503180413159.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ePm07qwc-1620227111549)(C:\Users\Sharm\AppData\Roaming\Typora\typora-user-images\image-20210503180251904.png)]
通过两个指令实现。
JDK1.6之前:
JDK1.6之后:提供三种锁,偏向锁、轻量级锁和重量级锁。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P7mBOFna-1620227111549)(C:\Users\Sharm\AppData\Roaming\Typora\typora-user-images\image-20210503182019858.png)]
可见性指多个线程对共享变量要可见。
线程工作时,分为本地内存和主内存。获取到锁之后,都从主内存读;当释放锁后,将本地内存的值写到主内存。下一个线程就能从主内存拿到最新的值,从而实现可见性。
synchronized | volatile轻量级线程同步机制 | |
---|---|---|
作用位置 | 代码块、方法 | 变量 |
作用 | 可以保证变量修改的可见性和原子性,可能会造成线程阻塞。 | 可以保证变量修改的可见性,但无法保证原子性,不会造成线程阻塞 |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rMCEQcka-1620227111550)(C:\Users\Sharm\AppData\Roaming\Typora\typora-user-images\image-20210503183536238.png)]
根据业务边界拆分,一般分为公共服务和业务服务。
例如:短信服务、邮件服务等
示例代码:重点@SpringBootApplication,SpringApplication.run()
@SpringBootApplicationpublic class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); }}
每个SpringBoot程序都有一个主入口,也就是main方法,main里面调用SpringApplication.run()启动整个spring-boot程序,该方法所在类需要使用@SpringBootApplication注解。
@SpringBootApplication = (默认属性)@Configuration + @EnableAutoConfiguration + @ComponentScan。
@ComponentScan:自动扫描并加载符合条件的组件(比如@Component和@Repository等)或者bean定义,最终将这些bean定义加载到IoC容器中。
我们可以通过basePackages等属性来细粒度的定制@ComponentScan自动扫描的范围,如果不指定,则默认Spring框架实现会从声明@ComponentScan所在类的package进行扫描。
所以SpringBoot的启动类最好是放在root package下,因为默认不指定basePackages。
但是一般满足不了我们的需求,还是需要自己指定扫描包路径
@EnableAutoConfiguration:开启自动配置。他实现的关键在于引入了AutoConfigurationImportSelector,其核心逻辑为selectImports方法,逻辑大致如下:
在spring.factories配置文件下,预先写好了WebMvcAutoConfiguration类,
Servlet3.0以后提供了SPI技术
规范:需要在项目根目录下WETA-INF/services文件夹下提供一个配置文件,配置文件名为javax.servlet.ServletContainerInitializer,在配置文件中指定实现了ServletContainerInitializer接口的类路径。
我们用的Spring框架,在spring-web包下已经实现了该规范,如下图所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RYnU1E7Y-1620227111550)(C:\Users\Sharm\AppData\Roaming\Typora\typora-user-images\image-20210505213012350.png)]
SpringServletContainerInitializer.java使用了@HandlesTypes({WebApplicationInitializer.class}),意味着对规范进行了扩展,提供了一个WebApplicationInitializer接口。启动时会自动将WebApplicationInitializer的实现类扫描成一个list,遍历执行各个类的onStartup方法。
后续我们想写的容器启动就加载的类,实现WebApplicationInitializer接口重写onStartup方法即可。
//// Source code recreated from a .class file by IntelliJ IDEA// (powered by Fernflower decompiler)//package org.springframework.web;import javax.servlet.ServletContext;import javax.servlet.ServletException;public interface WebApplicationInitializer { void onStartup(ServletContext var1) throws ServletException;}
转载地址:http://izqgn.baihongyu.com/