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

Posted

技术标签:

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

在一次采访中,面试官问我“”。

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

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

【问题讨论】:

你搜索如何@Autowired工作,你就能找到答案。 ***.com/questions/3153546/… 【参考方案1】:

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

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

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

这个答案的其余部分是一个过于简单的版本,说明这是如何发生的,就好像我们自己做的一样。值得庆幸的是,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。

【讨论】:

以上是关于Spring 如何知道在哪里搜索组件或 Bean?的主要内容,如果未能解决你的问题,请参考以下文章

Spring JSF 集成:如何在 JSF 托管 bean 中注入 Spring 组件/服务?

如何在filter等dubbo自管理组件中注入spring的bean

如果你还不知道如何控制springboot中bean的加载顺序,那你一定要看此篇

spring 组件和 bean 的作用域有啥区别? [复制]

spring中的“bean”有啥用处?

Spring注解驱动开发在@Import注解中使用ImportSelector接口导入bean