在一次采访中,面试官问我:">Spring怎么知道在哪里搜索组件或豆子?"。
由于我不知道内部流程细节,我无法正确回答问题。
我说通过@Component
和@Bean
我们可以找到。但面试官对这个问题并不满意。 如果有人知道,请分享您的知识。蒂亚
我喜欢回答面试问题。阅读下文...
@ComponentScan
如果你了解组件扫描,你就了解Spring。
Spring 是一个依赖注入框架。这一切都与依赖关系中的豆子和布线有关。
定义春豆的第一步是添加正确的注释——@Component
或@Service
或@Repository
。
然而,春天不知道豆子,除非它知道在哪里寻找它。
"告诉Spring在哪里搜索"的这一部分称为组件扫描。
定义必须扫描的包。
一旦你为一个包定义了组件扫描,Spring 就会在包及其所有子包中搜索组件/bean。
定义组件扫描
- 如果您使用的是 Spring 引导,请检查方法 1 中的配置。
- 如果你正在做一个JSP/Servlet或Spring MVC应用程序,没有 使用 Spring Boot,使用方法 2。
方法 1:Spring 引导项目中的组件扫描
如果其他程序包层次结构位于主应用下方,并带有@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
在包com.in28minutes.springboot.basics.springbootin10steps
中的SpringbootIn10StepsApplication
类中定义
@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:非弹簧引导项目
选项 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" />
IoC(控制反转)容器,在 Spring 中由类ApplicationContext
表示,是所有这一切背后的大脑。这一切都归结为以一种非常强大的方式使用反射。
为了简化起见,让我们考虑以下步骤(全部通过反射完成):
- 搜索类路径中的所有类
- 从这些类中,获取所有类,并用
@Component
- 对于每个用
@Component
注释的类,创建该类的新实例
检查 - 依赖关系,即对于每个创建的实例,检查所有用
@Autowired
注释的字段,并为每个字段创建一个实例。 - 将所有内容保留在上下文中,以便以后使用。
这个答案的其余部分是关于这种情况如何发生的过度简化版本,就好像我们自己做了一样。值得庆幸的是,春天存在,我们不需要自己做这件事。
注释
@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");
}
}
国际奥委会容器
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嵌入式服务器来监听请求并根据带注释的类型重定向到正确的控制器。
搜索 bean 的位置由@ComponentScan
定义,该可以在用于引导 Spring 的@Configuration
类上注释。
例如,它有一个名为scanBasePackages
的属性,它告诉Spring只从某些软件包及其子软件包中扫描bean(一个用@Component
或其立体型,如@Service
、@Repository
、@Controller
等注释的类)。
然后对于每个注册的 bean,它继续查看是否有任何带有@Bean
的方法注释。如果是,也将它们注册为 bean。