Spring 是⼀个包含了众多⼯具⽅法的 IoC 容器。既然是容器那么它就具备两个最基本的功能:
将对象存储到容器(Spring)中; 从容器中将对象取出来。
在 Java 语⾔中对象也叫做 Bean
接下来使⽤ Maven ⽅式来创建⼀个 Spring 项⽬,创建 Spring 项⽬和 Servlet 类似,总共分为以下 3 步:
1. 创建⼀个普通 Maven 项⽬。
2. 引入 Spring 依赖(spring-context、spring-beans)。
3. 添加启动类。
在项⽬的 pom.xml 中添加 Spring 框架的⽀持,xml 配置如下:
org.springframework
spring-context
5.2.3.RELEASE
org.springframework
spring-beans
5.2.3.RELEASE
从上述配置中可以看出,添加的框架有 spring-context:spring 上下⽂,还有 spring-beans:管理对 象的模块。
最后在创建好的项⽬ java ⽂件夹下创建⼀个启动类,包含 main ⽅法即可:
存储 Bean 分为以下 2 步:
1. 存储 Bean 之前,先得有 Bean 才⾏,因此先要创建⼀个 Bean。
2. 将创建的 Bean 注册到 Spring 容器中。 具体实现如下。
所谓的 Bean 就是 Java 语⾔中的⼀个普通对象,实现代码如下:
public class User {
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
在创建好的项⽬中添加 Spring 配置⽂件 spring-config.xml,将此⽂件放到 resources 的根⽬录下, 如下图所示:
Spring 配置⽂件的固定格式为以下内容(以下内容⽆需记忆,只需要保存到⾃⼰可以找到的地⽅就可以 了,因为它是固定不变的):
接下来,再将 User 对象注册到 Spring 中就可以,具体操作是在 中添加如下配置
获取并使⽤ Bean 对象,分为以下 3 步:
1. 得到 Spring 上下⽂对象,因为对象都交给 Spring 管理了,所以获取对象要从 Spring 中获取,那 么就得先得到 Spring 的上下⽂。
2. 通过 Spring 上下⽂,获取某⼀个指定的 Bean 对象。
3. 使⽤ Bean 对象。
(如果取多个 Bean 的话重复以上第 2、3 步骤。)
Spring 上下⽂对象可使⽤ ApplicationContext,实现代码如下:
// 1.得到 Spring 的上下⽂对象,创建的时候需要配置 Spring 配置信息
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
ApplicationContext:Spring的运行环境
除了 ApplicationContext 之外,我们还可以使⽤ BeanFactory 来作为 Spring 的上下⽂,如下代码所 示:
BeanFactory factory = new XmlBeanFactory(new ClassPathResource("spring-config.xml"));
ApplicationContext 和 BeanFactory 效果是⼀样的,ApplicationContext 属于 BeanFactory 的⼦ 类,它们的区别如下。
共同点:都是获取Spring bean
继承关系和功能⽅⾯来说:Spring 容器有两个顶级的接⼝:BeanFactory 和 ApplicationContext。其中 BeanFactory 提供了基础的访问容器的能⼒,⽽ ApplicationContext 属于 BeanFactory 的⼦类,它除了继承了 BeanFactory 的所有功能之外,它还拥有独特的特性, 还添加了对国际化⽀持、资源访问⽀持、以及事件传播等⽅⾯的⽀持。
从性能⽅⾯来说:ApplicationContext 是⼀次性加载并初始化所有的 Bean 对象,⽽ BeanFactory 是需要那个才去加载那个,因此更加轻量。
⽽ ClassPathXmlApplicationContext 属于 ApplicationContext 的⼦类,拥有 ApplicationContext 的所有功能,是通过 xml 的配置来获取所有的 Bean 容器的。
// 1.得到 Spring 上下⽂对象
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
// 2.加载某个 bean
User user = (User) context.getBean("user");
Bean 的 Id 要⼀⼀对应,如下图所示:
getBean() ⽅法有很多种重载⽅法,我们也可以使⽤其他⽅式来获取 Bean 对象,⽐如以下这两种:
1、根据类型获取 Bean:
UserController user = context.getBean(UserController.class);
2、名称 + 类型获取 Bean:
UserController user = context.getBean("user", UserController.class);
⼆者的区别:当有⼀个类型被重复注册到 spring-config.xml 中时,只能使⽤根据名称获取了
不论我们拿多少次,或者使用那种方式取对象,获取的都是同一个对象
public class App {
public static void main(String[] args) {
// 1.得到 Spring 上下⽂对象
ApplicationContext context =new ClassPathXmlApplicationContext("spring-config.xml");
// 2.加载某个 bean
User user = (User) context.getBean("user");
// 3.调⽤相应的⽅法
System.out.println(user.sayHi("Java"));
}
}
1. 操作容器之前,先要有容器,所以先要得到容器。
2. 存对象
a. 创建 Bean(普通类)。
b. 将 Bean 注册(配置)到 spring-confing.xml 中。
3. 取对象
a. 得到 Spring 上下⽂,并读取到 Spring 的配置⽂件。
b. 获取某⼀个 Bean 对象。
c. 使⽤ Bean 对象。
操作流程如下图所示:
在 Spring 中想要更简单的存储和读取对象的核⼼是使⽤注解
之前我们存储 Bean 时,需要在 spring-config 中添加⼀⾏ bean 注册内容才⾏,如下图所示:
⽽现在我们只需要⼀个注解就可以替代之前要写⼀⾏配置的尴尬了,不过在开始存储对象之前,我们先 要来点准备⼯作。
注意:想要将对象成功的存储到 Spring 中,我们需要配置⼀下存储对象的扫描包路径,只有被配置的 包下的所有类,添加了注解才能被正确的识别并保存到 Spring 中。
在 spring-config.xml 添加如下配置:
也就是说,即使添加了注解,如果不是在配置的扫描包下的类对象,也是不能被存储到 Spring 中的。
想要将对象存储在 Spring 中,有两种注解类型可以实现:
1. 类注解:@Controller、@Service、@Repository、@Component、@Configuration。
2. ⽅法注解:@Bean。
使⽤ @Controller 存储 bean 的代码如下所示:
@Controller // 将对象存储到 Spring 中
public class UserController {
public void sayHi(){
System.out.println("hi,userController");
}
}
此时我们先使⽤之前读取对象的⽅式来读取上⾯的 UserController 对象,如下代码所示:
public class App {
public static void main(String[] args) {
// 1.得到 spring 上下⽂
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
// 2.得到 bean
UserController userController = (UserController) context.getBean("userController");
// 3.调⽤ bean ⽅法
userController.sayHi();
}
}
使⽤ @Service 存储 bean 的代码如下所示:
@Service
public class UserService {
public void doService(){
System.out.println("userService");
}
}
读取 bean 的代码:
public class App {
public static void main(String[] args) {
// 1.得到 spring 上下⽂
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
// 2.得到 bean
UserService userService = (UserService)context.getBean("userService");
// 3.调⽤ bean ⽅法
userService.doService();
}
}
使⽤ @Repository 存储 bean 的代码如下所示:
@Repository
public class UserRepository {
public void sayHi(){
System.out.println("hi,userRepository");
}
}
读取 bean 的代码:
public class App {
public static void main(String[] args) {
// 1.得到 spring 上下⽂
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
// 2.得到 bean
UserRepository userRepository= (UserRepository) context.getBean("userRepository");
userRepository.sayHi();
}
}
使⽤ @Component 存储 bean 的代码如下所示:
@Component
public class UserComponent {
public void sayHi(){
System.out.println("hi,userComponent");
}
}
读取 bean 的代码:
public class App {
public static void main(String[] args) {
// 1.得到 spring 上下⽂
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
// 2.得到 bean
UserComponent userComponent= (UserComponent) context.getBean("userComponent");
userComponent.sayHi();
}
}
}
使⽤ @Configuration 存储 bean 的代码如下所示:
@Configuration
public class UserConfiguration {
public void sayHi(){
System.out.println("hi,Configuration");
}
}
读取 bean 的代码:
public class App {
public static void main(String[] args) {
// 1.得到 spring 上下⽂
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
// 2.得到 bean
UserConfiguration userConfiguration= (UserConfiguration) context.getBean("userConfiguration");
userConfiguration.sayHi();
}
}
}
为什么需要怎么多的类注解的原因,就是让程序员看到类注解之后,就能直接了解当前类 的⽤途,⽐如:
@Controller:表示的是业务逻辑层;控制器,通常是指程序的入口,比如参数校验,参数类型转换,前置处理工作...
@Servie:服务层;一般写业务代码,服务编排
@Repository:持久层;仓库,通常是指DB操作相关的代码
@Configuration:配置层;
@Component : 其他对象。
程序的⼯程分层,调⽤流程如下:
查看 @Controller / @Service / @Repository / @Configuration 等注解的源码发现:
其实这些注解⾥⾯都有⼀个注解 @Component,说明它们本身就是属于 @Component 的“⼦类”。
通过上⾯示例,我们可以看出,通常我们 bean 使⽤的都是标准的⼤驼峰命名,⽽读取的时候⾸字⺟⼩ 写就可以获取到 bean 了,如下图所示:
然⽽,当我们⾸字⺟和第⼆个字⺟都是⼤写时,就不能正常读取到 bean 了,如下图所示:
这个时候,我们就要查询 Spring 关于 bean 存储时⽣成的命名规则了。
我们可以在 Idea 中使⽤搜索关键字“beanName”可以看到以下内容:
顺藤摸⽠,我们最后找到了 bean 对象的命名规则的⽅法:
它使⽤的是 JDK Introspector 中的 decapitalize ⽅法,源码如下:
public static String decapitalize(String name) {
if (name == null || name.length() == 0) {
return name;
}
// 如果第⼀个字⺟和第⼆个字⺟都为⼤写的情况,是把 bean 的⾸字⺟也⼤写存储了
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);
}
所以对于上⾯报错的代码,我们只要改为以下代码就可以正常运⾏了:
类注解是添加到某个类上的,⽽⽅法注解是放到某个⽅法上的,如以下代码的实现:
public class Users {
@Bean
public User user1() {
User user = new User();
user.setId(1);
user.setName("Java");
return user;
}
}
然⽽,当我们写完以上代码,尝试获取 bean 对象中的 user1 时却发现,根本获取不到:
public class Application {
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
User user = (User) context.getBean("user1");
System.out.println(user.toString());
}
}
在 Spring 框架的设计中,⽅法注解 @Bean 要配合类注解才能将对象正常的存储到 Spring 容器中,如 下代码所示:
@Component
public class Users {
@Bean
public User user1() {
User user = new User();
user.setId(1);
user.setName("Java");
return user;
}
}
再次执⾏以上代码,运⾏结果如下:
可以通过设置 name 属性给 Bean 对象进⾏重命名操作,如下代码所示:
@Component
public class Users {
@Bean(name = {"u1"})
public User user1() {
User user = new User();
user.setId(1);
user.setName("Java");
return user;
}
}
此时我们使⽤ u1 就可以获取到 User 对象了,如下代码所示:
class App {
public static void main(String[] args) {
// 1.得到 spring 上下⽂
ApplicationContext context =new ClassPathXmlApplicationContext("spring-config.xml");
// 2.得到某个 bean
User user = (User) context.getBean("u1");
// 3.调⽤ bean ⽅法
System.out.println(user);
}
}
这个重命名的 name 其实是⼀个数组,⼀个 bean 可以有多个名字:
@Bean(name = {"u1", "us1"})
public User user1() {
User user = new User();
user.setId(1);
user.setName("Java");
return user;
}
并且 name={} 可以省略,如下代码所示:
@Bean({"u1", "us1"})
public User user1() {
User user = new User();
user.setId(1);
user.setName("Java");
return user;
}
获取 bean 对象也叫做对象装配,是把对象取出来放到某个类中,有时候也叫对象注⼊。
对象装配(对象注⼊)的实现⽅法以下 3 种:
1. 属性注⼊ 2. 构造⽅法注⼊ 3. Setter 注⼊
属性注⼊是使⽤ @Autowired 实现的,将 Service 类注⼊到 Controller 类中。
属性注⼊的核⼼实现如下:
@Controller
public class UserController {
@Autowired
private UserService userService;
public void sayHi(){
userService.doService();
System.out.println("hi,userController");
}
}
构造⽅法注⼊是在类的构造⽅法中实现注⼊,如下代码所示:
@Controller
public class UserController3 {
private UserService us;
private UserConfiguration userConfiguration;
@Autowired
public UserController3(UserService us) {
this.us = us;
}
public UserController3(UserService us, UserConfiguration userConfiguration) {
this.us = us;
this.userConfiguration = userConfiguration;
}
@Autowired
public void setUs(UserService us){
this.us = us;
}
public void sayHi(){
us.doService();
System.out.println("hi,userController3");
}
}
注意事项:如果只有⼀个构造⽅法,那么 @Autowired 注解可以省略
Setter 注⼊和属性的 Setter ⽅法实现类似,只不过在设置 set ⽅法的时候需要加上 @Autowired 注 解,如下代码所示:
@Controller
public class UserController2 {
private UserService us;
@Autowired
public void setUs(UserService us){
this.us = us;
}
public void sayHi(){
us.doService();
System.out.println("hi,userController");
}
}
属性注⼊:优点:是简洁,使⽤⽅便;缺点:是只能⽤于 IoC 容器,如果是⾮ IoC 容器不可⽤,并且只有在使⽤的时候才会出现 NPE(空指针异常).不能注入一个final修饰的属性。
构造⽅法注⼊是 Spring 推荐的注⼊⽅式,它的缺点是如果有多个注⼊会显得⽐较臃肿,但出现这 种情况你应该考虑⼀下当前类是否符合程序的单⼀职责的设计模式了,它的优点是通⽤性,在使⽤ 之前⼀定能把保证注⼊的类不为空;
Setter ⽅式是 Spring 前期版本推荐的注⼊⽅式,但通⽤性不如构造⽅法,所有 Spring 现版本已 经推荐使⽤构造⽅法注⼊的⽅式来进⾏类注⼊了。优点:方便在类实例之后,重新对该对象进行配置或注入。缺点:不能注入一个final修饰的属性。注入对象可能会被改变,因为setter方法可能会被多次调用,就有被修改的方法。
在进⾏类注⼊时,除了可以使⽤ @Autowired 关键字之外,我们还可以使⽤ @Resource 进⾏注⼊,如 下代码所示
@Controller
public class UserController4 {
@Resource
private UserService userService;
public void sayHi(){
userService.doService();
System.out.println("hi,userController4");
}
}
@Autowired 和 @Resource 的区别
出身不同:@Autowired 来⾃于 Spring,⽽ @Resource 来⾃于 JDK 的注解;
使⽤时设置的参数不同:相⽐于 @Autowired 来说,@Resource ⽀持更多的参数设置,例如 name 设置,根据名称获取 Bean。
@Autowired 可⽤于 Setter 注⼊、构造函数注⼊和属性注⼊,⽽ @Resource 只能⽤于 Setter 注 ⼊和属性注⼊,不能⽤于构造函数注⼊。
当出现多个 Bean,返回同⼀对象类型时程序会报错
@Component
public class Users {
@Bean
public User user1() {
User user = new User();
user.setId(1);
user.setName("Java");
return user;
}
@Bean
public User user2() {
User user = new User();
user.setId(2);
user.setName("MySQL");
return user;
}
}
在另⼀个类中获取 User 对象,如下代码如下:
@Controller
public class UserController4 {
// 注⼊
@Resource
private User user;
public User getUser() {
return user;
}
}
以上程序的执⾏结果如下:
报错的原因是,⾮唯⼀的 Bean 对象。
同⼀类型多个 Bean 报错处理
解决同⼀个类型,多个 bean 的解决⽅案有以下两个:
① 使⽤ @Resource(name="XXX")
@Controller
class UserController4 {
// 注⼊
@Resource(name = "user1")
private User user;
public User getUser() {
return user;
}
}
② 使⽤ @Qualifier
@Controller
public class UserController5 {
// 注⼊
@Autowired
@Qualifier(value = "user2")
private User user;
public User getUser() {
return user;
}
}
1. 将对象存储到 Spring 中:
a. 使⽤类注解:@Controller、@Service、@Repository、@Configuration、@Component【它 们之间的关系】
b. 使⽤⽅法注解:@Bean
【注意事项:必须配合类注解⼀起使⽤】
2. Bean 的命名规则:⾸字⺟和第⼆个字⺟都⾮⼤写,⾸字⺟⼩写来获取 Bean,如果⾸字⺟和第⼆个 字⺟都是⼤写,那么直接使⽤原 Bean 名来获取 Bean。
3. 从 Spring 中获取对象:
a. 属性注⼊
b. Setter 注⼊
c. 构造函数注⼊(推荐)
4. 注⼊的关键字有:
a. @Autowired
b. @Resource
5. @Autowired 和 @Resource 区别:
出身不同;使⽤时设置参数不同 @Resource ⽀持更多的参数,⽐如 name。
6. 解决同⼀类型多个 Bean 的报错:
a. 使⽤ @Resource(name="")
b. 使⽤ @Qualifier("")