从Java5.0开始,Java开始支持注解。Spring做为Java生态中的领军框架,从2.5版本后也开始支持注解。相比起之前使用xml来配置Spring框架,使用注解提供了更多的控制Spring框架的方式。
SpringFramework版本 | 对应jdk版本 | 重要特性 |
---|---|---|
SpringFramework 1.x | jdk 1.3 | 基于 xml 的配置 |
SpringFramework 2.x | jdk 1.4 | 改良 xml 文件、初步支持注解式配置 |
SpringFramework 3.x | Java 5 | 注解式配置、JavaConfig 编程式配置、Environment 抽象 |
SpringFramework 4.x | Java 6 | SpringBoot 1.x、核心容器增强、条件装配、WebMvc 基于 Servlet3.0 |
SpringFramework 5.x | Java 8 | SpringBoot 2.x、响应式编程、SpringWebFlux、支持 Kotlin |
1. 配置扫描路径
添加下面配置语句到配置文件中:
<content:component-scan base-package="org.example"></content:component-scan>
base-package="org.example"
表示注册扫描的包
对Bean对象的查找只会在这个路径以及它的所有子路径中。设计者的目的是为了缩小扫描的范围,节省开销,提高性能。
配置作用:
-
扫描包内及其子包内的所有“类”(不包含接口),并为添加了@Service、@Component、@Controller、@Repository修饰的类创建对象并存入IOC容器
-
@Service、@Component、@Controller、@Repository修饰的类中含有@Autowired修饰的成员变量,则创建对象时会从IOC容器中取值为该成员变量赋值
这里先提一嘴,后续慢慢道来。。
此时的整个配置文件是长这样的:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:content="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<content:component-scan base-package="org.example"></content:component-scan>
</beans>
2. 添加注解存储 Bean 对象
想要将对象存储在Spring中,有两种注解类型可以实现:
五大类注解:@Controller、@Service、@Repository、@Configuration、@Component
以及方法注解:@Bean
通过注解存储bean对象:
@Controller
public class UserController {
public void doController() {
System.out.println("I'm controller.");
}
}
3. 取出Bean
这里我们使用下面这种getBean()方法,和只传String的参数差不多,但是下面这种方式不需要强转类型,可读性更高:
3.1 Bean的默认命名
在使用getBean方法的时候开始犯难了,这个Bean对象的id是什么呢?
在Spring中,以注解形式存储的对象,id默认为类名的小驼峰形式,比如"UserController"
类,它的id就是"userController"
3.2 使用Bean
public class Application {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
UserController userController =
context.getBean("userController", UserController.class);
userController.doController();
}
}
3.3 Bean的特殊命名【3.1补充】
学会使用Bean之后,我们需要给Bean的命名规则进行一个填坑
特殊情况:当我把类命名为"UController"
的时候,再用上面那种方式去找"uController"
就会找不到 Bean:
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
UserController userController =
context.getBean("userController", UserController.class);
userController.doController();
UController uController =
context.getBean("uController", UController.class);
}
查看一下Bean名称命名的源码:
protected String buildDefaultBeanName(BeanDefinition definition) {
String beanClassName = definition.getBeanClassName();
Assert.state(beanClassName != null, "No bean class name set");
String shortClassName = ClassUtils.getShortName(beanClassName);
//发现这里调用了jdk中的方法,传入BeanName的参数:
return Introspector.decapitalize(shortClassName);
}
点进去查看一下decapitalize()
方法:
public static String decapitalize(String name) {
//如果name 为空,直接返回原name
if (name == null || name.length() == 0) {
return name;
}
//如果第一个字母和第二个字母都是大写的情况下,直接返回原name
if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) &&
Character.isUpperCase(name.charAt(0))){
return name;
}
//否则将第一个字符转为小写
char chars[] = name.toCharArray();
chars[0] = Character.toLowerCase(chars[0]);
return new String(chars);
}
看了上面的源码,我们终于豁然开朗,原来当第一个字母和第二个字母都是大写的情况下,Bean的Id并不需要改变:
4. 五大类注解的关系
- @Controller【控制器】:前后端交互的第一层 ,前端的数据一定是先到Controller层的
- @Service【服务】:第二层,负责服务的编排和汇总
- @Repository【仓库】:第三层,负责直接操作数据库
- @Configuration【配置】:项目的所有配置
- @Component【组件】:通用化的工具类
查看下面四种类注解的实现,它们都带着一个@Component的注解 :
结论:@Controller、@Service、@Repository、@Configuration都是基于@Component实现的,它们都作用都是将 Bean 存到 Spring 中
5. 方法注解@Bean
方法注解是用来修饰方法的,它会将该方法所返回的对象存入Spring中
注意事项:
- 方法注解必须配合五大类注解使用,否则在取 Bean 的时候会出现找不到 Bean 对象的问题
- 方法必须是无参的
5.1 方法注解存取对象
创建一个user的model类:
public class User {
private int userId;
private String userName;
private int age;
//记得generate一下getter和setter以及toString方法
//...
}
存储Bean:
//搭配任意类注解
@Component
public class GetUserBean {
@Bean
User user() {
User user = new User();
user.setUserId(1);
user.setUserName("zhangsan");
user.setAge(19);
return user;
}
}
取出Bean:
5.2 @Bean的命名
跟五大类注解不一样,Bean的Id是根据方法名按照前面所提到的命名规则得到的:
为什么Bean根据方法名来命名呢?
因为不同的方法返回相同类型的对象是一件很常见的事情,设计者考虑到这点,因此通过@Bean注解存储的Bean的Id是根据方法名来命名。
5.2.1 常见问题
但是与类名不同,在不同类中,有同样的方法名也不是一件奇怪的事,遇到了这种情况会怎么样呢?
@Component
public class GetUserBean {
@Bean
User user() {
User user = new User();
user.setUserId(1);
user.setUserName("GetUserBean类:zhangsan");
user.setAge(19);
return user;
}
}
@Component
public class GetUserBean_cp {
@Bean
User user() {
User user = new User();
user.setUserId(1);
user.setUserName("GetUserBean_cp类:zhangsan");
user.setAge(19);
return user;
}
}
结果发现并没有报错,结果如下:
但是这种结果并不是我们想要的,既然我们存了两个bean对象,应该在不同场景需要用不同的bean对象,接下来我们将这个问题的解决方案。
5.2.2 解决方案
通过给@Bean注解传参数取别名:
value
和name
参数都是用来修改被标注bean在IOC容器中的id属性。并且取别名后原来的方法名的id就用不了了。
@Component
public class GetUserBean {
@Bean(value = "user_default")
User user() {
//....省略代码
}
}
@Component
public class GetUserBean_cp {
@Bean(value = "user_cp")
User user() {
//....省略代码
}
}
列举所有取别名写法:
- value:
@Bean(value = "user_cp")
- name:
@Bean(name = "user_cp")
- 无参:
@Bean("user_cp")
- 多个id:
@Bean({"user_cp", "student", teacher"})
使用不同的id取Bean:
6. 总结
本章节我们学习了:
- Spring通过注解的方式存储对象,只需要在注解中配置一个扫描路径,简化了在xml文件中配置bean对象的步骤
- 初步了解了五大类注解@Controller、@Service、@Repository、@Configuration、@Component以及一个方法注解@Bean的使用
- 类注解和方法注解命名规范以及区别