目录
1.前言
上一篇文章中《Spring 控制反转(IOC)及依赖注入(DI)》https://blog.csdn.net/luofen521/article/details/120273369介绍了Spring 中IOC及DI的一些概念,并主要分析了XML形式setter方式注入的源码,这篇文章主要介绍使用@Autowired注解字段注入的方式来实现依赖注入的源码。
2.使用注解实现依赖注入
首先我们定义一个StudentDao,用于模拟操作数据库的操作接口,定义了getStudent方法,如下:
public interface StudentDao {
Student getStudent();
}
然后我们创建一个实现类StudentDaoImpl,在getStudent方法模拟了从数据库获取数据的操作,并在接口的定义加上@Repository注解,如下:
@Repository
public class StudentDaoImpl implements StudentDao {
@Override
public Student getStudent() {
Student student = new Student();
student.setId(24);
student.setName("zhangsan");
return student;
}
}
Dao层好了,我们现在来定义Service层
首先,创建一个SudentService接口,并定义了getStudent方法,如下:
public interface StudentService {
Student getStudent();
}
然后创建一个实现类StudentServiceImpl,在类的定义上加上@Service注解,并引入了StudentDao,用于调用Dao层的方法,在定义StudentDao时加上了@Resource注解,代码如下:
@Service
public class StudentServiceImpl implements StudentService{
@Resource
StudentDao studentDao;
@Override
public Student getStudent() {
return studentDao.getStudent();
}
}
到此,Service层也定义好了,我们在测试单元中去验证下,首先也是定义了StudentService ,并在定义上面加上了@Resource注解,然后调用其getStudent方法,代码如下:
@SpringBootTest
class ApplicationTests {
@Resource
StudentService studentService;
@Test
void contextLoads() {
Student student = studentService.getStudent();
System.out.println("学生id:" + student.getId());
System.out.println("学生姓名:" + student.getName());
}
}
运行结果如下:
可以看到,我们没有手动地去创建Servcie、Dao层对应类的对象,而是通过@Repository、@Service、@Resource等注解完成了Bean的创建和关联。
接下来我们看看这些注解的含义
3. 依赖注入相关的注解
3.1 为什么会需要使用这些注解?
在Spring早期的版本,是使用配置文件来管理bean的定义,当项目越来越复杂时,配置文件的管理成了一个很艰巨的任务,如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="book" class="Book" scope="singleton">
<property name="bookName" value="平凡的世界"/>
<property name="author" value="路遥"/>
</bean>
<bean id="book2" class="Book2" scope="prototype">
<property name="bookName" value="平凡的世界"/>
<property name="author" value="路遥"/>
</bean>
<bean id="bookService" class="BookService">
<property name="book" ref="book" />
</bean>
</beans>
所以在Spring后面的版本,逐渐引入了注解来标识哪些类需要由Spring容器来管理,这样做主要是为了管理方便。
看下面这样,是不是要简单得多?
@Service
public class StudentServiceImpl implements StudentService {
}
3.2 相关的注解
3.2.1 类的定义注解
其实除了上述用到的Service、Repository注解,还有@Controller、@Component也是常用的注解,它们用于定义类时标识类对象的创建有Spring来统一管理,我们先来看看他们的定义
@Controller
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
/**
* The value may indicate a suggestion for a logical component name,
* to be turned into a Spring bean in case of an autodetected component.
* @return the suggested component name, if any (or empty String otherwise)
*/
@AliasFor(annotation = Component.class)
String value() default "";
}
@Service
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
/**
* The value may indicate a suggestion for a logical component name,
* to be turned into a Spring bean in case of an autodetected component.
* @return the suggested component name, if any (or empty String otherwise)
*/
@AliasFor(annotation = Component.class)
String value() default "";
}
@Repository
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {
/**
* The value may indicate a suggestion for a logical component name,
* to be turned into a Spring bean in case of an autodetected component.
* @return the suggested component name, if any (or empty String otherwise)
*/
@AliasFor(annotation = Component.class)
String value() default "";
}
会发现@Controller、@Service、@Repository除了名字不同,实现一模一样,并且定义上都使用了 @Component注解。
所以可以这么理解,@Controller、@Service、@Repository =( @Component + 特定的功能),但他们在依赖注入这方面的功能是一样的。
@RestController
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
@AliasFor(
annotation = Controller.class
)
String value() default "";
}
而RestController结合了@Controller和@ReponseBody,不需要再在方法的返回上加@ReponseBody,下面是@Controller和@RestController的对比
@Controller
@Controller
@RequestMapping("books")
public class SimpleBookController {
@GetMapping("/{id}", produces = "application/json")
public @ResponseBody Book getBook(@PathVariable int id) {
return findBookById(id);
}
private Book findBookById(int id) {
// ...
}
}
@RestController
@RestController
@RequestMapping("books-rest")
public class SimpleBookRestController {
@GetMapping("/{id}", produces = "application/json")
public Book getBook(@PathVariable int id) {
return findBookById(id);
}
private Book findBookById(int id) {
// ...
}
}
总结如下:
序号 | 注解 | 含义 |
1 | @Component | 通用的注解,可以定义Spring管理的任务组件 |
2 | @Controller | 用于表现层(spring-mvc中使用),具有转发、重定向等特性 |
3 | @Service | 用于业务逻辑层 |
4 | @Repository | 用于持久层,并自动处理数据库操作产生的异常 |
5 | @RestControler | 用于表现层,集合了@Controller和@ReponseBody的特性 |
上述主要是类定义时,用到的注解,定义好之后,我们还需要在使用的地方使用引用的注解。
3.2.2 注入相关的注解
上述用到了@Resource注解来把我们需要的对象进行注入,其实常用的还有@Autowired
在上述的代码中,我们使用@Autowired来代替@Resource也是可以的,代码如下:
@Service
public class StudentServiceImpl implements StudentService{
@Autowired
StudentDao studentDao;
@Override
public Student getStudent() {
return studentDao.getStudent();
}
}
那@Resouce和@Autowired有什么区别呢?
首先,我们看看他们的定义,可以看到Resource是java自带的属性;而Autowired是springframework提供的注解
package javax.annotation;
public @interface Resource {
package org.springframework.beans.factory.annotation;
public @interface Autowired {
其次,@Autowired是根据类型来装配依赖对象,但是如果我们同一个类型有多个对象会怎么样?比如我们把上面的StudentDaoImpl类再复制一个放到新建的dao文件夹下,这时候就会有有2个类型为StudentDaoImpl的类,这时候我们运行程序就会报下面的错误:
Caused by: org.springframework.beans.factory.BeanDefinitionStoreException:
Failed to parse configuration class [com.yc.springboot.Application]; nested exception is org.springframework.context.annotation.ConflictingBeanDefinitionException:
Annotation-specified bean name 'studentDaoImpl' for bean class [com.yc.springboot.dao.StudentDaoImpl]
conflicts with existing, non-compatible bean definition of same name and class [com.yc.springboot.StudentDaoImpl]
... 63 more
可以看到,在装配StudentDao时,发现有两个相同类型的实现类,所以报错了,这个时候就需要用到@Qualifier注解。首先,我们在dao下的StudentDaoImpl中写value值“StudentDaoImpl2 ”,并设置name为“zhangsan 2”,代码如下:
package com.yc.springboot.dao;
import com.yc.springboot.Student;
import com.yc.springboot.StudentDao;
import org.springframework.stereotype.Repository;
@Repository("StudentDaoImpl2")
public class StudentDaoImpl implements StudentDao {
@Override
public Student getStudent() {
Student student = new Student();
student.setId(24);
student.setName("zhangsan 2");
return student;
}
}
然后在StudentServiceImpl中引入时,用@Qualifier("StudentDaoImpl2")来指定我们希望装配的对象,如下:
@Service
public class StudentServiceImpl implements StudentService{
@Autowired
@Qualifier("StudentDaoImpl2")
StudentDao studentDao;
@Override
public Student getStudent() {
return studentDao.getStudent();
}
}
运行结果如下,可以看到,这个时候打印出来的名字为“zhangsan 2”&