Spring 如何知道在哪里搜索组件或 Bean?

Nar*_*ddy 8 spring-boot

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

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

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

Kun*_*hra 5

我喜欢回答面试问题。参见下文...

@ComponentScan
Run Code Online (Sandbox Code Playgroud)

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

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

限定弹簧豆的第一个步骤是通过将合适的注释-@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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

@SpringBootApplicationSpringbootIn10StepsApplication在包中的类中定义com.in28minutes.springboot.basics.springbootin10steps

@SpringBootApplication在包上定义自动组件扫描com.in28minutes.springboot.basics.springbootin10steps

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

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

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

您有两个选择:

选项1:

@ComponentScan(“com.in28minutes.springboot”)
@SpringBootApplication
public class SpringbootIn10StepsApplication {...}
Run Code Online (Sandbox Code Playgroud)

选项 2 :: 定义为数组

@ComponentScan({"com.in28minutes.springboot.basics.springbootin10steps","com.in28minutes.springboot.somethingelse"})
@SpringBootApplication
public class SpringbootIn10StepsApplication {...}
Run Code Online (Sandbox Code Playgroud)

方法二:非Spring Boot项目

选项1:

@ComponentScan(“com.in28minutes)
@Configuration
public class SpringConfiguration {...}
Run Code Online (Sandbox Code Playgroud)

选项 2:

@ComponentScan({"com.in28minutes.package1","com.in28minutes.package2"})
@Configuration
public class SpringConfiguration {...}
Run Code Online (Sandbox Code Playgroud)

XML 应用上下文:

<context:component-scan base-package="com.in28minutes" />
Run Code Online (Sandbox Code Playgroud)

具体多包:

<context:component-scan base-package="com.in28minutes.package1, com.in28minutes.package2" />
Run Code Online (Sandbox Code Playgroud)


rph*_*rph 5

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

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

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

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

注释

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

@Retention(RetentionPolicy.RUNTIME)
public @interface Wire { }
Run Code Online (Sandbox Code Playgroud)

一些用于测试的带注释的类

@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");
    }
}
Run Code Online (Sandbox Code Playgroud)

国际奥委会容器

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));
    }
}
Run Code Online (Sandbox Code Playgroud)

以及让这一切开始的主类。

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

        ServiceA serviceA = ioc.getNodeByType(ServiceA.class);
        serviceA.doAStuff();
    }
}
Run Code Online (Sandbox Code Playgroud)

这将输出:

A stuff
B stuff
Run Code Online (Sandbox Code Playgroud)

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