面试官:说说 Springboot 中的 javaConfig(基于Spring5.2)

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: 在Spring3.0之前,我们的bean一直通过XML文件来配置的,后来在Spring3.0之后为我们提供了java的config版本。而且在Spring4.0之后推荐我们使用,这篇文章基于Spring5.2来分析。希望在平时使用和面试的时候对你有一丝帮助。

一、实例演示


我们先给出一个例子,看看如何使用,然后再来分析。创建一个普通的maven项目。


第一步:添加pom依赖


<dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
             <version>5.2.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>5.2.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.3.2</version>
            <scope>test</scope>
        </dependency>
</dependencies>

这里面添加了最核心的依赖。


第二步:在bean包添加User类


public class User {
    private Integer id;
    private String name;
    private String password;
    public User() {
    }
    public User(Integer id, String name, String password) {
        this.id = id;
        this.name = name;
        this.password = password;
    }
    //setter和getter方法
    //toString方法
}

第三步:在service包添加UserService类

public class UserService {
    public List<User> queryAll(){
        //创建一个新的list集合,用来承接数据,充当返回值
        List<User> users=new ArrayList<User>();
        //添加数据name,pass
        for (int i=0;i<10;i++){
            User user = new User();
            user.setId(i);
            user.setName("name==>"+i);
            user.setPassword("pass==>"+i);
            users.add(user);
        }
        return users;
    }
}

注意这里没有@Service注解。目的就是我们自己注入。


第四步:在config包添加UserConfig类


@Configuration
public class UserConfig {
    @Bean
    public UserService userService(){
        return new UserService();
    }
}

这个类是核心,我们使用了俩注解,一个Configuration还有一个Bean。我们一会就看他俩的作用。


第五步:测试


之前我们已经添加了测试依赖。直接测试一波:

public class MyConfigTest {
    @Test
    public  void test(){
        //获取java配置类
        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(UserConfig.class);
        //获取ioc容器中的对象
        UserService userService = (UserService) context.getBean("userService");
        //调用方法
        List<User> query = userService.queryAll();
        for (User user:query) {
            System.out.println(user);
        }
    }
}
User{id=0, name='name==>0', password='pass==>0'}
...
User{id=8, name='name==>8', password='pass==>8'}
User{id=9, name='name==>9', password='pass==>9'}

这就是一个最基本的案例,实现起来非常的简单。下面我们着重分析一下这俩注解的作用,为什么能实现类似于Spring中XML文件一样的作用。


二、分析


1、注解角度分析


想要了解为什么@Configuration会有这样的作用,我们可以跟进去这个注解看看。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
    @AliasFor(
        annotation = Component.class
    )
    String value() default "";
    boolean proxyBeanMethods() default true;
}

进去之后我们会发现,这个注解标签是一个元注解,由很多其他的注解实现,有一个我们应该很熟悉,那就是@Component,有着了这个注解就可以被@ComponentScan扫描并处理。Spring5.0已经自动扫描了,不需要我们自己再去添加了。现在我们进去到里面的代码看看:


首先是@AliasFor标签:


在Spring的众多注解中,经常会发现很多注解的不同属性起着相同的作用,比如@RequestMapping的value属性和path属性,这就需要做一些基本的限制,比如value和path的值不能冲突,比如任意设置value或者设置path属性的值,都能够通过另一个属性来获取值等等。为了统一处理这些情况,Spring创建了@AliasFor标签。


然后是value() :


意思是默认的值就是空,此时我们就可以指定@Configuration(value="属性值")的这种方式,因为只有一个value所以value可以省去不写。


最后是proxyBeanMethods:


有了 proxyBeanMethods 属性后,配置类不会被代理了。主要是为了提高性能,如果你的 @Bean 方法之间没有调用关系的话可以把 proxyBeanMethods 设置为 false。否则,方法内部引用的类生产的类和 Spring 容器中类是两个类。


3、运行角度分析


现在我们把目光转移,从测试运行的角度来分析。

//获取java配置类
AnnotationConfigApplicationContext context =
       new AnnotationConfigApplicationContext(UserConfig.class);
//获取ioc容器中的对象
UserService userService = (UserService) context.getBean("userService");

看到这里,可能就要深入到Spring的源码中看了。Spring容器启动时,ApplicationContext接口的实现类AnnotationConfigApplicationContext会执行refresh方法,往BeanFactory注册bean就在此方法完成。我们看到这个refresh是核心。我们进入到这个源码中看看:

public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        //准备刷新的上下文 环境  
        prepareRefresh();
        //初始化BeanFactory,并进行XML文件读取  
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
        //对beanFactory进行各种功能填充  
        prepareBeanFactory(beanFactory);
        try {
            //子类覆盖方法做额外处理  
            postProcessBeanFactory(beanFactory);
            //激活各种beanFactory处理器  
            invokeBeanFactoryPostProcessors(beanFactory);
            //注册拦截Bean创建的Bean处理器,这里只是注册,真正的调用实在getBean时候 
            registerBeanPostProcessors(beanFactory);
            //为上下文初始化Message源,即不同语言的消息体,国际化处理  
            initMessageSource();
            //初始化应用消息广播器,并放入“applicationEventMulticaster”bean中  
            initApplicationEventMulticaster();
            ...
        }
        ...
    }
}

我截取了其中一部分的源码,在里面有一个方法很关键,那就是invokeBeanFactoryPostProcessors,意思是我们Spring容器首先会初始化BeanFactory,然后激活各种beanFactory处理器,也就是执行invokeBeanFactoryPostProcessors,我们看看这个方法:

public static void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory, 
       List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
     Set<String> processedBeans = new HashSet<>();
     if (beanFactory instanceof BeanDefinitionRegistry) {
         // 定义BeanDefinitionRegistryPostProcessor集合
         List<BeanDefinitionRegistryPostProcessor> registryProcessors = new ArrayList<>();
         // 循环手动注册的beanFactoryPostProcessors
         for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {
             // 如果是BeanDefinitionRegistryPostProcessor的实例话,则调用其postProcessBeanDefinitionRegistry方法,对bean进行注册操作
             if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) {
                // 如果是BeanDefinitionRegistryPostProcessor类型,则直接调用其postProcessBeanDefinitionRegistry
                BeanDefinitionRegistryPostProcessor registryProcessor = (BeanDefinitionRegistryPostProcessor) postProcessor;
                registryProcessor.postProcessBeanDefinitionRegistry(registry);
                registryProcessors.add(registryProcessor);
            }
            // 否则则将其当做普通的BeanFactoryPostProcessor处理,直接加入regularPostProcessors集合,以备后续处理
            else {
                regularPostProcessors.add(postProcessor);
            }
         }
     }
     else {
         invokeBeanFactoryPostProcessors(beanFactoryPostProcessors, beanFactory);
     }
 }

在这个方法的内部的核心是ConfigurationClassPostProcessor,这个方法看到@Configuration,就会开启类的加载,这里也就是bean的加载。剩下的越挖越深,源码也越来越深。大体步骤我们可以总结一下:


ConfigurationClassPostProcessor处理器解析@configuration配置类主要过程:


(1)Spring容器初始化时注册ConfigurationClassPostProcessor


(2)Spring容器初始化执行refresh()方法中调用ConfigurationClassPostProcessor


(3)ConfigurationClassPostProcessor处理器借助ConfigurationClassParser完成配置类解析


(4)ConfigurationClassParser配置内解析过程中完成嵌套的MemberClass、@PropertySource注解、@ComponentScan注解(扫描package下的所有Class并进行迭代解析,主要是@Component组件解析及注册)、@ImportResource、@Bean等处理


(5)接下来完成@Bean注册, @ImportResource指定bean的注册以及@Import的bean注册


(6)有@Bean注解的方法在解析的时候作为ConfigurationClass的一个属性,最后还是会转换成BeanDefinition进行处理, 而实例化的时候会作为一个工厂方法进行Bean的创建


现在大致应该明白了,其实一句话说完,还是想办法识别注解,完成和XML一样的功能。

相关文章
|
3天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
14 2
|
30天前
|
架构师 Java 开发者
得物面试:Springboot自动装配机制是什么?如何控制一个bean 是否加载,使用什么注解?
在40岁老架构师尼恩的读者交流群中,近期多位读者成功获得了知名互联网企业的面试机会,如得物、阿里、滴滴等。然而,面对“Spring Boot自动装配机制”等核心面试题,部分读者因准备不足而未能顺利通过。为此,尼恩团队将系统化梳理和总结这一主题,帮助大家全面提升技术水平,让面试官“爱到不能自已”。
得物面试:Springboot自动装配机制是什么?如何控制一个bean 是否加载,使用什么注解?
|
1月前
|
Java 测试技术 开发者
springboot学习四:Spring Boot profile多环境配置、devtools热部署
这篇文章主要介绍了如何在Spring Boot中进行多环境配置以及如何整合DevTools实现热部署,以提高开发效率。
56 2
|
1月前
|
前端开发 Java 程序员
springboot 学习十五:Spring Boot 优雅的集成Swagger2、Knife4j
这篇文章是关于如何在Spring Boot项目中集成Swagger2和Knife4j来生成和美化API接口文档的详细教程。
84 1
|
1月前
|
Java API Spring
springboot学习七:Spring Boot2.x 拦截器基础入门&实战项目场景实现
这篇文章是关于Spring Boot 2.x中拦截器的入门教程和实战项目场景实现的详细指南。
25 0
springboot学习七:Spring Boot2.x 拦截器基础入门&实战项目场景实现
|
1月前
|
Java API Spring
springboot学习六:Spring Boot2.x 过滤器基础入门&实战项目场景实现
这篇文章是关于Spring Boot 2.x中过滤器的基础知识和实战项目应用的教程。
23 0
springboot学习六:Spring Boot2.x 过滤器基础入门&实战项目场景实现
|
1月前
|
Java 测试技术 Spring
springboot学习三:Spring Boot 配置文件语法、静态工具类读取配置文件、静态工具类读取配置文件
这篇文章介绍了Spring Boot中配置文件的语法、如何读取配置文件以及如何通过静态工具类读取配置文件。
46 0
springboot学习三:Spring Boot 配置文件语法、静态工具类读取配置文件、静态工具类读取配置文件
|
1月前
|
设计模式 缓存 Java
面试题:谈谈Spring用到了哪些设计模式?
面试题:谈谈Spring用到了哪些设计模式?
|
1月前
|
SQL Java 数据库
Springboot+spring-boot-starter-data-jdbc实现数据库的操作
本文介绍了如何使用Spring Boot的spring-boot-starter-data-jdbc依赖来操作数据库,包括添加依赖、配置数据库信息和编写基于JdbcTemplate的数据访问代码。
47 2
|
1月前
|
Java Spring
springboot 学习十一:Spring Boot 优雅的集成 Lombok
这篇文章是关于如何在Spring Boot项目中集成Lombok,以简化JavaBean的编写,避免冗余代码,并提供了相关的配置步骤和常用注解的介绍。
90 0