Spring的核心是围绕Bean进行的,无论是Spring Boot 还是 Spring Cloud ,凡是带Spring的技术,都离不开bean的定义,所以就显得尤为重要。
当然这么重要的工作,Spring给我们提供了多种简单的定义bean的方法,全得益于“约定大于配置”,但我们可能不是对所有的约定都了如指掌,仍然会在bean的定义上犯一些经典的错误。
接下来我们就了解一下那些经典错误并了解一下原理,你也可以对照自己是否犯过,想想自己当初是如何解决的。
隐式扫描不到bean的定义
通常我们为了快速定义一个web工程,通常使用spring boot来构建,下图是简单的一个web服务
其中,定义SringLearningAplication的代码定义如下:
package net.zyy.learning.application;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringLearningApplication {
public static void main(String[] args) {
SpringApplication.run(SpringLearningApplication.class, args);
}
}
定义HelloController的代码定义如下:
package net.zyy.learning.application;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class HelloController {
@ResponseBody
@GetMapping("/hello")
public String hello(){
return "Hello,World";
}
}
上述即实现了一个小功能,访问http://localhost:8080/hello即可返回HelloWorld,两个类在application同一个包中,因为HelloController添加了@Controller注解,则被Spring注册到容器中,但是,如果有一天,我们需要添加更多的Controller,并要求层次分明的包结构来进行管理,我们可能单独创建一个controller包,并调整类的位置,如图下:
实际上我们并没有改变代码,仅仅更改了类的位置,web应用就失效了,原因就在于我们找不到HelloController这个类了,这是为何?
解析
要想知道HelloController现在为什么会找不到,首先得知道Spring之前是如何找到的,对于Spring Boot而言,关键点在于@SpringBootApplication注解上,而这个注解使用了其他的注解,具体定义如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
定义我们就可以看出,关键点就在于@ComponentScan注解上,当SpringBoot启动时,Spring就会去扫描出所有的bean,那具体扫描什么位置呢,这是由@ComponentScan注解的basePackages属性来定义的,参考如下:
public @interface ComponentScan {
/**
* Alias for {@link #basePackages}.
* <p>Allows for more concise annotation declarations if no other attributes
* are needed — for example, {@code @ComponentScan("org.my.pkg")}
* instead of {@code @ComponentScan(basePackages = "org.my.pkg")}.
*/
@AliasFor("basePackages")
String[] value() default {};
而在我们的代码中,我们并没有定义value的值,所以默认为{},此时扫描的是哪个包,不如带着这个问题我们去debug一下,调试位置参考 ComponentScanAnnotationParser.parse 方法,debug如下:
从上图可以看出,当basePackages为空时,则默认选择declaringClass所在的包,所有之所以没有找到HelloController的原因我们就找到了,他已经离开了我们可以扫描到的最大范围。
问题修正
有了源码的剖析,我们自然而然就能找到解决方案,具体修改方式如下:
@SpringBootApplication
@ComponentScan("net.zyy.learning")
public class SpringLearningApplication {
public static void main(String[] args) {
SpringApplication.run(SpringLearningApplication.class, args);
}
}
我们可以扩大扫描范围,则包里的bean就都会被我们扫描到了。或者我们直接更换启动类的位置,则默认扫描启动类所在的包里的bean: