目录

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.”的错误。

解决方法:

  1. (推荐)不要在自动初始化的bean里声明有参数的构造函数。有非bean字段需要赋值的bean适合用@Configuration或xml显示定义并赋值。
  2. 定义一个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实例就会被固定为同一个。

解决方法:

  1. 使用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;
    }  

}
  1. 使用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);
    }
}

未完待续