Spring的注解@Bean
@Bean中的autowire属性
现在我们有如下几个方法,模拟一个简单的Spring的bean注入的过程。
AppConfig
@ComponentScan("com.zal")
public class AppConfig {
@Bean
public UserService userService() {
return new UserService();
}
}
Service
public class OrderService {
}
public class UserService {
private OrderService orderService;
public void test() {
System.out.println(orderService);
}
public OrderService getOrderService() {
return orderService;
}
public void setOrderService(OrderService orderService) {
this.orderService = orderService;
}
}
Test
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = applicationContext.getBean("userService", UserService.class);
userService.test();
}
}
这时我们如果去运行Test方法的话,控制台输出的肯定是null,因为虽然UserService被当成bean注入了,但是OrderUser并没有被注入容器,所以这时我们输出的是null。
如果我们想要给OrderService进行自动注入的话,通常的做法肯定是给OrderService上加上@Autowired注解。
现在就是说,我们有没有什么方式,不加@Autowired注解,就能实现OrderService的bean注入呢?
有,我们可以使用@Bean注解的autowire属性来进行bean的注入,前提是需要将OrderService装配成bean,也就是加上@Component注解。
@Component
public class OrderService {
}
@ComponentScan("com.zal")
public class AppConfig {
@Bean(autowire = Autowire.BY_NAME)
public UserService userService() {
return new UserService();
}
}
实现原理:
其实当我们在@Bean注解上使用了autowire属性时,在给UserService的属性OrderService赋值的同时,它不是从spring容器中直接去查找bean,然后进行赋值的。而是通过UserSerivce的get、set方法来进行赋值,首先它会解析UserService的所有的set方法,BY_TYPE就是通过参数类型来判断spring容器中是否有这个bean,如果有就传给set方法,进行赋值,BY_NAME则是通过参数的参数名来进行判断的,非常类似。
这一切都是spring帮我们来实现的。
但是这个方法现在已经过期了,不推荐使用。这是因为这种方法需要实现Service的get、set方法,但是我们也不确定有多少属性需要实现自动注入,我们将所有的属性都生成get、set方法,就显得有些浪费资源,而且这种方式没有直接加注解@Autowire,所以这种方法已经不推荐使用了。
@Bean中的autowireCandidate属性
作用:自动注入的候选者,默认值为true
这里我们先把UserService和OrderService都注入成bean,然后在给UserService中的OrderService属性加上@Autowire注解。
@ComponentScan("com.zal")
public class AppConfig {
@Bean
public UserService userService() {
return new UserService();
}
@Bean
public OrderService orderService(){
return new OrderService();
}
}
public class UserService {
@Autowired
private OrderService orderService;
}
显然,这两段代码,如果执行测试类的话,是没有什么问题的,但是如果我们注入多个OrderService呢?根据@Autowired的特性来说,先byType再byName,如果有多个OrderService进行注入,那么spring会自动匹配参数名称相同的那个,但是如果两个参数名称和我们添加了@Autowired注解的OrderService的参数的名称都不一样,那么就会报错。
No qualifying bean of type 'com.zal.service.OrderService' available: expected single matching bean but found 2: orderService,orderService1
报错信息很明显,就是找到了类型相同但是名称都不相同的bean。
这时我们给其中一个bean加上autowireCandidate属性,并且设置其属性为false。
@Bean(autowireCandidate = false)
public OrderService orderService1(){
return new OrderService();
}
这时再运行测试类,发现就没有什么问题了,也能正常输出。
这就说明@Bean的autowireCandidate表示该bean不能注入给其他bean,不能作为spring的依赖注入的候选者。
@Bean和@Component
@Bean注解不仅仅只能写在@Configrruation中,还可以写在@Component中。
@Component
public class UserService {
@Bean
public OrderService orderService(){
return new OrderService();
}
}
@Bean自定义注解
// 将UserService定义成多例bean
@Bean
@Scope(value = "prototype")
public UserService userService(){
return new UserService();
}
如果是一个bean或者两个bean,我们可以这样写,是没有什么问题的,但是我们如果给很多个bean都注册成为多例bean,那么我们就可以自定义一个注解,来实现这样的功能:
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Bean
@Scope(value = "prototype")
public @interface ProtoTypeBean {
}
如果我们想要实现多例bean的注册,只需要添加@ProtoTypeBean这一个注解就行了。
@Bean与@Configuration注解之间的关系
@ComponentScan("com.zal")
public class AppConfig {
@Bean
public UserService userService(){
return new UserService();
}
@Bean
public OrderService orderService(){
System.out.println(userService());
System.out.println(userService());
return new OrderService();
}
}
如果我们不加@Configuration注解的话,我们运行测试类会发现,这两个打印的userService是不相同的,而且与Spring注册的bean的地址也是不相同的,那么这就会出现问题了,因为我们的UserSercvice是单例bean,全局只能存在一个。而如果我们加上了@Configuration注解,所有的userService地址又都相同了,那么这是为什么呢?
如果我们不加@Configuration注解时,相当于每次创建UserService使用的就普通的AppConfig类,每次创建都会返回一个新的UserService,所以每次返回的对象都不一样。
普通的AppConfig: com.zal.AppConfig@4524411f
代理对象AppConfig: com.zal.AppConfig$$EnhancerBySpringCGLIB$$793ecb84@341b80b2(CGLIB代理对象)
但是如果加了@Configuration之后,每次返回的对象其实是一个代理对象。每次调用userService()的时候代理对象先去判断是否创建了相应的bean,如果没有,就创建并且存储到spring的容器中,如果有,就直接从容器中取出返回即可。