目录
Spring Bean 定义常见错误
案例一:隐式扫描不到 Bean 的定义
案例 2:定义的 Bean 缺少隐式依赖
案例 3:原型 Bean 被固定为单例bean
Spring Bean 依赖注入常见错误
案例4:定义了多个相同类型的bean,无法确定自动注入哪一个
案例5:@Value没有注入预期的值
Spring AOP 常见错误
案例6:this调用的当前类方法没有切面的增强功能
Spring Bean 定义常见错误
案例一:隐式扫描不到 Bean 的定义
现象:
默认情况下,当bean不在Application类所在的包及其子包下,会找不到bean。错误信息:"required a bean of type 'XXXX' that could not be found."
错误原因:
默认情况下,spring扫码bean,只会扫码配置类所在的包及其子包。定义在其他包的bean不会被扫描到。
解决方法:
显式配置@ComponentScan的basePackages参数,指定bean所在的包。
@ComponentScan(basePackages = {"com.focuse.componentscandemo.annotationcomps"})
案例 2:定义的 Bean 缺少隐式依赖
现象:
下面代码中的ServiceImpl的自动初始化会报错:"required a bean of type 'java.lang.String' that could not be found"
@Service
public class ServiceImpl {
private String serviceName;
public ServiceImpl(String serviceName){
this.serviceName = serviceName;
}
}
错误原因:
在bean的初始化过程中,构造器中的参数需要通过bean注入。由于没有定义String类型的bean,这里会报“required a bean of type 'java.lang.String' that could not be found.”的错误。
解决方法:
- (推荐)不要在自动初始化的bean里声明有参数的构造函数。有非bean字段需要赋值的bean适合用@Configuration或xml显示定义并赋值。
- 定义一个String类型的bean
案例 3:原型 Bean 被固定为单例bean
现象:
某些场景下我们不希望bean是单例模式,就会用到原型bean。下面的代码中,我们已经通过scope把ServiceImpl声明为原型bean,但多次访问http://localhost:8080/hi,返回的仍然是同一个ServiceImpl实例。
@Service
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class ServiceImpl {
}
@RestController
public class HelloWorldController {
@Autowired
private ServiceImpl serviceImpl;
@RequestMapping(path = "hi", method = RequestMethod.GET)
public String hi(){
return "helloworld, service is : " + serviceImpl;
};
}
错误原因:
当字段使用@Autowired注解或@Resource注解自动注入时,该自动只会被注入(赋值)一次,所以这个字段指向的bean实例就会被固定为同一个。
解决方法:
- 使用Lookup 注解
@RestController
public class HelloWorldController {
@RequestMapping(path = "hi", method = RequestMethod.GET)
public String hi(){
return "helloworld, service is : " + getServiceImpl();
};
@Lookup
public ServiceImpl getServiceImpl(){
return null;
}
}
- 使用ApplicationContext手动获取bean
@RestController
public class HelloWorldController {
@Autowired
private ApplicationContext applicationContext;
@RequestMapping(path = "hi", method = RequestMethod.GET)
public String hi(){
return "helloworld, service is : " + getServiceImpl();
};
public ServiceImpl getServiceImpl(){
return applicationContext.getBean(ServiceImpl.class);
}
}
Spring Bean 依赖注入常见错误
案例4:定义了多个相同类型的bean,无法确定自动注入哪一个
现象:
下面代码中,用xml定义了两个id不同的ServiceImpl类型的bean,在HelloWorldController中自动注入serviceImpl字段会报错:"required a single bean, but 2 were found"
@Service
public class ServiceImpl {
}
@RestController
public class HelloWorldController {
@Autowired
private ServiceImpl serviceImpl;
@RequestMapping(path = "hi", method = RequestMethod.GET)
public String hi(){
return "helloworld, service is : " + serviceImpl;
};
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
<bean id="serviceImpl1" class="com.example.demo.service.ServiceImpl">
</bean>
<bean id="serviceImpl2" class="com.example.demo.service.ServiceImpl">
</bean>
</beans>
错误原因:
@Autowired在查找bean的过程可以简单分为两步:
第一步会先按字段类型查找bean,这里有两个ServiceImpl类型的bean。
第二步如果根据类型查找到了多个bean,会把字段名称当作bean id,再根据bean id查找bean。这里的字段名称为serviceImpl,两个bean的id为serviceImpl1,serviceImpl2,根据bean id查找bean失败。
两步都没有找到唯一的bean,就会报错。
解决方法:
将字段名称与想用的bean id保持一致即可。
在指定bean id时需注意,不显示指定bean id时,bean id的生成规则是这样的:
如果是用注解声明bean:如果一个类名是以多个大写字母开头的,则首字母不变,其它情况下默认首字母变成小写
如果是用xml声明bean:bean id等于"类名全限定名#序号",例如"com.example.demo.service.ServiceImpl#0"
@Service
public class ServiceImpl {
}
@RestController
public class HelloWorldController {
@Autowired
private ServiceImpl serviceImpl1;
@RequestMapping(path = "hi", method = RequestMethod.GET)
public String hi(){
return "helloworld, service is : " + serviceImpl1;
};
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
<bean id="serviceImpl1" class="com.example.demo.service.ServiceImpl">
</bean>
<bean id="serviceImpl2" class="com.example.demo.service.ServiceImpl">
</bean>
</beans>
案例5:@Value没有注入预期的值
现象:
下面代码中,password被正确赋值,但username被注入的值不是在配置文件中指定的值。
username=admin
password=pass
@RestController
@Slf4j
public class ValueTestController {
@Value("${username}")
private String username;
@Value("${password}")
private String password;
@RequestMapping(path = "user", method = RequestMethod.GET)
public String getUser(){
return username + ":" + password;
};
}
错误原因:
@Value在查找值时,会从包括“系统环境变量”,“系统参数”,“配置文件”等多个源中按顺序查找,找到第一个匹配的后就返回。案例中的“username”变量与系统环境变量重名,所以实际上注入的值时系统环境变量中的“username”。
解决方法:
在配置文件中定义的变量,建议带上具有唯一标识作用的信息,以避免与机器中其他变量重名
Spring AOP 常见错误
案例6:this调用的当前类方法没有切面的增强功能
现象:
下面的代码中,有两个public方法:charge和pay,其中charge方法中通过this调用了pay方法。
给这两个方法都正确的加上切面,然后调用charge方法。
从控制台可以看到两个方法都执行了,并且charge方法成功被切面增强,但pay方法未被切面增强。
@Service
public class ElectricService {
public void charge() throws Exception {
System.out.println("Electric charging ...");
this.pay();
}
public void pay() throws Exception {
System.out.println("Pay with alipay ...");
Thread.sleep(1000);
}
}
错误原因:
切面增强某方法的方式是,创建一个原类的子类作为原类的代理类,在代理类中加入AOP的增强能力。而this是原类的实例,所以没有相应的增强功能。
解决方法:
通过@Autowired等注解注入被AOP增强过的代理类,然后用代理类调用pay方法:
@Service
public class ElectricService {
@Autowired
ElectricService electricService;
public void charge() throws Exception {
System.out.println("Electric charging ...");
//this.pay();
electricService.pay();
}
public void pay() throws Exception {
System.out.println("Pay with alipay ...");
Thread.sleep(1000);
}
}
未完待续