【问题标题】:How does Spring know where to search for Components or Beans?Spring 如何知道在哪里搜索组件或 Bean?
【发布时间】:2020-09-22 22:22:50
【问题描述】:

在一次采访中,面试官问我“Spring 如何知道在哪里搜索组件或 Bean?”。

由于我不了解内部流程细节,我无法正确回答问题。

我说通过@Component@Bean我们可以找到。但面试官对这个问题并不满意。 如果有人知道,请分享您的知识。 TIA

【问题讨论】:

标签: spring-boot


【解决方案1】:

IoC(控制反转)容器,在 Spring 中由 ApplicationContext 类表示,是其背后的大脑。这一切都归结为以一种非常强大的方式使用反射。

为简化起见,让我们考虑以下步骤(全部通过反射完成):

  1. 搜索类路径中的所有类
  2. 从这些类中,获取所有带有@Component 注释的类
  3. 对于每个使用 @Component 注释的类,创建该类的新实例
  4. 检查依赖关系,即,对于每个创建的实例,检查所有带有 @Autowired 注释的字段,并为每个字段创建一个实例。
  5. 将所有内容保存在上下文中,以便以后使用。

这个答案的其余部分是一个过于简单的版本,说明这是如何发生的,就好像我们自己做的一样。值得庆幸的是,Spring 存在,我们不需要自己做这些。

注释

@Retention(RetentionPolicy.RUNTIME)
public @interface Node {}

@Retention(RetentionPolicy.RUNTIME)
public @interface Wire { }

一些带注释的测试类

@Node
public class ServiceA {
    @Wire
    private ServiceB serviceB;

    public void doAStuff() {
        System.out.println("A stuff");
        serviceB.doBStuff();
    }
}

@Node
public class ServiceB {
    public void doBStuff() {
        System.out.println("B stuff");
    }
}

IoC 容器

import org.reflections.Reflections;
/* dependency org.reflections:reflections:0.9.12 */

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class IoC {
    private final Map<Class<?>, Object> allNodes = new HashMap<>();

    public void start() {
        Reflections reflections = new Reflections(IoC.class.getPackageName());
        Set<Class<?>> nodeClasses = reflections.getTypesAnnotatedWith(Node.class);

        try {
            for (Class<?> c : nodeClasses) {
                Object thisInstance = c.getDeclaredConstructor().newInstance();

                for (Field f : c.getDeclaredFields()) {
                    f.setAccessible(true);
                    if (f.getDeclaredAnnotation(Wire.class) != null) {
                        Object o = f.getType().getDeclaredConstructor().newInstance();
                        f.set(thisInstance, f.getType().cast(o));
                    }
                }

                allNodes.put(c, thisInstance);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public <T> T getNodeByType(Class<T> cls) {
        return cls.cast(allNodes.get(cls));
    }
}

以及让这一切开始的主要课程。

public class Application {
    public static void main(String[] args) {
        IoC ioc = new IoC();
        ioc.start();

        ServiceA serviceA = ioc.getNodeByType(ServiceA.class);
        serviceA.doAStuff();
    }
}

这将输出:

A stuff
B stuff

当然,Spring 比这更强大(和健壮)。它允许使用@ComponentScan、具有不同名称的相同类型的bean、单例/原型范围的bean、构造函数连接、属性文件注入等进行自定义包扫描。对于 Spring Boot,@SpringBootApplication 注释确保它找到并连接所有 @Controller 注释类,并设置 Netty/Jetty/Tomcat 嵌入式服务器以侦听请求并根据注释重定向到正确的控制器类型。

【讨论】:

    【解决方案2】:

    我喜欢回答面试问题。阅读下文...

    @ComponentScan
    

    如果你了解组件扫描,你就了解 Spring。

    Spring 是一个依赖注入框架。这完全是关于 bean 和依赖关系的布线。

    定义 Spring Beans 的第一步是添加正确的注解——@Component@Service@Repository

    但是,Spring 不知道 bean,除非它知道在哪里搜索它。

    “告诉 Spring 在哪里搜索”的这一部分称为组件扫描。

    您定义必须扫描的包。

    一旦你为一个包定义了一个组件扫描,Spring 就会在这个包及其所有子包中搜索组件/bean。

    定义组件扫描

    • 如果您使用的是 Spring Boot,请检查方法 1 中的配置。
    • 如果您正在执行 JSP/Servlet 或 Spring MVC 应用程序而没有 使用 Spring Boot,使用方法 2。

    方法一:Spring Boot 项目中的组件扫描

    如果您的其他包层次结构位于带有 @SpringBootApplication 注释的主应用程序下方,则隐式组件扫描将覆盖您。 如果其他包中存在非主包子包的bean/组件,则应手动添加为@ComponentScan

    考虑下面的类

    package com.in28minutes.springboot.basics.springbootin10steps;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ConfigurableApplicationContext;
    @SpringBootApplication
    public class SpringbootIn10StepsApplication {
        public static void main(String[] args) {
            ApplicationContext applicationContext =
                SpringApplication.run(SpringbootIn10StepsApplication.class, args);
            for (String name: applicationContext.getBeanDefinitionNames()) {
                System.out.println(name);
            }
        }
    }
    

    @SpringBootApplication 定义在 SpringbootIn10StepsApplication 类中,该类位于 com.in28minutes.springboot.basics.springbootin10steps 包中

    @SpringBootApplication 定义了对包com.in28minutes.springboot.basics.springbootin10steps 的自动组件扫描。

    如果你的所有组件都定义在上述包或它的子包中,你就可以了。

    但是,假设其中一个组件在包 com.in28minutes.springboot.somethingelse 中定义

    在这种情况下,您需要将新包添加到组件扫描中。

    你有两个选择:

    选项 1:

    @ComponentScan(“com.in28minutes.springboot”)
    @SpringBootApplication
    public class SpringbootIn10StepsApplication {...}
    

    选项2::定义为数组

    @ComponentScan({"com.in28minutes.springboot.basics.springbootin10steps","com.in28minutes.springboot.somethingelse"})
    @SpringBootApplication
    public class SpringbootIn10StepsApplication {...}
    

    方法 2:非 Spring Boot 项目

    选项 1:

    @ComponentScan(“com.in28minutes)
    @Configuration
    public class SpringConfiguration {...}
    

    选项 2:

    @ComponentScan({"com.in28minutes.package1","com.in28minutes.package2"})
    @Configuration
    public class SpringConfiguration {...}
    

    XML 应用程序上下文:

    <context:component-scan base-package="com.in28minutes" />
    

    具体的多个包:

    <context:component-scan base-package="com.in28minutes.package1, com.in28minutes.package2" />
    

    【讨论】:

      【解决方案3】:

      在哪里搜索 bean 由 @ComponentScan 定义,可以在用于引导 Spring 的 @Configuration 类上进行注释。

      例如,它有一个名为scanBasePackages 的属性,它告诉Spring 扫描bean(一个用@Component 或其原型如@Service@Repository@Controller 等注释的类)仅来自某些包及其子包。

      然后对于每个注册的bean,它会继续查看是否有任何带有@Bean的方法注释。如果有,也将它们注册为bean。

      【讨论】:

        猜你喜欢
        • 2017-07-03
        • 2016-04-13
        • 1970-01-01
        • 1970-01-01
        • 2017-12-26
        • 1970-01-01
        • 2017-05-23
        • 1970-01-01
        • 2015-11-03
        相关资源
        最近更新 更多