Spring Bean 定义常见错误案例

Spring Bean 定义常见错误案例

使用好 Spring,就一定要了解它的一些潜规则,例默认扫描 Bean 的范围、自动装配构造器等。通过本节案例的分析,我们也可以感受到 Spring 的很多实现是通过反射来完成的,了解了这点,对于理解它的源码实现会大有帮助。例如在案例 3 中,为什么定义了多个构造器就可能报错,因为使用反射方式来创建实例必须要明确使用的是哪一个构造器。

Spring Bean 定义常见错误案例_第1张图片

案例 1:spring bean 隐式扫描问题

案例描述:

spring boot 开发项目时,构建一个简单的web 程序示例如下:

QuestionApplication 启动类代码如下:

@SpringBootApplication
public class QuestionApplication {
    public static void main(String[] args) {
    SpringApplication.run(QuestionApplication.class, args);
   }
}

提供接口的 HelloWorldController 代码如下:

@RestController
public class HelloWorldController {

    @GetMapping("/hi")
    public String hello(){
        return "hello world";
    };
}

目录层级如下图所示,启动能正常访问:http://127.0.0.1:8089/hi
Spring Bean 定义常见错误案例_第2张图片

然后调整目录层级机构如下,启动访问 404 错误,问题原因是什么呢?
Spring Bean 定义常见错误案例_第3张图片

问题原因:

@SpringBootApplication 注解继承了@ComponentScan注解,此注解默认包扫描为{}, 当为空时,ComponentScanAnnotationParser#parse 方法 扫描的包其实就是QuestionApplication 所在的包,所以,综合来看,这个问题是因为我们不够了解 Spring Boot 的默认扫描规则引起的。
Spring Bean 定义常见错误案例_第4张图片

问题修正:

方法一:修改调整包结构

方式二:显式配置 @ComponentScan 或使用 @ComponentScans 来修复问题

案例 2:spring 原型 bean 创建 问题

案例描述:

ServiceImpl, HelloWorldController 代码如下:

@Service
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class ServiceImpl {
}
@RestController
public class HelloWorldController {

    @Autowired
    private ServiceImpl serviceImpl;

    @GetMapping("/hi")
    public String hello(){
        return "hello world" + serviceImpl;
    };
}

结果发现,访问多少次http://localhost:8080/hi,访问的结果都是不变的,如下:

hello worldcom.lvt.example.service.ServiceImpl@66d7d9da

很明显,这很可能和我们定义 ServiceImpl 为原型 Bean 的初衷背道而驰,这又是什么原因呢?

问题原因:

当一个属性成员声明为 @Autowired 后,那么在创建 这个 Bean 时,会先使用构造器反射出实例,然后来装配各个标记为 @Autowired 的属性成员。

所以,当一个单例的 Bean,使用 autowired 注解标记其属性时,你一定要注意这个属性值会被固定下来。

问题修正:

方法一:自动注入 Context

修正代码如下:

@RestController
public class HelloWorldController {

    @Autowired
    private ApplicationContext applicationContext;

    @GetMapping("/hi")
    public String hello(){
        return "hello world" + getServiceImpl();
    };

    public ServiceImpl getServiceImpl(){
        return applicationContext.getBean(ServiceImpl.class);
    }
}

案例 3:spring 定义缺少隐式依赖问题

案例描述:

ServiceImpl 因为标记为 @Service 而成为一个 Bean。另外我们 ServiceImpl 显式定义了一个构造器。某些编译器现在已能智能提示 构造器参数找不到。

@Service
public class ServiceImpl {
private String serviceName;

    public ServiceImpl(String serviceName){
        this.serviceName = serviceName;
    }
}

但是,上面的代码不是永远都能正确运行的,有时候会报下面这种错误:Parameter 0 of constructor in com.lvt.example.service.ServiceImpl required a bean of type ‘java.lang.String’ that could not be found. 那问题出在原因出在哪里呢?

问题原因:

隐式的规则:我们定义一个类为 Bean,如果再显式定义了构造器,那么这个 Bean 在构建时,会自动根据构造器参数定义寻找对应的 Bean,然后反射创建出这个 Bean。如果存在多个构造器,都可以调用时,到底应该调用哪个呢?最终 Spring 无从选择,只能尝试去调用默认构造器。

问题修正:

方法一:定义一个能让 Spring 装配给 ServiceImpl 构造器参数的 Bean

//这个bean装配给ServiceImpl的构造器参数“serviceName”
@Bean
public String serviceName(){
    return "MyServiceName";
}

更多精选好文请关注个人公众号:
Spring Bean 定义常见错误案例_第5张图片

你可能感兴趣的:(个人开发经验集,JAVA工程师,Spring,Framework)