目录
NoSuchMethodException 没有找到构造方法
获取 bean 对象也叫做对象装配,是把对象取出来放到某个类中,有时候也叫对象注入。 对象装配(对象注入)的实现方法以下 3 种:
- 属性注入
- Setter 注入
- 构造方法注入
以上三种注入方式,均需要使用 @Autowired 注解并搭配五大类注解,注解一般不能放在启动类中,一般注解放在 web 项目中。
接下来,我们具体来看以上三种实现方法是如何具体实现的。
1. 属性注入
在 UserController 类中注入 userService,直接使用 @Autowired:
@Controller
public class UserController {
@Autowired
private UserService userService;
public void sayHi(){
userService.sayHi();
System.out.println("hi,Controller...");
}
}
@Service
public class UserService {
public void sayHi(){
System.out.println("Hi,Service...");
}
}
public class App {
public static void main(String[] args) {
// 得到 Spring 上下文
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
// 获取 Bean 对象
UserController userController= (UserController) context.getBean("userController");
// 使用 Bean
userController.sayHi();
}
}
是实际开发中使用最多的,但是不被 Spring 官方推荐。
@Autowired 是根据类型进行匹配的,当我们改变对象名称时,依然不影响结果:
那么,当我们类型相同的对象有多个时,如何进行匹配呢?
@Configuration
public class BeanConfig {
@Bean
public Integer age(){
return 15;
}
@Bean
public Users user(Integer age){
Users user = new Users();
user.setName("小明");
user.setAge(age);
return user;
}
public Users user2(){
Users user = new Users();
user.setName("小蓝");
user.setAge(19);
return user;
}
}
此时,Users 对象有两个,分别是 user 和 user2,当我们根据同样的方法进行属性注入时,拿到的是哪个对象呢?
@Controller
public class UserController {
@Autowired
private UserService us;
@Autowired
private Users user;
public void sayHi(){
us.sayHi();
System.out.println(user.getName());
System.out.println("hi,Controller...");
}
}
可以看到我们获取到的是根据名称进行匹配的。
因此我们可以得出结论,属性注入 @AutoWired 和 @Bean 的匹配机制是一样的:当只存在一个对象时,根据对象的类型进行匹配;当存在多个对象时,根据对象的名称进行匹配。
当注入多个属性时,每个属性都需要加上 @Autowired。
2. Setter 注入
/**
* Setter 方法注入
*/
@Controller
public class UserController2 {
private UserService us;
public void setUs(UserService us) {
this.us = us;
}
public void sayHi(){
us.sayHi();
System.out.println("hi,Controller...");
}
}
public class App2 {
public static void main(String[] args) {
// 得到 Spring 上下文
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
/**
* setter 注入
*/
UserController userController= (UserController) context.getBean("userController2");
userController.sayHi();
}
}
直接运行以上代码,可以看到报错:
Exception in thread "main" java.lang.ClassCastException: SpringCoreDemo.springcore.UserController2 cannot be cast to SpringCoreDemo.springcore.UserController
原因是,此时我们使用的是 UserController2 这个类,但是在强制类型转换时使用的是 UserController,因此修改代码:
UserController2 userController2= (UserController2) context.getBean("userController2");
userController2.sayHi();
但是,当我们运行以上代码后,依然有报错:
因为 setUs 方法并未被调用,因此在使用 us.sayHi() 时,出现空指针异常。因此需要将 setUs 方法交给 Spring 去管理。
修改后完整代码如下:
@Controller
public class UserController2 {
private UserService us;
@Autowired
public void setUs(UserService us) {
this.us = us;
}
public void sayHi(){
us.sayHi();
System.out.println("hi,Controller...");
}
}
public class App2 {
public static void main(String[] args) {
// 得到 Spring 上下文
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
/**
* setter 注入
*/
UserController2 userController2= (UserController2) context.getBean("userController2");
userController2.sayHi();
}
}
3. 构造方法注入
直接加入构造方法:
@Controller
public class UserController3 {
private UserService us;
public UserController3(UserService us) {
this.us = us;
}
public void sayHi(){
us.sayHi();
System.out.println("hi,Controller...");
}
}
public class App3 {
public static void main(String[] args) {
// 得到 Spring 上下文
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
/**
* 构造方法注入
*/
UserController3 userController3= (UserController3) context.getBean("userController3");
userController3.sayHi();
}
}
可以看到直接运行是可以成功注入的。 那么。如果我们需要注入多个呢?
@Controller
public class UserController3 {
private UserService us;
private UserConfiguartion userConfiguartion;
public UserController3(UserService us) {
this.us = us;
}
public UserController3(UserService us, UserConfiguartion userConfiguartion) {
this.us = us;
this.userConfiguartion = userConfiguartion;
}
public void sayHi(){
us.sayHi();
System.out.println("hi,Controller...");
}
}
运行以上代码,出现报错:
Caused by: java.lang.NoSuchMethodException: SpringCoreDemo.springcore.UserController3.<init>()
在我们没有写构造函数时,会默认生成一个无参的构造函数,当我们写了有参构造函数时,就不再生成默认的无参构造函数。
此时,有两个有参的构造函数,创建对象需要调用构造函数,可能是我们自己的程序调用也可能是 Spring 通过反射(根据类名可以获取到内部的属性和方法)调用。因此,当出现两个构造函数时,程序不知道该调用哪个构造函数,所以会报错。所以,我们需要通过 @Autowired 注解来告诉 Spring 去调用哪个构造函数。
接下来,我们修改代码如下:
给第一个构造函数添加注解:
@Controller
public class UserController3 {
private UserService us;
private UserConfiguartion userConfiguartion;
@Autowired
public UserController3(UserService us) {
System.out.println("第一个构造函数...");
this.us = us;
}
public UserController3(UserService us, UserConfiguartion userConfiguartion) {
System.out.println("第二个构造函数...");
this.us = us;
this.userConfiguartion = userConfiguartion;
}
public void sayHi(){
us.sayHi();
System.out.println("hi,Controller...");
}
}
给第二个构造函数添加注解:
@Controller
public class UserController3 {
private UserService us;
private UserConfiguartion userConfiguartion;
public UserController3(UserService us) {
System.out.println("第一个构造函数...");
this.us = us;
}
@Autowired
public UserController3(UserService us, UserConfiguartion userConfiguartion) {
System.out.println("第二个构造函数...");
this.us = us;
this.userConfiguartion = userConfiguartion;
}
public void sayHi(){
us.sayHi();
System.out.println("hi,Controller...");
}
}
如果只有一个构造函数,可以省略 @Autowired 注解。
如果有多个构造函数,需要添加 @Autowired 注解来决定使用哪个构造函数。
不能同时对两个构造函数都添加 @Autowired 注解。
4. 三种注入优缺点分析
注入方法 | 优点 | 缺点 |
---|---|---|
属性注入 | 简洁,使用方便 | 1. 只能用于 IoC 容器,只有在使用时才出现 NPE (空指针异常) 2. 不能注入一个 Final 修饰的属性 |
Setter 方法注入 | 方便在类实例之后重新对该对象进行配置或注入 | 1. 不能注入一个 Final 修饰的属性 2. 注入对象可能会被改变,因为 Setter方法可能会被多次调用,有被修改的风险 |
构造函数 | 1. 可以注入 Final 属性 2. 注入的对象不会被修改 3. 依赖对象在使用前一定会被完全初始化,因为依赖实在类的构造方法中执行的,而构造方法是在类加载阶段就会执行的方法 4. 通用性好,构造方法是 JDK 支持的,所以更换任何框架都适用。 | 注入多个对象时,代码比较繁琐 |
Final 修饰的属性需要具备两个条件之一:
1. 初始化赋值
2. 构造函数赋值
当我们使用 @Autowired 注解时,就是已经交给 Spring 去管理了,并不需要再在创建对象时进行赋值。
构造方法注入是 Spring 推荐的方式,但是使用较多的是属性注入。
5. @Resource
@Controller
public class UserController4 {
@Resource
private UserService userService;
public void sayHi(){
userService.sayHi();
System.out.println("hi,Controller...");
}
}
public class App4 {
public static void main(String[] args) {
// 得到 Spring 上下文
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
UserController4 userController4= (UserController4) context.getBean("userController4");
userController4.sayHi();
}
}
可以看到同样能够运行成功。那么 @Autowired 和 @Resource 有什么区别呢?
6. @Autowired 和 @Resource 的区别
- 出身不同:@Autowired 来自于 Spring,@Resource 来自于 JDK 的注解;
- 使用时设置的参数不同:相比于 @Autowired 来说,@Resource 支持更多的参数设置,例如 name 设置,根据名称获取 Bean。
- @Autowired 可用于 Setter 注入、构造函数注入和属性注入,而 @Resource 只能用于 Setter 注入和属性注入,不能用于构造函数注入。
通过 @Resource 设置 name:
@Controller
public class UserController4 {
@Resource
private UserService userService;
@Resource(name = "user")
private Users user1;
public void sayHi(){
userService.sayHi();
System.out.println(user1.getName());
System.out.println("hi,Controller...");
}
}
public class App4 {
public static void main(String[] args) {
// 得到 Spring 上下文
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
UserController4 userController4= (UserController4) context.getBean("userController4");
userController4.sayHi();
}
}
@Aurowired 可以搭配 @Qualifier 使用设置 name,等同于 @Resouce 设置 name。
7. 根据日志定位问题
ClassCastException 类型匹配异常
Exception in thread "main" java.lang.ClassCastException: SpringCoreDemo.springcore.UserController2 cannot be cast to SpringCoreDemo.springcore.UserController
即类型转换出现错误。
NoSuchMethodException 没有找到构造方法
Caused by: java.lang.NoSuchMethodException: SpringCoreDemo.springcore.UserController3.<init>()