Spring注解方式管理Bean
点击关注公众号,Java干货及时送达
作者"汪伟俊
出品|公众号:Java技术迷(JavaFans1024)
虽然Spring以简化开发著称,但在学习的过程中我们发现,每新建一个类,就需要在配置文件中进行配置,并且类与类之间的关系也需要配置在标签中,好像这并没有简化我们的开发,反而增加了很多繁琐的配置。别担心,本篇文章我们就来学习一下用注解方式来管理Bean。
组件扫描
大家不要对组件这个词感到陌生,在Spring中,一个类可以被称为Bean,也被称为一个组件,回想一下,在之前,我们如何将一个组件注册到IOC容器中呢?没错,我们需要写一段配置,例如:
为了让大家从繁琐的配置中解脱出来,Spring提供了一种基于注解的管理方式,Spring提供了以下注解用来注册一个组件:
1.@Component2.@Controller3.@Service4.@Repository
这四个注解都可以用来注册一个组件,不过每个注解都有其意义,比如@Controller,它是用来注册一个前端控制器的,我们将在SpringMVC中对其进行详解;而@Service是用来注册一个服务层对象的;@Repository是用来注册一个持久层对象的。来体验一下它们的强大吧:
@Componentpublic class User { private String name; private Integer age;}public interface UserService {}@Servicepublic class UserServiceImpl implements UserService {}@Repositorypublic interface UserRepository {}
我们从容器中取出所有的组件,看看注册是否成功了:
public static void main(String[] args) throws Exception { ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml"); String[] beanDefinitionNames = context.getBeanDefinitionNames(); for (String beanDefinitionName : beanDefinitionNames) { System.out.println(beanDefinitionName); }}
当你运行这段测试代码时你会发现控制台没有任何输出,是我们获取的方式不对吗?不对,其实我们还需要进行一项配置:
运行结果:
useruserRepositoryuserServiceImplorg.springframework.context.annotation.internalConfigurationAnnotationProcessororg.springframework.context.annotation.internalAutowiredAnnotationProcessororg.springframework.context.annotation.internalCommonAnnotationProcessororg.springframework.context.event.internalEventListenerProcessororg.springframework.context.event.internalEventListenerFactory
可以看到我们的组件确实注册到Spring中了,剩下的是一些Spring内置的组件,我们无需关系。
context:component-scan标签是用来进行组件扫描的,其中base-package属性用于配置需要扫描的包,一般情况下我们会扫描项目的顶包,即:最外层的包,这样所有项目中的组件都会被扫描到并注册。
事实上,@Component、@Controller、@Service、@Repository四个注解的作用是完全一样的,你也可以在组件上随意地使用它们,比如:
@Repositorypublic class UserServiceImpl implements UserService {}@Servicepublic interface UserRepository {}
这是完全没有问题的,因为@Service、@Controller、@Repository注解是由@Component注解衍生出来的,但为了规范,还是建议将注解添加到指定的组件上。
自动注入
还记得Spring中的属性注入吗?如果不记得的话,我们来回顾一下:
public class User { private Pet pet;}
若是想将一个对象属性注入进去,我们需要进行配置:
但Spring提供了一种更加便捷的注入方式,自动注入:
public class User { @Autowired private Pet pet;}
只需在User类的对象属性上添加@Autowired注解即可将Pet对象自动注入进来,而且它非常智能,我们对程序进行一些改造,首先去掉Pet类的@Component注解:
public class Pet { private String name; private Integer age;}
然后添加一个Dog类继承Pet,并注册:
@Componentpublic class Dog extends Pet{ private String name; private Integer age;}
来测试一下:
public static void main(String[] args) throws Exception { ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml"); User user = context.getBean("user", User.class); System.out.println(user.getPet().getClass());}
运行结果:
class com.wwj.spring.demo.Dog
这样Dog类就被自动注入到User中了,但如果我们又创建了一个类继承Pet并注册:
@Componentpublic class Cat extends Pet{}
此时程序就会报错:
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type "com.wwj.spring.demo.Pet" available: expected single matching bean but found 2: cat,dog
这是Spring中比较常见的一个异常,意思是期望单个匹配的Bean:Pet,但是匹配到了两个Bean:cat、dog。错误非常好理解,因为Pet的子类有两个,所以Spring也不清楚我们到底想要哪一个Bean,所以抛出了异常。
这一问题会在Service层中出现,比如:
public interface UserService {}@Servicepublic class UserServiceImpl implements UserService {}@Servicepublic class EmployeeServiceImpl implements UserService {}
现在我们有一个UserService接口,并且有两个实现类,当自动注入UserService时显然会报错,那么如何解决这一问题呢?我们可以使用@Qualifier注解:
@Componentpublic class User { @Autowired @Qualifier("userServiceImpl") private UserService userService;}
该注解的值即为需要注入的组件名,如果没有配置组件名,则默认是类名且首字母小写,当然了,我们也可以进行配置:
@Service("esi")public class EmployeeServiceImpl implements UserService {}
注入方式如下:
@Componentpublic class User { @Autowired @Qualifier("esi") private UserService userService;}
这一问题也可以使用@Primary注解解决:
@Service@Primarypublic class UserServiceImpl implements UserService {}
当出现多个类型相同的类导致Spring无法选择时,如果某个类标注了@Primary,Spring将优先将该组件注册到IOC容器,不过这种方式确实不太优雅。
@Resource注解
刚才的问题其实可以通过换一个注解来解决,我们不妨试试看:
@Componentpublic class User { @Resource private UserService userService;}
@Resource注解是JSR-250定义的注解,它和Spring没有关系,但能够实现和@Autowired注解相同的功能,我们先来介绍一下这两个注解之间的区别:
•@Autowired默认按类型进行注入,若要按名称注入,则需要配合@Qualifier注解一起使用;@Resource既支持类型注入,也支持名称注入,默认为名称注入•@Autowired能够标注在构造器、方法、参数、成员变量、注解上;@Resource只能标注在类、成员变量和方法上•@Autowired是Spring提供的注解,脱离了Spring框架则无法使用;@Resource是JSR-250定义的注解,可以脱离任何框架使用
现在问题就解决了吗?其实并没有,当你运行测试代码时程序仍然会抛出异常,这是因为虽然@Resource默认为名称注入,但是在使用名称找不到组件的情况下,会继续使用类型注入,所以眼熟的异常就又出现了。
我们已经知道,Spring在扫描组件时会将类名且首字母小写作为组件的名称注入到IOC容器中,所以像这样注入就是没有问题的:
@Componentpublic class User { @Resource private UserService userServiceImpl;}
不过一般情况下我们不会这么写,而是像这样:
@Componentpublic class User { @Resource(name = "employeeServiceImpl") private UserService userService;}
通过@Resource注解,我们就解决了@Autowired和@Qualifier两个注解组合才能解决的问题,至于到底用哪个,还是看大家的使用习惯了。
@Value
可能有同学有疑问了,我知道对象类型的属性如何注入了,那基本类型数据如何注入呢?@Value注解能够帮助到你,使用方法如下:
@Componentpublic class User { @Value("zs") private String name; @Value("20") private Integer age;}
不过一般情况下,我们都不会把数据这样写死,都会将其放到配置文件中:
jdbc.url=jdbc:mysql:///testjdbc.driver=com.mysql.jdbc.Driverjdbc.username=rootjdbc.password=root
此时需要借助一个新注解@PropertySource将值注入到指定的组件中:
@Component@PropertySource("classpath:jdbc.properties")public class User { @Value("${jdbc.url}") private String url; @Value("${jdbc.driver}") private String driver; @Value("${jdbc.username}") private String username; @Value("${jdbc.password}") private String password;}
@Value还能够注入操作系统属性:
@Componentpublic class User { @Value("#{systemProperties["os.name"]}") private String name;}
还可以注入表达式计算后的结果:
@Componentpublic class User { @Value("#{ T(java.lang.Math).random() * 100.0 }") private String result;}
本文作者:汪伟俊为Java技术迷专栏作者投稿,未经允许请勿转载
往期推荐1、来自谷歌的开发心得:所有SQL和代码,都没必要藏着掖着
2、用了这么久的Chrome,你不会还没掌握这个功能吧?
3、一个比SpringBoot快44倍的Java框架
4、QQ最近上线的两个新功能,把我人都看傻了!
5、SpringBoot三大开发工具,你都用过么?
点分享
点收藏
点点赞
点在看
相关阅读
-
世界热推荐:今晚7:00直播丨下一个突破...
今晚19:00,Cocos视频号直播马上点击【预约】啦↓↓↓在运营了三年... -
NFT周刊|Magic Eden宣布支持Polygon网...
Block-986在NFT这样的市场,每周都会有相当多项目起起伏伏。在过去... -
环球今亮点!头条观察 | DeFi的兴衰与...
在比特币得到机构关注之后,许多财务专家预测世界将因为加密货币的... -
重新审视合作,体育Crypto的可靠关系才能双赢
Block-987即使在体育Crypto领域,人们的目光仍然集中在FTX上。随着... -
简讯:前端单元测试,更进一步
前端测试@2022如果从2014年Jest的第一个版本发布开始计算,前端开发... -
焦点热讯:刘强东这波操作秀
近日,刘强东发布京东全员信,信中提到:自2023年1月1日起,逐步为...