2021Java面试一

1、String反转字符

reverse()方法
通过String类的charAt()的方法来获取字符串中的每一个字符,然后将其拼接为一个新的字符串
String reverse = “”;
for (int i = 0; i < length; i++)
reverse = s.charAt(i) + reverse;
return reverse;
通过String的toCharArray()方法可以将字符串转换为字符数组,然后用一个空的字符串从后向前一个个的拼接成新的字符串。

2、String常用方法

length():求字符串的长度

indexOf():求某个字符在字符串中的位置

charAt():求一个字符串中某个位置的值

equals():比较两个字符串是否相同

replace():将字符串中的某些字符用别的字符替换掉。形如replace(“abc”,”ddd”);字符串中的abc将会被ddd替换掉。

split():根据给定正则表达式的匹配拆分此字符串。形如 String s = “The time is going quickly!”; str1=s.split(" ");

substring():输出一个新的字符串,它是此字符串中的子串,形如substring(3,7);它将字符串中的第四个第五个第六个输出。

trim():将字符串开头的空白(空格)和尾部的空白去掉。

format():使用指定的语言环境、格式字符串和参数返回一个格式化字符串。

toLowerCase():将字符串中所有的大写改变成小写

toUpperCase():将字符串中所有的小写改变为大写

3、ArrayList与LinkedList的区别

ArrayList是Array(动态数组)的数据结构,LinkedList是Link(链表)的数据结构
当随机访问List(get和set操作)时,ArrayList比LinkedList的效率更高,因为LinkedList是线性的数据存储方式,所以需要移动指针从前往后依次查找。

当对数据进行增加和删除的操作(add和remove操作)时,LinkedList比ArrayList的效率更高,因为ArrayList是数组,所以在其中进行增删操作时,会对操作点之后所有数据的下标索引造成影响,需要进行数据的移动。

ArrayList自由性较低,因为它需要手动的设置固定大小的容量,但是它的使用比较方便,只需要创建,然后添加数据,通过调用下标进行使用;而LinkedList自由性较高,能够动态的随数据量的变化而变化,但是它不便于使用

4、SpringCloud六大组件

eureka - 服务的注册与发现.

Feign + Ribbon. - 服务的调用和负载均衡.

Hystrix - 服务的降级,服务的熔断.

config - 分布式配置管理.(通过git管理)

zuul - 网关. (filter)

Sleuth - 服务追踪

5、SpringBoot常用的注解

链接: SpringBoot常用注解.

1)@SpringBootApplication

包含@Configuration、@EnableAutoConfiguration、@ComponentScan

通常用在主类上。

2)@Repository

用于标注数据访问组件,即DAO组件。

3)@Service

用于标注业务层组件。

4)@RestController

用于标注控制层组件(如struts中的action),包含@Controller和@ResponseBody

5)@ResponseBody

表示该方法的返回结果直接写入HTTP response body中

一般在异步获取数据时使用,在使用@RequestMapping后,返回值通常解析为跳转路径,加上@responsebody后返回结果不会被解析

为跳转路径,而是直接写入HTTP response body中。比如异步获取json数据,加上@responsebody后,会直接返回json数据。

6)@Component

泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注。

7)@ComponentScan

组件扫描。相当于,如果扫描到有@Component @Controller @Service等这些注解的类,则把

这些类注册为bean。

8)@Configuration

指出该类是 Bean 配置的信息源,相当于XML中的,一般加在主类上。

9)@Bean

相当于XML中的,放在方法的上面,而不是类,意思是产生一个bean,并交给spring管理。

10)@EnableAutoConfiguration

让 Spring Boot 根据应用所声明的依赖来对 Spring 框架进行自动配置,一般加在主类上。

11)@AutoWired

byType方式。把配置好的Bean拿来用,完成属性、方法的组装,它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。
当加上(required=false)时,就算找不到bean也不报错。

12)@Qualifier

当有多个同一类型的Bean时,可以用@Qualifier(“name”)来指定。与@Autowired配合使用

13)@Resource(name=“name”,type=“type”)

没有括号内内容的话,默认byName。与@Autowired干类似的事。

14)@RequestMapping

RequestMapping是一个用来处理请求地址映射的注解,可用于类或方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。

6、Redis存储key的类型

string hash list set zset

7、造成死锁的原因

若干进程因竞争资源而无休止地等待其他进程释放已占有的资源。
链接: 死锁产生的条件与原因.

8、如何避免死锁

1)避免多次锁定。尽量避免同一个线程对多个 Lock 进行锁定。例如上面的死锁程序,主线程要对 A、B 两个对象的 Lock 进行锁定,副线程也要对 A、B 两个对象的 Lock 进行锁定,这就埋下了导致死锁的隐患。
2)具有相同的加锁顺序。如果多个线程需要对多个 Lock 进行锁定,则应该保证它们以相同的顺序请求加锁。比如上面的死锁程序,主线程先对 A 对象的 Lock 加锁,再对 B 对象的 Lock 加锁;而副线程则先对 B 对象的 Lock 加锁,再对 A 对象的 Lock 加锁。这种加锁顺序很容易形成嵌套锁定,进而导致死锁。如果让主线程、副线程按照相同的顺序加锁,就可以避免这个问题。
3)使用定时锁。程序在调用 acquire() 方法加锁时可指定 timeout 参数,该参数指定超过 timeout 秒后会自动释放对 Lock 的锁定,这样就可以解开死锁了。
4)死锁检测。死锁检测是一种依靠算法机制来实现的死锁预防机制,它主要是针对那些不可能实现按序加锁,也不能使用定时锁的场景的。

9、SpringBoot搭建过程

链接: SpringBoot完整的搭建过程.

10、MySQL游标

游标(Cursor)它使用户可逐行访问由SQL Server返回的结果集。 
使用游标(cursor)的一个主要的原因就是把集合操作转换成单个记录处理方式。
1.声明游标
DECLARE cursor_name CURSOR FOR select_statement
这个语句声明一个游标。也可以在子程序中定义多个游标,但是一个块中的每一个游标必须有唯一的名字。声明游标后也是单条操作的,但是不能用SELECT语句不能有INTO子句。

  1. 游标OPEN语句
    OPEN cursor_name 这个语句打开先前声明的游标。

  2. 游标FETCH语句
    FETCH cursor_name INTO var_name [, var_name] …这个语句用指定的打开游标读取下一行(如果有下一行的话),并且前进游标指针。

  3. 游标CLOSE语句
    CLOSE cursor_name 这个语句关闭先前打开的游标。

11、MySQL存储过程for循环

BEGIN

    DECLARE startDate VARCHAR(20);  
    DECLARE endDate VARCHAR(20);
     declare i int;
     declare j int;
     set i=0;
     set j=0;
        #定义开始时间
    set startDate = '20180829';
    #定义结束时间
    set endDate = "20180831";
   
 -- 第一种,while循环
     while i < 3 do 
            select i;
--          i+1
            set i = i +1;
    end while;

    -- 第二种,repeat循环
    REPEAT
            select j;
            set j = j +1;
    UNTIL j > 3 

END REPEAT;

-- 第三种,loop循环
    test_loop: LOOP
    select startDate;
#开始时间加一天
    set startDate = DATE_FORMAT(date_add(startDate,interval 1 day),"%Y%m%d");
    IF startDate>endDate THEN
        LEAVE test_loop; 
    END IF; 
END LOOP test_loop;
END

12、去除ArrayList中重复的数值

可以用HashSet
可以用另外一个list来存
可以用list迭代器来双重循环,重复的就remove

13、vue-router与location.href的用法区别

①vue-router使用pushState进行路由更新,静态跳转,页面不会重新加载;location.href会触发浏览器,页面重新加载一次

②vue-router使用diff算法,实现按需加载,减少dom操作

③vue-router是路由跳转或同一个页面跳转;location.href是不同页面间跳转;

④vue-router是异步加载this.$nextTick(()=>{获取url});location.href是同步加载

14、vue的路由跳转方式

$route对象:

$route对象表示当前的路由信息,包含了当前 URL 解析得到的信息。包含当前的路径,参数,query对象等。

$router对象:

$router对象是全局路由的实例,是router构造方法的实例。

15、MySQL临时表

临时表分为外部临时表、内部临时表
外部临时表
通过CREATE TEMPORARY TABLE 创建的临时表,这种临时表称为外部临时表。这种临时表只对当前用户可见,当前会话结束的时候,该临时表会自动关闭。这种临时表的命名与非临时表可以同名(同名后非临时表将对当前会话不可见,直到临时表被删除)。
内部临时表
内部临时表是一种特殊轻量级的临时表,用来进行性能优化。这种临时表会被MySQL自动创建并用来存储某些操作的中间结果。这些操作可能包括在优化阶段或者执行阶段。这种内部表对用户来说是不可见的,但是通过EXPLAIN或者SHOW STATUS可以查看MYSQL是否使用了内部临时表用来帮助完成某个操作。内部临时表在SQL语句的优化过程中扮演着非常重要的角色, MySQL中的很多操作都要依赖于内部临时表来进行优化。但是使用内部临时表需要创建表以及中间数据的存取代价,所以用户在写SQL语句的时候应该尽量的去避免使用临时表。
内部临时表有两种类型:一种是HEAP临时表,这种临时表的所有数据都会存在内存中,对于这种表的操作不需要IO操作。另一种是OnDisk临时表,顾名思义,这种临时表会将数据存储在磁盘上。OnDisk临时表用来处理中间结果比较大的操作。如果HEAP临时表存储的数据大于MAX_HEAP_TABLE_SIZE(详情请参考MySQL手册中系统变量部分),HEAP临时表将会被自动转换成OnDisk临时表。OnDisk临时表在5.7中可以通过INTERNAL_TMP_DISK_STORAGE_ENGINE系统变量选择使用MyISAM引擎或者InnoDB引擎。

临时表的存储引擎:memor,myisam,merge,innodb,临时表不支持mysql cluster簇

16、MySQL函数

max/min/left/DateTime
链接: MySQL常用函数.

17、设计模式(工厂模式),观察者模式的代码实现

工厂模式、观察者模式、装饰模式、责任链模式

链接: 工厂模式与观察者模式.
链接: 工厂模式、观察者模式、装饰器模式.

18、jvm调优参数

链接: 深入理解jvm和jvm基本调优参数.

20、AOP应用

AOP是面向切面编程,将程序执行流程想象成一个柱形,那么面向切面就是在柱子的中间(横切面)插入一个功能,比如日志功能、过滤器
链接: AOP实现添加日志功能.

21、数据库连接原理

①装载数据库驱动程序;

②通过jdbc建立数据库连接;

③访问数据库,执行sql语句;

④断开数据库连接。
  
链接: 数据库连接池的原理.

22、String编码转换

ResultSet rs;
bytep[] bytes = rs.getBytes();
String str = new String(bytes, “gb2312”);

23、xml中体现一对多关系

collection

<association property="deviceType" javaType="com.model.iot.DeviceTypeDto" columnPrefix="type_">
	<id property="id" column="id"/>
	<result property="name" column="name"/>
	<!-- 嵌套属性一对多的关系 -->
	<collection property="typeExts" ofType="com.model.iot.DeviceTypeExtDto" columnPrefix="ext_">
		<id property="id" column="id"/>
		<result property="attribute" column="attribute"/>
		<result property="dataType" column="dataType"/>
		<result property="unit" column="unit"/>
	</collection>
</association>

24、Java中修饰接口的关键字

public abstract Interface implements

25、xml中所有的方法是否能重名

不同的namespace方法下可以重名

26、IOC注入方法

注解注入
setter注入(在setter注入中可以使用@Required注解让属性成为必须的依赖项)
构造方法注入

27、AOP两种代理方法

java代理:采用java内置的代理API实现
cglib代理:采用第三方API实现

28、插入排序(升序)

两两比较,小的在前

29、left左连接,如果右表有重复数据

则显示数量同右表

30、callablel与runnable的区别

Callable要实现call方法,Runnable要实现run方法
call方法可以返回值,run方法不能
call方法可以抛出checked exception,run方法不能
Runnable接口在jdk1.1就有了,Callable在Jdk1.5才有

31、订单金额要用什么类型来存储

BigDecimal

32、SpringBoot启动类注解

@SpringBootApplication
1)@SpringBootConfiguration 标注当前类是Java Cofig配置类,会被扫描并加在到IOC容器
2)@ComponentScan 扫描默认包和指定包下面的符合条件的组件并加在,如@Component 、@Controller
3)@EnableAutoConfiguration 从classpath中搜寻所有的META-INF/spring.factories配置文件,并将其中org.springframework.boot.autoconfigure.EnableAutoConfiguration对应的配置项通过反射(JavaRefletion)实例化为对应的标注了@Configuration的JavaConfig形式的IOC容器配置类,然后汇总为一个,并加载到IOC容器中
4)@AutoConfigurationPackages注册当前主程序类的同级以及子级的包中的符合条件(@Configuration)的Bean的定义
5)@Import(AutoConfigurationImportSelector.class) 扫描各个组件jarMETA-INF目录下的“spring.factories”文件,将下面的“包名.类名”中的工厂类全部进行加载到IOC容器中
比较重要的有三个注解:
1)@SpringBootConfiguration // 继承了Configuration,表示当前是注解类
2)@EnableAutoConfiguration // 开启springboot的注解功能,springboot的四大神器之一,其借助@Import的帮助
@ComponentScan(excludeFilters = { // 扫描路径设置})

33、SpringBoot配置文件注解

@Configuration

34、mysql慢查询优化

链接: MySQL慢查询优化.

35、Java中的异常怎么处理

throw或者throws抛出异常
try/catch捕捉异常

36、MySQL存储引擎以及之间的区别

InnoDB、MyISAM、Memory
MySQL数据库索引的区别

37、Spring中通过ApplicationContext getBean获取注入对象

链接: Spring中通过ApplicationContext getBean获取注入对象.

38、spring中@Autowrite注解和@Resource的区别

@Resource的作用相当于@Autowired,只不过@Autowired按byType自动注入,而@Resource默认按 byName自动注入罢了。@Resource有两个属性是比较重要的,分是name和type,Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。所以如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。如果既不指定name也不指定type属性,这时将通过反射机制使用byName自动注入策略。
  @Resource装配顺序
  1. 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常
  2. 如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常
  3. 如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常
  4. 如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配;
  @Autowired 与@Resource的区别:

1) @Autowired与@Resource都可以用来装配bean. 都可以写在字段上,或写在setter方法上。

2) @Autowired默认按类型装配(这个注解是属业spring的),默认情况下必须要求依赖对象必须存在,如果要允许null值,可以设置它的required属性为false,如:@Autowired(required=false) ,如果我们想使用名称装配可以结合@Qualifier注解进行使用,如下:
@Autowired () @Qualifier ( “baseDao” )
private BaseDao baseDao;

3)@Resource(这个注解属于J2EE的),默认安装名称进行装配,名称可以通过name属性进行指定,如果没有指定name属性,当注解写在字段上时,默认取字段名进行安装名称查找,如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。
@Resource (name= “baseDao” )
private BaseDao baseDao;

推荐使用:@Resource注解在字段上,这样就不用写setter方法了,并且这个注解是属于J2EE的,减少了与spring的耦合。这样代码看起就比较优雅。

39、Java中常见的异常

1)ArrayIndexOutOfBoundsException 非检查型异常,编译器在编译时不进行检查,而在运行的时候检查
2)检查型异常(eg:IoException)
编辑器在编译时检查

40、throws和throw的区别

1)throws(用于方法上)代表这个方法可能会抛出异常,如果抛出多个异常,中间可以用逗号隔开
2)throw(用于语句上)代表这里的语句一定需要抛出异常

41、ThreadLocal

threadlocal而是一个线程内部的存储类,可以在指定线程内存储数据,数据存储以后,只有指定线程可以得到存储数据。
ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
使用场景:
1)在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
2)线程间数据隔离
3)进行事务操作,用于存储线程事务信息。
4)数据库连接,Session会话管理。

42、三个线程如何保证其顺序性

一个共享变量。这个共享变量可以是一个简单的静态变量,可以是一个Semaphore等

线程的wait和notify
如果对底层了解一点,我个人理解性能最高的方式其实是直接使用LockSupport.park()

链接: 三个线程循环输出保证其顺序性.

43、单例线程的几种方式

饿汉式:
在类加载的时候就初始化创建单例对象,线程安全,但存在的问题就是无论使用与否都创建对象,造成内存浪费

代码实现:

/*
    饿汉式
 */
public class HungrySingleton {
    private  HungrySingleton(){}
    private  static  HungrySingleton instance = new HungrySingleton();
    public static HungrySingleton getInstance(){
        return  instance;
 
    }
 
    public static void main(String[] args) {
        HungrySingleton obj1 =  HungrySingleton.getInstance();
        HungrySingleton obj2 = HungrySingleton.getInstance();
        System.out.println(obj1 == obj2);
    }
}


懒汉式:
在外部调用时才会去加载,线程不安全,可以通过线程加锁来保证安全,但效率较低

代码实现:

/**
 * 懒汉式
 */
public class LazySingleton {
    private  LazySingleton(){}
    private static LazySingleton instance;
    public  static LazySingleton getInstance(){
        if (instance == null){
            instance = new LazySingleton();
        }
        return instance;
    }
 
    public static void main(String[] args) {
        LazySingleton obj1 = LazySingleton.getInstance();
        LazySingleton obj2 = LazySingleton.getInstance();
        System.out.println(obj1 == obj2);
    }
}


双重校验锁:
使用volatile以及多重检查,来减小锁范围,提升效率。

/**
 * 双重校验锁
 */
public class DoubleCheckSingleton {
    private DoubleCheckSingleton(){}
    private volatile static  DoubleCheckSingleton instance;
    public static DoubleCheckSingleton getInstance(){
        synchronized (DoubleCheckSingleton.class){
            if (instance == null){
                instance =new DoubleCheckSingleton();
            }
        }
 
        return instance;
    }
 
    public static void main(String[] args) {
        DoubleCheckSingleton obj1 = DoubleCheckSingleton.getInstance();
        DoubleCheckSingleton obj2 = DoubleCheckSingleton.getInstance();
        System.out.println(obj1 == obj2);
    }
}
静态内部类:
解决饿汉式(内存浪费)和懒汉式(线程不安全)的问题。

代码实现:

/**
 * 静态内部类
 */
public class StaticSingleton {
    private StaticSingleton(){}
    public static  StaticSingleton getInstance(){
 
        return StaticClass.instance;
    }
    private static class StaticClass{
 
        private  static  final StaticSingleton instance = new StaticSingleton();
    }
 
    public static void main(String[] args) {
        StaticSingleton obj1 = StaticSingleton.getInstance();
        StaticSingleton obj2 = StaticSingleton.getInstance();
        System.out.println(obj1 == obj2);
    }
 
}
 

44、SpringBoot启动原理

SpringBoot启动过程分析,首先打开SpringBoot的启用入口Main类:

@SpringBootApplication
public class ApplicationMain{
    public static void main(String[] args) {
        SpringApplication.run(ApplicationMain.class, args);
    }
}

可以看到main方法里面只有一行核心启用类:SpringApplication.run(ApplicationMain.class, args);这个是关键,在改行打上断点,debug模式启动该main类。点击下一步进入SpringApplication的源码对应的run方法:

public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
        return (new SpringApplication(sources)).run(args);
    }

初始化SpringApplication
SpringApplication实例化之前会调用构造方法进行初始化:

public SpringApplication(Object... sources) {
        this.bannerMode = Mode.CONSOLE;
        this.logStartupInfo = true;
        this.addCommandLineProperties = true;
        this.headless = true;
        this.registerShutdownHook = true;
        this.additionalProfiles = new HashSet();
        this.initialize(sources);
    }

而SpringApplication构造方法的核心是:this.initialize(sources);初始化方法,SpringApplication通过调用该方法来初始化。

private void initialize(Object[] sources) {
    if (sources != null && sources.length > 0) {
        this.sources.addAll(Arrays.asList(sources));
    }
    this.webEnvironment = deduceWebEnvironment();
    setInitializers((Collection) getSpringFactoriesInstances(
            ApplicationContextInitializer.class));
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = deduceMainApplicationClass();
}

1.deduceWebEnvironment方法是用来判断当前应用的环境,该方法通过获取这两个类来判断当前环境是否是web环境,如果能获得这两个类说明是web环境,否则不是。

javax.servlet.Servlet
org.springframework.web.context.ConfigurableWebApplicationContext

2.getSpringFactoriesInstances方法主要用来从spring.factories文件中找出key为ApplicationContextInitializer的类并实例化,然后调用setInitializers方法设置到SpringApplication的initializers属性中。这个过程就是找出所有的应用程序初始化器。

private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        Set<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        List<T> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
        AnnotationAwareOrderComparator.sort(instances);
        return instances;
    }


public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
        String factoryClassName = factoryClass.getName();

        try {
           //从spring.factories文件中找出key为ApplicationContextInitializer的类
            Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
            ArrayList result = new ArrayList();

            while(urls.hasMoreElements()) {
                URL url = (URL)urls.nextElement();
                Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
                String factoryClassNames = properties.getProperty(factoryClassName);
                result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
            }

            return result;
        } catch (IOException var8) {
            throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() + "] factories from location [" + "META-INF/spring.factories" + "]", var8);
        }
    }

当前的初始化器有如下几个:

3.同理调用getSpringFactoriesInstances从spring.factories文件中找出key为ApplicationListener的类并实例化,然后调用setListeners方法设置到SpringApplication的listeners属性中。这个过程就是找出所有的应用程序事件监听器。
当前的事件监听器有如下几个:

4.调用deduceMainApplicationClass方法找出main类,就是这里的ApplicationMain类。

运行SpringApplication
初始化SpringApplication完成之后,调用run方法运行:

public ConfigurableApplicationContext run(String... args) {
        //计时器,统计任务的执行时间
        StopWatch stopWatch = new StopWatch();
        //开始执行
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        FailureAnalyzers analyzers = null;
        this.configureHeadlessProperty();
        // 获取SpringApplicationRunListeners启动事件监听器,这里只有一个EventPublishingRunListener
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        // 封装成SpringApplicationEvent事件然后广播出去给SpringApplication中的listeners所监听
        listeners.starting();

        try {
            // 构造一个应用程序参数持有类
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            // 准备并配置环境
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
             // 打印banner图形
            Banner printedBanner = this.printBanner(environment);
            // 创建Spring容器
            context = this.createApplicationContext();
            new FailureAnalyzers(context);
            // 配置Spring容器
            this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            // 容器上下文刷新
            this.refreshContext(context);
            // 容器创建完成之后调用afterRefresh方法
            this.afterRefresh(context, applicationArguments);
            // 调用监听器,广播Spring启动结束的事件
            listeners.finished(context, (Throwable)null);
            // 停止计时器
            stopWatch.stop();
            if (this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }

            return context;
        } catch (Throwable var9) {
            this.handleRunFailure(context, listeners, (FailureAnalyzers)analyzers, var9);
            throw new IllegalStateException(var9);
        }
    }

SpringApplicationRunListeners
1.获取启动事件监听器,可以看看该方法:

SpringApplicationRunListeners listeners = this.getRunListeners(args);

 private SpringApplicationRunListeners getRunListeners(String[] args) {
        Class<?>[] types = new Class[]{SpringApplication.class, String[].class};
        return new SpringApplicationRunListeners(logger, this.getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
    }

同样的通过调用getSpringFactoriesInstances方法去META-INF/spring.factories文件中拿到SpringApplicationRunListener监听器,当前的SpringApplicationRunListener事件监听器只有一个EventPublishingRunListener广播事件监听器:

SpringApplicationRunListeners内部持有SpringApplicationRunListener集合和1个Log日志类。用于SpringApplicationRunListener监听器的批量执行。

SpringApplicationRunListener用于监听SpringApplication的run方法的执行,它定义了5个步骤:

1)、starting:run方法执行的时候立马执行,对应的事件类型是ApplicationStartedEvent
2)、environmentPrepared:ApplicationContext创建之前并且环境信息准备好的时候调用,对应的事件类型是ApplicationEnvironmentPreparedEvent
3)、contextPrepared:ApplicationContext创建好并且在source加载之前调用一次,没有具体的对应事件
4)、contextLoaded:ApplicationContext创建并加载之后并在refresh之前调用,对应的事件类型是ApplicationPreparedEvent
5)、finished:run方法结束之前调用,对应事件的类型是ApplicationReadyEvent或ApplicationFailedEvent

SpringApplicationRunListener目前只有一个实现类EventPublishingRunListener,详见获取SpringApplicationRunListeners。
它把监听的过程封装成了SpringApplicationEvent事件并让内部属性ApplicationEventMulticaster接口的实现类SimpleApplicationEventMulticaster广播出去,广播出去的事件对象会被SpringApplication中的listeners属性进行处理。

所以说SpringApplicationRunListener和ApplicationListener之间的关系是通过ApplicationEventMulticaster广播出去的SpringApplicationEvent所联系起来的

2.启动事件监听器
通过listeners.starting()可以启动事件监听器SpringApplicationRunListener ,SpringApplicationRunListener 是一个启动事件监听器接口:

public interface SpringApplicationRunListener {
    void starting();

    void environmentPrepared(ConfigurableEnvironment var1);

    void contextPrepared(ConfigurableApplicationContext var1);

    void contextLoaded(ConfigurableApplicationContext var1);

    void finished(ConfigurableApplicationContext var1, Throwable var2);
}



SpringApplicationRunListener 接口的具体实现就是EventPublishingRunListener类,我们主要来看一下它的startting方法,该方法会封装成SpringApplicationEvent事件然后广播出去给SpringApplication中的listeners所监听。

 public void starting() {
        this.initialMulticaster.multicastEvent(new ApplicationStartedEvent(this.application, this.args));
    }


配置并准备环境
private ConfigurableEnvironment prepareEnvironment(
        SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments) {
    // 创建应用程序的环境信息。如果是web程序,创建StandardServletEnvironment;否则,创建StandardEnvironment
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    // 配置环境信息。比如profile,命令行参数
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    // 广播出ApplicationEnvironmentPreparedEvent事件给相应的监听器执行
    listeners.environmentPrepared(environment);
    // 环境信息的校对
    if (!this.webEnvironment) {
        environment = new EnvironmentConverter(getClassLoader())
                .convertToStandardEnvironmentIfNecessary(environment);
    }
    return environment;
}


判断环境,如果是web程序,创建StandardServletEnvironment;否则,创建StandardEnvironment。

private ConfigurableEnvironment getOrCreateEnvironment() {
        if (this.environment != null) {
            return this.environment;
        } else {
            return (ConfigurableEnvironment)(this.webEnvironment ? new StandardServletEnvironment() : new StandardEnvironment());
        }
    }

创建Spring容器上下文
protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
        try {
            // 判断是否是web应用,
            // 如果是则创建AnnotationConfigEmbeddedWebApplicationContext,否则创建AnnotationConfigApplicationContext
            contextClass = Class.forName(this.webEnvironment
                    ? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
        }
        catch (ClassNotFoundException ex) {
            throw new IllegalStateException(
                    "Unable create a default ApplicationContext, "
                            + "please specify an ApplicationContextClass",
                    ex);
        }
    }
    return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
}

配置Spring容器上下文
private void prepareContext(ConfigurableApplicationContext context,
        ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments, Banner printedBanner) {
    // 设置Spring容器上下文的环境信息
    context.setEnvironment(environment);
    // Spring容器创建之后做一些额外的事
    postProcessApplicationContext(context);
    // SpringApplication的初始化器开始工作
    applyInitializers(context);
    // 遍历调用SpringApplicationRunListener的contextPrepared方法。目前只是将这个事件广播器注册到Spring容器中
    listeners.contextPrepared(context);
    if (this.logStartupInfo) {
        logStartupInfo(context.getParent() == null);
        logStartupProfileInfo(context);
    }

    // 把应用程序参数持有类注册到Spring容器中,并且是一个单例
    context.getBeanFactory().registerSingleton("springApplicationArguments",
            applicationArguments);
    if (printedBanner != null) {
        context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
    }

    // 加载sources,sources是main方法所在的类
    Set<Object> sources = getSources();
    Assert.notEmpty(sources, "Sources must not be empty");
    // 将sources加载到应用上下文中。最终调用的是AnnotatedBeanDefinitionReader.registerBean方法
    load(context, sources.toArray(new Object[sources.size()]));
    // 广播出ApplicationPreparedEvent事件给相应的监听器执行
    // 执行EventPublishingRunListener.contextLoaded方法
    listeners.contextLoaded(context);
}

Spring容器创建之后回调方法postProcessApplicationContext
protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
    // 如果SpringApplication设置了实例命名生成器,则注册到Spring容器中
    if (this.beanNameGenerator != null) {
        context.getBeanFactory().registerSingleton(
                AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,
                this.beanNameGenerator);
    }
    // 如果SpringApplication设置了资源加载器,设置到Spring容器中
    if (this.resourceLoader != null) {
        if (context instanceof GenericApplicationContext) {
            ((GenericApplicationContext) context)
                    .setResourceLoader(this.resourceLoader);
        }
        if (context instanceof DefaultResourceLoader) {
            ((DefaultResourceLoader) context)
                    .setClassLoader(this.resourceLoader.getClassLoader());
        }
    }
}

初始化器开始工作
protected void applyInitializers(ConfigurableApplicationContext context) {
    // 遍历每个初始化器,调用对应的initialize方法
    for (ApplicationContextInitializer initializer : getInitializers()) {
        Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
                initializer.getClass(), ApplicationContextInitializer.class);
        Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
        initializer.initialize(context);
    }
}

Spring容器创建完成之后会调用afterRefresh方法
ApplicationRunner、CommandLineRunner类都是在在afterRefresh方法中调用的,也就是说在Spring容器创建之后执行的。

protected void afterRefresh(ConfigurableApplicationContext context,
        ApplicationArguments args) {
    callRunners(context, args);
}

private void callRunners(ApplicationContext context, ApplicationArguments args) {
    List<Object> runners = new ArrayList<Object>();
    // 找出Spring容器中ApplicationRunner接口的实现类
    runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
    // 找出Spring容器中CommandLineRunner接口的实现类
    runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
    // 对runners进行排序
    AnnotationAwareOrderComparator.sort(runners);
    // 遍历runners依次执行
    for (Object runner : new LinkedHashSet<Object>(runners)) {
        if (runner instanceof ApplicationRunner) {
            callRunner((ApplicationRunner) runner, args);
        }
        if (runner instanceof CommandLineRunner) {
            callRunner((CommandLineRunner) runner, args);
        }
    }
}

参照:https://blog.csdn.net/chengbinbbs/article/details/88557162

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值