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的加载顺序,那你一定要看此篇