获取 Bean 对象也叫做对象装配,就是把对象取出来放到某个类中,有时候也叫对象注入。
对象装配(对象注入)的实现方法以下 3 种:
常见有关对象注入的注解有两个,一个是@Autowired
,另外一个是@Resource
它们两者的区别如下:
Spring
提供的,而 @Resource 是JDK
提供的。name
、type
等,而@Autowired只支持设置required
一个参数,用来设置注入 Bean 的时候该 Bean 是否必须存在(true/false)。 required
即可);而 @Resource 不存在误报的问题。属性注入只需要在需要注入对象的属性上加上 @Autowired 或者 @Resource 注解就可以了,这里以 @Autowired 为例。
首先来看第一种情况,待注入的同类对象只有一个,此时我们直接使用 @Autowired 注解就好,不必设置参数,例如我们在UserController
类里面注入UserService
对象。
下面UserService
的结构,先使用 @Service 将 Bean 存放到 Spring 中:
package com.tr.demo.service;
import org.springframework.stereotype.Service;
@Service
public class UserService {
public void sayHi() {
System.out.println("Hello, UserService~");
}
}
属性注入:
package com.tr.demo.controller;
import com.tr.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller
public class UserController {
//属性注入
@Autowired
private UserService userService;
public void sayHi() {
userService.sayHi();
}
}
此时我们就可以在启动类中,使用上下文对象来获取到UserController
对象,通过执行UserController
对象的sayHi
方法来进而调用到注入的UserService
对象中的sayHi
方法了,此时的UserService
对象就不是我们自己new
出来的了。
import com.tr.demo.controller.UserController;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
UserController usercontroller = context.getBean("userController", UserController.class);
usercontroller.sayHi();
}
}
运行结果:
上面说的是同类对象只有一个的情况,而如果存在多个同类对象,我们就得通过参数来告知容器我们要注入哪一个对象,不告知就会报错。
比如我们将多个User
对象添加到容器中,如下:
package com.tr.demo.model;
// User 结构
public class User {
private int id;
private String name;
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package com.tr.demo.model;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
@Component
public class UserBeans {
// 使用方法注解添加多个 User 对象到容器中
@Bean("user1")
public User user1(){
User user = new User();
user.setId(1);
user.setName("张三");
return user;
}
@Bean("user2")
public User user2(){
User user = new User();
user.setId(2);
user.setName("李四");
return user;
}
@Bean("user3")
public User user3(){
User user = new User();
user.setId(3);
user.setName("王五");
return user;
}
}
而在UserController2
类中需要注入User
对象,此时我们运行程序就会报错:
package com.tr.demo.controller;
import com.tr.demo.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller
public class UserController2 {
@Autowired
private User user;
public void sayHi() {
System.out.println("Hello, " + user);
}
}
我们试着以同样的方法来调用sayHi
方法:
@Autowired 依赖注入流程首先是先根据类型从容器中获取对象,如果只能获取到一个,那么就直接将此对象注入到当前属性上;如果能获取到多个对象,此时会使用 BeanName
进行匹配,而我们添加到 Spring 中的对象是没有一个叫user
的,所以程序就报错了。
此时就需要我们来告知容器我们需要哪一个具体的 Bean,要获得目标对象主要有下面三种方法:
BeanName
就可以了,后面的构造方法与 Setter 注入同理,将形参名设置成与BeanName
相同即可。value
参数为BeanName
即可,要注意 @Qualifier 注解不能修饰方法,只能修饰变量。name
参数值设置为BeanName
即可。 在构造方法加上 @Autowired 注解就可,要注意 @Resource 注解是不支持构造方法注入的,我们就直接演示如何获取取多个同类对象中的其中一个了,还是用上面添加到容器中的多个 User 对象。
方法1:将构造方法形参名设置为user1
package com.tr.demo.controller;
import com.tr.demo.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller
public class UserController3 {
private User user;
@Autowired
public UserController3(User user1) {
this.user = user1;
}
public void sayHi() {
System.out.println("Hello, " + user);
}
}
方法2:@Autowired 搭配 @Qualifier
package com.tr.demo.controller;
import com.tr.demo.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
@Controller
public class UserController4 {
private User user;
@Autowired
public UserController4(@Qualifier(value = "user2") User user) {
this.user = user;
}
public void sayHi() {
System.out.println("Hello, " + user);
}
}
运行结果:
对了,如果一个类中只有一个构造方法,@Autowired 是可以省略的,演示一下:
package com.tr.demo.controller;
import com.tr.demo.model.User;
import org.springframework.stereotype.Controller;
@Controller
public class UserController5 {
private User user;
public UserController5(User user3) {
this.user = user3;
}
public void sayHi() {
System.out.println("Hello, " + user);
}
}
此时仍然是可以成功注入对象。
如果有多个构造方法,要注意此时是不能省略 @Autowired 的,会导致会注入对象失败。
package com.tr.demo.controller;
import com.tr.demo.model.User;
import org.springframework.stereotype.Controller;
@Controller
public class UserController6 {
private User user;
public UserController6(User user1) {
this.user = user1;
}
public UserController6() {
System.out.println("调用无参构造");
}
public void sayHi() {
System.out.println("Hello, " + user);
}
}
此时可以看到注入对象失败了,输出的结果是null
。
当然此时加上 @Autowired 注解就能正常注入了,就不做展示了。
Setter 注入就是在 setXXX 系列方法上加上 @Resource 或者 @Autowired 进行注入,和构造方法注入大同小异,简单演示一下。
package com.tr.demo.controller;
import com.tr.demo.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
@Controller
public class UserController7 {
private User user;
@Autowired
public void setUser(@Qualifier(value = "user2") User user) {
this.user = user;
}
public void sayHi() {
System.out.println("Hello, " + user);
}
}
启动类和运行结果:
这里这里第一行输入的是因为启动程序会将上面写的UserController6
也添加到容器中,UserController6
的无参构造方法是我们自定义的。
在早期的 Spring 版本中,官方推荐使用的 Setter 注入,最开始说的原因就是不符合单一设计原则吧,而现在比较新的 Spring 版本(Sring 4.x 之后)中,官方最使用推荐的又是构造方法注入了,说法是因为它的通用性最好。
属性注入
优点:
缺点:
final
修饰的变量,因为 final 修饰的变量只有两种赋值方式,一是直接赋值,二是通过构造方法进行赋值,而属性注入这两种方式都不能满足。Setter 注入
优点:
缺点:
构造方法注入
优点:
缺点:
在 Spring 项⽬中,通过 main ⽅法获取到 Controller 类,调⽤ Controller ⾥⾯通过注⼊的⽅式调⽤ Service 类,Service 再通过注⼊的⽅式获取到 Repository 类,Repository 类⾥⾯有⼀个⽅法构建⼀ 个 User 对象,返回给 main ⽅法。Repository ⽆需连接数据库,使⽤伪代码即可。
首先要清楚的是在 main 方法中是不能使用依赖注入的,因为类的静态部分是在 Spring 注入之前的加载的,仔细想一下,在类加载时就要使用一个还没注入的对象这是不现实的。
所以我们要在 main 中执行的是将扫描路径中的类添加到 Spring 中,对象的注入要在 mian 方法所在类的外部去实现。
package com.tr.demo.model;
public class User {
private int id;
private String name;
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package com.tr.demo.repository;
import com.tr.demo.model.User;
import org.springframework.stereotype.Repository;
@Repository
public class UserRepository {
public User getUser(){
// 伪代码
User user = new User();
user.setId(1);
user.setName("张三");
return user;
}
}
package com.tr.demo.service;
import com.tr.demo.model.User;
import com.tr.demo.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public User getUser(){
return userRepository.getUser();
}
}
package com.tr.demo.contoller;
import com.tr.demo.model.User;
import com.tr.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller
public class UserController {
@Autowired
private UserService userService;
public User getUser(){
return userService.getUser();
}
}
package com.tr.demo;
import com.tr.demo.contoller.UserController;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* 启动类
*/
public class App {
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
UserController userController =
context.getBean("userController", UserController.class);
System.out.println(userController.getUser());
}
}
运行结果: